diff --git a/app/Http/Controllers/Settings/AppearanceController.php b/app/Http/Controllers/Settings/AppearanceController.php index f8caff7..cb06074 100644 --- a/app/Http/Controllers/Settings/AppearanceController.php +++ b/app/Http/Controllers/Settings/AppearanceController.php @@ -3,6 +3,9 @@ namespace App\Http\Controllers\Settings; use App\Http\Controllers\Controller; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; +use Illuminate\Validation\Rule; use Illuminate\View\View; class AppearanceController extends Controller @@ -11,4 +14,15 @@ public function edit(): View { return view('settings.appearance'); } + + public function update(Request $request): RedirectResponse + { + $validated = $request->validate([ + 'theme_preference' => ['required', Rule::in(['light', 'dark', 'system'])], + ]); + + $request->user()->update($validated); + + return back(); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 2d9bad8..f8e4108 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -22,6 +22,7 @@ class User extends Authenticatable 'name', 'email', 'password', + 'theme_preference', ]; /** diff --git a/database/migrations/2026_02_09_000925_add_theme_preference_to_users_table.php b/database/migrations/2026_02_09_000925_add_theme_preference_to_users_table.php new file mode 100644 index 0000000..969e69d --- /dev/null +++ b/database/migrations/2026_02_09_000925_add_theme_preference_to_users_table.php @@ -0,0 +1,28 @@ +string('theme_preference')->nullable()->default('system')->after('password'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('theme_preference'); + }); + } +}; diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php index 399671b..f9a1824 100644 --- a/resources/views/components/layouts/app.blade.php +++ b/resources/views/components/layouts/app.blade.php @@ -31,7 +31,11 @@ document.addEventListener("DOMContentLoaded", () => setButtons(appearance)) } } - window.setAppearance(window.localStorage.getItem('appearance') || 'system') + window.setAppearance( + "{{ auth()->user()->theme_preference ?? '' }}" || + window.localStorage.getItem('appearance') || + 'system' + ) @vite(['resources/css/app.css', 'resources/js/app.js']) diff --git a/resources/views/components/layouts/app/header.blade.php b/resources/views/components/layouts/app/header.blade.php index 8249937..cf82233 100644 --- a/resources/views/components/layouts/app/header.blade.php +++ b/resources/views/components/layouts/app/header.blade.php @@ -13,8 +13,81 @@ class="p-2 rounded-md text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:
{{ config('app.name') }}
- +
+ +
+ + +
+ + + + + +
+ + +
- - + +
+ @csrf + @method('PUT') + + +
+ +
+ + + +
-
+ + +
diff --git a/routes/web.php b/routes/web.php index f241b8c..1b793af 100644 --- a/routes/web.php +++ b/routes/web.php @@ -18,6 +18,7 @@ Route::get('settings/password', [Settings\PasswordController::class, 'edit'])->name('settings.password.edit'); Route::put('settings/password', [Settings\PasswordController::class, 'update'])->name('settings.password.update'); Route::get('settings/appearance', [Settings\AppearanceController::class, 'edit'])->name('settings.appearance.edit'); + Route::put('settings/appearance', [Settings\AppearanceController::class, 'update'])->name('settings.appearance.update'); }); require __DIR__.'/auth.php'; diff --git a/tests/Feature/Settings/AppearanceUpdateTest.php b/tests/Feature/Settings/AppearanceUpdateTest.php new file mode 100644 index 0000000..17965e7 --- /dev/null +++ b/tests/Feature/Settings/AppearanceUpdateTest.php @@ -0,0 +1,90 @@ +create(); + + $response = $this->actingAs($user)->get('/settings/appearance'); + + $response->assertStatus(200); + } + + public function test_users_can_update_theme_to_light(): void + { + $user = User::factory()->create([ + 'theme_preference' => 'system', + ]); + + $response = $this->actingAs($user) + ->from('/settings/appearance') + ->put('/settings/appearance', [ + 'theme_preference' => 'light', + ]); + + $response->assertSessionHasNoErrors(); + $response->assertRedirect('/settings/appearance'); + + $this->assertEquals('light', $user->refresh()->theme_preference); + } + + public function test_users_can_update_theme_to_dark(): void + { + $user = User::factory()->create([ + 'theme_preference' => 'light', + ]); + + $response = $this->actingAs($user)->put('/settings/appearance', [ + 'theme_preference' => 'dark', + ]); + + $response->assertSessionHasNoErrors(); + $this->assertEquals('dark', $user->refresh()->theme_preference); + } + + public function test_users_can_update_theme_to_system(): void + { + $user = User::factory()->create([ + 'theme_preference' => 'dark', + ]); + + $response = $this->actingAs($user)->put('/settings/appearance', [ + 'theme_preference' => 'system', + ]); + + $response->assertSessionHasNoErrors(); + $this->assertEquals('system', $user->refresh()->theme_preference); + } + + public function test_theme_update_requires_valid_value(): void + { + $user = User::factory()->create([ + 'theme_preference' => 'system', + ]); + + $response = $this->actingAs($user)->put('/settings/appearance', [ + 'theme_preference' => 'invalid-theme', + ]); + + $response->assertSessionHasErrors('theme_preference'); + $this->assertEquals('system', $user->refresh()->theme_preference); + } + + public function test_guests_cannot_update_theme_preference(): void + { + $response = $this->put('/settings/appearance', [ + 'theme_preference' => 'dark', + ]); + + $response->assertRedirect('/login'); + } +}