Fix admin modals replaying old action results after create or edit
This commit is contained in:
@@ -38,6 +38,18 @@
|
||||
let lastCreateForm = $state<ActionResult>(null);
|
||||
let lastReplaceForm = $state<ActionResult>(null);
|
||||
|
||||
let createTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
let replaceTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function clearTimer(timer: 'create' | 'replace') {
|
||||
const current = timer === 'create' ? createTimer : replaceTimer;
|
||||
if (current) {
|
||||
clearTimeout(current);
|
||||
if (timer === 'create') createTimer = undefined;
|
||||
else replaceTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!form) return;
|
||||
|
||||
@@ -48,9 +60,10 @@
|
||||
email = '';
|
||||
createError = '';
|
||||
createSuccess = 'License key created and emailed';
|
||||
setTimeout(() => {
|
||||
createTimer = setTimeout(() => {
|
||||
createOpen = false;
|
||||
createSuccess = '';
|
||||
createTimer = undefined;
|
||||
}, 1200);
|
||||
} else {
|
||||
createSuccess = '';
|
||||
@@ -62,9 +75,10 @@
|
||||
if (form.success) {
|
||||
replaceError = '';
|
||||
replaceSuccess = 'License key replaced';
|
||||
setTimeout(() => {
|
||||
replaceTimer = setTimeout(() => {
|
||||
replaceOpen = false;
|
||||
replaceSuccess = '';
|
||||
replaceTimer = undefined;
|
||||
}, 1200);
|
||||
} else {
|
||||
replaceSuccess = '';
|
||||
@@ -74,25 +88,30 @@
|
||||
});
|
||||
|
||||
function openCreate() {
|
||||
lastCreateForm = null;
|
||||
clearTimer('create');
|
||||
lastCreateForm = form;
|
||||
email = '';
|
||||
createError = '';
|
||||
createSuccess = '';
|
||||
createOpen = true;
|
||||
}
|
||||
|
||||
function openReplace(record: Record<string, unknown>) {
|
||||
clearTimer('replace');
|
||||
replaceRecord = record;
|
||||
lastReplaceForm = null;
|
||||
lastReplaceForm = form;
|
||||
replaceError = '';
|
||||
replaceSuccess = '';
|
||||
replaceOpen = true;
|
||||
}
|
||||
|
||||
function closeCreate() {
|
||||
clearTimer('create');
|
||||
createOpen = false;
|
||||
}
|
||||
|
||||
function closeReplace() {
|
||||
clearTimer('replace');
|
||||
replaceOpen = false;
|
||||
replaceRecord = null;
|
||||
}
|
||||
|
||||
@@ -26,15 +26,21 @@
|
||||
let successMessage = $state('');
|
||||
let lastForm = $state<ActionResult>(null);
|
||||
|
||||
$effect(() => {
|
||||
const rec = editingRecord ?? {};
|
||||
name = String(rec.name ?? '');
|
||||
if (!editingRecord) {
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function clearCloseTimer() {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetModalState() {
|
||||
clearCloseTimer();
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!form || form === lastForm) return;
|
||||
@@ -44,9 +50,10 @@
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = editingRecord ? 'Category updated' : 'Category created';
|
||||
setTimeout(() => {
|
||||
closeTimer = setTimeout(() => {
|
||||
modalOpen = false;
|
||||
successMessage = '';
|
||||
closeTimer = undefined;
|
||||
}, 1200);
|
||||
} else {
|
||||
successMessage = '';
|
||||
@@ -63,18 +70,23 @@
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
resetModalState();
|
||||
editingRecord = null;
|
||||
lastForm = null;
|
||||
name = '';
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function openEdit(record: Record<string, unknown>) {
|
||||
resetModalState();
|
||||
editingRecord = record;
|
||||
lastForm = null;
|
||||
name = String(record.name ?? '');
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
clearCloseTimer();
|
||||
modalOpen = false;
|
||||
editingRecord = null;
|
||||
}
|
||||
|
||||
@@ -26,15 +26,21 @@
|
||||
let successMessage = $state('');
|
||||
let lastForm = $state<ActionResult>(null);
|
||||
|
||||
$effect(() => {
|
||||
const rec = editingRecord ?? {};
|
||||
name = String(rec.name ?? '');
|
||||
if (!editingRecord) {
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function clearCloseTimer() {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetModalState() {
|
||||
clearCloseTimer();
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!form || form === lastForm) return;
|
||||
@@ -44,9 +50,10 @@
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = editingRecord ? 'Group updated' : 'Group created';
|
||||
setTimeout(() => {
|
||||
closeTimer = setTimeout(() => {
|
||||
modalOpen = false;
|
||||
successMessage = '';
|
||||
closeTimer = undefined;
|
||||
}, 1200);
|
||||
} else {
|
||||
successMessage = '';
|
||||
@@ -63,18 +70,23 @@
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
resetModalState();
|
||||
editingRecord = null;
|
||||
lastForm = null;
|
||||
name = '';
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function openEdit(record: Record<string, unknown>) {
|
||||
resetModalState();
|
||||
editingRecord = record;
|
||||
lastForm = null;
|
||||
name = String(record.name ?? '');
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
clearCloseTimer();
|
||||
modalOpen = false;
|
||||
editingRecord = null;
|
||||
}
|
||||
|
||||
@@ -53,19 +53,21 @@
|
||||
|
||||
let contentKey = $derived(editingRecord?.id ?? 'new');
|
||||
|
||||
$effect(() => {
|
||||
const record = editingRecord ?? {};
|
||||
name = String(record.name ?? '');
|
||||
solfeggioFrequencyId = String(record.solfeggioFrequencyId ?? '');
|
||||
categoryId = String(record.categoryId ?? '');
|
||||
groupId = String(record.groupId ?? '');
|
||||
content = typeof record.content === 'string' ? record.content : '';
|
||||
if (!editingRecord) {
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function clearCloseTimer() {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetModalState() {
|
||||
clearCloseTimer();
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!form || form === lastForm) return;
|
||||
@@ -75,6 +77,11 @@
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = editingRecord ? 'Proclamation updated' : 'Proclamation created';
|
||||
closeTimer = setTimeout(() => {
|
||||
modalOpen = false;
|
||||
successMessage = '';
|
||||
closeTimer = undefined;
|
||||
}, 1200);
|
||||
} else {
|
||||
successMessage = '';
|
||||
formError = form.error ?? '';
|
||||
@@ -133,18 +140,31 @@
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
resetModalState();
|
||||
editingRecord = null;
|
||||
lastForm = null;
|
||||
name = '';
|
||||
solfeggioFrequencyId = '';
|
||||
categoryId = '';
|
||||
groupId = '';
|
||||
content = '';
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function openEdit(record: Record<string, unknown>) {
|
||||
resetModalState();
|
||||
editingRecord = record;
|
||||
lastForm = null;
|
||||
name = String(record.name ?? '');
|
||||
solfeggioFrequencyId = String(record.solfeggioFrequencyId ?? '');
|
||||
categoryId = String(record.categoryId ?? '');
|
||||
groupId = String(record.groupId ?? '');
|
||||
content = typeof record.content === 'string' ? record.content : '';
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
clearCloseTimer();
|
||||
modalOpen = false;
|
||||
editingRecord = null;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,17 @@
|
||||
let successMessage = $state('');
|
||||
let lastForm = $state<ActionResult>(null);
|
||||
|
||||
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function clearCloseTimer() {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
clearCloseTimer();
|
||||
if (open) {
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
@@ -37,11 +47,19 @@
|
||||
if (!form || form === lastForm) return;
|
||||
lastForm = form;
|
||||
|
||||
// Consume results even while closed so a stale result is never
|
||||
// replayed when the modal is next opened, but only show feedback
|
||||
// while this modal is actually open.
|
||||
if (!open) return;
|
||||
|
||||
if (form.success) {
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = 'Profile created';
|
||||
setTimeout(() => onClose?.(), 1200);
|
||||
closeTimer = setTimeout(() => {
|
||||
closeTimer = undefined;
|
||||
onClose?.();
|
||||
}, 1200);
|
||||
} else {
|
||||
successMessage = '';
|
||||
formError = form.error ?? '';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte';
|
||||
import { enhance } from '$app/forms';
|
||||
import AdminModal from '$lib/components/admin/AdminModal.svelte';
|
||||
import ProfileFields from './ProfileFields.svelte';
|
||||
@@ -43,7 +44,9 @@
|
||||
let addrShipping = $state(false);
|
||||
|
||||
let lastRecordId = $state<string | null>(null);
|
||||
let lastForm = $state<ActionResult>(null);
|
||||
// Capture the form result as it is when this modal mounts so a stale
|
||||
// result from a previous record is not replayed on open.
|
||||
let lastForm = $state<ActionResult>(untrack(() => form ?? null));
|
||||
|
||||
function resetAddressFields() {
|
||||
addrStreet = '';
|
||||
|
||||
@@ -29,16 +29,21 @@
|
||||
let successMessage = $state('');
|
||||
let lastForm = $state<ActionResult>(null);
|
||||
|
||||
$effect(() => {
|
||||
const rec = editingRecord ?? {};
|
||||
name = String(rec.name ?? '');
|
||||
value = String(rec.value ?? '');
|
||||
if (!editingRecord) {
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function clearCloseTimer() {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetModalState() {
|
||||
clearCloseTimer();
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!form || form === lastForm) return;
|
||||
@@ -48,9 +53,10 @@
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = editingRecord ? 'Frequency updated' : 'Frequency created';
|
||||
setTimeout(() => {
|
||||
closeTimer = setTimeout(() => {
|
||||
modalOpen = false;
|
||||
successMessage = '';
|
||||
closeTimer = undefined;
|
||||
}, 1200);
|
||||
} else {
|
||||
successMessage = '';
|
||||
@@ -60,18 +66,25 @@
|
||||
});
|
||||
|
||||
function openCreate() {
|
||||
resetModalState();
|
||||
editingRecord = null;
|
||||
lastForm = null;
|
||||
name = '';
|
||||
value = '';
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function openEdit(record: Record<string, unknown>) {
|
||||
resetModalState();
|
||||
editingRecord = record;
|
||||
lastForm = null;
|
||||
name = String(record.name ?? '');
|
||||
value = String(record.value ?? '');
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
clearCloseTimer();
|
||||
modalOpen = false;
|
||||
editingRecord = null;
|
||||
}
|
||||
|
||||
@@ -28,15 +28,21 @@
|
||||
let successMessage = $state('');
|
||||
let lastForm = $state<ActionResult>(null);
|
||||
|
||||
$effect(() => {
|
||||
const rec = editingRecord ?? {};
|
||||
name = String(rec.name ?? '');
|
||||
if (!editingRecord) {
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function clearCloseTimer() {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetModalState() {
|
||||
clearCloseTimer();
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!form || form === lastForm) return;
|
||||
@@ -46,9 +52,10 @@
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = editingRecord ? 'Role updated' : 'Role created';
|
||||
setTimeout(() => {
|
||||
closeTimer = setTimeout(() => {
|
||||
modalOpen = false;
|
||||
successMessage = '';
|
||||
closeTimer = undefined;
|
||||
}, 1200);
|
||||
} else {
|
||||
successMessage = '';
|
||||
@@ -58,18 +65,23 @@
|
||||
});
|
||||
|
||||
function openCreate() {
|
||||
resetModalState();
|
||||
editingRecord = null;
|
||||
lastForm = null;
|
||||
name = '';
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function openEdit(record: Record<string, unknown>) {
|
||||
resetModalState();
|
||||
editingRecord = record;
|
||||
lastForm = null;
|
||||
name = String(record.name ?? '');
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
clearCloseTimer();
|
||||
modalOpen = false;
|
||||
editingRecord = null;
|
||||
}
|
||||
|
||||
@@ -46,17 +46,21 @@
|
||||
let successMessage = $state('');
|
||||
let lastForm = $state<ActionResult>(null);
|
||||
|
||||
$effect(() => {
|
||||
const rec = editingRecord ?? {};
|
||||
name = String(rec.name ?? '');
|
||||
email = String(rec.email ?? '');
|
||||
roleId = String(rec.roleId ?? '');
|
||||
if (!editingRecord) {
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function clearCloseTimer() {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetModalState() {
|
||||
clearCloseTimer();
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!form || form === lastForm) return;
|
||||
@@ -66,9 +70,10 @@
|
||||
formError = '';
|
||||
fieldErrors = {};
|
||||
successMessage = editingRecord ? 'User updated' : 'User created';
|
||||
setTimeout(() => {
|
||||
closeTimer = setTimeout(() => {
|
||||
modalOpen = false;
|
||||
successMessage = '';
|
||||
closeTimer = undefined;
|
||||
}, 1200);
|
||||
} else {
|
||||
successMessage = '';
|
||||
@@ -78,18 +83,27 @@
|
||||
});
|
||||
|
||||
function openCreate() {
|
||||
resetModalState();
|
||||
editingRecord = null;
|
||||
lastForm = null;
|
||||
name = '';
|
||||
email = '';
|
||||
roleId = '';
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function openEdit(record: Record<string, unknown>) {
|
||||
resetModalState();
|
||||
editingRecord = record;
|
||||
lastForm = null;
|
||||
name = String(record.name ?? '');
|
||||
email = String(record.email ?? '');
|
||||
roleId = String(record.roleId ?? '');
|
||||
lastForm = form;
|
||||
modalOpen = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
clearCloseTimer();
|
||||
modalOpen = false;
|
||||
editingRecord = null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { page } from 'vitest/browser';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
vi.mock('$app/forms', () => ({
|
||||
enhance: () => () => {}
|
||||
}));
|
||||
|
||||
import Page from './+page.svelte';
|
||||
|
||||
const superAdmin = {
|
||||
id: 'admin-1',
|
||||
name: 'Super Admin',
|
||||
roleId: 1,
|
||||
roleName: 'SUPER'
|
||||
};
|
||||
|
||||
function makeData(overrides: Partial<PageProps['data']> = {}): PageProps['data'] {
|
||||
return {
|
||||
records: [],
|
||||
roleOptions: [{ id: 4, name: 'USER' }],
|
||||
adminUser: superAdmin,
|
||||
userRoleId: 1,
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
async function clickButton(name: string) {
|
||||
const loc = page.getByRole('button', { name });
|
||||
const el = (await loc.element()) as HTMLElement;
|
||||
el.click();
|
||||
}
|
||||
|
||||
describe('users admin page', () => {
|
||||
it('does not replay a stale success result when reopening the create modal', async () => {
|
||||
render(Page, {
|
||||
params: {},
|
||||
data: makeData(),
|
||||
form: { success: true, action: 'upsert' }
|
||||
});
|
||||
|
||||
await clickButton('New User');
|
||||
|
||||
await expect.element(page.getByRole('button', { name: 'Create User' })).toBeInTheDocument();
|
||||
expect(page.getByText('User created').elements()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not replay a stale success result when opening the edit modal', async () => {
|
||||
const record = { id: 'user-1', name: 'Jane Doe', email: 'jane@example.com', roleId: 4 };
|
||||
|
||||
render(Page, {
|
||||
params: {},
|
||||
data: makeData({ records: [record] }),
|
||||
form: { success: true, action: 'upsert' }
|
||||
});
|
||||
|
||||
const cell = (await page.getByRole('cell', { name: 'Jane Doe' }).element()) as HTMLElement;
|
||||
cell.click();
|
||||
|
||||
await expect.element(page.getByRole('button', { name: 'Save User' })).toBeInTheDocument();
|
||||
expect(page.getByText('User updated').elements()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user