From b81bfc7f32a461ce606928b2ed6f2a5430134c23 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 29 May 2026 13:59:01 +0200 Subject: [PATCH] feat(profile): add appearance preferences page Add a profile appearance section for theme, page width, and zoom preferences. Move changelog access into the sidebar and bump the Coolify version to 4.1.2. --- app/Livewire/Profile/Appearance.php | 13 ++ app/Livewire/SettingsDropdown.php | 2 + config/constants.php | 2 +- docker-compose.dev.yml | 2 +- other/nightly/versions.json | 2 +- resources/views/components/navbar.blade.php | 86 +++++++- .../views/components/profile/navbar.blade.php | 17 ++ .../livewire/profile/appearance.blade.php | 119 +++++++++++ .../views/livewire/profile/index.blade.php | 3 +- .../livewire/settings-dropdown.blade.php | 191 +++++++----------- routes/web.php | 2 + .../ProfileAppearanceNavigationTest.php | 43 ++++ .../SidebarNavigationPreferencesTest.php | 63 ++++++ versions.json | 2 +- 14 files changed, 417 insertions(+), 130 deletions(-) create mode 100644 app/Livewire/Profile/Appearance.php create mode 100644 resources/views/components/profile/navbar.blade.php create mode 100644 resources/views/livewire/profile/appearance.blade.php create mode 100644 tests/Feature/ProfileAppearanceNavigationTest.php create mode 100644 tests/Feature/SidebarNavigationPreferencesTest.php diff --git a/app/Livewire/Profile/Appearance.php b/app/Livewire/Profile/Appearance.php new file mode 100644 index 000000000..6a1b72f80 --- /dev/null +++ b/app/Livewire/Profile/Appearance.php @@ -0,0 +1,13 @@ +getUnreadChangelogCount(); diff --git a/config/constants.php b/config/constants.php index e5dcee3fe..ac3c89feb 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.1.1', + 'version' => '4.1.2', 'helper_version' => '1.0.14', 'realtime_version' => '1.0.15', 'railpack_version' => '0.23.0', diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 50edc140f..9c93678af 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -129,7 +129,7 @@ services: networks: - coolify minio: - image: ghcr.io/coollabsio/maxio:latest + image: coollabsio/maxio:latest pull_policy: always container_name: coolify-minio ports: diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 78b027918..0c2bf23a2 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,7 +1,7 @@ { "coolify": { "v4": { - "version": "4.1.1" + "version": "4.1.2" }, "nightly": { "version": "4.2.0" diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 433102dcb..a8a9aaf76 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -49,23 +49,32 @@ localStorage.setItem('theme', type); this.queryTheme(); }, + cycleTheme() { + const themes = ['light', 'system', 'dark']; + const currentIndex = themes.indexOf(this.theme || localStorage.getItem('theme') || 'dark'); + this.setTheme(themes[(currentIndex + 1) % themes.length]); + }, queryTheme() { const darkModePreference = window.matchMedia('(prefers-color-scheme: dark)').matches; const userSettings = localStorage.getItem('theme') || 'dark'; localStorage.setItem('theme', userSettings); + let isDark = false; if (userSettings === 'dark') { document.documentElement.classList.add('dark'); this.theme = 'dark'; + isDark = true; } else if (userSettings === 'light') { document.documentElement.classList.remove('dark'); this.theme = 'light'; } else if (darkModePreference) { this.theme = 'system'; document.documentElement.classList.add('dark'); + isDark = true; } else if (!darkModePreference) { this.theme = 'system'; document.documentElement.classList.remove('dark'); } + document.querySelector('meta[name=theme-color]')?.setAttribute('content', isDark ? '#101010' : '#ffffff'); }, checkZoom() { if (this.zoom === null) { @@ -92,9 +101,9 @@ } } }"> -
-
+
+ @@ -107,10 +116,10 @@
-
+
-
- -
@@ -366,6 +372,70 @@ @endif @endif
+
  • + +
  • +
  • + + +
  • @if (isInstanceAdmin() && !isCloud()) @persist('upgrade')
  • diff --git a/resources/views/components/profile/navbar.blade.php b/resources/views/components/profile/navbar.blade.php new file mode 100644 index 000000000..4c1554ee3 --- /dev/null +++ b/resources/views/components/profile/navbar.blade.php @@ -0,0 +1,17 @@ +
    +

    Profile

    +
    Your user profile settings.
    + +
    diff --git a/resources/views/livewire/profile/appearance.blade.php b/resources/views/livewire/profile/appearance.blade.php new file mode 100644 index 000000000..45c2ac96c --- /dev/null +++ b/resources/views/livewire/profile/appearance.blade.php @@ -0,0 +1,119 @@ +
    + + Appearance | Coolify + + + +
    +
    +

    Appearance

    +
    Choose how Coolify looks in this browser.
    +
    + + + +
    +
    + +
    +

    Width

    +
    Choose the maximum page width for this browser.
    +
    + + +
    +
    + +
    +

    Zoom

    +
    Choose interface density for this browser.
    +
    + + +
    +
    +
    +
    diff --git a/resources/views/livewire/profile/index.blade.php b/resources/views/livewire/profile/index.blade.php index 11031b7f2..d5664fd68 100644 --- a/resources/views/livewire/profile/index.blade.php +++ b/resources/views/livewire/profile/index.blade.php @@ -2,8 +2,7 @@ Profile | Coolify -

    Profile

    -
    Your user profile settings.
    +

    General

    diff --git a/resources/views/livewire/settings-dropdown.blade.php b/resources/views/livewire/settings-dropdown.blade.php index efd359098..4c4ccd170 100644 --- a/resources/views/livewire/settings-dropdown.blade.php +++ b/resources/views/livewire/settings-dropdown.blade.php @@ -103,138 +103,98 @@ return new Date(b.published_at) - new Date(a.published_at); }); } -}" @click.outside="dropdownOpen = false"> - -
    - + @else + +
    + + + +
    +
    +
    + +
    + Width
    + - @else - + + +
    + Zoom
    + - @endif - - -
    - - -
    - Appearance
    - - - - - -
    - Width
    - - - - -
    - Zoom
    - - + +
    -
    + @endif @if ($showWhatsNewModal) @@ -262,8 +222,7 @@
    @if (isDev()) - + diff --git a/routes/web.php b/routes/web.php index 55067f46f..aed37a086 100644 --- a/routes/web.php +++ b/routes/web.php @@ -15,6 +15,7 @@ use App\Livewire\Notifications\Pushover as NotificationPushover; use App\Livewire\Notifications\Slack as NotificationSlack; use App\Livewire\Notifications\Telegram as NotificationTelegram; use App\Livewire\Notifications\Webhook as NotificationWebhook; +use App\Livewire\Profile\Appearance as ProfileAppearance; use App\Livewire\Profile\Index as ProfileIndex; use App\Livewire\Project\Application\Configuration as ApplicationConfiguration; use App\Livewire\Project\Application\Deployment\Index as DeploymentIndex; @@ -124,6 +125,7 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/settings/scheduled-jobs', SettingsScheduledJobs::class)->name('settings.scheduled-jobs'); Route::get('/profile', ProfileIndex::class)->name('profile'); + Route::get('/profile/appearance', ProfileAppearance::class)->name('profile.appearance'); Route::prefix('tags')->group(function () { Route::get('/{tagName?}', TagsShow::class)->name('tags.show'); diff --git a/tests/Feature/ProfileAppearanceNavigationTest.php b/tests/Feature/ProfileAppearanceNavigationTest.php new file mode 100644 index 000000000..b81a1c6a5 --- /dev/null +++ b/tests/Feature/ProfileAppearanceNavigationTest.php @@ -0,0 +1,43 @@ +toContain("Route::get('/profile/appearance', ProfileAppearance::class)->name('profile.appearance')") + ->and($profileNavbar) + ->toContain('route(\'profile\')') + ->toContain('route(\'profile.appearance\')') + ->toContain('General') + ->toContain('Appearance') + ->and($profileView) + ->toContain('') + ->not->toContain('

    Profile

    \n
    '); +}); + +it('moves appearance preferences to the profile appearance view', function () { + $appearanceView = file_get_contents(resource_path('views/livewire/profile/appearance.blade.php')); + + expect($appearanceView) + ->toContain('') + ->toContain("setTheme('light')") + ->toContain("setTheme('system')") + ->toContain("setTheme('dark')") + ->toContain("setWidth('center')") + ->toContain("setWidth('full')") + ->toContain("setZoom('100')") + ->toContain("setZoom('90')") + ->toContain('aria-label="Use light theme"') + ->toContain('aria-label="Use system theme"') + ->toContain('aria-label="Use dark theme"') + ->toContain('aria-label="Use centered width"') + ->toContain('aria-label="Use full width"') + ->toContain('aria-label="Use 100 percent zoom"') + ->toContain('aria-label="Use 90 percent zoom"') + ->toContain('max-w-2xl') + ->toContain('class="space-y-1.5"') + ->toContain('gap-1.5') + ->toContain('px-2 py-1 text-sm'); +}); diff --git a/tests/Feature/SidebarNavigationPreferencesTest.php b/tests/Feature/SidebarNavigationPreferencesTest.php new file mode 100644 index 000000000..b77758f58 --- /dev/null +++ b/tests/Feature/SidebarNavigationPreferencesTest.php @@ -0,0 +1,63 @@ +toContain('') + ->not->toContain('') + ->toContain('aria-label="Theme switcher"') + ->toContain('aria-label="Use light theme"') + ->toContain('aria-label="Use system theme"') + ->toContain('aria-label="Use dark theme"') + ->toContain('cycleTheme()') + ->toContain("const themes = ['light', 'system', 'dark'];") + ->toContain('pl-2 pr-3 items-start gap-3') + ->toContain('class="flex min-w-0 flex-1 flex-col"') + ->toContain('class="min-w-0 flex-1"') + ->toContain('class="flex h-8 w-full items-center justify-between'); +}); + +it('keeps changelog and appearance options out of the preferences dropdown', function () { + $dropdownView = file_get_contents(resource_path('views/livewire/settings-dropdown.blade.php')); + + expect($dropdownView) + ->toContain("\$trigger === 'changelog-sidebar'") + ->toContain('title="What\'s New"') + ->toContain('aria-label="What\'s New"') + ->toContain('wire:click="openWhatsNewModal"') + ->toContain('class="relative text-left menu-item"') + ->toContain('class="text-left menu-item-label"') + ->toContain("What's New") + ->not->toContain('Changelog') + ->not->toContain('Appearance
    ') + ->not->toContain("@click=\"setTheme('dark'); dropdownOpen = false\"") + ->not->toContain("@click=\"setTheme('light'); dropdownOpen = false\"") + ->not->toContain("@click=\"setTheme('system'); dropdownOpen = false\""); +}); + +it('opens and closes the changelog modal state', function () { + $component = new SettingsDropdown; + $component->trigger = 'changelog-sidebar'; + + expect($component->trigger)->toBe('changelog-sidebar') + ->and($component->showWhatsNewModal)->toBeFalse(); + + $component->openWhatsNewModal(); + + expect($component->showWhatsNewModal)->toBeTrue(); + + $component->closeWhatsNewModal(); + + expect($component->showWhatsNewModal)->toBeFalse(); +}); + +it('uses the default button palette for the changelog fetch action in light mode', function () { + $dropdownView = file_get_contents(resource_path('views/livewire/settings-dropdown.blade.php')); + + expect($dropdownView) + ->toContain('wire:click="manualFetchChangelog"') + ->not->toContain('bg-coolgray-200 hover:bg-coolgray-300'); +}); diff --git a/versions.json b/versions.json index 78b027918..0c2bf23a2 100644 --- a/versions.json +++ b/versions.json @@ -1,7 +1,7 @@ { "coolify": { "v4": { - "version": "4.1.1" + "version": "4.1.2" }, "nightly": { "version": "4.2.0"