first commit

main
Ricardo Gomes 2025-12-12 09:29:15 +00:00
parent 55bd58a761
commit 3390923c66
7 changed files with 530 additions and 290 deletions

View File

@ -1,35 +0,0 @@
import { Tabs } from 'expo-router';
import React from 'react';
import { HapticTab } from '@/components/haptic-tab';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Colors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';
export default function TabLayout() {
const colorScheme = useColorScheme();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
headerShown: false,
tabBarButton: HapticTab,
}}>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
}}
/>
<Tabs.Screen
name="explore"
options={{
title: 'Explore',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
}}
/>
</Tabs>
);
}

View File

@ -1,112 +0,0 @@
import { Image } from 'expo-image';
import { Platform, StyleSheet } from 'react-native';
import { Collapsible } from '@/components/ui/collapsible';
import { ExternalLink } from '@/components/external-link';
import ParallaxScrollView from '@/components/parallax-scroll-view';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Fonts } from '@/constants/theme';
export default function TabTwoScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
headerImage={
<IconSymbol
size={310}
color="#808080"
name="chevron.left.forwardslash.chevron.right"
style={styles.headerImage}
/>
}>
<ThemedView style={styles.titleContainer}>
<ThemedText
type="title"
style={{
fontFamily: Fonts.rounded,
}}>
Explore
</ThemedText>
</ThemedView>
<ThemedText>This app includes example code to help you get started.</ThemedText>
<Collapsible title="File-based routing">
<ThemedText>
This app has two screens:{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
</ThemedText>
<ThemedText>
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
sets up the tab navigator.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/router/introduction">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Android, iOS, and web support">
<ThemedText>
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
</ThemedText>
</Collapsible>
<Collapsible title="Images">
<ThemedText>
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
different screen densities
</ThemedText>
<Image
source={require('@/assets/images/react-logo.png')}
style={{ width: 100, height: 100, alignSelf: 'center' }}
/>
<ExternalLink href="https://reactnative.dev/docs/images">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Light and dark mode components">
<ThemedText>
This template has light and dark mode support. The{' '}
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
what the user&apos;s current color scheme is, and so you can adjust UI colors accordingly.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Animations">
<ThemedText>
This template includes an example of an animated component. The{' '}
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
the powerful{' '}
<ThemedText type="defaultSemiBold" style={{ fontFamily: Fonts.mono }}>
react-native-reanimated
</ThemedText>{' '}
library to create a waving hand animation.
</ThemedText>
{Platform.select({
ios: (
<ThemedText>
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
component provides a parallax effect for the header image.
</ThemedText>
),
})}
</Collapsible>
</ParallaxScrollView>
);
}
const styles = StyleSheet.create({
headerImage: {
color: '#808080',
bottom: -90,
left: -35,
position: 'absolute',
},
titleContainer: {
flexDirection: 'row',
gap: 8,
},
});

View File

@ -1,98 +0,0 @@
import { Image } from 'expo-image';
import { Platform, StyleSheet } from 'react-native';
import { HelloWave } from '@/components/hello-wave';
import ParallaxScrollView from '@/components/parallax-scroll-view';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { Link } from 'expo-router';
export default function HomeScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
headerImage={
<Image
source={require('@/assets/images/partial-react-logo.png')}
style={styles.reactLogo}
/>
}>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Welcome!</ThemedText>
<HelloWave />
</ThemedView>
<ThemedView style={styles.stepContainer}>
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
<ThemedText>
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
Press{' '}
<ThemedText type="defaultSemiBold">
{Platform.select({
ios: 'cmd + d',
android: 'cmd + m',
web: 'F12',
})}
</ThemedText>{' '}
to open developer tools.
</ThemedText>
</ThemedView>
<ThemedView style={styles.stepContainer}>
<Link href="/modal">
<Link.Trigger>
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
</Link.Trigger>
<Link.Preview />
<Link.Menu>
<Link.MenuAction title="Action" icon="cube" onPress={() => alert('Action pressed')} />
<Link.MenuAction
title="Share"
icon="square.and.arrow.up"
onPress={() => alert('Share pressed')}
/>
<Link.Menu title="More" icon="ellipsis">
<Link.MenuAction
title="Delete"
icon="trash"
destructive
onPress={() => alert('Delete pressed')}
/>
</Link.Menu>
</Link.Menu>
</Link>
<ThemedText>
{`Tap the Explore tab to learn more about what's included in this starter app.`}
</ThemedText>
</ThemedView>
<ThemedView style={styles.stepContainer}>
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
<ThemedText>
{`When you're ready, run `}
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
<ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
</ThemedText>
</ThemedView>
</ParallaxScrollView>
);
}
const styles = StyleSheet.create({
titleContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
stepContainer: {
gap: 8,
marginBottom: 8,
},
reactLogo: {
height: 178,
width: 290,
bottom: 0,
left: 0,
position: 'absolute',
},
});

