Update invoice page layout and mobile styling

- Improve responsive layout and spacing for better mobile experience
- Refactor invoice skeleton loader for consistent appearance
- Revise padding and spacing for better content hierarchy
- Add sticky header navigation and floating action bar
- Update icon sizes and button styles for better touch targets
This commit is contained in:
2025-07-15 23:59:35 -04:00
parent f505c9ff33
commit 76711d2c10
5 changed files with 310 additions and 324 deletions

View File

@@ -4,46 +4,64 @@ import { Skeleton } from "~/components/ui/skeleton";
export function InvoiceDetailsSkeleton() { export function InvoiceDetailsSkeleton() {
return ( return (
<div className="space-y-6"> <div className="space-y-6 pb-24">
<div className="grid gap-6 xl:grid-cols-3"> {/* Header */}
{/* Main Content */} <div className="flex items-center justify-between">
<div className="space-y-6 xl:col-span-2">
{/* Invoice Header Skeleton */}
<Card className="border-0 shadow-sm">
<CardContent className="p-4 sm:p-6">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div> <div>
<Skeleton className="h-8 w-48 sm:h-9 sm:w-64" />
<Skeleton className="mt-1 h-4 w-40 sm:w-48" />
</div>
<div className="flex items-center gap-2">
<Skeleton className="h-8 w-20 sm:h-9 sm:w-24" />
<Skeleton className="h-8 w-16 sm:h-9 sm:w-20" />
</div>
</div>
{/* Content */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
{/* Left Column */}
<div className="space-y-6 lg:col-span-2">
{/* Invoice Header Skeleton */}
<Card className="shadow-sm">
<CardContent className="p-4 sm:p-6">
<div className="space-y-4">
<div className="flex items-start justify-between gap-6">
<div className="min-w-0 flex-1 space-y-2">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3"> <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3">
<Skeleton className="h-6 w-48 sm:h-8" /> <Skeleton className="h-6 w-40 sm:h-8 sm:w-48" />
<Skeleton className="h-6 w-16" /> <Skeleton className="h-5 w-16 sm:h-6" />
</div> </div>
<Skeleton className="mt-1 h-4 w-64" /> <div className="space-y-1 sm:space-y-0">
<Skeleton className="h-3 w-32 sm:h-4 sm:w-40" />
<Skeleton className="h-3 w-28 sm:hidden sm:h-4 sm:w-36" />
</div>
</div>
<div className="flex-shrink-0 text-right">
<Skeleton className="h-3 w-20 sm:h-4" />
<Skeleton className="mt-1 h-6 w-24 sm:h-8 sm:w-28" />
</div> </div>
<div className="text-left sm:text-right">
<Skeleton className="h-4 w-20" />
<Skeleton className="mt-1 h-6 w-24 sm:h-8" />
</div> </div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
{/* Client & Business Information Skeleton */} {/* Client & Business Info */}
<div className="grid gap-4 sm:gap-6 lg:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
{Array.from({ length: 2 }).map((_, i) => ( {Array.from({ length: 2 }).map((_, i) => (
<Card key={i} className="border-0 shadow-sm"> <Card key={i} className="shadow-sm">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Skeleton className="h-4 w-4 sm:h-5 sm:w-5" /> <Skeleton className="h-4 w-4 sm:h-5 sm:w-5" />
<Skeleton className="h-6 w-16" /> <Skeleton className="h-5 w-16 sm:h-6" />
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-3 sm:space-y-4"> <CardContent className="space-y-4">
<Skeleton className="h-5 w-32 sm:h-6" /> <Skeleton className="h-5 w-32 sm:h-6" />
<div className="space-y-2 sm:space-y-3"> <div className="space-y-3">
{Array.from({ length: 3 }).map((_, j) => ( {Array.from({ length: 3 }).map((_, j) => (
<div key={j} className="flex items-center gap-2 sm:gap-3"> <div key={j} className="flex items-center gap-3">
<Skeleton className="h-6 w-6 rounded-lg sm:h-8 sm:w-8" /> <Skeleton className="h-8 w-8 rounded-lg" />
<Skeleton className="h-3 w-28 sm:h-4" /> <Skeleton className="h-4 w-28" />
</div> </div>
))} ))}
</div> </div>
@@ -53,130 +71,81 @@ export function InvoiceDetailsSkeleton() {
</div> </div>
{/* Invoice Items Skeleton */} {/* Invoice Items Skeleton */}
<Card className="border-0 shadow-sm"> <Card className="shadow-sm">
<CardHeader className="pb-3"> <CardHeader>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Skeleton className="h-4 w-4 sm:h-5 sm:w-5" /> <Skeleton className="h-4 w-4 sm:h-5 sm:w-5" />
<Skeleton className="h-5 w-28 sm:h-6" /> <Skeleton className="h-5 w-28 sm:h-6" />
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="p-0"> <CardContent className="space-y-4">
<div className="overflow-x-auto">
<table className="w-full min-w-[500px]">
<thead>
<tr className="border-b">
{["Date", "Description", "Hours", "Rate", "Amount"].map(
(header) => (
<th key={header} className="p-2 text-left sm:p-4">
<Skeleton className="h-3 w-16 sm:h-4" />
</th>
),
)}
</tr>
</thead>
<tbody>
{Array.from({ length: 3 }).map((_, i) => ( {Array.from({ length: 3 }).map((_, i) => (
<tr key={i} className="border-b last:border-0"> <div key={i} className="space-y-3 rounded-lg border p-4">
<td className="p-2 sm:p-4"> <div className="flex items-start justify-between gap-4">
<Skeleton className="h-3 w-20 sm:h-4" /> <div className="min-w-0 flex-1">
</td> <Skeleton className="mb-2 h-4 w-full sm:h-5 sm:w-3/4" />
<td className="p-2 sm:p-4"> <div className="space-y-1 sm:space-y-0">
<Skeleton className="h-3 w-48 sm:h-4" /> <Skeleton className="h-3 w-20 sm:h-4 sm:w-24" />
</td> <Skeleton className="h-3 w-16 sm:hidden sm:h-4 sm:w-20" />
<td className="p-2 sm:p-4"> <Skeleton className="h-3 w-24 sm:hidden sm:h-4 sm:w-28" />
<Skeleton className="h-3 w-12 sm:h-4" /> </div>
</td> </div>
<td className="p-2 sm:p-4"> <div className="flex-shrink-0 text-right">
<Skeleton className="h-3 w-16 sm:h-4" /> <Skeleton className="h-4 w-16 sm:h-5 sm:w-20" />
</td> </div>
<td className="p-2 sm:p-4"> </div>
<Skeleton className="h-3 w-20 sm:h-4" /> </div>
</td>
</tr>
))} ))}
</tbody>
</table>
</div>
{/* Totals Section Skeleton */} {/* Totals */}
<div className="bg-muted/20 border-t p-3 sm:p-4"> <div className="bg-muted/30 rounded-lg p-4">
<div className="flex justify-end"> <div className="space-y-3">
<div className="w-full max-w-64 space-y-2">
<div className="flex justify-between"> <div className="flex justify-between">
<Skeleton className="h-3 w-16 sm:h-4" /> <Skeleton className="h-4 w-16" />
<Skeleton className="h-3 w-20 sm:h-4" /> <Skeleton className="h-4 w-20" />
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<Skeleton className="h-3 w-20 sm:h-4" /> <Skeleton className="h-4 w-20" />
<Skeleton className="h-3 w-20 sm:h-4" /> <Skeleton className="h-4 w-16" />
</div> </div>
<Separator /> <Separator />
<div className="flex justify-between"> <div className="flex justify-between">
<Skeleton className="h-4 w-12 sm:h-6" /> <Skeleton className="h-5 w-12" />
<Skeleton className="h-4 w-24 sm:h-6" /> <Skeleton className="h-5 w-24" />
</div>
</div> </div>
</div> </div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
{/* Notes Skeleton */} {/* Notes */}
<Card className="border-0 shadow-sm"> <Card className="shadow-sm">
<CardHeader className="pb-3"> <CardHeader>
<Skeleton className="h-5 w-16 sm:h-6" /> <Skeleton className="h-6 w-16" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-2"> <div className="space-y-2">
<Skeleton className="h-3 w-full sm:h-4" /> <Skeleton className="h-4 w-full" />
<Skeleton className="h-3 w-3/4 sm:h-4" /> <Skeleton className="h-4 w-3/4" />
<Skeleton className="h-3 w-1/2 sm:h-4" /> <Skeleton className="h-4 w-1/2" />
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
{/* Sidebar Skeleton */} {/* Right Column - Actions */}
<div className="space-y-4 sm:space-y-6"> <div className="space-y-6">
{/* Actions Skeleton */} <Card className="sticky top-6 shadow-sm">
<Card className="border-0 shadow-sm"> <CardHeader>
<CardHeader className="pb-3">
<Skeleton className="h-5 w-16 sm:h-6" />
</CardHeader>
<CardContent className="space-y-2 sm:space-y-3">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={i} className="h-8 w-full sm:h-10" />
))}
</CardContent>
</Card>
{/* Details Skeleton */}
<Card className="border-0 shadow-sm">
<CardHeader className="pb-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Skeleton className="h-4 w-4 sm:h-5 sm:w-5" /> <Skeleton className="h-5 w-5" />
<Skeleton className="h-5 w-16 sm:h-6" /> <Skeleton className="h-6 w-16" />
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-2 sm:space-y-3"> <CardContent className="space-y-3">
<div className="grid grid-cols-2 gap-2 sm:gap-3"> {Array.from({ length: 3 }).map((_, i) => (
{Array.from({ length: 6 }).map((_, i) => ( <Skeleton key={i} className="h-10 w-full" />
<div key={i} className="space-y-1">
<Skeleton className="h-3 w-16 sm:h-4" />
<Skeleton className="h-3 w-20 sm:h-4" />
</div>
))} ))}
</div>
</CardContent>
</Card>
{/* Danger Zone Skeleton */}
<Card className="border-red-200 shadow-sm dark:border-red-800">
<CardHeader className="pb-3">
<Skeleton className="h-5 w-24 sm:h-6" />
</CardHeader>
<CardContent>
<Skeleton className="h-8 w-full sm:h-10" />
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View File

@@ -57,13 +57,12 @@ export function PDFDownloadButton({
onClick={handleDownloadPDF} onClick={handleDownloadPDF}
disabled={isGenerating} disabled={isGenerating}
variant="ghost" variant="ghost"
size="sm"
className={className} className={className}
> >
{isGenerating ? ( {isGenerating ? (
<Loader2 className="h-3 w-3 animate-spin sm:h-4 sm:w-4" /> <Loader2 className="h-5 w-5 animate-spin" />
) : ( ) : (
<Download className="h-3 w-3 sm:h-4 sm:w-4" /> <Download className="h-5 w-5" />
)} )}
</Button> </Button>
); );
@@ -74,17 +73,16 @@ export function PDFDownloadButton({
onClick={handleDownloadPDF} onClick={handleDownloadPDF}
disabled={isGenerating} disabled={isGenerating}
variant={variant} variant={variant}
size="default" className={`shadow-sm ${className ?? ""}`}
className={`w-full shadow-sm ${className}`}
> >
{isGenerating ? ( {isGenerating ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="h-5 w-5 animate-spin" />
<span>Generating PDF...</span> <span>Generating PDF...</span>
</> </>
) : ( ) : (
<> <>
<Download className="mr-2 h-4 w-4" /> <Download className="h-5 w-5" />
<span>Download PDF</span> <span>Download PDF</span>
</> </>
)} )}

View File

@@ -10,12 +10,9 @@ import { PageHeader } from "~/components/layout/page-header";
import { PDFDownloadButton } from "./_components/pdf-download-button"; import { PDFDownloadButton } from "./_components/pdf-download-button";
import { SendInvoiceButton } from "./_components/send-invoice-button"; import { SendInvoiceButton } from "./_components/send-invoice-button";
import { InvoiceDetailsSkeleton } from "./_components/invoice-details-skeleton"; import { InvoiceDetailsSkeleton } from "./_components/invoice-details-skeleton";
import { InvoiceActionsDropdown } from "./_components/invoice-actions-dropdown";
import { import {
ArrowLeft,
Building, Building,
Calendar,
Copy,
Edit, Edit,
FileText, FileText,
Mail, Mail,
@@ -23,12 +20,6 @@ import {
Phone, Phone,
User, User,
AlertTriangle, AlertTriangle,
Trash2,
DollarSign,
Clock,
Eye,
Download,
Send,
Check, Check,
} from "lucide-react"; } from "lucide-react";
@@ -75,20 +66,19 @@ async function InvoiceContent({ invoiceId }: { invoiceId: string }) {
return ( return (
<div className="space-y-6 pb-24"> <div className="space-y-6 pb-24">
{/* Header */} <PageHeader
<div className="flex items-center justify-between"> title="Invoice Details"
<div> description="View and manage invoice information"
<h1 className="text-foreground text-3xl font-bold"> variant="gradient"
Invoice Details >
</h1> <PDFDownloadButton invoiceId={invoice.id} variant="outline" />
<p className="text-muted-foreground mt-1"> <Button asChild variant="default">
View and manage invoice information <Link href={`/dashboard/invoices/${invoice.id}/edit`}>
</p> <Edit className="h-5 w-5" />
</div> <span>Edit</span>
<div className="flex items-center gap-2"> </Link>
<InvoiceActionsDropdown invoiceId={invoice.id} /> </Button>
</div> </PageHeader>
</div>
{/* Content */} {/* Content */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
@@ -96,7 +86,7 @@ async function InvoiceContent({ invoiceId }: { invoiceId: string }) {
<div className="space-y-6 lg:col-span-2"> <div className="space-y-6 lg:col-span-2">
{/* Invoice Header */} {/* Invoice Header */}
<Card className="shadow-sm"> <Card className="shadow-sm">
<CardContent className="p-6"> <CardContent className="p-4 sm:p-6">
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-start justify-between gap-6"> <div className="flex items-start justify-between gap-6">
<div className="min-w-0 flex-1 space-y-2"> <div className="min-w-0 flex-1 space-y-2">
@@ -276,7 +266,7 @@ async function InvoiceContent({ invoiceId }: { invoiceId: string }) {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
{invoice.items.map((item, index) => ( {invoice.items.map((item) => (
<div key={item.id} className="space-y-3 rounded-lg border p-4"> <div key={item.id} className="space-y-3 rounded-lg border p-4">
<div className="flex items-start justify-between gap-4"> <div className="flex items-start justify-between gap-4">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">

View File

@@ -24,31 +24,41 @@ export default function HomePage() {
<AuthRedirect /> <AuthRedirect />
{/* Navigation */} {/* Navigation */}
<nav className="sticky top-0 z-50 border-b bg-white/80 backdrop-blur-xl dark:bg-slate-900/80 dark:border-slate-700"> <nav className="sticky top-0 z-50 border-b bg-white/80 backdrop-blur-xl dark:border-slate-700 dark:bg-slate-900/80">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="flex h-16 items-center justify-between"> <div className="flex h-14 items-center justify-between sm:h-16">
<Logo /> <Logo />
<div className="hidden items-center space-x-8 md:flex"> <div className="hidden items-center space-x-6 md:flex">
<a <a
href="#features" href="#features"
className="text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-slate-100 transition-colors" className="text-slate-600 transition-colors hover:text-slate-900 dark:text-slate-300 dark:hover:text-slate-100"
> >
Features Features
</a> </a>
<a <a
href="#pricing" href="#pricing"
className="text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-slate-100 transition-colors" className="text-slate-600 transition-colors hover:text-slate-900 dark:text-slate-300 dark:hover:text-slate-100"
> >
Pricing Pricing
</a> </a>
</div> </div>
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-2">
<Link href="/auth/signin"> <Link href="/auth/signin">
<Button variant="ghost">Sign In</Button> <Button
variant="ghost"
size="sm"
className="hidden sm:inline-flex"
>
Sign In
</Button>
</Link> </Link>
<Link href="/auth/register"> <Link href="/auth/register">
<Button className="bg-gradient-to-r from-emerald-600 to-teal-600 shadow-lg shadow-emerald-500/25 transition-all duration-300 hover:shadow-xl hover:shadow-emerald-500/30"> <Button
Get Started Free size="sm"
className="bg-gradient-to-r from-emerald-600 to-teal-600 shadow-lg shadow-emerald-500/25 transition-all duration-300 hover:shadow-xl hover:shadow-emerald-500/30"
>
<span className="hidden sm:inline">Get Started Free</span>
<span className="sm:hidden">Start Free</span>
</Button> </Button>
</Link> </Link>
</div> </div>
@@ -57,31 +67,31 @@ export default function HomePage() {
</nav> </nav>
{/* Hero Section */} {/* Hero Section */}
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-emerald-50 to-teal-50 dark:from-slate-800 dark:via-emerald-900/20 dark:to-teal-900/20 pt-20 pb-16"> <section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-emerald-50 to-teal-50 px-4 pt-12 pb-16 sm:pt-20 dark:from-slate-800 dark:via-emerald-900/20 dark:to-teal-900/20">
{/* Background decoration */} {/* Background decoration */}
<div className="absolute inset-0"> <div className="absolute inset-0">
<div className="absolute top-0 left-1/4 h-96 w-96 rounded-full bg-gradient-to-r from-emerald-400/20 to-blue-400/20 dark:from-emerald-500/10 dark:to-blue-500/10 blur-3xl"></div> <div className="absolute top-0 left-1/4 h-96 w-96 rounded-full bg-gradient-to-r from-emerald-400/20 to-blue-400/20 blur-3xl dark:from-emerald-500/10 dark:to-blue-500/10"></div>
<div className="absolute top-32 right-1/4 h-80 w-80 rounded-full bg-gradient-to-r from-teal-400/20 to-emerald-400/20 dark:from-teal-500/10 dark:to-emerald-500/10 blur-3xl"></div> <div className="absolute top-32 right-1/4 h-80 w-80 rounded-full bg-gradient-to-r from-teal-400/20 to-emerald-400/20 blur-3xl dark:from-teal-500/10 dark:to-emerald-500/10"></div>
<div className="absolute bottom-0 left-1/2 h-64 w-64 rounded-full bg-gradient-to-r from-blue-400/20 to-purple-400/20 dark:from-blue-500/10 dark:to-purple-500/10 blur-3xl"></div> <div className="absolute bottom-0 left-1/2 h-64 w-64 rounded-full bg-gradient-to-r from-blue-400/20 to-purple-400/20 blur-3xl dark:from-blue-500/10 dark:to-purple-500/10"></div>
</div> </div>
<div className="relative container mx-auto px-4 text-center"> <div className="relative container mx-auto text-center">
<div className="mx-auto max-w-4xl"> <div className="mx-auto max-w-4xl">
<Badge <Badge
variant="secondary" variant="secondary"
className="mb-6 border-emerald-200 bg-emerald-100 text-emerald-800 dark:border-emerald-800 dark:bg-emerald-900 dark:text-emerald-200" className="mb-4 border-emerald-200 bg-emerald-100 text-emerald-800 sm:mb-6 dark:border-emerald-800 dark:bg-emerald-900 dark:text-emerald-200"
> >
<Sparkles className="mr-1 h-3 w-3" /> <Sparkles className="mr-1 h-3 w-3" />
100% Free Forever 100% Free Forever
</Badge> </Badge>
<h1 className="mb-6 text-6xl font-bold tracking-tight text-slate-900 dark:text-slate-100 sm:text-7xl lg:text-8xl"> <h1 className="mb-4 text-4xl font-bold tracking-tight text-slate-900 sm:mb-6 sm:text-6xl lg:text-7xl dark:text-slate-100">
Simple Invoicing for Simple Invoicing for
<span className="block bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent"> <span className="block bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">
Freelancers Freelancers
</span> </span>
</h1> </h1>
<p className="mx-auto mb-8 max-w-2xl text-xl leading-relaxed text-slate-600"> <p className="mx-auto mb-6 max-w-2xl text-lg leading-relaxed text-slate-600 sm:mb-8 sm:text-xl dark:text-slate-300">
Create professional invoices, manage clients, and track payments. Create professional invoices, manage clients, and track payments.
Built specifically for freelancers and small businesses Built specifically for freelancers and small businesses
<span className="bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text font-semibold text-transparent"> <span className="bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text font-semibold text-transparent">
@@ -90,29 +100,29 @@ export default function HomePage() {
. .
</p> </p>
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center"> <div className="flex flex-col items-center gap-3 sm:flex-row sm:justify-center sm:gap-4">
<Link href="/auth/register"> <Link href="/auth/register">
<Button <Button
size="lg" size="lg"
className="group bg-gradient-to-r from-emerald-500 to-teal-500 px-8 py-4 text-lg font-semibold shadow-xl shadow-emerald-500/30 transition-all duration-300 hover:from-emerald-600 hover:to-teal-600 hover:shadow-2xl hover:shadow-emerald-500/40" className="group w-full bg-gradient-to-r from-emerald-500 to-teal-500 px-6 py-3 text-base font-semibold shadow-xl shadow-emerald-500/30 transition-all duration-300 hover:from-emerald-600 hover:to-teal-600 hover:shadow-2xl hover:shadow-emerald-500/40 sm:w-auto sm:px-8 sm:py-4 sm:text-lg"
> >
Start Free Start Free
<ArrowRight className="ml-2 h-5 w-5 transition-transform group-hover:translate-x-1" /> <ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1 sm:h-5 sm:w-5" />
</Button> </Button>
</Link> </Link>
<Link href="#demo"> <Link href="#features">
<Button <Button
variant="outline" variant="outline"
size="lg" size="lg"
className="group border-emerald-200 bg-white/50 px-8 py-4 text-lg text-emerald-700 backdrop-blur-sm hover:border-emerald-300 hover:bg-emerald-50" className="group w-full border-emerald-200 bg-white/50 px-6 py-3 text-base text-emerald-700 backdrop-blur-sm hover:border-emerald-300 hover:bg-emerald-50 sm:w-auto sm:px-8 sm:py-4 sm:text-lg dark:border-emerald-700 dark:bg-emerald-900/20 dark:text-emerald-300 dark:hover:bg-emerald-900/40"
> >
See Features See Features
<ChevronRight className="ml-2 h-5 w-5 transition-transform group-hover:translate-x-1" /> <ChevronRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1 sm:h-5 sm:w-5" />
</Button> </Button>
</Link> </Link>
</div> </div>
<div className="mt-12 flex items-center justify-center gap-8 text-sm text-slate-600"> <div className="mt-8 flex flex-col items-center justify-center gap-2 text-sm text-slate-600 sm:mt-12 sm:flex-row sm:gap-6 dark:text-slate-400">
{[ {[
"No credit card required", "No credit card required",
"Setup in 2 minutes", "Setup in 2 minutes",
@@ -120,7 +130,7 @@ export default function HomePage() {
].map((text, i) => ( ].map((text, i) => (
<div key={i} className="flex items-center gap-2"> <div key={i} className="flex items-center gap-2">
<Check className="h-4 w-4 text-emerald-500" /> <Check className="h-4 w-4 text-emerald-500" />
{text} <span className="text-center">{text}</span>
</div> </div>
))} ))}
</div> </div>
@@ -128,58 +138,47 @@ export default function HomePage() {
</div> </div>
</section> </section>
{/* Stats */}
<section className="border-y bg-gradient-to-r from-emerald-50 to-teal-50 py-12">
<div className="container mx-auto px-4">
<div className="text-center">
<p className="text-slate-600">
Free invoicing for independent professionals
</p>
</div>
</div>
</section>
{/* Features Section */} {/* Features Section */}
<section <section
id="features" id="features"
className="bg-gradient-to-br from-white via-blue-50/30 to-emerald-50/50 py-24" className="bg-gradient-to-br from-white via-blue-50/30 to-emerald-50/50 py-16 sm:py-24 dark:from-slate-900 dark:via-slate-800/50 dark:to-emerald-900/20"
> >
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="mb-16 text-center"> <div className="mb-12 text-center sm:mb-16">
<Badge <Badge
variant="secondary" variant="secondary"
className="mb-4 border-blue-200 bg-blue-100 text-blue-800" className="mb-4 border-blue-200 bg-blue-100 text-blue-800 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-200"
> >
<Zap className="mr-1 h-3 w-3" /> <Zap className="mr-1 h-3 w-3" />
Supercharged Features Supercharged Features
</Badge> </Badge>
<h2 className="mb-4 text-5xl font-bold tracking-tight text-slate-900"> <h2 className="mb-4 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl lg:text-5xl dark:text-slate-100">
Everything you need to Everything you need to
<span className="block bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent"> <span className="block bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">
invoice professionally invoice professionally
</span> </span>
</h2> </h2>
<p className="mx-auto max-w-2xl text-xl text-slate-600"> <p className="mx-auto max-w-2xl text-lg text-slate-600 sm:text-xl dark:text-slate-300">
Simple, powerful features designed specifically for freelancers Simple, powerful features designed specifically for freelancers
and small businesses. and small businesses.
</p> </p>
</div> </div>
<div className="grid gap-8 lg:grid-cols-3"> <div className="grid gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3">
{/* Feature 1 */} {/* Feature 1 */}
<Card className="group bg-white/70 shadow-lg backdrop-blur-sm transition-all duration-300 hover:bg-white/90 hover:shadow-xl"> <Card className="group bg-white/70 shadow-lg backdrop-blur-sm transition-all duration-300 hover:bg-white/90 hover:shadow-xl dark:bg-slate-800/50 dark:hover:bg-slate-800/70">
<CardContent className="p-8"> <CardContent className="p-6 sm:p-8">
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-emerald-500 to-teal-500 text-white shadow-lg"> <div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-emerald-500 to-teal-500 text-white shadow-lg">
<Rocket className="h-6 w-6" /> <Rocket className="h-6 w-6" />
</div> </div>
<h3 className="mb-3 text-xl font-bold text-slate-900"> <h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
Quick Setup Quick Setup
</h3> </h3>
<p className="mb-4 text-slate-600"> <p className="mb-4 text-slate-600 dark:text-slate-300">
Start creating invoices immediately. No complicated setup or Start creating invoices immediately. No complicated setup or
configuration required. configuration required.
</p> </p>
<ul className="space-y-2 text-sm text-slate-600"> <ul className="space-y-2 text-sm text-slate-600 dark:text-slate-400">
<li className="flex items-center gap-2"> <li className="flex items-center gap-2">
<Check className="h-4 w-4 text-emerald-500" /> <Check className="h-4 w-4 text-emerald-500" />
Simple client management Simple client management
@@ -197,19 +196,19 @@ export default function HomePage() {
</Card> </Card>
{/* Feature 2 */} {/* Feature 2 */}
<Card className="group bg-white/70 shadow-lg backdrop-blur-sm transition-all duration-300 hover:bg-white/90 hover:shadow-xl"> <Card className="group bg-white/70 shadow-lg backdrop-blur-sm transition-all duration-300 hover:bg-white/90 hover:shadow-xl dark:bg-slate-800/50 dark:hover:bg-slate-800/70">
<CardContent className="p-8"> <CardContent className="p-6 sm:p-8">
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-blue-500 to-indigo-500 text-white shadow-lg"> <div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-blue-500 to-indigo-500 text-white shadow-lg">
<BarChart3 className="h-6 w-6" /> <BarChart3 className="h-6 w-6" />
</div> </div>
<h3 className="mb-3 text-xl font-bold text-slate-900"> <h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
Payment Tracking Payment Tracking
</h3> </h3>
<p className="mb-4 text-slate-600"> <p className="mb-4 text-slate-600 dark:text-slate-300">
Keep track of invoice status and monitor which clients have Keep track of invoice status and monitor which clients have
paid. paid.
</p> </p>
<ul className="space-y-2 text-sm text-slate-600"> <ul className="space-y-2 text-sm text-slate-600 dark:text-slate-400">
<li className="flex items-center gap-2"> <li className="flex items-center gap-2">
<Check className="h-4 w-4 text-emerald-500" /> <Check className="h-4 w-4 text-emerald-500" />
Invoice status tracking Invoice status tracking
@@ -227,15 +226,15 @@ export default function HomePage() {
</Card> </Card>
{/* Feature 3 */} {/* Feature 3 */}
<Card className="group bg-white/70 shadow-lg backdrop-blur-sm transition-all duration-300 hover:bg-white/90 hover:shadow-xl"> <Card className="group bg-white/70 shadow-lg backdrop-blur-sm transition-all duration-300 hover:bg-white/90 hover:shadow-xl dark:bg-slate-800/50 dark:hover:bg-slate-800/70">
<CardContent className="p-8"> <CardContent className="p-6 sm:p-8">
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-purple-500 to-pink-500 text-white shadow-lg"> <div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-purple-500 to-pink-500 text-white shadow-lg">
<Globe className="h-6 w-6" /> <Globe className="h-6 w-6" />
</div> </div>
<h3 className="mb-3 text-xl font-bold text-slate-900"> <h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
Professional Features Professional Features
</h3> </h3>
<p className="mb-4 text-slate-600"> <p className="mb-4 text-slate-600 dark:text-slate-300">
Everything you need to look professional and get paid on time. Everything you need to look professional and get paid on time.
</p> </p>
<ul className="space-y-2 text-sm text-slate-600"> <ul className="space-y-2 text-sm text-slate-600">
@@ -261,35 +260,37 @@ export default function HomePage() {
{/* Pricing Section */} {/* Pricing Section */}
<section <section
id="pricing" id="pricing"
className="bg-gradient-to-br from-emerald-50 via-teal-50 to-blue-50 py-24" className="bg-gradient-to-br from-emerald-50 via-teal-50 to-blue-50 py-16 sm:py-24 dark:from-emerald-900/20 dark:via-teal-900/20 dark:to-blue-900/20"
> >
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="mb-16 text-center"> <div className="mb-12 text-center sm:mb-16">
<h2 className="mb-4 text-5xl font-bold tracking-tight text-slate-900"> <h2 className="mb-4 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl lg:text-5xl dark:text-slate-100">
Simple, transparent pricing Simple, transparent pricing
</h2> </h2>
<p className="mx-auto max-w-2xl text-xl text-slate-600"> <p className="mx-auto max-w-2xl text-lg text-slate-600 sm:text-xl dark:text-slate-300">
Start free, stay free. No hidden fees, no gotchas, no limits on Start free, stay free. No hidden fees, no gotchas, no limits on
your success. your success.
</p> </p>
</div> </div>
<div className="mx-auto max-w-md"> <div className="mx-auto max-w-md">
<Card className="relative border-2 border-emerald-500 bg-white/90 shadow-2xl backdrop-blur-sm"> <Card className="relative border-2 border-emerald-500 bg-white/90 shadow-2xl backdrop-blur-sm dark:border-emerald-400 dark:bg-slate-800/90">
<div className="absolute -top-4 left-1/2 -translate-x-1/2"> <div className="absolute -top-4 left-1/2 -translate-x-1/2">
<Badge className="bg-emerald-500 px-6 py-1 text-white"> <Badge className="bg-emerald-500 px-6 py-1 text-white dark:bg-emerald-600">
Forever Free Forever Free
</Badge> </Badge>
</div> </div>
<CardContent className="p-8 text-center"> <CardContent className="p-6 text-center sm:p-8">
<div className="mb-6"> <div className="mb-6">
<div className="mb-2 bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-6xl font-bold text-transparent"> <div className="mb-2 bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-5xl font-bold text-transparent sm:text-6xl">
$0 $0
</div> </div>
<div className="text-slate-600">per month, forever</div> <div className="text-slate-600 dark:text-slate-400">
per month, forever
</div>
</div> </div>
<div className="mb-8 space-y-4 text-left"> <div className="mb-6 space-y-3 text-left sm:mb-8 sm:space-y-4">
{[ {[
"Unlimited invoices", "Unlimited invoices",
"Unlimited clients", "Unlimited clients",
@@ -302,18 +303,20 @@ export default function HomePage() {
].map((feature, i) => ( ].map((feature, i) => (
<div key={i} className="flex items-center gap-3"> <div key={i} className="flex items-center gap-3">
<Check className="h-5 w-5 flex-shrink-0 text-emerald-500" /> <Check className="h-5 w-5 flex-shrink-0 text-emerald-500" />
<span className="text-slate-700">{feature}</span> <span className="text-slate-700 dark:text-slate-300">
{feature}
</span>
</div> </div>
))} ))}
</div> </div>
<Link href="/auth/register"> <Link href="/auth/register">
<Button className="w-full bg-gradient-to-r from-emerald-500 to-teal-500 py-3 text-lg font-semibold shadow-lg shadow-emerald-500/30 transition-all duration-300 hover:from-emerald-600 hover:to-teal-600 hover:shadow-xl hover:shadow-emerald-500/40"> <Button className="w-full bg-gradient-to-r from-emerald-500 to-teal-500 py-3 text-base font-semibold shadow-lg shadow-emerald-500/30 transition-all duration-300 hover:from-emerald-600 hover:to-teal-600 hover:shadow-xl hover:shadow-emerald-500/40 sm:text-lg">
Get Started Now Get Started Now
</Button> </Button>
</Link> </Link>
<p className="mt-4 text-sm text-slate-600"> <p className="mt-4 text-sm text-slate-600 dark:text-slate-400">
No credit card required No credit card required
</p> </p>
</CardContent> </CardContent>
@@ -323,10 +326,10 @@ export default function HomePage() {
</section> </section>
{/* Why Choose */} {/* Why Choose */}
<section className="bg-gradient-to-br from-white via-emerald-50/30 to-teal-50/50 py-24"> <section className="bg-gradient-to-br from-white via-emerald-50/30 to-teal-50/50 py-16 sm:py-24 dark:from-slate-900 dark:via-emerald-900/10 dark:to-teal-900/10">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="mb-16 text-center"> <div className="mb-12 text-center sm:mb-16">
<h2 className="mb-4 text-5xl font-bold tracking-tight text-slate-900"> <h2 className="mb-4 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl lg:text-5xl dark:text-slate-100">
Why freelancers Why freelancers
<span className="block bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent"> <span className="block bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">
choose BeenVoice choose BeenVoice
@@ -334,15 +337,15 @@ export default function HomePage() {
</h2> </h2>
</div> </div>
<div className="grid gap-8 md:grid-cols-3"> <div className="grid gap-6 sm:gap-8 md:grid-cols-3">
<div className="text-center"> <div className="text-center">
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-emerald-500 to-teal-500 text-white shadow-lg"> <div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-emerald-500 to-teal-500 text-white shadow-lg">
<Zap className="h-6 w-6" /> <Zap className="h-6 w-6" />
</div> </div>
<h3 className="mb-3 text-xl font-bold text-slate-900"> <h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
Quick & Simple Quick & Simple
</h3> </h3>
<p className="text-slate-600"> <p className="text-slate-600 dark:text-slate-300">
No learning curve. Start creating professional invoices in No learning curve. Start creating professional invoices in
minutes, not hours. minutes, not hours.
</p> </p>
@@ -351,10 +354,10 @@ export default function HomePage() {
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-blue-500 to-indigo-500 text-white shadow-lg"> <div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-blue-500 to-indigo-500 text-white shadow-lg">
<Shield className="h-6 w-6" /> <Shield className="h-6 w-6" />
</div> </div>
<h3 className="mb-3 text-xl font-bold text-slate-900"> <h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
Always Free Always Free
</h3> </h3>
<p className="text-slate-600"> <p className="text-slate-600 dark:text-slate-300">
No hidden fees, no premium tiers. All features are free for as No hidden fees, no premium tiers. All features are free for as
long as you need them. long as you need them.
</p> </p>
@@ -363,10 +366,10 @@ export default function HomePage() {
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-purple-500 to-pink-500 text-white shadow-lg"> <div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-r from-purple-500 to-pink-500 text-white shadow-lg">
<Clock className="h-6 w-6" /> <Clock className="h-6 w-6" />
</div> </div>
<h3 className="mb-3 text-xl font-bold text-slate-900"> <h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
Save Time Save Time
</h3> </h3>
<p className="text-slate-600"> <p className="text-slate-600 dark:text-slate-300">
Focus on your work, not paperwork. Automated calculations and Focus on your work, not paperwork. Automated calculations and
professional formatting. professional formatting.
</p> </p>
@@ -376,7 +379,7 @@ export default function HomePage() {
</section> </section>
{/* CTA Section */} {/* CTA Section */}
<section className="relative overflow-hidden bg-gradient-to-br from-emerald-500 via-teal-600 to-blue-700 py-24"> <section className="relative overflow-hidden bg-gradient-to-br from-emerald-500 via-teal-600 to-blue-700 py-16 sm:py-24">
<div className="absolute inset-0 bg-gradient-to-br from-emerald-500/95 via-teal-600/95 to-blue-700/95"></div> <div className="absolute inset-0 bg-gradient-to-br from-emerald-500/95 via-teal-600/95 to-blue-700/95"></div>
<div className="absolute top-10 left-10 h-64 w-64 rounded-full bg-gradient-to-r from-white/20 to-emerald-300/20 blur-3xl"></div> <div className="absolute top-10 left-10 h-64 w-64 rounded-full bg-gradient-to-r from-white/20 to-emerald-300/20 blur-3xl"></div>
<div className="absolute right-10 bottom-10 h-80 w-80 rounded-full bg-gradient-to-r from-teal-300/15 to-blue-300/15 blur-3xl"></div> <div className="absolute right-10 bottom-10 h-80 w-80 rounded-full bg-gradient-to-r from-teal-300/15 to-blue-300/15 blur-3xl"></div>
@@ -384,11 +387,11 @@ export default function HomePage() {
<div className="relative container mx-auto px-4 text-center"> <div className="relative container mx-auto px-4 text-center">
<div className="mx-auto max-w-3xl"> <div className="mx-auto max-w-3xl">
<h2 className="mb-6 text-5xl font-bold text-white"> <h2 className="mb-4 text-3xl font-bold text-white sm:mb-6 sm:text-4xl lg:text-5xl">
Ready to revolutionize Ready to revolutionize
<span className="block">your invoicing?</span> <span className="block">your invoicing?</span>
</h2> </h2>
<p className="mb-8 text-xl text-emerald-100"> <p className="mb-6 text-lg text-emerald-100 sm:mb-8 sm:text-xl">
Join thousands of entrepreneurs who&apos;ve already transformed Join thousands of entrepreneurs who&apos;ve already transformed
their business with BeenVoice. Start your journey their business with BeenVoice. Start your journey
today&mdash;completely free. today&mdash;completely free.
@@ -399,15 +402,15 @@ export default function HomePage() {
<Button <Button
size="lg" size="lg"
variant="secondary" variant="secondary"
className="group bg-white px-8 py-4 text-lg font-semibold text-emerald-700 shadow-xl transition-all duration-300 hover:bg-gradient-to-r hover:from-white hover:to-emerald-50 hover:shadow-2xl" className="group w-full bg-white px-6 py-3 text-base font-semibold text-emerald-700 shadow-xl transition-all duration-300 hover:bg-gradient-to-r hover:from-white hover:to-emerald-50 hover:shadow-2xl sm:w-auto sm:px-8 sm:py-4 sm:text-lg"
> >
Start Your Success Story Start Your Success Story
<Rocket className="ml-2 h-5 w-5 transition-transform group-hover:translate-x-1" /> <Rocket className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1 sm:h-5 sm:w-5" />
</Button> </Button>
</Link> </Link>
</div> </div>
<div className="mt-8 flex items-center justify-center gap-8 text-emerald-200"> <div className="mt-6 flex flex-col items-center justify-center gap-3 text-emerald-200 sm:mt-8 sm:flex-row sm:gap-6">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Heart className="h-4 w-4" /> <Heart className="h-4 w-4" />
Free forever Free forever
@@ -426,42 +429,42 @@ export default function HomePage() {
</section> </section>
{/* Footer */} {/* Footer */}
<footer className="border-t bg-gradient-to-br from-slate-50 to-emerald-50/30 py-12"> <footer className="border-t bg-gradient-to-br from-slate-50 to-emerald-50/30 py-8 sm:py-12 dark:border-slate-700 dark:from-slate-900 dark:to-emerald-900/10">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="text-center"> <div className="text-center">
<Logo className="mx-auto mb-4" /> <Logo className="mx-auto mb-4" />
<p className="mb-6 text-slate-600"> <p className="mb-4 text-sm text-slate-600 sm:mb-6 sm:text-base dark:text-slate-400">
Simple invoicing for freelancers. Free, forever. Simple invoicing for freelancers. Free, forever.
</p> </p>
<div className="flex items-center justify-center gap-8 text-sm text-slate-600"> <div className="flex flex-wrap items-center justify-center gap-4 text-sm text-slate-600 sm:gap-6 dark:text-slate-400">
<Link <Link
href="/auth/signin" href="/auth/signin"
className="transition-colors hover:text-emerald-600" className="transition-colors hover:text-emerald-600 dark:hover:text-emerald-400"
> >
Sign In Sign In
</Link> </Link>
<Link <Link
href="/auth/register" href="/auth/register"
className="transition-colors hover:text-emerald-600" className="transition-colors hover:text-emerald-600 dark:hover:text-emerald-400"
> >
Get Started Get Started
</Link> </Link>
<a <a
href="#features" href="#features"
className="transition-colors hover:text-emerald-600" className="transition-colors hover:text-emerald-600 dark:hover:text-emerald-400"
> >
Features Features
</a> </a>
<a <a
href="#pricing" href="#pricing"
className="transition-colors hover:text-emerald-600" className="transition-colors hover:text-emerald-600 dark:hover:text-emerald-400"
> >
Pricing Pricing
</a> </a>
</div> </div>
<div className="mt-8 border-t pt-8"> <div className="mt-6 border-t pt-6 sm:mt-8 sm:pt-8">
<p className="text-slate-600"> <p className="text-sm text-slate-600 sm:text-base dark:text-slate-400">
&copy; 2024 BeenVoice. Built with &hearts; for entrepreneurs. &copy; 2024 BeenVoice. Built with for entrepreneurs.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -24,6 +24,7 @@ import { FileText, DollarSign, Clock, Save, Check } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { FloatingActionBar } from "~/components/layout/floating-action-bar"; import { FloatingActionBar } from "~/components/layout/floating-action-bar";
import { InvoiceLineItems } from "~/components/forms/invoice-line-items"; import { InvoiceLineItems } from "~/components/forms/invoice-line-items";
import { PageHeader } from "~/components/layout/page-header";
const STATUS_OPTIONS = [ const STATUS_OPTIONS = [
{ value: "draft", label: "Draft" }, { value: "draft", label: "Draft" },
@@ -39,35 +40,59 @@ interface InvoiceFormProps {
// Custom skeleton for invoice form // Custom skeleton for invoice form
function InvoiceFormSkeleton() { function InvoiceFormSkeleton() {
return ( return (
<div className="space-y-6"> <div className="space-y-6 pb-24">
{/* Header */} <div className="mb-8">
<div className="flex items-center justify-between"> <div className="flex items-start justify-between gap-4">
<div> <div className="bg-muted/30 h-8 w-48 animate-pulse rounded sm:h-9 sm:w-64"></div>
<div className="bg-muted/30 h-9 w-64 animate-pulse rounded"></div>
<div className="bg-muted/30 mt-1 h-4 w-48 animate-pulse rounded"></div>
</div> </div>
<div className="bg-muted/30 mt-2 h-4 w-36 animate-pulse rounded sm:w-48"></div>
</div> </div>
{/* Form Content */} {/* Form Content */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
{/* Left Column - Content with Tabs */} {/* Left Column - Content with Tabs */}
<div className="space-y-6 lg:col-span-2"> <div className="space-y-6 lg:col-span-2">
{/* Tabs */} {/* Tabs - Mobile stacked, desktop side-by-side */}
<div className="bg-muted grid w-full grid-cols-2 rounded-lg p-1"> <div className="bg-muted grid w-full grid-cols-1 gap-1 rounded-lg p-1 sm:grid-cols-2">
<div className="bg-background h-10 rounded-md shadow-sm"></div> <div className="bg-background h-9 rounded-md shadow-sm sm:h-10"></div>
<div className="bg-muted/30 h-10 rounded-md"></div> <div className="bg-muted/30 h-9 rounded-md sm:h-10"></div>
</div> </div>
{/* Invoice Details Card */} {/* Invoice Details Card */}
<Card className="shadow-sm"> <Card className="shadow-sm">
<CardHeader> <CardHeader className="pb-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="bg-muted/30 h-5 w-5 animate-pulse rounded"></div> <div className="bg-muted/30 h-5 w-5 animate-pulse rounded"></div>
<div className="bg-muted/30 h-6 w-32 animate-pulse rounded"></div> <div className="bg-muted/30 h-6 w-32 animate-pulse rounded"></div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2"> {/* First row - stacked on mobile */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div>
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-16 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div>
</div>
{/* Second row */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-18 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div>
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-16 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div>
</div>
{/* Third row */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
<div className="bg-muted/30 h-4 w-24 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-24 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div> <div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
@@ -77,32 +102,16 @@ function InvoiceFormSkeleton() {
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div> <div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{/* Status field */}
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-16 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded sm:w-48"></div>
</div>
{/* Notes field */}
<div className="space-y-2"> <div className="space-y-2">
<div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div>
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-28 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div>
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div>
</div>
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div>
<div className="space-y-2">
<div className="bg-muted/30 h-4 w-32 animate-pulse rounded"></div>
<div className="bg-muted/30 h-20 w-full animate-pulse rounded"></div> <div className="bg-muted/30 h-20 w-full animate-pulse rounded"></div>
</div> </div>
</CardContent> </CardContent>
@@ -110,24 +119,30 @@ function InvoiceFormSkeleton() {
{/* Invoice Items Card */} {/* Invoice Items Card */}
<Card className="shadow-sm"> <Card className="shadow-sm">
<CardHeader> <CardHeader className="pb-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="bg-muted/30 h-5 w-5 animate-pulse rounded"></div> <div className="bg-muted/30 h-5 w-5 animate-pulse rounded"></div>
<div className="bg-muted/30 h-6 w-28 animate-pulse rounded"></div> <div className="bg-muted/30 h-6 w-28 animate-pulse rounded"></div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="space-y-4 rounded-lg border p-4"> {/* Line item skeleton */}
<div className="space-y-4 rounded-lg border p-3 sm:p-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="bg-muted/30 h-5 w-16 animate-pulse rounded"></div> <div className="bg-muted/30 h-5 w-20 animate-pulse rounded"></div>
<div className="bg-muted/30 h-8 w-8 animate-pulse rounded"></div>
</div> </div>
{/* Description */}
<div className="space-y-2"> <div className="space-y-2">
<div className="bg-muted/30 h-4 w-24 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div>
<div className="bg-muted/30 h-15 w-full animate-pulse rounded"></div> <div className="bg-muted/30 h-16 w-full animate-pulse rounded"></div>
</div> </div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
{/* Date, Hours, Rate - stacked on mobile */}
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
<div className="space-y-2"> <div className="space-y-2">
<div className="bg-muted/30 h-4 w-12 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-10 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div> <div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@@ -135,10 +150,12 @@ function InvoiceFormSkeleton() {
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div> <div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="bg-muted/30 h-4 w-16 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-8 animate-pulse rounded"></div>
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div> <div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</div> </div>
</div> </div>
{/* Amount display */}
<div className="bg-muted/30 rounded-lg p-3"> <div className="bg-muted/30 rounded-lg p-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="bg-muted/50 h-4 w-16 animate-pulse rounded"></div> <div className="bg-muted/50 h-4 w-16 animate-pulse rounded"></div>
@@ -146,6 +163,8 @@ function InvoiceFormSkeleton() {
</div> </div>
</div> </div>
</div> </div>
{/* Add item button */}
<div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div> <div className="bg-muted/30 h-10 w-full animate-pulse rounded"></div>
</CardContent> </CardContent>
</Card> </Card>
@@ -154,20 +173,21 @@ function InvoiceFormSkeleton() {
{/* Right Column - Summary */} {/* Right Column - Summary */}
<div className="space-y-6"> <div className="space-y-6">
<Card className="sticky top-6 shadow-sm"> <Card className="sticky top-6 shadow-sm">
<CardHeader> <CardHeader className="pb-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="bg-muted/30 h-5 w-5 animate-pulse rounded"></div> <div className="bg-muted/30 h-5 w-5 animate-pulse rounded"></div>
<div className="bg-muted/30 h-6 w-16 animate-pulse rounded"></div> <div className="bg-muted/30 h-6 w-16 animate-pulse rounded"></div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
{/* Totals */}
<div className="space-y-3"> <div className="space-y-3">
<div className="flex justify-between"> <div className="flex justify-between">
<div className="bg-muted/30 h-4 w-16 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-16 animate-pulse rounded"></div>
<div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-12 animate-pulse rounded"></div>
<div className="bg-muted/30 h-4 w-16 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-16 animate-pulse rounded"></div>
</div> </div>
<Separator /> <Separator />
@@ -176,32 +196,43 @@ function InvoiceFormSkeleton() {
<div className="bg-muted/30 h-5 w-24 animate-pulse rounded"></div> <div className="bg-muted/30 h-5 w-24 animate-pulse rounded"></div>
</div> </div>
</div> </div>
<Separator /> <Separator />
{/* Stats */}
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between"> <div className="bg-muted/30 h-4 w-20 animate-pulse rounded"></div>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<div className="bg-muted/30 h-3 w-12 animate-pulse rounded"></div> <div className="bg-muted/30 h-3 w-12 animate-pulse rounded"></div>
<div className="bg-muted/30 h-3 w-8 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-8 animate-pulse rounded"></div>
</div> </div>
<div className="flex justify-between"> <div className="space-y-1">
<div className="bg-muted/30 h-3 w-12 animate-pulse rounded"></div>
<div className="bg-muted/30 h-3 w-20 animate-pulse rounded"></div>
</div>
<div className="flex justify-between">
<div className="bg-muted/30 h-3 w-16 animate-pulse rounded"></div> <div className="bg-muted/30 h-3 w-16 animate-pulse rounded"></div>
<div className="bg-muted/30 h-3 w-24 animate-pulse rounded"></div> <div className="bg-muted/30 h-4 w-12 animate-pulse rounded"></div>
</div>
</div> </div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</div> </div>
{/* Floating Action Bar Skeleton - Mobile only */}
<div className="fixed right-4 bottom-6 left-4 lg:hidden">
<div className="bg-background rounded-lg border p-4 shadow-lg">
<div className="flex items-center justify-between gap-3">
<div className="bg-muted/30 h-9 flex-1 animate-pulse rounded"></div>
<div className="bg-muted/30 h-9 w-20 animate-pulse rounded"></div>
</div>
</div>
</div>
</div> </div>
); );
} }
export function InvoiceForm({ invoiceId }: InvoiceFormProps) { export function InvoiceForm({ invoiceId }: InvoiceFormProps) {
const router = useRouter(); const router = useRouter();
const headerRef = useRef<HTMLDivElement>(null);
const footerRef = useRef<HTMLDivElement>(null); const footerRef = useRef<HTMLDivElement>(null);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
invoiceNumber: `INV-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}-${Date.now().toString().slice(-6)}`, invoiceNumber: `INV-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}-${Date.now().toString().slice(-6)}`,
@@ -263,8 +294,8 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) {
date: new Date(), date: new Date(),
description: "", description: "",
hours: 1, hours: 1,
rate: formData.defaultHourlyRate, rate: 100,
amount: formData.defaultHourlyRate, amount: 100,
}, },
], ],
}); });
@@ -292,7 +323,6 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) {
})); }));
} }
} }
}, [formData.clientId, clients]); }, [formData.clientId, clients]);
// Calculate totals // Calculate totals
@@ -441,17 +471,13 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) {
return ( return (
<> <>
<div className="space-y-6"> <div className="space-y-6">
{/* Header */} <PageHeader
<div ref={headerRef} className="flex items-center justify-between"> title={invoiceId ? "Edit Invoice" : "Create Invoice"}
<div> description={
<h1 className="bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-3xl font-bold text-transparent"> invoiceId ? "Update invoice details" : "Create a new invoice"
{invoiceId ? "Edit Invoice" : "Create Invoice"} }
</h1> variant="gradient"
<p className="text-muted-foreground mt-1"> />
{invoiceId ? "Update invoice details" : "Create a new invoice"}
</p>
</div>
</div>
{/* Form Content */} {/* Form Content */}
<form id="invoice-form" onSubmit={handleSubmit} className="space-y-6"> <form id="invoice-form" onSubmit={handleSubmit} className="space-y-6">