feat(admin): phase 8 — unsaved-changes guard on all detail panels
Add beforeNavigate + isDirty tracking to users/[id], users/new, groups/[id], groups/new, and tags/[id] edit panels. When a user navigates away with unsaved changes, the navigation is cancelled and an inline amber warning banner appears with a Discard button that resumes navigation. Saving successfully clears the dirty flag. Add i18n key admin_unsaved_warning (de/en/es). Add spec files for groups/[id] and tags/[id] panels. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { beforeNavigate, goto } from '$app/navigation';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
let isDirty = $state(false);
|
||||
let showUnsavedWarning = $state(false);
|
||||
let discardTarget = $state<string | null>(null);
|
||||
|
||||
beforeNavigate(({ cancel, to }) => {
|
||||
if (isDirty) {
|
||||
cancel();
|
||||
showUnsavedWarning = true;
|
||||
discardTarget = to?.url.href ?? null;
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (form?.success) {
|
||||
isDirty = false;
|
||||
showUnsavedWarning = false;
|
||||
}
|
||||
});
|
||||
|
||||
const STANDARD_PERMISSIONS: { value: string; label: string }[] = [
|
||||
{ value: 'WRITE_ALL', label: 'Lesen & Schreiben' }
|
||||
];
|
||||
@@ -43,6 +63,24 @@ const ADMIN_PERMISSIONS: { value: string; label: string }[] = [
|
||||
|
||||
<!-- Scrollable body -->
|
||||
<div class="flex-1 overflow-y-auto px-5 py-5">
|
||||
{#if showUnsavedWarning}
|
||||
<div
|
||||
class="mb-5 flex items-center justify-between rounded border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-300"
|
||||
>
|
||||
<span>{m.admin_unsaved_warning()}</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
isDirty = false;
|
||||
showUnsavedWarning = false;
|
||||
if (discardTarget) goto(discardTarget);
|
||||
}}
|
||||
class="ml-4 shrink-0 font-sans text-xs font-bold tracking-widest text-amber-800 uppercase hover:text-amber-900 dark:text-amber-300"
|
||||
>
|
||||
{m.person_discard_changes()}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if form?.success}
|
||||
<div
|
||||
class="mb-5 rounded border border-green-200 bg-green-50 p-3 text-sm text-green-700 dark:border-green-800 dark:bg-green-950/40 dark:text-green-400"
|
||||
@@ -58,7 +96,16 @@ const ADMIN_PERMISSIONS: { value: string; label: string }[] = [
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form id="edit-group-form" method="POST" action="?/update" use:enhance>
|
||||
<form
|
||||
id="edit-group-form"
|
||||
method="POST"
|
||||
action="?/update"
|
||||
use:enhance
|
||||
oninput={() => {
|
||||
isDirty = true;
|
||||
showUnsavedWarning = false;
|
||||
}}
|
||||
>
|
||||
<!-- Group name card -->
|
||||
<div class="mb-5 rounded-sm border border-line bg-surface p-5 shadow-sm">
|
||||
<h3 class="mb-4 text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||
|
||||
Reference in New Issue
Block a user