Skip to content
Snippets Groups Projects
Commit 520af4e7 authored by Teal's avatar Teal
Browse files

Merge branch 'light-switch' into 'main'

Add dark mode toggle button

See merge request !38
parents d0bfe026 59ea2827
Branches
No related tags found
1 merge request!38Add dark mode toggle button
Pipeline #38430 passed
class UsersController < ApplicationController class UsersController < ApplicationController
before_action :authenticate_user!, only: [ :update_theme ]
def leaderboard def leaderboard
@workload_data = User.leaderboard @workload_data = User.leaderboard
end 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 end
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
</div> </div>
</div> </div>
<nav class="flex items-center"> <nav class="flex items-center space-x-3">
<% if user_signed_in? %> <% if user_signed_in? %>
<!-- Desktop user navigation --> <!-- Desktop user navigation -->
<div class="hidden md:flex md:items-center md:space-x-4"> <div class="hidden md:flex md:items-center md:space-x-4">
...@@ -67,6 +67,16 @@ ...@@ -67,6 +67,16 @@
<%= render partial: "application/user_avatar", locals: { user: current_user } %> <%= render partial: "application/user_avatar", locals: { user: current_user } %>
</div> </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 --> <!-- Mobile hamburger menu -->
<div class="md:hidden relative ml-4"> <div class="md:hidden relative ml-4">
<button id="main-menu-toggle" class="text-gray-900 dark:text-white"> <button id="main-menu-toggle" class="text-gray-900 dark:text-white">
...@@ -100,11 +110,20 @@ ...@@ -100,11 +110,20 @@
<% else %> <% else %>
<!-- Not logged in state --> <!-- Not logged in state -->
<div class="flex items-center space-x-4"> <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 "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" %> <%= 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> </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 --> <!-- Mobile hamburger menu for non-logged in users -->
<div class="md:hidden relative ml-4"> <div class="md:hidden relative ml-4">
<button id="guest-menu-toggle" class="text-gray-900 dark:text-white"> <button id="guest-menu-toggle" class="text-gray-900 dark:text-white">
...@@ -131,6 +150,56 @@ ...@@ -131,6 +150,56 @@
</header> </header>
<script> <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() { function initializeNavMenus() {
// Initialize main menu for logged in users // Initialize main menu for logged in users
const menuToggle = document.getElementById('main-menu-toggle'); const menuToggle = document.getElementById('main-menu-toggle');
...@@ -174,12 +243,24 @@ ...@@ -174,12 +243,24 @@
}); });
} }
// Set user signed in state for JS
window.userSignedIn = <%= user_signed_in? %>;
// Initialize on DOMContentLoaded // Initialize on DOMContentLoaded
document.addEventListener('DOMContentLoaded', initializeNavMenus); document.addEventListener('DOMContentLoaded', () => {
initializeThemeToggle();
initializeNavMenus();
});
// Re-initialize on Turbo navigation // Re-initialize on Turbo navigation
document.addEventListener('turbo:load', initializeNavMenus); document.addEventListener('turbo:load', () => {
document.addEventListener('turbo:render', initializeNavMenus); initializeThemeToggle();
initializeNavMenus();
});
document.addEventListener('turbo:render', () => {
initializeThemeToggle();
initializeNavMenus();
});
</script> </script>
<main> <main>
......
...@@ -24,7 +24,9 @@ Rails.application.routes.draw do ...@@ -24,7 +24,9 @@ Rails.application.routes.draw do
end end
get "speakers/show" get "speakers/show"
get "users/leaderboard" 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. # 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. # 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.
Please register or to comment