Auth: convert auth views and verification email to Nova layout

This commit is contained in:
2026-02-21 07:37:08 +01:00
parent 93b009d42a
commit 795c7a835f
117 changed files with 5385 additions and 1291 deletions

View File

@@ -1,27 +1,31 @@
<x-guest-layout>
<div class="mb-4 text-sm text-gray-600">
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
@extends('layouts.nova')
@section('content')
<div class="px-4 py-8 md:px-6 md:py-10">
<div class="mx-auto w-full max-w-xl rounded-2xl border border-sb-line bg-panel-dark shadow-sb p-6 md:p-8">
<h1 class="text-2xl font-semibold text-white">Confirm Password</h1>
<p class="mt-2 text-sm text-sb-muted">Please confirm your password before continuing.</p>
<form method="POST" action="{{ route('password.confirm') }}" class="mt-4">
@csrf
<div>
<x-input-label for="password" :value="__('Password')" class="text-sb-muted" />
<x-text-input id="password" class="block mt-1 w-full bg-black/20 border-sb-line text-white"
type="password"
name="password"
required autocomplete="current-password" />
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<div class="flex justify-end mt-4">
<x-primary-button>
{{ __('Confirm') }}
</x-primary-button>
</div>
</form>
</div>
<form method="POST" action="{{ route('password.confirm') }}">
@csrf
<!-- Password -->
<div>
<x-input-label for="password" :value="__('Password')" />
<x-text-input id="password" class="block mt-1 w-full"
type="password"
name="password"
required autocomplete="current-password" />
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<div class="flex justify-end mt-4">
<x-primary-button>
{{ __('Confirm') }}
</x-primary-button>
</div>
</form>
</x-guest-layout>
</div>
@endsection

View File

@@ -1,25 +1,28 @@
<x-guest-layout>
<div class="mb-4 text-sm text-gray-600">
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
@extends('layouts.nova')
@section('content')
<div class="px-4 py-8 md:px-6 md:py-10">
<div class="mx-auto w-full max-w-xl rounded-2xl border border-sb-line bg-panel-dark shadow-sb p-6 md:p-8">
<h1 class="text-2xl font-semibold text-white">Reset Password</h1>
<p class="mt-2 text-sm text-sb-muted">Enter your email and we'll send a link to reset your password.</p>
<x-auth-session-status class="mt-4 mb-2 text-green-300" :status="session('status')" />
<form method="POST" action="{{ route('password.email') }}" class="mt-4">
@csrf
<div>
<x-input-label for="email" :value="__('Email')" class="text-sb-muted" />
<x-text-input id="email" class="block mt-1 w-full bg-black/20 border-sb-line text-white" type="email" name="email" :value="old('email')" required autofocus />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Email Password Reset Link') }}
</x-primary-button>
</div>
</form>
</div>
<!-- Session Status -->
<x-auth-session-status class="mb-4" :status="session('status')" />
<form method="POST" action="{{ route('password.email') }}">
@csrf
<!-- Email Address -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Email Password Reset Link') }}
</x-primary-button>
</div>
</form>
</x-guest-layout>
</div>
@endsection

View File

