diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx | 221 |
1 files changed, 151 insertions, 70 deletions
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index 1107360bd..5e0624cbf 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -19,17 +19,21 @@ import { Amounts, HttpStatusCode, Logger, - parsePaytoUri + TranslatedString, + buildPayto, + parsePaytoUri, + stringifyPaytoUri } from "@gnu-taler/taler-util"; import { RequestError, + notify, + notifyError, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { h, VNode, Fragment } from "preact"; +import { h, VNode, Fragment, Ref } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { useAccessAPI } from "../hooks/access.js"; -import { notifyError } from "../hooks/notification.js"; import { buildRequestErrorMessage, undefinedIfEmpty, @@ -41,10 +45,12 @@ const logger = new Logger("PaytoWireTransferForm"); export function PaytoWireTransferForm({ focus, onSuccess, + onCancel, limit, }: { focus?: boolean; onSuccess: () => void; + onCancel: (() => void) | undefined; limit: AmountJson; }): VNode { const [isRawPayto, setIsRawPayto] = useState(false); @@ -105,7 +111,51 @@ export function PaytoWireTransferForm({ ? i18n.str`IBAN should have just uppercased letters and numbers` : validateIBAN(parsed.iban, i18n), }); - // if (!isRawPayto) { + + async function doSend() { + let paytoUri: string | undefined; + + if (rawPaytoInput) { + paytoUri = rawPaytoInput + } else { + if (!iban || !subject) return; + const ibanPayto = buildPayto("iban", iban, undefined); + ibanPayto.params.message = encodeURIComponent(subject); + paytoUri = stringifyPaytoUri(ibanPayto); + } + + try { + await createTransaction({ + paytoUri, + amount: `${limit.currency}:${amount}`, + }); + onSuccess(); + setAmount(undefined); + setIban(undefined); + setSubject(undefined); + rawPaytoInputSetter(undefined) + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.BadRequest + ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) +} + } + + } + return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg"> <div class="px-4 sm:px-0"> <h2 class="text-base font-semibold leading-7 text-gray-900"><i18n.Translate>Transfer details</i18n.Translate></h2> @@ -118,7 +168,7 @@ export function PaytoWireTransferForm({ }} /> <span class="flex flex-1"> <span class="flex flex-col"> - <span id="project-type-0-label" class="block text-sm font-medium text-gray-900"> + <span class="block text-sm font-medium text-gray-900"> <i18n.Translate>form</i18n.Translate> </span> </span> @@ -133,7 +183,7 @@ export function PaytoWireTransferForm({ }} /> <span class="flex flex-1"> <span class="flex flex-col"> - <span id="project-type-1-label" class="block text-sm font-medium text-gray-900"> + <span class="block text-sm font-medium text-gray-900"> <i18n.Translate>payto://</i18n.Translate> </span> </span> @@ -143,23 +193,31 @@ export function PaytoWireTransferForm({ </div> </div> - <form class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"> + <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"> {!isRawPayto ? <Fragment> - <div class="sm:col-span-3"> - <label for="first-name" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Account number`}</label> + <div class="sm:col-span-5"> + <label for="iban" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Account number`}</label> <div class="mt-2"> <input ref={ref} type="text" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - id="iban" name="iban" + id="iban" value={iban ?? ""} placeholder="CC0123456789" + autocomplete="off" required pattern={ibanRegex} onInput={(e): void => { @@ -171,21 +229,18 @@ export function PaytoWireTransferForm({ isDirty={iban !== undefined} /> </div> - <p class="mt-2 text-sm text-gray-500" id="email-description">the receiver of the money</p> - </div> - - <div class="sm:col-span-3"> + <p class="mt-2 text-sm text-gray-500" >the receiver of the money</p> </div> - <div class="sm:col-span-3"> - <label for="first-name" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Transfer subject`}</label> + <div class="sm:col-span-5"> + <label for="subject" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Transfer subject`}</label> <div class="mt-2"> - <input type="text" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" name="subject" id="subject" + autocomplete="off" placeholder="subject" value={subject ?? ""} required @@ -198,37 +253,40 @@ export function PaytoWireTransferForm({ isDirty={subject !== undefined} /> </div> - <p class="mt-2 text-sm text-gray-500" id="email-description">some text to identify the transfer</p> - - </div> - - <div class="sm:col-span-3"> + <p class="mt-2 text-sm text-gray-500" >some text to identify the transfer</p> </div> - <div class="sm:col-span-3"> - <label for="first-name" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Amount`}</label> - <div class="mt-2"> - <input type="text" name="first-name" id="first-name" autocomplete="given-name" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" /> - </div> + <div class="sm:col-span-5"> + <label for="amount" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Amount`}</label> + <Amount + name="amount" + currency={limit.currency} + value={trimmedAmountStr} + onChange={(d) => { + setAmount(d) + }} + /> + <ShowInputErrorLabel + message={errorsWire?.subject} + isDirty={subject !== undefined} + /> + <p class="mt-2 text-sm text-gray-500" >amount to transfer</p> </div> - <div class="sm:col-span-3"> - </div> </Fragment> : <Fragment> <div class="sm:col-span-6"> - <label for="first-name" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label> + <label for="address" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label> <div class="mt-2"> <input name="address" + id="address" type="text" size={50} class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" ref={ref} - id="address" value={rawPaytoInput ?? ""} required placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`} - // pattern={`payto://iban/[A-Z][A-Z][0-9]+?message=[a-zA-Z0-9 ]+&amount=${currency}:[0-9]+(.[0-9]+)?`} onInput={(e): void => { rawPaytoInputSetter(e.currentTarget.value); }} @@ -244,9 +302,23 @@ export function PaytoWireTransferForm({ } </div> </div> - <div class="flex items-center justify-end gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"> - <button type="button" class="text-sm font-semibold leading-6 text-gray-900">Cancel</button> - <button type="submit" class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"> + <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"> + {onCancel ? + <button type="button" class="text-sm font-semibold leading-6 text-gray-900" + onClick={onCancel} + > + <i18n.Translate>Cancel</i18n.Translate> + </button> + : <div /> + } + <button type="submit" + class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + disabled={isRawPayto ? !!errorsPayto : !!errorsWire} + onClick={(e) => { + e.preventDefault() + doSend() + }} + > <i18n.Translate>Send</i18n.Translate> </button> </div> @@ -262,8 +334,6 @@ export function PaytoWireTransferForm({ // onSubmit={(e) => { // e.preventDefault(); // }} - // autoCapitalize="none" - // autoCorrect="off" // > // <label for="iban">{i18n.str`Receiver IBAN:`}</label> @@ -318,39 +388,7 @@ export function PaytoWireTransferForm({ // if (!(iban && subject && amount)) { // return; // } - // const ibanPayto = buildPayto("iban", iban, undefined); - // ibanPayto.params.message = encodeURIComponent(subject); - // const paytoUri = stringifyPaytoUri(ibanPayto); - - // try { - // await createTransaction({ - // paytoUri, - // amount: `${limit.currency}:${amount}`, - // }); - // onSuccess(); - // setAmount(undefined); - // setIban(undefined); - // setSubject(undefined); - // } catch (error) { - // if (error instanceof RequestError) { - // notifyError( - // buildRequestErrorMessage(i18n, error.cause, { - // onClientError: (status) => - // status === HttpStatusCode.BadRequest - // ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.` - // : undefined, - // }), - // ); - // } else { - // notifyError({ - // title: i18n.str`Operation failed, please report`, - // description: - // error instanceof Error - // ? error.message - // : JSON.stringify(error), - // }); - // } - // } + // }} // /> // <input @@ -389,3 +427,46 @@ export function PaytoWireTransferForm({ // </div> // ); } +export function Amount( + { + currency, + name, + value, + error, + onChange, + }: { + error?: string; + currency: string; + name: string; + value: string | undefined; + onChange?: (s: string) => void; + }, + ref: Ref<HTMLInputElement>, +): VNode { + return ( + <div class="mt-2"> + <div class="relative rounded-md shadow-sm"> + <div class="pointer-events-none absolute inset-y-0 flex items-center pl-3"> + <span class="text-gray-500 sm:text-sm">{currency}</span> + </div> + <input + type="number" + class="text-right block w-full rounded-md border-0 py-1.5 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + placeholder="0.00" aria-describedby="price-currency" + ref={ref} + name={name} + id={name} + autocomplete="off" + value={value ?? ""} + disabled={!onChange} + onInput={(e): void => { + if (onChange) { + onChange(e.currentTarget.value); + } + }} + /> + </div> + <ShowInputErrorLabel message={error} isDirty={value !== undefined} /> + </div> + ); +} |