117 lines
4.5 KiB
TypeScript
117 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Users, UserCheck, Crown, Zap } from "lucide-react";
|
|
import type { AdminUser } from "./types";
|
|
import { PLAN_LABELS } from "./types";
|
|
|
|
interface UserStatsProps {
|
|
users: AdminUser[];
|
|
total: number;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
export function UserStats({ users, total, isLoading }: UserStatsProps) {
|
|
const activeUsers = users.filter((u) => u.subscription_status === "active").length;
|
|
const proUsers = users.filter((u) => u.plan === "pro" || u.plan === "business" || u.plan === "enterprise").length;
|
|
const freeUsers = users.filter((u) => u.plan === "free" || u.plan === "starter").length;
|
|
|
|
const planDistribution = users.reduce(
|
|
(acc, user) => {
|
|
acc[user.plan] = (acc[user.plan] || 0) + 1;
|
|
return acc;
|
|
},
|
|
{} as Record<string, number>
|
|
);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
{[1, 2, 3, 4].map((i) => (
|
|
<Card key={i} className="animate-pulse">
|
|
<CardContent className="flex items-center gap-3 p-4">
|
|
<div className="size-9 rounded-lg bg-muted" />
|
|
<div className="flex-1 space-y-1">
|
|
<div className="h-3 w-16 rounded bg-muted" />
|
|
<div className="h-5 w-10 rounded bg-muted" />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
<Card className="py-0">
|
|
<CardContent className="flex items-center gap-3 px-4 py-3">
|
|
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-secondary">
|
|
<Users className="size-4 text-foreground" />
|
|
</div>
|
|
<div className="flex flex-1 flex-col gap-0.5">
|
|
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
Total Users
|
|
</span>
|
|
<span className="text-lg font-semibold text-foreground">{total}</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="py-0">
|
|
<CardContent className="flex items-center gap-3 px-4 py-3">
|
|
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-[oklch(0.59_0.16_145/0.1)]">
|
|
<UserCheck className="size-4 text-[oklch(0.59_0.16_145)]" />
|
|
</div>
|
|
<div className="flex flex-1 flex-col gap-0.5">
|
|
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
Active This Month
|
|
</span>
|
|
<span className="text-lg font-semibold text-foreground">{activeUsers}</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="py-0">
|
|
<CardContent className="flex items-center gap-3 px-4 py-3">
|
|
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-[oklch(0.70_0.14_255/0.1)]">
|
|
<Crown className="size-4 text-[oklch(0.70_0.14_255)]" />
|
|
</div>
|
|
<div className="flex flex-1 flex-col gap-0.5">
|
|
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
Pro Users
|
|
</span>
|
|
<span className="text-lg font-semibold text-foreground">{proUsers}</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="py-0">
|
|
<CardContent className="flex items-center gap-3 px-4 py-3">
|
|
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted">
|
|
<Zap className="size-4 text-muted-foreground" />
|
|
</div>
|
|
<div className="flex flex-1 flex-col gap-0.5">
|
|
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
Free Users
|
|
</span>
|
|
<span className="text-lg font-semibold text-foreground">{freeUsers}</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{Object.entries(planDistribution).length > 0 && (
|
|
<div className="col-span-2 flex flex-wrap items-center gap-2 md:col-span-4">
|
|
<span className="text-xs text-muted-foreground">Distribution:</span>
|
|
{Object.entries(planDistribution).map(([plan, count]) => (
|
|
<Badge key={plan} variant="outline" className="text-xs">
|
|
{PLAN_LABELS[plan as keyof typeof PLAN_LABELS] || plan}: {count}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|