clusev/resources/views/livewire/modals/recovery-codes.blade.php

55 lines
3.0 KiB
PHP

<div class="p-5 sm:p-6" x-data="{ codes: [] }" @reveal-codes.window="codes = $event.detail.codes" x-cloak>
<div class="flex items-start gap-3.5">
<span class="grid h-10 w-10 shrink-0 place-items-center rounded-md border border-accent/30 bg-accent/10 text-accent-text">
<x-icon name="shield" class="h-5 w-5" />
</span>
<div class="min-w-0 flex-1">
<h2 class="font-display text-base font-semibold text-ink">{{ __('auth.recovery_heading') }}</h2>
<p class="mt-1 text-sm leading-relaxed text-ink-2">{{ __('auth.recovery_subtitle') }}</p>
</div>
</div>
{{-- Revealed panel: shown only once codes arrive via the transient `reveal-codes` event.
The codes live in Alpine JS memory (x-for) and are never in the server HTML/snapshot. --}}
<div x-show="codes.length">
<div class="mt-4 flex items-start gap-2.5 rounded-md border border-warning/25 bg-warning/10 px-4 py-3">
<x-icon name="alert" class="mt-0.5 h-4 w-4 shrink-0 text-warning" />
<p class="text-sm text-ink-2">{{ __('auth.recovery_warning') }}</p>
</div>
<div class="mt-4 grid grid-cols-1 gap-2 rounded-md border border-line bg-inset p-4 sm:grid-cols-2">
<template x-for="c in codes" :key="c">
<span class="font-mono text-sm tracking-wide text-ink" x-text="c"></span>
</template>
</div>
<div class="mt-6 flex flex-wrap items-center justify-between gap-2">
{{-- POST + @csrf: consuming the one-time grant is a state change, so a cross-site
request cannot burn the grant. Behaves identically to a download link. --}}
<form method="POST" action="{{ route('two-factor.recovery.download') }}">
@csrf
<x-btn type="submit" variant="secondary">
<x-icon name="folder" class="h-3.5 w-3.5" /> {{ __('auth.recovery_download') }}
</x-btn>
</form>
<x-btn variant="primary" wire:click="$dispatch('closeModal')">{{ __('common.close') }}</x-btn>
</div>
</div>
{{-- Hidden notice: the default state (no codes in memory) manage view, or any replayed
snapshot, which fires no event and therefore never populates `codes`. --}}
<div x-show="!codes.length">
<div class="mt-4 flex items-start gap-2.5 rounded-md border border-line bg-inset px-4 py-3">
<x-icon name="shield" class="mt-0.5 h-4 w-4 shrink-0 text-ink-3" />
<p class="text-sm text-ink-2">{{ __('auth.recovery_hidden_notice') }}</p>
</div>
<div class="mt-6 flex flex-wrap items-center justify-between gap-2">
<x-btn variant="secondary" wire:click="regenerate" wire:target="regenerate" wire:loading.attr="disabled">
<x-icon name="rotate" class="h-3.5 w-3.5" /> {{ __('auth.recovery_regenerate') }}
</x-btn>
<x-btn variant="primary" wire:click="$dispatch('closeModal')">{{ __('common.close') }}</x-btn>
</div>
</div>
</div>