Replaces maryUI and daisyUI with nonameweb/livewire-material: the Vibrant indigo scheme, a system/light/dark theme under sealshare-theme, one top app bar with the account menu, the upload drop zone and link-ready moments, M3 fields, dialogs instead of wire:confirm, snackbars instead of flashed messages, a sortable admin table, and the starter-kit cleanup. Docker builds assets after Composer; CI drops the Flux step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
93 lines
3.5 KiB
PHP
93 lines
3.5 KiB
PHP
<?php
|
|
|
|
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
|
|
use Livewire\Attributes\Locked;
|
|
use Livewire\Component;
|
|
|
|
new class extends Component {
|
|
#[Locked]
|
|
public array $recoveryCodes = [];
|
|
|
|
/**
|
|
* Mount the component.
|
|
*/
|
|
public function mount(): void
|
|
{
|
|
$this->loadRecoveryCodes();
|
|
}
|
|
|
|
/**
|
|
* Generate new recovery codes for the user.
|
|
*/
|
|
public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generateNewRecoveryCodes): void
|
|
{
|
|
$generateNewRecoveryCodes(auth()->user());
|
|
|
|
$this->loadRecoveryCodes();
|
|
}
|
|
|
|
/**
|
|
* Load the recovery codes for the user.
|
|
*/
|
|
private function loadRecoveryCodes(): void
|
|
{
|
|
$user = auth()->user();
|
|
|
|
if ($user->hasEnabledTwoFactorAuthentication() && $user->two_factor_recovery_codes) {
|
|
try {
|
|
$this->recoveryCodes = json_decode(decrypt($user->two_factor_recovery_codes), true);
|
|
} catch (Exception) {
|
|
$this->addError('recoveryCodes', 'Failed to load recovery codes');
|
|
|
|
$this->recoveryCodes = [];
|
|
}
|
|
}
|
|
}
|
|
}; ?>
|
|
|
|
<x-card variant="outlined" wire:cloak x-data="{ showRecoveryCodes: false }">
|
|
<div class="grid gap-4">
|
|
<div>
|
|
<div class="flex items-center gap-2">
|
|
<x-icon name="lock" class="size-5 text-on-surface-variant" />
|
|
<h3 class="type-title-md">{{ __('2FA Recovery Codes') }}</h3>
|
|
</div>
|
|
<p class="mt-1 type-body-md text-on-surface-variant">
|
|
{{ __('Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.') }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span x-show="! showRecoveryCodes" class="inline-flex">
|
|
<x-button icon="visibility" :label="__('View Recovery Codes')" variant="tonal" x-on:click="showRecoveryCodes = true" />
|
|
</span>
|
|
<span x-show="showRecoveryCodes" x-cloak class="inline-flex">
|
|
<x-button icon="visibility_off" :label="__('Hide Recovery Codes')" variant="tonal" x-on:click="showRecoveryCodes = false" />
|
|
</span>
|
|
|
|
@if (filled($recoveryCodes))
|
|
<span x-show="showRecoveryCodes" x-cloak class="inline-flex">
|
|
<x-button icon="refresh" :label="__('Regenerate Codes')" variant="outlined" wire:click="regenerateRecoveryCodes" />
|
|
</span>
|
|
@endif
|
|
</div>
|
|
|
|
<div x-show="showRecoveryCodes" x-cloak id="recovery-codes-section" class="grid gap-3">
|
|
@error('recoveryCodes')
|
|
<x-alert color="error">{{ $message }}</x-alert>
|
|
@enderror
|
|
|
|
@if (filled($recoveryCodes))
|
|
<div class="grid gap-1 rounded-corner-md bg-surface-container-highest p-4 font-mono type-body-md" role="list" aria-label="{{ __('Recovery codes') }}">
|
|
@foreach ($recoveryCodes as $code)
|
|
<div role="listitem" class="select-text" wire:loading.class="animate-pulse opacity-50">{{ $code }}</div>
|
|
@endforeach
|
|
</div>
|
|
<p class="type-body-sm text-on-surface-variant">
|
|
{{ __('Each recovery code can be used once to access your account and will be removed after use. If you need more, click Regenerate Codes above.') }}
|
|
</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</x-card>
|