Files
MeowSpool/mobile/app/(auth)/register.tsx
T
Felipe d5af338ee1 feat: implement email verification and password reset features
- Add EmailTokenService for handling email verification and password reset tokens.
- Create EmailService for sending verification and reset emails via SMTP.
- Update AuthService to handle email verification status during login.
- Modify user registration to redirect to a check email screen instead of issuing a token.
- Implement resend verification email functionality.
- Add deep link handling for email verification and password reset in the mobile app.
- Update mobile app routes and components to support new email verification flow.
- Enhance error handling for unverified emails during login attempts.
- Update configuration to include SMTP settings for email service.
2026-03-14 23:38:15 -03:00

147 lines
4.4 KiB
TypeScript

import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native';
import { Link, useRouter } from 'expo-router';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Ionicons } from '@expo/vector-icons';
import { Screen } from '@presentation/components/layout/Screen';
import { Header } from '@presentation/components/layout/Header';
import { Input } from '@presentation/components/ui/Input';
import { Button } from '@presentation/components/ui/Button';
import { colors, typography, spacing } from '@shared/theme';
import { registerUseCase } from '@infrastructure/container';
const schema = z.object({
email: z.string().email('E-mail inválido'),
password: z
.string()
.min(8, 'Mínimo 8 caracteres'),
confirmPassword: z.string(),
}).refine((d) => d.password === d.confirmPassword, {
message: 'As senhas não coincidem',
path: ['confirmPassword'],
});
type FormData = z.infer<typeof schema>;
/**
* Tela de Cadastro — IX-0
*/
export default function RegisterScreen(): React.ReactElement {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const { control, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { email: '', password: '', confirmPassword: '' },
});
async function onSubmit(data: FormData): Promise<void> {
setIsLoading(true);
try {
await registerUseCase.execute({ email: data.email, password: data.password });
router.replace(`/(auth)/check-email?email=${encodeURIComponent(data.email)}`);
} catch {
Alert.alert('Erro', 'Não foi possível criar sua conta.');
} finally {
setIsLoading(false);
}
}
return (
<Screen scrollable keyboardAvoiding>
<Header title="Criar conta" showBack />
<View style={styles.form}>
<Controller
control={control}
name="email"
render={({ field }) => (
<Input
label="E-MAIL"
placeholder="seu@email.com"
keyboardType="email-address"
autoComplete="email"
leftIcon={<Ionicons name="mail-outline" size={18} color={colors.textSecondary} />}
error={errors.email?.message}
onChangeText={field.onChange}
value={field.value}
/>
)}
/>
<Controller
control={control}
name="password"
render={({ field }) => (
<Input
label="SENHA"
placeholder="Mínimo 8 caracteres"
isPassword
leftIcon={<Ionicons name="lock-closed-outline" size={18} color={colors.textSecondary} />}
error={errors.password?.message}
onChangeText={field.onChange}
value={field.value}
/>
)}
/>
<Controller
control={control}
name="confirmPassword"
render={({ field }) => (
<Input
label="CONFIRMAR SENHA"
placeholder="Repita a senha"
isPassword
leftIcon={<Ionicons name="lock-closed-outline" size={18} color={colors.textSecondary} />}
error={errors.confirmPassword?.message}
onChangeText={field.onChange}
value={field.value}
/>
)}
/>
<Button label="Criar conta" onPress={handleSubmit(onSubmit)} isLoading={isLoading} style={styles.cta} />
<View style={styles.footer}>
<Text style={styles.footerText}> tem uma conta? </Text>
<Link href="/(auth)/login" asChild>
<TouchableOpacity>
<Text style={styles.footerLink}>Entrar</Text>
</TouchableOpacity>
</Link>
</View>
</View>
</Screen>
);
}
const styles = StyleSheet.create({
form: {
gap: spacing[4],
paddingTop: spacing[6],
paddingBottom: spacing[8],
},
cta: {
marginTop: spacing[2],
},
footer: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: spacing[4],
},
footerText: {
fontFamily: typography.fontFamily.ui,
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
},
footerLink: {
fontFamily: typography.fontFamily.ui,
fontSize: typography.fontSize.sm,
color: colors.accent,
fontWeight: typography.fontWeight.semibold,
},
});