feat: polish invoice editor and viewer UI with custom NumberInput

component

- Create custom NumberInput component with increment/decrement buttons
- Add 0.25 step increments for hours and rates in invoice forms
- Implement emerald-themed styling with hover states and accessibility
- Add keyboard navigation (arrow keys) and proper ARIA support
- Condense invoice editor tax/totals section into efficient grid layout
- Update client dropdown to single-line format (name + email)
- Add fixed footer with floating action bar pattern matching business
  forms
- Redesign invoice viewer with better space utilization and visual
  hierarchy
- Maintain professional appearance and consistent design system
- Fix Next.js 15 params Promise handling across all invoice pages
- Resolve TypeScript compilation errors and type-only imports
This commit is contained in:
2025-07-15 00:29:02 -04:00
parent 89de059501
commit f331136090
79 changed files with 9944 additions and 4223 deletions
+48 -73
View File
@@ -46,6 +46,7 @@ import {
DialogTrigger,
} from "~/components/ui/dialog";
import { Textarea } from "~/components/ui/textarea";
import { PageHeader } from "~/components/page-header";
export default function SettingsPage() {
const { data: session } = useSession();
@@ -230,34 +231,26 @@ export default function SettingsPage() {
return (
<div className="space-y-8">
{/* Header */}
<div>
<h1 className="bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-4xl font-bold text-transparent dark:from-emerald-400 dark:to-teal-400">
Settings
</h1>
<p className="mt-2 text-lg text-gray-600 dark:text-gray-300">
Manage your account and data preferences
</p>
</div>
<PageHeader
title="Settings"
description="Manage your account and data preferences"
variant="large-gradient"
/>
<div className="grid gap-8 lg:grid-cols-2">
{/* Profile Section */}
<Card className="dark:border-gray-700 dark:bg-gray-800/80">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 dark:text-white">
<User className="h-5 w-5 dark:text-emerald-400" />
<CardTitle className="flex items-center gap-2">
<User className="h-5 w-5 text-emerald-600" />
Profile
</CardTitle>
<CardDescription className="dark:text-gray-300">
Update your personal information
</CardDescription>
<CardDescription>Update your personal information</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<form onSubmit={handleUpdateProfile} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name" className="dark:text-gray-300">
Name
</Label>
<Label htmlFor="name">Name</Label>
<Input
id="name"
value={name}
@@ -266,16 +259,14 @@ export default function SettingsPage() {
/>
</div>
<div className="space-y-2">
<Label htmlFor="email" className="dark:text-gray-300">
Email
</Label>
<Label htmlFor="email">Email</Label>
<Input
id="email"
value={session?.user?.email ?? ""}
disabled
className="bg-gray-50 dark:bg-gray-700 dark:text-gray-400"
className="bg-muted"
/>
<p className="text-sm text-gray-500 dark:text-gray-400">
<p className="text-muted-foreground text-sm">
Email cannot be changed
</p>
</div>
@@ -293,41 +284,33 @@ export default function SettingsPage() {
</Card>
{/* Data Statistics */}
<Card className="dark:border-gray-700 dark:bg-gray-800/80">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 dark:text-white">
<Database className="h-5 w-5 dark:text-emerald-400" />
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5 text-emerald-600" />
Your Data
</CardTitle>
<CardDescription className="dark:text-gray-300">
Overview of your account data
</CardDescription>
<CardDescription>Overview of your account data</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<div className="text-2xl font-bold text-emerald-600 dark:text-emerald-400">
<div className="text-2xl font-bold text-emerald-600">
{dataStats?.clients ?? 0}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Clients
</div>
<div className="text-muted-foreground text-sm">Clients</div>
</div>
<div>
<div className="text-2xl font-bold text-emerald-600 dark:text-emerald-400">
<div className="text-2xl font-bold text-emerald-600">
{dataStats?.businesses ?? 0}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Businesses
</div>
<div className="text-muted-foreground text-sm">Businesses</div>
</div>
<div>
<div className="text-2xl font-bold text-emerald-600 dark:text-emerald-400">
<div className="text-2xl font-bold text-emerald-600">
{dataStats?.invoices ?? 0}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Invoices
</div>
<div className="text-muted-foreground text-sm">Invoices</div>
</div>
</div>
</CardContent>
@@ -335,13 +318,13 @@ export default function SettingsPage() {
</div>
{/* Backup & Restore Section */}
<Card className="dark:border-gray-700 dark:bg-gray-800/80">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 dark:text-white">
<Shield className="h-5 w-5 dark:text-emerald-400" />
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5 text-emerald-600" />
Backup & Restore
</CardTitle>
<CardDescription className="dark:text-gray-300">
<CardDescription>
Export your data for backup or import from a previous backup
</CardDescription>
</CardHeader>
@@ -349,8 +332,8 @@ export default function SettingsPage() {
<div className="grid gap-4 md:grid-cols-2">
{/* Export Data */}
<div className="space-y-3">
<h3 className="font-semibold dark:text-white">Export Data</h3>
<p className="text-sm text-gray-600 dark:text-gray-300">
<h3 className="font-semibold">Export Data</h3>
<p className="text-muted-foreground text-sm">
Download all your clients, businesses, and invoices as a JSON
backup file.
</p>
@@ -367,8 +350,8 @@ export default function SettingsPage() {
{/* Import Data */}
<div className="space-y-3">
<h3 className="font-semibold dark:text-white">Import Data</h3>
<p className="text-sm text-gray-600 dark:text-gray-300">
<h3 className="font-semibold">Import Data</h3>
<p className="text-muted-foreground text-sm">
Restore your data from a previous backup file.
</p>
<Dialog
@@ -381,12 +364,10 @@ export default function SettingsPage() {
Import Data
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl dark:border-gray-700 dark:bg-gray-800">
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle className="dark:text-white">
Import Backup Data
</DialogTitle>
<DialogDescription className="dark:text-gray-300">
<DialogTitle>Import Backup Data</DialogTitle>
<DialogDescription>
Paste the contents of your backup JSON file below. This
will add the data to your existing account.
</DialogDescription>
@@ -424,11 +405,9 @@ export default function SettingsPage() {
</div>
</div>
<div className="rounded-lg bg-blue-50 p-4 dark:border dark:border-blue-800/30 dark:bg-blue-900/20">
<h4 className="font-medium text-blue-900 dark:text-blue-300">
Backup Tips
</h4>
<ul className="mt-2 space-y-1 text-sm text-blue-800 dark:text-blue-200">
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4">
<h4 className="font-medium text-blue-900">Backup Tips</h4>
<ul className="mt-2 space-y-1 text-sm text-blue-800">
<li> Regular backups help protect your data</li>
<li>
Backup files contain all your business data in JSON format
@@ -443,23 +422,21 @@ export default function SettingsPage() {
</Card>
{/* Danger Zone */}
<Card className="border-red-200 dark:border-red-800/50 dark:bg-gray-800/80">
<Card className="border-red-200">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-red-600 dark:text-red-400">
<CardTitle className="flex items-center gap-2 text-red-600">
<AlertTriangle className="h-5 w-5" />
Danger Zone
</CardTitle>
<CardDescription className="dark:text-gray-300">
<CardDescription>
Irreversible actions for your account data
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="rounded-lg bg-red-50 p-4 dark:border dark:border-red-800/30 dark:bg-red-900/20">
<h4 className="font-medium text-red-900 dark:text-red-300">
Delete All Data
</h4>
<p className="mt-1 text-sm text-red-800 dark:text-red-200">
<div className="rounded-lg border border-red-200 bg-red-50 p-4">
<h4 className="font-medium text-red-900">Delete All Data</h4>
<p className="mt-1 text-sm text-red-800">
This will permanently delete all your clients, businesses,
invoices, and related data. This action cannot be undone.
</p>
@@ -469,12 +446,10 @@ export default function SettingsPage() {
<AlertDialogTrigger asChild>
<Button variant="destructive">Delete All Data</Button>
</AlertDialogTrigger>
<AlertDialogContent className="dark:border-gray-700 dark:bg-gray-800">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="dark:text-white">
Are you absolutely sure?
</AlertDialogTitle>
<AlertDialogDescription className="space-y-2 dark:text-gray-300">
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription className="space-y-2">
<p>
This action cannot be undone. This will permanently delete
all your:
@@ -487,7 +462,7 @@ export default function SettingsPage() {
</ul>
<p className="font-medium">
Type{" "}
<span className="rounded bg-gray-100 px-1 font-mono dark:bg-gray-700 dark:text-gray-200">
<span className="bg-muted rounded px-1 font-mono">
DELETE ALL DATA
</span>{" "}
to confirm: