commit 5002a444a7d207295457cf6c21e5192c5c49c59c
parent 4577c84b9f552315f131b58631490d0e76e8559a
Author: Sebastian <sebasjm@gmail.com>
Date: Fri, 12 Feb 2021 02:43:11 -0300
amount regex and duration fields
Diffstat:
7 files changed, 193 insertions(+), 90 deletions(-)
diff --git a/src/components/yup/YupInput.tsx b/src/components/yup/YupInput.tsx
@@ -3,30 +3,38 @@ import { Text, useText } from "preact-i18n";
interface Props {
name: string;
- value: any;
+ object: any;
info: any;
errors: any;
valueHandler: any;
}
-export default function YupInput({ name, info, value, errors, valueHandler }: Props): VNode {
- const dict = useText({placeholder: `fields.instance.${name}.placeholder`})
+function convert(object: any, name: string, type?: string): any {
+ switch (type) {
+ case 'duration': return object[name]?.d_ms;
+ default: return object[name];
+ }
+}
+
+export default function YupInput({ name, info, object, errors, valueHandler }: Props): VNode {
+ const dict = useText({ placeholder: `fields.instance.${name}.placeholder` })
+ const value = convert(object, name, info.meta?.type)
return <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label"><Text id={`fields.instance.${name}.label`} /></label>
- </div>
- <div class="field-body">
- <div class="field">
- <p class="control is-expanded has-icons-left">
- <input class="input" type="text"
- placeholder={dict['placeholder']} readonly={info?.meta?.readonly}
- name={name} value={value[name]}
+ <div class="field-label is-normal">
+ <label class="label"><Text id={`fields.instance.${name}.label`} /></label>
+ </div>
+ <div class="field-body">
+ <div class="field">
+ <p class="control is-expanded has-icons-left">
+ <input class="input" type="text"
+ placeholder={dict['placeholder']} readonly={info?.meta?.readonly}
+ name={name} value={value}
onChange={(e): void => valueHandler((prev: any) => ({ ...prev, [name]: e.currentTarget.value }))} />
- <Text id={`fields.instance.${name}.help`} />
- </p>
- {errors[name] ? <p class="help is-danger"><Text id={`validation.${errors[name].type}`} fields={errors[name].params}>{errors[name].message}</Text></p> : null}
+ <Text id={`fields.instance.${name}.help`} />
+ </p>
+ {errors[name] ? <p class="help is-danger"><Text id={`validation.${errors[name].type}`} fields={errors[name].params}>{errors[name].message}</Text></p> : null}
+ </div>
</div>
</div>
-</div>
}
diff --git a/src/constants.ts b/src/constants.ts
@@ -1,2 +1,2 @@
export const PAYTO_REGEX=/^payto:\/\/[a-zA-Z][a-zA-Z0-9-.]*(\/[a-zA-Z0-9\-\.\~\(\)@_%:!$&'*+,;=]*)*\??((amount|receiver-name|sender-name|instruction|message)=[a-zA-Z0-9\-\.\~\(\)@_%:!$'*+,;=]*&?)*$/
-
+export const AMOUNT_REGEX=/^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
@@ -66,6 +66,12 @@ export default {
default_wire_fee_amortization: {
label: 'Amortización de pago',
},
+ default_pay_delay: {
+ label: 'Tiempo de espera de pago'
+ },
+ default_wire_transfer_delay: {
+ label: 'Tiempo de espera de transferencia bancaria'
+ },
},
},
validation: {
@@ -149,6 +155,12 @@ export default {
default_wire_fee_amortization: {
label: 'Max fee amortization',
},
+ default_pay_delay: {
+ label: 'Pay delay'
+ },
+ default_wire_transfer_delay: {
+ label: 'Wire transfer delay'
+ },
}
},
validation: {
diff --git a/src/routes/instances/CreateModal.tsx b/src/routes/instances/CreateModal.tsx
@@ -1,38 +1,10 @@
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
-import { Duration, MerchantBackend } from "../../declaration";
+import { MerchantBackend } from "../../declaration";
import * as yup from 'yup';
import ConfirmModal from "../../components/modal";
import YupInput from "../../components/yup/YupInput"
-
-function stringToArray(this: yup.AnySchema, current: any, original: string): string[] {
- if (this.isType(current)) return current;
- return original.split(',').filter(s => s.length > 0)
-}
-function numberToDuration(this: yup.AnySchema, current: any, original: string): Duration {
- if (this.isType(current)) return current;
- const d_ms = parseInt(original, 10)
- return { d_ms }
-}
-/*
-validations
- * delays size
- * payto-uri format
- * currency
-*/
-
-const schema = yup.object().shape({
- id: yup.string().required().label("Id"),
- name: yup.string().required().label("Name"),
- payto_uris: yup.array().of(yup.string())
- .min(1).label("Payment Method")
- .transform(stringToArray),
- default_max_deposit_fee: yup.string().required().label("Max Deposit Fee"),
- default_max_wire_fee: yup.string().required().label("Max Wire"),
- default_wire_fee_amortization: yup.number().required().label("WireFee Amortization"),
- // default_pay_delay: yup.number().required().label("Pay delay").transform(numberToDuration),
- // default_wire_transfer_delay: yup.number().required().label("Wire transfer Delay").transform(numberToDuration),
-});
+import { InstanceCreateSchema as schema } from '../../schemas'
interface Props {
onCancel: () => void;
@@ -61,7 +33,7 @@ export default function CreateModal({ onCancel, onConfirm }: Props): VNode {
return <ConfirmModal description="create_instance" active onConfirm={submit} onCancel={onCancel}>
{Object.keys(schema.fields).map(f => {
const info = schema.fields[f].describe()
- return <YupInput name={f} info={info} errors={errors} value={value} valueHandler={valueHandler} />
+ return <YupInput name={f} info={info} errors={errors} object={value} valueHandler={valueHandler} />
})}
</ConfirmModal>
diff --git a/src/routes/instances/UpdateModal.tsx b/src/routes/instances/UpdateModal.tsx
@@ -4,23 +4,7 @@ import { MerchantBackend } from "../../declaration";
import * as yup from 'yup';
import ConfirmModal from '../../components/modal'
import YupInput from "../../components/yup/YupInput";
-import { PAYTO_REGEX } from "../../constants";
-
-function stringToArray(this: yup.AnySchema, current: any, original: string): string[] {
- if (this.isType(current)) return current;
- return original.split(',').filter(s => s.length > 0)
-}
-
-const schema = yup.object().shape({
- name: yup.string().required(),
- payto_uris: yup.array().of(yup.string()).min(1)
- .transform(stringToArray).test('payto','{path} is not valid', (values): boolean => !!values && values.filter( v => v && PAYTO_REGEX.test(v) ).length > 0 ),
- default_max_deposit_fee: yup.string().required(),
- default_max_wire_fee: yup.string().required(),
- default_wire_fee_amortization: yup.number().required(),
- // default_pay_delay: yup.number().required().label("Pay delay").transform(numberToDuration),
- // default_wire_transfer_delay: yup.number().required().label("Wire transfer Delay").transform(numberToDuration),
-});
+import { InstanceUpdateSchema as schema } from '../../schemas'
interface Props {
element: MerchantBackend.Instances.QueryInstancesResponse | null;
@@ -33,7 +17,7 @@ interface KeyValue {
}
export default function UpdateModal({ element, onCancel, onConfirm }: Props): VNode {
- const copy: any = !element ? {} : Object.keys(schema.fields).reduce((prev,cur) => ({...prev, [cur]: (element as any)[cur] }), {})
+ const copy: any = !element ? {} : Object.keys(schema.fields).reduce((prev, cur) => ({ ...prev, [cur]: (element as any)[cur] }), {})
const [value, valueHandler] = useState(copy)
const [errors, setErrors] = useState<KeyValue>({})
@@ -42,7 +26,7 @@ export default function UpdateModal({ element, onCancel, onConfirm }: Props): VN
try {
schema.validateSync(value, { abortEarly: false })
- onConfirm({...schema.cast(value), address: {}, jurisdiction: {}, default_wire_transfer_delay: { d_ms: 6000 }, default_pay_delay: { d_ms: 3000 }} as MerchantBackend.Instances.InstanceReconfigurationMessage);
+ onConfirm({ ...schema.cast(value), address: {}, jurisdiction: {} } as MerchantBackend.Instances.InstanceReconfigurationMessage);
} catch (err) {
const errors = err.inner as yup.ValidationError[]
const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message } }), {})
@@ -54,7 +38,7 @@ export default function UpdateModal({ element, onCancel, onConfirm }: Props): VN
{Object.keys(schema.fields).map(f => {
const info = schema.fields[f].describe()
- return <YupInput name={f} info={info} errors={errors} value={value} valueHandler={valueHandler} />
+ return <YupInput name={f} info={info} errors={errors} object={value} valueHandler={valueHandler} />
})}
diff --git a/src/schemas/index.ts b/src/schemas/index.ts
@@ -0,0 +1,85 @@
+import * as yup from 'yup';
+import { AMOUNT_REGEX, PAYTO_REGEX } from "../constants";
+import { Duration } from '../declaration';
+
+yup.setLocale({
+ mixed: {
+ default: 'field_invalid',
+ },
+ number: {
+ min: ({ min }) => ({ key: 'field_too_short', values: { min } }),
+ max: ({ max }) => ({ key: 'field_too_big', values: { max } }),
+ },
+});
+
+function stringToArray(this: yup.AnySchema, current: any, original: string): string[] {
+ if (this.isType(current)) return current;
+ return original.split(',').filter(s => s.length > 0)
+}
+
+function listOfPayToUrisAreValid(values?: (string | undefined)[]): boolean {
+ return !!values && values.filter(v => v && PAYTO_REGEX.test(v)).length > 0;
+}
+
+function numberToDuration(this: yup.AnySchema, current: any, original: string): Duration {
+ if (this.isType(current)) return current;
+ const d_ms = parseInt(original, 10)
+ return { d_ms }
+}
+
+function currencyWithAmountIsValid(value?: string): boolean {
+ return !!value && AMOUNT_REGEX.test(value)
+}
+
+export const InstanceUpdateSchema = yup.object().shape({
+ name: yup.string().required(),
+ payto_uris: yup.array().of(yup.string())
+ .min(1)
+ .transform(stringToArray)
+ .test('payto', '{path} is not valid', listOfPayToUrisAreValid),
+ default_max_deposit_fee: yup.string()
+ .required()
+ .test('amount', '{path} is not valid', currencyWithAmountIsValid),
+ default_max_wire_fee: yup.string()
+ .required()
+ .test('amount', '{path} is not valid', currencyWithAmountIsValid),
+ default_wire_fee_amortization: yup.number()
+ .required(),
+ default_pay_delay: yup.object()
+ .shape({d_ms: yup.number() })
+ .required()
+ .meta({type:'duration'})
+ .transform(numberToDuration),
+ default_wire_transfer_delay: yup.object()
+ .shape({d_ms: yup.number() })
+ .required()
+ .meta({type:'duration'})
+ .transform(numberToDuration),
+});
+
+export const InstanceCreateSchema = yup.object().shape({
+ id: yup.string().required(),
+ name: yup.string().required(),
+ payto_uris: yup.array().of(yup.string())
+ .min(1)
+ .transform(stringToArray)
+ .test('payto', '{path} is not valid', listOfPayToUrisAreValid),
+ default_max_deposit_fee: yup.string()
+ .required()
+ .test('amount', '{path} is not valid', currencyWithAmountIsValid),
+ default_max_wire_fee: yup.string()
+ .required()
+ .test('amount', '{path} is not valid', currencyWithAmountIsValid),
+ default_wire_fee_amortization: yup.number()
+ .required(),
+ default_pay_delay: yup.object()
+ .shape({d_ms: yup.number() })
+ .required()
+ .meta({type:'duration'})
+ .transform(numberToDuration),
+ default_wire_transfer_delay: yup.object()
+ .shape({d_ms: yup.number() })
+ .required()
+ .meta({type:'duration'})
+ .transform(numberToDuration),
+});
diff --git a/tests/functions/regex.test.ts b/tests/functions/regex.test.ts
@@ -1,25 +1,66 @@
-import { PAYTO_REGEX } from "../../src/constants";
+import { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/constants";
-const valids = [
- 'payto://iban/DE75512108001245126199?amount=EUR:200.0&message=hello',
- 'payto://ach/122000661/1234',
- 'payto://upi/alice@example.com?receiver-name=Alice&amount=INR:200',
- 'payto://void/?amount=EUR:10.5',
- 'payto://ilp/g.acme.bob'
-]
+describe('payto uri format', () => {
+ const valids = [
+ 'payto://iban/DE75512108001245126199?amount=EUR:200.0&message=hello',
+ 'payto://ach/122000661/1234',
+ 'payto://upi/alice@example.com?receiver-name=Alice&amount=INR:200',
+ 'payto://void/?amount=EUR:10.5',
+ 'payto://ilp/g.acme.bob'
+ ]
+
+ test('should be valid', () => {
+ valids.forEach(v => expect(v).toMatch(PAYTO_REGEX))
+ });
+
+ const invalids = [
+ // has two question marks
+ 'payto://iban/DE75?512108001245126199?amount=EUR:200.0&message=hello',
+ // has a space
+ 'payto://ach /122000661/1234',
+ // has a space
+ 'payto://upi/alice@ example.com?receiver-name=Alice&amount=INR:200',
+ // invalid field name (mount instead of amount)
+ 'payto://void/?mount=EUR:10.5',
+ // payto:// is incomplete
+ 'payto: //ilp/g.acme.bob'
+ ]
+
+ test('should not be valid', () => {
+ invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX))
+ });
+})
-test('should be valid', () => {
- valids.forEach(v => expect(v).toMatch(PAYTO_REGEX))
-});
+describe('amount format', () => {
+ const valids = [
+ 'ARS:10',
+ 'COL:10.2',
+ 'UY:1,000.2',
+ 'ARS:10.123,123',
+ 'ARS:1,000,000',
+ 'ARSCOL:10',
+ 'THISISTHEMOTHERCOIN:1,000,000.123,123',
+ ]
+
+ test('should be valid', () => {
+ valids.forEach(v => expect(v).toMatch(AMOUNT_REGEX))
+ });
+
+ const invalids = [
+ //no currency name
+ ':10',
+ //use . instead of ,
+ 'ARS:1.000.000',
+ //currency name with numbers
+ '1ARS:10',
+ //currency name with numbers
+ 'AR5:10',
+ //missing value
+ 'USD:',
+ ]
+
+ test('should not be valid', () => {
+ invalids.forEach(v => expect(v).not.toMatch(AMOUNT_REGEX))
+ });
-const invalids = [
- 'payto://iban/DE75?512108001245126199?amount=EUR:200.0&message=hello',
- 'payto://ach/122000661 /1234',
- 'payto://upi/alice@ example.com?receiver-name=Alice&amount=INR:200',
- 'payto://void/?mount=EUR:10.5',
- 'payto: //ilp/g.acme.bob'
-]
-
-test('should not be valid', () => {
- invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX))
-});
+})
+\ No newline at end of file