Private
Public Access
Support multiple named, saved custom themes per user
Replaces the single custom_theme_colors blob (one palette per user) with a proper CustomTheme table -- users can now save, name, and switch between as many custom palettes as they like, not just one. Data model: users.active_custom_theme_id references whichever saved CustomTheme (if any) is currently active; theme='custom' + that id together determine what's rendered. The migration data-migrates any already-saved single palette into a named CustomTheme row on upgrade, and best-effort backfills the active one back into the old column shape on downgrade. New endpoints under /api/custom-themes: list, create, rename/recolor, delete (falls back the user to a preset if the deleted theme was active, so the two theme columns can never disagree), and activate. UserRead.active_custom_theme is only populated when theme == 'custom' even though the DB deliberately keeps the id set while a preset is active, so switching to a preset and back doesn't lose the saved palette. ProfileModal now lists saved themes as swatches (click to activate, pencil to edit -- active or not, trash to delete with a confirm), plus a "+ New" button that creates, activates, and opens the editor for a fresh theme immediately. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { apiFetch, ApiError, NetworkError } from './client'
|
||||
import type { CustomThemeColors, ThemeName, User } from '../types'
|
||||
import type { User } from '../types'
|
||||
|
||||
// No register() here: this is an invite-only site. Accounts are created by
|
||||
// an operator via the backend CLI (`python -m app.cli create-user`), not
|
||||
@@ -27,14 +27,16 @@ export function updateProfile(displayName: string | null): Promise<User> {
|
||||
})
|
||||
}
|
||||
|
||||
// Deliberately its own call sending only `theme` (and, for 'custom',
|
||||
// `custom_theme_colors` alongside it) -- the backend only applies fields
|
||||
// actually present in the request body, so this can't clobber display_name
|
||||
// (and updateProfile above can't clobber theme).
|
||||
export function updateTheme(theme: ThemeName, customThemeColors?: CustomThemeColors): Promise<User> {
|
||||
// Deliberately its own call sending only `theme` -- the backend only
|
||||
// applies fields actually present in the request body, so this can't
|
||||
// clobber display_name (and updateProfile above can't clobber theme).
|
||||
// Presets only ('dark'/'light'/'midnight'/'sunset') -- activating a custom
|
||||
// theme is POST /api/custom-themes/{id}/activate (see api/customThemes.ts),
|
||||
// since that needs an id and an ownership check, not just a bare name.
|
||||
export function updateTheme(theme: 'dark' | 'light' | 'midnight' | 'sunset'): Promise<User> {
|
||||
return apiFetch<User>('/api/auth/me', {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ theme, custom_theme_colors: customThemeColors }),
|
||||
body: JSON.stringify({ theme }),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user