diff --git a/README.md b/README.md index 5ceb747..35d7e53 100644 --- a/README.md +++ b/README.md @@ -259,4 +259,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file --- -Built with ❤️ for freelancers and small businesses who deserve better invoicing tools. +Built for freelancers and small businesses who deserve better invoicing tools. diff --git a/src/app/dashboard/clients/[id]/edit/page.tsx b/src/app/dashboard/clients/[id]/edit/page.tsx index 6583f65..fa1818f 100644 --- a/src/app/dashboard/clients/[id]/edit/page.tsx +++ b/src/app/dashboard/clients/[id]/edit/page.tsx @@ -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 ( -
- - - - -
- ); +export default function EditClientPage() { + const params = useParams(); + const clientId = Array.isArray(params?.id) ? params.id[0] : params?.id; + if (!clientId) return null; + + return ; } diff --git a/src/app/dashboard/clients/[id]/page.tsx b/src/app/dashboard/clients/[id]/page.tsx index edd11cd..96eae51 100644 --- a/src/app/dashboard/clients/[id]/page.tsx +++ b/src/app/dashboard/clients/[id]/page.tsx @@ -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 ( -
-
- - - +
+ + + + -
- {/* Client Information Card */} -
- - - - - Contact Information - - - - {/* Basic Info */} -
- {client.email && ( -
-
- -
-
-

Email

-

{client.email}

-
-
- )} - - {client.phone && ( -
-
- -
-
-

Phone

-

{client.phone}

-
-
- )} +
+ {/* Client Information Card */} +
+ + + +
+
- - {/* Address */} - {(client.addressLine1 ?? client.city ?? client.state) && ( -
-
-
- -
-
-

Address

-
+ Contact Information + + + + {/* Basic Info */} +
+ {client.email && ( +
+
+
-
- {client.addressLine1 &&

{client.addressLine1}

} - {client.addressLine2 &&

{client.addressLine2}

} +
+

+ Email +

+

{client.email}

+
+
+ )} + + {client.phone && ( +
+
+ +
+
+

+ Phone +

+

{client.phone}

+
+
+ )} +
+ + {/* Address */} + {(client.addressLine1 ?? client.city ?? client.state) && ( +
+

Client Address

+
+
+ +
+
+ {client.addressLine1 && ( +

{client.addressLine1}

+ )} + {client.addressLine2 && ( +

{client.addressLine2}

+ )} {(client.city ?? client.state ?? client.postalCode) && ( -

+

{[client.city, client.state, client.postalCode] .filter(Boolean) .join(", ")}

)} - {client.country &&

{client.country}

} + {client.country && ( +

{client.country}

+ )}
- )} +
+ )} - {/* Client Since */} -
-
- + {/* Client Since */} +
+

Client Details

+
+
+
-

Client Since

-

+

+ Client Since +

+

{formatDate(client.createdAt)}

- - -
+
+ + +
- {/* Stats Card */} -
+ {/* Stats Card */} +
+ + + +
+ +
+ Invoice Summary +
+
+ +
+

+ {formatCurrency(totalInvoiced)} +

+

Total Invoiced

+
+ +
+
+

+ {paidInvoices} +

+

Paid

+
+
+

+ {pendingInvoices} +

+

Pending

+
+
+
+
+ + {/* Recent Invoices */} + {client.invoices && client.invoices.length > 0 && ( - - - Invoice Summary + +
+ +
+ Recent Invoices
- -
-

- {formatCurrency(totalInvoiced)} -

-

Total Invoiced

-
- -
-
-

{paidInvoices}

-

Paid

-
-
-

- {pendingInvoices} -

-

Pending

-
+ +
+ {client.invoices.slice(0, 3).map((invoice) => ( +
+
+

+ {invoice.invoiceNumber} +

+

+ {formatDate(invoice.issueDate)} +

+
+
+

+ {formatCurrency(invoice.totalAmount)} +

+ + {invoice.status} + +
+
+ ))}
- - {/* Recent Invoices */} - {client.invoices && client.invoices.length > 0 && ( - - - - Recent Invoices - - - -
- {client.invoices.slice(0, 3).map((invoice) => ( -
-
-

- {invoice.invoiceNumber} -

-

- {formatDate(invoice.issueDate)} -

-
-
-

- {formatCurrency(invoice.totalAmount)} -

- - {invoice.status} - -
-
- ))} -
-
-
- )} -
+ )}
diff --git a/src/app/dashboard/clients/new/page.tsx b/src/app/dashboard/clients/new/page.tsx index fe460d8..629ccdc 100644 --- a/src/app/dashboard/clients/new/page.tsx +++ b/src/app/dashboard/clients/new/page.tsx @@ -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 ( -
- - - - -
- ); +import { ClientForm } from "~/components/forms/client-form"; + +export default function NewClientPage() { + return ; } diff --git a/src/app/dashboard/clients/page.tsx b/src/app/dashboard/clients/page.tsx index efb305d..2ffe1c6 100644 --- a/src/app/dashboard/clients/page.tsx +++ b/src/app/dashboard/clients/page.tsx @@ -14,7 +14,7 @@ export default async function ClientsPage() { description="Manage your clients and their information." variant="gradient" > -
-

+

${currentInvoice.totalAmount.toFixed(2)}

@@ -273,7 +273,7 @@ async function CurrentWork() {

@@ -68,17 +69,17 @@ export default function HomePage() {
- 100% Free Forever + Free Forever

Simple Invoicing for - Freelancers + Freelancers

-

+

Create professional invoices, manage clients, and track payments. - Built specifically for freelancers and small businesses— + Built for freelancers and small businesses— completely free.

@@ -86,9 +87,9 @@ export default function HomePage() { @@ -96,22 +97,22 @@ export default function HomePage() {
-
+
{[ "No credit card required", "Setup in 2 minutes", - "Cancel anytime", + "Free forever", ].map((text, i) => (
- + {text}
))} @@ -132,17 +133,14 @@ export default function HomePage() {
- Supercharged Features + Features

Everything you need to - - invoice professionally - + get paid

- Simple, powerful features designed specifically for freelancers - and small businesses. + Simple, powerful features for freelancers and small businesses.

@@ -157,8 +155,8 @@ export default function HomePage() { Quick Setup

- Start creating invoices immediately. No complicated setup or - configuration required. + Start creating invoices immediately. No complicated setup + required.

  • @@ -187,8 +185,7 @@ export default function HomePage() { Payment Tracking

    - Keep track of invoice status and monitor which clients have - paid. + Keep track of invoice status and monitor payments.

    • @@ -217,7 +214,7 @@ export default function HomePage() { Professional Features

      - Everything you need to look professional and get paid on time. + Professional features to help you get paid on time.

      • @@ -250,11 +247,10 @@ export default function HomePage() {

        - Simple, transparent pricing + Simple pricing

        - Start free, stay free. No hidden fees, no gotchas, no limits on - your success. + Start free, stay free. No hidden fees or limits.

        @@ -298,7 +294,7 @@ export default function HomePage() { variant="brand" className="w-full py-3 text-base font-semibold sm:text-lg" > - Get Started Now + Get Started @@ -319,10 +315,8 @@ export default function HomePage() {

        - Why freelancers - - choose BeenVoice - + Why choose + BeenVoice

        @@ -335,8 +329,7 @@ export default function HomePage() { Quick & Simple

        - No learning curve. Start creating professional invoices in - minutes, not hours. + No learning curve. Start creating invoices in minutes.

        @@ -347,8 +340,7 @@ export default function HomePage() { Always Free

        - No hidden fees, no premium tiers. All features are free for as - long as you need them. + No hidden fees, no premium tiers. All features are free.

        @@ -360,7 +352,7 @@ export default function HomePage() {

        Focus on your work, not paperwork. Automated calculations and - professional formatting. + formatting.

        @@ -377,13 +369,11 @@ export default function HomePage() {

        - Ready to revolutionize - your invoicing? + Ready to get started?

        -

        - Join thousands of entrepreneurs who've already transformed - their business with BeenVoice. Start your journey - today—completely free. +

        + Join thousands of freelancers already using BeenVoice. Start + today—completely free.

        @@ -391,15 +381,15 @@ export default function HomePage() {
        -
        +
        Free forever @@ -422,26 +412,38 @@ export default function HomePage() {
        -

        +

        Simple invoicing for freelancers. Free, forever.

        -
        - +
        + Sign In - + Register - + Features - + Pricing
        -
        -

        - © 2024 BeenVoice. Built with ♥ for entrepreneurs. +

        +

        + © 2025 Sean O'Connor.

        diff --git a/src/components/data/data-table.tsx b/src/components/data/data-table.tsx index 55bc842..3b09e1f 100644 --- a/src/components/data/data-table.tsx +++ b/src/components/data/data-table.tsx @@ -355,7 +355,7 @@ export function DataTable({ key={row.id} data-state={row.getIsSelected() && "selected"} className={cn( - "hover:bg-muted/20 data-[state=selected]:bg-muted/50 border-b transition-colors", + "hover:bg-muted/20 data-[state=selected]:bg-muted/50 border-border/40 border-b transition-colors", onRowClick && "cursor-pointer", )} onClick={(event) => diff --git a/src/components/forms/client-form.tsx b/src/components/forms/client-form.tsx index f391b71..c1e54fe 100644 --- a/src/components/forms/client-form.tsx +++ b/src/components/forms/client-form.tsx @@ -19,6 +19,7 @@ import { Label } from "~/components/ui/label"; import { Skeleton } from "~/components/ui/skeleton"; import { AddressForm } from "~/components/forms/address-form"; import { FloatingActionBar } from "~/components/layout/floating-action-bar"; +import { PageHeader } from "~/components/layout/page-header"; import { NumberInput } from "~/components/ui/number-input"; import { api } from "~/trpc/react"; import { @@ -246,181 +247,218 @@ export function ClientForm({ clientId, mode }: ClientFormProps) { } return ( -
        -
        - {/* Main Form Container - styled like data table */} -
        - {/* Basic Information */} - - -
        -
        - + <> +
        + + + + + + {/* Main Form Container - styled like data table */} +
        + {/* Basic Information */} + + +
        +
        + +
        +
        + Basic Information +

        + Enter the client's primary details +

        +
        -
        - Basic Information -

        - Enter the client's primary details -

        + + +
        + + handleInputChange("name", e.target.value)} + placeholder={PLACEHOLDERS.name} + className={`${errors.name ? "border-destructive" : ""}`} + disabled={isSubmitting} + /> + {errors.name && ( +

        {errors.name}

        + )}
        -
        -
        - -
        - - handleInputChange("name", e.target.value)} - placeholder={PLACEHOLDERS.name} - className={`${errors.name ? "border-destructive" : ""}`} - disabled={isSubmitting} + +
        +
        + + + handleInputChange("email", e.target.value) + } + placeholder={PLACEHOLDERS.email} + className={`${errors.email ? "border-destructive" : ""}`} + disabled={isSubmitting} + /> + {errors.email && ( +

        {errors.email}

        + )} +
        + +
        + + handlePhoneChange(e.target.value)} + placeholder={PLACEHOLDERS.phone} + className={`${errors.phone ? "border-destructive" : ""}`} + disabled={isSubmitting} + /> + {errors.phone && ( +

        {errors.phone}

        + )} +
        +
        + + + + {/* Address */} + + +
        +
        + + + + +
        +
        + Address +

        + Client's physical location +

        +
        +
        +
        + + - {errors.name && ( -

        {errors.name}

        - )} -
        +
        +
        -
        -
        - - handleInputChange("email", e.target.value)} - placeholder={PLACEHOLDERS.email} - className={`${errors.email ? "border-destructive" : ""}`} - disabled={isSubmitting} - /> - {errors.email && ( -

        {errors.email}

        - )} + {/* Billing Information */} + + +
        +
        + +
        +
        + Billing Information +

        + Default billing rates for this client +

        +
        - +
        +
        - - handlePhoneChange(e.target.value)} - placeholder={PLACEHOLDERS.phone} - className={`${errors.phone ? "border-destructive" : ""}`} - disabled={isSubmitting} - /> - {errors.phone && ( -

        {errors.phone}

        - )} -
        -
        - - - - {/* Address */} - - -
        -
        - - - - + Default Hourly Rate + + + handleInputChange("defaultHourlyRate", value) + } + min={0} + step={1} + prefix="$" + width="full" + disabled={isSubmitting} + /> + {errors.defaultHourlyRate && ( +

        + {errors.defaultHourlyRate} +

        + )}
        -
        - Address -

        - Client's physical location -

        -
        -
        -
        - - - -
        - - {/* Billing Information */} - - -
        -
        - -
        -
        - Billing Information -

        - Default billing rates for this client -

        -
        -
        -
        - -
        - - - handleInputChange("defaultHourlyRate", value) - } - min={0} - step={1} - prefix="$" - width="full" - disabled={isSubmitting} - /> - {errors.defaultHourlyRate && ( -

        - {errors.defaultHourlyRate} -

        - )} -
        -
        -
        -
        - + + +
        + +
        -
        + ); } diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index 2252c26..d5c5406 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -23,7 +23,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { return ( ); @@ -44,7 +44,7 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { tr]:last:border-b-0", + "bg-muted/50 border-border/60 border-t font-medium [&>tr]:last:border-b-0", className, )} {...props} @@ -57,7 +57,7 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {