feat(ui): Whitemode auth views + design tokens + vite server block + touch.js entry

- app.css: add accent/success/danger/surface tokens
- vite.config.js: server block (HMR wss), touch.js in input array
- Breeze components: text-input, primary-button, input-label, input-error, auth-session-status → oklch token system
- layouts/guest.blade.php: Lernschiff logo, white card, no CDN fonts
- All 5 auth pages (de): Willkommen zurück, password show/hide toggle, wire:loading spinners
- dev-quick-login: redesigned with accent hover, monospace email, heroicons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
Boban Blaskovic 2026-05-22 13:38:57 +02:00
parent d9a957b892
commit d522f0d376
14 changed files with 396 additions and 185 deletions

View File

@ -6,6 +6,12 @@
--color-text-secondary: oklch(0.42 0.012 270);
--color-text-muted: oklch(0.58 0.01 270);
--color-border-subtle: oklch(0.92 0.006 85);
--color-surface: #ffffff;
--color-accent: oklch(0.42 0.22 255);
--color-accent-hover: oklch(0.36 0.24 255);
--color-accent-muted: oklch(0.96 0.04 255);
--color-success: oklch(0.52 0.18 155);
--color-danger: oklch(0.50 0.22 25);
--font-display: 'Plus Jakarta Sans', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}

View File

@ -1,7 +1,10 @@
@props(['status'])
@if ($status)
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
<div {{ $attributes->merge([
'class' => 'flex items-center gap-2 text-sm font-medium text-[--color-success] bg-[oklch(0.96_0.05_155)] border border-[oklch(0.88_0.10_155)] rounded-lg px-4 py-3'
]) }}>
<x-heroicon-s-check-circle class="w-4 h-4 shrink-0" />
{{ $status }}
</div>
@endif

View File

@ -1,9 +1,12 @@
@props(['messages'])
@if ($messages)
<ul {{ $attributes->merge(['class' => 'text-sm text-red-600 space-y-1']) }}>
<ul {{ $attributes->merge(['class' => 'text-xs text-[--color-danger] space-y-0.5 mt-1.5']) }}>
@foreach ((array) $messages as $message)
<li>{{ $message }}</li>
<li class="flex items-center gap-1.5">
<x-heroicon-s-exclamation-circle class="w-3.5 h-3.5 shrink-0" />
{{ $message }}
</li>
@endforeach
</ul>
@endif

View File

@ -1,5 +1,5 @@
@props(['value'])
<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
<label {{ $attributes->merge(['class' => 'block text-sm font-medium text-[--color-text-secondary] mb-1']) }}>
{{ $value ?? $slot }}
</label>

View File

@ -1,3 +1,6 @@
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
<button {{ $attributes->merge([
'type' => 'submit',
'class' => 'inline-flex items-center justify-center gap-2 px-5 py-2.5 bg-[--color-accent] hover:bg-[--color-accent-hover] text-white font-semibold text-sm rounded-lg focus:outline-none focus:ring-2 focus:ring-[--color-accent] focus:ring-offset-2 focus:ring-offset-white transition-all duration-150 cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed'
]) }}>
{{ $slot }}
</button>

View File

@ -1,3 +1,8 @@
@props(['disabled' => false])
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) }}>
<input
@disabled($disabled)
{{ $attributes->merge([
'class' => 'w-full px-3.5 py-2.5 bg-white border border-[--color-border-subtle] rounded-lg text-sm text-[--color-text-primary] placeholder-[--color-text-muted] focus:outline-none focus:ring-2 focus:ring-[--color-accent] focus:border-transparent transition-all duration-150 disabled:opacity-50 disabled:cursor-not-allowed'
]) }}
>

View File

