merchant-backoffice

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

commit b4c42f71b69d017bb7275565a915479cbae6b342
parent 64451fe1b74508c3eda71dbc8f3d4101a255a330
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu, 25 Feb 2021 11:54:44 -0300

refactor YUP, just using it to build errors object

Diffstat:
MCHANGELOG.md | 16++++++++++------
Mpackages/frontend/src/ApplicationReadyRoutes.tsx | 20++++++++++++++++++++
Mpackages/frontend/src/InstanceRoutes.tsx | 21+++++++++++++++++++++
Apackages/frontend/src/components/form/Field.tsx | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/components/form/Input.tsx | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/components/form/InputArray.tsx | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/components/form/InputCurrency.tsx | 38++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/components/form/InputDuration.tsx | 48++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/components/form/InputGroup.tsx | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/components/form/InputSecured.tsx | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/components/form/InputWithAddon.tsx | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/frontend/src/components/modal/index.tsx | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Dpackages/frontend/src/components/yup/YupField.tsx | 79-------------------------------------------------------------------------------
Dpackages/frontend/src/components/yup/YupInput.tsx | 60------------------------------------------------------------
Dpackages/frontend/src/components/yup/YupInputArray.tsx | 81-------------------------------------------------------------------------------
Dpackages/frontend/src/components/yup/YupInputSecured.tsx | 68--------------------------------------------------------------------
Dpackages/frontend/src/components/yup/YupInputWithAddon.tsx | 68--------------------------------------------------------------------
Dpackages/frontend/src/components/yup/YupObjectInput.tsx | 59-----------------------------------------------------------
Mpackages/frontend/src/context/backend.ts | 9+++------
Mpackages/frontend/src/hooks/backend.ts | 24+++++++++++++++++++++++-
Mpackages/frontend/src/index.tsx | 2+-
Mpackages/frontend/src/messages/en.po | 14+++++++++++++-
Mpackages/frontend/src/routes/instances/create/CreatePage.tsx | 61+++++++++++++++++++++++++++++++++++++++++++++++++------------
Mpackages/frontend/src/routes/instances/details/DetailPage.tsx | 20+++++++++++---------
Mpackages/frontend/src/routes/instances/details/index.tsx | 2+-
Dpackages/frontend/src/routes/instances/list/CardTable.tsx | 101-------------------------------------------------------------------------------
Dpackages/frontend/src/routes/instances/list/DeleteModal.tsx | 36------------------------------------
Dpackages/frontend/src/routes/instances/list/EmptyTable.tsx | 32--------------------------------
Mpackages/frontend/src/routes/instances/list/Table.tsx | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mpackages/frontend/src/routes/instances/list/View.tsx | 2+-
Mpackages/frontend/src/routes/instances/list/index.tsx | 3++-
Mpackages/frontend/src/routes/instances/update/UpdatePage.tsx | 72++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mpackages/frontend/src/routes/instances/update/index.tsx | 35+++++++++++++++++++++++++----------
33 files changed, 947 insertions(+), 662 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -11,10 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - allow point separator for amounts - prevent letters to be input in numbers - red color when input is invalid (onchange) - - auth token config as popup with 3 actions (clear (sure?), cancel, set token) - - remove checkbox from auth token, use button (manage auth) - prepend payto:// to account - - validate on change everything + - 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 @@ -30,9 +28,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - prune scss styles to reduce size ## [Unreleased] - - check the url from the backend when login - - edit button should be a pen - - implement correct weblate feature + - REFACTOR: remove react-i18n and implement messageformat + - REFACTOR: routes definitions to allow nested routes and tokens + - REFACTOR: remove yup from input form defitions + - added PORT environment variable for `make dev` and `make serve` + - added `make dist` and `make install` + - remove checkbox from auth token, use button (manage auth) + - auth token config as popup with 3 actions (clear (sure?), cancel, set token) + - remove last '/' on the backend url + - change button on table row from "edit" to "view" - what happend if cannot access the config - reorder the fields from the address/juriction section (take example) - save every auth token of different instances diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx b/packages/frontend/src/ApplicationReadyRoutes.tsx @@ -1,3 +1,23 @@ +/* + 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, VNode } from 'preact'; import { useContext } from "preact/hooks"; import { Route, Router, route } from 'preact-router'; diff --git a/packages/frontend/src/InstanceRoutes.tsx b/packages/frontend/src/InstanceRoutes.tsx @@ -1,3 +1,24 @@ +/* + 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, VNode } from 'preact'; import { useCallback, useContext, useEffect } from "preact/hooks"; import { Route, Router, route } from 'preact-router'; diff --git a/packages/frontend/src/components/form/Field.tsx b/packages/frontend/src/components/form/Field.tsx @@ -0,0 +1,132 @@ +/* + 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, VNode, createContext } from "preact"; +import { StateUpdater, useContext, useMemo, useState } from "preact/hooks"; +import { BackendContext, ConfigContext } from '../../context/backend'; +import { Input } from "./Input"; +import { InputArray } from "./InputArray"; +import { InputWithAddon } from "./InputWithAddon"; +import { InputSecured } from "./InputSecured"; + +interface Props<T> { + name: keyof T; + info: any; + readonly?: boolean; +} + +export interface FormType<T> { + object: Partial<T>; + errors: FormErrors<T>; + toStr: FormtoStr<T>; + fromStr: FormfromStr<T>; + valueHandler: StateUpdater<Partial<T>>; +} +const FormContext = createContext<FormType<any>>(null!) + +export type ValidationError = { + type?: string; + message: string; + params?: any; +} + +export type FormErrors<T> = { + [P in keyof T]?: ValidationError +} + +export type FormtoStr<T> = { + [P in keyof T]?: ((f?: T[P]) => string) +} + +export type FormfromStr<T> = { + [P in keyof T]?: ((f: string) => T[P]) +} + +export type FormUpdater<T> = { + [P in keyof T]?: (f: keyof T) => (v: T[P]) => void +} + +interface ProviderProps<T> { + object?: Partial<T>; + errors?: FormErrors<T>; + // toStr?: FormtoStr<T>; + // fromStr?: FormfromStr<T>; + valueHandler: StateUpdater<Partial<T>>; + children: VNode[] | VNode +} + +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} + </FormContext.Provider> +} + +export function useField<T>(name: keyof T) { + const { errors, object, toStr, fromStr, valueHandler } = useContext<FormType<T>>(FormContext) + type P = typeof name + type V = T[P] + + const updateField = (f: P) => (v: V): void => { + return valueHandler((prev) => { + return ({ ...prev, [f]: v }) + }) + } + + const defaultToString = ((f?: V):string => String(!f ? '': f)) + const defaultFromString = ((v: string):V => v as any) + + return { + error: errors[name], + value: object[name], + onChange: updateField(name), + toStr: toStr[name] ? toStr[name]! : defaultToString, + fromStr: fromStr[name] ? fromStr[name]! : defaultFromString, + } +} + +// export function Field<T>({ name, info, readonly }: Props<T>): VNode { +// const {errors, object, valueHandler, updateField} = useForm<T>() + +// const backend = useContext(BackendContext) +// const config = useContext(ConfigContext) + +// switch (info.meta?.type) { + // case 'group': { + // return <InputObject name={name} readonly={readonly} + + // onChange={(updater: any): void => valueHandler((prev: any) => ({ ...prev, [name]: updater(prev[name]) }))} + // /> + // } + // case 'array': return <InputArray name={name} readonly={readonly} />; + // case 'amount': { + // if (config.currency) { + // return <InputWithAddon name={name} readonly={readonly} addon={config.currency} onChange={(v: string): void => values.onChange(`${config.currency}:${v}`)} value={values.value?.split(':')[1]} /> + // } + // return <Input name={name} readonly={readonly} />; + // } + // case 'url': return <InputWithAddon name={name} readonly={readonly} addon={`${backend.url}/private/instances/`} />; + // case 'secured': return <InputSecured name={name} readonly={readonly} />; + // case 'duration': return <InputWithAddon name={name} readonly={readonly} addon={readableDuration(values.value?.d_ms)} atTheEnd value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void => values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />; + // default: return <Input name={name} readonly={readonly} />; + + // } +// } diff --git a/packages/frontend/src/components/form/Input.tsx b/packages/frontend/src/components/form/Input.tsx @@ -0,0 +1,60 @@ +/* + 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, VNode } from "preact"; +import { Message, useMessage } from "preact-messages"; +import { useField } from "./Field"; + +interface Props<T> { + name: T; + readonly?: boolean; +} + +export function Input<T>({ name, readonly }: Props<keyof T>): VNode { + const { error, value, onChange, toStr, fromStr } = useField<T>(name); + + const placeholder = useMessage(`fields.instance.${name}.placeholder`); + const tooltip = useMessage(`fields.instance.${name}.tooltip`); + + return <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <Message id={`fields.instance.${name}.label`} /> + {tooltip && <span class="icon" data-tooltip={tooltip}> + <i class="mdi mdi-information" /> + </span>} + </label> + </div> + <div class="field-body"> + <div class="field"> + <p class="control"> + <input class={error ? "input is-danger" : "input"} type="text" + placeholder={placeholder} readonly={readonly} + name={String(name)} value={toStr(value)} disabled={readonly} + onChange={(e): void => onChange(fromStr(e.currentTarget.value))} /> + <Message id={`fields.instance.${name}.help`}> </Message> + </p> + {error ? <p class="help is-danger"> + <Message id={`validation.${error.type}`} fields={error.params}>{error.message} </Message> + </p> : null} + </div> + </div> + </div>; +} diff --git a/packages/frontend/src/components/form/InputArray.tsx b/packages/frontend/src/components/form/InputArray.tsx @@ -0,0 +1,81 @@ +/* + 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, VNode } from "preact"; +import { Message, useMessage } from "preact-messages"; +import { useState } from "preact/hooks"; +import { useField } from "./Field"; + +export interface Props<T> { + name: T; + readonly?: boolean; +} + +export function InputArray<T>({ name, readonly }: Props<keyof T>): VNode { + const { error, value, onChange, fromStr, toStr } = useField<T>(name); + + const placeholder = useMessage(`fields.instance.${name}.placeholder`); + const tooltip = useMessage(`fields.instance.${name}.tooltip`); + + const array: any[] = (value ? value! : []) as any; + const [currentValue, setCurrentValue] = useState(''); + + return <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <Message id={`fields.instance.${name}.label`} /> + {tooltip && <span class="icon" data-tooltip={tooltip}> + <i class="mdi mdi-information" /> + </span>} + </label> + </div> + <div class="field-body"> + <div class="field"> + <div class="field has-addons"> + <p class="control"> + <input class={error ? "input is-danger" : "input"} type="text" + placeholder={placeholder} readonly={readonly} disabled={readonly} + name={String(name)} value={currentValue} + onChange={(e): void => setCurrentValue(e.currentTarget.value)} /> + <Message id={`fields.instance.${name}.help`}> </Message> + </p> + <p class="control"> + <button class="button is-info" onClick={(): void => { + onChange([fromStr(currentValue), ...array] as any); + setCurrentValue(''); + }}>add</button> + </p> + </div> + {error ? <p class="help is-danger"> + <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> + <a class="tag is-medium is-danger is-delete" onClick={() => { + onChange(array.filter(f => f !== v) as any); + setCurrentValue( toStr(v) ); + }} /> + </div> + )} + </div> + + </div> + </div>; +} diff --git a/packages/frontend/src/components/form/InputCurrency.tsx b/packages/frontend/src/components/form/InputCurrency.tsx @@ -0,0 +1,38 @@ +/* + 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 { Amount } from "../../declaration"; +import { InputWithAddon } from "./InputWithAddon"; +import { useField } from "./Field"; + +export interface Props<T> { + name: keyof T; + readonly?: boolean; + currency: string; +} + +export function InputCurrency<T>({ name, readonly, currency }: Props<T>) { + return <InputWithAddon<T> name={name} readonly={readonly} addon={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 @@ -0,0 +1,48 @@ +/* + 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, VNode } from "preact"; +import { RelativeTime } from "../../declaration"; +import { InputWithAddon } from "./InputWithAddon"; +import { formatDuration, intervalToDuration } from "date-fns"; +import { useField } from "./Field"; + +export interface Props<T> { + name: keyof T; + readonly?: boolean; +} + +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 + 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 })} + /> +} + +function readableDuration(duration?: RelativeTime): string { + if (!duration) return "" + if (duration.d_ms === "forever") return "forever" + try { + return formatDuration(intervalToDuration({ start: 0, end: duration.d_ms })) + } catch (e) { + return '' + } +} diff --git a/packages/frontend/src/components/form/InputGroup.tsx b/packages/frontend/src/components/form/InputGroup.tsx @@ -0,0 +1,51 @@ +/* + 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, VNode } from "preact"; +import { Message } from "preact-messages"; +import { useState } from "preact/hooks"; + +export interface Props<T> { + name: keyof T; + children: VNode[] | VNode; +} + +export function InputGroup<T>({ name, children }: Props<T>): VNode { + const [active, setActive] = useState(false); + return <div class="card"> + <header class="card-header"> + <p class="card-header-title"> + <Message id={`fields.instance.${String(name)}.label`} /> + </p> + <button class="card-header-icon" aria-label="more options" onClick={(): void => setActive(!active)}> + <span class="icon"> + {active ? + <i class="mdi mdi-arrow-up" /> : + <i class="mdi mdi-arrow-down" />} + </span> + </button> + </header> + <div class={active ? "card-content" : "is-hidden"}> + <div class="content"> + {children} + </div> + </div> + </div>; +} diff --git a/packages/frontend/src/components/form/InputSecured.tsx b/packages/frontend/src/components/form/InputSecured.tsx @@ -0,0 +1,69 @@ +/* + 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, VNode } from "preact"; +import { Message, useMessage } from "preact-messages"; +import { useState } from "preact/hooks"; +import { DeleteModal } from "../modal"; +import { useField } from "./Field"; + +export interface Props<T> { + name: keyof T; + readonly?: boolean; +} + +export function InputSecured<T>({ name, readonly }: Props<T>): VNode { + const {error, value, onChange, toStr, fromStr } = useField<T>(name); + + const placeholder = useMessage(`fields.instance.${name}.placeholder`, {}); + const tooltip = useMessage(`fields.instance.${name}.tooltip`, {}); + + const [active, setActive] = useState(false); + + return <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <Message id={`fields.instance.${name}.label`} /> + {tooltip && <span class="icon" data-tooltip={tooltip}> + <i class="mdi mdi-information" /> + </span>} + </label> + </div> + <div class="field-body"> + <div class="field"> + <div class="field has-addons"> + <label class="b-checkbox checkbox"> + <input type="checkbox" checked={active} onClick={(): void => { onChange(fromStr('')); setActive(!active); }} /> + <span class="check" /> + </label> + <p class="control"> + <input class="input" type="text" + placeholder={placeholder} readonly={readonly || !active} + disabled={readonly || !active} + name={String(name)} value={toStr(value)} + onChange={(e): void => onChange(fromStr(e.currentTarget.value))} /> + <Message id={`fields.instance.${name}.help`}> </Message> + </p> + </div> + {error ? <p class="help is-danger"><Message id={`validation.${error.type}`} fields={error.params}>{error.message}</Message></p> : null} + </div> + </div> + </div>; +} diff --git a/packages/frontend/src/components/form/InputWithAddon.tsx b/packages/frontend/src/components/form/InputWithAddon.tsx @@ -0,0 +1,73 @@ +/* + 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, VNode } from "preact"; +import { Message, useMessage } from "preact-messages"; +import { useField } from "./Field"; + +export interface Props<T> { + name: keyof T; + readonly?: boolean; + addon: string; + atTheEnd?: boolean; + toStr?: (v?: any) => string; + fromStr?: (s: string) => any; +} + +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 { + const { error, value, onChange } = useField<T>(name); + + const placeholder = useMessage(`fields.instance.${name}.placeholder`); + const tooltip = useMessage(`fields.instance.${name}.tooltip`); + + return <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <Message id={`fields.instance.${name}.label`} /> + {tooltip && <span class="icon" data-tooltip={tooltip}> + <i class="mdi mdi-information" /> + </span>} + </label> + </div> + <div class="field-body"> + <div class="field"> + <div class="field has-addons"> + {!atTheEnd && <div class="control"> + <a class="button is-static">{addon}</a> + </div>} + <p class="control is-expanded"> + <input class={error ? "input is-danger" : "input"} type="text" + placeholder={placeholder} readonly={readonly} disabled={readonly} + name={String(name)} value={toStr(value)} + 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> + </div>} + </div> + {error ? <p class="help is-danger"><Message id={`validation.${error.type}`} fields={error.params}>{error.message}</Message></p> : null} + </div> + </div> + </div>; +} diff --git a/packages/frontend/src/components/modal/index.tsx b/packages/frontend/src/components/modal/index.tsx @@ -22,6 +22,9 @@ import { h, VNode } from "preact"; import { Message } from "preact-messages"; +import { useState } from "preact/hooks"; +import { FormProvider } from "../form/Field"; +import { Input } from "../form/Input"; interface Props { active?: boolean; @@ -30,14 +33,15 @@ interface Props { onConfirm?: () => void; children?: VNode[]; danger?: boolean; + disabled?: boolean; } -export function ConfirmModal({ active, description, onCancel, onConfirm, children, danger }: Props): VNode { +export function ConfirmModal({ active, description, onCancel, onConfirm, children, danger, disabled }: Props): VNode { return <div class={active ? "modal is-active" : "modal"}> <div class="modal-background " onClick={onCancel} /> <div class="modal-card"> <header class="modal-card-head"> - <p class="modal-card-title"> <Message id="confirm_modal.title" /> { !description ? null : <Message id={`confirm_modal.${description}`} /> }</p> + {!description ? null : <p class="modal-card-title"> <Message id={description} /></p> } <button class="delete " aria-label="close" onClick={onCancel} /> </header> <section class="modal-card-body"> @@ -45,9 +49,77 @@ export function ConfirmModal({ active, description, onCancel, onConfirm, childre </section> <footer class="modal-card-foot"> <button class="button " onClick={onCancel} ><Message id="Cancel" /></button> - <button class={danger ? "button is-danger " : "button is-info "} onClick={onConfirm} ><Message id="Confirm" /></button> + <button class={danger ? "button is-danger " : "button is-info "} disabled={disabled} onClick={onConfirm} ><Message id="Confirm" /></button> </footer> </div> <button class="modal-close is-large " aria-label="close" onClick={onCancel} /> </div> -} -\ No newline at end of file +} + +export function ClearConfirmModal({ description, onCancel, onClear, onConfirm, children, disabled }: Props & { onClear?: () => void }): VNode { + return <div class="modal is-active"> + <div class="modal-background " onClick={onCancel} /> + <div class="modal-card"> + <header class="modal-card-head"> + {!description ? null : <p class="modal-card-title"> <Message id={description} /></p> } + <button class="delete " aria-label="close" onClick={onCancel} /> + </header> + <section class="modal-card-body"> + {children} + </section> + <footer class="modal-card-foot"> + <button class="button " onClick={onCancel} ><Message id="Cancel" /></button> + <button class="button is-danger" onClick={onClear} disabled={disabled} ><Message id="Clear" /></button> + <button class="button is-info" onClick={onConfirm} disabled={disabled} ><Message id="Confirm" /></button> + </footer> + </div> + <button class="modal-close is-large " aria-label="close" onClick={onCancel} /> + </div> +} + +interface DeleteModalProps { + element: { id: string, name: string }; + onCancel: () => void; + onConfirm: (id: string) => void; +} + +export function DeleteModal({ element, onCancel, onConfirm }: DeleteModalProps): VNode { + return <ConfirmModal description="delete_instance" danger active onCancel={onCancel} onConfirm={() => onConfirm(element.id)}> + <p>This will permanently delete instance "{element.name}" with id <b>{element.id}</b></p> + <p>Please confirm this action</p> + </ConfirmModal> +} + +interface UpdateTokenModalProps { + element: { id: string, name: string }; + oldToken: string; + onCancel: () => void; + onConfirm: (value: string) => void; + onClear: () => void; +} + +export function UpdateTokenModal({ element, onCancel, onClear, onConfirm, oldToken }: UpdateTokenModalProps): VNode { + type State = {old_token: string, new_token: string} + const [form, setValue] = useState<Partial<State>>({ + old_token: '', new_token: '' + }) + + const errors = { + old_token: oldToken !== form.old_token ? { message: 'should be the same' } : undefined, + new_token: !form.new_token ? { message: 'should be the same' } : ( form.new_token === form.old_token ? { message: 'cant repeat' } : undefined ), + } + + return <ClearConfirmModal description="update_token" + onCancel={onCancel} + onConfirm={() => onConfirm(form.new_token!)} + onClear={onClear} + disabled={!!errors.new_token || !!errors.old_token} + > + <p>You are updating the authorization token from instance {element.name} with id <b>{element.id}</b></p> + <FormProvider errors={errors} object={form} valueHandler={setValue}> + <Input name="old_token" /> + <Input name="new_token" /> + </FormProvider> + <p>Clearing the auth token will mean public access to the instance</p> + </ClearConfirmModal> +} diff --git a/packages/frontend/src/components/yup/YupField.tsx b/packages/frontend/src/components/yup/YupField.tsx @@ -1,79 +0,0 @@ -/* - 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, VNode } from "preact"; -import { StateUpdater, useContext } from "preact/hooks"; -import { intervalToDuration, formatDuration } from 'date-fns' -import { BackendContext, ConfigContext } from '../../context/backend'; -import { YupObjectInput } from "./YupObjectInput"; -import { YupInput } from "./YupInput"; -import { YupInputArray } from "./YupInputArray"; -import { YupInputWithAddon } from "./YupInputWithAddon"; -import { YupInputSecured } from "./YupInputSecured"; - -function readableDuration(duration?: number): string { - if (!duration) return "" - return formatDuration(intervalToDuration({ start: 0, end: duration })) -} - -interface Props { - name: string; - field: string; - errors: any; - object: any; - valueHandler: StateUpdater<any>; - info: any; - readonly?: boolean; -} -export function YupField({ name, field, errors, object, valueHandler, info, readonly }: Props): VNode { - const updateField = (f: string) => (v: string): void => valueHandler((prev: any) => ({ ...prev, [f]: v })) - const values = { - name, errors, - readonly: readonly || info?.meta?.readonly, - value: object && object[field], - onChange: updateField(field) - } - const backend = useContext(BackendContext) - const config = useContext(ConfigContext) - - switch (info.meta?.type) { - case 'group': { - return <YupObjectInput name={name} - info={info} errors={errors} - value={object && object[field]} - readonly={values.readonly} - onChange={(updater: any): void => valueHandler((prev: any) => ({ ...prev, [field]: updater(prev[field]) }))} - /> - } - case 'array': return <YupInputArray {...values} />; - case 'amount': { - if (config.currency) { - return <YupInputWithAddon {...values} addon={config.currency} onChange={(v: string): void => values.onChange(`${config.currency}:${v}`)} value={values.value?.split(':')[1]} /> - } - return <YupInput {...values} />; - } - case 'url': return <YupInputWithAddon {...values} addon={`${backend.url}/private/instances/`} />; - case 'secured': return <YupInputSecured {...values} />; - case 'duration': return <YupInputWithAddon {...values} addon={readableDuration(values.value?.d_ms)} atTheEnd value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void => values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />; - default: return <YupInput {...values} />; - - } -} diff --git a/packages/frontend/src/components/yup/YupInput.tsx b/packages/frontend/src/components/yup/YupInput.tsx @@ -1,60 +0,0 @@ -/* - 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, VNode } from "preact"; -import { Message, useMessage } from "preact-messages"; - -interface Props { - name: string; - value: string; - readonly?: boolean; - errors: any; - onChange: any; -} - -export function YupInput({ name, readonly, value, errors, onChange }: Props): VNode { - const placeholder = useMessage(`fields.instance.${name}.placeholder`); - const tooltip = useMessage(`fields.instance.${name}.tooltip`); - - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - <Message id={`fields.instance.${name}.label`} /> - {tooltip && <span class="icon" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body"> - <div class="field"> - <p class="control"> - <input class={errors[name] ? "input is-danger" : "input"} type="text" - placeholder={placeholder} readonly={readonly} - name={name} value={value} disabled={readonly} - onChange={(e): void => onChange(e.currentTarget.value)} /> - <Message id={`fields.instance.${name}.help`}> </Message> - </p> - {errors[name] ? <p class="help is-danger"> - <Message id={`validation.${errors[name].type}`} fields={errors[name].params}>{errors[name].message} </Message> - </p> : null} - </div> - </div> - </div>; -} diff --git a/packages/frontend/src/components/yup/YupInputArray.tsx b/packages/frontend/src/components/yup/YupInputArray.tsx @@ -1,81 +0,0 @@ -/* - 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, VNode } from "preact"; -import { Message, useMessage } from "preact-messages"; -import { useState } from "preact/hooks"; - -export interface Props { - name: string; - value: string; - readonly?: boolean; - errors: any; - onChange: any; -} - -export function YupInputArray({ name, readonly, value, errors, onChange }: Props): VNode { - const placeholder = useMessage(`fields.instance.${name}.placeholder`); - const tooltip = useMessage(`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"> - <Message id={`fields.instance.${name}.label`} /> - {tooltip && <span class="icon" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body"> - <div class="field"> - <div class="field has-addons"> - <p class="control"> - <input class={errors[name] ? "input is-danger" : "input"} type="text" - placeholder={placeholder} readonly={readonly} disabled={readonly} - name={name} value={currentValue} - onChange={(e): void => setCurrentValue(e.currentTarget.value)} /> - <Message id={`fields.instance.${name}.help`}> </Message> - </p> - <p class="control"> - <button class="button is-info" onClick={(): void => { - onChange([currentValue, ...array]); - setCurrentValue(''); - }}>add</button> - </p> - </div> - {errors[name] ? <p class="help is-danger"> - <Message id={`validation.${errors[name].type}`} fields={errors[name].params}>{errors[name].message}</Message> - </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>; -} diff --git a/packages/frontend/src/components/yup/YupInputSecured.tsx b/packages/frontend/src/components/yup/YupInputSecured.tsx @@ -1,68 +0,0 @@ -/* - 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, VNode } from "preact"; -import { Message, useMessage } from "preact-messages"; -import { useState } from "preact/hooks"; - -export interface Props { - name: string; - value: string; - readonly?: boolean; - errors: any; - onChange: any; -} - -export function YupInputSecured({ name, readonly, value, errors, onChange }: Props): VNode { - const placeholder = useMessage(`fields.instance.${name}.placeholder`, {}); - const tooltip = useMessage(`fields.instance.${name}.tooltip`, {}); - - const [active, setActive] = useState(false); - - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - <Message id={`fields.instance.${name}.label`} /> - {tooltip && <span class="icon" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body"> - <div class="field"> - <div class="field has-addons"> - <label class="b-checkbox checkbox"> - <input type="checkbox" checked={active} onClick={(): void => { onChange(''); setActive(!active); }} /> - <span class="check" /> - </label> - <p class="control"> - <input class="input" type="text" - placeholder={placeholder} readonly={readonly || !active} - disabled={readonly || !active} - name={name} value={value} - onChange={(e): void => onChange(e.currentTarget.value)} /> - <Message id={`fields.instance.${name}.help`}> </Message> - </p> - </div> - {errors[name] ? <p class="help is-danger"><Message id={`validation.${errors[name].type}`} fields={errors[name].params}>{errors[name].message}</Message></p> : null} - </div> - </div> - </div>; -} diff --git a/packages/frontend/src/components/yup/YupInputWithAddon.tsx b/packages/frontend/src/components/yup/YupInputWithAddon.tsx @@ -1,68 +0,0 @@ -/* - 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, VNode } from "preact"; -import { Message, useMessage } from "preact-messages"; - -export interface Props { - name: string; - value: string; - readonly?: boolean; - errors: any; - onChange: any; - addon: string; - atTheEnd?: boolean; -} - -export function YupInputWithAddon({ name, readonly, value, errors, onChange, addon, atTheEnd }: Props): VNode { - const placeholder = useMessage(`fields.instance.${name}.placeholder`); - const tooltip = useMessage(`fields.instance.${name}.tooltip`); - - return <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - <Message id={`fields.instance.${name}.label`} /> - {tooltip && <span class="icon" data-tooltip={tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - </div> - <div class="field-body"> - <div class="field"> - <div class="field has-addons"> - {!atTheEnd && <div class="control"> - <a class="button is-static">{addon}</a> - </div>} - <p class="control is-expanded"> - <input class={errors[name] ? "input is-danger" : "input"} type="text" - placeholder={placeholder} readonly={readonly} disabled={readonly} - name={name} value={value} - onChange={(e): void => onChange(e.currentTarget.value)} /> - <Message id={`fields.instance.${name}.help`}> </Message> - </p> - {atTheEnd && <div class="control"> - <a class="button is-static">{addon}</a> - </div>} - </div> - {errors[name] ? <p class="help is-danger"><Message id={`validation.${errors[name].type}`} fields={errors[name].params}>{errors[name].message}</Message></p> : null} - </div> - </div> - </div>; -} diff --git a/packages/frontend/src/components/yup/YupObjectInput.tsx b/packages/frontend/src/components/yup/YupObjectInput.tsx @@ -1,59 +0,0 @@ -/* - 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, VNode } from "preact"; -import { Message } from "preact-messages"; -import { useState } from "preact/hooks"; -import { YupField } from "./YupField"; - -export interface PropsObject { - name: string; - info: any; - value: any; - errors: any; - onChange: any; - readonly?: boolean; -} - -export function YupObjectInput({ name, info, value, errors, onChange, readonly }: PropsObject): VNode { - const [active, setActive] = useState(false); - return <div class="card"> - <header class="card-header"> - <p class="card-header-title"> - <Message id={`fields.instance.${name}.label`} /> - </p> - <button class="card-header-icon" aria-label="more options" onClick={(): void => setActive(!active)}> - <span class="icon"> - {active ? - <i class="mdi mdi-arrow-up" /> : - <i class="mdi mdi-arrow-down" />} - </span> - </button> - </header> - <div class={active ? "card-content" : "is-hidden"}> - <div class="content"> - {Object.keys(info.fields).map(f => <YupField name={`${name}.${f}`} - field={f} errors={errors} object={value} - valueHandler={onChange} info={info.fields[f]} - readonly={readonly} />)} - </div> - </div> - </div>; -} diff --git a/packages/frontend/src/context/backend.ts b/packages/frontend/src/context/backend.ts @@ -28,8 +28,8 @@ export interface BackendContextType { } export interface ConfigContextType { - currency?: string; - version?: string; + currency: string; + version: string; } export interface InstanceContextType { @@ -48,10 +48,7 @@ export const BackendContext = createContext<BackendContextType>({ setLang: () => null, }) -export const ConfigContext = createContext<ConfigContextType>({ - currency: undefined, - version: undefined, -}) +export const ConfigContext = createContext<ConfigContextType>(null!) export const InstanceContext = createContext<InstanceContextType>({ id: '', diff --git a/packages/frontend/src/hooks/backend.ts b/packages/frontend/src/hooks/backend.ts @@ -85,6 +85,8 @@ interface BackendMutateAPI { interface BackendInstaceMutateAPI { updateInstance: (data: MerchantBackend.Instances.InstanceReconfigurationMessage) => Promise<void>; deleteInstance: () => Promise<void>; + clearToken: () => Promise<void>; + setNewToken: (token: string) => Promise<void>; } export function useBackendMutateAPI(): BackendMutateAPI { @@ -127,7 +129,27 @@ export function useBackendInstanceMutateAPI(): BackendInstaceMutateAPI { mutate(`/private/instances/${id}`, null) } - return { updateInstance, deleteInstance } + const clearToken = async (): Promise<void> => { + await request(`${url}/private/instances/${id}`, { + method: 'patch', + token, + data: { auth_token: null } + }) + + mutate(`/private/instances/${id}`, null) + } + + const setNewToken = async (token: string): Promise<void> => { + await request(`${url}/private/instances/${id}`, { + method: 'patch', + token, + data: { auth_token: token } + }) + + mutate(`/private/instances/${id}`, null) + } + + return { updateInstance, deleteInstance, setNewToken, clearToken } } export function useBackendInstances(): HttpResponse<MerchantBackend.Instances.InstancesResponse> { diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx @@ -95,7 +95,7 @@ function ApplicationStatusRoutes(): VNode { const addTokenCleanerNemo = useCallback((c: () => void) => { addTokenCleaner(c) }, [cleaner]) return <div id="app"> - <ConfigContext.Provider value={backendConfig.data || {}}> + <ConfigContext.Provider value={backendConfig.data || { currency: '', version: '' }}> <NavigationBar lang={lang} setLang={setLang} onLogout={() => { cleaners.forEach(c => c()) }} /> <Sidebar /> <Notifications notifications={notifications} removeNotification={removeNotification} /> diff --git a/packages/frontend/src/messages/en.po b/packages/frontend/src/messages/en.po @@ -151,6 +151,9 @@ msgstr "Update this instance" msgid "Cancel" msgstr "Cancel" +msgid "Clear" +msgstr "Clear" + msgid "Confirm" msgstr "Confirm" @@ -160,6 +163,16 @@ msgstr "English [en]" msgid "es" msgstr "EspaƱol [es]" + +msgid "fields.instance.old_token.label" +msgstr "Old token" + +msgid "fields.instance.new_token.label" +msgstr "New token" + +msgid "validations." +msgstr "New token" + msgid "fields.instance.id.label" msgstr "Id" @@ -191,4 +204,3 @@ msgstr "Instance details" - diff --git a/packages/frontend/src/routes/instances/create/CreatePage.tsx b/packages/frontend/src/routes/instances/create/CreatePage.tsx @@ -20,15 +20,25 @@ */ import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { MerchantBackend } from "../../../declaration"; +import { useContext, useState } from "preact/hooks"; +import { Amount, MerchantBackend, RelativeTime } from "../../../declaration"; import * as yup from 'yup'; -import { YupField } from "../../../components/yup/YupField" +import { FormErrors, FormProvider } from "../../../components/form/Field" import { InstanceCreateSchema 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 { 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"; + +type Entity = MerchantBackend.Instances.InstanceConfigurationMessage interface Props { - onCreate: (d: MerchantBackend.Instances.InstanceConfigurationMessage) => void; + onCreate: (d: Entity) => void; isLoading: boolean; onBack: () => void; } @@ -37,7 +47,7 @@ interface KeyValue { [key: string]: string; } -function with_defaults(): Partial<MerchantBackend.Instances.InstanceConfigurationMessage> { +function with_defaults(): Partial<Entity> { return { default_pay_delay: { d_ms: 1000 }, default_wire_fee_amortization: 10, @@ -47,12 +57,12 @@ function with_defaults(): Partial<MerchantBackend.Instances.InstanceConfiguratio export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode { const [value, valueHandler] = useState(with_defaults()) - const [errors, setErrors] = useState<KeyValue>({}) + const [errors, setErrors] = useState<FormErrors<Entity>>({}) const submit = (): void => { try { schema.validateSync(value, { abortEarly: false }) - onCreate(schema.cast(value) as MerchantBackend.Instances.InstanceConfigurationMessage); + onCreate(schema.cast(value) as Entity); onBack() } catch (err) { const errors = err.inner as yup.ValidationError[] @@ -60,6 +70,9 @@ export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode { setErrors(pathMessages) } } + const backend = useContext(BackendContext) + const config = useContext(ConfigContext) + return <div> <section class="section is-title-bar"> @@ -96,11 +109,35 @@ export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode { <div class="columns"> <div class="column" /> <div class="column is-two-thirds"> - {Object.keys(schema.fields) - .map(f => <YupField name={f} - field={f} errors={errors} object={value} - valueHandler={valueHandler} info={schema.fields[f].describe()} - />)} + <FormProvider<Entity> errors={errors} object={value} valueHandler={valueHandler} > + + <InputWithAddon<Entity> name="id" addon={`${backend.url}/private/instances/`} /> + + <Input<Entity> name="name" /> + + <InputSecured<Entity> name="auth_token" /> + + <InputArray<Entity> name="payto_uris" /> + + <InputCurrency<Entity> name="default_max_deposit_fee" currency={config.currency} /> + + <InputCurrency<Entity> name="default_max_wire_fee" currency={config.currency} /> + + <Input<Entity> name="default_wire_fee_amortization" /> + + <InputGroup name="address"> + <Input<Entity> name="name" /> + </InputGroup> + + <InputGroup name="jurisdiction"> + <Input<Entity> name="name" /> + </InputGroup> + + <InputDuration<Entity> name="default_pay_delay" /> + + <InputDuration<Entity> name="default_wire_transfer_delay" /> + + </FormProvider> <div class="buttons is-right"> <button class="button" onClick={onBack} ><Message id="Cancel" /></button> <button class="button is-success" onClick={submit} ><Message id="Confirm" /></button> diff --git a/packages/frontend/src/routes/instances/details/DetailPage.tsx b/packages/frontend/src/routes/instances/details/DetailPage.tsx @@ -22,10 +22,12 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { MerchantBackend } from "../../../declaration"; -import { YupField } from "../../../components/yup/YupField" import { InstanceSchema as schema } from '../../../schemas' import { Message } from "preact-messages"; +import { Input } from "../../../components/form/Input"; +import { FormProvider } from "../../../components/form/Field"; +type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage; interface Props { onUpdate: () => void; onDelete: () => void; @@ -37,7 +39,7 @@ interface KeyValue { [key: string]: string; } -function convert(from: MerchantBackend.Instances.QueryInstancesResponse): MerchantBackend.Instances.InstanceReconfigurationMessage { +function convert(from: MerchantBackend.Instances.QueryInstancesResponse): Entity { const { accounts, ...rest } = from const payto_uris = accounts.filter(a => a.active).map(a => a.payto_uri) const defaults = { @@ -49,7 +51,7 @@ function convert(from: MerchantBackend.Instances.QueryInstancesResponse): Mercha } export function DetailPage({ onUpdate, isLoading, selected, onDelete }: Props): VNode { - const [value, valueHandler] = useState(convert(selected)) + const [value, valueHandler] = useState<Partial<Entity>>(convert(selected)) const [errors, setErrors] = useState<KeyValue>({}) return <div> @@ -88,12 +90,12 @@ export function DetailPage({ onUpdate, isLoading, selected, onDelete }: Props): <div class="columns"> <div class="column" /> <div class="column is-6"> - {Object.keys(schema.pick(['name','address']).fields) - .map(f => <YupField name={f} - field={f} errors={errors} object={value} - valueHandler={valueHandler} info={schema.fields[f].describe()} - readonly - />)} + <FormProvider<Entity> errors={errors} object={value} valueHandler={valueHandler} > + + <Input<Entity> name="name" readonly /> + <Input<Entity> name="payto_uris" readonly /> + + </FormProvider> <div class="buttons is-right"> <button class="button is-danger" onClick={() => onDelete()} > <span class="icon"><i class="mdi mdi-delete" /></span> diff --git a/packages/frontend/src/routes/instances/details/index.tsx b/packages/frontend/src/routes/instances/details/index.tsx @@ -18,8 +18,8 @@ import { useContext, useState } from "preact/hooks"; import { InstanceContext } from "../../../context/backend"; import { Notification } from "../../../utils/types"; import { useBackendInstance, useBackendInstanceMutateAPI, SwrError } from "../../../hooks/backend"; -import { DeleteModal } from "../list/DeleteModal"; import { DetailPage } from "./DetailPage"; +import { DeleteModal } from "../../../components/modal"; interface Props { onUnauthorized: () => VNode; diff --git a/packages/frontend/src/routes/instances/list/CardTable.tsx b/packages/frontend/src/routes/instances/list/CardTable.tsx @@ -1,100 +0,0 @@ -/* - 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, VNode } from "preact"; -import { Message } from "preact-messages"; -import { useEffect, useState } from "preact/hooks"; -import { MerchantBackend } from "../../../declaration"; -import { EmptyTable } from "./EmptyTable"; -import { Table } from "./Table"; - -interface Props { - instances: MerchantBackend.Instances.Instance[]; - onUpdate: (id: string) => void; - onDelete: (id: MerchantBackend.Instances.Instance) => void; - onCreate: () => void; - selected?: boolean; -} - -interface Actions { - element: MerchantBackend.Instances.Instance; - type: 'DELETE' | 'UPDATE'; -} - -function notEmpty<TValue>(value: TValue | null | undefined): value is TValue { - return value !== null && value !== undefined; -} - -function buildActions(intances: MerchantBackend.Instances.Instance[], selected: string[], action: 'DELETE'): Actions[] { - return selected.map(id => intances.find(i => i.id === id)) - .filter(notEmpty) - .map(id => ({ element: id, type: action })) -} - -export function CardTable({ instances, onCreate, onUpdate, onDelete, selected }: Props): VNode { - const [actionQueue, actionQueueHandler] = useState<Actions[]>([]); - const [rowSelection, rowSelectionHandler] = useState<string[]>([]) - - useEffect(() => { - if (actionQueue.length > 0 && !selected && actionQueue[0].type == 'DELETE') { - onDelete(actionQueue[0].element) - actionQueueHandler(actionQueue.slice(1)) - } - }, [actionQueue, selected, onDelete]) - - useEffect(() => { - if (actionQueue.length > 0 && !selected && actionQueue[0].type == 'UPDATE') { - onUpdate(actionQueue[0].element.id) - actionQueueHandler(actionQueue.slice(1)) - } - }, [actionQueue, selected, onUpdate]) - - - 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><Message id="Instances" /></p> - - <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"> - <div class="table-wrapper has-mobile-cards"> - {instances.length > 0 ? - <Table instances={instances} onUpdate={onUpdate} onDelete={onDelete} rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} /> : - <EmptyTable /> - } - </div> - </div> - </div> - </div> -} -\ No newline at end of file diff --git a/packages/frontend/src/routes/instances/list/DeleteModal.tsx b/packages/frontend/src/routes/instances/list/DeleteModal.tsx @@ -1,36 +0,0 @@ -/* - 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, VNode } from "preact"; -import { ConfirmModal } from "../../../components/modal"; - -interface Props { - element: {id: string, name: string}; - onCancel: () => void; - onConfirm: (id: string) => void; -} - -export function DeleteModal({ element, onCancel, onConfirm }: Props): VNode { - return <ConfirmModal description="delete_instance" danger active onCancel={onCancel} onConfirm={() => onConfirm(element.id)}> - <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/packages/frontend/src/routes/instances/list/EmptyTable.tsx b/packages/frontend/src/routes/instances/list/EmptyTable.tsx @@ -1,32 +0,0 @@ -/* - 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, VNode } from "preact"; -import { Message, useMessageTemplate } from "preact-messages"; - -export 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><Message id="There is no instances yet, add more pressing the + sign" /></p> - </div> -} diff --git a/packages/frontend/src/routes/instances/list/Table.tsx b/packages/frontend/src/routes/instances/list/Table.tsx @@ -21,10 +21,67 @@ import { h, VNode } from "preact" import { Message } from "preact-messages" -import { StateUpdater } from "preact/hooks" +import { StateUpdater, useEffect, useState } from "preact/hooks" import { MerchantBackend } from "../../../declaration" interface Props { + instances: MerchantBackend.Instances.Instance[]; + onUpdate: (id: string) => void; + onDelete: (id: MerchantBackend.Instances.Instance) => void; + onCreate: () => void; + selected?: boolean; +} + +export function CardTable({ instances, onCreate, onUpdate, onDelete, selected }: Props): VNode { + const [actionQueue, actionQueueHandler] = useState<Actions[]>([]); + const [rowSelection, rowSelectionHandler] = useState<string[]>([]) + + useEffect(() => { + if (actionQueue.length > 0 && !selected && actionQueue[0].type == 'DELETE') { + onDelete(actionQueue[0].element) + actionQueueHandler(actionQueue.slice(1)) + } + }, [actionQueue, selected, onDelete]) + + useEffect(() => { + if (actionQueue.length > 0 && !selected && actionQueue[0].type == 'UPDATE') { + onUpdate(actionQueue[0].element.id) + actionQueueHandler(actionQueue.slice(1)) + } + }, [actionQueue, selected, onUpdate]) + + + 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><Message id="Instances" /></p> + + <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"> + <div class="table-wrapper has-mobile-cards"> + {instances.length > 0 ? + <Table instances={instances} onUpdate={onUpdate} onDelete={onDelete} rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} /> : + <EmptyTable /> + } + </div> + </div> + </div> + </div> +} +interface TableProps { rowSelection: string[]; instances: MerchantBackend.Instances.Instance[]; onUpdate: (id: string) => void; @@ -36,7 +93,7 @@ function toggleSelected<T>(id: T): (prev: T[]) => T[] { return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] : prev.filter(e => e != id) } -export function Table({ rowSelection, rowSelectionHandler, instances, onUpdate, onDelete }: Props): VNode { +function Table({ rowSelection, rowSelectionHandler, instances, onUpdate, onDelete }: TableProps): VNode { return ( <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <thead> @@ -82,4 +139,31 @@ export function Table({ rowSelection, rowSelectionHandler, instances, onUpdate, </tbody> </table>) -} -\ No newline at end of file +} + +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><Message id="There is no instances yet, add more pressing the + sign" /></p> + </div> +} + + +interface Actions { + element: MerchantBackend.Instances.Instance; + type: 'DELETE' | 'UPDATE'; +} + +function notEmpty<TValue>(value: TValue | null | undefined): value is TValue { + return value !== null && value !== undefined; +} + +function buildActions(intances: MerchantBackend.Instances.Instance[], selected: string[], action: 'DELETE'): Actions[] { + return selected.map(id => intances.find(i => i.id === id)) + .filter(notEmpty) + .map(id => ({ element: id, type: action })) +} + + diff --git a/packages/frontend/src/routes/instances/list/View.tsx b/packages/frontend/src/routes/instances/list/View.tsx @@ -21,7 +21,7 @@ import { h, VNode } from "preact"; import { MerchantBackend } from "../../../declaration"; -import { CardTable } from './CardTable'; +import { CardTable } from './Table'; import { Message } from "preact-messages"; interface Props { diff --git a/packages/frontend/src/routes/instances/list/index.tsx b/packages/frontend/src/routes/instances/list/index.tsx @@ -25,7 +25,8 @@ import { useBackendInstances, useBackendInstanceMutateAPI, SwrError } from '../. import { useState } from 'preact/hooks'; import { MerchantBackend } from '../../../declaration'; import { Notification } from '../../../utils/types'; -import { DeleteModal } from './DeleteModal'; +import { DeleteModal } from '../../../components/modal'; + interface Props { pushNotification: (n: Notification) => void; onUnauthorized: () => VNode; diff --git a/packages/frontend/src/routes/instances/update/UpdatePage.tsx b/packages/frontend/src/routes/instances/update/UpdatePage.tsx @@ -20,25 +20,33 @@ */ import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { MerchantBackend } from "../../../declaration"; +import { useContext, useState } from "preact/hooks"; +import { Amount, MerchantBackend, RelativeTime } from "../../../declaration"; import * as yup from 'yup'; -import { YupField } from "../../../components/yup/YupField" +import { FormProvider, FormErrors } from "../../../components/form/Field" +import { InputGroup } from "../../../components/form/InputGroup" + 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 { InputArray } from "../../../components/form/InputArray"; +import { InputDuration } from "../../../components/form/InputDuration"; +import { InputCurrency } from "../../../components/form/InputCurrency"; + +type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage interface Props { - onUpdate: (d: MerchantBackend.Instances.InstanceReconfigurationMessage) => void; + onUpdate: (d: Entity) => void; selected: MerchantBackend.Instances.QueryInstancesResponse; isLoading: boolean; onBack: () => void; } -interface KeyValue { - [key: string]: string; -} - -function convert(from: MerchantBackend.Instances.QueryInstancesResponse): MerchantBackend.Instances.InstanceReconfigurationMessage { +function convert(from: MerchantBackend.Instances.QueryInstancesResponse): Entity { const { accounts, ...rest } = from const payto_uris = accounts.filter(a => a.active).map(a => a.payto_uri) const defaults = { @@ -49,9 +57,11 @@ function convert(from: MerchantBackend.Instances.QueryInstancesResponse): Mercha return { ...defaults, ...rest, payto_uris }; } + + export function UpdatePage({ onUpdate, isLoading, selected, onBack }: Props): VNode { - const [value, valueHandler] = useState(convert(selected)) - const [errors, setErrors] = useState<KeyValue>({}) + const [value, valueHandler] = useState<Partial<Entity>>(convert(selected)) + const [errors, setErrors] = useState<FormErrors<Entity>>({}) const submit = (): void => { try { @@ -64,6 +74,7 @@ export function UpdatePage({ onUpdate, isLoading, selected, onBack }: Props): VN setErrors(pathMessages) } } + const config = useContext(ConfigContext) return <div> <section class="section is-title-bar"> @@ -101,12 +112,35 @@ export function UpdatePage({ onUpdate, isLoading, selected, onBack }: Props): VN <div class="columns"> <div class="column" /> <div class="column is-two-thirds"> - {Object.keys(schema.fields) - .map(f => <YupField name={f} - field={f} errors={errors} object={value} - valueHandler={valueHandler} info={schema.fields[f].describe()} - />)} - <div class="buttons is-right"> + <FormProvider<Entity> errors={errors} object={value} valueHandler={valueHandler} > + + <Input<Entity> name="name" /> + + <InputSecured<Entity> name="auth_token" /> + + <InputArray<Entity> name="payto_uris" /> + + <InputCurrency<Entity> name="default_max_deposit_fee" currency={config.currency} /> + + <InputCurrency<Entity> name="default_max_wire_fee" currency={config.currency} /> + + <Input<Entity> name="default_wire_fee_amortization" /> + + <InputGroup name="address"> + <Input<Entity> name="name" /> + </InputGroup> + + <InputGroup name="jurisdiction"> + <Input<Entity> name="name" /> + </InputGroup> + + <InputDuration<Entity> name="default_pay_delay" /> + + <InputDuration<Entity> name="default_wire_transfer_delay" /> + + </FormProvider> + + <div class="buttons is-right"> <button class="button" onClick={onBack} ><Message id="Cancel" /></button> <button class="button is-success" onClick={submit} ><Message id="Confirm" /></button> </div> @@ -117,5 +151,4 @@ export function UpdatePage({ onUpdate, isLoading, selected, onBack }: Props): VN </div> - // </ConfirmModal> -} -\ No newline at end of file +} diff --git a/packages/frontend/src/routes/instances/update/index.tsx b/packages/frontend/src/routes/instances/update/index.tsx @@ -13,7 +13,10 @@ 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/> */ -import { h, VNode } from "preact"; +import { Fragment, h, VNode } from "preact"; +import { useContext, useState } from "preact/hooks"; +import { UpdateTokenModal } from "../../../components/modal"; +import { InstanceContext } from "../../../context/backend"; import { MerchantBackend } from "../../../declaration"; import { SwrError, useBackendInstance, useBackendInstanceMutateAPI } from "../../../hooks/backend"; import { UpdatePage } from "./UpdatePage"; @@ -30,22 +33,34 @@ interface Props { } export default function Update({ onBack, onConfirm, onLoadError, onUpdateError, onUnauthorized }: Props): VNode { - const { updateInstance } = useBackendInstanceMutateAPI(); + const { updateInstance, setNewToken, clearToken } = useBackendInstanceMutateAPI(); + const [updatingToken, setUpdatingToken] = useState<string | null>(null) const details = useBackendInstance() + const { id } = useContext(InstanceContext) if (!details.data) { if (details.unauthorized) return onUnauthorized() if (details.error) return onLoadError(details.error) return <div> loading .... - </div> + </div> } - return <UpdatePage - onBack={onBack} - isLoading={false} - selected={details.data} - onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => { - return updateInstance(d).then(onConfirm).catch(onUpdateError) - }} /> + return <Fragment> + <UpdatePage + onBack={onBack} + isLoading={false} + selected={details.data} + onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => { + return updateInstance(d).then(onConfirm).catch(onUpdateError) + }} /> + <input type="checkbox" checked={updatingToken !== null} onClick={() => setUpdatingToken(!updatingToken ? "caca": null)} /> + {updatingToken && <UpdateTokenModal + oldToken={updatingToken} + element={{ id, name: details.data.name }} + onCancel={() => setUpdatingToken(null)} + onClear={() => clearToken()} + onConfirm={(newToken) => setNewToken(newToken) } + />} + </Fragment> } \ No newline at end of file