@@ -1,47 +1,49 @@
<x-guest-layout>
<!-- Session Status -->
<x-auth-session-status class="mb-4" :status="session('status')" />
@extends('layouts.nova')
<form method="POST" action="{{ route('login') }}">
@csrf
@section('content')
<div class="px-4 py-8 md:px-6 md:py-10">
<div class="mx-auto w-full max-w-xl rounded-2xl border border-sb-line bg-panel-dark shadow-sb p-6 md:p-8">
<h1 class="text-2xl font-semibold text-white">Log in</h1>
<p class="mt-2 text-sm text-sb-muted">Sign in to continue to your Skinbase account.</p>
<!-- Email Address -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus autocomplete="username" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<x-auth-session-status class="mt-4 mb-2 text-green-300" :status="session('status')" />
<!-- Password -->
<div class="mt-4">
<x-input-label for="password" :value="__('Password')" />
<form method="POST" action="{{ route('login') }}" class="mt-4 space-y-4">
@csrf
<x-text-input id="password" class="block mt-1 w-full"
type="password"
name="password"
required autocomplete="current-password" />
<div>
<x-input-label for="email" :value="__('Email')" class="text-sb-muted" />
<x-text-input id="email" class="block mt-1 w-full bg-black/20 border-sb-line text-white" type="email" name="email" :value="old('email')" required autofocus autocomplete="username" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<div>
<x-input-label for="password" :value="__('Password')" class="text-sb-muted" />
<x-text-input id="password" class="block mt-1 w-full bg-black/20 border-sb-line text-white" type="password" name="password" required autocomplete="current-password" />
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<!-- Remember Me -->
<div class="block mt-4">
<label for="remember_me" class="inline-flex items-center">
<input id="remember_me" type="checkbox" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500" name="remember">
<span class="ms-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
</label>
</div>
<div class="block mt-1">
<label for="remember_me" class="inline-flex items-center">
<input id="remember_me" type="checkbox" class="rounded border-sb-line bg-black/20 text-sb-blue shadow-sm focus:ring-sb-blue" name="remember">
<span class="ms-2 text-sm text-sb-muted">{{ __('Remember me') }}</span>
</label>
</div>
<div class="flex items-center justify-end mt-4">
@if (Route::has('password.request'))
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('password.request') }}">
{{ __('Forgot your password?') }}
</a>
@endif
<div class="flex items-center justify-between pt-2">
@if (Route::has('password.request'))
<a class="underline text-sm text-sb-muted hover:text-white" href="{{ route('password.request') }}">
{{ __('Forgot your password?') }}
</a>
@else
<span></span>
@endif
<x-primary-button class="ms-3">
{{ __('Log in') }}
</x-primary-button>
</div>
</form>
</x-guest-layout>
<x-primary-button class="justify-center">
{{ __('Log in') }}
</x-primary-button>
</div>
</form>
</div>
</div>
@endsection

View File

@@ -0,0 +1,27 @@
@php
$steps = [
'email' => 'Email',
'verified' => 'Verified',
'password' => 'Password',
'complete' => 'Username',
];
$currentIndex = array_search($currentStep ?? 'email', array_keys($steps), true);
if ($currentIndex === false) {
$currentIndex = 0;
}
@endphp
<div class="mb-6">
<div class="flex items-center justify-between text-xs sm:text-sm text-gray-600">
@foreach($steps as $key => $label)
@php $idx = array_search($key, array_keys($steps), true); @endphp
<span class="{{ $idx <= $currentIndex ? 'text-gray-900 font-semibold' : '' }}">
{{ $label }}
</span>
@endforeach
</div>
<div class="mt-2 h-2 w-full bg-gray-200 rounded-full overflow-hidden">
<div class="h-full bg-gray-900 rounded-full" style="width: {{ (($currentIndex + 1) / count($steps)) * 100 }}%"></div>
</div>
</div>

View File