@ -1,30 +1,37 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<html lang="de" class="h-full">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content="{{ csrf_token() }}" />
<title>{{ config('app.name', 'Lernschiff') }}</title>
@vite(['resources/css/app.css', 'resources/js/auth.js'])
@livewireStyles
</head>
<body class="min-h-screen bg-[--color-bg-base] flex items-center justify-center px-4 py-12">
<title>{{ config('app.name', 'Laravel') }}</title>
<div class="w-full max-w-[420px]">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="font-sans text-gray-900 antialiased">
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
<div>
<a href="/" wire:navigate>
<x-application-logo class="w-20 h-20 fill-current text-gray-500" />
</a>
{{-- Logo --}}
<div class="flex items-center justify-center gap-2.5 mb-8">
<div class="w-9 h-9 rounded-xl bg-[--color-accent] flex items-center justify-center shadow-sm">
<x-heroicon-o-academic-cap class="w-5 h-5 text-white" />
</div>
<span class="font-bold text-xl tracking-tight text-[--color-text-primary]">Lernschiff</span>
</div>
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
{{-- Card --}}
<div class="bg-white rounded-2xl border border-[--color-border-subtle] shadow-sm px-8 py-8">
{{ $slot }}
</div>
{{-- Footer --}}
<p class="text-center text-xs text-[--color-text-muted] mt-6">
© {{ date('Y') }} Lernschiff · DACH · Alle Rechte vorbehalten
</p>
</div>
</body>
@livewireScripts
</body>
</html>

View File

@ -1,14 +1,50 @@
<div class="p-8 max-w-lg mx-auto">
<h1 class="text-2xl font-bold mb-6 text-[--color-text-primary]">Quick Login (Dev Only)</h1>
<div class="min-h-screen bg-[--color-bg-base] flex items-center justify-center px-4 py-12">
<div class="w-full max-w-[480px]">
{{-- Header --}}
<div class="flex items-center gap-2.5 mb-8">
<div class="w-9 h-9 rounded-xl bg-[--color-accent] flex items-center justify-center shadow-sm">
<x-heroicon-o-academic-cap class="w-5 h-5 text-white" />
</div>
<div>
<span class="font-bold text-xl tracking-tight text-[--color-text-primary]">Lernschiff</span>
<span class="ml-2 text-xs font-semibold uppercase tracking-widest text-[--color-text-muted]">Dev</span>
</div>
</div>
<div class="bg-white rounded-2xl border border-[--color-border-subtle] shadow-sm px-8 py-8">
<div class="flex items-center gap-2 mb-6">
<x-heroicon-o-bolt class="w-5 h-5 text-[--color-accent]" />
<div>
<h1 class="text-lg font-bold text-[--color-text-primary]">Quick Login</h1>
<p class="text-xs text-[--color-text-muted]">Nur lokal sichtbar · ALLOW_QUICK_LOGIN=true</p>
</div>
</div>
<div class="space-y-2">
@foreach($users as $user)
<button
wire:click="loginAs('{{ $user->email }}')"
class="w-full text-left px-4 py-3 rounded-lg border border-[--color-border-subtle] hover:bg-white transition-colors"
wire:loading.attr="disabled"
wire:target="loginAs('{{ $user->email }}')"
class="w-full flex items-center justify-between px-4 py-3 rounded-xl border border-[--color-border-subtle] hover:border-[--color-accent] hover:bg-[--color-accent-muted] transition-all duration-150 group cursor-pointer disabled:opacity-50"
>
<span class="font-semibold">{{ $user->name }}</span>
<span class="text-sm text-[--color-text-muted] ml-2">{{ $user->email }}</span>
<div class="text-left">
<p class="text-sm font-semibold text-[--color-text-primary] group-hover:text-[--color-accent]">
{{ $user->name }}
</p>
<p class="text-xs text-[--color-text-muted] font-mono">{{ $user->email }}</p>
</div>
<x-heroicon-o-arrow-right class="w-4 h-4 text-[--color-text-muted] group-hover:text-[--color-accent] transition-colors" />
</button>
@endforeach
</div>
</div>
<p class="text-center text-xs text-[--color-text-muted] mt-6">
© {{ date('Y') }} Lernschiff · DACH
</p>
</div>
</div>

View File

