Files
ember-market-frontend/components/dashboard/recent-customers-widget.tsx
g fe01f31538
Some checks failed
Build Frontend / build (push) Failing after 7s
Refactor UI imports and update component paths
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.
2026-01-13 05:02:13 +00:00

157 lines
6.9 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/common/card"
import { Button } from "@/components/common/button"
import { Skeleton } from "@/components/common/skeleton"
import { Users, User, ArrowRight, DollarSign } from "lucide-react"
import { getCustomerInsightsWithStore, formatGBP } from "@/lib/api"
import { Avatar, AvatarFallback } from "@/components/common/avatar"
import Link from "next/link"
interface RecentCustomersWidgetProps {
settings?: {
itemCount?: number
showSpent?: boolean
}
}
interface Customer {
id: string
name: string
username?: string
orderCount: number
totalSpent: number
}
export default function RecentCustomersWidget({ settings }: RecentCustomersWidgetProps) {
const itemCount = settings?.itemCount || 5
const showSpent = settings?.showSpent !== false
const [customers, setCustomers] = useState<Customer[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const fetchCustomers = async () => {
try {
setIsLoading(true)
setError(null)
// The API returns topCustomers, but we'll use 'recent' sorting to show new engagement
const response = await getCustomerInsightsWithStore(1, itemCount, "recent")
const mappedCustomers = (response.topCustomers || []).map((c: any) => ({
id: c._id,
name: c.displayName || c.username || `Customer ${c._id.slice(-4)}`,
username: c.username,
orderCount: c.orderCount || 0,
totalSpent: c.totalSpent || 0
}))
setCustomers(mappedCustomers)
} catch (err) {
console.error("Error fetching customers:", err)
setError("Failed to load customer data")
} finally {
setIsLoading(false)
}
}
useEffect(() => {
fetchCustomers()
}, [itemCount])
if (isLoading) {
return (
<Card className="border-border/40 bg-background/50 backdrop-blur-sm">
<CardHeader className="pb-2">
<Skeleton className="h-5 w-32 mb-1" />
<Skeleton className="h-4 w-48" />
</CardHeader>
<CardContent className="space-y-4">
{[1, 2, 3].map((i) => (
<div key={i} className="flex items-center gap-4">
<Skeleton className="h-10 w-10 rounded-full" />
<div className="space-y-2 flex-1">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-3 w-1/4" />
</div>
</div>
))}
</CardContent>
</Card>
)
}
return (
<Card className="border-border/40 bg-background/50 backdrop-blur-sm overflow-hidden flex flex-col h-full">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<div className="space-y-1">
<CardTitle className="flex items-center gap-2">
<Users className="h-5 w-5 text-indigo-500" />
Recent Customers
</CardTitle>
<CardDescription>
Latest and newest connections
</CardDescription>
</div>
<Link href="/dashboard/customers">
<Button variant="ghost" size="sm" className="h-8 gap-1.5 text-xs">
View All
<ArrowRight className="h-3 w-3" />
</Button>
</Link>
</CardHeader>
<CardContent className="pt-4 flex-grow">
{error ? (
<div className="flex flex-col items-center justify-center py-10 text-center">
<User className="h-10 w-10 text-muted-foreground/20 mb-3" />
<p className="text-sm text-muted-foreground">{error}</p>
</div>
) : customers.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="h-12 w-12 rounded-full bg-indigo-500/10 flex items-center justify-center mb-4">
<Users className="h-6 w-6 text-indigo-500" />
</div>
<h3 className="font-medium">No customers yet</h3>
<p className="text-sm text-muted-foreground mt-1 max-w-xs">
This widget will populate once people start browsing and buying.
</p>
</div>
) : (
<div className="space-y-1">
{customers.map((customer) => (
<div
key={customer.id}
className="flex items-center gap-4 p-3 rounded-xl hover:bg-muted/50 transition-colors group"
>
<Avatar className="h-10 w-10 border shadow-sm group-hover:scale-105 transition-transform">
<AvatarFallback className="bg-indigo-500/10 text-indigo-600 text-xs font-bold">
{customer.name.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex-grow min-w-0">
<h4 className="font-semibold text-sm truncate group-hover:text-primary transition-colors">{customer.name}</h4>
<div className="flex items-center gap-2 mt-0.5">
<span className="text-xs text-muted-foreground">
{customer.orderCount} order{customer.orderCount !== 1 ? 's' : ''}
</span>
</div>
</div>
{showSpent && (
<div className="text-right">
<div className="text-sm font-bold text-foreground">
{formatGBP(customer.totalSpent)}
</div>
<div className="text-[10px] text-muted-foreground font-medium uppercase tracking-tighter">Total Spent</div>
</div>
)}
</div>
))}
</div>
)}
</CardContent>
</Card>
)
}