From 88957f8f5742b10aeb0f75163ac91fc529020e31 Mon Sep 17 00:00:00 2001 From: nexxo Date: Sat, 25 Jul 2026 18:16:27 +0200 Subject: [PATCH] feat(auth): enterprise split login + signup (Fortify registration) via frontend-design - login/register redesigned as a split brand-panel + clean form (Apple-ish enterprise look), cross-linked; brand panel uses accent gradient + tokens - Fortify registration enabled; /register full-page Livewire + RegisterTest Co-Authored-By: Claude Opus 4.8 --- app/Livewire/Auth/Register.php | 19 +++++ config/fortify.php | 2 +- lang/de/auth.php | 20 +++++ lang/en/auth.php | 20 +++++ resources/views/livewire/auth/login.blade.php | 79 +++++++++++-------- .../views/livewire/auth/register.blade.php | 55 +++++++++++++ routes/web.php | 1 + tests/Feature/Auth/RegisterTest.php | 36 +++++++++ 8 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 app/Livewire/Auth/Register.php create mode 100644 resources/views/livewire/auth/register.blade.php create mode 100644 tests/Feature/Auth/RegisterTest.php diff --git a/app/Livewire/Auth/Register.php b/app/Livewire/Auth/Register.php new file mode 100644 index 0000000..ea7ef04 --- /dev/null +++ b/app/Livewire/Auth/Register.php @@ -0,0 +1,19 @@ + 'Passwort', 'remember' => 'Angemeldet bleiben', 'sign_in' => 'Anmelden', + 'no_account' => 'Noch kein Konto?', + 'sign_up' => 'Registrieren', + + // Register + 'register_title' => 'Konto erstellen', + 'register_subtitle' => 'Starten Sie mit Ihrer verwalteten CluPilot-Cloud.', + 'name' => 'Name', + 'password_confirm' => 'Passwort bestätigen', + 'create_account' => 'Konto erstellen', + 'have_account' => 'Bereits registriert?', + + // Brand panel + 'brand_headline' => 'Ihre Cloud. Vollständig verwaltet.', + 'brand_sub' => 'Nextcloud für Ihr Unternehmen — sicher betrieben, automatisch aktualisiert, gesichert.', + 'brand_point_1' => 'Automatische Backups & Updates', + 'brand_point_2' => 'Eigene Domain & Branding', + 'brand_point_3' => 'Gehostet in der EU, DSGVO-konform', + 'brand_footer' => '© CluPilot — Managed Nextcloud.', + 'register_brand_headline' => 'In Minuten startklar.', + 'register_brand_sub' => 'Konto erstellen, Paket wählen — den Rest übernehmen wir vollautomatisch.', // Two-factor 'twofa_title' => 'Zwei-Faktor-Bestätigung', diff --git a/lang/en/auth.php b/lang/en/auth.php index a5737ee..9f985d7 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -13,6 +13,26 @@ return [ 'password_label' => 'Password', 'remember' => 'Stay signed in', 'sign_in' => 'Sign in', + 'no_account' => 'No account yet?', + 'sign_up' => 'Sign up', + + // Register + 'register_title' => 'Create your account', + 'register_subtitle' => 'Get started with your managed CluPilot cloud.', + 'name' => 'Name', + 'password_confirm' => 'Confirm password', + 'create_account' => 'Create account', + 'have_account' => 'Already registered?', + + // Brand panel + 'brand_headline' => 'Your cloud. Fully managed.', + 'brand_sub' => 'Nextcloud for your business — securely operated, auto-updated, backed up.', + 'brand_point_1' => 'Automatic backups & updates', + 'brand_point_2' => 'Custom domain & branding', + 'brand_point_3' => 'Hosted in the EU, GDPR-compliant', + 'brand_footer' => '© CluPilot — Managed Nextcloud.', + 'register_brand_headline' => 'Ready in minutes.', + 'register_brand_sub' => 'Create an account, pick a plan — we handle the rest, fully automated.', // Two-factor 'twofa_title' => 'Two-factor confirmation', diff --git a/resources/views/livewire/auth/login.blade.php b/resources/views/livewire/auth/login.blade.php index 434e39c..b2a46df 100644 --- a/resources/views/livewire/auth/login.blade.php +++ b/resources/views/livewire/auth/login.blade.php @@ -1,41 +1,58 @@ -
-
-
- CluPilot +
+ {{-- Brand panel (enterprise, hidden on small screens) --}} + + + {{-- Form panel --}} +
+
+
+ + CluPilot +
+ +

{{ __('auth.login_title') }}

+

{{ __('auth.login_subtitle') }}

@if (session('status')) - {{ session('status') }} + {{ session('status') }} @endif -
+ @csrf - - + + - - - {{ __('auth.sign_in') }} - + {{ __('auth.sign_in') }} - -
+ +

+ {{ __('auth.no_account') }} + {{ __('auth.sign_up') }} +

+
+
diff --git a/resources/views/livewire/auth/register.blade.php b/resources/views/livewire/auth/register.blade.php new file mode 100644 index 0000000..0adfb52 --- /dev/null +++ b/resources/views/livewire/auth/register.blade.php @@ -0,0 +1,55 @@ +
+ {{-- Brand panel (enterprise, hidden on small screens) --}} + + + {{-- Form panel --}} +
+
+
+ + CluPilot +
+ +

{{ __('auth.register_title') }}

+

{{ __('auth.register_subtitle') }}

+ +
+ @csrf + + + + + {{ __('auth.create_account') }} + + +

+ {{ __('auth.have_account') }} + {{ __('auth.sign_in') }} +

+
+
+
diff --git a/routes/web.php b/routes/web.php index 6dfaeb6..90d5d13 100644 --- a/routes/web.php +++ b/routes/web.php @@ -31,6 +31,7 @@ Route::prefix('legal')->name('legal.')->group(function () { // handles the POST actions (login.store, two-factor.login.store) with views off. Route::middleware('guest')->group(function () { Route::get('/login', Login::class)->name('login'); + Route::get('/register', \App\Livewire\Auth\Register::class)->name('register'); Route::get('/two-factor-challenge', TwoFactorChallenge::class)->name('two-factor.login'); }); diff --git a/tests/Feature/Auth/RegisterTest.php b/tests/Feature/Auth/RegisterTest.php new file mode 100644 index 0000000..7444c6e --- /dev/null +++ b/tests/Feature/Auth/RegisterTest.php @@ -0,0 +1,36 @@ +get(route('register'))->assertOk()->assertSee(__('auth.register_title')); +}); + +it('links between login and signup', function () { + $this->get(route('login'))->assertOk()->assertSee(route('register')); + $this->get(route('register'))->assertOk()->assertSee(route('login')); +}); + +it('registers a new account and signs in', function () { + $this->post(route('register.store'), [ + 'name' => 'New Co', + 'email' => 'new@signup.test', + 'password' => 'password1234', + 'password_confirmation' => 'password1234', + ])->assertRedirect(); + + $user = User::query()->where('email', 'new@signup.test')->first(); + expect($user)->not->toBeNull()->and($user->isOperator())->toBeFalse(); + $this->assertAuthenticatedAs($user); +}); + +it('rejects a mismatched password confirmation', function () { + $this->post(route('register.store'), [ + 'name' => 'X', + 'email' => 'x@signup.test', + 'password' => 'password1234', + 'password_confirmation' => 'different', + ])->assertSessionHasErrors('password'); + + expect(User::query()->where('email', 'x@signup.test')->exists())->toBeFalse(); +});