foodsnap/src/App.tsx

327 lines
11 KiB
TypeScript
Raw Normal View History

import React, { useState, useEffect } from 'react';
import Header from './components/landing/Header';
import Hero from './components/landing/Hero';
import CoachHighlight from './components/landing/CoachHighlight';
import HowItWorks from './components/landing/HowItWorks';
import Features from './components/landing/Features';
import Testimonials from './components/landing/Testimonials';
import Pricing from './components/landing/Pricing';
import FAQ from './components/landing/FAQ';
import Footer from './components/landing/Footer';
import RegistrationModal from './components/modals/RegistrationModal';
import CalculatorsModal from './components/modals/CalculatorsModal';
import Dashboard from './pages/Dashboard';
import AdminPanel from './pages/AdminPanel';
import ProfessionalDashboard from './pages/ProfessionalDashboard';
import FAQPage from './pages/FAQPage';
import { LanguageProvider } from './contexts/LanguageContext';
import { supabase } from './lib/supabase';
import { Loader2 } from 'lucide-react';
import { User } from './types';
// removed User interface definition
const AppContent: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isToolsOpen, setIsToolsOpen] = useState(false);
const [authMode, setAuthMode] = useState<'login' | 'register'>('register');
const [selectedPlan, setSelectedPlan] = useState('starter');
const [currentView, setCurrentView] = useState<'home' | 'faq'>('home'); // Estado de navegação
const [isCompletingProfile, setIsCompletingProfile] = useState(false); // Novo estado para controle de perfil incompleto
const [user, setUser] = useState<User | null>(null);
const [isAdminView, setIsAdminView] = useState(false);
const [isProfessionalView, setIsProfessionalView] = useState(false);
const [isLoadingSession, setIsLoadingSession] = useState(true);
// Check active session on load
// Check active session on load
useEffect(() => {
let mounted = true;
const initializeAuth = async () => {
try {
// Obter sessão inicial sem race conditions complexas
const { data: { session }, error } = await supabase.auth.getSession();
if (error) {
console.error("Erro ao obter sessão inicial:", error);
if (mounted) setIsLoadingSession(false);
return;
}
if (session?.user) {
console.log("App: Sessão encontrada, carregando perfil...");
if (mounted) {
await fetchUserProfile(session.user.id, session.user.email);
}
} else {
console.log("App: Nenhuma sessão ativa.");
if (mounted) setIsLoadingSession(false);
}
} catch (err) {
console.error("Erro inesperado na autenticação:", err);
if (mounted) setIsLoadingSession(false);
}
};
initializeAuth();
const { data: { subscription } } = supabase.auth.onAuthStateChange(async (event, session) => {
console.log(`Auth event: ${event}`);
if (!mounted) return;
if (event === 'SIGNED_OUT') {
setUser(null);
setIsAdminView(false);
setIsProfessionalView(false);
setIsLoadingSession(false);
setCurrentView('home');
setIsCompletingProfile(false);
} else if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
if (session?.user) {
// Apenas recarrega se o usuário ainda não estiver setado ou se mudou
// Mas para garantir atualização de claims/perfil, recarregamos.
await fetchUserProfile(session.user.id, session.user.email);
}
}
});
return () => {
mounted = false;
subscription.unsubscribe();
};
}, []);
const fetchUserProfile = async (userId: string, email?: string) => {
try {
let profile = null;
// Tentativa única de buscar perfil. O cliente Supabase já trata retries de rede.
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.maybeSingle();
if (error) throw error;
profile = data;
// Se não tem perfil ou falta telefone, solicitamos completar cadastro
if (!profile || !profile.phone_e164) {
console.warn("Perfil incompleto. Solicitando dados.");
// Não fazemos signOut, apenas abrimos o modal para completar
setIsCompletingProfile(true);
setAuthMode('register'); // Visualmente irrelevante pois isCompletingProfile domina
setIsModalOpen(true);
// User fica null, então cai na Landing Page com Modal aberto. Perfeito.
return;
}
// Se perfil ok, garante que flag de completar está false
setIsCompletingProfile(false);
const { data: entitlement } = await supabase
.from('user_entitlements')
.select('*')
.eq('user_id', userId)
.maybeSingle();
let plan: 'free' | 'pro' | 'trial' = 'free';
if (entitlement) {
if (entitlement.entitlement_code === 'pro' && entitlement.is_active) plan = 'pro';
else if (entitlement.entitlement_code === 'trial' && entitlement.is_active) plan = 'trial';
}
setUser({
id: userId,
name: profile.full_name || 'Usuário',
email: email || profile.email || '',
phone: profile.phone_e164,
public_id: profile.public_id,
avatar: undefined, // Column does not exist in DB, using undefined to trigger UI fallback
is_admin: profile.is_admin,
is_professional: profile.is_professional,
plan: plan,
plan_valid_until: entitlement?.valid_until
});
// Auto-switch logic:
// If user is professional, default to Professional View.
// If user is Admin, they usually see Admin View but we might default to user dashboard for them unless they toggle.
// Logic requested: "System verifies and logs him in correct panel".
const loginIntent = localStorage.getItem('login_intent');
if (profile.is_professional) {
setIsProfessionalView(true);
} else {
setIsProfessionalView(false);
}
// Override if explicit intent was set (though we removed the button, old intents might linger, safe to ignore or keep)
if (loginIntent === 'user') {
// If they explicitly wanted user view but are pro, maybe respect it?
// For now, let's stick to the requested "System verifies" rule above.
}
} catch (error) {
console.error('Error fetching profile:', error);
setUser(null);
} finally {
setIsLoadingSession(false);
}
};
const handleOpenRegister = (plan: string = 'starter') => {
setSelectedPlan(plan);
setAuthMode('register');
setIsModalOpen(true);
setIsCompletingProfile(false);
};
const handleOpenLogin = (context?: 'user' | 'professional') => {
// If context is professional, we can store this intent to redirect after login
// For now, we'll use a simple localStorage flag or state
if (context === 'professional') {
localStorage.setItem('login_intent', 'professional');
} else {
localStorage.setItem('login_intent', 'user');
}
setAuthMode('login');
setIsModalOpen(true);
setIsCompletingProfile(false);
};
const handleAuthSuccess = async () => {
setIsModalOpen(false);
setIsCompletingProfile(false);
// Force refresh profile to ensure we have latest data
const { data: { session } } = await supabase.auth.getSession();
if (session?.user) {
await fetchUserProfile(session.user.id, session.user.email);
// Check intent
const intent = localStorage.getItem('login_intent');
if (intent === 'professional') {
// We can't know for sure if they are pro yet inside this function scope easily unless we use the user state which might be stale
// But fetchUserProfile updates 'user'.
// Ideally we wait for user state to update.
// For simplicity, let's rely on the useEffect that watches 'user' or just check 'isProfessionalView' toggle inside fetchUserProfile?
// Lets keep it simple: We just set the view if the profile allows it.
// Actually, fetchUserProfile runs, updates User.
// We can check localstorage IN fetchUserProfile.
}
localStorage.removeItem('login_intent');
}
};
const handleLogout = async () => {
await supabase.auth.signOut();
setUser(null);
setIsAdminView(false);
};
const toggleAdminView = () => {
if (user?.is_admin) {
setIsAdminView(!isAdminView);
}
};
// Helper function for navigating
const handleNavigate = (view: 'home' | 'faq') => {
setCurrentView(view);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
if (isLoadingSession) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<Loader2 className="w-8 h-8 animate-spin text-brand-600" />
</div>
);
}
// Rota Admin
if (user && isAdminView && user.is_admin) {
return <AdminPanel user={user} onExitAdmin={toggleAdminView} onLogout={handleLogout} />;
}
// Rota Profissional
if (user && isProfessionalView) {
return <ProfessionalDashboard user={user} onExit={() => setIsProfessionalView(false)} onLogout={handleLogout} />;
}
// Rota Dashboard Usuário
if (user) {
return (
<Dashboard
user={user}
onLogout={handleLogout}
onOpenAdmin={user.is_admin ? toggleAdminView : undefined}
onOpenPro={() => setIsProfessionalView(true)}
/>
);
}
// Rota Pública (Landing Page ou FAQ Page)
return (
<div className="min-h-screen bg-white text-gray-900 font-sans selection:bg-brand-100 selection:text-brand-900">
<Header
onRegister={() => handleOpenRegister('starter')}
onLogin={handleOpenLogin}
onOpenTools={() => setIsToolsOpen(true)}
onNavigate={handleNavigate} // Passa navegação
/>
<main>
{currentView === 'home' ? (
<>
<Hero onRegister={() => handleOpenRegister('starter')} />
<CoachHighlight onRegister={() => handleOpenRegister('starter')} />
<HowItWorks />
<Features />
<Testimonials />
<Pricing onRegister={handleOpenRegister} />
<FAQ />
</>
) : (
<FAQPage onBack={() => handleNavigate('home')} />
)}
</main>
<Footer
onRegister={() => handleOpenRegister('starter')}
onNavigate={handleNavigate} // Passa navegação
/>
<RegistrationModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
plan={selectedPlan}
mode={authMode}
isCompletingProfile={isCompletingProfile} // Passa o estado de completar perfil
onSuccess={handleAuthSuccess}
/>
<CalculatorsModal
isOpen={isToolsOpen}
onClose={() => setIsToolsOpen(false)}
/>
</div>
);
};
const App: React.FC = () => {
return (
<LanguageProvider>
<AppContent />
</LanguageProvider>
);
};
export default App;