refactor(web): pushToast store + ToastHost; 8 sites migrated (W3)
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user