"use client";
import { useEffect } from "react";
import { motion } from "framer-motion";
import {
TrendingUp, TrendingDown, DollarSign, CreditCard,
ArrowUpRight, ArrowDownRight, Sparkles, Shield,
ChevronRight, Activity, AlertTriangle
} from "lucide-react";
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip,
ResponsiveContainer, PieChart, Pie, Cell
} from "recharts";
import { useDashboardStore } from "@/lib/stores/dashboardStore";
import { useAuthStore } from "@/lib/stores/authStore";
import { useLanguageStore } from "@/lib/stores/languageStore";
import { useThemeStore } from "@/lib/stores/themeStore";
import Link from "next/link";
// ─── Category colors ──────────────────────────────────────────────────────────
const CATEGORY_COLORS = [
"#3b82f6", "#10b981", "#f59e0b", "#8b5cf6", "#ec4899",
"#06b6d4", "#84cc16", "#f97316", "#6b7280",
];
// ─── Animation variants ───────────────────────────────────────────────────────
const containerVariants = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { staggerChildren: 0.08 } },
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: "easeOut" as const } },
};
// ─── Skeleton loader ──────────────────────────────────────────────────────────
function Skeleton({ className }: { className?: string }) {
return
;
}
// ─── Custom Tooltip ───────────────────────────────────────────────────────────
interface TooltipProps {
active?: boolean;
payload?: Array<{ name: string; value: number; color?: string }>;
label?: string;
}
function CustomTooltip({ active, payload, label }: TooltipProps) {
if (!active || !payload?.length) return null;
return (
{label}
{payload.map((entry) => (
{entry.name}
${entry.value.toLocaleString()}
))}
);
}
// ─── Stat Card ────────────────────────────────────────────────────────────────
interface StatCardProps {
title: string;
value: string;
change?: string;
positive?: boolean;
icon: React.ElementType;
gradient: string;
lightGradient: string;
iconColor: string;
borderColor: string;
lightBorderColor: string;
loading?: boolean;
isLight: boolean;
}
function StatCard({
title, value, change, positive, icon: Icon,
gradient, lightGradient, iconColor, borderColor, lightBorderColor, loading, isLight
}: StatCardProps) {
return (
{title}
{loading ? (
) : (
{value}
)}
{change && !loading && (
{positive ? (
) : (
)}
{change}
vs last month
)}
);
}
// ─── Main Page ────────────────────────────────────────────────────────────────
export default function Home() {
const { data, isLoading, error, fetch } = useDashboardStore();
const { user } = useAuthStore();
const { t } = useLanguageStore();
const { theme } = useThemeStore();
const isLight = theme === "light";
// Recharts axis tick color
const tickColor = isLight ? "#64748b" : "#71717a";
const gridColor = isLight ? "rgba(15,23,42,0.07)" : "rgba(255,255,255,0.05)";
useEffect(() => {
fetch();
}, [fetch]);
const firstName = user?.name?.split(" ")[0] || "there";
const greeting = () => {
const h = new Date().getHours();
if (h < 12) return t("good_morning");
if (h < 17) return t("good_afternoon");
return t("good_evening");
};
const briefingText = data?.ai_briefing
? (typeof data.ai_briefing === "string"
? data.ai_briefing
: data.ai_briefing.summary || data.ai_briefing.briefing || "Your financial health looks good today.")
: null;
const categoryData = (data?.spending_by_category || []).slice(0, 6).map((c, i) => ({
...c,
color: CATEGORY_COLORS[i % CATEGORY_COLORS.length],
}));
const totalCategorySpend = categoryData.reduce((s, c) => s + c.value, 0);
return (
{/* Header */}
{greeting()}, {firstName} 👋
{t("financial_overview")}
{/* Error banner */}
{error && (
{t("backend_offline")} — {t("showing_cached")}{" "}
)}
{/* AI Briefing */}
{isLoading ? (
<>
>
) : (
<>
{t("ai_daily_briefing")}{" "}
{briefingText?.slice(0, 100) || "Your financial health looks good today."}
{t("health_score")} {data?.health_score ?? "—"}/100
{data?.fraud_alert_count
? ` · ${data.fraud_alert_count} fraud alert(s)`
: ` · ${t("no_fraud_alerts")}`}
>
)}
Ask AI
{/* Stat Cards */}
20 ? "+" : ""}${(data.savings_rate - 20).toFixed(1)}% vs target` : undefined}
positive={data ? data.savings_rate >= 20 : true}
icon={Activity}
gradient="from-purple-500/20 to-violet-500/20"
lightGradient="from-purple-50 to-violet-50"
iconColor="text-purple-500"
borderColor="border-purple-500/20"
lightBorderColor="border-purple-200/70"
loading={isLoading}
isLight={isLight}
/>
{/* Charts Row */}
{/* Cash Flow Chart */}
{t("cash_flow")}
{t("income_vs_expenses")}
{[
{ label: t("income"), color: "#10b981" },
{ label: t("expenses"), color: "#f59e0b" },
{ label: t("savings"), color: "#3b82f6" },
].map((l) => (
))}
{isLoading ? (
) : (
{[
{ id: "incomeGrad", color: "#10b981" },
{ id: "expenseGrad", color: "#f59e0b" },
{ id: "savingsGrad", color: "#3b82f6" },
].map(({ id, color }) => (
))}
} />
)}
{/* Spending Breakdown */}
{t("spending_breakdown")}
{t("this_month_by_category")}
{isLoading ? (
) : (
{categoryData.map((entry, index) => (
|
))}
active && payload?.length ? (
{payload[0].name}
${(payload[0].value as number).toLocaleString()}
) : null
}
/>
)}
{isLoading
? [1, 2, 3, 4].map((i) =>
)
: categoryData.map((cat) => (
0 ? (cat.value / totalCategorySpend) * 100 : 0}%`,
background: cat.color,
}}
/>
${cat.value.toLocaleString()}
))}
{/* Recent Transactions */}
{t("recent_transactions")}
{t("latest_activity")}
{t("view_all")}
{isLoading
? [1, 2, 3, 4, 5].map((i) =>
)
: (data?.recent_transactions || []).map((tx, i) => (
{tx.merchant.charAt(0).toUpperCase()}
{tx.merchant}
{tx.category} · {tx.timestamp ? new Date(tx.timestamp).toLocaleDateString() : ""}
0 ? "text-emerald-500" : ""}`}
style={tx.amount <= 0 ? { color: "var(--fg)" } : undefined}
>
{tx.amount > 0 ? "+" : ""}${Math.abs(tx.amount).toFixed(2)}
{tx.amount > 0 ? (
) : (
)}
))}
{/* Fraud Shield */}
{t("fraud_shield_active")}
{data?.fraud_alert_count
? ` — ${data.fraud_alert_count} ${t("alerts_require_attention")}`
: ` — ${t("all_monitored")}`}
{data?.fraud_alert_count ? (
{t("review")}
) : null}
);
}