feat(web): disable submit while saving + Save & create another + Cmd/Ctrl+Enter (#46)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 23:15:21 +02:00
parent ed0c13907c
commit 3900bc362c
7 changed files with 122 additions and 25 deletions
+40 -14
View File
@@ -54,7 +54,7 @@ export function ObjectForm({
}: {
mode: "create" | "edit";
defaults?: { core: ObjectCore; fields: Record<string, unknown> };
onSubmit: (values: ObjectFormValues) => void;
onSubmit: (values: ObjectFormValues, opts?: { createAnother?: boolean }) => Promise<boolean> | boolean;
onCancel: () => void;
formError?: string | null;
fieldErrorKey?: string | null;
@@ -76,7 +76,7 @@ export function ObjectForm({
},
});
const { register, handleSubmit, formState: { errors } } = form;
const { register, handleSubmit, formState: { errors, isSubmitting } } = form;
useEffect(() => {
if (fieldErrorKey) {
@@ -87,15 +87,21 @@ export function ObjectForm({
}
}, [fieldErrorKey, form, t]);
const submit = handleSubmit((data) => {
const fields = pruneFields(data.fields, localizedTextKeys, default_language);
const runSubmit = (createAnother: boolean) =>
handleSubmit(async (data) => {
const fields = pruneFields(data.fields, localizedTextKeys, default_language);
const values =
mode === "create"
? { core: data.core, visibility: data.visibility, fields }
: { core: data.core, fields };
const ok = await onSubmit(values, { createAnother });
if (ok && createAnother) {
form.reset({ core: EMPTY_CORE, visibility: "draft", fields: {} });
document.getElementById("object_number")?.focus();
}
});
onSubmit(
mode === "create"
? { core: data.core, visibility: data.visibility, fields }
: { core: data.core, fields },
);
});
const submit = runSubmit(false);
const coreField = (
key: keyof ObjectCore,
@@ -125,7 +131,16 @@ export function ObjectForm({
);
return (
<form onSubmit={submit} className="space-y-4 overflow-auto p-4">
<form
onSubmit={submit}
onKeyDown={(e) => {
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
e.preventDefault();
void submit();
}
}}
className="space-y-4 overflow-auto p-4"
>
{formError && (
<p role="alert" className="text-sm text-destructive">
{formError}
@@ -177,11 +192,22 @@ export function ObjectForm({
)}
<div className="flex gap-2 pt-2">
<Button type="submit">
{mode === "create" ? t("form.create") : t("form.save")}
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? t("form.saving") : mode === "create" ? t("form.create") : t("form.save")}
</Button>
<Button type="button" variant="ghost" onClick={onCancel}>
{mode === "create" && (
<Button
type="button"
variant="secondary"
disabled={isSubmitting}
onClick={() => void runSubmit(true)()}
>
{t("form.createAnother")}
</Button>
)}
<Button type="button" variant="ghost" disabled={isSubmitting} onClick={onCancel}>
{t("form.cancel")}
</Button>
</div>