@@ -0,0 +1,82 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@conusai-kit/ui/primitives"
|
||||
|
||||
// Adds a retention class. A class never changes once it exists: a new policy is a new class.
|
||||
|
||||
import type { ProjectSlugs } from "@conusai-kit/ui"
|
||||
import { describeError, useWrite } from "@conusai-kit/ui/data"
|
||||
import { SelectField, serviceNotice, usePageNavigation } from "@conusai-kit/ui/patterns"
|
||||
import { Alert, AlertDescription, Field, FieldDescription, FieldLabel, Input } from "@conusai-kit/ui/primitives"
|
||||
import { type FormEvent, type ReactElement, useState } from "react"
|
||||
import { RETENTION_BASES, type RetentionClassView } from "../contract/index.js"
|
||||
|
||||
export interface RetentionFormLabels {
|
||||
readonly heading: string
|
||||
readonly code: string
|
||||
readonly keepFor: string
|
||||
readonly keepForHint: string
|
||||
readonly from: string
|
||||
readonly submit: string
|
||||
readonly busy: string
|
||||
readonly bases: Readonly<Record<RetentionClassView["from"], string>>
|
||||
readonly failed: string
|
||||
/** The text of each business refusal by problem code (`businessRefusalNotices` of `@conusai-kit/business/refusals`). */
|
||||
readonly refusals: Readonly<Record<string, string>>
|
||||
}
|
||||
|
||||
export function RetentionForm({ slugs, labels }: { slugs: ProjectSlugs; labels: RetentionFormLabels }): ReactElement {
|
||||
const navigation = usePageNavigation()
|
||||
const create = useWrite<Record<string, unknown>, unknown>("archive.retention.create")
|
||||
const [requestId, setRequestId] = useState(() => crypto.randomUUID())
|
||||
const [state, setState] = useState<{ kind: "idle" | "busy" } | { kind: "failed"; detail: string }>({ kind: "idle" })
|
||||
|
||||
const submit = async (event: FormEvent<HTMLFormElement>): Promise<void> => {
|
||||
event.preventDefault()
|
||||
const form = new FormData(event.currentTarget)
|
||||
setState({ kind: "busy" })
|
||||
try {
|
||||
await create.mutate({
|
||||
...slugs,
|
||||
code: String(form.get("code") ?? "").trim(),
|
||||
keepFor: String(form.get("keepFor") ?? "").trim(),
|
||||
from: String(form.get("from") ?? "created"),
|
||||
requestId,
|
||||
})
|
||||
await navigation.refresh()
|
||||
setRequestId(crypto.randomUUID())
|
||||
setState({ kind: "idle" })
|
||||
} catch (error) {
|
||||
setState({ kind: "failed", detail: serviceNotice(error, labels.refusals)?.text ?? describeError(error) })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form key={requestId} onSubmit={submit} aria-busy={state.kind === "busy"}>
|
||||
<h2>{labels.heading}</h2>
|
||||
<Field>
|
||||
<FieldLabel>{labels.code}</FieldLabel>
|
||||
<Input name="code" type="text" required maxLength={64} pattern="[A-Za-z0-9_\-]+" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{labels.keepFor}</FieldLabel>
|
||||
<Input name="keepFor" type="text" required maxLength={64} />
|
||||
<FieldDescription>{labels.keepForHint}</FieldDescription>
|
||||
</Field>
|
||||
<SelectField
|
||||
name="from"
|
||||
label={labels.from}
|
||||
defaultValue="created"
|
||||
options={RETENTION_BASES.map((base) => ({ value: base, label: labels.bases[base] }))}
|
||||
/>
|
||||
<Button type="submit" disabled={state.kind === "busy"}>
|
||||
{state.kind === "busy" ? labels.busy : labels.submit}
|
||||
</Button>
|
||||
{state.kind === "failed" ? (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>{labels.failed.replace("{detail}", state.detail)}</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user