merchant-backoffice

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

commit 8a0fbe79ef3c16cd256c4ebc35f4e8b0bc38dba3
parent 1a9abf809f9f0fba683da9f76d2b19c15ce81cde
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu, 11 Feb 2021 18:11:39 -0300

more i18n

Diffstat:
MREADME.md | 2++
Asrc/components/modal/index.tsx | 32++++++++++++++++++++++++++++++++
Asrc/components/yup/YupInput.tsx | 32++++++++++++++++++++++++++++++++
Msrc/i18n/index.ts | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/routes/instances/CardTable.tsx | 3++-
Dsrc/routes/instances/ConfirmModal.tsx | 31-------------------------------
Asrc/routes/instances/CreateModal.stories.tsx | 16++++++++++++++++
Msrc/routes/instances/CreateModal.tsx | 27+++++----------------------
Msrc/routes/instances/DeleteModal.tsx | 4++--
Msrc/routes/instances/EmptyTable.tsx | 3++-
Msrc/routes/instances/Table.tsx | 9+++++----
Msrc/routes/instances/UpdateModal.stories.tsx | 6+++---
Msrc/routes/instances/UpdateModal.tsx | 22++++++++++------------
Msrc/routes/instances/View.stories.tsx | 34++++++++++++++++++++++++++--------
Msrc/routes/instances/View.tsx | 7++++---
15 files changed, 242 insertions(+), 87 deletions(-)

