Refactor UI imports and update component paths
Some checks failed
Build Frontend / build (push) Failing after 7s

Replaces imports from 'components/ui' with 'components/common' across the app and dashboard pages, and updates model and API imports to use new paths under 'lib'. Removes redundant authentication checks from several dashboard pages. Adds new dashboard components and utility files, and reorganizes hooks and services into the 'lib' directory for improved structure.
This commit is contained in:
g
2026-01-13 05:02:13 +00:00
parent a6e6cd0757
commit fe01f31538
173 changed files with 1512 additions and 867 deletions

View File

@@ -8,43 +8,47 @@ import { WidgetSettings } from "./widget-settings"
import { WidgetSettingsModal } from "./widget-settings-modal"
import { DashboardEditor } from "./dashboard-editor"
import { DraggableWidget } from "./draggable-widget"
import { CommandPalette } from "./command-palette"
import RevenueWidget from "./revenue-widget"
import LowStockWidget from "./low-stock-widget"
import RecentCustomersWidget from "./recent-customers-widget"
import { ProductPeek } from "./product-peek"
import PendingChatsWidget from "./pending-chats-widget"
import { getGreeting } from "@/lib/utils/general"
import { statsConfig } from "@/config/dashboard"
import { getRandomQuote } from "@/config/quotes"
import type { OrderStatsData } from "@/lib/types"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/common/card"
import { ShoppingCart, RefreshCcw, ArrowRight } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useToast } from "@/components/ui/use-toast"
import { Skeleton } from "@/components/ui/skeleton"
import { Button } from "@/components/common/button"
import { useToast } from "@/components/common/use-toast"
import { Skeleton } from "@/components/common/skeleton"
import { clientFetch } from "@/lib/api"
import { motion } from "framer-motion"
import Link from "next/link"
import { useWidgetLayout, WidgetConfig } from "@/hooks/useWidgetLayout"
import { useWidgetLayout } from "@/lib/hooks/useWidgetLayout"
import type { TopProduct, WidgetConfig } from "@/lib/types/dashboard"
interface ContentProps {
username: string
orderStats: OrderStatsData
}
interface TopProduct {
id: string;
name: string;
price: number | number[];
image: string;
count: number;
revenue: number;
}
// TopProduct interface moved to @/lib/types/dashboard
export default function Content({ username, orderStats }: ContentProps) {
const [greeting, setGreeting] = useState("");
const [topProducts, setTopProducts] = useState<TopProduct[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [errorQuery, setErrorQuery] = useState(false);
const [selectedProductId, setSelectedProductId] = useState<string | null>(null);
const [isProductPeekOpen, setIsProductPeekOpen] = useState(false);
const [trends, setTrends] = useState<any[]>([]);
const handleProductPeek = (id: string) => {
setSelectedProductId(id);
setIsProductPeekOpen(true);
};
const { toast } = useToast();
const { widgets, toggleWidget, moveWidget, reorderWidgets, resetLayout, isWidgetVisible, updateWidgetSettings } = useWidgetLayout();
const [configuredWidget, setConfiguredWidget] = useState<WidgetConfig | null>(null);
@@ -59,14 +63,25 @@ export default function Content({ username, orderStats }: ContentProps) {
const fetchTopProducts = async () => {
try {
setIsLoading(true);
setLoading(true);
const data = await clientFetch('/orders/top-products');
setTopProducts(data);
} catch (err) {
console.error("Error fetching top products:", err);
setError(err instanceof Error ? err.message : "Failed to fetch top products");
setErrorQuery(true);
} finally {
setIsLoading(false);
setLoading(false);
}
};
const fetchTrends = async () => {
try {
const data = await clientFetch('/analytics/revenue-trends?period=30');
if (Array.isArray(data)) {
setTrends(data);
}
} catch (err) {
console.error("Error fetching trends:", err);
}
};
@@ -88,16 +103,35 @@ export default function Content({ username, orderStats }: ContentProps) {
<section className="space-y-4">
<h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground ml-1">Overview</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{statsConfig.map((stat, index) => (
<OrderStats
key={stat.title}
title={stat.title}
value={orderStats[stat.key as keyof OrderStatsData]?.toLocaleString() || "0"}
icon={stat.icon}
index={index}
filterStatus={stat.filterStatus}
/>
))}
{statsConfig.map((stat, index) => {
const colors = ["#8884d8", "#10b981", "#3b82f6", "#f59e0b"];
const val = Number(orderStats[stat.key as keyof OrderStatsData]) || 0;
// Map real trend data if available, otherwise fallback to empty (or subtle random if just started)
let trendData = trends.map(t => ({
value: stat.key === "revenue" ? t.revenue : t.orders
})).slice(-12); // Last 12 points
// Fallback for demo/new stores if no trends yet
if (trendData.length === 0) {
trendData = Array.from({ length: 12 }, (_, i) => ({
value: Math.max(0, val * (0.8 + Math.random() * 0.4 + (i / 20)))
}));
}
return (
<OrderStats
key={stat.title}
title={stat.title}
value={orderStats[stat.key as keyof OrderStatsData]?.toLocaleString() || "0"}
icon={stat.icon}
index={index}
filterStatus={stat.filterStatus}
trendData={trendData}
trendColor={colors[index % colors.length]}
/>
);
})}
</div>
</section>
);
@@ -118,7 +152,7 @@ export default function Content({ username, orderStats }: ContentProps) {
<CardTitle>Top Performing Listings</CardTitle>
<CardDescription>Your products with the highest sales volume</CardDescription>
</div>
{error && (
{errorQuery && (
<Button
variant="outline"
size="sm"
@@ -131,7 +165,7 @@ export default function Content({ username, orderStats }: ContentProps) {
)}
</CardHeader>
<CardContent>
{isLoading ? (
{loading ? (
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="flex items-center gap-4">
@@ -144,7 +178,7 @@ export default function Content({ username, orderStats }: ContentProps) {
</div>
))}
</div>
) : error ? (
) : errorQuery ? (
<div className="py-12 text-center">
<div className="text-muted-foreground mb-4">Failed to load product insights</div>
</div>
@@ -164,7 +198,8 @@ export default function Content({ username, orderStats }: ContentProps) {
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 + index * 0.05 }}
className="flex items-center gap-4 p-3 rounded-xl hover:bg-muted/50 transition-colors group"
className="flex items-center gap-4 p-3 rounded-xl hover:bg-white/5 transition-colors group cursor-pointer"
onClick={() => handleProductPeek(product.id)}
>
<div
className="h-14 w-14 bg-muted bg-cover bg-center rounded-xl border flex-shrink-0 flex items-center justify-center overflow-hidden group-hover:scale-105 transition-transform"
@@ -179,15 +214,19 @@ export default function Content({ username, orderStats }: ContentProps) {
)}
</div>
<div className="flex-grow min-w-0">
<h4 className="font-semibold text-lg truncate group-hover:text-primary transition-colors">{product.name}</h4>
<div className="flex items-center gap-3 mt-0.5">
<span className="text-sm text-muted-foreground font-medium">£{(Number(Array.isArray(product.price) ? product.price[0] : product.price) || 0).toFixed(2)}</span>
</div>
<h4 className="font-semibold text-lg truncate group-hover:text-primary transition-colors pr-4">{product.name}</h4>
</div>
<div className="text-right">
<div className="text-xl font-bold">{product.count}</div>
<div className="text-xs text-muted-foreground font-medium uppercase tracking-tighter mb-1">Units Sold</div>
<div className="text-sm font-semibold text-primary">£{product.revenue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</div>
<div className="flex shrink-0 items-center gap-6 pr-4 text-right">
<div className="flex flex-col items-end">
<span className={`text-xl font-bold leading-none ${product.currentStock && product.currentStock <= 5 ? 'text-amber-500' : 'text-muted-foreground'}`}>
{product.currentStock ?? 0}
</span>
<span className="text-[10px] text-muted-foreground font-bold uppercase tracking-widest mt-1">Stock</span>
</div>
<div className="flex flex-col items-end">
<span className="text-2xl font-black text-white leading-none">{product.count}</span>
<span className="text-[10px] text-muted-foreground font-bold uppercase tracking-widest mt-1">Sold</span>
</div>
</div>
</motion.div>
))}
@@ -213,10 +252,21 @@ export default function Content({ username, orderStats }: ContentProps) {
useEffect(() => {
setGreeting(getGreeting());
fetchTopProducts();
fetchTrends();
}, []);
return (
<div className="space-y-10 pb-10">
<div className="space-y-8 pb-10">
<ProductPeek
productId={selectedProductId}
open={isProductPeekOpen}
onOpenChange={setIsProductPeekOpen}
/>
<CommandPalette
onResetLayout={resetLayout}
onToggleWidget={toggleWidget}
availableWidgets={widgets}
/>
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
@@ -248,7 +298,7 @@ export default function Content({ username, orderStats }: ContentProps) {
onReorder={reorderWidgets}
onReset={resetLayout}
>
<div className="space-y-10">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{widgets.map((widget) => {
if (!widget.visible) return null;
@@ -280,3 +330,5 @@ export default function Content({ username, orderStats }: ContentProps) {
);
}