api-server gets /create-acc-data and /create-user-data POST routes that INSERT into the respective tables with required-field validation. Frontend adds an 'Add' button next to Refresh in each table head; opens a native <dialog> form with all fields. Inputs use 16px font on phone (sm:text-[13px] desktop) so iOS doesn't auto-zoom. A small form-dialog-shell helper centralizes the modal chrome, field label, and input class so create-account-dialog and create-user-dialog stay focused on their fields and validation.
110 lines
2.8 KiB
TypeScript
110 lines
2.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState, useTransition } from "react";
|
|
import { createAccount } from "@/app/actions";
|
|
import FormDialogShell, { Field, inputClass } from "./form-dialog-shell";
|
|
|
|
type Props = {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
prefixPattern?: string;
|
|
};
|
|
|
|
export default function CreateAccountDialog({ open, onClose, prefixPattern }: Props) {
|
|
const [pending, startTransition] = useTransition();
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [username, setUsername] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [status, setStatus] = useState("");
|
|
const [link, setLink] = useState("");
|
|
|
|
// Reset on open.
|
|
useEffect(() => {
|
|
if (open) {
|
|
setUsername("");
|
|
setPassword("");
|
|
setStatus("");
|
|
setLink("");
|
|
setError(null);
|
|
}
|
|
}, [open]);
|
|
|
|
function submit() {
|
|
if (!username.trim() || !password) {
|
|
setError("Username and password are required");
|
|
return;
|
|
}
|
|
setError(null);
|
|
startTransition(async () => {
|
|
const result = await createAccount({
|
|
username: username.trim(),
|
|
password,
|
|
status,
|
|
link,
|
|
});
|
|
if (result.ok) {
|
|
onClose();
|
|
} else {
|
|
setError(result.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
return (
|
|
<FormDialogShell
|
|
open={open}
|
|
onCancel={onClose}
|
|
onSubmit={submit}
|
|
title="New account"
|
|
submitLabel="Create"
|
|
pending={pending}
|
|
error={error}
|
|
>
|
|
<Field label="Username" required hint={prefixPattern ? `Suggested prefix: ${prefixPattern}` : undefined}>
|
|
<input
|
|
value={username}
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
disabled={pending}
|
|
autoFocus
|
|
autoComplete="off"
|
|
spellCheck={false}
|
|
placeholder={prefixPattern ? `${prefixPattern}1234` : "username"}
|
|
className={inputClass}
|
|
/>
|
|
</Field>
|
|
<Field label="Password" required>
|
|
<input
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
disabled={pending}
|
|
autoComplete="off"
|
|
spellCheck={false}
|
|
className={inputClass}
|
|
/>
|
|
</Field>
|
|
<Field label="Status" hint="Empty | wait | done">
|
|
<input
|
|
value={status}
|
|
onChange={(e) => setStatus(e.target.value)}
|
|
disabled={pending}
|
|
autoComplete="off"
|
|
spellCheck={false}
|
|
placeholder="(leave blank for available)"
|
|
className={inputClass}
|
|
/>
|
|
</Field>
|
|
<Field label="Link">
|
|
<input
|
|
value={link}
|
|
onChange={(e) => setLink(e.target.value)}
|
|
disabled={pending}
|
|
autoComplete="off"
|
|
spellCheck={false}
|
|
placeholder="https://..."
|
|
className={inputClass}
|
|
/>
|
|
</Field>
|
|
</FormDialogShell>
|
|
);
|
|
}
|