diff options
author | Sebastian <sebasjm@gmail.com> | 2021-06-25 11:28:13 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-06-25 11:28:22 -0300 |
commit | 1ce7ccc04487406250cee603638950b80d6a779a (patch) | |
tree | 7ce159c27b7e896f31e893d18bb42502b0589610 /packages/frontend | |
parent | 81bda791096b23fbd677d885a04886a8d4290038 (diff) | |
download | merchant-backoffice-1ce7ccc04487406250cee603638950b80d6a779a.tar.gz merchant-backoffice-1ce7ccc04487406250cee603638950b80d6a779a.tar.bz2 merchant-backoffice-1ce7ccc04487406250cee603638950b80d6a779a.zip |
fix validation messages
Diffstat (limited to 'packages/frontend')
6 files changed, 118 insertions, 30 deletions
diff --git a/packages/frontend/src/components/form/InputArray.tsx b/packages/frontend/src/components/form/InputArray.tsx index 211d995..9f16cbf 100644 --- a/packages/frontend/src/components/form/InputArray.tsx +++ b/packages/frontend/src/components/form/InputArray.tsx @@ -34,10 +34,12 @@ const defaultToString = (f?: any): string => f || '' const defaultFromString = (v: string): any => v as any export function InputArray<T>({ name, readonly, placeholder, tooltip, label, help, addonBefore, isValid = () => true, fromStr = defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode { - const { error: formError, value, onChange } = useField<T>(name); + const { error: formError, value, onChange, required } = useField<T>(name); const [localError, setLocalError] = useState<string | null>(null) - const error = formError || localError + const error = localError || formError + + console.log(formError, localError) const array: any[] = (value ? value! : []) as any; const [currentValue, setCurrentValue] = useState(''); @@ -58,11 +60,14 @@ export function InputArray<T>({ name, readonly, placeholder, tooltip, label, hel {addonBefore && <div class="control"> <a class="button is-static">{addonBefore}</a> </div>} - <p class="control is-expanded"> + <p class="control is-expanded has-icons-right"> <input class={error ? "input is-danger" : "input"} type="text" placeholder={placeholder} readonly={readonly} disabled={readonly} name={String(name)} value={currentValue} onChange={(e): void => setCurrentValue(e.currentTarget.value)} /> + {required && <span class="icon has-text-danger is-right"> + <i class="mdi mdi-alert" /> + </span>} </p> <p class="control"> <button class="button is-info has-tooltip-left" disabled={!currentValue} onClick={(): void => { diff --git a/packages/frontend/src/components/form/InputDuration.tsx b/packages/frontend/src/components/form/InputDuration.tsx index 258858b..e5849b4 100644 --- a/packages/frontend/src/components/form/InputDuration.tsx +++ b/packages/frontend/src/components/form/InputDuration.tsx @@ -99,11 +99,11 @@ export function InputDuration<T>({ name, expand, placeholder, tooltip, label, he </div> {error && <p class="help is-danger">{error}</p>} </div> - {!readonly && <span data-tooltip={i18n`change value to empty`}> - <button class="button is-info mr-3" onClick={() => onChange(undefined as any)} ><Translate>clear</Translate></button> - </span>} {withForever && <span data-tooltip={i18n`change value to never`}> - <button class="button is-info" onClick={() => onChange({ d_ms: 'forever' } as any)}><Translate>forever</Translate></button> + <button class="button is-info mr-3" onClick={() => onChange({ d_ms: 'forever' } as any)}><Translate>forever</Translate></button> + </span>} + {!readonly && <span data-tooltip={i18n`change value to empty`}> + <button class="button is-info " onClick={() => onChange(undefined as any)} ><Translate>clear</Translate></button> </span>} </div> {opened && <SimpleModal onCancel={() => setOpened(false)}> diff --git a/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx b/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx index 2d7f93f..4187d77 100644 --- a/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx +++ b/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx @@ -75,11 +75,11 @@ export function DefaultInstanceFormFields({ readonlyId, showId }: { readonlyId?: <InputDuration<Entity> name="default_pay_delay" label={i18n`Default payment delay`} + withForever tooltip={i18n`Time customers have to pay an order before the offer expires by default.`} /> <InputDuration<Entity> name="default_wire_transfer_delay" label={i18n`Default wire transfer delay`} - withForever tooltip={i18n`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`} /> </Fragment>; diff --git a/packages/frontend/src/components/picker/DurationPicker.tsx b/packages/frontend/src/components/picker/DurationPicker.tsx index b452561..235a63e 100644 --- a/packages/frontend/src/components/picker/DurationPicker.tsx +++ b/packages/frontend/src/components/picker/DurationPicker.tsx @@ -107,7 +107,7 @@ function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, onC <div class="rdp-column" style={{ top: 0 }}> - <div class="rdp-cell" key={value - 1}> + <div class="rdp-cell" key={value - 2}> {onDecrease && <button style={{ width: '100%', textAlign: 'center', margin: 5 }} onClick={onDecrease}> <span class="icon"> @@ -130,7 +130,7 @@ function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, onC {value < max ? toTwoDigitString(value + 1) : ''} </div> - <div class="rdp-cell" key={value - 1}> + <div class="rdp-cell" key={value + 2}> {onIncrease && <button style={{ width: '100%', textAlign: 'center', margin: 5 }} onClick={onIncrease}> <span class="icon"> diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx b/packages/frontend/src/paths/admin/create/CreatePage.tsx index c9276d7..ff7e6d6 100644 --- a/packages/frontend/src/paths/admin/create/CreatePage.tsx +++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx @@ -27,8 +27,9 @@ import { FormErrors, FormProvider } from "../../../components/form/FormProvider" import { SetTokenNewInstanceModal } from "../../../components/modal"; import { MerchantBackend } from "../../../declaration"; import { Translate, useTranslator } from "../../../i18n"; -import { InstanceCreateSchema as schema } from '../../../schemas'; import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields"; +import { PAYTO_REGEX } from "../../../utils/constants"; +import { Amounts } from "@gnu-taler/taler-util"; export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & { auth_token?: string } @@ -46,18 +47,64 @@ function with_defaults(id?: string): Partial<Entity> { default_wire_transfer_delay: { d_ms: 1000 * 2 * 60 * 60 * 24 }, // one day }; } + +function undefinedIfEmpty<T>(obj: T): T | undefined { + return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : undefined +} + export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { const [value, valueHandler] = useState(with_defaults(forceId)) const [isTokenSet, updateIsTokenSet] = useState<boolean>(false); const [isTokenDialogActive, updateIsTokenDialogActive] = useState<boolean>(false); - let errors: FormErrors<Entity> = {} - try { - schema.validateSync(value, { abortEarly: false }) - } catch (err) { - const yupErrors = err.inner as yup.ValidationError[] - errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: cur.message }), {}) - } + const i18n = useTranslator() + + const errors: FormErrors<Entity> = { + id: !value.id ? i18n`required` : undefined, + name: !value.name ? i18n`required` : undefined, + auth: { + method: value.auth?.method === 'token' && !value.auth?.token ? i18n`token can't be empty` : ( + value.auth?.method !== 'external' ? i18n`access token is not defined` : undefined + ) + }, + payto_uris: + !value.payto_uris || !value.payto_uris.length ? i18n`required` : ( + undefinedIfEmpty(value.payto_uris.map(p => { + return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined + })) + ), + default_max_deposit_fee: + !value.default_max_deposit_fee ? i18n`required` : ( + !Amounts.parse(value.default_max_deposit_fee) ? i18n`invalid format` : + undefined + ), + default_max_wire_fee: + !value.default_max_wire_fee ? i18n`required` : ( + !Amounts.parse(value.default_max_wire_fee) ? i18n`invalid format` : + undefined + ), + default_wire_fee_amortization: + value.default_wire_fee_amortization === undefined ? i18n`required` : ( + isNaN(value.default_wire_fee_amortization) ? i18n`is not a number` : ( + value.default_wire_fee_amortization < 1 ? i18n`must be 1 or greater` : + undefined + ) + ), + default_pay_delay: + !value.default_pay_delay ? i18n`required` : undefined, + default_wire_transfer_delay: + !value.default_wire_transfer_delay ? i18n`required` : undefined, + address: undefinedIfEmpty({ + address_lines: + value.address?.address_lines && value.address?.address_lines.length > 7 ? i18n`max 7 lines` : + undefined + }), + jurisdiction: undefinedIfEmpty({ + address_lines: value.address?.address_lines && value.address?.address_lines.length > 7 ? i18n`max 7 lines` : + undefined + }), + }; + const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined) const submit = (): Promise<void> => { @@ -67,15 +114,13 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { value.auth = newToken === null || newToken === undefined ? { method: "external" } : { method: "token", token: `secret-token:${newToken}` }; // remove above use conversion // schema.validateSync(value, { abortEarly: false }) - return onCreate(schema.cast(value) as Entity); + return onCreate(value as Entity); } function updateToken(token: string | null) { valueHandler(old => ({ ...old, auth_token: token === null ? undefined : token })) } - const i18n = useTranslator() - return <div> <div class="columns"> <div class="column" /> diff --git a/packages/frontend/src/paths/instance/update/UpdatePage.tsx b/packages/frontend/src/paths/instance/update/UpdatePage.tsx index c900192..6e7b9eb 100644 --- a/packages/frontend/src/paths/instance/update/UpdatePage.tsx +++ b/packages/frontend/src/paths/instance/update/UpdatePage.tsx @@ -30,6 +30,8 @@ import { MerchantBackend } from "../../../declaration"; import { Translate, useTranslator } from "../../../i18n"; import { InstanceUpdateSchema as schema } from '../../../schemas'; import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields"; +import { PAYTO_REGEX } from "../../../utils/constants"; +import { Amounts } from "@gnu-taler/taler-util"; type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & { auth_token?: string } @@ -60,7 +62,9 @@ function getTokenValuePart(t?: string): string | undefined { return match[1] } - +function undefinedIfEmpty<T>(obj: T): T | undefined { + return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : undefined +} export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: Props): VNode { const { id, token } = useInstanceContext() @@ -79,13 +83,48 @@ export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: Props): const [value, valueHandler] = useState<Partial<Entity>>(convert(selected)) - let errors: FormErrors<Entity> = {} - try { - schema.validateSync(value, { abortEarly: false }) - } catch (err) { - const yupErrors = err.inner as yup.ValidationError[] - errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: cur.message }), {}) - } + const i18n = useTranslator() + + const errors: FormErrors<Entity> = { + name: !value.name ? i18n`required` : undefined, + payto_uris: + !value.payto_uris || !value.payto_uris.length ? i18n`required` : ( + undefinedIfEmpty(value.payto_uris.map(p => { + return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined + })) + ), + default_max_deposit_fee: + !value.default_max_deposit_fee ? i18n`required` : ( + !Amounts.parse(value.default_max_deposit_fee) ? i18n`invalid format` : + undefined + ), + default_max_wire_fee: + !value.default_max_wire_fee ? i18n`required` : ( + !Amounts.parse(value.default_max_wire_fee) ? i18n`invalid format` : + undefined + ), + default_wire_fee_amortization: + value.default_wire_fee_amortization === undefined ? i18n`required` : ( + isNaN(value.default_wire_fee_amortization) ? i18n`is not a number` : ( + value.default_wire_fee_amortization < 1 ? i18n`must be 1 or greater` : + undefined + ) + ), + default_pay_delay: + !value.default_pay_delay ? i18n`required` : undefined, + default_wire_transfer_delay: + !value.default_wire_transfer_delay ? i18n`required` : undefined, + address: undefinedIfEmpty({ + address_lines: + value.address?.address_lines && value.address?.address_lines.length > 7 ? i18n`max 7 lines` : + undefined + }), + jurisdiction: undefinedIfEmpty({ + address_lines: value.address?.address_lines && value.address?.address_lines.length > 7 ? i18n`max 7 lines` : + undefined + }), + }; + const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined) const submit = async (): Promise<void> => { await onUpdate(schema.cast(value)); @@ -93,7 +132,6 @@ export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: Props): return Promise.resolve() } const [active, setActive] = useState(false); - const i18n = useTranslator() return <div> <section class="section"> |