@@ -0,0 +1,86 @@
@extends('layouts.nova')
@section('content')
<div class="px-4 py-8 md:px-6 md:py-10">
<div class="mx-auto w-full max-w-xl rounded-2xl border border-sb-line bg-panel-dark shadow-sb p-6 md:p-8">
<div class="mb-4 text-sm text-sb-muted">
<p class="font-medium text-white">Check your inbox</p>
@if($email !== '')
<p class="mt-1">We sent a verification link to <strong class="text-white">{{ $email }}</strong>.</p>
<p class="mt-1">Click the link in that email to continue setup.</p>
@else
<p class="mt-1">Enter your email to resend verification if needed.</p>
@endif
</div>
@if (session('status'))
<div class="mb-4 rounded-md border border-green-700/60 bg-green-900/20 px-3 py-2 text-sm text-green-300">
{{ session('status') }}
</div>
@endif
@if ($errors->any())
<div class="mb-4 rounded-md border border-red-700/60 bg-red-900/20 px-3 py-2 text-sm text-red-300">
{{ $errors->first() }}
</div>
@endif
<form method="POST" action="{{ route('register.resend') }}" class="space-y-4" id="resend-form" data-resend-seconds="{{ (int) $resendSeconds }}">
@csrf
<div>
<x-input-label for="email" value="Email" class="text-sb-muted" />
<x-text-input id="email" class="block mt-1 w-full bg-black/20 border-sb-line text-white" type="email" name="email" :value="old('email', $email)" required autocomplete="email" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<div class="flex items-center justify-between gap-3">
<div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:gap-3">
<a class="underline text-sm text-sb-muted hover:text-white" href="{{ route('login') }}">Back to login</a>
<a class="underline text-sm text-sb-muted hover:text-white" href="{{ route('register', ['email' => $email]) }}">Change email</a>
</div>
<x-primary-button id="resend-btn" class="justify-center" type="submit">
Resend verification email
</x-primary-button>
</div>
<p id="resend-timer" class="text-xs text-sb-muted"></p>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('resend-form');
const button = document.getElementById('resend-btn');
const timerText = document.getElementById('resend-timer');
if (!form || !button || !timerText) return;
let remaining = parseInt(form.dataset.resendSeconds || '0', 10);
const render = () => {
if (remaining > 0) {
button.setAttribute('disabled', 'disabled');
timerText.textContent = `You can resend in ${remaining}s.`;
} else {
button.removeAttribute('disabled');
timerText.textContent = 'Did not receive it? You can resend now.';
}
};
render();
if (remaining <= 0) return;
const interval = setInterval(() => {
remaining -= 1;
render();
if (remaining <= 0) {
clearInterval(interval);
}
}, 1000);
});
</script>
@endsection

View File

@@ -1,52 +1,41 @@
<x-guest-layout>
<form method="POST" action="{{ route('register') }}">
@csrf
@extends('layouts.nova')
<!-- Name -->
<div>
<x-input-label for="name" :value="__('Name')" />
<x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
<x-input-error :messages="$errors->get('name')" class="mt-2" />
</div>
@section('content')
<div class="px-4 py-8 md:px-6 md:py-10">
<div class="mx-auto w-full max-w-xl rounded-2xl border border-sb-line bg-panel-dark shadow-sb p-6 md:p-8">
<h1 class="text-2xl font-semibold text-white">Create Account</h1>
<p class="mt-2 text-sm text-sb-muted">Start with your email. You will set your password and username after verification.</p>
<!-- Email Address -->
<div class="mt-4">
<x-input-label for="email" :value="__('Email')" />
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<form method="POST" action="{{ route('register') }}" class="mt-6 space-y-4">
@csrf
<!-- Password -->
<div class="mt-4">
<x-input-label for="password" :value="__('Password')" />
<div style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;" aria-hidden="true">
<label for="website">Website</label>
<input id="website" type="text" name="website" tabindex="-1" autocomplete="off" />
</div>
<x-text-input id="password" class="block mt-1 w-full"
type="password"
name="password"
required autocomplete="new-password" />
<!-- Email Address -->
<div>
<x-input-label for="email" :value="__('Email')" class="text-sb-muted" />
<x-text-input id="email" class="block mt-1 w-full bg-black/20 border-sb-line text-white" type="email" name="email" :value="old('email', $prefillEmail ?? '')" required autofocus autocomplete="email" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
@if(config('services.recaptcha.enabled'))
<input type="hidden" name="g-recaptcha-response" value="{{ old('g-recaptcha-response') }}" />
<x-input-error :messages="$errors->get('captcha')" class="mt-2" />
@endif
<!-- Confirm Password -->
<div class="mt-4">
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
<div class="flex items-center justify-between pt-2">
<a class="underline text-sm text-sb-muted hover:text-white" href="{{ route('login') }}">
{{ __('Already registered?') }}
</a>
<x-text-input id="password_confirmation" class="block mt-1 w-full"
type="password"
name="password_confirmation" required autocomplete="new-password" />
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
</div>
<div class="flex items-center justify-end mt-4">
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('login') }}">
{{ __('Already registered?') }}
</a>
<x-primary-button class="ms-4">
{{ __('Register') }}
</x-primary-button>
</div>
</form>
</x-guest-layout>
<x-primary-button class="justify-center">
{{ __('Register') }}
</x-primary-button>
</div>
</form>
</div>
</div>
@endsection

