merchant-backoffice

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

commit 929e8f389ea8eec3bb942c111f8e37efffba2d63
parent d37369bbdeb9ff471cee42e794076a3d7d7eda45
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu, 18 Feb 2021 09:46:00 -0300

payto as array

Diffstat:
Msrc/components/hooks/useLang.ts | 2+-
Msrc/components/hooks/useLocalStorage.ts | 26+++++++++++++++-----------
Msrc/components/navbar/index.tsx | 5+++--
Msrc/components/yup/YupField.tsx | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/declaration.d.ts | 5+++--
Msrc/i18n/index.ts | 2+-
Msrc/routes/instances/CardTable.tsx | 21+++++++++++++--------
Msrc/schemas/index.ts | 22++++++++++------------
8 files changed, 108 insertions(+), 47 deletions(-)

diff --git a/src/components/hooks/useLang.ts b/src/components/hooks/useLang.ts @@ -2,5 +2,5 @@ import { StateUpdater } from "preact/hooks"; import { useNotNullLocalStorage } from "./useLocalStorage"; export default function useLang(): [string, StateUpdater<string>] { - return useNotNullLocalStorage('lang-preference', navigator.language || (navigator as any).userLanguage ) + return useNotNullLocalStorage('lang-preference', typeof window !== "undefined" ? navigator.language || (navigator as any).userLanguage : 'en' ) } diff --git a/src/components/hooks/useLocalStorage.ts b/src/components/hooks/useLocalStorage.ts @@ -2,16 +2,18 @@ import { StateUpdater, useState } from "preact/hooks"; export function useLocalStorage(key: string, initialValue?: string): [string | undefined, StateUpdater<string | undefined>] { const [storedValue, setStoredValue] = useState<string | undefined>((): string | undefined => { - return window.localStorage.getItem(key) || initialValue; + return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; }); const setValue = (value?: string | ((val?: string) => string | undefined)) => { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); - if (!valueToStore) { - window.localStorage.removeItem(key) - } else { - window.localStorage.setItem(key, valueToStore); + if (typeof window !== "undefined") { + if (!valueToStore) { + window.localStorage.removeItem(key) + } else { + window.localStorage.setItem(key, valueToStore); + } } }; @@ -20,17 +22,19 @@ export function useLocalStorage(key: string, initialValue?: string): [string | u export function useNotNullLocalStorage(key: string, initialValue: string): [string, StateUpdater<string>] { const [storedValue, setStoredValue] = useState<string>((): string => { - return window.localStorage.getItem(key) || initialValue; + return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; }); const setValue = (value: string | ((val: string) => string)) => { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); - if (!valueToStore) { - window.localStorage.removeItem(key) - } else { - window.localStorage.setItem(key, valueToStore); - } + if (typeof window !== "undefined") { + if (!valueToStore) { + window.localStorage.removeItem(key) + } else { + window.localStorage.setItem(key, valueToStore); + } + } }; return [storedValue, setValue]; diff --git a/src/components/navbar/index.tsx b/src/components/navbar/index.tsx @@ -2,7 +2,8 @@ import { h, VNode } from 'preact'; import { useState } from 'preact/hooks'; import LoginPage from '../auth/LoginPage'; import i18n from '../../i18n' -import logo from '../../assets/logo.jpeg' +// TODO: Fix compilation problem +// import * as logo from '../../assets/logo.jpeg'; interface Props { lang: string; @@ -14,7 +15,7 @@ export default function NavigationBar({ lang, setLang }: Props): VNode { return (<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation"> <div class="navbar-brand"> <a class="navbar-item" href="https://taler.net"> - <img src={logo} style={{height: 50, maxHeight: 50}} /> + <img src="https://taler.net/static/images/logo-2020.jpg" style={{height: 50, maxHeight: 50}} /> </a> <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample"> diff --git a/src/components/yup/YupField.tsx b/src/components/yup/YupField.tsx @@ -1,7 +1,6 @@ import { h, VNode } from "preact"; -import { Localizer, Text, useText } from "preact-i18n"; +import { Text, useText } from "preact-i18n"; import { useState } from "preact/hooks"; -import { KeyValue } from "../../declaration"; import { useBackendConfig } from "../hooks/backend"; import useBackend from "../hooks/useBackend"; @@ -31,15 +30,17 @@ export default function YupField(name: string, field: string, errors: any, objec onChange: updateField(field) } const [backend] = useBackend() - const {data} = useBackendConfig() - + const { data } = useBackendConfig() + const currency = data?.currency || '' + switch (info.meta?.type) { case 'group': return <YupObjectInput name={name} info={info} errors={errors} value={object && object[field]} onChange={(updater: any): void => valueHandler((prev: any) => ({ ...prev, [field]: updater(prev[field]) }))} /> - case 'amount': return <YupInputWithAddon {...values} addon={data?.currency || ''} />; + case 'array': return <YupInputArray {...values} />; + case 'amount': return <YupInputWithAddon {...values} addon={currency} onChange={(v: string): void => values.onChange(`${currency}:${v}`)} value={values.value?.split(':')[1]} />; case 'url': return <YupInputWithAddon {...values} addon={`${backend.url}/private/instances/`} />; case 'secured': return <YupInputSecured {...values} />; case 'duration': return <YupInput {...values} value={object && object[field]?.d_ms} />; @@ -57,9 +58,9 @@ function YupObjectInput({ name, info, value, errors, onChange }: Props2): VNode </p> <button class="card-header-icon" aria-label="more options" onClick={(): void => setActive(!active)}> <span class="icon"> - { active ? + {active ? <i class="mdi mdi-arrow-up" /> : - <i class="mdi mdi-arrow-down" /> } + <i class="mdi mdi-arrow-down" />} </span> </button> </header> @@ -90,7 +91,7 @@ function YupInput({ name, readonly, value, errors, onChange }: Props): VNode { <div class="field"> <p class="control"> <input class={errors[name] ? "input is-danger" : "input"} type="text" - placeholder={dict['placeholder']} readonly={readonly} + placeholder={dict.placeholder} readonly={readonly} name={name} value={value} onChange={(e): void => onChange(e.currentTarget.value)} /> <Text id={`fields.instance.${name}.help`} /> @@ -103,6 +104,57 @@ function YupInput({ name, readonly, value, errors, onChange }: Props): VNode { </div> } +function YupInputArray({ name, readonly, value, errors, onChange }: Props): VNode { + const dict = useText({ + placeholder: `fields.instance.${name}.placeholder`, + tooltip: `fields.instance.${name}.tooltip`, + }) + const array = value as unknown as string[] || [] + const [currentValue, setCurrentValue] = useState('') + + return <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <Text id={`fields.instance.${name}.label`} /> + {dict.tooltip && <span class="icon" data-tooltip={dict.tooltip}> + <i class="mdi mdi-information" /> + </span>} + </label> + </div> + <div class="field-body"> + <div class="field"> + <div class="field has-addons"> + <p class="control"> + <button class="button is-info" onClick={(): void => { + onChange([currentValue, ...array]) + setCurrentValue('') + }} >add</button> + </p> + <p class="control"> + <input class={errors[name] ? "input is-danger" : "input"} type="text" + placeholder={dict.placeholder} readonly={readonly} + name={name} value={currentValue} + onChange={(e): void => setCurrentValue(e.currentTarget.value)} /> + <Text id={`fields.instance.${name}.help`} /> + </p> + </div> + {errors[name] ? <p class="help is-danger"> + <Text id={`validation.${errors[name].type}`} fields={errors[name].params}>{errors[name].message}</Text> + </p> : null} + {array.map(v => <div class="tags has-addons"> + <span class="tag is-medium is-info">{v}</span> + <a class="tag is-medium is-danger is-delete" onClick={() => { + onChange(array.filter(f => f !== v)) + setCurrentValue(v) + }} /> + </div> + )} + </div> + + </div> + </div> +} + function YupInputWithAddon({ name, readonly, value, errors, onChange, addon }: Props & { addon: string }): VNode { const dict = useText({ placeholder: `fields.instance.${name}.placeholder`, @@ -126,7 +178,7 @@ function YupInputWithAddon({ name, readonly, value, errors, onChange, addon }: P </div> <p class="control is-expanded"> <input class={errors[name] ? "input is-danger" : "input"} type="text" - placeholder={dict['placeholder']} readonly={readonly} + placeholder={dict.placeholder} readonly={readonly} name={name} value={value} onChange={(e): void => onChange(e.currentTarget.value)} /> <Text id={`fields.instance.${name}.help`} /> @@ -145,7 +197,7 @@ function YupInputSecured({ name, readonly, value, errors, onChange }: Props): VN }) const [active, setActive] = useState(false) - + return <div class="field is-horizontal"> <div class="field-label is-normal"> <label class="label"> diff --git a/src/declaration.d.ts b/src/declaration.d.ts @@ -5,9 +5,10 @@ declare module "*.css" { export default mapping; } declare module "*.jpeg" { - const mapping: Record<string, string>; - export default mapping; + const value: any; + export = value; } + declare module "*.scss" { const mapping: Record<string, string>; export default mapping; diff --git a/src/i18n/index.ts b/src/i18n/index.ts @@ -151,7 +151,7 @@ export default { }, payto_uris: { label: 'Bank accounts', - placeholder: 'comma separated values', + tooltip: 'Bank account URI', help: 'payto://x-taler-bank/bank.taler:5882/blogger', }, default_max_deposit_fee: { diff --git a/src/routes/instances/CardTable.tsx b/src/routes/instances/CardTable.tsx @@ -49,15 +49,20 @@ 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><Text id="text.instances" /></p> - - <button class={rowSelection.length > 0 ? "card-header-icon" : "is-hidden"} - type="button" onClick={(): void => actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} > - <span class="icon"><i class="mdi mdi-trash-can" /></span> - </button> - <button class="card-header-icon" type="button" onClick={onCreate}> - <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span> - </button> + <div class="card-header-icon" aria-label="more options"> + + <button class={rowSelection.length > 0 ? "button is-danger" : "is-hidden"} + type="button" onClick={(): void => actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} > + <span class="icon"><i class="mdi mdi-trash-can" /></span> + </button> + </div> + <div class="card-header-icon" aria-label="more options"> + <button class="button is-info" type="button" onClick={onCreate}> + <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span> + </button> + </div> + </header> <div class="card-content"> <div class="b-table has-pagination"> diff --git a/src/schemas/index.ts b/src/schemas/index.ts @@ -12,13 +12,8 @@ yup.setLocale({ }, }); -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; + return !!values && values.every(v => v && PAYTO_REGEX.test(v)); } function numberToDuration(this: yup.AnySchema, current: any, original: string): Duration { @@ -33,10 +28,13 @@ function currencyWithAmountIsValid(value?: string): boolean { const InstanceSchema = yup.object().shape({ id: yup.string().required().meta({type: 'url'}), name: yup.string().required(), - auth_token: yup.string().min(8).max(20).optional().meta({type: 'secured'}), + auth_token: yup.string() + .min(8).max(20) + .optional() + .meta({type: 'secured'}), payto_uris: yup.array().of(yup.string()) .min(1) - .transform(stringToArray) + .meta({type: 'array'}) .test('payto', '{path} is not valid', listOfPayToUrisAreValid), default_max_deposit_fee: yup.string() .required() @@ -75,13 +73,13 @@ const InstanceSchema = yup.object().shape({ default_pay_delay: yup.object() .shape({ d_ms: yup.number() }) .required() - .meta({ type: 'duration' }) - .transform(numberToDuration), + .meta({ type: 'duration' }), + // .transform(numberToDuration), default_wire_transfer_delay: yup.object() .shape({ d_ms: yup.number() }) .required() - .meta({ type: 'duration' }) - .transform(numberToDuration), + .meta({ type: 'duration' }), + // .transform(numberToDuration), }) export const InstanceUpdateSchema = InstanceSchema.clone().omit(['id']);