foodsnap/src/App.tsx

251 lines
No EOL
8.9 KiB
TypeScript

import React, { useState, useEffect, Suspense, lazy } 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 { LanguageProvider } from './contexts/LanguageContext';
import { UserProvider, useUser } from './contexts/UserContext';
import { Loader2 } from 'lucide-react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
const ProfessionalDashboard = lazy(() => import('./pages/ProfessionalDashboard'));
const FAQPage = lazy(() => import('./pages/FAQPage'));
const PrivacyPolicy = lazy(() => import('./pages/legal/PrivacyPolicy'));
const TermsOfService = lazy(() => import('./pages/legal/TermsOfService'));
const DataDeletion = lazy(() => import('./pages/legal/DataDeletion'));
export type ViewState = 'home' | 'faq' | 'privacy' | 'terms' | 'data-deletion';
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');
// Custom simple router state based on URL
const [currentPath, setCurrentPath] = useState(window.location.pathname);
useEffect(() => {
const handleLocationChange = () => setCurrentPath(window.location.pathname);
window.addEventListener('popstate', handleLocationChange);
return () => window.removeEventListener('popstate', handleLocationChange);
}, []);
// Helper mapping from path to ViewState internally if needed
const getCurrentView = (): ViewState => {
const normalizedPath = currentPath.replace(/\/$/, '') || '/';
if (normalizedPath.includes('/faq')) return 'faq';
if (normalizedPath.includes('/privacidade')) return 'privacy';
if (normalizedPath.includes('/termos')) return 'terms';
if (normalizedPath.includes('/exclusao-de-dados')) return 'data-deletion';
return 'home';
};
const currentView = getCurrentView();
// Consume UserContext
const {
user,
loading,
isAdminView,
isProfessionalView,
isCompletingProfile,
toggleAdminView,
setIsProfessionalView,
logout,
refreshProfile
} = useUser();
// Effect to handle "Complete Profile" flow automatically
useEffect(() => {
if (isCompletingProfile) {
setAuthMode('register');
setIsModalOpen(true);
}
}, [isCompletingProfile]);
const handleOpenRegister = (plan: string = 'starter') => {
setSelectedPlan(plan);
setAuthMode('register');
setIsModalOpen(true);
};
const handleOpenLogin = (context?: 'user' | 'professional') => {
if (context === 'professional') {
localStorage.setItem('login_intent', 'professional');
} else {
localStorage.setItem('login_intent', 'user');
}
setAuthMode('login');
setIsModalOpen(true);
};
const handleAuthSuccess = async () => {
setIsModalOpen(false);
await refreshProfile();
// Se acabou de fazer o cadastro clicando em um plano pago (monthly), leva direto pro Stripe!
if (authMode === 'register' && selectedPlan === 'monthly') {
try {
const { data: { session } } = await supabase.auth.getSession();
if (session) {
const res = await fetch(`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/stripe-checkout`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${session.access_token}`,
},
body: JSON.stringify({ plan: "mensal" })
});
const { url, error } = await res.json();
if (!error && url) {
window.location.href = url; // Redireciona pro Checkout
return;
}
}
} catch (e) {
console.error("Falha ao redirecionar para o checkout:", e);
}
}
// Login intent logic handled inside context or simply by state update
localStorage.removeItem('login_intent');
};
// Helper function for navigating with real URLs
const handleNavigate = (view: ViewState) => {
let path = '/';
if (view === 'faq') path = '/faq';
if (view === 'privacy') path = '/privacidade';
if (view === 'terms') path = '/termos';
if (view === 'data-deletion') path = '/exclusao-de-dados';
window.history.pushState({}, '', path);
setCurrentPath(path);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
if (loading) {
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 (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center bg-white"><Loader2 className="w-8 h-8 animate-spin text-brand-600" /></div>}>
<AdminPanel user={user} onExitAdmin={toggleAdminView} onLogout={logout} />
</Suspense>
);
}
// Rota Profissional
if (user && isProfessionalView) {
return (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center bg-white"><Loader2 className="w-8 h-8 animate-spin text-brand-600" /></div>}>
<ProfessionalDashboard user={user} onExit={() => setIsProfessionalView(false)} onLogout={logout} />
</Suspense>
);
}
// Rota Dashboard Usuário
if (user) {
return (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center bg-gray-50"><Loader2 className="w-8 h-8 animate-spin text-brand-600" /></div>}>
<Dashboard
user={user}
onLogout={logout}
onOpenAdmin={user.is_admin ? toggleAdminView : undefined}
onOpenPro={() => setIsProfessionalView(true)}
initialTab={currentPath.includes('/meu-plano') ? 'coach' : 'overview'}
/>
</Suspense>
);
}
// 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}
/>
<main>
{currentView === 'home' ? (
<>
<Hero onRegister={() => handleOpenRegister('starter')} />
<CoachHighlight onRegister={() => handleOpenRegister('starter')} />
<HowItWorks />
<Features />
<Testimonials />
<Pricing onRegister={handleOpenRegister} />
<FAQ />
</>
) : currentView === 'privacy' ? (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center bg-white"><Loader2 className="w-8 h-8 animate-spin text-brand-600" /></div>}>
<PrivacyPolicy onBack={() => handleNavigate('home')} />
</Suspense>
) : currentView === 'terms' ? (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center bg-white"><Loader2 className="w-8 h-8 animate-spin text-brand-600" /></div>}>
<TermsOfService onBack={() => handleNavigate('home')} />
</Suspense>
) : currentView === 'data-deletion' ? (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center bg-white"><Loader2 className="w-8 h-8 animate-spin text-brand-600" /></div>}>
<DataDeletion onBack={() => handleNavigate('home')} />
</Suspense>
) : (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center bg-white"><Loader2 className="w-8 h-8 animate-spin text-brand-600" /></div>}>
<FAQPage onBack={() => handleNavigate('home')} />
</Suspense>
)}
</main>
<Footer
onRegister={() => handleOpenRegister('starter')}
onNavigate={handleNavigate}
/>
<RegistrationModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
plan={selectedPlan}
mode={authMode}
isCompletingProfile={isCompletingProfile}
onSuccess={handleAuthSuccess}
/>
<CalculatorsModal
isOpen={isToolsOpen}
onClose={() => setIsToolsOpen(false)}
/>
</div>
);
};
const App: React.FC = () => {
return (
<UserProvider>
<LanguageProvider>
<AppContent />
</LanguageProvider>
</UserProvider>
);
};
export default App;