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:
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