merge: resolve conflicts between feat/impersonation-backend and main

Keep both backend impersonation (schema, routes, types) and main's
additions (settings, branding, dev login, full customer portal UI).

Portal frontend files retain main's versions (complete UI with sidebar,
sections, mock impersonation). Wiring frontend to real impersonation
backend API remains as follow-up work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Groom Book CTO
2026-03-20 02:17:02 +00:00
50 changed files with 4677 additions and 503 deletions
@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS "business_settings" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"business_name" text DEFAULT 'GroomBook' NOT NULL,
"logo_base64" text,
"logo_mime_type" text,
"primary_color" text DEFAULT '#4f8a6f' NOT NULL,
"accent_color" text DEFAULT '#8b7355' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
-- Seed a default row so GET always returns something
INSERT INTO "business_settings" ("business_name", "primary_color", "accent_color")
VALUES ('GroomBook', '#4f8a6f', '#8b7355')
ON CONFLICT DO NOTHING;
@@ -0,0 +1,6 @@
-- Add client status (soft-delete support)
CREATE TYPE "client_status" AS ENUM ('active', 'disabled');
ALTER TABLE "clients"
ADD COLUMN "status" "client_status" NOT NULL DEFAULT 'active',
ADD COLUMN "disabled_at" timestamp;
+14
View File
@@ -57,6 +57,20 @@
"when": 1773820800000,
"tag": "0007_tip_splitting",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1773907200000,
"tag": "0008_business_settings",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1773993600000,
"tag": "0009_client_soft_delete",
"breakpoints": true
}
]
}
+2 -2
View File
@@ -7,8 +7,8 @@
"types": "./src/index.ts",
"exports": {
".": {
"types": "./src/index.ts",
"default": "./dist/index.js"
"default": "./dist/index.js",
"types": "./src/index.ts"
}
},
"scripts": {
+18
View File
@@ -42,6 +42,11 @@ export const paymentMethodEnum = pgEnum("payment_method", [
"other",
]);
export const clientStatusEnum = pgEnum("client_status", [
"active",
"disabled",
]);
// ─── Tables ───────────────────────────────────────────────────────────────────
export const clients = pgTable("clients", {
@@ -53,6 +58,8 @@ export const clients = pgTable("clients", {
notes: text("notes"),
// Set to true if the client has opted out of email reminders/notifications
emailOptOut: boolean("email_opt_out").notNull().default(false),
status: clientStatusEnum("status").notNull().default("active"),
disabledAt: timestamp("disabled_at"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
});
@@ -252,6 +259,17 @@ export const impersonationAuditLogs = pgTable("impersonation_audit_logs", {
createdAt: timestamp("created_at").notNull().defaultNow(),
});
export const businessSettings = pgTable("business_settings", {
id: uuid("id").primaryKey().defaultRandom(),
businessName: text("business_name").notNull().default("GroomBook"),
logoBase64: text("logo_base64"),
logoMimeType: text("logo_mime_type"),
primaryColor: text("primary_color").notNull().default("#4f8a6f"),
accentColor: text("accent_color").notNull().default("#8b7355"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
});
export const groomingVisitLogs = pgTable("grooming_visit_logs", {
id: uuid("id").primaryKey().defaultRandom(),
petId: uuid("pet_id")