Add Turso/Vercel deployment configuration

- Updated database connection to support Turso auth token
- Added vercel.json with bun build configuration
- Updated environment schema for production deployment
- Added new features and components for production readiness
This commit is contained in:
2025-07-12 01:42:43 -04:00
parent 2d217fab47
commit a1b40e7a9c
75 changed files with 8821 additions and 1803 deletions
@@ -0,0 +1,26 @@
"use client";
import { InvoiceView } from "~/components/invoice-view";
import { InvoiceForm } from "~/components/invoice-form";
interface UnifiedInvoicePageProps {
invoiceId: string;
mode: string;
}
export function UnifiedInvoicePage({
invoiceId,
mode,
}: UnifiedInvoicePageProps) {
return (
<div>
{/* Always render InvoiceForm to preserve state, but hide when in view mode */}
<div className={mode === "edit" ? "block" : "hidden"}>
<InvoiceForm invoiceId={invoiceId} />
</div>
{/* Show InvoiceView only when in view mode */}
{mode === "view" && <InvoiceView invoiceId={invoiceId} />}
</div>
);
}
@@ -1,53 +0,0 @@
import { auth } from "~/server/auth";
import { api, HydrateClient } from "~/trpc/server";
import { Button } from "~/components/ui/button";
import { InvoiceForm } from "~/components/invoice-form";
import Link from "next/link";
import { notFound } from "next/navigation";
interface EditInvoicePageProps {
params: Promise<{ id: string }>;
}
export default async function EditInvoicePage({ params }: EditInvoicePageProps) {
const session = await auth();
const { id } = await params;
if (!session?.user) {
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-emerald-50 via-white to-teal-50">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Access Denied</h1>
<p className="text-muted-foreground mb-8">Please sign in to edit invoices</p>
<Link href="/api/auth/signin">
<Button
size="lg"
className="bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700 text-white font-medium shadow-lg hover:shadow-xl transition-all duration-200"
>
Sign In
</Button>
</Link>
</div>
</div>
);
}
// Prefetch invoice data
try {
await api.invoices.getById.prefetch({ id: id });
} catch (error) {
notFound();
}
return (
<div>
<div className="mb-8">
<h1 className="text-3xl font-bold bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Edit Invoice</h1>
<p className="text-gray-600 mt-1 text-lg">Update the invoice details below.</p>
</div>
<HydrateClient>
<InvoiceForm invoiceId={id} />
</HydrateClient>
</div>
);
}
+54 -45
View File
@@ -1,63 +1,72 @@
import { auth } from "~/server/auth";
import { api, HydrateClient } from "~/trpc/server";
import { Button } from "~/components/ui/button";
import { InvoiceView } from "~/components/invoice-view";
import { InvoiceForm } from "~/components/invoice-form";
import Link from "next/link";
import { notFound } from "next/navigation";
import { Edit } from "lucide-react";
import { Edit, Eye, ArrowLeft } from "lucide-react";
import { UnifiedInvoicePage } from "./_components/unified-invoice-page";
interface InvoicePageProps {
params: Promise<{ id: string }>;
searchParams: Promise<{ mode?: string }>;
}
export default async function InvoicePage({ params }: InvoicePageProps) {
const session = await auth();
export default async function InvoicePage({
params,
searchParams,
}: InvoicePageProps) {
const { id } = await params;
if (!session?.user) {
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-emerald-50 via-white to-teal-50">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Access Denied</h1>
<p className="text-muted-foreground mb-8">Please sign in to view invoices</p>
<Link href="/api/auth/signin">
<Button
size="lg"
className="bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700 text-white font-medium shadow-lg hover:shadow-xl transition-all duration-200"
>
Sign In
</Button>
</Link>
</div>
</div>
);
}
// Prefetch invoice data
try {
await api.invoices.getById.prefetch({ id: id });
} catch (error) {
notFound();
}
const { mode = "view" } = await searchParams;
return (
<div>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-8 gap-4">
<div>
<h1 className="text-3xl font-bold bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Invoice Details</h1>
<p className="text-gray-600 mt-1 text-lg">View and manage invoice information.</p>
</div>
<div className="flex gap-3">
<Button asChild variant="outline" size="lg" className="bg-white/80 border-gray-200 hover:bg-gray-50 text-gray-700 font-medium shadow-lg hover:shadow-xl">
<Link href={`/dashboard/invoices/${id}/edit`}>
<Edit className="mr-2 h-5 w-5" /> Edit Invoice
<div className="mb-6">
<div className="mb-4 flex items-center justify-between">
<div>
<h1 className="bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-3xl font-bold text-transparent">
Invoice Details
</h1>
<p className="mt-1 text-lg text-gray-600">
View and manage invoice information.
</p>
</div>
<div className="relative flex rounded-lg border border-gray-200 bg-gray-100 p-1">
<div
className={`absolute top-1 bottom-1 rounded-md bg-white shadow-sm transition-all duration-300 ease-in-out ${
mode === "view" ? "left-1 w-10" : "left-11 w-10"
}`}
/>
<Link
href={`/dashboard/invoices/${id}?mode=view`}
className={`relative z-10 rounded-md px-3 py-2 transition-all duration-200 ${
mode === "view"
? "text-emerald-600"
: "text-gray-600 hover:bg-gray-50 hover:text-gray-800"
}`}
>
<Eye className="h-4 w-4" />
</Link>
</Button>
<Link
href={`/dashboard/invoices/${id}?mode=edit`}
className={`relative z-10 rounded-md px-3 py-2 transition-all duration-200 ${
mode === "edit"
? "text-emerald-600"
: "text-gray-600 hover:bg-gray-50 hover:text-gray-800"
}`}
>
<Edit className="h-4 w-4" />
</Link>
</div>
</div>
<div className="mt-4">
<HydrateClient>
<UnifiedInvoicePage invoiceId={id} mode={mode} />
</HydrateClient>
</div>
</div>
<HydrateClient>
<InvoiceView invoiceId={id} />
</HydrateClient>
</div>
);
}
}
@@ -0,0 +1,15 @@
"use client";
import { api } from "~/trpc/react";
import { UniversalTable } from "~/components/ui/universal-table";
import { TableSkeleton } from "~/components/ui/skeleton";
export function InvoicesTable() {
const { isLoading } = api.invoices.getAll.useQuery();
if (isLoading) {
return <TableSkeleton rows={8} />;
}
return <UniversalTable resource="invoices" />;
}
+8 -28
View File
@@ -1,36 +1,16 @@
import Link from "next/link";
import { auth } from "~/server/auth";
import { api, HydrateClient } from "~/trpc/server";
import { Button } from "~/components/ui/button";
import { HydrateClient } from "~/trpc/server";
import { CSVImportPage } from "~/components/csv-import-page";
export default async function ImportPage() {
const session = await auth();
if (!session?.user) {
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-emerald-50 via-white to-teal-50">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Access Denied</h1>
<p className="text-muted-foreground mb-8">Please sign in to import invoices</p>
<Link href="/api/auth/signin">
<Button
size="lg"
className="bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700 text-white font-medium shadow-lg hover:shadow-xl transition-all duration-200"
>
Sign In
</Button>
</Link>
</div>
</div>
);
}
return (
<div>
<div className="mb-8">
<h1 className="text-3xl font-bold bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Import Invoices</h1>
<p className="text-gray-600 mt-1 text-lg">Upload CSV files to create invoices in batch.</p>
<h1 className="bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-3xl font-bold text-transparent">
Import Invoices
</h1>
<p className="mt-1 text-lg text-gray-600">
Upload CSV files to create invoices in batch.
</p>
</div>
<HydrateClient>
@@ -38,4 +18,4 @@ export default async function ImportPage() {
</HydrateClient>
</div>
);
}
}
+7 -27
View File
@@ -1,40 +1,20 @@
import { auth } from "~/server/auth";
import { HydrateClient } from "~/trpc/server";
import { Button } from "~/components/ui/button";
import Link from "next/link";
import { InvoiceForm } from "~/components/invoice-form";
export default async function NewInvoicePage() {
const session = await auth();
if (!session?.user) {
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-emerald-50 via-white to-teal-50">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Access Denied</h1>
<p className="text-muted-foreground mb-8">Please sign in to create invoices</p>
<Link href="/api/auth/signin">
<Button
size="lg"
className="bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700 text-white font-medium shadow-lg hover:shadow-xl transition-all duration-200"
>
Sign In
</Button>
</Link>
</div>
</div>
);
}
return (
<div>
<div className="mb-8">
<h1 className="text-3xl font-bold bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Create Invoice</h1>
<p className="text-gray-600 mt-1 text-lg">Fill out the details below to create a new invoice.</p>
<h1 className="bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-3xl font-bold text-transparent">
Create Invoice
</h1>
<p className="mt-1 text-lg text-gray-600">
Fill out the details below to create a new invoice.
</p>
</div>
<HydrateClient>
<InvoiceForm />
</HydrateClient>
</div>
);
}
}
+22 -33
View File
@@ -1,49 +1,38 @@
import Link from "next/link";
import { auth } from "~/server/auth";
import { api, HydrateClient } from "~/trpc/server";
import { Button } from "~/components/ui/button";
import { UniversalTable } from "~/components/ui/universal-table";
import { Plus, Upload } from "lucide-react";
import { InvoicesTable } from "./_components/invoices-table";
export default async function InvoicesPage() {
const session = await auth();
if (!session?.user) {
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-emerald-50 via-white to-teal-50">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Access Denied</h1>
<p className="text-muted-foreground mb-8">Please sign in to view invoices</p>
<Link href="/api/auth/signin">
<Button
size="lg"
className="bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700 text-white font-medium shadow-lg hover:shadow-xl transition-all duration-200"
>
Sign In
</Button>
</Link>
</div>
</div>
);
}
// Prefetch invoices data
void api.invoices.getAll.prefetch();
return (
<div>
<div className="flex flex-col md:flex-row md:items-center md:justify-between mb-8 gap-4">
<div className="mb-8 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h1 className="text-3xl font-bold bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">Invoices</h1>
<p className="text-gray-600 mt-1 text-lg">Manage your invoices and payments.</p>
<h1 className="bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-3xl font-bold text-transparent">
Invoices
</h1>
<p className="mt-1 text-lg text-gray-600">
Manage your invoices and payments.
</p>
</div>
<div className="flex gap-3">
<Button asChild variant="outline" size="lg" className="bg-white/80 border-gray-200 hover:bg-gray-50 text-gray-700 font-medium shadow-lg hover:shadow-xl">
<Button
asChild
variant="outline"
size="lg"
className="border-gray-200 bg-white/80 font-medium text-gray-700 shadow-lg hover:bg-gray-50 hover:shadow-xl"
>
<Link href="/dashboard/invoices/import">
<Upload className="mr-2 h-5 w-5" /> Import CSV
</Link>
</Button>
<Button asChild size="lg" className="bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700 text-white font-medium shadow-lg hover:shadow-xl">
<Button
asChild
size="lg"
className="bg-gradient-to-r from-emerald-600 to-teal-600 font-medium text-white shadow-lg hover:from-emerald-700 hover:to-teal-700 hover:shadow-xl"
>
<Link href="/dashboard/invoices/new">
<Plus className="mr-2 h-5 w-5" /> Add Invoice
</Link>
@@ -51,8 +40,8 @@ export default async function InvoicesPage() {
</div>
</div>
<HydrateClient>
<UniversalTable resource="invoices" />
<InvoicesTable />
</HydrateClient>
</div>
);
}
}