@ -8,10 +8,8 @@ use Livewire\Volt\Component;
new #[Layout('layouts.guest')] class extends Component
{
public string $password = '';
public bool $showPassword = false;
/**
* Confirm the current user's password.
*/
public function confirmPassword(): void
{
$this->validate([
@ -28,35 +26,58 @@ new #[Layout('layouts.guest')] class extends Component
}
session(['auth.password_confirmed_at' => time()]);
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
}
}; ?>
<div>
<div class="mb-4 text-sm text-gray-600">
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
<div class="mb-6">
<h1 class="text-2xl font-bold tracking-tight text-[--color-text-primary]">Sicherheitsbereich</h1>
<p class="text-sm text-[--color-text-muted] mt-1">
Bitte bestätige dein Passwort, um fortzufahren.
</p>
</div>
<form wire:submit="confirmPassword">
<!-- Password -->
<form wire:submit="confirmPassword" class="space-y-4">
<div>
<x-input-label for="password" :value="__('Password')" />
<x-text-input wire:model="password"
<x-input-label for="password" value="Aktuelles Passwort" />
<div class="relative">
<x-text-input
wire:model="password"
id="password"
class="block mt-1 w-full"
type="password"
:type="$showPassword ? 'text' : 'password'"
name="password"
required autocomplete="current-password" />
<x-input-error :messages="$errors->get('password')" class="mt-2" />
required
autocomplete="current-password"
class="pr-10"
/>
<button type="button" wire:click="$set('showPassword', !$showPassword)"
class="absolute right-3 top-1/2 -translate-y-1/2 text-[--color-text-muted] hover:text-[--color-text-secondary] transition-colors duration-150 cursor-pointer"
aria-label="Passwort anzeigen">
@if($showPassword)
<x-heroicon-o-eye-slash class="w-4 h-4" />
@else
<x-heroicon-o-eye class="w-4 h-4" />
@endif
</button>
</div>
<x-input-error :messages="$errors->get('password')" />
</div>
<div class="flex justify-end mt-4">
<x-primary-button>
{{ __('Confirm') }}
<x-primary-button class="w-full" wire:loading.attr="disabled">
<span wire:loading.remove wire:target="confirmPassword">
<x-heroicon-o-shield-check class="w-4 h-4" />
Bestätigen
</span>
<span wire:loading wire:target="confirmPassword" class="flex items-center gap-2">
<svg class="animate-spin w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
Wird geprüft…
</span>
</x-primary-button>
</div>
</form>
</div>

View File

@ -8,54 +8,70 @@ new #[Layout('layouts.guest')] class extends Component
{
public string $email = '';
/**
* Send a password reset link to the provided email address.
*/
public function sendPasswordResetLink(): void
{
$this->validate([
'email' => ['required', 'string', 'email'],
]);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$status = Password::sendResetLink(
$this->only('email')
);
$status = Password::sendResetLink($this->only('email'));
if ($status != Password::RESET_LINK_SENT) {
$this->addError('email', __($status));
return;
}
$this->reset('email');
session()->flash('status', __($status));
}
}; ?>
<div>
<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.') }}
<div class="mb-6">
<h1 class="text-2xl font-bold tracking-tight text-[--color-text-primary]">Passwort zurücksetzen</h1>
<p class="text-sm text-[--color-text-muted] mt-1">
Gib deine E-Mail-Adresse ein wir senden dir einen Reset-Link.
</p>
</div>
<!-- Session Status -->
<x-auth-session-status class="mb-4" :status="session('status')" />
<x-auth-session-status class="mb-5" :status="session('status')" />
<form wire:submit="sendPasswordResetLink" class="space-y-4">
<form wire:submit="sendPasswordResetLink">
<!-- Email Address -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input wire:model="email" id="email" class="block mt-1 w-full" type="email" name="email" required autofocus />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
<x-input-label for="email" value="E-Mail-Adresse" />
<x-text-input
wire:model="email"
id="email"
type="email"
name="email"
required
autofocus
autocomplete="username"
placeholder="name@schule.de"
/>
<x-input-error :messages="$errors->get('email')" />
</div>
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Email Password Reset Link') }}
<x-primary-button class="w-full" wire:loading.attr="disabled">
<span wire:loading.remove wire:target="sendPasswordResetLink">
<x-heroicon-o-envelope class="w-4 h-4" />
Reset-Link senden
</span>
<span wire:loading wire:target="sendPasswordResetLink" class="flex items-center gap-2">
<svg class="animate-spin w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
Wird gesendet…
</span>
</x-primary-button>
</div>
</form>
<div class="mt-5 text-center">
<a href="{{ route('login') }}" wire:navigate class="text-sm text-[--color-accent] hover:underline font-medium">
Zurück zur Anmeldung
</a>
</div>
</div>

View File

@ -9,63 +9,120 @@ new #[Layout('layouts.guest')] class extends Component
{
public LoginForm $form;
/**
* Handle an incoming authentication request.
*/
public bool $showPassword = false;
public function login(): void
{
$this->validate();
$this->form->authenticate();
Session::regenerate();
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
}
}; ?>
<div>
<!-- Session Status -->
<x-auth-session-status class="mb-4" :status="session('status')" />
<div class="mb-6">
<h1 class="text-2xl font-bold tracking-tight text-[--color-text-primary]">Willkommen zurück</h1>
<p class="text-sm text-[--color-text-muted] mt-1">Melde dich mit deinem Konto an</p>
</div>
<x-auth-session-status class="mb-5" :status="session('status')" />
<form wire:submit="login" class="space-y-4">
<form wire:submit="login">
<!-- Email Address -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input wire:model="form.email" id="email" class="block mt-1 w-full" type="email" name="email" required autofocus autocomplete="username" />
<x-input-error :messages="$errors->get('form.email')" class="mt-2" />
<x-input-label for="email" value="E-Mail-Adresse" />
<x-text-input
wire:model="form.email"
id="email"
type="email"
name="email"
required
autofocus
autocomplete="username"
placeholder="name@schule.de"
/>
<x-input-error :messages="$errors->get('form.email')" />
</div>
<!-- Password -->
<div class="mt-4">
<x-input-label for="password" :value="__('Password')" />
<x-text-input wire:model="form.password" id="password" class="block mt-1 w-full"
type="password"
<div>
<x-input-label for="password" value="Passwort" />
<div class="relative">
<x-text-input
wire:model="form.password"
id="password"
:type="$showPassword ? 'text' : 'password'"
name="password"
required autocomplete="current-password" />
<x-input-error :messages="$errors->get('form.password')" class="mt-2" />
required
autocomplete="current-password"
class="pr-10"
/>
<button
type="button"
wire:click="$set('showPassword', !$showPassword)"
class="absolute right-3 top-1/2 -translate-y-1/2 text-[--color-text-muted] hover:text-[--color-text-secondary] transition-colors duration-150 cursor-pointer"
aria-label="Passwort anzeigen / verbergen"
>
@if($showPassword)
<x-heroicon-o-eye-slash class="w-4 h-4" />
@else
<x-heroicon-o-eye class="w-4 h-4" />
@endif
</button>
</div>
<x-input-error :messages="$errors->get('form.password')" />
</div>
<!-- Remember Me -->
<div class="block mt-4">
<label for="remember" class="inline-flex items-center">
<input wire:model="form.remember" id="remember" 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>
<div class="flex items-center justify-between pt-1">
<label class="flex items-center gap-2 cursor-pointer select-none">
<input
wire:model="form.remember"
id="remember"
type="checkbox"
class="w-4 h-4 rounded border-[--color-border-subtle] accent-[--color-accent] cursor-pointer"
>
<span class="text-sm text-[--color-text-secondary]">Angemeldet bleiben</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') }}" wire:navigate>
{{ __('Forgot your password?') }}
<a
href="{{ route('password.request') }}"
wire:navigate
class="text-sm text-[--color-accent] hover:underline font-medium"
>
Passwort vergessen?
</a>
@endif
<x-primary-button class="ms-3">
{{ __('Log in') }}
</x-primary-button>
</div>
<x-primary-button class="w-full mt-2" wire:loading.attr="disabled">
<span wire:loading.remove wire:target="login">
<x-heroicon-o-arrow-right-on-rectangle class="w-4 h-4" />
Anmelden
</span>
<span wire:loading wire:target="login" class="flex items-center gap-2">
<svg class="animate-spin w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
Wird angemeldet…
</span>
</x-primary-button>
</form>
@if(app()->isLocal() && env('ALLOW_QUICK_LOGIN', false))
<div class="mt-6 pt-5 border-t border-[--color-border-subtle]">
<p class="text-xs font-medium text-[--color-text-muted] uppercase tracking-widest mb-3">
Dev · Quick Login
</p>
<a
href="{{ route('dev.quick-login') }}"
class="inline-flex items-center gap-1.5 text-xs text-[--color-accent] hover:underline"
>
<x-heroicon-o-bolt class="w-3.5 h-3.5" />
Alle Seed-User anzeigen
</a>
</div>
@endif
</div>

View File

@ -18,19 +18,15 @@ new #[Layout('layouts.guest')] class extends Component
public string $password = '';
public string $password_confirmation = '';
/**
* Mount the component.
*/
public bool $showPassword = false;
public bool $showPasswordConfirmation = false;
public function mount(string $token): void
{
$this->token = $token;
$this->email = request()->string('email');
}
/**
* Reset the password for the given user.
*/
public function resetPassword(): void
{
$this->validate([
@ -39,9 +35,6 @@ new #[Layout('layouts.guest')] class extends Component
'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset(
$this->only('email', 'password', 'password_confirmation', 'token'),
function ($user) {
@ -49,57 +42,105 @@ new #[Layout('layouts.guest')] class extends Component
'password' => Hash::make($this->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
if ($status != Password::PASSWORD_RESET) {
$this->addError('email', __($status));
return;
}
Session::flash('status', __($status));
$this->redirectRoute('login', navigate: true);
}
}; ?>
<div>
<form wire:submit="resetPassword">
<!-- Email Address -->
<div class="mb-6">
<h1 class="text-2xl font-bold tracking-tight text-[--color-text-primary]">Neues Passwort</h1>
<p class="text-sm text-[--color-text-muted] mt-1">Wähle ein sicheres neues Passwort für dein Konto.</p>
</div>
<form wire:submit="resetPassword" class="space-y-4">
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input wire:model="email" id="email" class="block mt-1 w-full" type="email" name="email" required autofocus autocomplete="username" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
<x-input-label for="email" value="E-Mail-Adresse" />
<x-text-input
wire:model="email"
id="email"
type="email"
name="email"
required
autofocus
autocomplete="username"
/>
<x-input-error :messages="$errors->get('email')" />
</div>
<!-- Password -->
<div class="mt-4">
<x-input-label for="password" :value="__('Password')" />
<x-text-input wire:model="password" 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>
<x-input-label for="password" value="Neues Passwort" />
<div class="relative">
<x-text-input
wire:model="password"
id="password"
:type="$showPassword ? 'text' : 'password'"
name="password"
required
autocomplete="new-password"
class="pr-10"
/>
<button type="button" wire:click="$set('showPassword', !$showPassword)"
class="absolute right-3 top-1/2 -translate-y-1/2 text-[--color-text-muted] hover:text-[--color-text-secondary] transition-colors duration-150 cursor-pointer"
aria-label="Passwort anzeigen">
@if($showPassword)
<x-heroicon-o-eye-slash class="w-4 h-4" />
@else
<x-heroicon-o-eye class="w-4 h-4" />
@endif
</button>
</div>
<x-input-error :messages="$errors->get('password')" />
</div>
<!-- Confirm Password -->
<div class="mt-4">
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
<x-text-input wire:model="password_confirmation" 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>
<x-input-label for="password_confirmation" value="Passwort bestätigen" />
<div class="relative">
<x-text-input
wire:model="password_confirmation"
id="password_confirmation"
:type="$showPasswordConfirmation ? 'text' : 'password'"
name="password_confirmation"
required
autocomplete="new-password"
class="pr-10"
/>
<button type="button" wire:click="$set('showPasswordConfirmation', !$showPasswordConfirmation)"
class="absolute right-3 top-1/2 -translate-y-1/2 text-[--color-text-muted] hover:text-[--color-text-secondary] transition-colors duration-150 cursor-pointer"
aria-label="Passwort anzeigen">
@if($showPasswordConfirmation)
<x-heroicon-o-eye-slash class="w-4 h-4" />
@else
<x-heroicon-o-eye class="w-4 h-4" />
@endif
</button>
</div>
<x-input-error :messages="$errors->get('password_confirmation')" />
</div>
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Reset Password') }}
<x-primary-button class="w-full" wire:loading.attr="disabled">
<span wire:loading.remove wire:target="resetPassword">
<x-heroicon-o-lock-closed class="w-4 h-4" />
Passwort speichern
</span>
<span wire:loading wire:target="resetPassword" class="flex items-center gap-2">
<svg class="animate-spin w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
Wird gespeichert…
</span>
</x-primary-button>
</div>
</form>
</div>

View File

@ -8,51 +8,53 @@ use Livewire\Volt\Component;
new #[Layout('layouts.guest')] class extends Component
{
/**
* Send an email verification notification to the user.
*/
public function sendVerification(): void
{
if (Auth::user()->hasVerifiedEmail()) {
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
return;
}
Auth::user()->sendEmailVerificationNotification();
Session::flash('status', 'verification-link-sent');
}
/**
* Log the current user out of the application.
*/
public function logout(Logout $logout): void
{
$logout();
$this->redirect('/', navigate: true);
}
}; ?>
<div>
<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 class="mb-6">
<h1 class="text-2xl font-bold tracking-tight text-[--color-text-primary]">E-Mail bestätigen</h1>
<p class="text-sm text-[--color-text-muted] mt-1">
Bitte klicke auf den Link in der E-Mail, die wir dir gesendet haben.
Kein Link erhalten? Wir schicken dir einen neuen.
</p>
</div>
@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 class="flex items-center gap-2 text-sm font-medium text-[--color-success] bg-[oklch(0.96_0.05_155)] border border-[oklch(0.88_0.10_155)] rounded-lg px-4 py-3 mb-5">
<x-heroicon-s-check-circle class="w-4 h-4 shrink-0" />
Neuer Bestätigungslink wurde gesendet.
</div>
@endif
<div class="mt-4 flex items-center justify-between">
<x-primary-button wire:click="sendVerification">
{{ __('Resend Verification Email') }}
<div class="space-y-3">
<x-primary-button class="w-full" wire:click="sendVerification" wire:loading.attr="disabled">
<x-heroicon-o-envelope class="w-4 h-4" />
Bestätigungslink erneut senden
</x-primary-button>
<button wire:click="logout" 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
wire:click="logout"
type="button"
class="w-full flex items-center justify-center gap-2 px-5 py-2.5 text-sm font-medium text-[--color-text-secondary] hover:text-[--color-text-primary] border border-[--color-border-subtle] rounded-lg hover:bg-[--color-bg-base] transition-all duration-150 cursor-pointer"
>
<x-heroicon-o-arrow-left-on-rectangle class="w-4 h-4" />
Abmelden
</button>
</div>
</div>

View File

@ -7,7 +7,7 @@ export default defineConfig({
plugins: [
tailwindcss(),
laravel({
input: ['resources/css/app.css', 'resources/js/app.js', 'resources/js/auth.js'],
input: ['resources/css/app.css', 'resources/js/auth.js', 'resources/js/app.js', 'resources/js/touch.js'],
refresh: true,
}),
VitePWA({
@ -34,4 +34,15 @@ export default defineConfig({
},
}),
],
server: {
host: '0.0.0.0',
port: 5173,
watch: { ignored: ['**/storage/framework/views/**'] },
hmr: {
host: 'app.dev.lernschiff.com',
protocol: 'wss',
clientPort: 443,
},
cors: true,
},
});