diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx | 268 |
1 files changed, 159 insertions, 109 deletions
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index 5f5a6ce3b..785dc4264 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -55,10 +55,11 @@ export function PaytoWireTransferForm({ onCancel: (() => void) | undefined; limit: AmountJson; }): VNode { - const [isRawPayto, setIsRawPayto] = useState(false); - const [iban, setIban] = useState<string | undefined>(undefined); - const [subject, setSubject] = useState<string | undefined>(undefined); - const [amount, setAmount] = useState<string | undefined>(undefined); + const [isRawPayto, setIsRawPayto] = useState(true); + // FIXME: remove this + const [iban, setIban] = useState<string | undefined>("DE4745461198061"); + const [subject, setSubject] = useState<string | undefined>("ASD"); + const [amount, setAmount] = useState<string | undefined>("1.00001"); const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>( undefined, @@ -76,17 +77,17 @@ export function PaytoWireTransferForm({ const errorsWire = undefinedIfEmpty({ iban: !iban - ? i18n.str`Missing IBAN` + ? i18n.str`required` : !IBAN_REGEX.test(iban) ? i18n.str`IBAN should have just uppercased letters and numbers` : validateIBAN(iban, i18n), - subject: !subject ? i18n.str`Missing subject` : undefined, + subject: !subject ? i18n.str`required` : undefined, amount: !trimmedAmountStr - ? i18n.str`Missing amount` + ? i18n.str`required` : !parsedAmount - ? i18n.str`Amount is not valid` + ? i18n.str`not valid` : Amounts.isZero(parsedAmount) - ? i18n.str`Should be greater than 0` + ? i18n.str`should be greater than 0` : Amounts.cmp(limit, parsedAmount) === -1 ? i18n.str`balance is not enough` : undefined, @@ -101,14 +102,14 @@ export function PaytoWireTransferForm({ ? i18n.str`required` : !parsed ? i18n.str`does not follow the pattern` - : !parsed.params.amount - ? i18n.str`use the "amount" parameter to specify the amount to be transferred` - : Amounts.parse(parsed.params.amount) === undefined - ? i18n.str`the amount is not valid` - : !parsed.params.message - ? i18n.str`use the "message" parameter to specify a reference text for the transfer` - : !parsed.isKnown || parsed.targetType !== "iban" - ? i18n.str`only "IBAN" target are supported` + : !parsed.isKnown || parsed.targetType !== "iban" + ? i18n.str`only "IBAN" target are supported` + : !parsed.params.amount + ? i18n.str`use the "amount" parameter to specify the amount to be transferred` + : Amounts.parse(parsed.params.amount) === undefined + ? i18n.str`the amount is not valid` + : !parsed.params.message + ? i18n.str`use the "message" parameter to specify a reference text for the transfer` : !IBAN_REGEX.test(parsed.iban) ? i18n.str`IBAN should have just uppercased letters and numbers` : validateIBAN(parsed.iban, i18n), @@ -159,6 +160,9 @@ export function PaytoWireTransferForm({ } 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"> + {/** + * FIXME: Scan a qr code + */} <div class="px-4 sm:px-0"> <h2 class="text-base font-semibold leading-7 text-gray-900"> {title} @@ -167,6 +171,17 @@ export function PaytoWireTransferForm({ <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-1 sm:gap-x-4"> <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (!isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}> <input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onChange={() => { + if (parsed && parsed.isKnown && parsed.targetType === "iban") { + setIban(parsed.iban) + const amount = Amounts.parse(parsed.params["amount"]) + if (amount) { + setAmount(Amounts.stringifyValue(amount)) + } + const subject = parsed.params["subject"] + if (subject) { + setSubject(subject) + } + } setIsRawPayto(false) }} /> <span class="flex flex-1"> @@ -180,12 +195,22 @@ export function PaytoWireTransferForm({ <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}> <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onChange={() => { + if (iban) { + const payto = buildPayto("iban", iban, undefined) + if (parsedAmount) { + payto.params["amount"] = Amounts.stringify(parsedAmount) + } + if (subject) { + payto.params["message"] = subject + } + rawPaytoInputSetter(stringifyPaytoUri(payto)) + } setIsRawPayto(true) }} /> <span class="flex flex-1"> <span class="flex flex-col"> <span class="block text-sm font-medium text-gray-900"> - <i18n.Translate>using the payto:// format</i18n.Translate> + <i18n.Translate>Import payto:// URI</i18n.Translate> </span> </span> </span> @@ -195,7 +220,7 @@ export function PaytoWireTransferForm({ </div> <form - class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2" + class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2 w-fit mx-auto" autoCapitalize="none" autoCorrect="off" onSubmit={e => { @@ -203,105 +228,106 @@ export function PaytoWireTransferForm({ }} > <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-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" - name="iban" - id="iban" - value={iban ?? ""} - placeholder="CC0123456789" - autocomplete="off" - required - pattern={ibanRegex} - onInput={(e): void => { - setIban(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errorsWire?.iban} - isDirty={iban !== undefined} - /> - </div> - <p class="mt-2 text-sm text-gray-500" >the receiver of the money</p> - </div> + {!isRawPayto ? + <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> - <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 - onInput={(e): void => { - setSubject(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errorsWire?.subject} - isDirty={subject !== undefined} - /> - </div> - <p class="mt-2 text-sm text-gray-500" >some text to identify the transfer</p> + <div class="sm:col-span-5"> + <label for="iban" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Recipient`}</label> + <div class="mt-2"> + <input + ref={focus ? doAutoFocus : undefined} + 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="iban" + id="iban" + value={iban ?? ""} + placeholder="CC0123456789" + autocomplete="off" + required + pattern={ibanRegex} + onInput={(e): void => { + setIban(e.currentTarget.value.toUpperCase()); + }} + /> + <ShowInputErrorLabel + message={errorsWire?.iban} + isDirty={iban !== undefined} + /> </div> + <p class="mt-2 text-sm text-gray-500" > + <i18n.Translate>IBAN of the recipient's account</i18n.Translate> + </p> + </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) + <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 + onInput={(e): void => { + setSubject(e.currentTarget.value); }} /> <ShowInputErrorLabel message={errorsWire?.subject} isDirty={subject !== undefined} /> - <p class="mt-2 text-sm text-gray-500" >amount to transfer</p> </div> + <p class="mt-2 text-sm text-gray-500" >some text to identify the transfer</p> + </div> - </Fragment> : - <Fragment> - <div class="sm:col-span-6"> - <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} - value={rawPaytoInput ?? ""} - required - placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`} - onInput={(e): void => { - rawPaytoInputSetter(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errorsPayto?.rawPaytoInput} - isDirty={rawPaytoInput !== undefined} - /> - </div> - </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" + left + currency={limit.currency} + value={trimmedAmountStr} + onChange={(d) => { + setAmount(d) + }} + /> + <ShowInputErrorLabel + message={errorsWire?.amount} + isDirty={subject !== undefined} + /> + <p class="mt-2 text-sm text-gray-500" >amount to transfer</p> + </div> - </Fragment> - } - </div> + </div> : + <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6 w-full"> + <div class="sm:col-span-6"> + <label for="address" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label> + <div class="mt-2"> + <textarea + ref={focus ? doAutoFocus : undefined} + name="address" + id="address" + type="textarea" + rows={3} + class="block overflow-hidden w-64 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" + value={rawPaytoInput ?? ""} + required + placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`} + onInput={(e): void => { + rawPaytoInputSetter(e.currentTarget.value); + }} + /> + <ShowInputErrorLabel + message={errorsPayto?.rawPaytoInput} + isDirty={rawPaytoInput !== undefined} + /> + </div> + </div> + </div> + } </div> <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"> {onCancel ? @@ -328,17 +354,37 @@ export function PaytoWireTransferForm({ ) } + +/** + * Show the element when the load ended + * @param element + */ +export function doAutoFocus(element: HTMLElement | null) { + if (element) { + window.requestIdleCallback(() => { + element.focus() + element.scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "center" + }) + }) + } +} + export function Amount( { currency, name, value, error, + left, onChange, }: { error?: string; currency: string; name: string; + left?: boolean | undefined, value: string | undefined; onChange?: (s: string) => void; }, @@ -346,13 +392,16 @@ export function Amount( ): 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"> + <div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600"> + <div + class="pointer-events-none inset-y-0 flex items-center px-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" + data-left={left} + class="text-right rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6" placeholder="0.00" aria-describedby="price-currency" ref={ref} name={name} @@ -371,3 +420,4 @@ export function Amount( </div> ); } + |