View File

@@ -1,39 +1,48 @@
<x-guest-layout>
<form method="POST" action="{{ route('password.store') }}">
@csrf
@extends('layouts.nova')
<!-- Password Reset Token -->
<input type="hidden" name="token" value="{{ $request->route('token') }}">
@section('content')
<div class="px-4 py-8 md:px-6 md:py-10">
<div class="mx-auto w-full max-w-xl rounded-2xl border border-sb-line bg-panel-dark shadow-sb p-6 md:p-8">
<h1 class="text-2xl font-semibold text-white">Reset Password</h1>
<p class="mt-2 text-sm text-sb-muted">Choose a new password for your account.</p>
<!-- Email Address -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email', $request->email)" required autofocus autocomplete="username" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<form method="POST" action="{{ route('password.store') }}" class="mt-4">
@csrf
<!-- Password -->
<div class="mt-4">
<x-input-label for="password" :value="__('Password')" />
<x-text-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<!-- Password Reset Token -->
<input type="hidden" name="token" value="{{ $request->route('token') }}">
<!-- Confirm Password -->
<div class="mt-4">
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
<!-- Email Address -->
<div>
<x-input-label for="email" :value="__('Email')" class="text-sb-muted" />
<x-text-input id="email" class="block mt-1 w-full bg-black/20 border-sb-line text-white" type="email" name="email" :value="old('email', $request->email)" required autofocus autocomplete="username" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<x-text-input id="password_confirmation" class="block mt-1 w-full"
type="password"
name="password_confirmation" required autocomplete="new-password" />
<!-- Password -->
<div class="mt-4">
<x-input-label for="password" :value="__('Password')" class="text-sb-muted" />
<x-text-input id="password" class="block mt-1 w-full bg-black/20 border-sb-line text-white" type="password" name="password" required autocomplete="new-password" />
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
</div>
<!-- Confirm Password -->
<div class="mt-4">
<x-input-label for="password_confirmation" :value="__('Confirm Password')" class="text-sb-muted" />
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Reset Password') }}
</x-primary-button>
</div>
</form>
</x-guest-layout>
<x-text-input id="password_confirmation" class="block mt-1 w-full bg-black/20 border-sb-line text-white"
type="password"
name="password_confirmation" required autocomplete="new-password" />
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
</div>
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Reset Password') }}
</x-primary-button>
</div>
</form>
</div>
</div>
@endsection

View File

