From 07b5756014ba3be174370a3fbe1c781aa34d1834 Mon Sep 17 00:00:00 2001
From: Matt
Date: Sat, 9 May 2026 18:36:31 +0200
Subject: [PATCH] feat(profile): first/last name fields + collapse notification
preferences
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Two related cleanups for the user profile surface area:
(1) Add canonical first_name + last_name columns to user_profiles.
Migration 0049 backfills from display_name by splitting on the
first whitespace run; single-token names land as
(display_name, NULL) so we never throw away existing data.
Display name becomes an optional override (nicknames, vanity
formatting). /api/v1/me PATCH now accepts firstName/lastName,
and the user-settings form surfaces them as the primary inputs
with display name as a secondary "How your name appears" field.
(2) Remove the broken Notifications card from user-settings (it called
PATCH on an endpoint that has GET/PUT only and used a flat shape
vs the actual array shape). Replace with the working
NotificationPreferencesForm + ReminderDigestForm under a
#notifications anchor. /notifications/preferences becomes a
server-side redirect to /settings#notifications for back-compat;
the mobile More-sheet + user-menu Bell entry now deep-link to the
new anchor directly.
Drops the auto-generated drizzle-kit catch-up migration so we're not
sneaking accumulated schema drift into the journal — only the targeted
0049 lands here.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.../notifications/preferences/page.tsx | 28 ++--
src/app/api/v1/me/route.ts | 18 ++-
src/components/layout/mobile/more-sheet.tsx | 5 +-
src/components/layout/user-menu.tsx | 2 +-
src/components/settings/user-settings.tsx | 130 ++++++++----------
.../0049_user_profiles_first_last_name.sql | 22 +++
src/lib/db/migrations/meta/_journal.json | 4 +-
src/lib/db/schema/users.ts | 11 ++
8 files changed, 124 insertions(+), 96 deletions(-)
create mode 100644 src/lib/db/migrations/0049_user_profiles_first_last_name.sql
diff --git a/src/app/(dashboard)/[portSlug]/notifications/preferences/page.tsx b/src/app/(dashboard)/[portSlug]/notifications/preferences/page.tsx
index df1fd132..bc6d1c2d 100644
--- a/src/app/(dashboard)/[portSlug]/notifications/preferences/page.tsx
+++ b/src/app/(dashboard)/[portSlug]/notifications/preferences/page.tsx
@@ -1,17 +1,15 @@
-import { NotificationPreferencesForm } from '@/components/notifications/notification-preferences-form';
-import { ReminderDigestForm } from '@/components/notifications/reminder-digest-form';
+import { redirect } from 'next/navigation';
-export default function NotificationPreferencesPage() {
- return (
-
-
-
Notification Preferences
-
- Choose which notifications you receive and how.
-
-
+
setDisplayName(e.target.value)}
- placeholder="Your name"
+ placeholder="How your name appears in the UI"
/>
+
+ Defaults to first + last when blank. Override with a nickname if you prefer.
+
@@ -377,25 +368,16 @@ export function UserSettings() {
-
-
- Notifications
- Choose which notifications you receive
-
-
- {notifPrefs &&
- Object.entries(NOTIF_LABELS).map(([key, label]) => (
-
-
- toggleNotifPref(key, checked)}
- />
-
- ))}
-
-
+
+
+
Notifications
+
+ Choose which notifications you receive and how they're delivered.
+
+
+
+
+
crypto.randomUUID()),
userId: text('user_id').notNull().unique(), // references Better Auth user ID
+ /**
+ * Canonical first/last name pair. Added 2026-05-09 as the primary
+ * source for greetings, invoicing, and DocSign field-merging — the
+ * older `displayName` is now kept around as a derived/optional
+ * override (e.g. for nicknames or vanity formatting). When migrating
+ * production, backfill these columns from displayName by splitting
+ * on the first space and zero-pad the trailing column with NULL so
+ * single-token names don't fail the not-null assumption.
+ */
+ firstName: text('first_name'),
+ lastName: text('last_name'),
displayName: text('display_name').notNull(),
avatarUrl: text('avatar_url'),
/** FK into the polymorphic `files` table — the avatar is stored