diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/admin/AccountForm.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/admin/AccountForm.tsx | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx new file mode 100644 index 000000000..9ca0323a1 --- /dev/null +++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx @@ -0,0 +1,219 @@ +import { VNode,h } from "preact"; +import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js"; +import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js"; +import { useState } from "preact/hooks"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { parsePaytoUri } from "@gnu-taler/taler-util"; + +const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/; +const EMAIL_REGEX = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; +const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/; + +/** + * Create valid account object to update or create + * Take template as initial values for the form + * Purpose indicate if all field al read only (show), part of them (update) + * or none (create) + * @param param0 + * @returns + */ +export function AccountForm({ + template, + purpose, + onChange, + }: { + template: SandboxBackend.Circuit.CircuitAccountData | undefined; + onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => void; + purpose: "create" | "update" | "show"; + }): VNode { + const initial = initializeFromTemplate(template); + const [form, setForm] = useState(initial); + const [errors, setErrors] = useState< + RecursivePartial<typeof initial> | undefined + >(undefined); + const { i18n } = useTranslationContext(); + + function updateForm(newForm: typeof initial): void { + const parsed = !newForm.cashout_address + ? undefined + : parsePaytoUri(newForm.cashout_address); + + const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({ + cashout_address: !newForm.cashout_address + ? i18n.str`required` + : !parsed + ? i18n.str`does not follow the pattern` + : !parsed.isKnown || parsed.targetType !== "iban" + ? i18n.str`only "IBAN" target are supported` + : !IBAN_REGEX.test(parsed.iban) + ? i18n.str`IBAN should have just uppercased letters and numbers` + : validateIBAN(parsed.iban, i18n), + contact_data: undefinedIfEmpty({ + email: !newForm.contact_data?.email + ? i18n.str`required` + : !EMAIL_REGEX.test(newForm.contact_data.email) + ? i18n.str`it should be an email` + : undefined, + phone: !newForm.contact_data?.phone + ? i18n.str`required` + : !newForm.contact_data.phone.startsWith("+") + ? i18n.str`should start with +` + : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone) + ? i18n.str`phone number can't have other than numbers` + : undefined, + }), + iban: !newForm.iban + ? undefined //optional field + : !IBAN_REGEX.test(newForm.iban) + ? i18n.str`IBAN should have just uppercased letters and numbers` + : validateIBAN(newForm.iban, i18n), + name: !newForm.name ? i18n.str`required` : undefined, + username: !newForm.username ? i18n.str`required` : undefined, + }); + setErrors(errors); + setForm(newForm); + onChange(errors === undefined ? (newForm as any) : undefined); + } + + return ( + <form class="pure-form"> + <fieldset> + <label for="username"> + {i18n.str`Username`} + {purpose === "create" && <b style={{ color: "red" }}>*</b>} + </label> + <input + name="username" + type="text" + disabled={purpose !== "create"} + value={form.username} + onChange={(e) => { + form.username = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + />{" "} + <ShowInputErrorLabel + message={errors?.username} + isDirty={form.username !== undefined} + /> + </fieldset> + <fieldset> + <label> + {i18n.str`Name`} + {purpose === "create" && <b style={{ color: "red" }}>*</b>} + </label> + <input + disabled={purpose !== "create"} + value={form.name ?? ""} + onChange={(e) => { + form.name = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + <ShowInputErrorLabel + message={errors?.name} + isDirty={form.name !== undefined} + /> + </fieldset> + {purpose !== "create" && ( + <fieldset> + <label>{i18n.str`Internal IBAN`}</label> + <input + disabled={true} + value={form.iban ?? ""} + onChange={(e) => { + form.iban = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + <ShowInputErrorLabel + message={errors?.iban} + isDirty={form.iban !== undefined} + /> + </fieldset> + )} + <fieldset> + <label> + {i18n.str`Email`} + {purpose !== "show" && <b style={{ color: "red" }}>*</b>} + </label> + <input + disabled={purpose === "show"} + value={form.contact_data.email ?? ""} + onChange={(e) => { + form.contact_data.email = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + <ShowInputErrorLabel + message={errors?.contact_data?.email} + isDirty={form.contact_data.email !== undefined} + /> + </fieldset> + <fieldset> + <label> + {i18n.str`Phone`} + {purpose !== "show" && <b style={{ color: "red" }}>*</b>} + </label> + <input + disabled={purpose === "show"} + value={form.contact_data.phone ?? ""} + onChange={(e) => { + form.contact_data.phone = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + <ShowInputErrorLabel + message={errors?.contact_data?.phone} + isDirty={form.contact_data?.phone !== undefined} + /> + </fieldset> + <fieldset> + <label> + {i18n.str`Cashout address`} + {purpose !== "show" && <b style={{ color: "red" }}>*</b>} + </label> + <input + disabled={purpose === "show"} + value={(form.cashout_address ?? "").substring("payto://iban/".length)} + onChange={(e) => { + form.cashout_address = "payto://iban/" + e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + <ShowInputErrorLabel + message={errors?.cashout_address} + isDirty={form.cashout_address !== undefined} + /> + </fieldset> + </form> + ); + } + + function initializeFromTemplate( + account: SandboxBackend.Circuit.CircuitAccountData | undefined, + ): WithIntermediate<SandboxBackend.Circuit.CircuitAccountData> { + const emptyAccount = { + cashout_address: undefined, + iban: undefined, + name: undefined, + username: undefined, + contact_data: undefined, + }; + const emptyContact = { + email: undefined, + phone: undefined, + }; + + const initial: PartialButDefined<SandboxBackend.Circuit.CircuitAccountData> = + structuredClone(account) ?? emptyAccount; + if (typeof initial.contact_data === "undefined") { + initial.contact_data = emptyContact; + } + initial.contact_data.email; + return initial as any; + } + + +
\ No newline at end of file |