[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] branch master updated: amount regex and dura
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] branch master updated: amount regex and duration fields |
Date: |
Fri, 12 Feb 2021 06:43:20 +0100 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository merchant-backoffice.
The following commit(s) were added to refs/heads/master by this push:
new 5002a44 amount regex and duration fields
5002a44 is described below
commit 5002a444a7d207295457cf6c21e5192c5c49c59c
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Feb 12 02:43:11 2021 -0300
amount regex and duration fields
---
src/components/yup/YupInput.tsx | 40 ++++++++++-------
src/constants.ts | 2 +-
src/i18n/index.ts | 12 +++++
src/routes/instances/CreateModal.tsx | 34 ++-------------
src/routes/instances/UpdateModal.tsx | 24 ++--------
src/schemas/index.ts | 85 ++++++++++++++++++++++++++++++++++++
tests/functions/regex.test.ts | 85 ++++++++++++++++++++++++++----------
7 files changed, 192 insertions(+), 90 deletions(-)
diff --git a/src/components/yup/YupInput.tsx b/src/components/yup/YupInput.tsx
index b10ff22..faf8da4 100644
--- 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
index fec7f7c..826aa61 100644
--- 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
index 9cb060d..7a99719 100644
--- 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
index b66b483..d9e9828 100644
--- 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
index 9fc1309..8340465 100644
--- 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
new file mode 100644
index 0000000..53cd709
--- /dev/null
+++ 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
index 2c1d63f..696a515 100644
--- 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
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-merchant-backoffice] branch master updated: amount regex and duration fields,
gnunet <=