feat: improve invoice view responsiveness and settings UX

- Replace custom invoice items table with responsive DataTable component
- Fix server/client component error by creating InvoiceItemsTable client
  component
- Merge danger zone with actions sidebar and use destructive button
  variant
- Standardize button text sizing across all action buttons
- Remove false claims from homepage (testimonials, ratings, fake user
  counts)
- Focus homepage messaging on freelancers with honest feature
  descriptions
- Fix dark mode support throughout app by replacing hard-coded colors
  with semantic classes
- Remove aggressive red styling from settings, add subtle red accents
  only
- Align import/export buttons and improve delete confirmation UX
- Update dark mode background to have subtle green tint instead of pure
  black
- Fix HTML nesting error in AlertDialog by using div instead of nested p
  tags

This update makes the invoice view properly responsive, removes
misleading marketing claims, and ensures consistent dark mode support
across the entire application.
This commit is contained in:
2025-07-15 02:35:55 -04:00
parent f331136090
commit c9a664869c
71 changed files with 2795 additions and 3043 deletions
@@ -1,263 +0,0 @@
"use client";
import { api } from "~/trpc/react";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Button } from "~/components/ui/button";
import { StatusBadge, type StatusType } from "~/components/ui/status-badge";
import {
Users,
FileText,
TrendingUp,
Calendar,
Plus,
ArrowRight,
} from "lucide-react";
import Link from "next/link";
import {
DashboardStatsSkeleton,
DashboardActivitySkeleton,
} from "~/components/ui/skeleton";
// Client component for dashboard stats
export function DashboardStats() {
const { data: clients, isLoading: clientsLoading } =
api.clients.getAll.useQuery();
const { data: invoices, isLoading: invoicesLoading } =
api.invoices.getAll.useQuery();
if (clientsLoading || invoicesLoading) {
return <DashboardStatsSkeleton />;
}
const totalClients = clients?.length ?? 0;
const totalInvoices = invoices?.length ?? 0;
const totalRevenue =
invoices?.reduce((sum, invoice) => sum + invoice.totalAmount, 0) ?? 0;
const pendingInvoices =
invoices?.filter(
(invoice) => invoice.status === "sent" || invoice.status === "draft",
).length ?? 0;
// Calculate month-over-month changes (simplified)
const lastMonthClients = 0; // This would need historical data
const lastMonthInvoices = 0;
const lastMonthRevenue = 0;
return (
<div className="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
<Card className="shadow-xl backdrop-blur-sm transition-all duration-300 hover:shadow-2xl">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-muted-foreground text-sm font-medium">
Total Clients
</CardTitle>
<div className="rounded-lg bg-emerald-100 p-2">
<Users className="h-4 w-4 text-emerald-600" />
</div>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-emerald-600">
{totalClients}
</div>
<p className="text-muted-foreground text-xs">
{totalClients > lastMonthClients ? "+" : ""}
{totalClients - lastMonthClients} from last month
</p>
</CardContent>
</Card>
<Card className="shadow-xl backdrop-blur-sm transition-all duration-300 hover:shadow-2xl">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-muted-foreground text-sm font-medium">
Total Invoices
</CardTitle>
<div className="rounded-lg bg-blue-100 p-2">
<FileText className="h-4 w-4 text-blue-600" />
</div>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-blue-600">
{totalInvoices}
</div>
<p className="text-muted-foreground text-xs">
{totalInvoices > lastMonthInvoices ? "+" : ""}
{totalInvoices - lastMonthInvoices} from last month
</p>
</CardContent>
</Card>
<Card className="shadow-xl backdrop-blur-sm transition-all duration-300 hover:shadow-2xl">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-muted-foreground text-sm font-medium">
Revenue
</CardTitle>
<div className="rounded-lg bg-teal-100 p-2">
<TrendingUp className="h-4 w-4 text-teal-600" />
</div>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-teal-600">
${totalRevenue.toFixed(2)}
</div>
<p className="text-muted-foreground text-xs">
{totalRevenue > lastMonthRevenue ? "+" : ""}
{(
((totalRevenue - lastMonthRevenue) / (lastMonthRevenue || 1)) *
100
).toFixed(1)}
% from last month
</p>
</CardContent>
</Card>
<Card className="shadow-xl backdrop-blur-sm transition-all duration-300 hover:shadow-2xl">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-muted-foreground text-sm font-medium">
Pending Invoices
</CardTitle>
<div className="rounded-lg bg-orange-100 p-2">
<Calendar className="h-4 w-4 text-orange-600" />
</div>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-orange-600">
{pendingInvoices}
</div>
<p className="text-muted-foreground text-xs">Due this month</p>
</CardContent>
</Card>
</div>
);
}
// Client component for dashboard cards
export function DashboardCards() {
return (
<div className="mb-8 grid grid-cols-1 gap-8 lg:grid-cols-2">
<Card className="shadow-xl backdrop-blur-sm transition-all duration-300 hover:shadow-2xl">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-emerald-700">
<div className="rounded-lg bg-emerald-100 p-2">
<Users className="h-5 w-5" />
</div>
Manage Clients
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-muted-foreground">
Add new clients and manage your existing client relationships.
</p>
<div className="flex gap-3">
<Button asChild variant="brand">
<Link href="/dashboard/clients/new">
<Plus className="mr-2 h-4 w-4" />
Add Client
</Link>
</Button>
<Button variant="outline" asChild className="font-medium">
<Link href="/dashboard/clients">
View All Clients
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</CardContent>
</Card>
<Card className="shadow-xl backdrop-blur-sm transition-all duration-300 hover:shadow-2xl">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-emerald-700">
<div className="rounded-lg bg-emerald-100 p-2">
<FileText className="h-5 w-5" />
</div>
Create Invoices
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-muted-foreground">
Generate professional invoices and track payments.
</p>
<div className="flex gap-3">
<Button asChild variant="brand">
<Link href="/dashboard/invoices/new">
<Plus className="mr-2 h-4 w-4" />
New Invoice
</Link>
</Button>
<Button variant="outline" asChild className="font-medium">
<Link href="/dashboard/invoices">
View All Invoices
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</CardContent>
</Card>
</div>
);
}
// Client component for recent activity
export function DashboardActivity() {
const { data: invoices, isLoading } = api.invoices.getAll.useQuery();
if (isLoading) {
return <DashboardActivitySkeleton />;
}
const recentInvoices = invoices?.slice(0, 5) ?? [];
return (
<Card className="shadow-xl backdrop-blur-sm">
<CardHeader>
<CardTitle className="text-emerald-700">Recent Activity</CardTitle>
</CardHeader>
<CardContent>
{recentInvoices.length === 0 ? (
<div className="text-muted-foreground py-12 text-center">
<div className="bg-muted mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full p-4">
<FileText className="text-muted-foreground h-8 w-8" />
</div>
<p className="text-foreground mb-2 text-lg font-medium">
No recent activity
</p>
<p className="text-muted-foreground text-sm">
Start by adding your first client or creating an invoice
</p>
</div>
) : (
<div className="space-y-4">
{recentInvoices.map((invoice) => (
<div
key={invoice.id}
className="bg-muted/50 flex items-center justify-between rounded-lg p-4"
>
<div className="flex items-center gap-3">
<div className="rounded-lg bg-emerald-100 p-2">
<FileText className="h-4 w-4 text-emerald-600" />
</div>
<div>
<p className="text-foreground font-medium">
Invoice #{invoice.invoiceNumber}
</p>
<p className="text-muted-foreground text-sm">
{invoice.client?.name ?? "Unknown Client"} $
{invoice.totalAmount.toFixed(2)}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<StatusBadge status={invoice.status as StatusType} />
<Button variant="ghost" size="sm" asChild>
<Link href={`/dashboard/invoices/${invoice.id}`}>
<ArrowRight className="h-4 w-4" />
</Link>
</Button>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
);
}