cm_bot_v2/web/components/create-user-dialog.tsx
yiekheng eebbcb3db2 feat(web): success toast on confirmed create/delete
Adds a small top-centered <Toast> that fires only when the Server
Action returns { ok: true } (i.e., the DB write actually succeeded).
Auto-dismisses after 3s.

Wires both create dialogs (CreateAccountDialog, CreateUserDialog) with
an onSuccess callback that the table parent uses to push the toast,
and the delete confirm-flow does the same. Inline-edit success stays
quiet (no toast) — only add/delete trigger it, per the requested
scope.
2026-05-02 21:20:25 +08:00

108 lines
2.8 KiB
TypeScript

"use client";
import { useEffect, useState, useTransition } from "react";
import { createUser } from "@/app/actions";
import FormDialogShell, { Field, inputClass } from "./form-dialog-shell";
type Props = {
open: boolean;
onClose: () => void;
onSuccess?: (fUsername: string) => void;
};
export default function CreateUserDialog({ open, onClose, onSuccess }: Props) {
const [pending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const [fUsername, setFUsername] = useState("");
const [fPassword, setFPassword] = useState("");
const [tUsername, setTUsername] = useState("");
const [tPassword, setTPassword] = useState("");
useEffect(() => {
if (open) {
setFUsername("");
setFPassword("");
setTUsername("");
setTPassword("");
setError(null);
}
}, [open]);
function submit() {
if (!fUsername.trim() || !fPassword || !tUsername.trim() || !tPassword) {
setError("All fields are required");
return;
}
setError(null);
const trimmedFUsername = fUsername.trim();
startTransition(async () => {
const result = await createUser({
f_username: trimmedFUsername,
f_password: fPassword,
t_username: tUsername.trim(),
t_password: tPassword,
});
if (result.ok) {
onSuccess?.(trimmedFUsername);
onClose();
} else {
setError(result.error);
}
});
}
return (
<FormDialogShell
open={open}
onCancel={onClose}
onSubmit={submit}
title="New user pairing"
submitLabel="Create"
pending={pending}
error={error}
>
<Field label="From username" required>
<input
value={fUsername}
onChange={(e) => setFUsername(e.target.value)}
disabled={pending}
autoFocus
autoComplete="off"
spellCheck={false}
className={inputClass}
/>
</Field>
<Field label="From password" required>
<input
value={fPassword}
onChange={(e) => setFPassword(e.target.value)}
disabled={pending}
autoComplete="off"
spellCheck={false}
className={inputClass}
/>
</Field>
<Field label="To username" required>
<input
value={tUsername}
onChange={(e) => setTUsername(e.target.value)}
disabled={pending}
autoComplete="off"
spellCheck={false}
className={inputClass}
/>
</Field>
<Field label="To password (security PIN)" required>
<input
value={tPassword}
onChange={(e) => setTPassword(e.target.value)}
disabled={pending}
autoComplete="off"
spellCheck={false}
className={inputClass}
/>
</Field>
</FormDialogShell>
);
}