mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-10 07:55:21 +02:00
User theme settings
This commit is contained in:
parent
52d170e36c
commit
f1947a2dbe
7 changed files with 105 additions and 8 deletions
|
@ -5,6 +5,8 @@
|
|||
One-off styling (3rd party overrides, etc.) should be done in the application.css file.
|
||||
*/
|
||||
|
||||
@custom-variant theme-dark (&:where([data-theme=dark], [data-theme=dark] *));
|
||||
|
||||
@theme {
|
||||
/* Font families */
|
||||
--font-sans: 'Geist', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
|
@ -241,18 +243,34 @@
|
|||
/* Design system color utilities */
|
||||
@utility text-primary {
|
||||
@apply text-gray-900;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-white;
|
||||
}
|
||||
}
|
||||
|
||||
@utility text-secondary {
|
||||
@apply text-gray-500;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
@utility text-subdued {
|
||||
@apply text-gray-400;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-600;
|
||||
}
|
||||
}
|
||||
|
||||
@utility text-link {
|
||||
@apply text-blue-600;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-blue-500;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-surface {
|
||||
|
|
|
@ -74,7 +74,7 @@ class UsersController < ApplicationController
|
|||
|
||||
def user_params
|
||||
params.require(:user).permit(
|
||||
:first_name, :last_name, :email, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at, :show_sidebar, :default_period, :show_ai_sidebar, :ai_enabled,
|
||||
:first_name, :last_name, :email, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at, :show_sidebar, :default_period, :show_ai_sidebar, :ai_enabled, :theme,
|
||||
family_attributes: [ :name, :currency, :country, :locale, :date_format, :timezone, :id, :data_enrichment_enabled ]
|
||||
)
|
||||
end
|
||||
|
|
73
app/javascript/controllers/theme_controller.js
Normal file
73
app/javascript/controllers/theme_controller.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { userPreference: String }
|
||||
|
||||
connect() {
|
||||
this.applyTheme()
|
||||
this.startSystemThemeListener()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.stopSystemThemeListener()
|
||||
}
|
||||
|
||||
// Called automatically by Stimulus when the userPreferenceValue changes (e.g., after form submit/page reload)
|
||||
userPreferenceValueChanged() {
|
||||
this.applyTheme()
|
||||
}
|
||||
|
||||
// Called when a theme radio button is clicked
|
||||
updateTheme(event) {
|
||||
const selectedTheme = event.currentTarget.value
|
||||
if (selectedTheme === "system") {
|
||||
this.setTheme(this.systemPrefersDark())
|
||||
} else if (selectedTheme === "dark") {
|
||||
this.setTheme(true)
|
||||
} else {
|
||||
this.setTheme(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Applies theme based on the userPreferenceValue (from server)
|
||||
applyTheme() {
|
||||
if (this.userPreferenceValue === "system") {
|
||||
this.setTheme(this.systemPrefersDark())
|
||||
} else if (this.userPreferenceValue === "dark") {
|
||||
this.setTheme(true)
|
||||
} else {
|
||||
this.setTheme(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Sets or removes the data-theme attribute
|
||||
setTheme(isDark) {
|
||||
if (isDark) {
|
||||
document.documentElement.setAttribute("data-theme", "dark")
|
||||
} else {
|
||||
document.documentElement.removeAttribute("data-theme")
|
||||
}
|
||||
}
|
||||
|
||||
systemPrefersDark() {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
}
|
||||
|
||||
handleSystemThemeChange = (event) => {
|
||||
// Only apply system theme changes if the user preference is currently 'system'
|
||||
if (this.userPreferenceValue === "system") {
|
||||
this.setTheme(event.matches)
|
||||
}
|
||||
}
|
||||
|
||||
startSystemThemeListener() {
|
||||
this.darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
this.darkMediaQuery.addEventListener("change", this.handleSystemThemeChange)
|
||||
}
|
||||
|
||||
stopSystemThemeListener() {
|
||||
if (this.darkMediaQuery) {
|
||||
this.darkMediaQuery.removeEventListener("change", this.handleSystemThemeChange)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="h-full text-primary overflow-hidden lg:overflow-auto font-sans <%= @os %>" lang="en">
|
||||
<html class="h-full text-primary overflow-hidden lg:overflow-auto font-sans <%= @os %>" lang="en" data-controller="theme" data-theme-user-preference-value="<%= Current.user&.theme || 'system' %>">
|
||||
<head>
|
||||
<%= render "layouts/shared/head" %>
|
||||
<%= yield :head %>
|
||||
|
|
|
@ -47,26 +47,26 @@
|
|||
|
||||
<%= settings_section title: t(".theme_title"), subtitle: t(".theme_subtitle") do %>
|
||||
<div>
|
||||
<%= styled_form_with model: @user, class: "flex justify-between items-center" do |form| %>
|
||||
<%= styled_form_with model: @user, class: "flex justify-between items-center", data: { controller: "auto-submit-form" } do |form| %>
|
||||
<%= form.hidden_field :redirect_to, value: "preferences" %>
|
||||
<div class="text-center">
|
||||
<%= image_tag("light-mode-preview.png", alt: "Light Theme Preview", class: "h-44 mb-4") %>
|
||||
<div class="flex justify-center items-center gap-2">
|
||||
<%= form.radio_button :theme, t(".theme_light"), checked: true %>
|
||||
<%= form.radio_button :theme, "light", checked: @user.theme == "light", data: { auto_submit_form_target: "auto", action: "theme#updateTheme" } %>
|
||||
<%= form.label :theme_light, t(".theme_light"), value: "light" %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<%= image_tag("dark-mode-preview.png", alt: "Dark Theme Preview", class: "h-44 mb-4") %>
|
||||
<div class="flex justify-center items-center gap-2">
|
||||
<%= form.radio_button :theme, t(".theme_dark"), disabled: true, class: "cursor-not-allowed" %>
|
||||
<%= form.radio_button :theme, "dark", checked: @user.theme == "dark", data: { auto_submit_form_target: "auto", action: "theme#updateTheme" } %>
|
||||
<%= form.label :theme_dark, t(".theme_dark"), value: "dark" %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<%= image_tag("system-mode-preview.png", alt: "System Theme Preview", class: "h-44 mb-4") %>
|
||||
<div class="flex items-center gap-2 justify-center">
|
||||
<%= form.radio_button :theme, t(".theme_system"), disabled: true, class: "cursor-not-allowed" %>
|
||||
<%= form.radio_button :theme, "system", checked: @user.theme == "system", data: { auto_submit_form_target: "auto", action: "theme#updateTheme" } %>
|
||||
<%= form.label :theme_system, t(".theme_system"), value: "system" %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
5
db/migrate/20250410144939_add_theme_to_users.rb
Normal file
5
db/migrate/20250410144939_add_theme_to_users.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AddThemeToUsers < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :users, :theme, :string, default: "system"
|
||||
end
|
||||
end
|
5
db/schema.rb
generated
5
db/schema.rb
generated
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_03_19_212839) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_04_10_144939) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
|
@ -101,7 +101,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_03_19_212839) do
|
|||
t.decimal "balance", precision: 19, scale: 4
|
||||
t.string "currency"
|
||||
t.boolean "is_active", default: true, null: false
|
||||
t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY ((ARRAY['Loan'::character varying, 'CreditCard'::character varying, 'OtherLiability'::character varying])::text[])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true
|
||||
t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY (ARRAY[('Loan'::character varying)::text, ('CreditCard'::character varying)::text, ('OtherLiability'::character varying)::text])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true
|
||||
t.uuid "import_id"
|
||||
t.uuid "plaid_account_id"
|
||||
t.boolean "scheduled_for_deletion", default: false
|
||||
|
@ -614,6 +614,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_03_19_212839) do
|
|||
t.uuid "last_viewed_chat_id"
|
||||
t.boolean "show_ai_sidebar", default: true
|
||||
t.boolean "ai_enabled", default: false, null: false
|
||||
t.string "theme", default: "system"
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["family_id"], name: "index_users_on_family_id"
|
||||
t.index ["last_viewed_chat_id"], name: "index_users_on_last_viewed_chat_id"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue