diff --git a/src/components/auth/ConfirmationActions.tsx b/src/components/auth/ConfirmationActions.tsx new file mode 100644 index 0000000..675f38c --- /dev/null +++ b/src/components/auth/ConfirmationActions.tsx @@ -0,0 +1,93 @@ + +import { useNavigate } from 'react-router-dom'; +import { Button } from "@/components/ui/button"; +import { ConfirmationStatus } from '@/hooks/useEmailConfirmation'; + +interface ConfirmationActionsProps { + status: ConfirmationStatus; + message: string; +} + +const ConfirmationActions = ({ status, message }: ConfirmationActionsProps) => { + const navigate = useNavigate(); + + const handleBackToLogin = () => { + navigate('/auth'); + }; + + const handleGoToDashboard = () => { + navigate('/dashboard'); + }; + + const handleResendEmail = () => { + navigate('/auth', { + state: { + showSuccessMessage: true, + message: "Faça login novamente para receber um novo email de confirmação." + } + }); + }; + + if (status === 'loading') { + return ( +

+ Aguarde enquanto confirmamos seu email... +

+ ); + } + + if (status === 'success') { + return ( + <> +

+ {message} +

+
+ + +
+ + ); + } + + if (status === 'expired') { + return ( + <> +

+ {message} +

+
+ + +
+ + ); + } + + // Error state + return ( + <> +

+ {message} +

+
+ + +
+ + ); +}; + +export default ConfirmationActions; diff --git a/src/components/auth/ConfirmationStatusIcon.tsx b/src/components/auth/ConfirmationStatusIcon.tsx new file mode 100644 index 0000000..853af34 --- /dev/null +++ b/src/components/auth/ConfirmationStatusIcon.tsx @@ -0,0 +1,23 @@ + +import { CheckCircle, XCircle, Loader2, AlertTriangle } from 'lucide-react'; +import { ConfirmationStatus } from '@/hooks/useEmailConfirmation'; + +interface ConfirmationStatusIconProps { + status: ConfirmationStatus; +} + +const ConfirmationStatusIcon = ({ status }: ConfirmationStatusIconProps) => { + switch (status) { + case 'loading': + return ; + case 'success': + return ; + case 'expired': + return ; + case 'error': + default: + return ; + } +}; + +export default ConfirmationStatusIcon; diff --git a/src/components/auth/EmailConfirmationCard.tsx b/src/components/auth/EmailConfirmationCard.tsx new file mode 100644 index 0000000..ef16032 --- /dev/null +++ b/src/components/auth/EmailConfirmationCard.tsx @@ -0,0 +1,52 @@ + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { ConfirmationStatus } from '@/hooks/useEmailConfirmation'; +import ConfirmationStatusIcon from './ConfirmationStatusIcon'; +import ConfirmationActions from './ConfirmationActions'; + +interface EmailConfirmationCardProps { + status: ConfirmationStatus; + message: string; +} + +const EmailConfirmationCard = ({ status, message }: EmailConfirmationCardProps) => { + const getStatusMessage = () => { + switch (status) { + case 'loading': + return "Processando confirmação do seu email..."; + case 'success': + return "Email confirmado com sucesso!"; + case 'expired': + return "Link de confirmação expirado"; + case 'error': + default: + return "Problema na confirmação"; + } + }; + + return ( + + +
+ Finance Home Logo + Finance Home +
+ + {getStatusMessage()} + +
+ +
+ + +
+
+
+ ); +}; + +export default EmailConfirmationCard; diff --git a/src/hooks/useEmailConfirmation.ts b/src/hooks/useEmailConfirmation.ts new file mode 100644 index 0000000..f1097a7 --- /dev/null +++ b/src/hooks/useEmailConfirmation.ts @@ -0,0 +1,161 @@ + +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { supabase } from '@/integrations/supabase/client'; +import { toast } from "sonner"; + +export type ConfirmationStatus = 'loading' | 'success' | 'error' | 'expired'; + +export const useEmailConfirmation = () => { + const navigate = useNavigate(); + const [status, setStatus] = useState('loading'); + const [message, setMessage] = useState(''); + + useEffect(() => { + const handleEmailConfirmation = async () => { + try { + console.log('🔐 Iniciando processo de confirmação de email...'); + console.log('📍 URL atual:', window.location.href); + console.log('🔗 Hash:', window.location.hash); + console.log('🔗 Search:', window.location.search); + + // Verificar se há erros explícitos na URL + const urlParams = new URLSearchParams(window.location.search); + const hashParams = new URLSearchParams(window.location.hash.substring(1)); + + const errorFromUrl = urlParams.get('error') || hashParams.get('error'); + const errorCode = urlParams.get('error_code') || hashParams.get('error_code'); + const errorDescription = urlParams.get('error_description') || hashParams.get('error_description'); + + console.log('🔍 Verificando erros na URL:', { + error: errorFromUrl, + errorCode, + errorDescription + }); + + // Tratar erros específicos + if (errorFromUrl) { + console.error('❌ Erro detectado na URL:', errorFromUrl, errorDescription); + + if (errorCode === 'otp_expired' || errorDescription?.includes('expired')) { + setStatus('expired'); + setMessage('O link de confirmação expirou. Solicite um novo email de confirmação.'); + toast.error("Link expirado", { + description: "Faça login novamente para receber um novo email de confirmação.", + }); + return; + } + + setStatus('error'); + setMessage('Link de confirmação inválido ou com problema. Tente fazer login novamente.'); + toast.error("Erro na confirmação", { + description: errorDescription || "Link inválido", + }); + return; + } + + // Verificar tokens no hash fragment (formato padrão do Supabase) + const accessToken = hashParams.get('access_token'); + const refreshToken = hashParams.get('refresh_token'); + const tokenType = hashParams.get('type'); + + // Verificar tokens nos query params (formato antigo) + const tokenHash = urlParams.get('token_hash'); + const type = urlParams.get('type'); + + console.log('📋 Tokens encontrados:', { + accessToken: accessToken ? 'presente' : 'ausente', + refreshToken: refreshToken ? 'presente' : 'ausente', + tokenType, + tokenHash: tokenHash ? 'presente' : 'ausente', + type + }); + + let confirmationSuccess = false; + + // Processar tokens do hash fragment (formato mais comum) + if (accessToken && refreshToken && tokenType) { + console.log('✅ Processando tokens do hash fragment...'); + + const { data, error } = await supabase.auth.setSession({ + access_token: accessToken, + refresh_token: refreshToken + }); + + if (error) { + console.error('❌ Erro ao definir sessão:', error); + throw error; + } else { + console.log('✅ Sessão definida com sucesso:', data); + confirmationSuccess = true; + } + } + // Processar tokens dos query params (formato antigo) + else if (tokenHash && type) { + console.log('✅ Processando tokens dos query params...'); + + const { data, error } = await supabase.auth.verifyOtp({ + token_hash: tokenHash, + type: type as any, + }); + + if (error) { + console.error('❌ Erro na verificação OTP:', error); + throw error; + } else { + console.log('✅ OTP verificado com sucesso:', data); + confirmationSuccess = true; + } + } + + if (confirmationSuccess) { + // Aguardar um momento para a sessão ser estabelecida + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Verificar se a sessão foi estabelecida + const { data: { session } } = await supabase.auth.getSession(); + + if (session) { + console.log('✅ Confirmação bem-sucedida com sessão ativa'); + setStatus('success'); + setMessage('Email confirmado com sucesso! Redirecionando para o dashboard...'); + toast.success("Email confirmado!", { + description: "Sua conta foi ativada com sucesso. Redirecionando...", + }); + + setTimeout(() => { + navigate('/dashboard', { replace: true }); + }, 2000); + } else { + console.log('⚠️ Confirmação processada mas sem sessão ativa'); + setStatus('success'); + setMessage('Email confirmado com sucesso! Você pode fazer login agora.'); + toast.success("Email confirmado!", { + description: "Sua conta foi ativada. Faça login para continuar.", + }); + } + } else { + // Nenhum token válido encontrado + console.warn('⚠️ Nenhum token válido encontrado na URL'); + setStatus('error'); + setMessage('Link de confirmação inválido. Verifique se você clicou no link correto do email.'); + toast.error("Link inválido", { + description: "Este link de confirmação não é válido.", + }); + } + + } catch (error) { + console.error('💥 Erro geral na confirmação:', error); + setStatus('error'); + setMessage('Ocorreu um erro inesperado. Tente fazer login normalmente.'); + toast.error("Erro inesperado", { + description: "Tente fazer login normalmente.", + }); + } + }; + + handleEmailConfirmation(); + }, [navigate]); + + return { status, message }; +}; diff --git a/src/pages/EmailConfirmation.tsx b/src/pages/EmailConfirmation.tsx index a666e94..5379329 100644 --- a/src/pages/EmailConfirmation.tsx +++ b/src/pages/EmailConfirmation.tsx @@ -1,289 +1,13 @@ -import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { CheckCircle, XCircle, Loader2, AlertTriangle } from 'lucide-react'; -import { supabase } from '@/integrations/supabase/client'; -import { toast } from "sonner"; +import { useEmailConfirmation } from '@/hooks/useEmailConfirmation'; +import EmailConfirmationCard from '@/components/auth/EmailConfirmationCard'; const EmailConfirmation = () => { - const navigate = useNavigate(); - const [status, setStatus] = useState<'loading' | 'success' | 'error' | 'expired'>('loading'); - const [message, setMessage] = useState(''); - - useEffect(() => { - const handleEmailConfirmation = async () => { - try { - console.log('🔐 Iniciando processo de confirmação de email...'); - console.log('📍 URL atual:', window.location.href); - console.log('🔗 Hash:', window.location.hash); - console.log('🔗 Search:', window.location.search); - - // Verificar se há erros explícitos na URL - const urlParams = new URLSearchParams(window.location.search); - const hashParams = new URLSearchParams(window.location.hash.substring(1)); - - const errorFromUrl = urlParams.get('error') || hashParams.get('error'); - const errorCode = urlParams.get('error_code') || hashParams.get('error_code'); - const errorDescription = urlParams.get('error_description') || hashParams.get('error_description'); - - console.log('🔍 Verificando erros na URL:', { - error: errorFromUrl, - errorCode, - errorDescription - }); - - // Tratar erros específicos - if (errorFromUrl) { - console.error('❌ Erro detectado na URL:', errorFromUrl, errorDescription); - - if (errorCode === 'otp_expired' || errorDescription?.includes('expired')) { - setStatus('expired'); - setMessage('O link de confirmação expirou. Solicite um novo email de confirmação.'); - toast.error("Link expirado", { - description: "Faça login novamente para receber um novo email de confirmação.", - }); - return; - } - - setStatus('error'); - setMessage('Link de confirmação inválido ou com problema. Tente fazer login novamente.'); - toast.error("Erro na confirmação", { - description: errorDescription || "Link inválido", - }); - return; - } - - // Verificar tokens no hash fragment (formato padrão do Supabase) - const accessToken = hashParams.get('access_token'); - const refreshToken = hashParams.get('refresh_token'); - const tokenType = hashParams.get('type'); - - // Verificar tokens nos query params (formato antigo) - const tokenHash = urlParams.get('token_hash'); - const type = urlParams.get('type'); - - console.log('📋 Tokens encontrados:', { - accessToken: accessToken ? 'presente' : 'ausente', - refreshToken: refreshToken ? 'presente' : 'ausente', - tokenType, - tokenHash: tokenHash ? 'presente' : 'ausente', - type - }); - - let confirmationSuccess = false; - - // Processar tokens do hash fragment (formato mais comum) - if (accessToken && refreshToken && tokenType) { - console.log('✅ Processando tokens do hash fragment...'); - - const { data, error } = await supabase.auth.setSession({ - access_token: accessToken, - refresh_token: refreshToken - }); - - if (error) { - console.error('❌ Erro ao definir sessão:', error); - throw error; - } else { - console.log('✅ Sessão definida com sucesso:', data); - confirmationSuccess = true; - } - } - // Processar tokens dos query params (formato antigo) - else if (tokenHash && type) { - console.log('✅ Processando tokens dos query params...'); - - const { data, error } = await supabase.auth.verifyOtp({ - token_hash: tokenHash, - type: type as any, - }); - - if (error) { - console.error('❌ Erro na verificação OTP:', error); - throw error; - } else { - console.log('✅ OTP verificado com sucesso:', data); - confirmationSuccess = true; - } - } - - if (confirmationSuccess) { - // Aguardar um momento para a sessão ser estabelecida - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Verificar se a sessão foi estabelecida - const { data: { session } } = await supabase.auth.getSession(); - - if (session) { - console.log('✅ Confirmação bem-sucedida com sessão ativa'); - setStatus('success'); - setMessage('Email confirmado com sucesso! Redirecionando para o dashboard...'); - toast.success("Email confirmado!", { - description: "Sua conta foi ativada com sucesso. Redirecionando...", - }); - - setTimeout(() => { - navigate('/dashboard', { replace: true }); - }, 2000); - } else { - console.log('⚠️ Confirmação processada mas sem sessão ativa'); - setStatus('success'); - setMessage('Email confirmado com sucesso! Você pode fazer login agora.'); - toast.success("Email confirmado!", { - description: "Sua conta foi ativada. Faça login para continuar.", - }); - } - } else { - // Nenhum token válido encontrado - console.warn('⚠️ Nenhum token válido encontrado na URL'); - setStatus('error'); - setMessage('Link de confirmação inválido. Verifique se você clicou no link correto do email.'); - toast.error("Link inválido", { - description: "Este link de confirmação não é válido.", - }); - } - - } catch (error) { - console.error('💥 Erro geral na confirmação:', error); - setStatus('error'); - setMessage('Ocorreu um erro inesperado. Tente fazer login normalmente.'); - toast.error("Erro inesperado", { - description: "Tente fazer login normalmente.", - }); - } - }; - - handleEmailConfirmation(); - }, [navigate]); - - const handleBackToLogin = () => { - navigate('/auth'); - }; - - const handleGoToDashboard = () => { - navigate('/dashboard'); - }; - - const handleResendEmail = () => { - // Redirecionar para o login para que o usuário possa solicitar novo email - navigate('/auth', { - state: { - showSuccessMessage: true, - message: "Faça login novamente para receber um novo email de confirmação." - } - }); - }; - - const getStatusIcon = () => { - switch (status) { - case 'loading': - return ; - case 'success': - return ; - case 'expired': - return ; - case 'error': - default: - return ; - } - }; - - const getStatusMessage = () => { - switch (status) { - case 'loading': - return "Processando confirmação do seu email..."; - case 'success': - return "Email confirmado com sucesso!"; - case 'expired': - return "Link de confirmação expirado"; - case 'error': - default: - return "Problema na confirmação"; - } - }; + const { status, message } = useEmailConfirmation(); return (
- - -
- Finance Home Logo - Finance Home -
- - {getStatusMessage()} - -
- -
- {status === 'loading' && ( - <> - {getStatusIcon()} -

- Aguarde enquanto confirmamos seu email... -

- - )} - - {status === 'success' && ( - <> - {getStatusIcon()} -

- {message} -

-
- - -
- - )} - - {status === 'expired' && ( - <> - {getStatusIcon()} -

- {message} -

-
- - -
- - )} - - {status === 'error' && ( - <> - {getStatusIcon()} -

- {message} -

-
- - -
- - )} -
-
-
+
); };