umami icon indicating copy to clipboard operation
umami copied to clipboard

feat: Persist user preferences in database

Open ceviixx opened this issue 2 months ago • 3 comments

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_preferences adds columns to user table
  • Existing users: preferences will be null until they change settings

ceviixx avatar Nov 12 '25 21:11 ceviixx

@ceviixx is attempting to deploy a commit to the umami-software Team on Vercel.

A member of the Team first needs to authorize it.

vercel[bot] avatar Nov 12 '25 21:11 vercel[bot]

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 usePreferences hook and potential inconsistencies in preference management lower the confidence
  • Pay close attention to the usePreferences hook for silent error handling and the removeClientPreferences function 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"

greptile-apps[bot] avatar Nov 12 '25 21:11 greptile-apps[bot]

@mikecao @franciscao633 is it possible to merge or should I create an new PR to dev?

ceviixx avatar Nov 14 '25 02:11 ceviixx