diff --git a/app/assets/stylesheets/global.scss b/app/assets/stylesheets/global.scss index df7f93d..8f2fa35 100644 --- a/app/assets/stylesheets/global.scss +++ b/app/assets/stylesheets/global.scss @@ -95,8 +95,8 @@ button, input[type=submit] { border-radius: $border-radius; padding: 8px 16px; cursor: pointer; - - @media (prefers-color-scheme: dark) { + + [data-theme="dark"] & { color: $white; border: 1px solid $light; @@ -104,7 +104,7 @@ button, input[type=submit] { border-color: $white; } } - + } input[type=text] { @@ -114,7 +114,7 @@ input[type=text] { border-bottom: 1px solid $dark; outline: none; - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { color: $light; border-bottom: 1px solid $light; } @@ -130,7 +130,7 @@ textarea { padding: 0 8px; resize: none; - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { color: $light; } } @@ -139,14 +139,10 @@ textarea { display: none; } -@media (prefers-color-scheme: dark) { - //transition: all 1s; - - body { - background: $dark; - color: $light; - transition: ease all 1s; - } +body[data-theme="dark"] { + background: $dark; + color: $light; + transition: ease all 1s; a { color: $light; diff --git a/app/assets/stylesheets/home.scss b/app/assets/stylesheets/home.scss index f9512fb..f8f1075 100644 --- a/app/assets/stylesheets/home.scss +++ b/app/assets/stylesheets/home.scss @@ -20,7 +20,7 @@ border: 1px solid $dark; padding: 10px; - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { border: 1px solid $light; } @@ -60,7 +60,7 @@ } } - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { .primary-link, .secondary-link { border-color: lightgray; @@ -79,5 +79,28 @@ margin-top: 20px; opacity: 0.4; } + + .theme-toggle { + border: none !important; + background: none; + padding: 4px 8px; + cursor: pointer; + color: gray; + font-size: 14px; + line-height: 30px; + + &:hover { + color: black; + background: lightgray; + border-radius: $border-radius; + } + + [data-theme="dark"] & { + &:hover { + color: white; + background: #202020; + } + } + } } diff --git a/app/assets/stylesheets/mixins.scss b/app/assets/stylesheets/mixins.scss index 22b679b..656e5c6 100644 --- a/app/assets/stylesheets/mixins.scss +++ b/app/assets/stylesheets/mixins.scss @@ -34,26 +34,22 @@ } @mixin logo-darkmode { - @media (prefers-color-scheme: dark) { - .logo { - &.light { - display: none; - } - &.dark { - display: block; - } + .logo { + &.light { + display: block; + } + &.dark { + display: none; } - - transition: ease all 1s; } - @media (prefers-color-scheme: light) { + [data-theme="dark"] & { .logo { &.light { - display: block; + display: none; } &.dark { - display: none; + display: block; } } @@ -84,7 +80,7 @@ } } - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { textarea { background: #202020; color: lightgray; diff --git a/app/assets/stylesheets/sessions/index.scss b/app/assets/stylesheets/sessions/index.scss index 9f9facf..3dff913 100644 --- a/app/assets/stylesheets/sessions/index.scss +++ b/app/assets/stylesheets/sessions/index.scss @@ -29,7 +29,7 @@ } } - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { .primary-link, .secondary-link { border-color: lightgray; diff --git a/app/assets/stylesheets/sessions/sessions.scss b/app/assets/stylesheets/sessions/sessions.scss index eedc8bc..ba72c9b 100644 --- a/app/assets/stylesheets/sessions/sessions.scss +++ b/app/assets/stylesheets/sessions/sessions.scss @@ -44,10 +44,9 @@ opacity: 1; } - @media (prefers-color-scheme: light) { - color: $dark; - } - @media (prefers-color-scheme: dark) { + color: $dark; + + [data-theme="dark"] & { color: $light; } @@ -127,7 +126,7 @@ border-radius: $border-radius; max-height: 100px; - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { border: 1px solid $border-color-dark; } @@ -162,7 +161,7 @@ justify-content: flex-start; border-left: 1px solid $border-color-dark; - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { border-left: 1px solid $border-color-dark; } @@ -181,9 +180,8 @@ background: black; color: white; - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { background: rgba(255, 255, 255, 0.4); - // color: black; } } } diff --git a/app/assets/stylesheets/user/index.scss b/app/assets/stylesheets/user/index.scss index 53d991f..bf529d5 100644 --- a/app/assets/stylesheets/user/index.scss +++ b/app/assets/stylesheets/user/index.scss @@ -39,18 +39,36 @@ opacity: 1; } - @media (prefers-color-scheme: light) { - border: 1px solid black; - color: black; - } + border: 1px solid black; + color: black; - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { border: 1px solid white; color: white; } } } + .theme-options { + display: flex; + gap: 16px; + margin-top: 5px; + + label { + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + } + + input[type="radio"] { + width: auto; + margin: 0; + padding: 0; + border: none; + } + } + .actions { input[type="submit"] { width: 100%; @@ -65,13 +83,11 @@ opacity: 1; } - @media (prefers-color-scheme: light) { - border: 1px solid black; - color: white; - background: black; - } + border: 1px solid black; + color: white; + background: black; - @media (prefers-color-scheme: dark) { + [data-theme="dark"] & { border: 1px solid white; color: black; background: white; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 857932b..49ab438 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,8 +1,13 @@ class ApplicationController < ActionController::Base before_action :authenticate_user! + before_action :configure_permitted_parameters, if: :devise_controller? protected + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:account_update, keys: [:theme]) + end + rescue_from CanCan::AccessDenied do |_exception| redirect_to root_path, flash: { error: 'You are not authorized to perform this action.' } end diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb new file mode 100644 index 0000000..0a7ce20 --- /dev/null +++ b/app/controllers/users/registrations_controller.rb @@ -0,0 +1,7 @@ +class Users::RegistrationsController < Devise::RegistrationsController + protected + + def after_update_path_for(resource) + edit_user_registration_path + end +end diff --git a/app/controllers/users/themes_controller.rb b/app/controllers/users/themes_controller.rb new file mode 100644 index 0000000..5f61c44 --- /dev/null +++ b/app/controllers/users/themes_controller.rb @@ -0,0 +1,9 @@ +class Users::ThemesController < ApplicationController + def update + new_theme = params[:theme] + if %w[light dark system].include?(new_theme) + current_user.update!(theme: new_theme) + end + head :ok + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be79..6fd0189 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,5 @@ module ApplicationHelper + def theme_preference + current_user&.theme || "system" + end end diff --git a/app/javascript/application.js b/app/javascript/application.js index 7ca6576..12fff96 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -3,4 +3,7 @@ import '@hotwired/turbo-rails' import 'jquery' import 'sortablejs' -import 'controllers' \ No newline at end of file +import toastr from 'toastr' +import 'controllers' + +window.toastr = toastr \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 260c72e..cc5dda8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,6 +7,8 @@ class User < ApplicationRecord has_many :writing_sessions, dependent: :destroy has_many :stories, dependent: :destroy + validates :theme, inclusion: { in: %w[light dark system] } + after_create :subscribe_to_mailing def password_required? diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index a150a3e..c66f04c 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -2,6 +2,11 @@

+ <%= link_to 'Account', edit_user_registration_path, class: 'header-action', data: {'turbo-action': :replace} %> <%= link_to 'Logout', destroy_user_session_path, method: 'DELETE', class: 'header-action', data: {'turbo-action': :restore, "turbo-method": :delete} %>
@@ -28,3 +33,43 @@
+ + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index c0187bb..a2de7b7 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -13,7 +13,8 @@ <%= javascript_importmap_tags %> - + + <%= render "shared/theme" %> <% unless flash.empty? %> - + + <%= render "shared/theme" %> <% unless flash.empty? %> - + + <%= render "shared/theme" %> <% unless flash.empty? %> diff --git a/config/routes.rb b/config/routes.rb index 43ca22a..8cf795b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,11 @@ Rails.application.routes.draw do mount Blazer::Engine, at: 'blazer' - devise_for :user + devise_for :user, controllers: { registrations: 'users/registrations' } + + namespace :users do + resource :theme, only: [:update] + end resources :stories do resources :writing_sessions diff --git a/db/migrate/20260223160211_add_theme_to_users.rb b/db/migrate/20260223160211_add_theme_to_users.rb new file mode 100644 index 0000000..1e7f7f1 --- /dev/null +++ b/db/migrate/20260223160211_add_theme_to_users.rb @@ -0,0 +1,5 @@ +class AddThemeToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :theme, :string, default: "system", null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 4a0d84e..cc98218 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_06_12_164159) do +ActiveRecord::Schema[8.0].define(version: 2026_02_23_160211) do create_table "blazer_audits", force: :cascade do |t| t.integer "user_id" t.integer "query_id" @@ -103,6 +103,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "admin_role" + t.string "theme", default: "system", null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end