merchant-backoffice

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

commit 877bcbb0a5ed1c560ee8ff41270dc6d0664c98e9
parent e0e111cc971431e83b68f1c095a99e3df84e9f79
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu, 25 Feb 2021 16:48:10 -0300

severals fixes

Diffstat:
MCHANGELOG.md | 16++++++++++++----
Mpackages/frontend/src/ApplicationReadyRoutes.tsx | 2+-
Mpackages/frontend/src/InstanceRoutes.tsx | 10+++++-----
Mpackages/frontend/src/components/auth/index.tsx | 35++++++++++++++++++-----------------
Mpackages/frontend/src/components/form/Field.tsx | 7++++++-
Mpackages/frontend/src/components/form/InputArray.tsx | 34+++++++++++++++++++++++++++-------
Mpackages/frontend/src/components/form/InputCurrency.tsx | 2+-
Mpackages/frontend/src/components/form/InputDuration.tsx | 2+-
Apackages/frontend/src/components/form/InputPayto.tsx | 40++++++++++++++++++++++++++++++++++++++++
Mpackages/frontend/src/components/form/InputWithAddon.tsx | 14+++++++-------
Mpackages/frontend/src/components/navbar/index.tsx | 51+++++++++++++++++++++++++++++++++++++--------------
Mpackages/frontend/src/components/sidebar/index.tsx | 34++++++++++++++++++++++------------
Mpackages/frontend/src/hooks/backend.ts | 4+---
Mpackages/frontend/src/index.tsx | 16++++++++++------
Mpackages/frontend/src/messages/en.po | 8+++++++-
Mpackages/frontend/src/routes/instances/create/CreatePage.tsx | 9++++-----
Mpackages/frontend/src/routes/instances/list/Table.tsx | 9+--------
Mpackages/frontend/src/routes/instances/update/UpdatePage.tsx | 6++----
Mpackages/frontend/src/scss/_theme-default.scss | 30++++++++++++++++++++----------
Mpackages/frontend/src/utils/constants.ts | 3++-
20 files changed, 224 insertions(+), 108 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -6,16 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Future work] - date format (error handling) - - bug: set text int the intpu date (seconds) - connection errors - allow point separator for amounts - prevent letters to be input in numbers - red color when input is invalid (onchange) - - prepend payto:// to account - validate everything using onChange - - all button to the right - feature: input as date format - bug: there is missing a mutate call when updating to remove the instance from cache + - submit form on key press == enter - add order section - add product section @@ -27,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - configure prettier - prune scss styles to reduce size -## [Unreleased] +## [0.0.2] - 2021-02-25 - REFACTOR: remove react-i18n and implement messageformat - REFACTOR: routes definitions to allow nested routes and tokens - REFACTOR: remove yup from input form defitions @@ -44,6 +42,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - show the connection state (url, currency, version) in the sidebar - add backend url without slash - added linter rule for source header + - bug: set text int the intpu date (seconds) + - row in the list instance are now clickable + - re implemented the language selector, remove the current lang from the dropdown + - remove payment adress and public key from instance listing + - fix bug on CORS error + - moved the login button to the sidebar + - remove the details page, go directly to the update page + - login modal: url before token, and removed the checkbox + - added payto:// to the field + - validate payto_uris on add ## [0.0.1] - 2021-02-18 ### Changed diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx b/packages/frontend/src/ApplicationReadyRoutes.tsx @@ -53,7 +53,7 @@ export function ApplicationReadyRoutes({ pushNotification, addTokenCleaner }: { }} onUpdate={(id: string): void => { - route(`/instance/${id}/`); + route(`/instance/${id}/update`); }} onUnauthorized={() => <LoginPage diff --git a/packages/frontend/src/InstanceRoutes.tsx b/packages/frontend/src/InstanceRoutes.tsx @@ -57,7 +57,7 @@ export function InstanceRoutes({ id, pushNotification, addTokenCleaner }: Props) return <InstanceContext.Provider value={{ id, token }}> <Router> - <Route path={InstancePaths.details} + {/* <Route path={InstancePaths.details} component={InstanceDetailsPage} pushNotification={pushNotification} @@ -77,7 +77,7 @@ export function InstanceRoutes({ id, pushNotification, addTokenCleaner }: Props) pushNotification({ message: i18n`update_load_error`, type: 'ERROR', params: e }); route(`/instance/${id}/`); return <div />; - }} /> + }} /> */} <Route path={InstancePaths.update} component={InstanceUpdatePage} @@ -87,15 +87,15 @@ export function InstanceRoutes({ id, pushNotification, addTokenCleaner }: Props) onConfirm={updateLoginStatus} />} onLoadError={(e: SwrError) => { pushNotification({ message: i18n`update_load_error`, type: 'ERROR', params: e }); - route(`/instance/${id}/`); + route(`/instances`); return <div />; }} onBack={() => { - route(`/instance/${id}/`); + route(`/instances`); }} onConfirm={() => { pushNotification({ message: i18n`create_success`, type: 'SUCCESS' }); - route(`/instance/${id}/`); + route(`/instances`); }} onUpdateError={(e: Error) => { pushNotification({ message: i18n`update_error`, type: 'ERROR', params: e }); diff --git a/packages/frontend/src/components/auth/index.tsx b/packages/frontend/src/components/auth/index.tsx @@ -32,10 +32,10 @@ interface Props { export function LoginModal({ onConfirm, withMessage }: Props): VNode { const backend = useContext(BackendContext) - const [updatingToken, setUpdatingToken] = useState(false) const [token, setToken] = useState(backend.token || '') const [url, setURL] = useState(backend.url) - const toggleUpdatingToken = (): void => setUpdatingToken(v => !v) + // const [updatingToken, setUpdatingToken] = useState(false) + // const toggleUpdatingToken = (): void => setUpdatingToken(v => !v) return <div class="modal is-active is-clipped"> <div class="modal-background" /> @@ -43,11 +43,11 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { {withMessage && <div class={withMessage.type === 'ERROR' ? "notification is-danger" : "notification is-info"}> <div class="columns is-vcentered"> <div class="column is-12"> - <div> - <p><strong>{withMessage.message}</strong></p> - {withMessage.description} - </div> + <div> + <p><strong>{withMessage.message}</strong></p> + {withMessage.description} </div> + </div> </div> </div>} <header class="modal-card-head"> @@ -57,29 +57,30 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { Please enter your auth token. Token should have "secret-token:" and start with Bearer or ApiKey <div class="field is-horizontal"> <div class="field-label is-normal"> - <label class="label">Updte token</label> + <label class="label">URL</label> </div> <div class="field-body"> - <div class="field has-addons"> - <label class="b-checkbox checkbox"> - <input type="checkbox" checked={updatingToken} onClick={toggleUpdatingToken} /> - <span class="check" /> - </label> - + <div class="field"> <p class="control is-expanded"> - <input class="input" type="text" placeholder={updatingToken ? "set new token" : "hidden token value"} disabled={!updatingToken} name="id" value={token} onInput={(e): void => setToken(e?.currentTarget.value)} /> + <input class="input" type="text" placeholder="set new url" name="id" value={url} onInput={(e): void => setURL(e?.currentTarget.value)} /> </p> </div> </div> </div> <div class="field is-horizontal"> <div class="field-label is-normal"> - <label class="label">URL</label> + <label class="label">Token</label> </div> <div class="field-body"> + {/* <div class="field has-addons"> + <label class="b-checkbox checkbox"> + <input type="checkbox" checked={updatingToken} onClick={toggleUpdatingToken} /> + <span class="check" /> + </label> */} + <div class="field"> <p class="control is-expanded"> - <input class="input" type="text" placeholder="set new url" name="id" value={url} onInput={(e): void => setURL(e?.currentTarget.value)} /> + <input class="input" type="text" placeholder={true ? "set new token" : "hidden token value"} name="id" value={token} onInput={(e): void => setToken(e?.currentTarget.value)} /> </p> </div> </div> @@ -87,7 +88,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { </section> <footer class="modal-card-foot " style={{ justifyContent: 'flex-end' }}> <button class="button is-info" onClick={(): void => { - onConfirm(url, updatingToken && token ? token : undefined); + onConfirm(url, token ? token : undefined); }} >Confirm</button> </footer> </div> diff --git a/packages/frontend/src/components/form/Field.tsx b/packages/frontend/src/components/form/Field.tsx @@ -76,7 +76,12 @@ interface ProviderProps<T> { export function FormProvider<T>({ object = {}, errors = {}, valueHandler, children }: ProviderProps<T>) { const value = useMemo<FormType<T>>(() => ({errors, object, valueHandler, toStr: {}, fromStr: {}}), [errors, object, valueHandler]) return <FormContext.Provider value={value}> - {children} + <form onSubmit={(e) => { + e.preventDefault() + valueHandler(object) + }}> + {children} + </form> </FormContext.Provider> } diff --git a/packages/frontend/src/components/form/InputArray.tsx b/packages/frontend/src/components/form/InputArray.tsx @@ -19,23 +19,33 @@ * @author Sebastian Javier Marchano (sebasjm) */ import { h, VNode } from "preact"; -import { Message, useMessage } from "preact-messages"; +import { Message, useMessage, useMessageTemplate } from "preact-messages"; import { useState } from "preact/hooks"; -import { useField } from "./Field"; +import { FormErrors, useField, ValidationError } from "./Field"; export interface Props<T> { name: T; readonly?: boolean; + isValid: (e: any) => boolean; + addonBefore?: string; + toStr?: (v?: any) => string; + fromStr?: (s: string) => any; } -export function InputArray<T>({ name, readonly }: Props<keyof T>): VNode { - const { error, value, onChange, fromStr, toStr } = useField<T>(name); +const defaultToString = (f?: any): string => f || '' +const defaultFromString = (v: string): any => v as any +export function InputArray<T>({ name, readonly, addonBefore, isValid, fromStr = defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode { + const { error: formError, value, onChange } = useField<T>(name); + const [localError, setLocalError] = useState<ValidationError | null>(null) const placeholder = useMessage(`fields.instance.${name}.placeholder`); const tooltip = useMessage(`fields.instance.${name}.tooltip`); + const error = formError || localError + const array: any[] = (value ? value! : []) as any; const [currentValue, setCurrentValue] = useState(''); + const i18n = useMessageTemplate(); return <div class="field is-horizontal"> <div class="field-label is-normal"> @@ -49,6 +59,9 @@ export function InputArray<T>({ name, readonly }: Props<keyof T>): VNode { <div class="field-body"> <div class="field"> <div class="field has-addons"> + {addonBefore && <div class="control"> + <a class="button is-static">{addonBefore}</a> + </div>} <p class="control"> <input class={error ? "input is-danger" : "input"} type="text" placeholder={placeholder} readonly={readonly} disabled={readonly} @@ -58,7 +71,14 @@ export function InputArray<T>({ name, readonly }: Props<keyof T>): VNode { </p> <p class="control"> <button class="button is-info" onClick={(): void => { - onChange([fromStr(currentValue), ...array] as any); + const v = fromStr(currentValue) + if (!isValid(v)) { + setLocalError({ message: i18n`The value ${v} is invalid for a payment url` }) + return; + } else { + setLocalError(null) + } + onChange([v, ...array] as any); setCurrentValue(''); }}>add</button> </p> @@ -67,10 +87,10 @@ export function InputArray<T>({ name, readonly }: Props<keyof T>): VNode { <Message id={`validation.${error.type}`} fields={error.params}>{error.message}</Message> </p> : null} {array.map(v => <div class="tags has-addons"> - <span class="tag is-medium is-info">{toStr(v)}</span> + <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) as any); - setCurrentValue( toStr(v) ); + setCurrentValue(toStr(v)); }} /> </div> )} diff --git a/packages/frontend/src/components/form/InputCurrency.tsx b/packages/frontend/src/components/form/InputCurrency.tsx @@ -30,7 +30,7 @@ export interface Props<T> { } export function InputCurrency<T>({ name, readonly, currency }: Props<T>) { - return <InputWithAddon<T> name={name} readonly={readonly} addon={currency} + return <InputWithAddon<T> name={name} readonly={readonly} addonBefore={currency} toStr={(v?: Amount) => v?.split(':')[1] || ''} fromStr={(v: string) => `${currency}:${v}`} /> diff --git a/packages/frontend/src/components/form/InputDuration.tsx b/packages/frontend/src/components/form/InputDuration.tsx @@ -31,7 +31,7 @@ export interface Props<T> { export function InputDuration<T>({ name, readonly }: Props<T>) { const { value } = useField<T>(name); - return <InputWithAddon<T> name={name} readonly={readonly} addon={readableDuration( value as any )} atTheEnd + return <InputWithAddon<T> name={name} readonly={readonly} addonAfter={readableDuration( value as any )} toStr={(v?: RelativeTime) => `${(v && v.d_ms !== "forever" && v.d_ms ? v.d_ms / 1000 : '')}`} fromStr={(v: string) => ({ d_ms: (parseInt(v, 10) * 1000) || undefined })} /> diff --git a/packages/frontend/src/components/form/InputPayto.tsx b/packages/frontend/src/components/form/InputPayto.tsx @@ -0,0 +1,40 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ +import { h } from "preact"; +import { InputArray } from "./InputArray"; +import { PAYTO_REGEX } from "../../utils/constants"; + +export interface Props<T> { + name: keyof T; + readonly?: boolean; +} + +const PAYTO_START_REGEX = /^payto:\/\// + +export function InputPayto<T>({ name, readonly }: Props<T>) { + return <InputArray<T> name={name} readonly={readonly} + addonBefore="payto://" + isValid={(v) => v && PAYTO_REGEX.test(v) } + toStr={(v?: string) => !v ? '': v.replace(PAYTO_START_REGEX, '')} + fromStr={(v: string) => `payto://${v}` } + /> +} + diff --git a/packages/frontend/src/components/form/InputWithAddon.tsx b/packages/frontend/src/components/form/InputWithAddon.tsx @@ -25,8 +25,8 @@ import { useField } from "./Field"; export interface Props<T> { name: keyof T; readonly?: boolean; - addon: string; - atTheEnd?: boolean; + addonBefore?: string; + addonAfter?: string; toStr?: (v?: any) => string; fromStr?: (s: string) => any; } @@ -34,7 +34,7 @@ export interface Props<T> { const defaultToString = (f?: any):string => f || '' const defaultFromString = (v: string):any => v as any -export function InputWithAddon<T>({ name, readonly, addon, atTheEnd, toStr = defaultToString, fromStr = defaultFromString }: Props<T>): VNode { +export function InputWithAddon<T>({ name, readonly, addonBefore, addonAfter, toStr = defaultToString, fromStr = defaultFromString }: Props<T>): VNode { const { error, value, onChange } = useField<T>(name); const placeholder = useMessage(`fields.instance.${name}.placeholder`); @@ -52,8 +52,8 @@ export function InputWithAddon<T>({ name, readonly, addon, atTheEnd, toStr = def <div class="field-body"> <div class="field"> <div class="field has-addons"> - {!atTheEnd && <div class="control"> - <a class="button is-static">{addon}</a> + {addonBefore && <div class="control"> + <a class="button is-static">{addonBefore}</a> </div>} <p class="control is-expanded"> <input class={error ? "input is-danger" : "input"} type="text" @@ -62,8 +62,8 @@ export function InputWithAddon<T>({ name, readonly, addon, atTheEnd, toStr = def onChange={(e): void => onChange(fromStr(e.currentTarget.value))} /> <Message id={`fields.instance.${name}.help`}> </Message> </p> - {atTheEnd && <div class="control"> - <a class="button is-static">{addon}</a> + {addonAfter && <div class="control"> + <a class="button is-static">{addonAfter}</a> </div>} </div> {error ? <p class="help is-danger"><Message id={`validation.${error.type}`} fields={error.params}>{error.message}</Message></p> : null} diff --git a/packages/frontend/src/components/navbar/index.tsx b/packages/frontend/src/components/navbar/index.tsx @@ -23,15 +23,30 @@ import { h, VNode } from 'preact'; import * as messages from '../../messages' import logo from '../../assets/logo.jpeg'; import langIcon from '../../assets/icons/languageicon.svg' -import { Message } from 'preact-messages'; +import { useState } from 'preact/hooks'; interface Props { lang: string; setLang: (l: string) => void; - onLogout: () => void; } -export function NavigationBar({ lang, setLang, onLogout }: Props): VNode { +type LangsNames = { + [P in keyof typeof messages]: string +} + +const names: LangsNames = { + es: 'EspaƱol [es]', + en: 'English [en]', +} + +function getLangName(s: keyof LangsNames | string) { + if (s === 'es' || s === 'en') return names[s] + return s +} + + +export function NavigationBar({ lang, setLang }: Props): VNode { + const [updatingLang, setUpdatingLang] = useState(false) 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"> @@ -49,20 +64,28 @@ export function NavigationBar({ lang, setLang, onLogout }: Props): VNode { <div class="navbar-end"> <div class="navbar-item"> - <div class="control has-icons-left"> - <div class="select"> - <select onChange={(e): void => setLang(e.currentTarget.value)}> - {Object.keys(messages).map(l => <option selected={lang === l} value={l}><Message id={l} /></option>)} - </select> - </div> - <div class="icon is-small is-left"> - <img src={langIcon} /> + + <div class="dropdown is-active "> + <div class="dropdown-trigger"> + <button class="button" aria-haspopup="true" aria-controls="dropdown-menu" onClick={() => setUpdatingLang(!updatingLang)}> + <div class="icon is-small is-left"> + <img src={langIcon} /> + </div> + <span>{getLangName(lang)}</span> + <div class="icon is-right"> + <i class="mdi mdi-chevron-down" /> + </div> + </button> </div> + { updatingLang && <div class="dropdown-menu" id="dropdown-menu" role="menu"> + <div class="dropdown-content"> + {Object.keys(messages) + .filter((l) => l !== lang) + .map(l => <a class="dropdown-item" value={l} onClick={() => {setLang(l); setUpdatingLang(false)}}>{getLangName(l)}</a>)} + </div> + </div> } </div> </div> - <div class="navbar-item"> - <button class="button is-primary" onClick={(): void => onLogout()}>Log out</button> - </div> </div> </div> </nav> diff --git a/packages/frontend/src/components/sidebar/index.tsx b/packages/frontend/src/components/sidebar/index.tsx @@ -24,7 +24,11 @@ import { h, VNode } from 'preact'; import { useContext } from 'preact/hooks'; import { BackendContext, ConfigContext } from '../../context/backend'; -export function Sidebar(): VNode { +interface Props { + onLogout: () => void; +} + +export function Sidebar({ onLogout }: Props): VNode { const config = useContext(ConfigContext); const backend = useContext(BackendContext); return ( @@ -74,21 +78,27 @@ export function Sidebar(): VNode { <p class="menu-label">Connection</p> <ul class="menu-list"> <li> - <a> - <span class="icon"><i class="mdi mdi-currency-eur" /></span> - <span class="menu-item-label">{config.currency}</span> - </a> + <div> + <span class="icon"><i class="mdi mdi-currency-eur" /></span> + <span class="menu-item-label">{config.currency}</span> + </div> </li> <li> - <a> - <span class="icon"><i class="mdi mdi-web" /></span> - <span class="menu-item-label">{backend.url}</span> - </a> + <div> + <span class="icon"><i class="mdi mdi-web" /></span> + <span class="menu-item-label">{backend.url}</span> + </div> + </li> + <li> + <div> + <span class="icon">V</span> + <span class="menu-item-label">{config.version}</span> + </div> </li> <li> - <a> - <span class="icon">V</span> - <span class="menu-item-label">{config.version}</span> + <a class="has-icon is-state-info is-hoverable" onClick={(): void => onLogout()}> + <span class="icon"><i class="mdi mdi-logout default"></i></span> + <span class="menu-item-label">Log out</span> </a> </li> </ul> diff --git a/packages/frontend/src/hooks/backend.ts b/packages/frontend/src/hooks/backend.ts @@ -169,9 +169,7 @@ export function useBackendInstance(): HttpResponse<MerchantBackend.Instances.Que export function useBackendConfig(): HttpResponse<MerchantBackend.VersionResponse> { const { url, token } = useContext(BackendContext) - const { data, error } = useSWR<MerchantBackend.VersionResponse, SwrError>(['/config', token, url], fetcher, { - shouldRetryOnError: false - }) + const { data, error } = useSWR<MerchantBackend.VersionResponse, SwrError>(['/config', token, url], fetcher) return { data, unauthorized: error?.status === 401, error } } diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx @@ -22,7 +22,7 @@ import "./scss/main.scss" import { h, VNode } from 'preact'; -import { useCallback, useContext, useEffect, useState } from "preact/hooks"; +import { useCallback, useContext, useEffect, useMemo, useState } from "preact/hooks"; import { Route, route } from 'preact-router'; import { MessageProvider, useMessageTemplate } from 'preact-messages'; @@ -47,7 +47,6 @@ export enum RootPaths { } export enum InstancePaths { - details = '/instance/:id/', update = '/instance/:id/update', } @@ -94,12 +93,17 @@ function ApplicationStatusRoutes(): VNode { const addTokenCleaner = (c: () => void) => setCleaners(cs => [...cs, c]) const addTokenCleanerNemo = useCallback((c: () => void) => { addTokenCleaner(c) }, [cleaner]) + const ctx = useMemo(() => ({ currency: backendConfig.data?.currency || '', version: backendConfig.data?.version || '' }), [backendConfig.data?.currency, backendConfig.data?.version]) + return <div id="app"> - <ConfigContext.Provider value={backendConfig.data || { currency: '', version: '' }}> - <NavigationBar lang={lang} setLang={setLang} onLogout={() => { cleaners.forEach(c => c()) }} /> - <Sidebar /> + <ConfigContext.Provider value={ctx}> + <NavigationBar lang={lang} setLang={setLang} /> + <Sidebar onLogout={() => { + cleaners.forEach(c => c()) + route(RootPaths.list_instances) + }} /> <Notifications notifications={notifications} removeNotification={removeNotification} /> - {!backendConfig.data ? + {!ctx.currency ? <Route default component={LoginWithError} /> : <Route default component={ApplicationReadyRoutes} pushNotification={pushNotification} addTokenCleaner={addTokenCleanerNemo} /> } diff --git a/packages/frontend/src/messages/en.po b/packages/frontend/src/messages/en.po @@ -20,6 +20,12 @@ msgstr "List of configured instances" msgid "There is no instances yet, add more pressing the + sign" msgstr "There is no instances yet, add more pressing the + sign" +msgid "Invalid payment address" +msgstr "Invalid payment address" + +msgid "The value %s is invalid for a payment url" +msgstr "The value \"%s\" is invalid for a payment url" + # msgctxt "fields.instance.name" # msgid "placeholder" # msgstr "" @@ -50,7 +56,7 @@ msgid "fields.instance.payto_uris.label" msgstr "Account address" msgid "fields.instance.payto_uris.help" -msgstr "payto://x-taler-bank/bank.taler:5882/blogger" +msgstr "x-taler-bank/bank.taler:5882/blogger" msgid "fields.instance.default_max_deposit_fee.label" msgstr "Max deposit fee label" diff --git a/packages/frontend/src/routes/instances/create/CreatePage.tsx b/packages/frontend/src/routes/instances/create/CreatePage.tsx @@ -21,7 +21,7 @@ import { h, VNode } from "preact"; import { useContext, useState } from "preact/hooks"; -import { Amount, MerchantBackend, RelativeTime } from "../../../declaration"; +import { MerchantBackend } from "../../../declaration"; import * as yup from 'yup'; import { FormErrors, FormProvider } from "../../../components/form/Field" import { InstanceCreateSchema as schema } from '../../../schemas' @@ -31,9 +31,9 @@ import { InputSecured } from "../../../components/form/InputSecured"; import { InputWithAddon } from "../../../components/form/InputWithAddon"; import { InputGroup } from "../../../components/form/InputGroup"; import { BackendContext, ConfigContext } from "../../../context/backend"; -import { InputArray } from "../../../components/form/InputArray"; import { InputDuration } from "../../../components/form/InputDuration"; import { InputCurrency } from "../../../components/form/InputCurrency"; +import { InputPayto } from "../../../components/form/InputPayto"; type Entity = MerchantBackend.Instances.InstanceConfigurationMessage @@ -63,7 +63,6 @@ export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode { try { schema.validateSync(value, { abortEarly: false }) onCreate(schema.cast(value) as Entity); - onBack() } 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 } }), {}) @@ -111,13 +110,13 @@ export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode { <div class="column is-two-thirds"> <FormProvider<Entity> errors={errors} object={value} valueHandler={valueHandler} > - <InputWithAddon<Entity> name="id" addon={`${backend.url}/private/instances/`} /> + <InputWithAddon<Entity> name="id" addonBefore={`${backend.url}/private/instances/`} /> <Input<Entity> name="name" /> <InputSecured<Entity> name="auth_token" /> - <InputArray<Entity> name="payto_uris" /> + <InputPayto<Entity> name="payto_uris" /> <InputCurrency<Entity> name="default_max_deposit_fee" currency={config.currency} /> diff --git a/packages/frontend/src/routes/instances/list/Table.tsx b/packages/frontend/src/routes/instances/list/Table.tsx @@ -106,14 +106,12 @@ function Table({ rowSelection, rowSelectionHandler, instances, onUpdate, onDelet </th> <th><Message id="fields.instance.id.label" /></th> <th><Message id="fields.instance.name.label" /></th> - <th><Message id="fields.instance.merchant_pub.label" /></th> - <th><Message id="fields.instance.payment_targets.label" /></th> <th /> </tr> </thead> <tbody> {instances.map(i => { - return <tr> + return <tr onClick={(): void => onUpdate(i.id)} style={{cursor: 'pointer'}}> <td class="is-checkbox-cell"> <label class="b-checkbox checkbox"> <input type="checkbox" checked={rowSelection.indexOf(i.id) != -1} onClick={(): void => rowSelectionHandler(toggleSelected(i.id))} /> @@ -122,13 +120,8 @@ function Table({ rowSelection, rowSelectionHandler, instances, onUpdate, onDelet </td> <td >{i.id}</td> <td >{i.name}</td> - <td >{i.merchant_pub}</td> - <td >{i.payment_targets}</td> <td class="is-actions-cell"> <div class="buttons is-right"> - <button class="button is-small is-link" type="button" onClick={(): void => onUpdate(i.id)}> - <span class="icon"><i class="mdi mdi-eye" /></span> - </button> <button class="button is-small is-danger jb-modal" type="button" onClick={(): void => onDelete(i)}> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> diff --git a/packages/frontend/src/routes/instances/update/UpdatePage.tsx b/packages/frontend/src/routes/instances/update/UpdatePage.tsx @@ -21,7 +21,7 @@ import { h, VNode } from "preact"; import { useContext, useState } from "preact/hooks"; -import { Amount, MerchantBackend, RelativeTime } from "../../../declaration"; +import { MerchantBackend } from "../../../declaration"; import * as yup from 'yup'; import { FormProvider, FormErrors } from "../../../components/form/Field" import { InputGroup } from "../../../components/form/InputGroup" @@ -30,9 +30,7 @@ import { InstanceUpdateSchema as schema } from '../../../schemas' import { Message } from "preact-messages"; import { Input } from "../../../components/form/Input"; import { InputSecured } from "../../../components/form/InputSecured"; -import { InputWithAddon } from "../../../components/form/InputWithAddon"; -import { BackendContext, ConfigContext } from "../../../context/backend"; -import { intervalToDuration, formatDuration } from 'date-fns' +import { ConfigContext } from "../../../context/backend"; import { InputArray } from "../../../components/form/InputArray"; import { InputDuration } from "../../../components/form/InputDuration"; import { InputCurrency } from "../../../components/form/InputCurrency"; diff --git a/packages/frontend/src/scss/_theme-default.scss b/packages/frontend/src/scss/_theme-default.scss @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - /** +/** * * @author Sebastian Javier Marchano (sebasjm) */ @@ -31,7 +31,7 @@ $family-sans-serif: "Nunito", sans-serif; /* Base color */ $base-color: #2e323a; -$base-color-light: rgba(24, 28, 33, .06); +$base-color-light: rgba(24, 28, 33, 0.06); /* General overrides */ $primary: $turquoise; @@ -44,14 +44,14 @@ $hr-height: 1px; /* NavBar: specifics */ $navbar-input-color: $grey-darker; $navbar-input-placeholder-color: $grey-lighter; -$navbar-box-shadow: 0 1px 0 rgba(24,28,33,.04); -$navbar-divider-border: 1px solid rgba($grey-lighter, .25); -$navbar-item-h-padding: $default-padding * .75; +$navbar-box-shadow: 0 1px 0 rgba(24, 28, 33, 0.04); +$navbar-divider-border: 1px solid rgba($grey-lighter, 0.25); +$navbar-item-h-padding: $default-padding * 0.75; $navbar-avatar-size: 1.75rem; /* Aside: Bulma override */ $menu-item-radius: 0; -$menu-list-link-padding: $size-base * .5 0; +$menu-list-link-padding: $size-base * 0.5 0; $menu-label-color: lighten($base-color, 25%); $menu-item-color: lighten($base-color, 30%); $menu-item-hover-color: $white; @@ -63,7 +63,7 @@ $menu-item-active-background-color: darken($base-color, 2.5%); $aside-width: $size-base * 14; $aside-mobile-width: $size-base * 15; $aside-icon-width: $size-base * 3; -$aside-submenu-font-size: $size-base * .95; +$aside-submenu-font-size: $size-base * 0.95; $aside-box-shadow: none; $aside-background-color: $base-color; $aside-tools-background-color: darken($aside-background-color, 10%); @@ -107,11 +107,11 @@ $modal-card-width-mobile: 90vw; $modal-card-foot-background-color: $white-ter; /* Notification: Bulma override */ -$notification-padding: $default-padding * .75 $default-padding; +$notification-padding: $default-padding * 0.75 $default-padding; /* Footer: Bulma override */ $footer-background-color: $white; -$footer-padding: $default-padding * .33 $default-padding; +$footer-padding: $default-padding * 0.33 $default-padding; /* Footer: specifics */ $footer-logo-height: $size-base * 2; @@ -120,7 +120,17 @@ $footer-logo-height: $size-base * 2; $progress-bar-background-color: $grey-lighter; /* Icon: specifics */ -$icon-update-mark-size: $size-base * .5; +$icon-update-mark-size: $size-base * 0.5; $icon-update-mark-color: $yellow; $input-disabled-border-color: $grey-lighter; +$table-row-hover-background-color: hsl(0, 0%, 80%); + +.menu-list { + div { + border-radius: $menu-item-radius; + color: $menu-item-color; + display: block; + padding: $menu-list-link-padding; + } +} diff --git a/packages/frontend/src/utils/constants.ts b/packages/frontend/src/utils/constants.ts @@ -19,5 +19,6 @@ * @author Sebastian Javier Marchano (sebasjm) */ -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\-\.\~\(\)@_%:!$'*+,;=]*&?)*$/ + //https://tools.ietf.org/html/rfc8905 +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,]*$/