Skip to content
Snippets Groups Projects
Commit 59ea2827 authored by Teal Bauer's avatar Teal Bauer
Browse files

Add dark mode toggle button

parent d0bfe026
No related branches found
No related tags found
1 merge request!38Add dark mode toggle button
Pipeline #38429 passed
class UsersController < ApplicationController
before_action :authenticate_user!, only: [ :update_theme ]
def leaderboard
@workload_data = User.leaderboard
end
def update_theme
if current_user.present?
# Ensure darkmode parameter is valid (light, dark)
theme = params[:darkmode].to_s
if [ "light", "dark" ].include?(theme)
if current_user.update(darkmode: theme)
render json: { success: true, theme: theme }, status: :ok
else
render json: { success: false, errors: current_user.errors.full_messages }, status: :unprocessable_entity
end
else
render json: { success: false, error: "Invalid theme value" }, status: :bad_request
end
else
render json: { success: false, error: "User not authenticated" }, status: :unauthorized
end
end
end
......@@ -45,7 +45,7 @@
</div>
</div>
<nav class="flex items-center">
<nav class="flex items-center space-x-3">
<% if user_signed_in? %>
<!-- Desktop user navigation -->
<div class="hidden md:flex md:items-center md:space-x-4">
......@@ -66,6 +66,16 @@
<div class="flex items-center ml-4">
<%= render partial: "application/user_avatar", locals: { user: current_user } %>
</div>
<!-- Dark mode toggle button -->
<button id="theme-toggle" class="text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white" aria-label="Toggle dark mode">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 hidden dark:block" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 block dark:hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
</button>
<!-- Mobile hamburger menu -->
<div class="md:hidden relative ml-4">
......@@ -100,10 +110,19 @@
<% else %>
<!-- Not logged in state -->
<div class="flex items-center space-x-4">
<span class="px-2 text-gray-600 dark:text-gray-300 hidden md:inline">not logged in</span>
<%= link_to "Assignments", assignments_path, class: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white" %>
<%= link_to "Log in", new_user_session_path, class: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white" %>
</div>
<!-- Dark mode toggle button -->
<button id="theme-toggle" class="text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white block" aria-label="Toggle dark mode">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 hidden dark:block" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 block dark:hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
</button>
<!-- Mobile hamburger menu for non-logged in users -->
<div class="md:hidden relative ml-4">
......@@ -131,6 +150,56 @@
</header>
<script>
function initializeThemeToggle() {
const themeToggle = document.getElementById('theme-toggle');
if (!themeToggle) return;
// Remove any existing event listeners to prevent duplicates
const newThemeToggle = themeToggle.cloneNode(true);
themeToggle.parentNode.replaceChild(newThemeToggle, themeToggle);
newThemeToggle.addEventListener('click', function() {
const htmlElement = document.documentElement;
const isDark = htmlElement.classList.contains('dark');
const newMode = isDark ? 'light' : 'dark';
// Remove all theme classes
htmlElement.classList.remove('dark', 'light', 'auto');
// Add the new theme class
htmlElement.classList.add(newMode);
// For logged-in users, save preference via AJAX
if (window.userSignedIn) {
fetch('/users/update_theme', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ darkmode: newMode })
}).catch(error => console.error('Error updating theme preference:', error));
}
// For logged-out users, save preference in localStorage
else {
localStorage.setItem('theme', newMode);
}
});
// If logged out and we have a theme in localStorage, apply it
if (!window.userSignedIn && localStorage.getItem('theme')) {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark' || savedTheme === 'light') {
const htmlElement = document.documentElement;
// Only apply if we're in 'auto' mode (default for logged-out users)
if (htmlElement.classList.contains('auto')) {
htmlElement.classList.remove('auto', 'dark', 'light');
htmlElement.classList.add(savedTheme);
}
}
}
}
function initializeNavMenus() {
// Initialize main menu for logged in users
const menuToggle = document.getElementById('main-menu-toggle');
......@@ -174,12 +243,24 @@
});
}
// Set user signed in state for JS
window.userSignedIn = <%= user_signed_in? %>;
// Initialize on DOMContentLoaded
document.addEventListener('DOMContentLoaded', initializeNavMenus);
document.addEventListener('DOMContentLoaded', () => {
initializeThemeToggle();
initializeNavMenus();
});
// Re-initialize on Turbo navigation
document.addEventListener('turbo:load', initializeNavMenus);
document.addEventListener('turbo:render', initializeNavMenus);
document.addEventListener('turbo:load', () => {
initializeThemeToggle();
initializeNavMenus();
});
document.addEventListener('turbo:render', () => {
initializeThemeToggle();
initializeNavMenus();
});
</script>
<main>
......
......@@ -24,7 +24,9 @@ Rails.application.routes.draw do
end
get "speakers/show"
get "users/leaderboard"
patch "users/update_theme", to: "users#update_theme"
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment