feat(profile): first/last name fields + collapse notification preferences
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) <noreply@anthropic.com>
This commit is contained in:
22
src/lib/db/migrations/0049_user_profiles_first_last_name.sql
Normal file
22
src/lib/db/migrations/0049_user_profiles_first_last_name.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
-- Add canonical first/last name pair to user_profiles. The older
|
||||
-- display_name column is kept as a derived/optional override (nicknames,
|
||||
-- vanity formatting). Backfills first_name + last_name 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.
|
||||
ALTER TABLE "user_profiles"
|
||||
ADD COLUMN IF NOT EXISTS "first_name" text,
|
||||
ADD COLUMN IF NOT EXISTS "last_name" text;
|
||||
|
||||
-- Backfill: split display_name on the first run of whitespace.
|
||||
-- - "Alice" → first_name='Alice', last_name=NULL
|
||||
-- - "Alice Smith" → first_name='Alice', last_name='Smith'
|
||||
-- - "Alice Mary Smith" → first_name='Alice', last_name='Mary Smith'
|
||||
-- Skip rows that already have a first_name set so re-runs are no-ops.
|
||||
UPDATE "user_profiles"
|
||||
SET
|
||||
"first_name" = COALESCE(NULLIF(SPLIT_PART("display_name", ' ', 1), ''), "display_name"),
|
||||
"last_name" = NULLIF(
|
||||
REGEXP_REPLACE("display_name", '^\S+\s+', ''),
|
||||
"display_name"
|
||||
)
|
||||
WHERE "first_name" IS NULL;
|
||||
@@ -302,6 +302,6 @@
|
||||
"when": 1778500000000,
|
||||
"tag": "0042_missing_fk_constraints",
|
||||
"breakpoints": true
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -224,6 +224,17 @@ export const userProfiles = pgTable(
|
||||
.primaryKey()
|
||||
.$defaultFn(() => 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
|
||||
|
||||
Reference in New Issue
Block a user