diff options
author | Sebastian <sebasjm@gmail.com> | 2023-09-21 13:10:16 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-09-25 14:50:41 -0300 |
commit | 0b7bbed99d155ba030a1328e357ab6751bdbb835 (patch) | |
tree | 7ef5e136aec9c1253f55295a1b20b66043a924f6 /packages/demobank-ui/src/pages/admin/AccountForm.tsx | |
parent | 062939d9cc016a186a282f7a48492c3e01cd740c (diff) | |
download | wallet-core-0b7bbed99d155ba030a1328e357ab6751bdbb835.tar.xz |
more ui: business and admin
Diffstat (limited to 'packages/demobank-ui/src/pages/admin/AccountForm.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/admin/AccountForm.tsx | 494 |
1 files changed, 297 insertions, 197 deletions
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx index 9ca0323a1..02df824a2 100644 --- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx @@ -1,9 +1,9 @@ -import { VNode,h } from "preact"; +import { ComponentChildren, 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 { useEffect, useRef, useState } from "preact/hooks"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { parsePaytoUri } from "@gnu-taler/taler-util"; +import { buildPayto, parsePaytoUri } from "@gnu-taler/taler-util"; const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/; const EMAIL_REGEX = @@ -19,201 +19,301 @@ const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/; * @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 + template, + purpose, + onChange, + focus, + children, +}: { + focus?: boolean, + children: ComponentChildren, + 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(); + const ref = useRef<HTMLInputElement>(null); + useEffect(() => { + if (focus) ref.current?.focus(); + }, [focus]); + + function updateForm(newForm: typeof initial): void { + + const parsed = !newForm.cashout_address + ? undefined + : buildPayto("iban", newForm.cashout_address, undefined);; + + 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` - : !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` + : !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, - 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> - ); + }), + // 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); } - - 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; + + return ( + <form + class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2" + autoCapitalize="none" + autoCorrect="off" + onSubmit={e => { + e.preventDefault() + }} + > + <div class="px-4 py-6 sm:p-8"> + <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> + + + <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="username" + > + {i18n.str`Username`} + {purpose === "create" && <b style={{ color: "red" }}> *</b>} + </label> + <div class="mt-2"> + <input + ref={ref} + type="text" + class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="username" + id="username" + data-error={!!errors?.username && form.username !== undefined} + disabled={purpose !== "create"} + value={form.username ?? ""} + onChange={(e) => { + form.username = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + // placeholder="" + autocomplete="off" + /> + <ShowInputErrorLabel + message={errors?.username} + isDirty={form.username !== undefined} + /> + </div> + <p class="mt-2 text-sm text-gray-500" > + <i18n.Translate>account identification in the bank</i18n.Translate> + </p> + </div> + + <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="name" + > + {i18n.str`Name`} + {purpose === "create" && <b style={{ color: "red" }}> *</b>} + </label> + <div class="mt-2"> + <input + type="text" + class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="name" + data-error={!!errors?.name && form.name !== undefined} + id="name" + disabled={purpose !== "create"} + value={form.name ?? ""} + onChange={(e) => { + form.name = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + // placeholder="" + autocomplete="off" + /> + <ShowInputErrorLabel + message={errors?.name} + isDirty={form.name !== undefined} + /> + </div> + <p class="mt-2 text-sm text-gray-500" > + <i18n.Translate>name of the person owner the account</i18n.Translate> + </p> + </div> + + + {purpose !== "create" && (<div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="internal-iban" + > + {i18n.str`Internal IBAN`} + </label> + <div class="mt-2"> + <input + type="text" + class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="internal-iban" + id="internal-iban" + disabled={true} + value={form.iban ?? ""} + /> + </div> + <p class="mt-2 text-sm text-gray-500" > + <i18n.Translate>international bank account number</i18n.Translate> + </p> + </div>)} + + <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="email" + > + {i18n.str`Email`} + {purpose === "create" && <b style={{ color: "red" }}> *</b>} + </label> + <div class="mt-2"> + <input + type="email" + class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="email" + id="email" + data-error={!!errors?.contact_data?.email && form.contact_data.email !== undefined} + disabled={purpose !== "create"} + value={form.contact_data.email ?? ""} + onChange={(e) => { + form.contact_data.email = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + autocomplete="off" + /> + <ShowInputErrorLabel + message={errors?.contact_data?.email} + isDirty={form.contact_data.email !== undefined} + /> + </div> + </div> + + <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="phone" + > + {i18n.str`Phone`} + {purpose === "create" && <b style={{ color: "red" }}> *</b>} + </label> + <div class="mt-2"> + <input + type="text" + class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="phone" + id="phone" + disabled={purpose !== "create"} + value={form.contact_data.phone ?? ""} + data-error={!!errors?.contact_data?.phone && form.contact_data.phone !== undefined} + onChange={(e) => { + form.contact_data.phone = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + // placeholder="" + autocomplete="off" + /> + <ShowInputErrorLabel + message={errors?.contact_data?.phone} + isDirty={form.contact_data.phone !== undefined} + /> + </div> + </div> + + + <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="cashout" + > + {i18n.str`Cashout IBAN`} + {purpose !== "show" && <b style={{ color: "red" }}> *</b>} + </label> + <div class="mt-2"> + <input + type="text" + data-error={!!errors?.cashout_address && form.cashout_address !== undefined} + class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="cashout" + id="cashout" + disabled={purpose === "show"} + value={form.cashout_address ?? ""} + onChange={(e) => { + form.cashout_address = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + autocomplete="off" + /> + <ShowInputErrorLabel + message={errors?.cashout_address} + isDirty={form.cashout_address !== undefined} + /> + </div> + <p class="mt-2 text-sm text-gray-500" > + <i18n.Translate>account number where the money is going to be sent when doing cashouts</i18n.Translate> + </p> + </div> + + </div> + </div> + {children} + </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; } - - -
\ No newline at end of file + initial.contact_data.email; + return initial as any; +} + + |