From 439e95f4784358bf50425e3d04be8a44fe276801 Mon Sep 17 00:00:00 2001 From: DariusIII Date: Thu, 16 Oct 2025 12:52:38 +0200 Subject: [PATCH] Make dark mode per user --- PER_USER_DARK_MODE.md | 153 ++++++++++++++++++ app/Http/Controllers/ProfileController.php | 30 ++++ ...16_101145_add_dark_mode_to_users_table.php | 29 ++++ resources/views/layouts/admin.blade.php | 45 +++++- resources/views/layouts/main.blade.php | 45 +++++- resources/views/profile/edit.blade.php | 79 +++++++++ resources/views/profile/index.blade.php | 71 +++++--- routes/web.php | 1 + 8 files changed, 419 insertions(+), 34 deletions(-) create mode 100644 PER_USER_DARK_MODE.md create mode 100644 database/migrations/2025_10_16_101145_add_dark_mode_to_users_table.php diff --git a/PER_USER_DARK_MODE.md b/PER_USER_DARK_MODE.md new file mode 100644 index 000000000..65a445536 --- /dev/null +++ b/PER_USER_DARK_MODE.md @@ -0,0 +1,153 @@ +# Per-User Dark Mode Implementation + +## Overview +The dark mode/light mode switch is now **per-user** instead of global. Each authenticated user's theme preference is stored in the database and persists across different browsers and devices. + +## What Changed + +### 1. Database Migration +- **File**: `database/migrations/2025_10_16_101145_add_dark_mode_to_users_table.php` +- Added `dark_mode` boolean column to the `users` table +- Default value: `false` (light mode) +- Migration has been applied successfully + +### 2. User Model +- The `dark_mode` field is now part of the User model +- Users can have their own individual theme preferences + +### 3. ProfileController +- **File**: `app/Http/Controllers/ProfileController.php` +- Added `updateTheme()` method to handle theme preference updates +- Accepts POST requests with `dark_mode` boolean value +- Validates input and saves to database +- Returns JSON response with success status + +### 4. Routes +- **File**: `routes/web.php` +- Added new route: `POST /profile/update-theme` +- Route name: `profile.update-theme` +- Protected by `isVerified` middleware (authenticated users only) + +### 5. Main Layout (User Interface) +- **File**: `resources/views/layouts/main.blade.php` +- **Initialization**: Theme is loaded from user's database preference for authenticated users +- **Toggle Button**: Sends AJAX request to backend when clicked +- **Fallback**: Non-authenticated users still use localStorage + +### 6. Admin Layout +- **File**: `resources/views/layouts/admin.blade.php` +- Same behavior as main layout +- Theme preference is synchronized across both admin and user interfaces + +- Informative descriptions for each theme option + +**Method 2: Profile Edit Page (Permanent Setting)** +1. Navigate to Profile → Edit Profile +2. In the "Theme Preference" section, select either: + - **Light Mode**: Bright and clean interface (sun icon) + - **Dark Mode**: Easy on the eyes, especially at night (moon icon) +3. The theme changes instantly as you select (preview) +4. Click "Save Changes" to permanently save the preference +5. Theme preference applies across all devices and browsers + +**Synchronized Across:** +- Different browsers +- Different devices +- Admin and user interfaces +- All sessions +1. When page loads, the user's `dark_mode` preference from database is applied immediately +2. When user clicks the theme toggle button: + - The DOM is updated instantly (visual feedback) + - An AJAX POST request is sent to `/profile/update-theme` + - The preference is saved to the database +3. Theme preference follows the user across: + - Different browsers + - Different devices + - Admin and user interfaces + +### For Non-Authenticated Users (Guests) +- Theme preference is stored in browser's localStorage +- Works the same as before (browser-specific) + +## Benefits + +1. **Consistency**: User's theme preference is consistent across all devices +2. **Persistence**: Theme preference survives browser cache clearing +3. **User-Specific**: Each user can have their own preference +4. **Seamless**: No page reload required when toggling theme +5. **Backward Compatible**: Guest users still have dark mode functionality + +## API Endpoint + +### Update Theme Preference +``` +POST /profile/update-theme +Content-Type: application/json +X-CSRF-TOKEN: {token} + +Request Body: +{ + "dark_mode": true // or false +} + +Response (Success): +{ + "success": true, + "dark_mode": true +} + +Response (Error): +{ + "success": false, + "message": "Error message" +} +### Quick Toggle Method +``` + +## Database Schema + + +### Profile Edit Page Method +1. **Login as a user** +2. **Navigate to** Profile → Edit Profile +3. **Select a theme** in the "Theme Preference" section +4. **Observe instant preview** - theme changes immediately +5. **Click "Save Changes"** to persist the setting +6. **Refresh the page** - theme should remain +7. **Login on a different device** - same theme should apply + +### Verify in Database +```sql +SELECT username, dark_mode FROM users WHERE id = {your_user_id}; +``` + +## Testing + +To test the implementation: + +1. **Login as a user** +2. **Toggle dark mode** using the button in bottom-left corner +3. **Refresh the page** - theme should persist +4. **Login on a different browser** - same theme should apply +5. **Check database**: + ```sql + SELECT username, dark_mode FROM users WHERE id = {your_user_id}; + ``` + +## Rollback + +If you need to rollback this feature: + +```bash +php artisan migrate:rollback --step=1 +``` + +This will remove the `dark_mode` column from the users table. You'll also need to revert the layout files to use only localStorage. + +## Notes + +- The feature is fully implemented and ready to use +- Routes have been cached successfully +- No breaking changes to existing functionality +- Guest users retain their localStorage-based theme switching + diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 569a7f86e..49fc8508a 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -142,6 +142,11 @@ class ProfileController extends BasePageController 'None', ); + // Update dark mode preference + if ($request->has('dark_mode')) { + User::where('id', $userid)->update(['dark_mode' => (bool) $request->input('dark_mode')]); + } + // Handle Console permission if ($request->has('viewconsole')) { if (! $this->userdata->hasDirectPermission('view console')) { @@ -299,4 +304,29 @@ class ProfileController extends BasePageController return view('errors.503')->with('warning', 'Dont try to delete another user account!'); } + + /** + * Update user's dark mode preference + */ + public function updateTheme(Request $request) + { + $user = Auth::user(); + + if (!$user) { + return response()->json(['success' => false, 'message' => 'User not authenticated'], 401); + } + + $validator = Validator::make($request->all(), [ + 'dark_mode' => ['required', 'boolean'], + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'message' => 'Invalid input'], 400); + } + + $user->dark_mode = $request->input('dark_mode'); + $user->save(); + + return response()->json(['success' => true, 'dark_mode' => $user->dark_mode]); + } } diff --git a/database/migrations/2025_10_16_101145_add_dark_mode_to_users_table.php b/database/migrations/2025_10_16_101145_add_dark_mode_to_users_table.php new file mode 100644 index 000000000..6524b9065 --- /dev/null +++ b/database/migrations/2025_10_16_101145_add_dark_mode_to_users_table.php @@ -0,0 +1,29 @@ +boolean('dark_mode')->default(false)->after('notes'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('dark_mode'); + }); + } +}; + diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index 8fd549c3a..e68da5441 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -17,10 +17,19 @@ @@ -100,10 +109,34 @@ if (isDark) { html.classList.remove('dark'); - localStorage.setItem('theme', 'light'); + @auth + // Save to backend for authenticated users + fetch('{{ route('profile.update-theme') }}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + }, + body: JSON.stringify({ dark_mode: false }) + }); + @else + localStorage.setItem('theme', 'light'); + @endauth } else { html.classList.add('dark'); - localStorage.setItem('theme', 'dark'); + @auth + // Save to backend for authenticated users + fetch('{{ route('profile.update-theme') }}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + }, + body: JSON.stringify({ dark_mode: true }) + }); + @else + localStorage.setItem('theme', 'dark'); + @endauth } }); diff --git a/resources/views/layouts/main.blade.php b/resources/views/layouts/main.blade.php index 0580cabe5..c88e7676d 100644 --- a/resources/views/layouts/main.blade.php +++ b/resources/views/layouts/main.blade.php @@ -18,10 +18,19 @@ @@ -119,10 +128,34 @@ if (isDark) { html.classList.remove('dark'); - localStorage.setItem('theme', 'light'); + @auth + // Save to backend for authenticated users + fetch('{{ route('profile.update-theme') }}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + }, + body: JSON.stringify({ dark_mode: false }) + }); + @else + localStorage.setItem('theme', 'light'); + @endauth } else { html.classList.add('dark'); - localStorage.setItem('theme', 'dark'); + @auth + // Save to backend for authenticated users + fetch('{{ route('profile.update-theme') }}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + }, + body: JSON.stringify({ dark_mode: true }) + }); + @else + localStorage.setItem('theme', 'dark'); + @endauth } }); diff --git a/resources/views/profile/edit.blade.php b/resources/views/profile/edit.blade.php index a34d0e3e6..dca420b00 100644 --- a/resources/views/profile/edit.blade.php +++ b/resources/views/profile/edit.blade.php @@ -61,6 +61,44 @@ class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> + +
+

