merchant-backoffice

ZZZ: Inactive/Deprecated
Log | Files | Refs | Submodules | README

commit 6b3d4771429459c4928ed53f6674fd6614f21236
parent d6b719772d37848192d92bad2ef6f3697606b24d
Author: Sebastian <sebasjm@gmail.com>
Date:   Tue, 19 Apr 2022 18:34:34 -0300

added bitcoin and ethereum target type for payto uri

Diffstat:
Mpackages/merchant-backoffice/src/components/form/InputPaytoForm.tsx | 98++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mpackages/merchant-backoffice/src/components/form/InputSelector.tsx | 81+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
2 files changed, 132 insertions(+), 47 deletions(-)

diff --git a/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx b/packages/merchant-backoffice/src/components/form/InputPaytoForm.tsx @@ -51,6 +51,43 @@ type Entity = { }; }; +function isEthereumAddress(address: string) { + if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) { + return false; + } else if ( + /^(0x|0X)?[0-9a-f]{40}$/.test(address) || + /^(0x|0X)?[0-9A-F]{40}$/.test(address) + ) { + return true; + } + return checkAddressChecksum(address); +} + +function checkAddressChecksum(address: string) { + //TODO implement ethereum checksum + return true; +} + +function validateBitcoin(addr: string, i18n: Translator): string | undefined { + try { + const valid = /^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/.test(addr); + if (valid) return undefined; + } catch (e) { + console.log(e); + } + return i18n`This is not a valid bitcoin address.`; +} + +function validateEthereum(addr: string, i18n: Translator): string | undefined { + try { + const valid = isEthereumAddress(addr); + if (valid) return undefined; + } catch (e) { + console.log(e); + } + return i18n`This is not a valid Ethereum address.`; +} + /** * An IBAN is validated by converting it into an integer and performing a * basic mod-97 operation (as described in ISO 7064) on it. @@ -107,8 +144,15 @@ function validateIBAN(iban: string, i18n: Translator): string | undefined { } // const targets = ['ach', 'bic', 'iban', 'upi', 'bitcoin', 'ilp', 'void', 'x-taler-bank'] -const targets = ["iban", "x-taler-bank"]; -const defaultTarget = { target: "iban", options: {} }; +const targets = [ + "Choose one...", + "iban", + "x-taler-bank", + "bitcoin", + "ethereum", +]; +const noTargetValue = targets[0]; +const defaultTarget = { target: noTargetValue, options: {} }; function undefinedIfEmpty<T>(obj: T): T | undefined { return Object.keys(obj).some((k) => (obj as any)[k] !== undefined) @@ -147,11 +191,15 @@ export function InputPaytoForm<T>({ const paytoURL = url.toString(); const errors: FormErrors<Entity> = { - target: !value.target ? i18n`required` : undefined, + target: value.target === noTargetValue ? i18n`required` : undefined, path1: !value.path1 ? i18n`required` : value.target === "iban" ? validateIBAN(value.path1, i18n) + : value.target === "bitcoin" + ? validateBitcoin(value.path1, i18n) + : value.target === "ethereum" + ? validateEthereum(value.path1, i18n) : undefined, path2: value.target === "x-taler-bank" @@ -193,6 +241,7 @@ export function InputPaytoForm<T>({ label={i18n`Target type`} tooltip={i18n`Method to use for wire transfer`} values={targets} + toStr={(v) => (v === noTargetValue ? i18n`Choose one...` : v)} /> {value.target === "ach" && ( @@ -246,6 +295,15 @@ export function InputPaytoForm<T>({ /> </Fragment> )} + {value.target === "ethereum" && ( + <Fragment> + <Input<Entity> + name="path1" + label={i18n`Address`} + tooltip={i18n`Ethereum protocol.`} + /> + </Fragment> + )} {value.target === "ilp" && ( <Fragment> <Input<Entity> @@ -271,11 +329,13 @@ export function InputPaytoForm<T>({ </Fragment> )} - <Input - name="options.receiver-name" - label={i18n`Name`} - tooltip={i18n`Bank account owner's name.`} - /> + {value.target !== noTargetValue && ( + <Input + name="options.receiver-name" + label={i18n`Name`} + tooltip={i18n`Bank account owner's name.`} + /> + )} <div class="field is-horizontal"> <div class="field-label is-normal" /> @@ -304,16 +364,18 @@ export function InputPaytoForm<T>({ </div> </div> - <div class="buttons is-right mt-5"> - <button - class="button is-info" - data-tooltip={i18n`add tax to the tax list`} - disabled={hasErrors} - onClick={submit} - > - <Translate>Add</Translate> - </button> - </div> + {value.target !== noTargetValue && ( + <div class="buttons is-right mt-5"> + <button + class="button is-info" + data-tooltip={i18n`add tax to the tax list`} + disabled={hasErrors} + onClick={submit} + > + <Translate>Add</Translate> + </button> + </div> + )} </FormProvider> </InputGroup> ); diff --git a/packages/merchant-backoffice/src/components/form/InputSelector.tsx b/packages/merchant-backoffice/src/components/form/InputSelector.tsx @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; import { InputProps, useField } from "./useField"; @@ -29,35 +29,58 @@ interface Props<T> extends InputProps<T> { fromStr?: (s: string) => any; } -const defaultToString = (f?: any): string => f || '' -const defaultFromString = (v: string): any => v as any +const defaultToString = (f?: any): string => f || ""; +const defaultFromString = (v: string): any => v as any; -export function InputSelector<T>({ name, readonly, expand, placeholder, tooltip, label, help, values, fromStr = defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode { +export function InputSelector<T>({ + name, + readonly, + expand, + placeholder, + tooltip, + label, + help, + values, + toStr = defaultToString, +}: Props<keyof T>): VNode { const { error, value, onChange } = useField<T>(name); - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - {label} - {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <p class={expand ? "control is-expanded select" : "control select"}> - <select class={error ? "select is-danger" : "select"} - name={String(name)} disabled={readonly} readonly={readonly} - onChange={(e) => { onChange(fromStr(e.currentTarget.value)) }}> - {placeholder && <option>{placeholder}</option>} - {values - .map((v, i) => <option key={i} value={toStr(v)} selected={value === toStr(v)}>{toStr(v)}</option>)} - </select> - {help} - </p> - {error && <p class="help is-danger">{error}</p>} + return ( + <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + {label} + {tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + </div> + <div class="field-body is-flex-grow-3"> + <div class="field"> + <p class={expand ? "control is-expanded select" : "control select"}> + <select + class={error ? "select is-danger" : "select"} + name={String(name)} + disabled={readonly} + readonly={readonly} + onChange={(e) => { + onChange(e.currentTarget.value as any); + }} + > + {placeholder && <option>{placeholder}</option>} + {values.map((v, i) => ( + <option key={i} value={v} selected={value === v}> + {toStr(v)} + </option> + ))} + </select> + {help} + </p> + {error && <p class="help is-danger">{error}</p>} + </div> </div> </div> - </div>; + ); }