View File

@ -1,24 +1,14 @@
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
// app/_layout.tsx
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import 'react-native-reanimated';
import { useColorScheme } from '@/hooks/use-color-scheme';
export const unstable_settings = {
anchor: '(tabs)',
};
export default function RootLayout() {
const colorScheme = useColorScheme();
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
<>
<StatusBar style="dark" />
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" />
</Stack>
<StatusBar style="auto" />
</ThemeProvider>
</>
);
}

215
app/index.tsx Normal file
View File

@ -0,0 +1,215 @@
// app/index.tsx - SUA TELA DE LOGIN
// Adicione esta linha com os outros imports:
import { Link } from 'expo-router';
import React, { useState } from 'react';
import {
View,
TextInput,
TouchableOpacity,
Text,
StyleSheet,
KeyboardAvoidingView,
Platform,
Alert,
Image,
ActivityIndicator
} from 'react-native';
export default function LoginScreen() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const handleLogin = () => {
if (!email || !password) {
Alert.alert('Atenção', 'Por favor, preencha todos os campos');
return;
}
if (!email.includes('@')) {
Alert.alert('Email inválido', 'Por favor, insira um email válido');
return;
}
setLoading(true);
// SIMULAÇÃO DE LOGIN (depois troca por API real)
setTimeout(() => {
setLoading(false);
Alert.alert(
'Login realizado!',
`Bem-vindo(a), ${email.split('@')[0]}!`,
[{ text: 'OK', onPress: () => console.log('Login OK') }]
);
// Aqui você navegaria para o app principal: router.replace('/(tabs)');
}, 1500);
};
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.container}
>
<View style={styles.content}>
{/* LOGO/TÍTULO */}
<View style={styles.header}>
<Text style={styles.title}>📱 Estágios+</Text>
<Text style={styles.subtitle}>Escola Profissional de Vila do Conde</Text>
</View>
{/* FORMULÁRIO */}
<View style={styles.form}>
<Text style={styles.label}>Email</Text>
<TextInput
style={styles.input}
placeholder="Insira o seu email"
placeholderTextColor="#999"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
editable={!loading}
/>
<Text style={styles.label}>Palavra-passe</Text>
<TextInput
style={styles.input}
placeholder="Insira a sua palavra-passe"
placeholderTextColor="#999"
value={password}
onChangeText={setPassword}
secureTextEntry
editable={!loading}
/>
{/* BOTÃO ENTRAR */}
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleLogin}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}>ENTRAR</Text>
)}
</TouchableOpacity>
{/* LINK ESQUECI SENHA */}
<TouchableOpacity style={styles.forgotLink}>
<Text style={styles.forgotText}>Esqueceu-se da palavra-passe?</Text>
</TouchableOpacity>
</View>
{/* CADASTRO */}
<View style={styles.footer}>
<Text style={styles.footerText}>Não tem uma conta?</Text>
<Link href="/register" asChild>
<TouchableOpacity>
<Text style={styles.registerText}> Crie uma conta agora</Text>
</TouchableOpacity>
</Link>
</View>
</View>
</KeyboardAvoidingView>
);
}
// ESTILOS
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8f9fa',
},
content: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: 24,
},
header: {
alignItems: 'center',
marginBottom: 48,
},
title: {
fontSize: 32,
fontWeight: '800',
color: '#2d3436',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: '#636e72',
textAlign: 'center',
},
form: {
backgroundColor: '#fff',
borderRadius: 20,
padding: 24,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 12,
elevation: 5,
},
label: {
fontSize: 14,
fontWeight: '600',
color: '#2d3436',
marginBottom: 8,
marginLeft: 4,
},
input: {
backgroundColor: '#f8f9fa',
borderRadius: 12,
paddingHorizontal: 16,
paddingVertical: 14,
fontSize: 16,
marginBottom: 20,
borderWidth: 1,
borderColor: '#dfe6e9',
color: '#2d3436',
},
button: {
backgroundColor: '#0984e3',
borderRadius: 12,
paddingVertical: 16,
alignItems: 'center',
marginTop: 8,
marginBottom: 24,
},
buttonDisabled: {
backgroundColor: '#74b9ff',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '700',
},
forgotLink: {
alignItems: 'center',
},
forgotText: {
color: '#0984e3',
fontSize: 15,
fontWeight: '500',
},
footer: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: 40,
paddingTop: 24,
borderTopWidth: 1,
borderTopColor: '#dfe6e9',
},
footerText: {
color: '#636e72',
fontSize: 15,
},
registerText: {
color: '#0984e3',
fontSize: 15,
fontWeight: '700',
},
});