+ Theme Preference +

+
+ + +
+

+ Your theme preference will be applied across all devices and browsers when you're logged in. +

+
+

Cover View Preferences

@@ -276,3 +314,44 @@
@endsection +@push('scripts') + +@endpush + diff --git a/resources/views/profile/index.blade.php b/resources/views/profile/index.blade.php index 90ebad11f..3e055c562 100644 --- a/resources/views/profile/index.blade.php +++ b/resources/views/profile/index.blade.php @@ -140,30 +140,57 @@

UI Preferences

-
+ + +
+

+ Theme Mode +

- - Movie Covers + @if($user->dark_mode) +
+ + Dark Mode +
+ @else +
+ + Light Mode +
+ @endif
-
- - Music Covers -
-
- - Console Covers -
-
- - Game Covers -
-
- - Book Covers -
-
- - XXX Covers +
+ + +
+

+ Cover Display +

+
+
+ + Movie Covers +
+
+ + Music Covers +
+
+ + Console Covers +
+
+ + Game Covers +
+
+ + Book Covers +
+
+ + XXX Covers +
diff --git a/routes/web.php b/routes/web.php index 469736ae3..42edeaecc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -142,6 +142,7 @@ Route::middleware('isVerified')->group(function () { Route::post('contact-us', [ContactUsController::class, 'contact']); Route::match(['GET', 'POST'], 'profileedit', [ProfileController::class, 'edit'])->name('profileedit'); Route::match(['GET', 'POST'], 'profile_delete', [ProfileController::class, 'destroy'])->name('profile_delete'); + Route::post('profile/update-theme', [ProfileController::class, 'updateTheme'])->name('profile.update-theme'); Route::match(['GET', 'POST'], 'search', [SearchController::class, 'search'])->name('search'); Route::match(['GET', 'POST'], 'mymovies', [MyMoviesController::class, 'show'])->name('mymovies'); Route::match(['GET', 'POST'], 'myshows', [MyShowsController::class, 'show'])->name('myshows');