refactor(web): pushToast store + ToastHost; 8 sites migrated (W3)

This commit is contained in:
2026-05-08 05:45:34 -04:00
parent 970752a153
commit 617477b702
12 changed files with 229 additions and 223 deletions
+22 -42
View File
@@ -17,6 +17,7 @@
type CreateUserInput
} from '$lib/api/admin';
import { errCode } from '$lib/api/errors';
import { pushToast } from '$lib/stores/toast.svelte';
import Modal from '$lib/components/Modal.svelte';
const client = useQueryClient();
@@ -29,18 +30,8 @@
const invitesQuery = $derived($invitesStore);
const invites = $derived((invitesQuery.data ?? []) as AdminInvite[]);
let toast = $state<string | null>(null);
let toastTimer: ReturnType<typeof setTimeout> | null = null;
let saving = $state(false);
function showToast(msg: string) {
if (toastTimer) clearTimeout(toastTimer);
toast = msg;
toastTimer = setTimeout(() => {
toast = null;
}, 5000);
}
// Modal state
let showCreateModal = $state(false);
let createForm = $state<CreateUserInput & { confirmPassword: string }>({
@@ -62,13 +53,13 @@
try {
await updateUserAdmin(u.id, !u.is_admin);
await client.invalidateQueries({ queryKey: qk.adminUsers() });
showToast(u.is_admin ? `${u.username} is no longer an admin.` : `${u.username} is now an admin.`);
pushToast(u.is_admin ? `${u.username} is no longer an admin.` : `${u.username} is now an admin.`);
} catch (e: unknown) {
const code = errCode(e);
if (code === 'last_admin') {
showToast(`Can't remove the last admin — promote someone else first.`);
pushToast(`Can't remove the last admin — promote someone else first.`, 'error');
} else {
showToast(`Action failed: ${code}`);
pushToast(`Action failed: ${code}`, 'error');
}
} finally {
saving = false;
@@ -78,7 +69,7 @@
async function onSubmitCreate(e: SubmitEvent) {
e.preventDefault();
if (createForm.password !== createForm.confirmPassword) {
showToast('Passwords do not match.');
pushToast('Passwords do not match.', 'error');
return;
}
saving = true;
@@ -92,9 +83,9 @@
await client.invalidateQueries({ queryKey: qk.adminUsers() });
showCreateModal = false;
createForm = { username: '', password: '', confirmPassword: '', display_name: '', is_admin: false };
showToast('User created.');
pushToast('User created.');
} catch (e: unknown) {
showToast(createUserErrorMessage(errCode(e)));
pushToast(createUserErrorMessage(errCode(e)), 'error');
} finally {
saving = false;
}
@@ -115,14 +106,14 @@
try {
await deleteUser(confirmDeleteTarget.id);
await client.invalidateQueries({ queryKey: qk.adminUsers() });
showToast(`Deleted ${confirmDeleteTarget.username}.`);
pushToast(`Deleted ${confirmDeleteTarget.username}.`);
confirmDeleteTarget = null;
} catch (e: unknown) {
const code = errCode(e);
if (code === 'last_admin') {
showToast(`Can't delete the last admin — promote someone else first.`);
pushToast(`Can't delete the last admin — promote someone else first.`, 'error');
} else {
showToast(`Delete failed: ${code}`);
pushToast(`Delete failed: ${code}`, 'error');
}
} finally {
saving = false;
@@ -133,22 +124,22 @@
e.preventDefault();
if (!resetPasswordTarget) return;
if (resetPasswordValue !== resetPasswordConfirm) {
showToast('Passwords do not match.');
pushToast('Passwords do not match.', 'error');
return;
}
saving = true;
try {
await resetUserPassword(resetPasswordTarget.id, resetPasswordValue);
showToast(`Password reset for ${resetPasswordTarget.username}.`);
pushToast(`Password reset for ${resetPasswordTarget.username}.`);
resetPasswordTarget = null;
resetPasswordValue = '';
resetPasswordConfirm = '';
} catch (e: unknown) {
const code = errCode(e);
if (code === 'password_too_short') {
showToast('Password must be at least 8 characters.');
pushToast('Password must be at least 8 characters.', 'error');
} else {
showToast(`Reset failed: ${code}`);
pushToast(`Reset failed: ${code}`, 'error');
}
} finally {
saving = false;
@@ -160,11 +151,11 @@
try {
await updateUserAutoApprove(u.id, !u.auto_approve_requests);
await client.invalidateQueries({ queryKey: qk.adminUsers() });
showToast(u.auto_approve_requests
pushToast(u.auto_approve_requests
? `Auto-approve disabled for ${u.username}.`
: `Auto-approve enabled for ${u.username}.`);
} catch (e: unknown) {
showToast(`Toggle failed: ${errCode(e)}`);
pushToast(`Toggle failed: ${errCode(e)}`, 'error');
} finally {
saving = false;
}
@@ -175,9 +166,9 @@
try {
await createInvite();
await client.invalidateQueries({ queryKey: qk.adminInvites() });
showToast('Invite generated. Copy the token from the list below.');
pushToast('Invite generated. Copy the token from the list below.');
} catch (e: unknown) {
showToast(`Generate failed: ${errCode(e)}`);
pushToast(`Generate failed: ${errCode(e)}`, 'error');
} finally {
saving = false;
}
@@ -188,9 +179,9 @@
try {
await deleteInvite(invite.token);
await client.invalidateQueries({ queryKey: qk.adminInvites() });
showToast('Invite revoked.');
pushToast('Invite revoked.');
} catch (e: unknown) {
showToast(`Revoke failed: ${errCode(e)}`);
pushToast(`Revoke failed: ${errCode(e)}`, 'error');
} finally {
saving = false;
}
@@ -199,9 +190,9 @@
async function copyToken(token: string) {
try {
await navigator.clipboard.writeText(token);
showToast('Token copied to clipboard.');
pushToast('Token copied to clipboard.');
} catch {
showToast('Copy failed — select the token and copy manually.');
pushToast('Copy failed — select the token and copy manually.', 'error');
}
}
@@ -362,17 +353,6 @@
</section>
</div>
{#if toast}
<div
role="status"
aria-live="polite"
class="fixed bottom-4 right-4 z-50 max-w-sm rounded-md border border-border bg-surface px-4 py-3 text-sm text-text-primary shadow-lg"
data-testid="toast"
>
{toast}
</div>
{/if}
<Modal
title="New user"
open={showCreateModal}