View File

@ -1,29 +0,0 @@
import { Link } from 'expo-router';
import { StyleSheet } from 'react-native';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
export default function ModalScreen() {
return (
<ThemedView style={styles.container}>
<ThemedText type="title">This is a modal</ThemedText>
<Link href="/" dismissTo style={styles.link}>
<ThemedText type="link">Go to home screen</ThemedText>
</Link>
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
link: {
marginTop: 15,
paddingVertical: 15,
},
});

309
app/register.tsx Normal file
View File

@ -0,0 +1,309 @@
// app/register.tsx - TELA DE CRIAR CONTA (CORRIGIDA)
import React, { useState } from 'react';
import {
View,
TextInput,
TouchableOpacity,
Text,
StyleSheet,
ScrollView,
Alert,
ActivityIndicator,
KeyboardAvoidingView,
Platform
} from 'react-native';
import { Link } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function RegisterScreen() {
const [form, setForm] = useState({
nome: '',
email: '',
telefone: '',
password: '',
confirmarPassword: ''
});
const [loading, setLoading] = useState(false);
const handleChange = (field: string, value: string) => {
setForm(prev => ({ ...prev, [field]: value }));
};
const handleRegister = () => {
if (!form.nome.trim()) {
Alert.alert('Erro', 'Por favor, insira seu nome');
return;
}
if (!form.email.includes('@')) {
Alert.alert('Erro', 'Por favor, insira um email válido');
return;
}
if (form.password.length < 6) {
Alert.alert('Erro', 'A senha deve ter pelo menos 6 caracteres');
return;
}
if (form.password !== form.confirmarPassword) {
Alert.alert('Erro', 'As senhas não coincidem');
return;
}
setLoading(true);
setTimeout(() => {
setLoading(false);
Alert.alert(
'Sucesso!',
`Conta criada para ${form.nome}`,
[{ text: 'OK' }]
);
setForm({
nome: '',
email: '',
telefone: '',
password: '',
confirmarPassword: ''
});
}, 1500);
};
return (
<SafeAreaView style={styles.safeArea}>
<StatusBar style="dark" />
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.container}
>
<ScrollView
contentContainerStyle={styles.scrollContainer}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* BOTÃO VOLTAR NO TOPO (AGORA SEM BUG DO NOTCH) */}
<Link href="/" asChild>
<TouchableOpacity style={styles.backHeaderButton} disabled={loading}>
<Text style={styles.backHeaderText}></Text>
</TouchableOpacity>
</Link>
{/* CABEÇALHO */}
<View style={styles.header}>
<Text style={styles.title}>Criar Nova Conta</Text>
<Text style={styles.subtitle}>
Preencha os dados abaixo para se registar
</Text>
</View>
{/* FORMULÁRIO */}
<View style={styles.formCard}>
{/* NOME */}
<View style={styles.inputGroup}>
<Text style={styles.label}>Nome Completo</Text>
<TextInput
style={styles.input}
placeholder="Digite seu nome completo"
value={form.nome}
onChangeText={(text) => handleChange('nome', text)}
editable={!loading}
/>
</View>
{/* EMAIL */}
<View style={styles.inputGroup}>
<Text style={styles.label}>Email</Text>
<TextInput
style={styles.input}
placeholder="exemplo@email.com"
value={form.email}
onChangeText={(text) => handleChange('email', text)}
keyboardType="email-address"
autoCapitalize="none"
editable={!loading}
/>
</View>
{/* TELEFONE */}
<View style={styles.inputGroup}>
<Text style={styles.label}>Telefone</Text>
<TextInput
style={styles.input}
placeholder="912 345 678"
value={form.telefone}
onChangeText={(text) => handleChange('telefone', text)}
keyboardType="phone-pad"
editable={!loading}
/>
</View>
{/* SENHA */}
<View style={styles.inputGroup}>
<Text style={styles.label}>Senha</Text>
<TextInput
style={styles.input}
placeholder="Mínimo 6 caracteres"
value={form.password}
onChangeText={(text) => handleChange('password', text)}
secureTextEntry
editable={!loading}
/>
</View>
{/* CONFIRMAR SENHA */}
<View style={styles.inputGroup}>
<Text style={styles.label}>Confirmar Senha</Text>
<TextInput
style={styles.input}
placeholder="Digite novamente a senha"
value={form.confirmarPassword}
onChangeText={(text) => handleChange('confirmarPassword', text)}
secureTextEntry
editable={!loading}
/>
</View>
{/* BOTÃO REGISTRAR */}
<TouchableOpacity
style={[styles.registerButton, loading && styles.buttonDisabled]}
onPress={handleRegister}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#FFFFFF" />
) : (
<Text style={styles.registerButtonText}>CRIAR CONTA</Text>
)}
</TouchableOpacity>
{/* AVISO */}
<View style={styles.termsContainer}>
<Text style={styles.termsText}>
Ao criar uma conta, concorda com os nossos Termos de Serviço e Política de Privacidade.
</Text>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
// ESTILOS
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#FFFFFF',
},
container: {
flex: 1,
},
scrollContainer: {
flexGrow: 1,
padding: 20,
paddingTop: 20, // SafeArea já resolve o notch
},
backHeaderButton: {
position: 'absolute',
top: 0,
left: 10,
zIndex: 50,
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#f0f0f0',
justifyContent: 'center',
alignItems: 'center',
},
backHeaderText: {
fontSize: 24,
color: '#007AFF',
fontWeight: 'bold',
},
header: {
alignItems: 'center',
marginTop: 50,
marginBottom: 40,
},
title: {
fontSize: 28,
fontWeight: 'bold',
color: '#1a1a1a',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: '#666',
textAlign: 'center',
},
formCard: {
backgroundColor: '#f8f9fa',
borderRadius: 16,
padding: 24,
marginBottom: 24,
borderWidth: 1,
borderColor: '#e9ecef',
},
inputGroup: {
marginBottom: 20,
},
label: {
fontSize: 14,
fontWeight: '600',
color: '#333',
marginBottom: 6,
marginLeft: 4,
},
input: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
paddingHorizontal: 16,
paddingVertical: 14,
fontSize: 16,
borderWidth: 1,
borderColor: '#ddd',
color: '#333',
},
registerButton: {
backgroundColor: '#007AFF',
borderRadius: 12,
paddingVertical: 16,
alignItems: 'center',
marginTop: 10,
marginBottom: 20,
},
buttonDisabled: {
backgroundColor: '#7bb8ff',
},
registerButtonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: 'bold',
},
termsContainer: {
backgroundColor: '#e8f4ff',
borderRadius: 8,
padding: 12,
borderWidth: 1,
borderColor: '#cce5ff',
},
termsText: {
fontSize: 13,
color: '#0066cc',
textAlign: 'center',
lineHeight: 18,
},
backButton: {
paddingVertical: 14,
alignItems: 'center',
},
backButtonText: {
color: '#007AFF',
fontSize: 16,
fontWeight: '500',
},
});