From a680f89a469c5717a95c2417fa30853d43a4a1d0 Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Mon, 11 Aug 2025 01:50:20 -0400 Subject: [PATCH] Add business nickname support across app and API --- drizzle/0000_warm_squadron_sinister.sql | 130 --- drizzle/meta/0000_snapshot.json | 995 ------------------ drizzle/meta/_journal.json | 13 - src/app/dashboard/businesses/[id]/page.tsx | 41 +- .../_components/businesses-data-table.tsx | 34 +- src/app/dashboard/businesses/page.tsx | 2 +- src/app/dashboard/invoices/[id]/send/page.tsx | 1 + src/components/data/data-table.tsx | 18 +- src/components/data/invoice-view.tsx | 8 +- src/components/forms/business-form.tsx | 49 +- src/components/forms/invoice-form.tsx | 49 +- src/components/ui/select.tsx | 16 +- src/lib/email-templates/invoice-email.ts | 1 + src/lib/pdf-export.tsx | 1 + src/server/api/routers/businesses.ts | 112 +- src/server/api/routers/email.ts | 8 +- src/server/api/routers/settings.ts | 20 +- src/server/db/schema.ts | 2 + src/types/invoice.ts | 1 + 19 files changed, 308 insertions(+), 1193 deletions(-) delete mode 100644 drizzle/0000_warm_squadron_sinister.sql delete mode 100644 drizzle/meta/0000_snapshot.json delete mode 100644 drizzle/meta/_journal.json diff --git a/drizzle/0000_warm_squadron_sinister.sql b/drizzle/0000_warm_squadron_sinister.sql deleted file mode 100644 index abbbfb1..0000000 --- a/drizzle/0000_warm_squadron_sinister.sql +++ /dev/null @@ -1,130 +0,0 @@ -CREATE TABLE "beenvoice_account" ( - "userId" varchar(255) NOT NULL, - "type" varchar(255) NOT NULL, - "provider" varchar(255) NOT NULL, - "providerAccountId" varchar(255) NOT NULL, - "refresh_token" text, - "access_token" text, - "expires_at" integer, - "token_type" varchar(255), - "scope" varchar(255), - "id_token" text, - "session_state" varchar(255), - CONSTRAINT "beenvoice_account_provider_providerAccountId_pk" PRIMARY KEY("provider","providerAccountId") -); ---> statement-breakpoint -CREATE TABLE "beenvoice_business" ( - "id" varchar(255) PRIMARY KEY NOT NULL, - "name" varchar(255) NOT NULL, - "email" varchar(255), - "phone" varchar(50), - "addressLine1" varchar(255), - "addressLine2" varchar(255), - "city" varchar(100), - "state" varchar(50), - "postalCode" varchar(20), - "country" varchar(100), - "website" varchar(255), - "taxId" varchar(100), - "logoUrl" varchar(500), - "isDefault" boolean DEFAULT false, - "resendApiKey" varchar(255), - "resendDomain" varchar(255), - "emailFromName" varchar(255), - "createdById" varchar(255) NOT NULL, - "createdAt" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, - "updatedAt" timestamp -); ---> statement-breakpoint -CREATE TABLE "beenvoice_client" ( - "id" varchar(255) PRIMARY KEY NOT NULL, - "name" varchar(255) NOT NULL, - "email" varchar(255), - "phone" varchar(50), - "addressLine1" varchar(255), - "addressLine2" varchar(255), - "city" varchar(100), - "state" varchar(50), - "postalCode" varchar(20), - "country" varchar(100), - "defaultHourlyRate" real DEFAULT 100 NOT NULL, - "createdById" varchar(255) NOT NULL, - "createdAt" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, - "updatedAt" timestamp -); ---> statement-breakpoint -CREATE TABLE "beenvoice_invoice_item" ( - "id" varchar(255) PRIMARY KEY NOT NULL, - "invoiceId" varchar(255) NOT NULL, - "date" timestamp NOT NULL, - "description" varchar(500) NOT NULL, - "hours" real NOT NULL, - "rate" real NOT NULL, - "amount" real NOT NULL, - "position" integer DEFAULT 0 NOT NULL, - "createdAt" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL -); ---> statement-breakpoint -CREATE TABLE "beenvoice_invoice" ( - "id" varchar(255) PRIMARY KEY NOT NULL, - "invoiceNumber" varchar(100) NOT NULL, - "businessId" varchar(255), - "clientId" varchar(255) NOT NULL, - "issueDate" timestamp NOT NULL, - "dueDate" timestamp NOT NULL, - "status" varchar(50) DEFAULT 'draft' NOT NULL, - "totalAmount" real DEFAULT 0 NOT NULL, - "taxRate" real DEFAULT 0 NOT NULL, - "notes" varchar(1000), - "createdById" varchar(255) NOT NULL, - "createdAt" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, - "updatedAt" timestamp -); ---> statement-breakpoint -CREATE TABLE "beenvoice_session" ( - "sessionToken" varchar(255) PRIMARY KEY NOT NULL, - "userId" varchar(255) NOT NULL, - "expires" timestamp NOT NULL -); ---> statement-breakpoint -CREATE TABLE "beenvoice_user" ( - "id" varchar(255) PRIMARY KEY NOT NULL, - "name" varchar(255), - "email" varchar(255) NOT NULL, - "password" varchar(255), - "emailVerified" timestamp DEFAULT CURRENT_TIMESTAMP, - "image" varchar(255) -); ---> statement-breakpoint -CREATE TABLE "beenvoice_verification_token" ( - "identifier" varchar(255) NOT NULL, - "token" varchar(255) NOT NULL, - "expires" timestamp NOT NULL, - CONSTRAINT "beenvoice_verification_token_identifier_token_pk" PRIMARY KEY("identifier","token") -); ---> statement-breakpoint -ALTER TABLE "beenvoice_account" ADD CONSTRAINT "beenvoice_account_userId_beenvoice_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "beenvoice_business" ADD CONSTRAINT "beenvoice_business_createdById_beenvoice_user_id_fk" FOREIGN KEY ("createdById") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "beenvoice_client" ADD CONSTRAINT "beenvoice_client_createdById_beenvoice_user_id_fk" FOREIGN KEY ("createdById") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "beenvoice_invoice_item" ADD CONSTRAINT "beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk" FOREIGN KEY ("invoiceId") REFERENCES "public"."beenvoice_invoice"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "beenvoice_invoice" ADD CONSTRAINT "beenvoice_invoice_businessId_beenvoice_business_id_fk" FOREIGN KEY ("businessId") REFERENCES "public"."beenvoice_business"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "beenvoice_invoice" ADD CONSTRAINT "beenvoice_invoice_clientId_beenvoice_client_id_fk" FOREIGN KEY ("clientId") REFERENCES "public"."beenvoice_client"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "beenvoice_invoice" ADD CONSTRAINT "beenvoice_invoice_createdById_beenvoice_user_id_fk" FOREIGN KEY ("createdById") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "beenvoice_session" ADD CONSTRAINT "beenvoice_session_userId_beenvoice_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."beenvoice_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "account_user_id_idx" ON "beenvoice_account" USING btree ("userId");--> statement-breakpoint -CREATE INDEX "business_created_by_idx" ON "beenvoice_business" USING btree ("createdById");--> statement-breakpoint -CREATE INDEX "business_name_idx" ON "beenvoice_business" USING btree ("name");--> statement-breakpoint -CREATE INDEX "business_email_idx" ON "beenvoice_business" USING btree ("email");--> statement-breakpoint -CREATE INDEX "business_is_default_idx" ON "beenvoice_business" USING btree ("isDefault");--> statement-breakpoint -CREATE INDEX "client_created_by_idx" ON "beenvoice_client" USING btree ("createdById");--> statement-breakpoint -CREATE INDEX "client_name_idx" ON "beenvoice_client" USING btree ("name");--> statement-breakpoint -CREATE INDEX "client_email_idx" ON "beenvoice_client" USING btree ("email");--> statement-breakpoint -CREATE INDEX "invoice_item_invoice_id_idx" ON "beenvoice_invoice_item" USING btree ("invoiceId");--> statement-breakpoint -CREATE INDEX "invoice_item_date_idx" ON "beenvoice_invoice_item" USING btree ("date");--> statement-breakpoint -CREATE INDEX "invoice_item_position_idx" ON "beenvoice_invoice_item" USING btree ("position");--> statement-breakpoint -CREATE INDEX "invoice_business_id_idx" ON "beenvoice_invoice" USING btree ("businessId");--> statement-breakpoint -CREATE INDEX "invoice_client_id_idx" ON "beenvoice_invoice" USING btree ("clientId");--> statement-breakpoint -CREATE INDEX "invoice_created_by_idx" ON "beenvoice_invoice" USING btree ("createdById");--> statement-breakpoint -CREATE INDEX "invoice_number_idx" ON "beenvoice_invoice" USING btree ("invoiceNumber");--> statement-breakpoint -CREATE INDEX "invoice_status_idx" ON "beenvoice_invoice" USING btree ("status");--> statement-breakpoint -CREATE INDEX "session_userId_idx" ON "beenvoice_session" USING btree ("userId"); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json deleted file mode 100644 index f8900a6..0000000 --- a/drizzle/meta/0000_snapshot.json +++ /dev/null @@ -1,995 +0,0 @@ -{ - "id": "c86256e7-28b4-48ea-8104-933cbff6e217", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.beenvoice_account": { - "name": "beenvoice_account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "account_user_id_idx": { - "name": "account_user_id_idx", - "columns": [ - { - "expression": "userId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "beenvoice_account_userId_beenvoice_user_id_fk": { - "name": "beenvoice_account_userId_beenvoice_user_id_fk", - "tableFrom": "beenvoice_account", - "tableTo": "beenvoice_user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "beenvoice_account_provider_providerAccountId_pk": { - "name": "beenvoice_account_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.beenvoice_business": { - "name": "beenvoice_business", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "varchar(255)", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "phone": { - "name": "phone", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false - }, - "addressLine1": { - "name": "addressLine1", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "addressLine2": { - "name": "addressLine2", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "city": { - "name": "city", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false - }, - "state": { - "name": "state", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false - }, - "postalCode": { - "name": "postalCode", - "type": "varchar(20)", - "primaryKey": false, - "notNull": false - }, - "country": { - "name": "country", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false - }, - "website": { - "name": "website", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "taxId": { - "name": "taxId", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false - }, - "logoUrl": { - "name": "logoUrl", - "type": "varchar(500)", - "primaryKey": false, - "notNull": false - }, - "isDefault": { - "name": "isDefault", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "resendApiKey": { - "name": "resendApiKey", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "resendDomain": { - "name": "resendDomain", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "emailFromName": { - "name": "emailFromName", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "createdById": { - "name": "createdById", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "CURRENT_TIMESTAMP" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "business_created_by_idx": { - "name": "business_created_by_idx", - "columns": [ - { - "expression": "createdById", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "business_name_idx": { - "name": "business_name_idx", - "columns": [ - { - "expression": "name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "business_email_idx": { - "name": "business_email_idx", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "business_is_default_idx": { - "name": "business_is_default_idx", - "columns": [ - { - "expression": "isDefault", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "beenvoice_business_createdById_beenvoice_user_id_fk": { - "name": "beenvoice_business_createdById_beenvoice_user_id_fk", - "tableFrom": "beenvoice_business", - "tableTo": "beenvoice_user", - "columnsFrom": [ - "createdById" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.beenvoice_client": { - "name": "beenvoice_client", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "varchar(255)", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "phone": { - "name": "phone", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false - }, - "addressLine1": { - "name": "addressLine1", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "addressLine2": { - "name": "addressLine2", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "city": { - "name": "city", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false - }, - "state": { - "name": "state", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false - }, - "postalCode": { - "name": "postalCode", - "type": "varchar(20)", - "primaryKey": false, - "notNull": false - }, - "country": { - "name": "country", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false - }, - "defaultHourlyRate": { - "name": "defaultHourlyRate", - "type": "real", - "primaryKey": false, - "notNull": true, - "default": 100 - }, - "createdById": { - "name": "createdById", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "CURRENT_TIMESTAMP" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "client_created_by_idx": { - "name": "client_created_by_idx", - "columns": [ - { - "expression": "createdById", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "client_name_idx": { - "name": "client_name_idx", - "columns": [ - { - "expression": "name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "client_email_idx": { - "name": "client_email_idx", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "beenvoice_client_createdById_beenvoice_user_id_fk": { - "name": "beenvoice_client_createdById_beenvoice_user_id_fk", - "tableFrom": "beenvoice_client", - "tableTo": "beenvoice_user", - "columnsFrom": [ - "createdById" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.beenvoice_invoice_item": { - "name": "beenvoice_invoice_item", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "varchar(255)", - "primaryKey": true, - "notNull": true - }, - "invoiceId": { - "name": "invoiceId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "date": { - "name": "date", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "varchar(500)", - "primaryKey": false, - "notNull": true - }, - "hours": { - "name": "hours", - "type": "real", - "primaryKey": false, - "notNull": true - }, - "rate": { - "name": "rate", - "type": "real", - "primaryKey": false, - "notNull": true - }, - "amount": { - "name": "amount", - "type": "real", - "primaryKey": false, - "notNull": true - }, - "position": { - "name": "position", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "CURRENT_TIMESTAMP" - } - }, - "indexes": { - "invoice_item_invoice_id_idx": { - "name": "invoice_item_invoice_id_idx", - "columns": [ - { - "expression": "invoiceId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invoice_item_date_idx": { - "name": "invoice_item_date_idx", - "columns": [ - { - "expression": "date", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invoice_item_position_idx": { - "name": "invoice_item_position_idx", - "columns": [ - { - "expression": "position", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk": { - "name": "beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk", - "tableFrom": "beenvoice_invoice_item", - "tableTo": "beenvoice_invoice", - "columnsFrom": [ - "invoiceId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.beenvoice_invoice": { - "name": "beenvoice_invoice", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "varchar(255)", - "primaryKey": true, - "notNull": true - }, - "invoiceNumber": { - "name": "invoiceNumber", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true - }, - "businessId": { - "name": "businessId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "clientId": { - "name": "clientId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "issueDate": { - "name": "issueDate", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "dueDate": { - "name": "dueDate", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "default": "'draft'" - }, - "totalAmount": { - "name": "totalAmount", - "type": "real", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "taxRate": { - "name": "taxRate", - "type": "real", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "notes": { - "name": "notes", - "type": "varchar(1000)", - "primaryKey": false, - "notNull": false - }, - "createdById": { - "name": "createdById", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "CURRENT_TIMESTAMP" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "invoice_business_id_idx": { - "name": "invoice_business_id_idx", - "columns": [ - { - "expression": "businessId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invoice_client_id_idx": { - "name": "invoice_client_id_idx", - "columns": [ - { - "expression": "clientId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invoice_created_by_idx": { - "name": "invoice_created_by_idx", - "columns": [ - { - "expression": "createdById", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invoice_number_idx": { - "name": "invoice_number_idx", - "columns": [ - { - "expression": "invoiceNumber", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invoice_status_idx": { - "name": "invoice_status_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "beenvoice_invoice_businessId_beenvoice_business_id_fk": { - "name": "beenvoice_invoice_businessId_beenvoice_business_id_fk", - "tableFrom": "beenvoice_invoice", - "tableTo": "beenvoice_business", - "columnsFrom": [ - "businessId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "beenvoice_invoice_clientId_beenvoice_client_id_fk": { - "name": "beenvoice_invoice_clientId_beenvoice_client_id_fk", - "tableFrom": "beenvoice_invoice", - "tableTo": "beenvoice_client", - "columnsFrom": [ - "clientId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "beenvoice_invoice_createdById_beenvoice_user_id_fk": { - "name": "beenvoice_invoice_createdById_beenvoice_user_id_fk", - "tableFrom": "beenvoice_invoice", - "tableTo": "beenvoice_user", - "columnsFrom": [ - "createdById" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.beenvoice_session": { - "name": "beenvoice_session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "varchar(255)", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - { - "expression": "userId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "beenvoice_session_userId_beenvoice_user_id_fk": { - "name": "beenvoice_session_userId_beenvoice_user_id_fk", - "tableFrom": "beenvoice_session", - "tableTo": "beenvoice_user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.beenvoice_user": { - "name": "beenvoice_user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "varchar(255)", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "password": { - "name": "password", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "CURRENT_TIMESTAMP" - }, - "image": { - "name": "image", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.beenvoice_verification_token": { - "name": "beenvoice_verification_token", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "beenvoice_verification_token_identifier_token_pk": { - "name": "beenvoice_verification_token_identifier_token_pk", - "columns": [ - "identifier", - "token" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json deleted file mode 100644 index 28de774..0000000 --- a/drizzle/meta/_journal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1753825898609, - "tag": "0000_warm_squadron_sinister", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/src/app/dashboard/businesses/[id]/page.tsx b/src/app/dashboard/businesses/[id]/page.tsx index b8b9501..18c8c8f 100644 --- a/src/app/dashboard/businesses/[id]/page.tsx +++ b/src/app/dashboard/businesses/[id]/page.tsx @@ -45,7 +45,7 @@ export default async function BusinessDetailPage({ return (
@@ -69,7 +69,7 @@ export default async function BusinessDetailPage({ -
+
Business Information @@ -84,7 +84,7 @@ export default async function BusinessDetailPage({
{business.email && (
-
+
@@ -100,7 +100,7 @@ export default async function BusinessDetailPage({ {business.phone && (
-
+
@@ -116,7 +116,7 @@ export default async function BusinessDetailPage({ {business.website && (
-
+
@@ -137,7 +137,7 @@ export default async function BusinessDetailPage({ {business.taxId && (
-
+
@@ -162,7 +162,7 @@ export default async function BusinessDetailPage({ Business Address
-
+
@@ -205,7 +205,7 @@ export default async function BusinessDetailPage({

Business Details

-
+
@@ -218,10 +218,31 @@ export default async function BusinessDetailPage({
+ {business.nickname && ( +
+
+ +
+
+
+

+ Nickname +

+ + Internal only + +
+

+ {business.nickname} +

+
+
+ )} + {/* Default Business Badge */} {business.isDefault && (
-
+
@@ -248,7 +269,7 @@ export default async function BusinessDetailPage({ -
+
Quick Actions diff --git a/src/app/dashboard/businesses/_components/businesses-data-table.tsx b/src/app/dashboard/businesses/_components/businesses-data-table.tsx index 00b0c4b..b160f29 100644 --- a/src/app/dashboard/businesses/_components/businesses-data-table.tsx +++ b/src/app/dashboard/businesses/_components/businesses-data-table.tsx @@ -22,6 +22,7 @@ import { toast } from "sonner"; interface Business { id: string; name: string; + nickname: string | null; email: string | null; phone: string | null; addressLine1: string | null; @@ -61,6 +62,11 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) { const utils = api.useUtils(); + const searchableBusinesses = businesses.map((b) => ({ + ...b, + searchValue: `${b.name} ${b.nickname ?? ""}`.trim(), + })); + const deleteBusinessMutation = api.businesses.delete.useMutation({ onSuccess: () => { toast.success("Business deleted successfully"); @@ -91,7 +97,7 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) { const business = row.original; return (
-
+
@@ -104,6 +110,17 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) { ); }, }, + { + accessorKey: "nickname", + header: ({ column }) => ( + + ), + cell: ({ row }) => row.original.nickname ?? "—", + meta: { + headerClassName: "hidden sm:table-cell", + cellClassName: "hidden sm:table-cell", + }, + }, { accessorKey: "phone", header: ({ column }) => ( @@ -175,6 +192,15 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) { ); }, }, + { + accessorKey: "searchValue", + header: "Search", + cell: () => null, + meta: { + headerClassName: "hidden", + cellClassName: "hidden", + }, + }, { id: "actions", cell: ({ row }) => { @@ -210,9 +236,9 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) { <> diff --git a/src/app/dashboard/businesses/page.tsx b/src/app/dashboard/businesses/page.tsx index cfac894..47509c9 100644 --- a/src/app/dashboard/businesses/page.tsx +++ b/src/app/dashboard/businesses/page.tsx @@ -31,7 +31,7 @@ export default async function BusinessesPage() { - }> + }> diff --git a/src/app/dashboard/invoices/[id]/send/page.tsx b/src/app/dashboard/invoices/[id]/send/page.tsx index aa91693..a503dc3 100644 --- a/src/app/dashboard/invoices/[id]/send/page.tsx +++ b/src/app/dashboard/invoices/[id]/send/page.tsx @@ -165,6 +165,7 @@ export default function SendEmailPage() { business: invoiceData.business ? { name: invoiceData.business.name, + nickname: invoiceData.business.nickname, email: invoiceData.business.email, } : undefined, diff --git a/src/components/data/data-table.tsx b/src/components/data/data-table.tsx index a655e72..10d0023 100644 --- a/src/components/data/data-table.tsx +++ b/src/components/data/data-table.tsx @@ -98,6 +98,7 @@ export function DataTable({ React.useState({}); const [rowSelection, setRowSelection] = React.useState({}); const [globalFilter, setGlobalFilter] = React.useState(""); + const [searchInput, setSearchInput] = React.useState(""); // Mobile detection hook const [isMobile, setIsMobile] = React.useState(false); @@ -171,6 +172,19 @@ export function DataTable({ table.setPageSize(isMobile ? 5 : pageSize); }, [isMobile, pageSize, table]); + // Debounce search input updates to the table's global filter + React.useEffect(() => { + const timeout = setTimeout(() => { + setGlobalFilter(searchInput); + }, 300); + return () => clearTimeout(timeout); + }, [searchInput]); + + // Keep search input in sync when globalFilter is changed externally (e.g., "Clear filters") + React.useEffect(() => { + setSearchInput(globalFilter ?? ""); + }, [globalFilter]); + const pageSizeOptions = [5, 10, 20, 30, 50, 100]; // Handle row click @@ -223,8 +237,8 @@ export function DataTable({ setGlobalFilter(event.target.value)} + value={searchInput ?? ""} + onChange={(event) => setSearchInput(event.target.value)} className="h-9 w-full pr-3 pl-9" />
diff --git a/src/components/data/invoice-view.tsx b/src/components/data/invoice-view.tsx index 4eb18d6..36ba5c4 100644 --- a/src/components/data/invoice-view.tsx +++ b/src/components/data/invoice-view.tsx @@ -188,7 +188,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
-
+
@@ -239,7 +239,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) { > {isExportingPDF ? ( <> -
+
Generating PDF... ) : ( @@ -326,7 +326,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) { -
+
@@ -479,7 +479,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) { {/* Danger Zone */} - + Danger Zone diff --git a/src/components/forms/business-form.tsx b/src/components/forms/business-form.tsx index e5b1b47..5a47f75 100644 --- a/src/components/forms/business-form.tsx +++ b/src/components/forms/business-form.tsx @@ -46,6 +46,7 @@ interface BusinessFormProps { interface FormData { name: string; + nickname: string; email: string; phone: string; addressLine1: string; @@ -64,6 +65,7 @@ interface FormData { interface FormErrors { name?: string; + nickname?: string; email?: string; phone?: string; addressLine1?: string; @@ -80,6 +82,7 @@ interface FormErrors { const initialFormData: FormData = { name: "", + nickname: "", email: "", phone: "", addressLine1: "", @@ -153,6 +156,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) { if (business && mode === "edit") { setFormData({ name: business.name, + nickname: business.nickname ?? "", email: business.email ?? "", phone: business.phone ?? "", addressLine1: business.addressLine1 ?? "", @@ -198,6 +202,10 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) { if (!formData.name.trim()) { newErrors.name = VALIDATION_MESSAGES.required; } + // Nickname validation (optional, max 255 chars) + if (formData.nickname && formData.nickname.length > 255) { + newErrors.nickname = "Nickname must be 255 characters or less"; + } // Email validation if (formData.email && !isValidEmail(formData.email)) { @@ -280,6 +288,8 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) { // Format website URL before submission const dataToSubmit = { ...formData, + name: formData.name.trim(), + nickname: formData.nickname.trim(), website: formData.website ? formatWebsiteUrl(formData.website) : "", }; @@ -287,6 +297,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) { // Create business data (excluding email config fields) const businessData = { name: dataToSubmit.name, + nickname: dataToSubmit.nickname, email: dataToSubmit.email, phone: dataToSubmit.phone, addressLine1: dataToSubmit.addressLine1, @@ -320,6 +331,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) { // Update business data (excluding email config fields) const businessData = { name: dataToSubmit.name, + nickname: dataToSubmit.nickname, email: dataToSubmit.email, phone: dataToSubmit.phone, addressLine1: dataToSubmit.addressLine1, @@ -442,7 +454,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
-
+
@@ -475,6 +487,29 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) { )}
+
+ + + handleInputChange("nickname", e.target.value) + } + placeholder="e.g., Personal, Work, LLC" + disabled={isSubmitting} + /> + {errors.nickname && ( +

+ {errors.nickname} +

+ )} +
+