2026-02-17 20:49:42 +00:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
LayoutDashboard, History, CreditCard, Settings, LogOut, Plus, Search, Calendar, ChevronRight, Zap, ExternalLink, MessageCircle, Loader2, Utensils, ShieldAlert, Smartphone, QrCode, CheckCircle2, Dumbbell, Timer, PlayCircle, ScanEye, BrainCircuit, Activity, ScanLine, Sparkles, TrendingUp
|
|
|
|
|
} from 'lucide-react';
|
|
|
|
|
import CoachWizard from '@/components/coach/CoachWizard';
|
|
|
|
|
import { User } from '@/types';
|
|
|
|
|
import { supabase } from '@/lib/supabase';
|
|
|
|
|
import { useLanguage } from '@/contexts/LanguageContext';
|
|
|
|
|
|
|
|
|
|
// Custom Hooks
|
|
|
|
|
import { useDashboardStats } from '@/hooks/useDashboardStats';
|
|
|
|
|
import { useDashboardHistory } from '@/hooks/useDashboardHistory';
|
|
|
|
|
import { useCoachPlan } from '@/hooks/useCoachPlan';
|
|
|
|
|
|
|
|
|
|
// Layout Components
|
|
|
|
|
import Sidebar from '@/components/layout/Sidebar';
|
|
|
|
|
import MobileNav from '@/components/layout/MobileNav';
|
|
|
|
|
|
|
|
|
|
// Feature Components
|
|
|
|
|
import DashboardOverview from '@/components/dashboard/DashboardOverview';
|
|
|
|
|
import DashboardHistory from '@/components/dashboard/DashboardHistory';
|
|
|
|
|
import DashboardSubscription from '@/components/dashboard/DashboardSubscription';
|
|
|
|
|
import DashboardCoach from '@/components/dashboard/DashboardCoach';
|
|
|
|
|
|
|
|
|
|
interface DashboardProps {
|
|
|
|
|
user: User;
|
|
|
|
|
onLogout: () => void;
|
|
|
|
|
onOpenAdmin?: () => void; // Optional prop for admin toggle
|
|
|
|
|
onOpenPro?: () => void; // Optional prop for professional toggle
|
2026-02-26 00:17:54 +00:00
|
|
|
initialTab?: 'overview' | 'history' | 'subscription' | 'coach';
|
2026-02-17 20:49:42 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-26 00:17:54 +00:00
|
|
|
const Dashboard: React.FC<DashboardProps> = ({ user, onLogout, onOpenAdmin, onOpenPro, initialTab }) => {
|
2026-02-17 20:49:42 +00:00
|
|
|
const { t, language } = useLanguage();
|
2026-02-26 00:17:54 +00:00
|
|
|
const [activeTab, setActiveTab] = useState<'overview' | 'history' | 'subscription' | 'coach'>(initialTab || 'overview');
|
2026-02-17 20:49:42 +00:00
|
|
|
const [isCoachWizardOpen, setIsCoachWizardOpen] = useState(false);
|
|
|
|
|
// Custom Hooks
|
|
|
|
|
const { stats, loadingStats } = useDashboardStats(user.id);
|
|
|
|
|
const { history, loadingHistory } = useDashboardHistory(user.id);
|
|
|
|
|
const { coachPlan, setCoachPlan, coachHistory } = useCoachPlan(user.id);
|
|
|
|
|
|
|
|
|
|
// WhatsApp Config
|
|
|
|
|
const [whatsappNumber, setWhatsappNumber] = useState("5541999999999"); // Default fallback
|
|
|
|
|
|
|
|
|
|
const fetchSystemSettings = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const { data } = await supabase
|
|
|
|
|
.from('app_settings')
|
|
|
|
|
.select('value')
|
|
|
|
|
.eq('key', 'whatsapp_number')
|
|
|
|
|
.maybeSingle();
|
|
|
|
|
|
|
|
|
|
if (data && data.value) {
|
|
|
|
|
setWhatsappNumber(data.value);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to fetch settings", err);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchSystemSettings();
|
|
|
|
|
|
|
|
|
|
// Realtime Subscription: Escuta alterações na tabela app_settings
|
|
|
|
|
const settingsChannel = supabase
|
|
|
|
|
.channel('public:app_settings')
|
|
|
|
|
.on(
|
|
|
|
|
'postgres_changes',
|
|
|
|
|
{
|
|
|
|
|
event: '*', // Escuta INSERT e UPDATE
|
|
|
|
|
schema: 'public',
|
|
|
|
|
table: 'app_settings',
|
|
|
|
|
filter: 'key=eq.whatsapp_number',
|
|
|
|
|
},
|
|
|
|
|
(payload) => {
|
|
|
|
|
if (payload.new && (payload.new as any).value) {
|
|
|
|
|
setWhatsappNumber((payload.new as any).value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
.subscribe();
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
supabase.removeChannel(settingsChannel);
|
|
|
|
|
};
|
|
|
|
|
}, [user.id]);
|
|
|
|
|
|
|
|
|
|
const whatsappUrl = `https://wa.me/${whatsappNumber}?text=Oi`;
|
|
|
|
|
const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(whatsappUrl)}`;
|
|
|
|
|
|
|
|
|
|
const handleStripePortal = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
|
if (!session) {
|
|
|
|
|
alert("Sessão expirada. Faça login novamente.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 21:32:47 +00:00
|
|
|
const response = await fetch(`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/stripe-portal`, {
|
2026-02-17 20:49:42 +00:00
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Authorization": `Bearer ${session.access_token}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) throw new Error("Erro ao gerar link do portal");
|
|
|
|
|
|
|
|
|
|
const { url } = await response.json();
|
|
|
|
|
if (url) {
|
|
|
|
|
window.location.href = url;
|
|
|
|
|
} else {
|
|
|
|
|
alert("Erro: URL do portal não retornada.");
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Erro no portal:", error);
|
|
|
|
|
alert("Não foi possível acessar o portal de pagamentos.");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Helper para o nome do plano (Correção do bug de nome vazio)
|
|
|
|
|
const getPlanLabel = () => {
|
|
|
|
|
if (user.plan === 'pro') return 'PRO';
|
|
|
|
|
if (user.plan === 'trial') return 'Trial';
|
|
|
|
|
// Traduções manuais para o plano gratuito
|
|
|
|
|
if (language === 'pt') return 'Gratuito';
|
|
|
|
|
if (language === 'es') return 'Gratis';
|
|
|
|
|
return 'Free';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const planName = getPlanLabel();
|
|
|
|
|
const fallbackImage = "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=400&q=80";
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="min-h-screen bg-gray-50 flex font-sans text-gray-900">
|
|
|
|
|
|
|
|
|
|
{/* Sidebar Navigation */}
|
|
|
|
|
<Sidebar
|
|
|
|
|
user={user}
|
|
|
|
|
activeTab={activeTab}
|
|
|
|
|
setActiveTab={setActiveTab}
|
|
|
|
|
onLogout={onLogout}
|
|
|
|
|
onOpenAdmin={onOpenAdmin}
|
|
|
|
|
onOpenPro={onOpenPro}
|
|
|
|
|
t={t}
|
|
|
|
|
coachHistory={coachHistory}
|
|
|
|
|
onSelectCoachPlan={(plan) => {
|
|
|
|
|
setCoachPlan(plan);
|
|
|
|
|
setActiveTab('coach');
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Mobile Bottom Navigation */}
|
|
|
|
|
<MobileNav
|
|
|
|
|
activeTab={activeTab}
|
|
|
|
|
setActiveTab={setActiveTab}
|
|
|
|
|
t={t}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<main className="flex-1 md:ml-64 p-4 md:p-8 pb-24 md:pb-8">
|
|
|
|
|
|
|
|
|
|
{/* Mobile Header */}
|
|
|
|
|
<div className="md:hidden flex justify-between items-center mb-6">
|
|
|
|
|
<span className="font-bold text-lg">FoodSnap</span>
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
{onOpenAdmin && (
|
|
|
|
|
<button onClick={onOpenAdmin} className="p-2 text-gray-500 hover:text-red-600">
|
|
|
|
|
<ShieldAlert size={20} />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
<button onClick={onLogout}><LogOut size={20} className="text-gray-500" /></button>
|
|
|
|
|
</div>
|
|
|
|
|
</div >
|
|
|
|
|
|
|
|
|
|
{/* Content Switcher */}
|
|
|
|
|
{activeTab === 'overview' && (
|
|
|
|
|
<DashboardOverview
|
|
|
|
|
user={user}
|
|
|
|
|
stats={stats}
|
|
|
|
|
loadingStats={loadingStats}
|
|
|
|
|
history={history}
|
|
|
|
|
loadingHistory={loadingHistory}
|
|
|
|
|
planName={planName}
|
|
|
|
|
t={t}
|
|
|
|
|
whatsappUrl={whatsappUrl}
|
|
|
|
|
qrCodeUrl={qrCodeUrl}
|
|
|
|
|
whatsappNumber={whatsappNumber}
|
|
|
|
|
setActiveTab={setActiveTab}
|
|
|
|
|
fallbackImage={fallbackImage}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{activeTab === 'history' && (
|
|
|
|
|
<DashboardHistory
|
|
|
|
|
history={history}
|
|
|
|
|
loadingHistory={loadingHistory}
|
|
|
|
|
t={t}
|
|
|
|
|
fallbackImage={fallbackImage}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{activeTab === 'subscription' && (
|
|
|
|
|
<DashboardSubscription
|
|
|
|
|
user={user}
|
|
|
|
|
planName={planName}
|
|
|
|
|
t={t}
|
|
|
|
|
handleStripePortal={handleStripePortal}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{activeTab === 'coach' && (
|
|
|
|
|
<DashboardCoach
|
|
|
|
|
coachPlan={coachPlan}
|
|
|
|
|
setCoachPlan={setCoachPlan}
|
|
|
|
|
coachHistory={coachHistory}
|
|
|
|
|
setIsCoachWizardOpen={setIsCoachWizardOpen}
|
2026-02-17 22:07:10 +00:00
|
|
|
userPlan={user.plan}
|
2026-02-17 20:49:42 +00:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
</main >
|
|
|
|
|
|
|
|
|
|
<CoachWizard
|
|
|
|
|
isOpen={isCoachWizardOpen}
|
|
|
|
|
onClose={() => setIsCoachWizardOpen(false)}
|
|
|
|
|
onComplete={(data: any) => {
|
|
|
|
|
console.log("Wizard Completed:", data);
|
|
|
|
|
setCoachPlan(data);
|
|
|
|
|
setIsCoachWizardOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div >
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default Dashboard;
|