From 7b526e4aaa76c73fd2b438625e0ff1ac9f271837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Mon, 6 Jul 2026 11:20:51 +0200 Subject: [PATCH] feat: implement auth screens (login, register) and profile screen --- frontend-mobile/app/(tabs)/profile.tsx | 206 ++++++++++++++++++++++++- frontend-mobile/app/auth/login.tsx | 155 +++++++++++++++++++ frontend-mobile/app/auth/register.tsx | 172 +++++++++++++++++++++ 3 files changed, 528 insertions(+), 5 deletions(-) create mode 100644 frontend-mobile/app/auth/login.tsx create mode 100644 frontend-mobile/app/auth/register.tsx diff --git a/frontend-mobile/app/(tabs)/profile.tsx b/frontend-mobile/app/(tabs)/profile.tsx index d9b088f..f471c2e 100644 --- a/frontend-mobile/app/(tabs)/profile.tsx +++ b/frontend-mobile/app/(tabs)/profile.tsx @@ -1,23 +1,219 @@ -import { View, Text, StyleSheet } from 'react-native'; +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native'; +import { useRouter } from 'expo-router'; +import { Ionicons } from '@expo/vector-icons'; +import { useAuth } from '../../hooks/useAuth'; +import { colors, spacing, borderRadius } from '../../constants/theme'; export default function ProfileScreen() { + const router = useRouter(); + const { user, isAuthenticated, isLoading, logout, isAdmin } = useAuth(); + + const handleLogout = async () => { + Alert.alert( + 'Cerrar Sesión', + '¿Estás seguro que deseas cerrar sesión?', + [ + { text: 'Cancelar', style: 'cancel' }, + { + text: 'Cerrar Sesión', + style: 'destructive', + onPress: async () => { + await logout(); + router.replace('/auth/login'); + } + }, + ] + ); + }; + + if (isLoading) { + return ( + + Cargando... + + ); + } + + if (!isAuthenticated) { + return ( + + + + Inicia Sesión + + Inicia sesión para acceder a todas las funcionalidades + + router.push('/auth/login')} + > + Iniciar Sesión + + router.push('/auth/register')} + > + Crear cuenta nueva + + + + ); + } + return ( - Perfil + + + + + {user?.username} + {isAdmin && Administrador} + + + + {isAdmin && ( + + + Panel Admin + + + )} + + + + Favoritos + + + + + + Notificaciones + + + + + + Ayuda + + + + + + + Cerrar Sesión + ); } const styles = StyleSheet.create({ container: { + flex: 1, + backgroundColor: colors.background, + }, + loadingText: { + textAlign: 'center', + marginTop: spacing.xxl, + color: colors.textSecondary, + }, + authPrompt: { flex: 1, justifyContent: 'center', alignItems: 'center', - backgroundColor: '#F2F2F7', + padding: spacing.xl, }, - title: { + authTitle: { fontSize: 24, fontWeight: 'bold', - color: '#1C1C1E', + color: colors.text, + marginTop: spacing.lg, + }, + authSubtitle: { + fontSize: 16, + color: colors.textSecondary, + textAlign: 'center', + marginTop: spacing.sm, + marginBottom: spacing.xl, + }, + header: { + alignItems: 'center', + padding: spacing.xl, + backgroundColor: colors.card, + }, + avatar: { + width: 80, + height: 80, + borderRadius: 40, + backgroundColor: colors.background, + justifyContent: 'center', + alignItems: 'center', + }, + username: { + fontSize: 20, + fontWeight: 'bold', + color: colors.text, + marginTop: spacing.md, + }, + adminBadge: { + fontSize: 12, + color: colors.primary, + backgroundColor: '#E3F2FD', + paddingHorizontal: spacing.sm, + paddingVertical: spacing.xs, + borderRadius: borderRadius.sm, + marginTop: spacing.sm, + }, + menuSection: { + marginTop: spacing.md, + backgroundColor: colors.card, + }, + menuItem: { + flexDirection: 'row', + alignItems: 'center', + padding: spacing.md, + borderBottomWidth: 1, + borderBottomColor: colors.separator, + }, + menuText: { + flex: 1, + marginLeft: spacing.md, + fontSize: 16, + color: colors.text, + }, + button: { + backgroundColor: colors.primary, + borderRadius: borderRadius.md, + paddingVertical: spacing.md, + paddingHorizontal: spacing.xl, + }, + buttonText: { + color: colors.textInverse, + fontSize: 16, + fontWeight: '600', + }, + linkButton: { + marginTop: spacing.md, + }, + linkText: { + color: colors.primary, + fontSize: 14, + }, + logoutButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + margin: spacing.xl, + padding: spacing.md, + backgroundColor: colors.card, + borderRadius: borderRadius.md, + borderWidth: 1, + borderColor: colors.danger, + }, + logoutText: { + marginLeft: spacing.sm, + color: colors.danger, + fontSize: 16, + fontWeight: '500', }, }); diff --git a/frontend-mobile/app/auth/login.tsx b/frontend-mobile/app/auth/login.tsx new file mode 100644 index 0000000..dd24add --- /dev/null +++ b/frontend-mobile/app/auth/login.tsx @@ -0,0 +1,155 @@ +import React, { useState } from 'react'; +import { + View, + Text, + TextInput, + TouchableOpacity, + StyleSheet, + KeyboardAvoidingView, + Platform, + Alert +} from 'react-native'; +import { useRouter } from 'expo-router'; +import { useAuthStore } from '../../store/authStore'; +import { colors, spacing, borderRadius } from '../../constants/theme'; + +export default function LoginScreen() { + const router = useRouter(); + const { login } = useAuthStore(); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const handleLogin = async () => { + if (!username || !password) { + Alert.alert('Error', 'Por favor ingresa usuario y contraseña'); + return; + } + + setIsLoading(true); + try { + await login(username, password); + router.replace('/(tabs)'); + } catch (error) { + Alert.alert('Error', 'Credenciales incorrectas'); + } finally { + setIsLoading(false); + } + }; + + return ( + + + Iniciar Sesión + Ingresa tus credenciales para continuar + + + Usuario + + + + + Contraseña + + + + + + {isLoading ? 'Ingresando...' : 'Iniciar Sesión'} + + + + router.push('/auth/register')} + > + ¿No tienes cuenta? Regístrate + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + form: { + flex: 1, + justifyContent: 'center', + padding: spacing.xl, + }, + title: { + fontSize: 28, + fontWeight: 'bold', + color: colors.text, + marginBottom: spacing.sm, + }, + subtitle: { + fontSize: 16, + color: colors.textSecondary, + marginBottom: spacing.xl, + }, + inputContainer: { + marginBottom: spacing.md, + }, + label: { + fontSize: 14, + fontWeight: '500', + color: colors.text, + marginBottom: spacing.xs, + }, + input: { + backgroundColor: colors.card, + borderRadius: borderRadius.md, + padding: spacing.md, + fontSize: 16, + color: colors.text, + }, + button: { + backgroundColor: colors.primary, + borderRadius: borderRadius.md, + padding: spacing.md, + alignItems: 'center', + marginTop: spacing.md, + }, + buttonDisabled: { + opacity: 0.6, + }, + buttonText: { + color: colors.textInverse, + fontSize: 16, + fontWeight: '600', + }, + linkButton: { + marginTop: spacing.lg, + alignItems: 'center', + }, + linkText: { + color: colors.primary, + fontSize: 14, + }, +}); diff --git a/frontend-mobile/app/auth/register.tsx b/frontend-mobile/app/auth/register.tsx new file mode 100644 index 0000000..eeffd21 --- /dev/null +++ b/frontend-mobile/app/auth/register.tsx @@ -0,0 +1,172 @@ +import React, { useState } from 'react'; +import { + View, + Text, + TextInput, + TouchableOpacity, + StyleSheet, + KeyboardAvoidingView, + Platform, + Alert +} from 'react-native'; +import { useRouter } from 'expo-router'; +import { register } from '../../services/auth'; +import { colors, spacing, borderRadius } from '../../constants/theme'; + +export default function RegisterScreen() { + const router = useRouter(); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const handleRegister = async () => { + if (!username || !password || !confirmPassword) { + Alert.alert('Error', 'Por favor completa todos los campos'); + return; + } + + if (password !== confirmPassword) { + Alert.alert('Error', 'Las contraseñas no coinciden'); + return; + } + + setIsLoading(true); + try { + await register(username, password); + router.replace('/(tabs)'); + } catch (error) { + Alert.alert('Error', 'No se pudo crear la cuenta'); + } finally { + setIsLoading(false); + } + }; + + return ( + + + Crear Cuenta + Regístrate para empezar + + + Usuario + + + + + Contraseña + + + + + Confirmar Contraseña + + + + + + {isLoading ? 'Creando cuenta...' : 'Crear Cuenta'} + + + + router.back()} + > + ¿Ya tienes cuenta? Inicia sesión + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + form: { + flex: 1, + justifyContent: 'center', + padding: spacing.xl, + }, + title: { + fontSize: 28, + fontWeight: 'bold', + color: colors.text, + marginBottom: spacing.sm, + }, + subtitle: { + fontSize: 16, + color: colors.textSecondary, + marginBottom: spacing.xl, + }, + inputContainer: { + marginBottom: spacing.md, + }, + label: { + fontSize: 14, + fontWeight: '500', + color: colors.text, + marginBottom: spacing.xs, + }, + input: { + backgroundColor: colors.card, + borderRadius: borderRadius.md, + padding: spacing.md, + fontSize: 16, + color: colors.text, + }, + button: { + backgroundColor: colors.primary, + borderRadius: borderRadius.md, + padding: spacing.md, + alignItems: 'center', + marginTop: spacing.md, + }, + buttonDisabled: { + opacity: 0.6, + }, + buttonText: { + color: colors.textInverse, + fontSize: 16, + fontWeight: '600', + }, + linkButton: { + marginTop: spacing.lg, + alignItems: 'center', + }, + linkText: { + color: colors.primary, + fontSize: 14, + }, +});