QR: add type-to-confirm delete on the QR code page
Deploy Ladill QR Plus / deploy (push) Successful in 59s
Deploy Ladill QR Plus / deploy (push) Successful in 59s
Users asked to delete codes they no longer need. Add a destroy route + controller action (authorized via QrCodePolicy::delete) and a danger-zone delete on the show page behind a confirmation modal that requires typing the code's label — deliberately not a one-click delete since it permanently breaks the printed code and removes its scan history. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c4e2c75324
commit
46d0ef8825
@@ -279,6 +279,20 @@ class QrCodeController extends Controller
|
|||||||
return back()->with('success', 'QR code updated.');
|
return back()->with('success', 'QR code updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function destroy(Request $request, QrCode $qrCode): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('delete', $qrCode);
|
||||||
|
|
||||||
|
// Remove the code and its scan history. Deletion is gated behind a
|
||||||
|
// type-to-confirm dialog in the UI — this is intentionally permanent.
|
||||||
|
$qrCode->scans()->delete();
|
||||||
|
$qrCode->delete();
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('user.qr-codes.index')
|
||||||
|
->with('success', 'QR code deleted.');
|
||||||
|
}
|
||||||
|
|
||||||
public function stylePreview(Request $request): Response
|
public function stylePreview(Request $request): Response
|
||||||
{
|
{
|
||||||
$validated = $request->validate(array_merge($this->styleRules(), [
|
$validated = $request->validate(array_merge($this->styleRules(), [
|
||||||
|
|||||||
@@ -339,5 +339,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Danger zone: delete this QR code (type-to-confirm, not a one-click delete) --}}
|
||||||
|
<div class="rounded-2xl border border-red-200 bg-red-50/40 p-5 shadow-sm"
|
||||||
|
x-data="{ open: false, confirm: '' }">
|
||||||
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-semibold text-red-800">Delete this QR code</h3>
|
||||||
|
<p class="mt-1 text-sm text-red-700/80">The printed code stops working permanently and its scan history is removed. This can't be undone.</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" @click="open = true; confirm = ''"
|
||||||
|
class="inline-flex shrink-0 items-center justify-center rounded-xl border border-red-300 bg-white px-4 py-2 text-sm font-semibold text-red-700 transition hover:bg-red-100">
|
||||||
|
Delete code
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="open" x-cloak
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||||
|
@keydown.escape.window="open = false">
|
||||||
|
<div class="absolute inset-0 bg-slate-900/40" @click="open = false"></div>
|
||||||
|
<div class="relative w-full max-w-md rounded-2xl bg-white p-6 shadow-xl">
|
||||||
|
<h3 class="text-base font-bold text-slate-900">Delete “{{ $qrCode->label }}”?</h3>
|
||||||
|
<p class="mt-2 text-sm text-slate-600">
|
||||||
|
This permanently deletes the code and its scan history. Anyone who scans the printed code will get an error.
|
||||||
|
<span class="font-semibold">This cannot be undone.</span>
|
||||||
|
</p>
|
||||||
|
<label class="mt-4 block text-sm font-medium text-slate-700">
|
||||||
|
Type <span class="font-semibold text-slate-900">{{ $qrCode->label }}</span> to confirm
|
||||||
|
</label>
|
||||||
|
<input type="text" x-model="confirm" autocomplete="off" autofocus
|
||||||
|
class="mt-1.5 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm focus:border-red-400 focus:ring-2 focus:ring-red-100 focus:outline-none"
|
||||||
|
placeholder="{{ $qrCode->label }}">
|
||||||
|
<div class="mt-5 flex items-center justify-end gap-2">
|
||||||
|
<button type="button" @click="open = false"
|
||||||
|
class="rounded-xl px-4 py-2 text-sm font-semibold text-slate-600 hover:bg-slate-100">Cancel</button>
|
||||||
|
<form action="{{ route('user.qr-codes.destroy', $qrCode) }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit"
|
||||||
|
:disabled="confirm.trim() !== @js($qrCode->label)"
|
||||||
|
class="rounded-xl bg-red-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-red-700 disabled:cursor-not-allowed disabled:opacity-40">
|
||||||
|
Delete permanently
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</x-user-layout>
|
</x-user-layout>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
Route::post('/qr-codes/style-preview', [QrCodeController::class, 'stylePreview'])->name('user.qr-codes.style-preview');
|
Route::post('/qr-codes/style-preview', [QrCodeController::class, 'stylePreview'])->name('user.qr-codes.style-preview');
|
||||||
Route::get('/qr-codes/{qrCode}', [QrCodeController::class, 'show'])->name('user.qr-codes.show');
|
Route::get('/qr-codes/{qrCode}', [QrCodeController::class, 'show'])->name('user.qr-codes.show');
|
||||||
Route::patch('/qr-codes/{qrCode}', [QrCodeController::class, 'update'])->name('user.qr-codes.update');
|
Route::patch('/qr-codes/{qrCode}', [QrCodeController::class, 'update'])->name('user.qr-codes.update');
|
||||||
|
Route::delete('/qr-codes/{qrCode}', [QrCodeController::class, 'destroy'])->name('user.qr-codes.destroy');
|
||||||
Route::post('/qr-codes/{qrCode}/canonical-image', [QrCodeController::class, 'storeCanonicalImage'])->name('user.qr-codes.canonical-image');
|
Route::post('/qr-codes/{qrCode}/canonical-image', [QrCodeController::class, 'storeCanonicalImage'])->name('user.qr-codes.canonical-image');
|
||||||
Route::get('/qr-codes/{qrCode}/preview.png', [QrCodeController::class, 'preview'])->name('user.qr-codes.preview');
|
Route::get('/qr-codes/{qrCode}/preview.png', [QrCodeController::class, 'preview'])->name('user.qr-codes.preview');
|
||||||
Route::get('/qr-codes/{qrCode}/download/{format}', [QrCodeController::class, 'download'])->name('user.qr-codes.download')->whereIn('format', ['png', 'svg', 'pdf']);
|
Route::get('/qr-codes/{qrCode}/download/{format}', [QrCodeController::class, 'download'])->name('user.qr-codes.download')->whereIn('format', ['png', 'svg', 'pdf']);
|
||||||
|
|||||||
Reference in New Issue
Block a user