diff --git a/README.md b/README.md @@ -47,6 +47,8 @@ Use the browser to navigate into `http://localhost:8080` * preact-router: URL component router for Preact +* preact-i18n: Simple localization for Preact + * SWR: React Hooks library for data fetching. (stale-while-revalidate) * Yup: schema builder for value parsing and validation diff --git a/src/components/modal/index.tsx b/src/components/modal/index.tsx @@ -0,0 +1,31 @@ +import { h, VNode } from "preact"; +import { Text } from "preact-i18n"; + +interface Props { + active?: boolean; + description?: string; + onCancel?: () => void; + onConfirm?: () => void; + children?: VNode[]; + danger?: boolean; +} + +export default function ConfirmModal({ active, description, onCancel, onConfirm, children, danger }: Props): VNode { + return <div class={active ? "modal is-active is-clipped" : "modal"}> + <div class="modal-background jb-modal-close" onClick={onCancel} /> + <div class="modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title"> <Text id="confirm_modal.title" /> { !description ? null : <Text id={`confirm_modal.${description}`} /> }</p> + <button class="delete jb-modal-close" aria-label="close" onClick={onCancel} /> + </header> + <section class="modal-card-body"> + {children} + </section> + <footer class="modal-card-foot"> + <button class="button jb-modal-close" onClick={onCancel} ><Text id="cancel" /></button> + <button class={danger ? "button is-danger jb-modal-close" : "button is-info jb-modal-close"} onClick={onConfirm} ><Text id="confirm" /></button> + </footer> + </div> + <button class="modal-close is-large jb-modal-close" aria-label="close" onClick={onCancel} /> + </div> +} +\ No newline at end of file diff --git a/src/components/yup/YupInput.tsx b/src/components/yup/YupInput.tsx @@ -0,0 +1,32 @@ +import { h, VNode } from "preact"; +import { Text, useText } from "preact-i18n"; + +interface Props { + name: string; + value: 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`}) + + 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]} + 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} + </div> + </div> +</div> +} diff --git a/src/i18n/index.ts b/src/i18n/index.ts @@ -1,18 +1,119 @@ export default { es: { + confirm_modal: { + title: 'confirmar accion', + create_instance: 'crear instancia', + delete_instance: 'borrar instancia', + update_instance: 'actualizar instancia', + }, notification: { unauthorized: { title: 'acceso no autorizado', description: 'el servidor a denegado el acceso' }, }, + cancel: 'cancelar', + confirm: 'confirmar', + fields: { + instance: { + id: { + label: 'Id', + }, + merchant_pub: { + label: 'Clave pública' + }, + payment_targets: { + label: 'Dirección de pago', + }, + name: { + label: 'Nombre', + }, + payto_uris: { + label: 'PaytTO URI', + placeholder: 'comma separated values', + help: 'payto://x-taler-bank/bank.taler:5882/blogger', + }, + default_max_deposit_fee: { + label: 'Máximo pago por depósito', + }, + default_max_wire_fee: { + label: 'Máximo pago por transferencia bancaria', + }, + default_wire_fee_amortization: { + label: 'Amortización de pago', + }, + }, + }, + validation: { + required: '{{label}} es obligatorio', + typeError: '{{label}} ' + }, + text: { + instances: 'Instancias', + merchant: 'Merchant', + list_of_configured_instances: 'Lista de instancias configuradas', + instance: { + empty_list: 'No instance configured yet, setup one pressing the + button', + } + } }, en: { + confirm_modal: { + title: 'confirm action', + create_instance: 'create instance', + delete_instance: 'delete instance', + update_instance: 'update instance', + }, notification: { unauthorized: { title: 'unauthorized access', description: 'backend has denied access' }, }, + cancel: 'cancel', + confirm: 'confirm', + fields: { + instance: { + id: { + label: 'Id', + }, + merchant_pub: { + label: 'Public Key' + }, + payment_targets: { + label: 'Payment targets', + }, + name: { + label: 'Name', + }, + payto_uris: { + label: 'PaytTO URI', + placeholder: 'comma separated values', + help: 'payto://x-taler-bank/bank.taler:5882/blogger', + }, + default_max_deposit_fee: { + label: 'Max deposit fee', + }, + default_max_wire_fee: { + label: 'Max wire fee', + }, + default_wire_fee_amortization: { + label: 'Max fee amortization', + }, + } + }, + validation: { + required: '{{label}} is required', + typeError: '{{label}} ' + }, + text: { + instances: 'Instances', + merchant: 'Merchant', + list_of_configured_instances: 'List of configured instances', + + instance: { + empty_list: 'No instance configured yet, setup one pressing the + button', + } + }, }, } \ No newline at end of file diff --git a/src/routes/instances/CardTable.tsx b/src/routes/instances/CardTable.tsx @@ -1,4 +1,5 @@ import { h, VNode } from "preact"; +import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; import { MerchantBackend, WidthId as WithId } from "../../declaration"; import EmptyTable from "./EmptyTable"; @@ -47,7 +48,7 @@ export default function CardTable({ instances, onCreate, onSelect, selected }: P return <div class="card has-table"> <header class="card-header"> - <p class="card-header-title"><span class="icon"><i class="mdi mdi-account-multiple" /></span>Instances</p> + <p class="card-header-title"><span class="icon"><i class="mdi mdi-account-multiple" /></span><Text id="text.instances" /></p> <button class={rowSelection.length > 0 ? "card-header-icon" : "card-header-icon is-hidden"} type="button" onClick={(): void => actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} > diff --git a/src/routes/instances/ConfirmModal.tsx b/src/routes/instances/ConfirmModal.tsx @@ -1,30 +0,0 @@ -import { h, VNode } from "preact"; - -interface Props { - active?: boolean; - description?: string; - onCancel?: () => void; - onConfirm?: () => void; - children?: VNode[]; - danger?: boolean; -} - -export default function ConfirmModal({ active, description, onCancel, onConfirm, children, danger }: Props): VNode { - return <div class={active ? "modal is-active is-clipped" : "modal"}> - <div class="modal-background jb-modal-close" onClick={onCancel} /> - <div class="modal-card"> - <header class="modal-card-head"> - <p class="modal-card-title">Confirm action: {description}</p> - <button class="delete jb-modal-close" aria-label="close" onClick={onCancel} /> - </header> - <section class="modal-card-body"> - {children} - </section> - <footer class="modal-card-foot"> - <button class="button jb-modal-close" onClick={onCancel} >Cancel</button> - <button class={danger ? "button is-danger jb-modal-close" : "button is-info jb-modal-close"} onClick={onConfirm} >Confirm</button> - </footer> - </div> - <button class="modal-close is-large jb-modal-close" aria-label="close" onClick={onCancel} /> - </div> -} -\ No newline at end of file diff --git a/src/routes/instances/CreateModal.stories.tsx b/src/routes/instances/CreateModal.stories.tsx @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { h, VNode } from 'preact'; +import CreateModal from './CreateModal' + + +export default { + title: 'Instances/CreateModal', + component: CreateModal, + argTypes: { + element: { control: 'object' }, + onCancel: { action: 'onCancel' }, + onConfirm: { action: 'onConfirm' }, + } +}; + +export const Example = (a: any): VNode => <CreateModal {...a} />; diff --git a/src/routes/instances/CreateModal.tsx b/src/routes/instances/CreateModal.tsx @@ -2,7 +2,8 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Duration, MerchantBackend } from "../../declaration"; import * as yup from 'yup'; -import ConfirmModal from "./ConfirmModal"; +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; @@ -25,7 +26,6 @@ const schema = yup.object().shape({ name: yup.string().required().label("Name"), payto_uris: yup.array().of(yup.string()) .min(1).label("Payment Method") - .meta({ placeholder: 'comma separated values', help: 'payto://x-taler-bank/bank.taler:5882/blogger' }) .transform(stringToArray), default_max_deposit_fee: yup.string().required().label("Max Deposit Fee"), default_max_wire_fee: yup.string().required().label("Max Wire"), @@ -53,32 +53,15 @@ export default function CreateModal({ onCancel, onConfirm }: Props): VNode { onConfirm({ ...schema.cast(value), address: {}, jurisdiction: {}, default_wire_transfer_delay: { d_ms: 6000 }, default_pay_delay: { d_ms: 3000 } } as MerchantBackend.Instances.InstanceConfigurationMessage); } catch (err) { const errors = err.inner as yup.ValidationError[] - const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: cur.message }), {}) + const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message } }), {}) setErrors(pathMessages) } } - return <ConfirmModal description="create instance" active onConfirm={submit} onCancel={onCancel}> + return <ConfirmModal description="create_instance" active onConfirm={submit} onCancel={onCancel}> {Object.keys(schema.fields).map(f => { - const info = schema.fields[f].describe() - - // Just text field for now - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label">{info.label}</label> - </div> - <div class="field-body"> - <div class="field"> - <p class="control is-expanded has-icons-left"> - <input class="input" type="text" placeholder={info?.meta?.placeholder} readonly={info?.meta?.readonly} name={f} value={value[f]} onChange={(e): void => valueHandler((prev: any) => ({ ...prev, [f]: e.currentTarget.value }))} /> - {info?.meta?.help} - </p> - {errors[f] ? <p class="help is-danger">{errors[f]}</p> : null} - </div> - </div> - </div> - + return <YupInput name={f} info={info} errors={errors} value={value} valueHandler={valueHandler} /> })} </ConfirmModal> diff --git a/src/routes/instances/DeleteModal.tsx b/src/routes/instances/DeleteModal.tsx @@ -1,6 +1,6 @@ import { h, VNode } from "preact"; import { MerchantBackend, WidthId } from "../../declaration"; -import ConfirmModal from "./ConfirmModal"; +import ConfirmModal from "../../components/modal"; interface Props { element: MerchantBackend.Instances.QueryInstancesResponse & WidthId; @@ -9,7 +9,7 @@ interface Props { } export default function DeleteModal({ element, onCancel, onConfirm }: Props): VNode { - return <ConfirmModal description="delete instance" danger active onCancel={onCancel} onConfirm={() => onConfirm(element)}> + return <ConfirmModal description="delete_instance" danger active onCancel={onCancel} onConfirm={() => onConfirm(element)}> <p>This will permanently delete instance "{element.name}" with id <b>{element.id}</b></p> <p>Please confirm this action</p> </ConfirmModal> diff --git a/src/routes/instances/EmptyTable.tsx b/src/routes/instances/EmptyTable.tsx @@ -1,10 +1,11 @@ import { h, VNode } from "preact"; +import { Text } from "preact-i18n"; export default function EmptyTable(): VNode { return <div class="content has-text-grey has-text-centered"> <p> <span class="icon is-large"><i class="mdi mdi-emoticon-sad mdi-48px" /></span> </p> - <p>No instance configured yet, setup one pressing the + button </p> + <p><Text id="text.intance.empty_list" /></p> </div> } diff --git a/src/routes/instances/Table.tsx b/src/routes/instances/Table.tsx @@ -1,4 +1,5 @@ import { h, VNode } from "preact" +import { Text } from "preact-i18n" import { StateUpdater } from "preact/hooks" import { MerchantBackend } from "../../declaration" @@ -24,10 +25,10 @@ export default function Table({ rowSelection, rowSelectionHandler, instances, on <span class="check" /> </label> </th> - <th>id</th> - <th>name</th> - <th>public key</th> - <th>payments</th> + <th><Text id="fields.instance.id.label" /></th> + <th><Text id="fields.instance.name.label" /></th> + <th><Text id="fields.instance.merchant_pub.label" /></th> + <th><Text id="fields.instance.payment_targets.label" /></th> <th /> </tr> </thead> diff --git a/src/routes/instances/UpdateModal.stories.tsx b/src/routes/instances/UpdateModal.stories.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { h } from 'preact'; +import { h, VNode } from 'preact'; import UpdateModal from './UpdateModal' @@ -13,11 +13,11 @@ export default { } }; -export const WithDefaultInstance = (a: any) => <UpdateModal {...a} />; +export const WithDefaultInstance = (a: any): VNode => <UpdateModal {...a} />; WithDefaultInstance.args = { element: { id: 'default', - name: 'the default instance', + name: 'update', merchant_pub: 'abcdef', payment_targets: ['qwe','asd'] } diff --git a/src/routes/instances/UpdateModal.tsx b/src/routes/instances/UpdateModal.tsx @@ -2,7 +2,8 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { MerchantBackend } from "../../declaration"; import * as yup from 'yup'; -import ConfirmModal from "./ConfirmModal"; +import ConfirmModal from '../../components/modal' +import { Text } from "preact-i18n"; function stringToArray(this: yup.AnySchema, current: any, original: string): string[] { if (this.isType(current)) return current; @@ -10,14 +11,11 @@ function stringToArray(this: yup.AnySchema, current: any, original: string): str } const schema = yup.object().shape({ - name: yup.string().required().label("Name"), - payto_uris: yup.array().of(yup.string()) - .min(1).label("Payment Method") - .meta({ placeholder: 'comma separated values', help: 'payto://x-taler-bank/bank.taler:5882/blogger' }) - .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"), + name: yup.string().required(), + payto_uris: yup.array().of(yup.string()).min(1).transform(stringToArray), + 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), }); @@ -45,12 +43,12 @@ export default function UpdateModal({ element, onCancel, onConfirm }: Props): VN onConfirm({...schema.cast(value), address: {}, jurisdiction: {}, default_wire_transfer_delay: { d_ms: 6000 }, default_pay_delay: { d_ms: 3000 }} 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]: cur.message }), {}) + const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message } }), {}) setErrors(pathMessages) } } - return <ConfirmModal description="update instance" active={element != null} onConfirm={submit} onCancel={onCancel}> + return <ConfirmModal description="update_instance" active={element != null} onConfirm={submit} onCancel={onCancel}> {Object.keys(schema.fields).map(f => { const info = schema.fields[f].describe() @@ -58,7 +56,7 @@ export default function UpdateModal({ element, onCancel, onConfirm }: Props): VN // Just text field for now return <div class="field is-horizontal"> <div class="field-label is-normal"> - <label class="label">{info.label}</label> + <label class="label"><Text id={`fields.instance.${f}.label`} /></label> </div> <div class="field-body"> <div class="field"> diff --git a/src/routes/instances/View.stories.tsx b/src/routes/instances/View.stories.tsx @@ -7,9 +7,7 @@ export default { title: 'Instances/View', component: View, argTypes: { - onLogin: { action: 'onLogin' }, - onLogout: { action: 'onLogout' }, - onCreateAccount: { action: 'onCreateAccount' }, + onSelect: { action: 'onSelect' }, }, }; @@ -28,17 +26,37 @@ WithDefaultInstance.args = { }] } -export const WithTwoInstance = (a: any) => <View {...a} />; -WithTwoInstance.args = { +export const WithFiveInstance = (a: any) => <View {...a} />; +WithFiveInstance.args = { instances: [{ id: 'first', name: 'the first instance', merchant_pub: 'abcdefgh', - payment_targets: [] + payment_targets: ['asd'] },{ id: 'second', - name: 'other instance', + name: 'the second instance', + merchant_pub: 'zxczxcz', + payment_targets: ['asd'] + },{ + id: 'third', + name: 'the third instance', + merchant_pub: 'QWEQWEWQE', + payment_targets: ['asd'] + },{ + id: 'other', + name: 'the other instance', + merchant_pub: 'FHJHGJGHJ', + payment_targets: ['asd'] + },{ + id: 'another', + name: 'the another instance', + merchant_pub: 'abcd3423423efgh', + payment_targets: ['asd'] + },{ + id: 'last', + name: 'last instance', merchant_pub: 'zxcvvbnm', - payment_targets: ['pay-to'] + payment_targets: ['pay-to','asd'] }] } diff --git a/src/routes/instances/View.tsx b/src/routes/instances/View.tsx @@ -5,6 +5,7 @@ import Table from './CardTable'; import DeleteModal from './DeleteModal' import UpdateModal from './UpdateModal' import CreateModal from './CreateModal' +import { Text } from "preact-i18n"; interface Props { instances: MerchantBackend.Instances.Instance[]; @@ -32,8 +33,8 @@ export default function View({ instances, isLoading, onCreate, onDelete, onSelec <div class="level-left"> <div class="level-item"> <ul> - <li>Merchant</li> - <li>Instances</li> + <li><Text id="text.merchant" /></li> + <li><Text id="text.instances" /></li> </ul> </div> </div> @@ -45,7 +46,7 @@ export default function View({ instances, isLoading, onCreate, onDelete, onSelec <div class="level"> <div class="level-left"> <div class="level-item"> - <h1 class="title">List of configured instances</h1> + <h1 class="title"><Text id="text.list_of_configured_instances" /></h1> </div> </div> <div class="level-right" style="display: none;">