@@ -0,0 +1,44 @@
@extends('layouts.nova')
@section('content')
<div class="px-4 py-8 md:px-6 md:py-10">
<div class="mx-auto w-full max-w-2xl rounded-2xl border border-sb-line bg-panel-dark shadow-sb p-6 md:p-8">
<h1 class="text-2xl font-semibold text-white">Set Your Password</h1>
<div class="mt-4 text-white/90">
@include('auth.partials.onboarding-progress', ['currentStep' => 'verified'])
@if (session('status'))
<div class="mb-4 rounded-md border border-green-700/60 bg-green-900/20 px-3 py-2 text-sm text-green-300">
{{ session('status') }}
</div>
@endif
<p class="mb-4 text-sm text-sb-muted">
{{ __('Create a password for ') }}<strong>{{ $email }}</strong>
</p>
<form method="POST" action="{{ route('setup.password.store') }}">
@csrf
<div>
<x-input-label for="password" :value="__('Password')" class="text-sb-muted" />
<x-text-input id="password" class="block mt-1 w-full bg-black/20 border-sb-line text-white" type="password" name="password" required autocomplete="new-password" />
<x-input-error :messages="$errors->get('password')" class="mt-2" />
<p class="mt-2 text-xs text-sb-muted">{{ __('Minimum 10 characters, include at least one number and one symbol.') }}</p>
</div>
<div class="mt-4">
<x-input-label for="password_confirmation" :value="__('Confirm Password')" class="text-sb-muted" />
<x-text-input id="password_confirmation" class="block mt-1 w-full bg-black/20 border-sb-line text-white" type="password" name="password_confirmation" required autocomplete="new-password" />
</div>
<div class="mt-6 flex justify-end">
<x-primary-button class="w-full sm:w-auto justify-center">
{{ __('Continue') }}
</x-primary-button>
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,52 @@
@extends('layouts.nova')
@section('content')
<div class="px-4 py-8 md:px-6 md:py-10">
<div class="mx-auto w-full max-w-2xl rounded-2xl border border-sb-line bg-panel-dark shadow-sb p-6 md:p-8">
<h1 class="text-2xl font-semibold text-white">Choose Username</h1>
<div class="mt-4 text-white/90">
@include('auth.partials.onboarding-progress', ['currentStep' => 'password'])
@if (session('status'))
<div class="mb-4 rounded-md border border-green-700/60 bg-green-900/20 px-3 py-2 text-sm text-green-300">
{{ session('status') }}
</div>
@endif
@if ($errors->any())
<div class="mb-4 rounded-md border border-red-700/60 bg-red-900/20 px-3 py-2 text-sm text-red-300">
{{ $errors->first() }}
</div>
@endif
<form method="POST" action="{{ route('setup.username.store') }}">
@csrf
<div>
<x-input-label for="username" :value="__('Username')" class="text-sb-muted" />
<x-text-input
id="username"
class="block mt-1 w-full bg-black/20 border-sb-line text-white"
type="text"
name="username"
:value="old('username', $username)"
required
autocomplete="username"
data-username-field="true"
data-availability-url="{{ route('api.username.availability') }}"
data-availability-target="setup-username-availability"
/>
<p id="setup-username-availability" class="mt-1 text-xs text-sb-muted"></p>
<x-input-error :messages="$errors->get('username')" class="mt-2" />
</div>
<div class="mt-6 flex justify-end">
<x-primary-button class="w-full sm:w-auto justify-center">
{{ __('Complete Setup') }}
</x-primary-button>
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@@ -1,31 +1,36 @@
<x-guest-layout>
<div class="mb-4 text-sm text-gray-600">
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
</div>
@extends('layouts.nova')
@if (session('status') == 'verification-link-sent')
<div class="mb-4 font-medium text-sm text-green-600">
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
</div>
@endif
@section('content')
<div class="px-4 py-8 md:px-6 md:py-10">
<div class="mx-auto w-full max-w-xl rounded-2xl border border-sb-line bg-panel-dark shadow-sb p-6 md:p-8">
<h1 class="text-2xl font-semibold text-white">Verify Your Email</h1>
<p class="mt-2 text-sm text-sb-muted">Before getting started, please verify your email address by clicking the link we sent you.</p>
<div class="mt-4 flex items-center justify-between">
<form method="POST" action="{{ route('verification.send') }}">
@csrf
<div>
<x-primary-button>
{{ __('Resend Verification Email') }}
</x-primary-button>
@if (session('status') == 'verification-link-sent')
<div class="mb-4 rounded-md border border-green-700/60 bg-green-900/20 px-3 py-2 text-sm text-green-300">
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
</div>
</form>
@endif
<form method="POST" action="{{ route('logout') }}">
@csrf
<div class="mt-4 flex items-center justify-between">
<form method="POST" action="{{ route('verification.send') }}">
@csrf
<button type="submit" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
{{ __('Log Out') }}
</button>
</form>
<div>
<x-primary-button>
{{ __('Resend Verification Email') }}
</x-primary-button>
</div>
</form>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="underline text-sm text-sb-muted hover:text-white rounded-md">
{{ __('Log Out') }}
</button>
</form>
</div>
</div>
</x-guest-layout>
</div>
@endsection