Files
MeowSpool/mobile/app/verify-email.tsx
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

117 lines
3.6 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { Screen } from '@presentation/components/layout/Screen';
import { Button } from '@presentation/components/ui/Button';
import { colors, typography, spacing, radius } from '@shared/theme';
import { verifyEmailUseCase } from '@infrastructure/container';
type Status = 'loading' | 'success' | 'error';
/**
* Handler de deep link: meowspool://verify-email?token=xxx
* Chama o backend para confirmar o e-mail e exibe o resultado.
*/
export default function VerifyEmailDeepLink(): React.ReactElement {
const { token } = useLocalSearchParams<{ token: string }>();
const router = useRouter();
const [status, setStatus] = useState<Status>('loading');
const [errorMessage, setErrorMessage] = useState('Não foi possível verificar o e-mail.');
useEffect(() => {
if (!token) {
setErrorMessage('Link inválido ou expirado.');
setStatus('error');
return;
}
verifyEmailUseCase.execute(token)
.then(() => setStatus('success'))
.catch((e: unknown) => {
const msg = e instanceof Error ? e.message : 'Erro desconhecido.';
setErrorMessage(msg);
setStatus('error');
});
}, [token]);
return (
<Screen>
<View style={styles.content}>
{status === 'loading' && (
<>
<ActivityIndicator size="large" color={colors.accent} />
<Text style={styles.message}>Verificando e-mail</Text>
</>
)}
{status === 'success' && (
<>
<View style={[styles.iconWrapper, styles.iconSuccess]}>
<Ionicons name="checkmark-circle-outline" size={64} color={colors.accent} />
</View>
<Text style={styles.title}>E-mail verificado!</Text>
<Text style={styles.message}>Sua conta está ativa. Faça login para continuar.</Text>
<Button
label="Ir para o login"
onPress={() => router.replace('/(auth)/login')}
style={styles.cta}
/>
</>
)}
{status === 'error' && (
<>
<View style={[styles.iconWrapper, styles.iconError]}>
<Ionicons name="close-circle-outline" size={64} color={colors.error ?? '#FF6B6B'} />
</View>
<Text style={styles.title}>Falha na verificação</Text>
<Text style={styles.message}>{errorMessage}</Text>
<Button
label="Voltar ao login"
onPress={() => router.replace('/(auth)/login')}
style={styles.cta}
/>
</>
)}
</View>
</Screen>
);
}
const styles = StyleSheet.create({
content: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: spacing[4],
paddingHorizontal: spacing[6],
},
iconWrapper: {
width: 100,
height: 100,
borderRadius: radius.xl,
backgroundColor: colors.bgSurface,
alignItems: 'center',
justifyContent: 'center',
marginBottom: spacing[2],
},
iconSuccess: {},
iconError: {},
title: {
fontFamily: typography.fontFamily.ui,
fontSize: typography.fontSize.xl,
fontWeight: typography.fontWeight.bold,
color: colors.textPrimary,
textAlign: 'center',
},
message: {
fontFamily: typography.fontFamily.ui,
fontSize: typography.fontSize.base,
color: colors.textSecondary,
textAlign: 'center',
lineHeight: typography.fontSize.base * 1.6,
},
cta: { marginTop: spacing[2], width: '100%' },
});