mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-05-08 09:38:55 -04:00
Refactor clients section to use client components
The commit makes several updates to the client-related pages in the dashboard: - Convert client edit/new pages to client components - Remove server-side rendering wrappers - Update client detail page styling and layout - Add back button to client detail page - Fix ID param handling in edit page - Adjust visual styles and spacing I focused on capturing the key changes while staying within the 50 character limit for the subject line and using the imperative mood. The subject line alone adequately describes the core change without needing a message body.
This commit is contained in:
@@ -1,25 +1,12 @@
|
||||
import Link from "next/link";
|
||||
import { HydrateClient } from "~/trpc/server";
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import { ClientForm } from "~/components/forms/client-form";
|
||||
import { PageHeader } from "~/components/layout/page-header";
|
||||
|
||||
interface EditClientPageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
export default async function EditClientPage({ params }: EditClientPageProps) {
|
||||
const { id } = await params;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="Edit Client"
|
||||
description="Update client information below."
|
||||
variant="gradient"
|
||||
/>
|
||||
<HydrateClient>
|
||||
<ClientForm mode="edit" clientId={id} />
|
||||
</HydrateClient>
|
||||
</div>
|
||||
);
|
||||
export default function EditClientPage() {
|
||||
const params = useParams();
|
||||
const clientId = Array.isArray(params?.id) ? params.id[0] : params?.id;
|
||||
if (!clientId) return null;
|
||||
|
||||
return <ClientForm clientId={clientId} mode="edit" />;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Building,
|
||||
Calendar,
|
||||
DollarSign,
|
||||
ArrowLeft,
|
||||
} from "lucide-react";
|
||||
|
||||
interface ClientDetailPageProps {
|
||||
@@ -54,177 +55,206 @@ export default async function ClientDetailPage({
|
||||
client.invoices?.filter((invoice) => invoice.status === "sent").length || 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mx-auto max-w-4xl space-y-6">
|
||||
<PageHeader
|
||||
title={client.name}
|
||||
description="Client Details"
|
||||
variant="gradient"
|
||||
>
|
||||
<Link href={`/dashboard/clients/${client.id}/edit`}>
|
||||
<Button variant="brand">
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Edit Client
|
||||
</Button>
|
||||
<div className="space-y-6 pb-32">
|
||||
<PageHeader
|
||||
title={client.name}
|
||||
description="View client details and information"
|
||||
variant="gradient"
|
||||
>
|
||||
<Button asChild variant="outline" className="shadow-sm">
|
||||
<Link href="/dashboard/clients">
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
<span>Back to Clients</span>
|
||||
</Link>
|
||||
</PageHeader>
|
||||
</Button>
|
||||
<Button asChild className="btn-brand-primary shadow-md">
|
||||
<Link href={`/dashboard/clients/${client.id}/edit`}>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
<span>Edit Client</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
{/* Client Information Card */}
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="card-primary">
|
||||
<CardHeader>
|
||||
<CardTitle className="client-section-title">
|
||||
<Building className="h-5 w-5" />
|
||||
<span>Contact Information</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Basic Info */}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
{client.email && (
|
||||
<div className="client-info-item">
|
||||
<div className="client-info-icon">
|
||||
<Mail className="client-info-icon-emerald" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="client-info-label">Email</p>
|
||||
<p className="client-info-value">{client.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{client.phone && (
|
||||
<div className="client-info-item">
|
||||
<div className="client-info-icon">
|
||||
<Phone className="client-info-icon-emerald" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="client-info-label">Phone</p>
|
||||
<p className="client-info-value">{client.phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
{/* Client Information Card */}
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="card-primary">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<div className="bg-blue-subtle rounded-lg p-2">
|
||||
<Building className="text-icon-blue h-5 w-5" />
|
||||
</div>
|
||||
|
||||
{/* Address */}
|
||||
{(client.addressLine1 ?? client.city ?? client.state) && (
|
||||
<div className="space-y-4">
|
||||
<div className="client-info-item">
|
||||
<div className="client-info-icon">
|
||||
<MapPin className="client-info-icon-emerald" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="client-info-label">Address</p>
|
||||
</div>
|
||||
<span>Contact Information</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Basic Info */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{client.email && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="bg-green-subtle rounded-lg p-2">
|
||||
<Mail className="text-icon-green h-4 w-4" />
|
||||
</div>
|
||||
<div className="client-address-content">
|
||||
{client.addressLine1 && <p>{client.addressLine1}</p>}
|
||||
{client.addressLine2 && <p>{client.addressLine2}</p>}
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm font-medium">
|
||||
Email
|
||||
</p>
|
||||
<p className="text-foreground text-sm">{client.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{client.phone && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="bg-green-subtle rounded-lg p-2">
|
||||
<Phone className="text-icon-green h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm font-medium">
|
||||
Phone
|
||||
</p>
|
||||
<p className="text-foreground text-sm">{client.phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Address */}
|
||||
{(client.addressLine1 ?? client.city ?? client.state) && (
|
||||
<div>
|
||||
<h3 className="mb-4 text-lg font-semibold">Client Address</h3>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="bg-green-subtle rounded-lg p-2">
|
||||
<MapPin className="text-icon-green h-4 w-4" />
|
||||
</div>
|
||||
<div className="space-y-1 text-sm">
|
||||
{client.addressLine1 && (
|
||||
<p className="text-foreground">{client.addressLine1}</p>
|
||||
)}
|
||||
{client.addressLine2 && (
|
||||
<p className="text-foreground">{client.addressLine2}</p>
|
||||
)}
|
||||
{(client.city ?? client.state ?? client.postalCode) && (
|
||||
<p>
|
||||
<p className="text-foreground">
|
||||
{[client.city, client.state, client.postalCode]
|
||||
.filter(Boolean)
|
||||
.join(", ")}
|
||||
</p>
|
||||
)}
|
||||
{client.country && <p>{client.country}</p>}
|
||||
{client.country && (
|
||||
<p className="text-foreground">{client.country}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Client Since */}
|
||||
<div className="client-info-item">
|
||||
<div className="client-info-icon">
|
||||
<Calendar className="client-info-icon-emerald" />
|
||||
{/* Client Since */}
|
||||
<div>
|
||||
<h3 className="mb-4 text-lg font-semibold">Client Details</h3>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="bg-green-subtle rounded-lg p-2">
|
||||
<Calendar className="text-icon-green h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="client-info-label">Client Since</p>
|
||||
<p className="client-info-value">
|
||||
<p className="text-muted-foreground text-sm font-medium">
|
||||
Client Since
|
||||
</p>
|
||||
<p className="text-foreground text-sm">
|
||||
{formatDate(client.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Stats Card */}
|
||||
<div className="space-y-6">
|
||||
{/* Stats Card */}
|
||||
<div className="space-y-6">
|
||||
<Card className="card-primary">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<div className="bg-blue-subtle rounded-lg p-2">
|
||||
<DollarSign className="text-icon-blue h-5 w-5" />
|
||||
</div>
|
||||
<span>Invoice Summary</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="text-center">
|
||||
<p className="text-primary text-3xl font-bold">
|
||||
{formatCurrency(totalInvoiced)}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">Total Invoiced</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 text-center">
|
||||
<div>
|
||||
<p className="text-foreground text-xl font-semibold">
|
||||
{paidInvoices}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">Paid</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-foreground text-xl font-semibold">
|
||||
{pendingInvoices}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">Pending</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Recent Invoices */}
|
||||
{client.invoices && client.invoices.length > 0 && (
|
||||
<Card className="card-primary">
|
||||
<CardHeader>
|
||||
<CardTitle className="client-stats-title">
|
||||
<DollarSign className="h-5 w-5" />
|
||||
<span>Invoice Summary</span>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<div className="bg-blue-subtle rounded-lg p-2">
|
||||
<DollarSign className="text-icon-blue h-5 w-5" />
|
||||
</div>
|
||||
<span>Recent Invoices</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="text-center">
|
||||
<p className="client-total-amount">
|
||||
{formatCurrency(totalInvoiced)}
|
||||
</p>
|
||||
<p className="client-total-label">Total Invoiced</p>
|
||||
</div>
|
||||
|
||||
<div className="client-stats-grid">
|
||||
<div className="text-center">
|
||||
<p className="client-stat-value-paid">{paidInvoices}</p>
|
||||
<p className="client-stat-label">Paid</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="client-stat-value-pending">
|
||||
{pendingInvoices}
|
||||
</p>
|
||||
<p className="client-stat-label">Pending</p>
|
||||
</div>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{client.invoices.slice(0, 3).map((invoice) => (
|
||||
<div
|
||||
key={invoice.id}
|
||||
className="flex items-center justify-between rounded-lg border p-3"
|
||||
>
|
||||
<div>
|
||||
<p className="text-foreground font-medium">
|
||||
{invoice.invoiceNumber}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{formatDate(invoice.issueDate)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-foreground font-semibold">
|
||||
{formatCurrency(invoice.totalAmount)}
|
||||
</p>
|
||||
<Badge
|
||||
variant={
|
||||
invoice.status === "paid"
|
||||
? "default"
|
||||
: invoice.status === "sent"
|
||||
? "secondary"
|
||||
: "outline"
|
||||
}
|
||||
className="text-xs"
|
||||
>
|
||||
{invoice.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Recent Invoices */}
|
||||
{client.invoices && client.invoices.length > 0 && (
|
||||
<Card className="card-primary">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg dark:text-white">
|
||||
Recent Invoices
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{client.invoices.slice(0, 3).map((invoice) => (
|
||||
<div key={invoice.id} className="invoice-item">
|
||||
<div>
|
||||
<p className="invoice-item-title">
|
||||
{invoice.invoiceNumber}
|
||||
</p>
|
||||
<p className="invoice-item-date">
|
||||
{formatDate(invoice.issueDate)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="invoice-item-amount">
|
||||
{formatCurrency(invoice.totalAmount)}
|
||||
</p>
|
||||
<Badge
|
||||
variant={
|
||||
invoice.status === "paid"
|
||||
? "default"
|
||||
: invoice.status === "sent"
|
||||
? "secondary"
|
||||
: "outline"
|
||||
}
|
||||
className="text-xs"
|
||||
>
|
||||
{invoice.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
import Link from "next/link";
|
||||
import { HydrateClient } from "~/trpc/server";
|
||||
import { ClientForm } from "~/components/forms/client-form";
|
||||
import { PageHeader } from "~/components/layout/page-header";
|
||||
"use client";
|
||||
|
||||
export default async function NewClientPage() {
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="Add Client"
|
||||
description="Enter client details below to add a new client."
|
||||
variant="gradient"
|
||||
/>
|
||||
<HydrateClient>
|
||||
<ClientForm mode="create" />
|
||||
</HydrateClient>
|
||||
</div>
|
||||
);
|
||||
import { ClientForm } from "~/components/forms/client-form";
|
||||
|
||||
export default function NewClientPage() {
|
||||
return <ClientForm mode="create" />;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function ClientsPage() {
|
||||
description="Manage your clients and their information."
|
||||
variant="gradient"
|
||||
>
|
||||
<Button asChild variant="brand">
|
||||
<Button asChild className="btn-brand-primary shadow-md">
|
||||
<Link href="/dashboard/clients/new">
|
||||
<Plus className="mr-2 h-5 w-5" />
|
||||
<span>Add Client</span>
|
||||
|
||||
+28
-28
@@ -24,11 +24,11 @@ import {
|
||||
// Modern gradient background component
|
||||
function DashboardHero({ firstName }: { firstName: string }) {
|
||||
return (
|
||||
<div className="relative mb-8 overflow-hidden rounded-3xl bg-gradient-to-br from-green-500 via-green-600 to-green-700 p-8 text-white">
|
||||
<div className="relative mb-8 overflow-hidden rounded-3xl bg-gradient-to-br from-teal-600 via-blue-600 to-blue-700 p-8 text-white">
|
||||
<div className="absolute inset-0 bg-black/10" />
|
||||
<div className="relative z-10">
|
||||
<h1 className="mb-2 text-3xl font-bold">Welcome back, {firstName}!</h1>
|
||||
<p className="text-lg text-green-100">
|
||||
<p className="text-lg text-blue-100">
|
||||
Ready to manage your invoicing business
|
||||
</p>
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@ async function DashboardStats() {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mb-8 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="mb-8 grid grid-cols-2 gap-3 sm:gap-6 lg:grid-cols-4">
|
||||
{stats.map((stat) => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
@@ -102,17 +102,17 @@ async function DashboardStats() {
|
||||
key={stat.title}
|
||||
className="border-0 shadow-sm transition-shadow hover:shadow-md"
|
||||
>
|
||||
<CardContent className="p-4 lg:p-6">
|
||||
<div className="mb-3 flex items-center justify-between lg:mb-4">
|
||||
<div className={`rounded-lg p-2 ${stat.bgColor}`}>
|
||||
<Icon className="h-4 w-4 text-gray-700 lg:h-5 lg:w-5 dark:text-gray-800" />
|
||||
<CardContent className="p-3 sm:p-4 lg:p-6">
|
||||
<div className="mb-2 flex items-center justify-between sm:mb-3 lg:mb-4">
|
||||
<div className={`rounded-lg p-1.5 sm:p-2 ${stat.bgColor}`}>
|
||||
<Icon className="h-3 w-3 text-gray-700 sm:h-4 sm:w-4 lg:h-5 lg:w-5 dark:text-gray-800" />
|
||||
</div>
|
||||
<span className="text-xs font-medium text-green-600 lg:text-sm dark:text-green-400">
|
||||
<span className="text-xs font-medium text-teal-600 dark:text-teal-400">
|
||||
{stat.change}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-1 text-xl font-bold text-gray-900 lg:text-2xl dark:text-gray-100">
|
||||
<p className="mb-1 text-base font-bold text-gray-900 sm:text-xl lg:text-2xl dark:text-gray-100">
|
||||
{stat.value}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 lg:text-sm dark:text-gray-300">
|
||||
@@ -157,7 +157,7 @@ function QuickActions() {
|
||||
<Card className="border-0 shadow-sm">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Plus className="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
<Plus className="h-5 w-5 text-teal-600 dark:text-teal-400" />
|
||||
Quick Actions
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -171,7 +171,7 @@ function QuickActions() {
|
||||
variant={action.primary ? "default" : "outline"}
|
||||
className={`h-12 w-full justify-start px-3 ${
|
||||
action.primary
|
||||
? "bg-green-600 text-white hover:bg-green-700"
|
||||
? "bg-teal-600 text-white hover:bg-teal-700"
|
||||
: "border-gray-200 hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
@@ -218,7 +218,7 @@ async function CurrentWork() {
|
||||
<p className="mb-4 text-gray-600 dark:text-gray-300">
|
||||
No draft invoices found
|
||||
</p>
|
||||
<Button asChild className="bg-green-600 hover:bg-green-700">
|
||||
<Button asChild className="bg-teal-600 hover:bg-teal-700">
|
||||
<Link href="/dashboard/invoices/new">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create Invoice
|
||||
@@ -254,7 +254,7 @@ async function CurrentWork() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-2xl font-bold text-green-600 dark:text-green-400">
|
||||
<p className="text-2xl font-bold text-teal-600 dark:text-teal-400">
|
||||
${currentInvoice.totalAmount.toFixed(2)}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
@@ -273,7 +273,7 @@ async function CurrentWork() {
|
||||
<Button
|
||||
asChild
|
||||
size="sm"
|
||||
className="flex-1 bg-green-600 hover:bg-green-700"
|
||||
className="flex-1 bg-teal-600 hover:bg-teal-700"
|
||||
>
|
||||
<Link href={`/dashboard/invoices/${currentInvoice.id}/edit`}>
|
||||
<Edit className="mr-2 h-3 w-3" />
|
||||
@@ -331,7 +331,7 @@ async function RecentActivity() {
|
||||
<p className="mb-4 text-gray-600 dark:text-gray-300">
|
||||
No invoices yet
|
||||
</p>
|
||||
<Button asChild className="bg-green-600 hover:bg-green-700">
|
||||
<Button asChild className="bg-teal-600 hover:bg-teal-700">
|
||||
<Link href="/dashboard/invoices/new">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create Your First Invoice
|
||||
@@ -348,12 +348,12 @@ async function RecentActivity() {
|
||||
>
|
||||
<Card className="card-secondary transition-colors hover:bg-gray-200/70 dark:hover:bg-gray-700/60">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-lg bg-gray-100 p-2 dark:bg-gray-700">
|
||||
<FileText className="h-4 w-4 text-gray-600 dark:text-gray-300" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-medium text-gray-900 dark:text-gray-100">
|
||||
#{invoice.invoiceNumber}
|
||||
</p>
|
||||
@@ -362,8 +362,11 @@ async function RecentActivity() {
|
||||
{new Date(invoice.issueDate).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg p-1 transition-colors hover:bg-gray-300/50 dark:hover:bg-gray-600/50">
|
||||
<Eye className="h-4 w-4 text-gray-600 dark:text-gray-300" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Badge
|
||||
className={`border ${getStatusColor(invoice.status)}`}
|
||||
>
|
||||
@@ -372,9 +375,6 @@ async function RecentActivity() {
|
||||
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
${invoice.totalAmount.toFixed(2)}
|
||||
</p>
|
||||
<div className="rounded-lg p-1 transition-colors hover:bg-gray-300/50 dark:hover:bg-gray-600/50">
|
||||
<Eye className="h-4 w-4 text-gray-600 dark:text-gray-300" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -391,16 +391,16 @@ async function RecentActivity() {
|
||||
// Loading skeletons
|
||||
function StatsSkeleton() {
|
||||
return (
|
||||
<div className="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="mb-8 grid grid-cols-2 gap-3 sm:gap-6 lg:grid-cols-4">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Card key={i} className="border-0 shadow-sm">
|
||||
<CardContent className="p-6">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<Skeleton className="h-9 w-9 rounded-lg" />
|
||||
<Skeleton className="h-4 w-12" />
|
||||
<CardContent className="p-3 sm:p-4 lg:p-6">
|
||||
<div className="mb-2 flex items-center justify-between sm:mb-3 lg:mb-4">
|
||||
<Skeleton className="h-6 w-6 rounded-lg sm:h-8 sm:w-8 lg:h-9 lg:w-9" />
|
||||
<Skeleton className="h-3 w-8 sm:h-4 sm:w-12" />
|
||||
</div>
|
||||
<Skeleton className="mb-2 h-8 w-20" />
|
||||
<Skeleton className="h-4 w-24" />
|
||||
<Skeleton className="mb-1 h-5 w-16 sm:mb-2 sm:h-6 sm:w-20 lg:h-8" />
|
||||
<Skeleton className="h-3 w-20 sm:h-4 sm:w-24" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user