"use client"
import { useCallback, useEffect, useState } from "react"
import Link from "next/link"
import {
TrendingUp,
ArrowUpRight,
Loader2,
Calendar,
ChevronDown,
Package,
ShoppingCart,
Users,
Lightbulb,
TrendingDown,
Minus,
Sparkles,
} from "lucide-react"
import {
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
AreaChart,
Area,
LineChart,
Line,
BarChart,
Bar,
PieChart,
Pie,
Cell,
Legend,
} from "recharts"
import { useTheme } from "@/components/ThemeProvider"
import { cn } from "@/lib/utils"
type TimeRange = "7days" | "30days" | "1year"
type BusinessMetricsPayload = {
currency: string
disclaimer: string
kpis: {
key: string
label: string
value: number
changePct: number
suffix?: string
sparkline: number[]
}[]
monthlyProfitBars: { month: string; profit: number }[]
feeDonut: { name: string; value: number }[]
feeTotal: number
productMargins: {
product: string
unitsSold: number
cogs: number
netProfit: number
marginPct: number
trend: "up" | "down" | "flat"
refundUnits: number
}[]
aiInsight: string
totals: { gross: number; refunds: number; orders: number }
}
function formatMoney(amount: number, currency: string) {
try {
return new Intl.NumberFormat(undefined, { style: "currency", currency, maximumFractionDigits: 0 }).format(amount)
} catch {
return `${currency} ${amount.toFixed(0)}`
}
}
function MiniSpark({
data,
color,
}: {
data: number[]
color: string
}) {
const pts = data.map((y, i) => ({ i, y }))
return (
)
}
export default function DashboardPage() {
const { theme } = useTheme()
const isLight = theme === "light"
const [snapshot, setSnapshot] = useState({
totalSales: 0,
products: 0,
orders: 0,
customers: 0,
})
const [business, setBusiness] = useState(null)
const [chartData, setChartData] = useState<{ date: string; amount: number }[]>([])
const [loading, setLoading] = useState(true)
const [bizErr, setBizErr] = useState(null)
const [timeRange, setTimeRange] = useState("7days")
const fetchDashboardData = useCallback(async () => {
try {
setLoading(true)
const [productsRes, ordersRes, customersRes] = await Promise.all([
fetch("/api/shopify/inventory").catch(() => ({
ok: false,
json: async () => ({ products: [], message: "Failed to fetch products" }),
})),
fetch("/api/shopify/orders").catch(() => ({
ok: false,
json: async () => ({ orders: [], message: "Failed to fetch orders" }),
})),
fetch("/api/shopify/customers").catch(() => ({
ok: false,
json: async () => ({ customers: [], message: "Failed to fetch customers" }),
})),
])
const [productsData, ordersData, customersData] = await Promise.all([
productsRes.json().catch(() => ({ products: [] })),
ordersRes.json().catch(() => ({ orders: [] })),
customersRes.json().catch(() => ({ customers: [] })),
])
try {
const bizRes = await fetch("/api/dashboard/business-metrics")
const j = await bizRes.json().catch(() => null)
if (bizRes.ok && j && typeof j === "object" && !("message" in j && typeof j.message === "string")) {
setBusiness(j as BusinessMetricsPayload)
setBizErr(null)
} else {
setBusiness(null)
setBizErr(typeof j?.message === "string" ? j.message : null)
}
} catch {
setBusiness(null)
setBizErr(null)
}
const orders = ordersData.orders || []
const totalSales =
orders.reduce((sum: number, order: { total_price?: string }) => sum + parseFloat(order.total_price ?? "0"), 0) || 0
const generateDateRange = (range: TimeRange) => {
const dates = []
const now = new Date()
if (range === "1year") {
for (let i = 11; i >= 0; i--) {
const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
dates.push(d.toLocaleDateString("en-US", { month: "short", year: "2-digit" }))
}
} else {
const count = range === "30days" ? 30 : 7
for (let i = count - 1; i >= 0; i--) {
const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i)
dates.push(d.toLocaleDateString("en-US", { month: "short", day: "numeric" }))
}
}
return dates
}
const rangeLabels = generateDateRange(timeRange)
const salesByDate: Record = {}
orders.forEach((order: { created_at?: string; total_price?: string }) => {
const orderDate = new Date(order.created_at ?? "")
let label = ""
if (timeRange === "1year") {
label = orderDate.toLocaleDateString("en-US", { month: "short", year: "2-digit" })
} else {
label = orderDate.toLocaleDateString("en-US", { month: "short", day: "numeric" })
}
if (rangeLabels.includes(label)) {
salesByDate[label] = (salesByDate[label] || 0) + parseFloat(order.total_price ?? "0")
}
})
const processedChartData = rangeLabels.map((date) => ({
date,
amount: Math.round((salesByDate[date] || 0) * 100) / 100,
}))
setChartData(processedChartData)
setSnapshot({
totalSales,
products: productsData.products?.length ?? 0,
orders: orders.length,
customers: customersData.customers?.length ?? 0,
})
} catch (error) {
console.error("Dashboard stats fetch error:", error)
} finally {
setLoading(false)
}
}, [timeRange])
useEffect(() => {
void fetchDashboardData()
}, [fetchDashboardData])
const currency = business?.currency ?? "USD"
const gridStroke = isLight ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.08)"
const chartMuted = isLight ? "#64748b" : "#94a3b8"
const donutColors = ["#6366f1", "#a855f7", "#f43f5e", "#f97316"]
const kpiAccent: Record = {
netProfit: "#22c55e",
grossRevenue: "#3b82f6",
fees: "#f43f5e",
margin: "#22c55e",
}
return (
Overview
Shopify snapshot plus estimated profit, fees, and product economics from your synced orders.
{snapshot.orders} orders
{snapshot.products} products
{snapshot.customers} customers
AI analytics
setTimeRange(e.target.value as TimeRange)}
className={cn(
"appearance-none h-11 pl-10 pr-10 rounded-xl font-bold border transition-all text-sm cursor-pointer focus:outline-none focus:ring-2 focus:ring-indigo-500/20",
isLight
? "bg-white border-slate-200 text-slate-700 hover:border-slate-300 shadow-sm"
: "bg-white/10 border-white/10 text-white hover:bg-white/15 backdrop-blur-sm",
)}
>
Last 7 Days
Last 1 Month
Last 1 Year
Export
{/* Financial KPI row — Image 2 style */}
{(business?.kpis ?? []).map((kpi) => (
{kpi.label}
{loading && !business ? (
) : kpi.suffix ? (
`${kpi.value.toFixed(1)}${kpi.suffix}`
) : (
formatMoney(kpi.value, currency)
)}
= 0 ? "text-emerald-500" : "text-rose-400",
)}
>
{kpi.changePct >= 0 ? "+" : ""}
{kpi.changePct}% vs prior month
))}
{!business && !loading && (
{bizErr
? `Could not load estimated profit metrics: ${bizErr}.`
: "Estimated profit and fee cards will appear once synced order history is available for your active store."}
)}
{/* Profit bars + fee donut */}
{business && (
Monthly profit (est.)
Based on gross revenue × assumed net margin ({business.disclaimer.slice(0, 80)}…)
formatMoney(Number(v), currency)}
/>
[formatMoney(Number(v ?? 0), currency), "Profit (est.)"]}
/>
Fee breakdown (est.)
Total estimated fees {formatMoney(business.feeTotal, currency)}
{business.feeDonut.map((_, i) => (
|
))}
formatMoney(Number(v ?? 0), currency)}
contentStyle={{
borderRadius: 12,
border: isLight ? "1px solid #e2e8f0" : "1px solid rgba(255,255,255,0.12)",
background: isLight ? "#fff" : "#0f172a",
fontWeight: 700,
}}
/>
)}
{/* Snapshot icons row — compact */}
{[
{ label: "Live revenue (period)", value: formatMoney(snapshot.totalSales, currency), icon: TrendingUp },
{ label: "Catalog size", value: String(snapshot.products), icon: Package },
{ label: "Orders in sync", value: String(snapshot.orders), icon: ShoppingCart },
{ label: "Customers", value: String(snapshot.customers), icon: Users },
].map((row) => (
))}
{/* Product margins + AI insight */}
{business && business.productMargins.length > 0 && (
Product-level margins (est.)
Product
Units
Est. COGS
Est. net
Margin
Trend
{business.productMargins.map((r) => (
{r.product}
{r.unitsSold}
{formatMoney(r.cogs, currency)}
{formatMoney(r.netProfit, currency)}
{r.marginPct}%
{r.trend === "up" && }
{r.trend === "down" && }
{r.trend === "flat" && }
))}
)}
Recent Sales Trend
Revenue analysis over time
{(["7days", "30days", "1year"] as const).map((r) => (
setTimeRange(r)}
className={cn(
"px-3 py-1.5 rounded-md text-[10px] font-bold uppercase tracking-wider transition-all",
timeRange === r
? "bg-white dark:bg-indigo-600 text-indigo-600 dark:text-white shadow-sm"
: "text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-white",
)}
>
{r === "7days" ? "7D" : r === "30days" ? "1M" : "1Y"}
))}
{loading ? (
) : chartData.length > 0 ? (
`$${val > 999 ? (val / 1000).toFixed(1) + "k" : val}`}
/>
{
const num = typeof value === "number" ? value : Number(value)
const v = Number.isFinite(num) ? num : 0
return [`$${v.toLocaleString()}`, "Revenue"]
}}
/>
) : (
No sales data found for this period.
)}
)
}