feat: Persist user preferences in database
Changes
- Add database columns for user preferences (language, timezone, theme, dateRange)
- Store preferences in database when user changes settings
- Load and sync preferences to localStorage on login/refresh
- Remove preferences from localStorage on logout
Benefits
- User settings persist across devices and browsers
- Consistent experience after clearing browser data
- Settings are backed up and recoverable
Migration
- New migration
15_add_user_preferencesadds columns to user table - Existing users: preferences will be null until they change settings
@ceviixx is attempting to deploy a commit to the umami-software Team on Vercel.
A member of the Team first needs to authorize it.
Greptile Overview
Greptile Summary
This PR implements persistent user preferences by migrating them from localStorage-only storage to database-backed persistence. The changes add four new nullable columns (dateRange, timezone, language, theme) to the user table and create the infrastructure to sync these preferences between the database and client-side storage.
The implementation introduces a new usePreferences hook that provides a unified interface for updating preferences, and modifies all settings components (DateRangeSetting, LanguageSetting, ThemeSetting, TimezoneSetting) to call both local storage functions and the new database persistence layer. Authentication flows (login and token verification) are enhanced to load and apply stored preferences, ensuring users get consistent settings across devices and browsers. The logout process is updated to clear preferences from localStorage, maintaining proper state hygiene.
The solution maintains backward compatibility through nullable database columns and graceful fallbacks, allowing existing users to continue using the application while new preference updates get persisted to the database.
Important Files Changed
| Filename | Score | Overview |
|---|---|---|
prisma/migrations/15_add_user_preferences/migration.sql |
4/5 | Adds four nullable columns for user preferences to the user table |
src/queries/prisma/user.ts |
5/5 | Adds database functions for getting and updating user preferences |
src/components/hooks/usePreferences.ts |
4/5 | New hook providing interface for updating user preferences via API |
src/app/api/users/[userId]/preferences/route.ts |
4/5 | New REST API endpoint for managing user preferences with validation |
src/lib/client.ts |
4/5 | Adds client-side preference sync functions for localStorage and state management |
src/app/login/LoginForm.tsx |
5/5 | Integrates preference loading and theme application on successful login |
src/app/api/auth/login/route.ts |
4/5 | Includes user preferences in login response for immediate sync |
src/app/api/auth/verify/route.ts |
4/5 | Adds preferences to auth verification response for token refresh scenarios |
src/components/hooks/queries/useLoginQuery.ts |
4/5 | Handles preference synchronization and theme application during login flow |
src/app/(main)/settings/preferences/ThemeSetting.tsx |
4/5 | Enhanced with database persistence and reset functionality for theme preferences |
src/app/(main)/settings/preferences/DateRangeSetting.tsx |
5/5 | Integrates database persistence while maintaining localStorage compatibility |
src/app/(main)/settings/preferences/TimezoneSetting.tsx |
4/5 | Updated to sync timezone changes to database alongside localStorage |
src/app/(main)/settings/preferences/LanguageSetting.tsx |
5/5 | Adds database persistence for language settings with proper reset handling |
src/app/logout/LogoutPage.tsx |
5/5 | Ensures preferences are cleared from localStorage during logout |
src/lib/constants.ts |
3/5 | Changes theme configuration key from 'umami.theme' to 'zen.theme' |
Confidence score: 4/5
- This PR introduces comprehensive preference persistence with good error handling and backward compatibility, though some edge cases need attention
- Score reflects solid architecture and implementation patterns, but concerns about silent error handling in the
usePreferenceshook and potential inconsistencies in preference management lower the confidence - Pay close attention to the
usePreferenceshook for silent error handling and theremoveClientPreferencesfunction which doesn't clear LOCALE_CONFIG consistently
Sequence Diagram
sequenceDiagram
participant User
participant LoginForm as "Login Form"
participant AuthAPI as "Auth API"
participant UserDB as "User Database"
participant ClientStorage as "Client Storage"
participant AppStore as "App Store"
User->>LoginForm: "Enter credentials and submit"
LoginForm->>AuthAPI: "POST /auth/login with username/password"
AuthAPI->>UserDB: "getUserByUsername() and checkPassword()"
UserDB-->>AuthAPI: "User data with preferences"
AuthAPI->>UserDB: "getUserPreferences(userId)"
UserDB-->>AuthAPI: "User preferences (theme, language, timezone, dateRange)"
AuthAPI-->>LoginForm: "Return token and user data with preferences"
LoginForm->>ClientStorage: "setClientAuthToken(token)"
LoginForm->>ClientStorage: "setClientPreferences(preferences)"
LoginForm->>AppStore: "setTheme(preferences.theme)"
LoginForm->>AppStore: "setUser(user)"
LoginForm->>User: "Redirect to /websites"
Note over User,AppStore: User changes preferences in settings
User->>LanguageSetting: "Select new language"
LanguageSetting->>ClientStorage: "saveLocale(language)"
LanguageSetting->>AuthAPI: "POST /users/{userId}/preferences with language"
AuthAPI->>UserDB: "updateUserPreferences(userId, {language})"
UserDB-->>AuthAPI: "Updated preferences"
User->>ThemeSetting: "Select new theme"
ThemeSetting->>AppStore: "setTheme(theme)"
ThemeSetting->>AuthAPI: "POST /users/{userId}/preferences with theme"
AuthAPI->>UserDB: "updateUserPreferences(userId, {theme})"
UserDB-->>AuthAPI: "Updated preferences"
User->>TimezoneSetting: "Select new timezone"
TimezoneSetting->>ClientStorage: "saveTimezone(timezone)"
TimezoneSetting->>AuthAPI: "POST /users/{userId}/preferences with timezone"
AuthAPI->>UserDB: "updateUserPreferences(userId, {timezone})"
UserDB-->>AuthAPI: "Updated preferences"
User->>DateRangeSetting: "Select new date range"
DateRangeSetting->>ClientStorage: "setItem(DATE_RANGE_CONFIG, dateRange)"
DateRangeSetting->>AuthAPI: "POST /users/{userId}/preferences with dateRange"
AuthAPI->>UserDB: "updateUserPreferences(userId, {dateRange})"
UserDB-->>AuthAPI: "Updated preferences"
Note over User,AppStore: User logs out
User->>LogoutPage: "Navigate to /logout"
LogoutPage->>ClientStorage: "removeClientAuthToken()"
LogoutPage->>ClientStorage: "removeClientPreferences()"
LogoutPage->>AppStore: "setUser(null)"
LogoutPage->>AuthAPI: "POST /auth/logout"
LogoutPage->>User: "Redirect to /login"
Note over User,AppStore: User logs in on different device
User->>LoginForm: "Enter credentials and submit"
LoginForm->>AuthAPI: "POST /auth/login with username/password"
AuthAPI->>UserDB: "getUserByUsername() and getUserPreferences()"
UserDB-->>AuthAPI: "User data with saved preferences from database"
AuthAPI-->>LoginForm: "Return token and user data with preferences"
LoginForm->>ClientStorage: "setClientPreferences(preferences) - sync from database"
LoginForm->>AppStore: "setTheme(preferences.theme) - restore user's theme"
LoginForm->>User: "User sees consistent settings across devices"
@mikecao @franciscao633 is it possible to merge or should I create an new PR to dev?