diff --git a/src/App.tsx b/src/App.tsx index 27b7448..8df253a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,9 @@ - import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { Suspense, lazy } from 'react'; import { Toaster } from "@/components/ui/sonner"; import Auth from './pages/Auth'; import EmailConfirmation from './pages/EmailConfirmation'; +import CompleteProfile from './pages/CompleteProfile'; import ProtectedRoute from './components/auth/ProtectedRoute'; import { authStore } from './stores/authStore'; @@ -27,18 +27,24 @@ function App() { - {/* The auth route should not be inside ProtectedRoute */} + {/* Auth route */} : } /> - {/* Email confirmation route - should be accessible without authentication */} + {/* Email confirmation route */} } /> + {/* Complete profile route - deve ser acessível para usuários logados */} + } + /> + {/* Protected routes */} diff --git a/src/components/auth/ProtectedRoute.tsx b/src/components/auth/ProtectedRoute.tsx index dfa5d01..0d51ee1 100644 --- a/src/components/auth/ProtectedRoute.tsx +++ b/src/components/auth/ProtectedRoute.tsx @@ -5,31 +5,31 @@ import LoadingState from '../whatsapp/LoadingState'; import { authStore } from '@/stores/authStore'; import { supabase } from '@/integrations/supabase/client'; import { Session } from '@supabase/supabase-js'; +import { useProfileCompletion } from '@/hooks/useProfileCompletion'; interface ProtectedRouteProps { children: ReactNode; } const ProtectedRoute = ({ children }: ProtectedRouteProps) => { - // We use local state here to avoid re-renders of the whole app const [session, setSession] = useState(null); const [isLoading, setIsLoading] = useState(true); const location = useLocation(); - // We still update the global store for other components to use const setLoggedIn = authStore((state) => state.setLoggedIn); const setUser = authStore((state) => state.setUser); + const isProfileComplete = authStore((state) => state.isProfileComplete); + + // Hook para verificar completude do perfil + const { isChecking } = useProfileCompletion(session?.user?.email || ''); useEffect(() => { - // onAuthStateChange fires once on initial load with the current session, - // and then every time the auth state changes. const { data: { subscription } } = supabase.auth.onAuthStateChange( (_event, session) => { setSession(session); setLoggedIn(!!session); setUser(session?.user ? { id: session.user.id } : null); - // Store or remove user email from localStorage if (session?.user?.email) { localStorage.setItem('userEmail', session.user.email); } else { @@ -45,7 +45,8 @@ const ProtectedRoute = ({ children }: ProtectedRouteProps) => { }; }, [setLoggedIn, setUser]); - if (isLoading) { + // Mostrar loading enquanto verifica auth ou perfil + if (isLoading || isChecking) { return (
@@ -53,12 +54,17 @@ const ProtectedRoute = ({ children }: ProtectedRouteProps) => { ); } + // Se não tem sessão, redirecionar para login if (!session) { - // Redirect to the login page if there is no session return ; } - // Render the protected content if a session exists + // Se tem sessão mas perfil não está completo, redirecionar para completar perfil + if (session && !isProfileComplete) { + return ; + } + + // Se tudo ok, renderizar conteúdo protegido return <>{children}; }; diff --git a/src/hooks/useProfileCompletion.ts b/src/hooks/useProfileCompletion.ts new file mode 100644 index 0000000..bc28856 --- /dev/null +++ b/src/hooks/useProfileCompletion.ts @@ -0,0 +1,50 @@ + +import { useState, useEffect } from 'react'; +import { supabase } from '@/integrations/supabase/client'; +import { authStore } from '@/stores/authStore'; + +export const useProfileCompletion = (userEmail: string) => { + const [isChecking, setIsChecking] = useState(true); + const setProfileComplete = authStore((state) => state.setProfileComplete); + + useEffect(() => { + const checkProfileCompletion = async () => { + if (!userEmail) { + setIsChecking(false); + return; + } + + try { + console.log('🔍 Verificando completude do perfil para:', userEmail); + + const { data: usuario, error } = await supabase + .from('usuarios') + .select('nome, whatsapp') + .eq('email', userEmail.toLowerCase().trim()) + .single(); + + if (error) { + console.error('❌ Erro ao verificar perfil:', error); + setProfileComplete(false); + } else if (usuario) { + // Verificar se tem nome e whatsapp preenchidos + const isComplete = !!(usuario.nome?.trim() && usuario.whatsapp?.trim()); + console.log('📋 Perfil completo:', isComplete, { nome: usuario.nome, whatsapp: usuario.whatsapp }); + setProfileComplete(isComplete); + } else { + console.log('👤 Usuário não encontrado na tabela usuarios'); + setProfileComplete(false); + } + } catch (error) { + console.error('❌ Erro na verificação do perfil:', error); + setProfileComplete(false); + } finally { + setIsChecking(false); + } + }; + + checkProfileCompletion(); + }, [userEmail, setProfileComplete]); + + return { isChecking }; +}; diff --git a/src/pages/CompleteProfile.tsx b/src/pages/CompleteProfile.tsx new file mode 100644 index 0000000..3eb374c --- /dev/null +++ b/src/pages/CompleteProfile.tsx @@ -0,0 +1,193 @@ + +import { useState, useEffect } 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 { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { toast } from "sonner"; +import { supabase } from '@/integrations/supabase/client'; +import { authStore } from '@/stores/authStore'; +import { sendNewUserWebhook } from '@/services/newUserWebhookService'; +import { formatarWhatsapp } from '@/utils/whatsappFormatter'; + +const CompleteProfile = () => { + const navigate = useNavigate(); + const [nome, setNome] = useState(''); + const [empresa, setEmpresa] = useState(''); + const [whatsapp, setWhatsapp] = useState('55'); + const [isLoading, setIsLoading] = useState(false); + const [userEmail, setUserEmail] = useState(''); + const [userId, setUserId] = useState(''); + + const setProfileComplete = authStore((state) => state.setProfileComplete); + + useEffect(() => { + const getUserData = async () => { + const { data: { user } } = await supabase.auth.getUser(); + if (user) { + setUserEmail(user.email || ''); + setUserId(user.id); + + // Pre-fill nome if available from Google + const displayName = user.user_metadata?.full_name || user.user_metadata?.name || ''; + if (displayName) { + setNome(displayName); + } + } else { + // Se não há usuário logado, redirecionar para auth + navigate('/auth'); + } + }; + + getUserData(); + }, [navigate]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Validações + if (!nome.trim() || nome.trim().length < 2) { + toast.error("Nome obrigatório", { + description: "Por favor, informe seu nome completo (mínimo 2 caracteres)." + }); + return; + } + + if (!whatsapp || whatsapp.replace(/\D/g, '').length < 11) { + toast.error("WhatsApp obrigatório", { + description: "Por favor, informe um número de WhatsApp válido." + }); + return; + } + + setIsLoading(true); + + try { + console.log('📝 Salvando dados complementares do perfil...'); + + // Atualizar dados do usuário no Supabase + const { error: updateError } = await supabase + .from('usuarios') + .update({ + nome: nome.trim(), + empresa: empresa.trim() || null, + whatsapp: whatsapp.replace(/\D/g, '') + }) + .eq('email', userEmail.toLowerCase().trim()); + + if (updateError) { + console.error('❌ Erro ao atualizar usuário:', updateError); + throw new Error('Erro ao salvar dados do perfil'); + } + + console.log('✅ Dados do perfil salvos com sucesso'); + + // Enviar webhook para N8N + console.log('📡 Enviando webhook para N8N...'); + const webhookSuccess = await sendNewUserWebhook( + userEmail, + userId, + whatsapp.replace(/\D/g, '') + ); + + if (webhookSuccess) { + console.log('✅ Webhook N8N enviado com sucesso'); + } else { + console.warn('⚠️ Falha no webhook N8N, mas perfil foi salvo'); + } + + // Marcar perfil como completo + setProfileComplete(true); + + toast.success("Perfil completado!", { + description: "Seus dados foram salvos e sua conta está ativa.", + }); + + // Redirecionar para dashboard + navigate('/'); + + } catch (error) { + console.error('❌ Erro ao completar perfil:', error); + toast.error("Erro ao salvar dados", { + description: "Ocorreu um erro ao salvar seus dados. Tente novamente.", + }); + } finally { + setIsLoading(false); + } + }; + + const handleWhatsappChange = (value: string) => { + setWhatsapp(formatarWhatsapp(value)); + }; + + return ( +
+ + + Complete seu Perfil + + Para continuar, precisamos de algumas informações obrigatórias para configurar sua conta + + + +
+
+ + setNome(e.target.value)} + required + disabled={isLoading} + /> +
+ +
+ + setEmpresa(e.target.value)} + disabled={isLoading} + /> +
+ +
+ + handleWhatsappChange(e.target.value)} + required + disabled={isLoading} + /> +

+ Necessário para integração com WhatsApp e notificações +

+
+ + +
+ +
+

+ Por que estes dados?
+ Nome e WhatsApp são necessários para configurar automaticamente seu workspace e integrações personalizadas. +

+
+
+
+
+ ); +}; + +export default CompleteProfile; diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts index 481d102..78bf4f7 100644 --- a/src/stores/authStore.ts +++ b/src/stores/authStore.ts @@ -4,13 +4,17 @@ import { create } from 'zustand'; interface AuthState { isLoggedIn: boolean; user: { id: string } | null; + isProfileComplete: boolean; setLoggedIn: (status: boolean) => void; setUser: (user: { id: string } | null) => void; + setProfileComplete: (complete: boolean) => void; } export const authStore = create((set) => ({ isLoggedIn: false, user: null, + isProfileComplete: true, // Default true para não afetar usuários existentes setLoggedIn: (status) => set({ isLoggedIn: status }), setUser: (user) => set({ user }), + setProfileComplete: (complete) => set({ isProfileComplete: complete }), }));