[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] 01/03: refactor router
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] 01/03: refactor router |
Date: |
Mon, 22 Feb 2021 23:09:13 +0100 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository merchant-backoffice.
commit 59e4d7e1873e701ef6041219bce0e0431e659de5
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Mon Feb 22 01:28:39 2021 -0300
refactor router
---
CHANGELOG.md | 5 +-
packages/frontend/src/components/auth/index.tsx | 32 ++--
packages/frontend/src/components/footer/index.tsx | 49 -----
packages/frontend/src/components/modal/index.tsx | 4 +-
packages/frontend/src/components/navbar/index.tsx | 9 +-
.../notifications/Notifications.stories.tsx | 59 +++---
.../src/components/notifications/index.tsx | 4 +-
packages/frontend/src/components/yup/YupField.tsx | 27 +--
packages/frontend/src/context/backend.ts | 28 ++-
packages/frontend/src/hooks/backend.ts | 105 ++++++-----
packages/frontend/src/hooks/index.ts | 51 +++--
packages/frontend/src/hooks/notifications.ts | 22 ++-
packages/frontend/src/i18n/index.ts | 6 +-
packages/frontend/src/index.tsx | 207 ++++++++++++++++++---
.../instances/{ => create}/Create.stories.tsx | 0
.../routes/instances/{ => create}/CreatePage.tsx | 16 +-
.../frontend/src/routes/instances/create/index.tsx | 21 +++
.../{UpdatePage.tsx => details/DetailPage.tsx} | 35 ++--
.../src/routes/instances/details/index.tsx | 52 ++++++
packages/frontend/src/routes/instances/index.tsx | 97 ----------
.../src/routes/instances/{ => list}/CardTable.tsx | 19 +-
.../routes/instances/{ => list}/DeleteModal.tsx | 9 +-
.../src/routes/instances/{ => list}/EmptyTable.tsx | 0
.../src/routes/instances/{ => list}/Table.tsx | 11 +-
.../routes/instances/{ => list}/View.stories.tsx | 0
.../src/routes/instances/{ => list}/View.tsx | 32 +---
.../frontend/src/routes/instances/list/index.tsx | 108 +++++++++++
.../routes/instances/{ => update}/UpdatePage.tsx | 21 ++-
.../frontend/src/routes/instances/update/index.tsx | 37 ++++
packages/frontend/src/routes/login/index.tsx | 9 +
packages/frontend/src/scss/main.scss | 11 +-
packages/frontend/tests/header.test.tsx | 11 +-
pnpm-lock.yaml | 86 ++++-----
33 files changed, 723 insertions(+), 460 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e093a1f..9aedc98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,14 +27,11 @@ and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0
- remove checkbox from auth token, use button (manage auth)
- prepend payto:// to account
- validate on change everything
- - remove footer
+ <!-- - remove footer -->
- implement proper error handling
- - PATCH payto uri not working as expeced: re-enable, creating with multiple
uris
- replace Yup and type definition with a taler-library for the purpose (first
wait Florian to refactor wallet core)
- add more doc style comments
- - check the field names in forms dont break spaces
- - update spanish lang
- save every auth token of different instances
- configure eslint
- configure prettier
diff --git a/packages/frontend/src/components/auth/index.tsx
b/packages/frontend/src/components/auth/index.tsx
index 6ec69c0..8c0a20b 100644
--- a/packages/frontend/src/components/auth/index.tsx
+++ b/packages/frontend/src/components/auth/index.tsx
@@ -20,40 +20,41 @@
*/
import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { useBackend } from "../../hooks";
+import { useContext, useState } from "preact/hooks";
+import { BackendContext } from "../../context/backend";
interface Props {
- onConfirm?: () => void;
+ onConfirm: (backend: string, token?: string) => void;
}
-export function LoginPage({ onConfirm }: Props): VNode {
- const [backend, setBackend] = useBackend()
- const [url, setUrl] = useState(backend.url)
- const [token, setToken] = useState(backend.token)
- const [changeToken, setChangeToken] = useState(false)
+export function LoginModal({ onConfirm }: Props): VNode {
+ const backend = useContext(BackendContext)
+ const [updatingToken, setUpdatingToken] = useState(false)
+ const [token, setToken] = useState(backend.token || '')
+ const [url, setURL] = useState(backend.url)
+ const toggleUpdatingToken = (): void => setUpdatingToken(v => !v)
return <div class="modal is-active is-clipped">
<div class="modal-background" />
<div class="modal-card">
<header class="modal-card-head">
- <p class="modal-card-title">Authentication required</p>
+ <p class="modal-card-title">Login required</p>
</header>
<section class="modal-card-body">
Please enter your auth token. Token should have "secret-token:" and
start with Bearer or ApiKey
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Change Token</label>
+ <label class="label">Updte token</label>
</div>
<div class="field-body">
<div class="field has-addons">
<label class="b-checkbox checkbox">
- <input type="checkbox" checked={changeToken} onClick={(): void
=> setChangeToken(!changeToken)} />
+ <input type="checkbox" checked={updatingToken}
onClick={toggleUpdatingToken} />
<span class="check" />
</label>
<p class="control is-expanded">
- <input class="input" type="text" placeholder={changeToken ?
"set new token" : "hidden token value"} disabled={!changeToken} name="id"
value={token} onInput={(e): void => setToken(e?.currentTarget.value)} />
+ <input class="input" type="text" placeholder={updatingToken ?
"set new token" : "hidden token value"} disabled={!updatingToken} name="id"
value={token} onInput={(e): void => setToken(e?.currentTarget.value)} />
</p>
</div>
</div>
@@ -65,16 +66,15 @@ export function LoginPage({ onConfirm }: Props): VNode {
<div class="field-body">
<div class="field">
<p class="control is-expanded">
- <input class="input" type="text" placeholder="set new url"
name="id" value={url} onInput={(e): void => setUrl(e?.currentTarget.value)} />
+ <input class="input" type="text" placeholder="set new url"
name="id" value={url} onInput={(e): void => setURL(e?.currentTarget.value)} />
</p>
</div>
</div>
</div>
</section>
- <footer class="modal-card-foot">
+ <footer class="modal-card-foot " style={{justifyContent: 'flex-end'}}>
<button class="button is-info" onClick={(): void => {
- setBackend({ token, url });
- onConfirm && onConfirm();
+ onConfirm(url, updatingToken && token ? token : undefined);
}} >Confirm</button>
</footer>
</div>
diff --git a/packages/frontend/src/components/footer/index.tsx
b/packages/frontend/src/components/footer/index.tsx
deleted file mode 100644
index 6a31e88..0000000
--- a/packages/frontend/src/components/footer/index.tsx
+++ /dev/null
@@ -1,49 +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';
-
-export function Footer(): VNode {
- return (
- <footer class="footer">
- <div class="container-fluid">
- <div class="level">
- <div class="level-left">
- <div class="level-item">copyleft</div>
- <div class="level-item">
- <a href="https://taler.net/" style="height: 20px">
- Taler
- </a>
- </div>
- </div>
- <div class="level-right">
- <div class="level-item">
- <div class="logo">
- sebasjm
- </div>
- </div>
- </div>
- </div>
- </div>
- </footer>
- )
-}
-
diff --git a/packages/frontend/src/components/modal/index.tsx
b/packages/frontend/src/components/modal/index.tsx
index ec6d681..e3c957c 100644
--- a/packages/frontend/src/components/modal/index.tsx
+++ b/packages/frontend/src/components/modal/index.tsx
@@ -43,8 +43,8 @@ export function ConfirmModal({ active, description, onCancel,
onConfirm, childre
{children}
</section>
<footer class="modal-card-foot">
- <button class="button " onClick={onCancel} ><Text id="cancel"
/></button>
- <button class={danger ? "button is-danger " : "button is-info "}
onClick={onConfirm} ><Text id="confirm" /></button>
+ <button class="button " onClick={onCancel} ><Text id="text.cancel"
/></button>
+ <button class={danger ? "button is-danger " : "button is-info "}
onClick={onConfirm} ><Text id="text.confirm" /></button>
</footer>
</div>
<button class="modal-close is-large " aria-label="close"
onClick={onCancel} />
diff --git a/packages/frontend/src/components/navbar/index.tsx
b/packages/frontend/src/components/navbar/index.tsx
index 626f471..ec00c70 100644
--- a/packages/frontend/src/components/navbar/index.tsx
+++ b/packages/frontend/src/components/navbar/index.tsx
@@ -20,8 +20,6 @@
*/
import { h, VNode } from 'preact';
-import { useState } from 'preact/hooks';
-import { LoginPage } from '../auth';
import { translations } from '../../i18n'
// TODO: Fix compilation problem
// import * as logo from '../../assets/logo.jpeg';
@@ -29,10 +27,10 @@ import { translations } from '../../i18n'
interface Props {
lang: string;
setLang: (l: string) => void;
+ onLogout: () => void;
}
-export function NavigationBar({ lang, setLang }: Props): VNode {
- const [showLogin, setShowLogin] = useState(false)
+export function NavigationBar({ lang, setLang, onLogout }: Props): VNode {
return (<nav class="navbar is-fixed-top" role="navigation" aria-label="main
navigation">
<div class="navbar-brand">
<a class="navbar-item" href="https://taler.net">
@@ -62,11 +60,10 @@ export function NavigationBar({ lang, setLang }: Props):
VNode {
</div>
</div>
<div class="navbar-item">
- <button class="button is-primary" onClick={(): void =>
setShowLogin(true)}>Change access</button>
+ <button class="button is-primary" onClick={(): void =>
onLogout()}>Log out</button>
</div>
</div>
</div>
- {showLogin && <LoginPage onConfirm={(): void => setShowLogin(false)} />}
</nav>
);
}
\ No newline at end of file
diff --git
a/packages/frontend/src/components/notifications/Notifications.stories.tsx
b/packages/frontend/src/components/notifications/Notifications.stories.tsx
index 88549fe..734644d 100644
--- a/packages/frontend/src/components/notifications/Notifications.stories.tsx
+++ b/packages/frontend/src/components/notifications/Notifications.stories.tsx
@@ -26,40 +26,29 @@ import { Notifications } from './index'
export default {
title: 'Components/Notification',
component: Notifications,
+ argTypes: {
+ removeNotification: { action: 'removeNotification' },
+ },
};
-export const NotificationInfo = (): VNode => {
- return <div>
- <Notifications notifications={[{
- messageId: 'unauthorized',
- type: 'INFO',
- }]} />
- </div>
-};
-
-export const NotificationWarn = (): VNode => {
- return <div>
- <Notifications notifications={[{
- messageId: 'unauthorized',
- type: 'WARN',
- }]} />
- </div>
-};
-
-export const NotificationError = (): VNode => {
- return <div>
- <Notifications notifications={[{
- messageId: 'unauthorized',
- type: 'ERROR',
- }]} />
- </div>
-};
-
-export const NotificationSuccess = (): VNode => {
- return <div>
- <Notifications notifications={[{
- messageId: 'unauthorized',
- type: 'SUCCESS',
- }]} />
- </div>
-};
+export const Info = (a: any) => <Notifications {...a} />;
+Info.args = {
+ notifications: [{
+ messageId: 'unauthorized',
+ type: 'INFO',
+ }]
+}
+export const Warn = (a: any) => <Notifications {...a} />;
+Warn.args = {
+ notifications: [{
+ messageId: 'unauthorized',
+ type: 'WARN',
+ }]
+}
+export const Error = (a: any) => <Notifications {...a} />;
+Error.args = {
+ notifications: [{
+ messageId: 'unauthorized',
+ type: 'ERROR',
+ }]
+}
diff --git a/packages/frontend/src/components/notifications/index.tsx
b/packages/frontend/src/components/notifications/index.tsx
index 7c32ed2..a581823 100644
--- a/packages/frontend/src/components/notifications/index.tsx
+++ b/packages/frontend/src/components/notifications/index.tsx
@@ -25,6 +25,7 @@ import { MessageType, Notification } from "../../declaration";
interface Props {
notifications: Notification[];
+ removeNotification: (n: Notification) => void;
}
function messageStyle(type: MessageType): string {
@@ -37,11 +38,12 @@ function messageStyle(type: MessageType): string {
}
}
-export function Notifications({ notifications }: Props): VNode {
+export function Notifications({ notifications, removeNotification }: Props):
VNode {
return <div class="toast">
{notifications.map(n => <article class={messageStyle(n.type)}>
<div class="message-header">
<p><Text id={`notification.${n.messageId}.title`} /> </p>
+ <button class="delete" onClick={()=> removeNotification(n)} />
</div>
<div class="message-body">
<Text id={`notification.${n.messageId}.description`} fields={n.params}
/>
diff --git a/packages/frontend/src/components/yup/YupField.tsx
b/packages/frontend/src/components/yup/YupField.tsx
index 5bbcf33..ea15bda 100644
--- a/packages/frontend/src/components/yup/YupField.tsx
+++ b/packages/frontend/src/components/yup/YupField.tsx
@@ -23,8 +23,10 @@ import { h, VNode } from "preact";
import { Text, useText } from "preact-i18n";
import { StateUpdater, useContext, useState } from "preact/hooks";
import { intervalToDuration, formatDuration } from 'date-fns'
+import { BackendContext, ConfigContext } from '../../context/backend';
-function readableDuration(duration: number): string {
+function readableDuration(duration?: number): string {
+ if (!duration) return ""
return formatDuration(intervalToDuration({ start: 0, end: duration }))
}
@@ -55,8 +57,6 @@ interface Props {
valueHandler: StateUpdater<any>;
info: any;
}
-import { ConfigContext } from '../../context/backend';
-
export function YupField({ name, field, errors, object, valueHandler, info }:
Props): VNode {
const updateField = (f: string) => (v: string): void => valueHandler((prev:
any) => ({ ...prev, [f]: v }))
const values = {
@@ -65,24 +65,27 @@ export function YupField({ name, field, errors, object,
valueHandler, info }: Pr
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]}
- onChange={(updater: any): void => valueHandler((prev: any) => ({
...prev, [field]: updater(prev[field]) }))}
- />
+ case 'group': {
+ return <YupObjectInput name={name}
+ info={info} errors={errors}
+ value={object && object[field]}
+ onChange={(updater: any): void => valueHandler((prev: any) => ({
...prev, [field]: updater(prev[field]) }))}
+ />
+ }
case '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} />;
+ }
+ return <YupInput {...values} />;
}
- case 'url': return <YupInputWithAddon {...values}
addon={`${config.backendURL}/private/instances/`} />;
+ case 'url': return <YupInputWithAddon {...values}
addon={`${backend.url}/private/instances/`} />;
case 'secured': return <YupInputSecured {...values} />;
- case 'duration': return <YupInputWithAddon
addon={readableDuration(values.value?.d_ms)} atTheEnd {...values}
value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void =>
values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />;
+ 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/context/backend.ts
b/packages/frontend/src/context/backend.ts
index beb6a1b..64e4dd2 100644
--- a/packages/frontend/src/context/backend.ts
+++ b/packages/frontend/src/context/backend.ts
@@ -1,11 +1,29 @@
import { createContext } from 'preact'
-interface GlobalContext {
- backendURL: string;
+export interface BackendContextType {
+ url: string;
+ token?: string;
+ changeBackend: (url: string) => void;
+ clearToken: () => void;
+ updateToken: (token:string) => void;
+ lang: string;
+ setLang: (lang: string) => void;
+}
+
+export interface ConfigContextType {
currency?: string;
}
-export const ConfigContext = createContext<GlobalContext>({
- backendURL: '',
- currency: '',
+export const BackendContext = createContext<BackendContextType>({
+ url: '',
+ lang: 'en',
+ token: undefined,
+ changeBackend: () => null,
+ clearToken: () => null,
+ updateToken: () => null,
+ setLang: () => null,
+})
+
+export const ConfigContext = createContext<ConfigContextType>({
+ currency: undefined,
})
diff --git a/packages/frontend/src/hooks/backend.ts
b/packages/frontend/src/hooks/backend.ts
index 0804c7b..c1118f9 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -14,14 +14,16 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
import useSWR, { mutate } from 'swr';
import axios from 'axios'
import { MerchantBackend } from '../declaration';
+import { useContext } from 'preact/hooks';
+import { BackendContext } from '../context/backend';
type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError<T>;
@@ -30,82 +32,101 @@ interface HttpResponseOk<T> {
}
interface HttpResponseError<T> {
data: undefined;
- needsAuth: boolean;
+ unauthorized: boolean;
error: Error;
}
type Methods = 'get' | 'post' | 'patch' | 'delete' | 'put';
-async function request(url: string, method?: Methods, data?: any):
Promise<any> {
- const backend = localStorage.getItem('backend-url')
- const token = localStorage.getItem('backend-token')
- const headers = token ? { Authorization: `${token}` } : undefined
+interface RequestOptions {
+ method?: Methods;
+ token?: string;
+ data?: any;
+}
+
+async function request(url: string, options: RequestOptions = {}):
Promise<any> {
+ const headers = options.token ? { Authorization: `${options.token}` } :
undefined
try {
const res = await axios({
- method: method || 'get',
- url: `${backend}${url}`,
+ method: options.method || 'get',
+ url,
responseType: 'json',
headers,
- data
+ data: options.data
})
return res.data
} catch (e) {
const info = e.response?.data
const status = e.response?.status
- throw { info, status, error:e, backend, hasToken: !!token }
+ throw { info, status, error: e, backend: url, hasToken: !!options.token }
}
}
-async function fetcher(url: string): Promise<any> {
- return request(url, 'get')
+function fetcher(url: string, token: string, backend: string) {
+ return request(`${backend}${url}`, { token })
}
-interface WithCreate<T> {
- create: (data: T) => Promise<void>;
-}
-interface WithUpdate<T> {
- update: (id: string, data: T) => Promise<void>;
-}
-interface WithDelete {
- delete: (id: string) => Promise<void>;
+interface BackendMutateAPI {
+ createInstance: (data:
MerchantBackend.Instances.InstanceConfigurationMessage) => Promise<void>;
+ updateInstance: (id: string, data:
MerchantBackend.Instances.InstanceReconfigurationMessage) => Promise<void>;
+ deleteInstance: (id: string) => Promise<void>;
}
-export function useBackendInstances():
HttpResponse<MerchantBackend.Instances.InstancesResponse> &
WithCreate<MerchantBackend.Instances.InstanceConfigurationMessage> {
- const { data, error } =
useSWR<MerchantBackend.Instances.InstancesResponse>('/private/instances',
fetcher)
+export function useBackendMutateAPI(): BackendMutateAPI {
+ const { url, token } = useContext(BackendContext)
- const create = async (instance:
MerchantBackend.Instances.InstanceConfigurationMessage): Promise<void> => {
- await request('/private/instances', 'post', instance)
+ const createInstance = async (instance:
MerchantBackend.Instances.InstanceConfigurationMessage): Promise<void> => {
+ await request(`${url}/private/instances`, {
+ method: 'post',
+ token,
+ data: instance
+ })
mutate('/private/instances')
}
-
- return { data, needsAuth: error?.status === 401, error, create }
-}
-
-export function useBackendInstance(id: string | null):
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> &
WithUpdate<MerchantBackend.Instances.InstanceReconfigurationMessage> &
WithDelete {
- const { data, error } =
useSWR<MerchantBackend.Instances.QueryInstancesResponse>(id ?
`/private/instances/${id}` : null, fetcher)
-
- const update = async (updateId: string, instance:
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
- await request(`/private/instances/${updateId}`, 'patch', instance)
+ const updateInstance = async (updateId: string, instance:
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
+ await request(`${url}/private/instances/${updateId}`, {
+ method: 'patch',
+ token,
+ data: instance
+ })
mutate('/private/instances', null)
mutate(`/private/instances/${updateId}`, null)
};
- const _delete = async (deleteId: string): Promise<void> => {
- await request(`/private/instances/${deleteId}`, 'delete')
+ const deleteInstance = async (deleteId: string): Promise<void> => {
+ await request(`${url}/private/instances/${deleteId}`, {
+ method: 'delete',
+ token,
+ })
mutate('/private/instances', null)
mutate(`/private/instances/${deleteId}`, null)
}
- return { data, needsAuth: error?.status === 401, error, update, delete:
_delete }
+ return { createInstance, updateInstance, deleteInstance }
+}
+
+export function useBackendInstances():
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
+ const { url, token } = useContext(BackendContext)
+ const { data, error } =
useSWR<MerchantBackend.Instances.InstancesResponse>(['/private/instances',
token, url], fetcher)
+
+ return { data, unauthorized: error?.status === 401, error }
+}
+
+export function useBackendInstance(id: string | null):
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
+ const { url, token } = useContext(BackendContext)
+ const { data, error } =
useSWR<MerchantBackend.Instances.QueryInstancesResponse>(id ?
[`/private/instances/${id}`, token, url] : null, fetcher)
+
+ return { data, unauthorized: error?.status === 401, error }
}
-export function useBackendConfig():
HttpResponse<MerchantBackend.VersionResponse> {
- const { data, error } = useSWR<MerchantBackend.VersionResponse>(`/config`,
fetcher)
+export function useBackendConfig():
HttpResponse<MerchantBackend.VersionResponse> {
+ const { url, token } = useContext(BackendContext)
+ const { data, error } = useSWR<MerchantBackend.VersionResponse>(['/config',
token, url], fetcher)
- return { data, needsAuth: error?.status === 401, error }
+ return { data, unauthorized: error?.status === 401, error }
}
diff --git a/packages/frontend/src/hooks/index.ts
b/packages/frontend/src/hooks/index.ts
index 267b765..12d65d4 100644
--- a/packages/frontend/src/hooks/index.ts
+++ b/packages/frontend/src/hooks/index.ts
@@ -19,29 +19,52 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { StateUpdater, useState } from "preact/hooks";
+import { StateUpdater, useEffect, useState } from "preact/hooks";
import { mutate } from 'swr';
-interface State {
- token?: string;
- url: string;
+export function useBackendURL(): [string, StateUpdater<string>] {
+ return useNotNullLocalStorage('backend-url', window.location.origin)
+}
+export function useBackendDefaultToken(): [string | undefined,
StateUpdater<string | undefined>] {
+ return useLocalStorage('backend-token')
}
-export function useBackend(): [State, StateUpdater<State>] {
- const [url, setUrl] = useLocalStorage('backend-url', window.location.origin)
- const [token, setToken] = useLocalStorage('backend-token')
-
- const updater: StateUpdater<State> = (value: State | ((value: State) =>
State)) => {
- const valueToStore = value instanceof Function ? value({ token, url: url
|| window.location.origin }) : value;
- setUrl(valueToStore.url)
- setToken(valueToStore.token)
+export function useBackendInstanceToken(id: string): [string | undefined,
StateUpdater<string | undefined>, VoidFunction] {
+ const [token, setToken] = useLocalStorage(`backend-token-${id}`)
+ const [ids, setIds] = useLocalStorage(`backend-token-ids`)
- mutate('/private/instances', null)
+ function clearAllTokens() {
+ // TODO: refactor this
+ ids?.split(',').map(i => localStorage.removeItem(`backend-token-${i}`))
}
- return [{ token, url: url || window.location.origin }, updater]
+ useEffect(() => {
+ setIds((ids: string | undefined): string | undefined => {
+ if (!ids) return ids
+ const all = ids.split(',')
+ if (all.includes(id)) return ids
+ return all.concat(id).filter(Boolean).join(',')
+ })
+ }, [id, setIds])
+
+ return [token, setToken, clearAllTokens]
}
+// export function useBackend(): [State, StateUpdater<State>] {
+// const [url, setUrl] = useLocalStorage('backend-url',
window.location.origin)
+// const [token, setToken] = useLocalStorage('backend-token')
+
+// const updater: StateUpdater<State> = (value: State | ((value: State) =>
State)) => {
+// const valueToStore = value instanceof Function ? value({ token, url:
url || window.location.origin }) : value;
+// setUrl(valueToStore.url)
+// setToken(valueToStore.token)
+
+// mutate('/private/instances', null)
+// }
+
+// return [{ token, url: url || window.location.origin }, updater]
+// }
+
export function useLang(): [string, StateUpdater<string>] {
return useNotNullLocalStorage('lang-preference', typeof window !==
"undefined" ? navigator.language || (navigator as any).userLanguage : 'en')
}
diff --git a/packages/frontend/src/hooks/notifications.ts
b/packages/frontend/src/hooks/notifications.ts
index 986ddaf..2a3cb3f 100644
--- a/packages/frontend/src/hooks/notifications.ts
+++ b/packages/frontend/src/hooks/notifications.ts
@@ -14,10 +14,10 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
import { useState } from "preact/hooks";
import { Notification } from '../declaration';
@@ -25,16 +25,24 @@ import { Notification } from '../declaration';
interface Result {
notifications: Notification[];
pushNotification: (n: Notification) => void;
+ removeNotification: (n: Notification) => void;
}
+type NotificationWithDate = Notification & { since: Date }
+
export function useNotifications(timeout = 3000): Result {
- const [notifications, setNotifications] = useState<(Notification & { since:
Date })[]>([])
+ const [notifications, setNotifications] =
useState<(NotificationWithDate)[]>([])
+
const pushNotification = (n: Notification): void => {
const entry = { ...n, since: new Date() }
setNotifications(ns => [...ns, entry])
- setTimeout(() => {
+ if (n.type !== 'ERROR') setTimeout(() => {
setNotifications(ns => ns.filter(x => x.since !== entry.since))
}, timeout)
}
- return { notifications, pushNotification }
+
+ const removeNotification = (notif: Notification) => {
+ setNotifications((ns: NotificationWithDate[]) => ns.filter(n => n !==
notif))
+ }
+ return { notifications, pushNotification, removeNotification }
}
diff --git a/packages/frontend/src/i18n/index.ts
b/packages/frontend/src/i18n/index.ts
index 27b9f72..cdc6984 100644
--- a/packages/frontend/src/i18n/index.ts
+++ b/packages/frontend/src/i18n/index.ts
@@ -150,8 +150,6 @@ export const translations = {
description: 'the delete process completed'
},
},
- cancel: 'cancel',
- confirm: 'confirm',
fields: {
instance: {
id: {
@@ -271,6 +269,10 @@ export const translations = {
list_of_configured_instances: 'List of configured instances',
create_new_instance: 'Create new instance',
+ cancel: 'cancel',
+ confirm: 'confirm',
+ delete: 'delete',
+ update: 'update',
instance: {
empty_list: 'No instance configured yet, setup one pressing the +
button',
}
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index c613514..07d34ac 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -22,43 +22,200 @@
import "./scss/main.scss"
import { h, VNode } from 'preact';
-import { Route, Router } from 'preact-router';
+import { useContext, useEffect } from "preact/hooks";
+import { Route, Router, route } from 'preact-router';
import { IntlProvider } from 'preact-i18n';
-import { Footer } from './components/footer';
+import { Notification } from "./declaration";
import { Sidebar } from './components/sidebar';
import { NavigationBar } from './components/navbar';
import { Notifications } from './components/notifications';
import { translations } from './i18n';
-import { useBackend, useLang } from './hooks';
-
-import NotFoundPage from './routes/notfound';
-import Instances from './routes/instances';
+import { useBackendURL, useBackendDefaultToken, useLang,
useBackendInstanceToken } from './hooks';
import { useNotifications } from "./hooks/notifications";
-import { ConfigContext } from './context/backend';
+import { BackendContext } from './context/backend';
import { useBackendConfig } from "./hooks/backend";
-export default function App(): VNode {
- const { notifications, pushNotification } = useNotifications()
+import NotFoundPage from './routes/notfound';
+import Login from './routes/login';
+import Instances from './routes/instances/list';
+import Create from "./routes/instances/create";
+import Details from "./routes/instances/details";
+import Update from "./routes/instances/update";
+
+enum RootPages {
+ root = '/',
+ instances = '/instances',
+ new = '/new',
+ instance_id_route = '/instance/:id/:rest*',
+}
+
+enum InstancePages {
+ details = '/instance/:id/',
+ update = '/instance/:id/update',
+}
+
+function Redirect({ to }: { to: string }): null {
+ useEffect(() => {
+ route(to, true)
+ })
+ return null
+}
+
+
+
+function AppRouting(): VNode {
+ const { notifications, pushNotification, removeNotification } =
useNotifications()
+ const { lang, setLang, clearToken, changeBackend, updateToken } =
useContext(BackendContext)
+ const backendConfig = useBackendConfig();
+
+ const error = backendConfig.data || backendConfig.error
+
+ useEffect(() =>{
+ pushNotification({
+ messageId: 'error',
+ type: 'ERROR',
+ params: error
+ })
+ }, [error, pushNotification])
+
+ return <div id="app">
+ <NavigationBar lang={lang} setLang={setLang} onLogout={clearToken} />
+ <Sidebar />
+ <Notifications notifications={notifications}
removeNotification={removeNotification} />
+ {!backendConfig.data ?
+ <Route default
+ component={Login}
+
+ onConfirm={(url: string, token?: string) => {
+ changeBackend(url)
+ if (token) updateToken(token)
+ route(RootPages.instances)
+ }} /> :
+ <Route default component={AppReady} pushNotification={pushNotification}
/>
+ }
+ </div>
+}
+
+function AppReady({ pushNotification }: { pushNotification: (n: Notification)
=> void }): VNode {
+ const { changeBackend, updateToken } = useContext(BackendContext)
+
+ const updateLoginStatus = (url: string, token?: string) => {
+ changeBackend(url)
+ if (token) updateToken(token)
+ }
+
+ return <Router>
+ <Route path={RootPages.root} component={Redirect} to={RootPages.instances}
/>
+
+ <Route path={RootPages.instances}
+ component={Instances}
+
+ onCreate={() => {
+ route(RootPages.new)
+ }}
+
+ onUpdate={(id: string): void => {
+ route(`/instance/${id}/update`)
+ }}
+
+ onUnauthorized={() => <Login onConfirm={updateLoginStatus} />}
+
+ onError={(error: Error) => {
+ pushNotification({ messageId: 'error', params: error, type: 'ERROR' })
+ return <div />
+ }}
+ />
+
+ <Route path={RootPages.new}
+ component={Create}
+ onBack={() => route(RootPages.instances)}
+
+ onConfirm={() => {
+ pushNotification({ messageId: 'create_success', type: 'SUCCESS' })
+ route(RootPages.instances)
+ }}
+
+ onError={(error: any) => {
+ pushNotification({ messageId: 'create_error', type: 'ERROR', params:
error })
+ }}
+ />
+
+ <Route path={RootPages.instance_id_route} component={SubPages}
pushNotification={pushNotification} />
+
+ <Route default component={NotFoundPage} />
+
+ </Router>
+}
+
+function useBackendContextState() {
const [lang, setLang] = useLang()
- const [{url: backendURL}] = useBackend();
- const { data } = useBackendConfig();
+ const [url, changeBackend] = useBackendURL();
+ const [token, updateToken] = useBackendDefaultToken();
+ const clearToken = () => updateToken(undefined)
+
+ return { url, token, changeBackend, clearToken, updateToken, lang, setLang }
+}
+export default function Application(): VNode {
+ const state = useBackendContextState()
return (
- <ConfigContext.Provider value={{backendURL, currency: data &&
data.currency}}>
-
- <IntlProvider definition={(translations as any)[lang] ||
translations.en}>
- <div id="app">
- <NavigationBar lang={lang} setLang={setLang} />
- <Sidebar />
- <Notifications notifications={notifications} />
- <Router>
- <Route path="/" component={Instances}
pushNotification={pushNotification} />
- <Route default component={NotFoundPage} />
- </Router>
- <Footer />
- </div>
+ <BackendContext.Provider value={state}>
+ <IntlProvider definition={(translations as any)[state.lang] ||
translations.en}>
+ <AppRouting />
</IntlProvider >
- </ConfigContext.Provider>
+ </BackendContext.Provider>
);
+}
+interface SubPagesProps {
+ id: string;
+ pushNotification: (n: Notification) => void;
+}
+
+function SubPages({ id, pushNotification }: SubPagesProps): VNode {
+ const [, updateToken] = useBackendInstanceToken(id);
+ const { changeBackend } = useContext(BackendContext)
+
+ const updateLoginStatus = (url: string, token?: string) => {
+ changeBackend(url)
+ if (token) updateToken(token)
+ }
+
+ return <Router>
+ <Route path={InstancePages.details}
+ component={Details}
+ onUnauthorized={() => <Login onConfirm={updateLoginStatus} />}
+ onUpdate={() => {
+ route(`/instance/${id}/update`)
+ }}
+ onLoadError={(e: Error) => {
+ pushNotification({ messageId: 'update_load_error', type: 'ERROR',
params: e })
+ route(`/instance/${id}/`)
+ return <div />
+ }}
+
+ />
+
+ <Route path={InstancePages.update}
+ component={Update}
+ onUnauthorized={() => <Login onConfirm={updateLoginStatus} />}
+ onLoadError={(e: Error) => {
+ pushNotification({ messageId: 'update_load_error', type: 'ERROR',
params: e })
+ route(`/instance/${id}/`)
+ return <div />
+ }}
+ onBack={() => {
+ route(`/instance/${id}/`)
+ }}
+ onConfirm={() => {
+ pushNotification({ messageId: 'create_success', type: 'SUCCESS' })
+ route(`/instance/${id}/`)
+ }}
+ onUpdateError={(e: Error) => {
+ pushNotification({ messageId: 'update_error', type: 'ERROR', params: e
})
+ }}
+ />
+
+ <Route default component={NotFoundPage} />
+ </Router>
}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/Create.stories.tsx
b/packages/frontend/src/routes/instances/create/Create.stories.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/Create.stories.tsx
rename to packages/frontend/src/routes/instances/create/Create.stories.tsx
diff --git a/packages/frontend/src/routes/instances/CreatePage.tsx
b/packages/frontend/src/routes/instances/create/CreatePage.tsx
similarity index 89%
rename from packages/frontend/src/routes/instances/CreatePage.tsx
rename to packages/frontend/src/routes/instances/create/CreatePage.tsx
index 7f16c2d..44aa1cc 100644
--- a/packages/frontend/src/routes/instances/CreatePage.tsx
+++ b/packages/frontend/src/routes/instances/create/CreatePage.tsx
@@ -21,16 +21,16 @@
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
-import { MerchantBackend } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
import * as yup from 'yup';
-import { YupField } from "../../components/yup/YupField"
-import { InstanceCreateSchema as schema } from '../../schemas'
+import { YupField } from "../../../components/yup/YupField"
+import { InstanceCreateSchema as schema } from '../../../schemas'
import { Text } from "preact-i18n";
interface Props {
onCreate: (d: MerchantBackend.Instances.InstanceConfigurationMessage) =>
void;
isLoading: boolean;
- goBack: () => void;
+ onBack: () => void;
}
interface KeyValue {
@@ -45,7 +45,7 @@ function with_defaults():
Partial<MerchantBackend.Instances.InstanceConfiguratio
};
}
-export function CreatePage({ onCreate, isLoading, goBack }: Props): VNode {
+export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode {
const [value, valueHandler] = useState(with_defaults())
const [errors, setErrors] = useState<KeyValue>({})
@@ -53,7 +53,7 @@ export function CreatePage({ onCreate, isLoading, goBack }:
Props): VNode {
try {
schema.validateSync(value, { abortEarly: false })
onCreate(schema.cast(value) as
MerchantBackend.Instances.InstanceConfigurationMessage);
- goBack()
+ onBack()
} catch (err) {
const errors = err.inner as yup.ValidationError[]
const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message
} }), {})
@@ -102,8 +102,8 @@ export function CreatePage({ onCreate, isLoading, goBack }:
Props): VNode {
valueHandler={valueHandler} info={schema.fields[f].describe()}
/>)}
<div class="buttons is-right">
- <button class="button" onClick={goBack} ><Text id="cancel"
/></button>
- <button class="button is-success" onClick={submit} ><Text
id="confirm" /></button>
+ <button class="button" onClick={onBack} ><Text id="text.cancel"
/></button>
+ <button class="button is-success" onClick={submit} ><Text
id="text.confirm" /></button>
</div>
</div>
<div class="column" />
diff --git a/packages/frontend/src/routes/instances/create/index.tsx
b/packages/frontend/src/routes/instances/create/index.tsx
new file mode 100644
index 0000000..4af7b5a
--- /dev/null
+++ b/packages/frontend/src/routes/instances/create/index.tsx
@@ -0,0 +1,21 @@
+import { h, VNode } from "preact";
+import { MerchantBackend } from "../../../declaration";
+import { useBackendMutateAPI } from "../../../hooks/backend";
+import { CreatePage } from "./CreatePage";
+
+interface Props {
+ onBack: () => void;
+ onConfirm: () => void;
+ onError: (error: any) => void;
+}
+
+export default function Create({ onBack, onConfirm, onError }: Props): VNode {
+ const { createInstance } = useBackendMutateAPI();
+
+ return <CreatePage
+ onBack={onBack}
+ isLoading={false}
+ onCreate={(d: MerchantBackend.Instances.InstanceConfigurationMessage):
Promise<void> => {
+ return createInstance(d).then(onConfirm).catch(onError)
+ }} />
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/UpdatePage.tsx
b/packages/frontend/src/routes/instances/details/DetailPage.tsx
similarity index 71%
copy from packages/frontend/src/routes/instances/UpdatePage.tsx
copy to packages/frontend/src/routes/instances/details/DetailPage.tsx
index feed125..f4ab4a7 100644
--- a/packages/frontend/src/routes/instances/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/details/DetailPage.tsx
@@ -21,17 +21,17 @@
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
-import { MerchantBackend, WidthId } from "../../declaration";
-import * as yup from 'yup';
-import { YupField } from "../../components/yup/YupField"
-import { InstanceUpdateSchema as schema } from '../../schemas'
+import { MerchantBackend } from "../../../declaration";
+import { YupField } from "../../../components/yup/YupField"
+import { InstanceUpdateSchema as schema } from '../../../schemas'
import { Text } from "preact-i18n";
interface Props {
- onUpdate: (id: string, d:
MerchantBackend.Instances.InstanceReconfigurationMessage) => void;
- selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
+ onUpdate: () => void;
+ onDelete: () => void;
+ selected: MerchantBackend.Instances.QueryInstancesResponse;
+ id: string,
isLoading: boolean;
- goBack: () => void;
}
interface KeyValue {
@@ -49,22 +49,10 @@ function convert(from:
MerchantBackend.Instances.QueryInstancesResponse): Mercha
return { ...defaults, ...rest, payto_uris };
}
-export function UpdatePage({ onUpdate, isLoading, selected, goBack }: Props):
VNode {
+export function DetailPage({ onUpdate, isLoading, selected, onDelete }:
Props): VNode {
const [value, valueHandler] = useState(convert(selected))
const [errors, setErrors] = useState<KeyValue>({})
- const submit = (): void => {
- try {
- schema.validateSync(value, { abortEarly: false })
- onUpdate(selected.id, schema.cast(value));
- goBack()
- } catch (err) {
- const errors = err.inner as yup.ValidationError[]
- const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message
} }), {})
- setErrors(pathMessages)
- }
- }
-
return <div>
<section class="section is-title-bar">
@@ -106,9 +94,9 @@ export function UpdatePage({ onUpdate, isLoading, selected,
goBack }: Props): VN
field={f} errors={errors} object={value}
valueHandler={valueHandler} info={schema.fields[f].describe()}
/>)}
- <div class="buttons is-right">
- <button class="button" onClick={goBack} ><Text id="cancel"
/></button>
- <button class="button is-success" onClick={submit} ><Text
id="confirm" /></button>
+ <div class="buttons is-right">
+ <button class="button is-danger" onClick={() => onDelete()} ><Text
id="text.delete" /></button>
+ <button class="button is-success" onClick={() => onUpdate()}
><Text id="text.update" /></button>
</div>
</div>
<div class="column" />
@@ -117,5 +105,4 @@ export function UpdatePage({ onUpdate, isLoading, selected,
goBack }: Props): VN
</div>
- // </ConfirmModal>
}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/details/index.tsx
b/packages/frontend/src/routes/instances/details/index.tsx
new file mode 100644
index 0000000..177278d
--- /dev/null
+++ b/packages/frontend/src/routes/instances/details/index.tsx
@@ -0,0 +1,52 @@
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Notification } from "../../../declaration";
+import { useBackendInstance, useBackendMutateAPI } from
"../../../hooks/backend";
+import { DeleteModal } from "../list/DeleteModal";
+import { DetailPage } from "./DetailPage";
+
+interface Props {
+ onUnauthorized: () => VNode;
+ onLoadError: (e: Error) => VNode;
+ onUpdate: () => void;
+ id: string;
+ pushNotification: (n: Notification) => void;
+}
+
+export default function Detail({ onUpdate, onLoadError, onUnauthorized, id,
pushNotification }: Props): VNode {
+ const details = useBackendInstance(id)
+ const [deleting, setDeleting] = useState<boolean>(false)
+
+ const { deleteInstance } = useBackendMutateAPI()
+
+ if (!details.data) {
+ if (details.unauthorized) return onUnauthorized()
+ if (details.error) return onLoadError(details.error)
+ return <div>
+ loading ....
+ </div>
+ }
+
+ return <Fragment>
+ <DetailPage
+ isLoading={false}
+ selected={details.data} id={id}
+ onUpdate={onUpdate}
+ onDelete={() => setDeleting(true) }
+ />
+ {deleting && <DeleteModal
+ element={{name: details.data.name, id }}
+ onCancel={() => setDeleting(false) }
+ onConfirm={async (id: string): Promise<void> => {
+ try {
+ await deleteInstance(id)
+ pushNotification({ messageId: 'delete_success', type: 'SUCCESS' })
+ } catch (error) {
+ pushNotification({ messageId: 'delete_error', type: 'ERROR', params:
error })
+ }
+ setDeleting(false)
+ }}
+ />}
+
+ </Fragment>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/index.tsx
b/packages/frontend/src/routes/instances/index.tsx
deleted file mode 100644
index e44b087..0000000
--- a/packages/frontend/src/routes/instances/index.tsx
+++ /dev/null
@@ -1,97 +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 { View } from './View';
-import { LoginPage } from '../../components/auth';
-import { useBackendInstance, useBackendInstances } from '../../hooks/backend';
-import { useEffect, useState } from 'preact/hooks';
-import { Notification } from '../../declaration';
-import { CreatePage } from './CreatePage';
-import { UpdatePage } from './UpdatePage';
-
-interface Props {
- pushNotification: (n: Notification) => void;
-}
-
-export default function Instances({ pushNotification }: Props): VNode {
- const list = useBackendInstances()
- const [selectedId, select] = useState<string | null>(null)
- const details = useBackendInstance(selectedId)
- const [create, setCreate] = useState<boolean>(false)
- const [update, setUpdate] = useState<boolean>(false)
-
-
- const requiresToken = (!list.data && list.needsAuth) || (selectedId != null
&& !details.data && details.needsAuth)
- const isLoadingTheList = (!list.data && !list.error)
- const isLoadingTheDetails = (!details.data && !details.error)
-
- const genericError = !list.data && list.error || !details.data &&
details.error
-
- useEffect(() => {
- if (requiresToken) {
- pushNotification({ messageId: 'unauthorized', type: 'ERROR' })
- } else if (genericError) {
- pushNotification({ messageId: 'error', params: genericError, type:
'ERROR' })
- }
- }, [requiresToken, genericError])
-
-
- if (requiresToken) {
- return <LoginPage />
- }
-
- if (create) {
- return <CreatePage
- goBack={() => setCreate(false)}
- isLoading={false}
-
- onCreate={(d): Promise<void> => list.create(d)
- .then((): void => pushNotification({ messageId: 'create_success',
type: 'SUCCESS' }))
- .catch((error): void => pushNotification({ messageId: 'create_error',
type: 'ERROR', params: error }))
- }
- />
- }
-
- if (update && details.data && selectedId) {
- return <UpdatePage
- goBack={() => setUpdate(false)}
- isLoading={false}
- selected={{ ...details.data, id: selectedId }}
- onUpdate={(id, d): Promise<void> => details.update(id, d)
- .then((): void => pushNotification({ messageId: 'update_success',
type: 'SUCCESS' }))
- .catch((error): void => pushNotification({ messageId: 'update_error',
type: 'ERROR', params: error }))
- }
- />
- }
-
- return <View instances={list.data?.instances || []}
- isLoading={isLoadingTheList || isLoadingTheDetails}
- onCreate={setCreate}
- onUpdate={setUpdate}
- onDelete={(id): Promise<void> => details.delete(id)
- .then((): void => pushNotification({ messageId: 'delete_success', type:
'SUCCESS' }))
- .catch((error): void => pushNotification({ messageId: 'delete_error',
type: 'ERROR', params: error }))
- }
- onSelect={select}
- selected={!details.data || !selectedId ? undefined : { ...details.data,
id: selectedId }}
- />;
-}
diff --git a/packages/frontend/src/routes/instances/CardTable.tsx
b/packages/frontend/src/routes/instances/list/CardTable.tsx
similarity index 82%
rename from packages/frontend/src/routes/instances/CardTable.tsx
rename to packages/frontend/src/routes/instances/list/CardTable.tsx
index 2a4e9d5..46c32ff 100644
--- a/packages/frontend/src/routes/instances/CardTable.tsx
+++ b/packages/frontend/src/routes/instances/list/CardTable.tsx
@@ -22,15 +22,16 @@
import { h, VNode } from "preact";
import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks";
-import { MerchantBackend, WidthId as WithId } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
import { EmptyTable } from "./EmptyTable";
import { Table } from "./Table";
interface Props {
instances: MerchantBackend.Instances.Instance[];
- onSelect: (id: string | null, action: 'UPDATE' | 'DELETE') => void;
+ onUpdate: (id: string) => void;
+ onDelete: (id: MerchantBackend.Instances.Instance) => void;
onCreate: () => void;
- selected: MerchantBackend.Instances.QueryInstancesResponse & WithId |
undefined;
+ selected?: boolean;
}
interface Actions {
@@ -48,23 +49,23 @@ function buildActions(intances:
MerchantBackend.Instances.Instance[], selected:
.map(id => ({ element: id, type: action }))
}
-export function CardTable({ instances, onCreate, onSelect, selected }: Props):
VNode {
+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') {
- onSelect(actionQueue[0].element.id, 'DELETE')
+ onDelete(actionQueue[0].element)
actionQueueHandler(actionQueue.slice(1))
}
- }, [actionQueue, selected, onSelect])
+ }, [actionQueue, selected, onDelete])
useEffect(() => {
if (actionQueue.length > 0 && !selected && actionQueue[0].type ==
'UPDATE') {
- onSelect(actionQueue[0].element.id, 'UPDATE')
+ onUpdate(actionQueue[0].element.id)
actionQueueHandler(actionQueue.slice(1))
}
- }, [actionQueue, selected, onSelect])
+ }, [actionQueue, selected, onUpdate])
return <div class="card has-table">
@@ -89,7 +90,7 @@ export function CardTable({ instances, onCreate, onSelect,
selected }: Props): V
<div class="b-table has-pagination">
<div class="table-wrapper has-mobile-cards">
{instances.length > 0 ?
- <Table instances={instances} onSelect={onSelect}
rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} /> :
+ <Table instances={instances} onUpdate={onUpdate}
onDelete={onDelete} rowSelection={rowSelection}
rowSelectionHandler={rowSelectionHandler} /> :
<EmptyTable />
}
</div>
diff --git a/packages/frontend/src/routes/instances/DeleteModal.tsx
b/packages/frontend/src/routes/instances/list/DeleteModal.tsx
similarity index 76%
rename from packages/frontend/src/routes/instances/DeleteModal.tsx
rename to packages/frontend/src/routes/instances/list/DeleteModal.tsx
index a682d12..f526456 100644
--- a/packages/frontend/src/routes/instances/DeleteModal.tsx
+++ b/packages/frontend/src/routes/instances/list/DeleteModal.tsx
@@ -20,17 +20,16 @@
*/
import { h, VNode } from "preact";
-import { MerchantBackend, WidthId } from "../../declaration";
-import { ConfirmModal } from "../../components/modal";
+import { ConfirmModal } from "../../../components/modal";
interface Props {
- element: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
+ element: {id: string, name: string};
onCancel: () => void;
- onConfirm: (i: MerchantBackend.Instances.QueryInstancesResponse & WidthId)
=> 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)}>
+ 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/EmptyTable.tsx
b/packages/frontend/src/routes/instances/list/EmptyTable.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/EmptyTable.tsx
rename to packages/frontend/src/routes/instances/list/EmptyTable.tsx
diff --git a/packages/frontend/src/routes/instances/Table.tsx
b/packages/frontend/src/routes/instances/list/Table.tsx
similarity index 91%
rename from packages/frontend/src/routes/instances/Table.tsx
rename to packages/frontend/src/routes/instances/list/Table.tsx
index 4faf6b5..eb44447 100644
--- a/packages/frontend/src/routes/instances/Table.tsx
+++ b/packages/frontend/src/routes/instances/list/Table.tsx
@@ -22,12 +22,13 @@
import { h, VNode } from "preact"
import { Text } from "preact-i18n"
import { StateUpdater } from "preact/hooks"
-import { MerchantBackend } from "../../declaration"
+import { MerchantBackend } from "../../../declaration"
interface Props {
rowSelection: string[];
instances: MerchantBackend.Instances.Instance[];
- onSelect: (id: string | null, action: 'UPDATE' | 'DELETE') => void;
+ onUpdate: (id: string) => void;
+ onDelete: (id: MerchantBackend.Instances.Instance) => void;
rowSelectionHandler: StateUpdater<string[]>;
}
@@ -35,7 +36,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, onSelect
}: Props): VNode {
+export function Table({ rowSelection, rowSelectionHandler, instances,
onUpdate, onDelete }: Props): VNode {
return (
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
@@ -68,10 +69,10 @@ export function Table({ rowSelection, rowSelectionHandler,
instances, onSelect }
<td >{i.payment_targets}</td>
<td class="is-actions-cell">
<div class="buttons is-right">
- <button class="button is-small is-primary" type="button"
onClick={(): void => onSelect(i.id, 'UPDATE')}>
+ <button class="button is-small is-primary" type="button"
onClick={(): void => onUpdate(i.id)}>
<span class="icon"><i class="mdi mdi-eye" /></span>
</button>
- <button class="button is-small is-danger jb-modal"
type="button" onClick={(): void => onSelect(i.id, 'DELETE')}>
+ <button class="button is-small is-danger jb-modal"
type="button" onClick={(): void => onDelete(i)}>
<span class="icon"><i class="mdi mdi-trash-can" /></span>
</button>
</div>
diff --git a/packages/frontend/src/routes/instances/View.stories.tsx
b/packages/frontend/src/routes/instances/list/View.stories.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/View.stories.tsx
rename to packages/frontend/src/routes/instances/list/View.stories.tsx
diff --git a/packages/frontend/src/routes/instances/View.tsx
b/packages/frontend/src/routes/instances/list/View.tsx
similarity index 65%
rename from packages/frontend/src/routes/instances/View.tsx
rename to packages/frontend/src/routes/instances/list/View.tsx
index 438e54f..48b2a46 100644
--- a/packages/frontend/src/routes/instances/View.tsx
+++ b/packages/frontend/src/routes/instances/list/View.tsx
@@ -20,30 +20,20 @@
*/
import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { MerchantBackend, WidthId } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
import { CardTable } from './CardTable';
-import { DeleteModal } from './DeleteModal'
import { Text } from "preact-i18n";
interface Props {
instances: MerchantBackend.Instances.Instance[];
- onCreate: (s: boolean) => void;
- onUpdate: (s: boolean) => void;
- onDelete: (id: string) => void;
- onSelect: (id: string | null) => void;
- selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId |
undefined;
+ onCreate: () => void;
+ onUpdate: (id: string) => void;
+ onDelete: (id: MerchantBackend.Instances.Instance) => void;
+ selected?: boolean;
isLoading: boolean;
}
-export function View({ instances, isLoading, onCreate, onDelete, onSelect,
onUpdate, selected }: Props): VNode {
- const [action, setAction] = useState<'UPDATE' | 'DELETE' | null>(null)
-
- const onSelectAction = (id: string | null, action?: 'UPDATE' | 'DELETE'):
void => {
- onSelect(id)
- setAction(action || null)
- if (action === 'UPDATE') onUpdate(true)
- }
+export function View({ instances, isLoading, onCreate, onDelete, onUpdate,
selected }: Props): VNode {
return <div id="app">
<section class="section is-title-bar">
@@ -77,16 +67,8 @@ export function View({ instances, isLoading, onCreate,
onDelete, onSelect, onUpd
</div>
</section>
<section class="section is-main-section">
- <CardTable instances={instances} onSelect={onSelectAction}
selected={selected} onCreate={(): void => onCreate(true)} />
+ <CardTable instances={instances} onDelete={onDelete} onUpdate={onUpdate}
selected={selected} onCreate={onCreate} />
</section>
- {selected && action === 'DELETE' ?
- <DeleteModal element={selected} onCancel={(): void =>
onSelectAction(null)} onConfirm={(i): void => {
- onDelete(i.id)
- onSelectAction(null);
- }}
- />
- : null}
-
</div >
}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/list/index.tsx
b/packages/frontend/src/routes/instances/list/index.tsx
new file mode 100644
index 0000000..b9eedc9
--- /dev/null
+++ b/packages/frontend/src/routes/instances/list/index.tsx
@@ -0,0 +1,108 @@
+/*
+ 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 { Fragment, h, VNode } from 'preact';
+import { View } from './View';
+import { useBackendInstances, useBackendMutateAPI } from
'../../../hooks/backend';
+import { useState } from 'preact/hooks';
+import { MerchantBackend, Notification } from '../../../declaration';
+import { DeleteModal } from './DeleteModal';
+interface Props {
+ pushNotification: (n: Notification) => void;
+ onUnauthorized: () => VNode;
+ onError: (e: Error) => VNode;
+ onCreate: () => void;
+ onUpdate: (id: string) => void;
+}
+
+export default function Instances({ pushNotification, onUnauthorized, onError,
onCreate, onUpdate }: Props): VNode {
+ const list = useBackendInstances()
+ const [deleting, setDeleting] = useState<MerchantBackend.Instances.Instance
| null>(null)
+ // const details = useBackendInstance(selectedId)
+ // const [create, setCreate] = useState<boolean>(false)
+ // const [update, setUpdate] = useState<boolean>(false)
+ const { deleteInstance } = useBackendMutateAPI()
+
+ // || (selectedId != null && !details.data && details.unauthorized)
+ const isLoadingTheList = (!list.data && !list.error)
+ // const isLoadingTheDetails = (!details.data && !details.error)
+ const error = !list.data && list.error
+ // || !details.data && details.error
+
+ if (!list.data) {
+ if (list.unauthorized) return onUnauthorized()
+ if (list.error) return onError(list.error)
+ }
+
+ // if (create) {
+ // return <CreatePage
+ // goBack={() => setCreate(false)}
+ // isLoading={false}
+
+ // onCreate={(d): Promise<void> => list.create(d)
+ // .then((): void => pushNotification({ messageId: 'create_success',
type: 'SUCCESS' }))
+ // .catch((error): void => pushNotification({ messageId:
'create_error', type: 'ERROR', params: error }))
+ // }
+ // />
+ // }
+
+ // if (update && details.data && selectedId) {
+ // return <UpdatePage
+ // goBack={() => setUpdate(false)}
+ // isLoading={false}
+ // selected={{ ...details.data, id: selectedId }}
+ // onUpdate={(id, d): Promise<void> => details.update(id, d)
+ // .then((): void => pushNotification({ messageId: 'update_success',
type: 'SUCCESS' }))
+ // .catch((error): void => pushNotification({ messageId:
'update_error', type: 'ERROR', params: error }))
+ // }
+ // />
+ // }
+ // {selected && action === 'DELETE' ?
+
+ // : null}
+
+ // (id): Promise<void> => deleteInstance(id)
+ // .then((): void => pushNotification({ messageId: 'delete_success', type:
'SUCCESS' }))
+ // .catch((error): void => pushNotification({ messageId: 'delete_error',
type: 'ERROR', params: error }))
+
+ return <Fragment>
+ <View instances={list.data?.instances || []}
+ isLoading={isLoadingTheList}
+ onDelete={setDeleting}
+ onCreate={onCreate}
+ onUpdate={onUpdate}
+ selected={!!deleting}
+ />
+ {deleting && <DeleteModal
+ element={deleting}
+ onCancel={() => setDeleting(null) }
+ onConfirm={async (id: string): Promise<void> => {
+ try {
+ await deleteInstance(id)
+ pushNotification({ messageId: 'delete_success', type: 'SUCCESS' })
+ } catch (e) {
+ pushNotification({ messageId: 'delete_error', type: 'ERROR', params:
error })
+ }
+ setDeleting(null)
+ }}
+ />}
+ </Fragment>;
+}
diff --git a/packages/frontend/src/routes/instances/UpdatePage.tsx
b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
similarity index 86%
rename from packages/frontend/src/routes/instances/UpdatePage.tsx
rename to packages/frontend/src/routes/instances/update/UpdatePage.tsx
index feed125..f99f797 100644
--- a/packages/frontend/src/routes/instances/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
@@ -21,17 +21,18 @@
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
-import { MerchantBackend, WidthId } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
import * as yup from 'yup';
-import { YupField } from "../../components/yup/YupField"
-import { InstanceUpdateSchema as schema } from '../../schemas'
+import { YupField } from "../../../components/yup/YupField"
+import { InstanceUpdateSchema as schema } from '../../../schemas'
import { Text } from "preact-i18n";
interface Props {
onUpdate: (id: string, d:
MerchantBackend.Instances.InstanceReconfigurationMessage) => void;
- selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
+ selected: MerchantBackend.Instances.QueryInstancesResponse;
+ id: string,
isLoading: boolean;
- goBack: () => void;
+ onBack: () => void;
}
interface KeyValue {
@@ -49,15 +50,15 @@ function convert(from:
MerchantBackend.Instances.QueryInstancesResponse): Mercha
return { ...defaults, ...rest, payto_uris };
}
-export function UpdatePage({ onUpdate, isLoading, selected, goBack }: Props):
VNode {
+export function UpdatePage({ onUpdate, isLoading, selected, id, onBack }:
Props): VNode {
const [value, valueHandler] = useState(convert(selected))
const [errors, setErrors] = useState<KeyValue>({})
const submit = (): void => {
try {
schema.validateSync(value, { abortEarly: false })
- onUpdate(selected.id, schema.cast(value));
- goBack()
+ onUpdate(id, schema.cast(value));
+ onBack()
} catch (err) {
const errors = err.inner as yup.ValidationError[]
const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message
} }), {})
@@ -107,8 +108,8 @@ export function UpdatePage({ onUpdate, isLoading, selected,
goBack }: Props): VN
valueHandler={valueHandler} info={schema.fields[f].describe()}
/>)}
<div class="buttons is-right">
- <button class="button" onClick={goBack} ><Text id="cancel"
/></button>
- <button class="button is-success" onClick={submit} ><Text
id="confirm" /></button>
+ <button class="button" onClick={onBack} ><Text id="text.cancel"
/></button>
+ <button class="button is-success" onClick={submit} ><Text
id="text.confirm" /></button>
</div>
</div>
<div class="column" />
diff --git a/packages/frontend/src/routes/instances/update/index.tsx
b/packages/frontend/src/routes/instances/update/index.tsx
new file mode 100644
index 0000000..8147ff8
--- /dev/null
+++ b/packages/frontend/src/routes/instances/update/index.tsx
@@ -0,0 +1,37 @@
+import { h, VNode } from "preact";
+import { MerchantBackend } from "../../../declaration";
+import { useBackendInstance, useBackendMutateAPI } from
"../../../hooks/backend";
+import { UpdatePage } from "./UpdatePage";
+
+interface Props {
+ onBack: () => void;
+ onConfirm: () => void;
+ pushNotification: (n: Notification) => void;
+
+ onUnauthorized: () => VNode;
+ onLoadError: (e: Error) => VNode;
+ onUpdateError: (e: Error) => void;
+
+ id: string;
+}
+
+export default function Update({ onBack, onConfirm, onLoadError,
onUpdateError, onUnauthorized, id }: Props): VNode {
+ const { updateInstance } = useBackendMutateAPI();
+ const details = useBackendInstance(id)
+
+ if (!details.data) {
+ if (details.unauthorized) return onUnauthorized()
+ if (details.error) return onLoadError(details.error)
+ return <div>
+ loading ....
+ </div>
+ }
+
+ return <UpdatePage
+ onBack={onBack}
+ isLoading={false}
+ selected={details.data} id={id}
+ onUpdate={(id: string, d:
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
+ return updateInstance(id, d).then(onConfirm).catch(onUpdateError)
+ }} />
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/login/index.tsx
b/packages/frontend/src/routes/login/index.tsx
new file mode 100644
index 0000000..1651bf7
--- /dev/null
+++ b/packages/frontend/src/routes/login/index.tsx
@@ -0,0 +1,9 @@
+import { h, VNode } from "preact";
+import { LoginModal } from '../../components/auth';
+
+interface Props {
+ onConfirm: (url: string, token?: string) => void;
+}
+export default function LoginPage({onConfirm}: Props):VNode {
+ return <LoginModal onConfirm={onConfirm} />
+}
\ No newline at end of file
diff --git a/packages/frontend/src/scss/main.scss
b/packages/frontend/src/scss/main.scss
index 4696247..9e9e5c0 100644
--- a/packages/frontend/src/scss/main.scss
+++ b/packages/frontend/src/scss/main.scss
@@ -48,17 +48,16 @@
@import
"../../node_modules/@creativebulma/bulma-tooltip/dist/bulma-tooltip.min.css";
.toast {
- position: fixed;
- right: 10px;
- top: 10px;
+ position: absolute;
+ width: 60%;
+ margin-left: 10%;
+ margin-right: 10%;
z-index: 999;
display: flex;
flex-direction: column;
padding: 15px;
- text-align: right;
- align-items: flex-end;
- width: auto;
+ text-align: center;
pointer-events: none;
}
diff --git a/packages/frontend/tests/header.test.tsx
b/packages/frontend/tests/header.test.tsx
index 04932b7..2eefda3 100644
--- a/packages/frontend/tests/header.test.tsx
+++ b/packages/frontend/tests/header.test.tsx
@@ -20,14 +20,13 @@
*/
import { h } from 'preact';
-import { Footer } from '../src/components/footer';
+import { Sidebar } from '../src/components/sidebar';
// See: https://github.com/preactjs/enzyme-adapter-preact-pure
import { shallow } from 'enzyme';
-describe('Initial Test of the Footer', () => {
- test('Footer renders an anchor with Taler text', () => {
- const context = shallow(<Footer />);
- expect(context.find('a').text()).toBe('Taler');
- expect(context.find('footer').length).toBe(1);
+describe('Initial Test of the Sidebar', () => {
+ test('Sidbar renders anchors with text', () => {
+ const context = shallow(<Sidebar />);
+ expect(context.find('a').map( a => a.text())).toEqual(["Instances",
"Details", "Orders", "Inventory", "Tipping", "About"]);
});
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cc9e8b1..242438d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -97,7 +97,7 @@ importers:
typedoc: ^0.20.25
typescript: ^4.1.3
yup: ^0.32.8
-lockfileVersion: 5.1
+lockfileVersion: 5.2
packages:
/@babel/code-frame/7.12.11:
dependencies:
@@ -1546,7 +1546,7 @@ packages:
jest-haste-map: 26.6.2
jest-message-util: 26.6.2
jest-regex-util: 26.0.0
- jest-resolve: 26.6.2_jest-resolve@26.6.2
+ jest-resolve: 26.6.2
jest-resolve-dependencies: 26.6.3
jest-runner: 26.6.3
jest-runtime: 26.6.3
@@ -1616,7 +1616,7 @@ packages:
istanbul-lib-source-maps: 4.0.0
istanbul-reports: 3.0.2
jest-haste-map: 26.6.2
- jest-resolve: 26.6.2_jest-resolve@26.6.2
+ jest-resolve: 26.6.2
jest-util: 26.6.2
jest-worker: 26.6.2
slash: 3.0.0
@@ -1833,7 +1833,7 @@ packages:
'@prefresh/core': 0.8.1_preact@10.5.12
'@prefresh/utils': 0.3.1
preact: 10.5.12
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
peerDependencies:
preact: ^10.4.0
@@ -2536,7 +2536,7 @@ packages:
unfetch: 4.2.0
url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
util-deprecate: 1.0.2
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-dev-middleware: 3.7.3_webpack@4.46.0
webpack-filter-warnings-plugin: 1.2.1_webpack@4.46.0
webpack-hot-middleware: 2.25.0
@@ -2646,7 +2646,7 @@ packages:
unfetch: 4.2.0
url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
util-deprecate: 1.0.2
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-dev-middleware: 3.7.3_webpack@4.46.0
webpack-filter-warnings-plugin: 1.2.1_webpack@4.46.0
webpack-hot-middleware: 2.25.0
@@ -4150,7 +4150,7 @@ packages:
dependencies:
chalk: 2.4.1
deepcopy: 1.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
peerDependencies:
webpack: ^4.28.4
@@ -4217,7 +4217,7 @@ packages:
loader-utils: 1.4.0
make-dir: 3.1.0
schema-utils: 2.7.1
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
engines:
node: '>= 8.9'
@@ -5564,7 +5564,7 @@ packages:
find-cache-dir: 3.3.1
schema-utils: 2.7.1
serialize-javascript: 4.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-sources: 1.4.3
dev: true
engines:
@@ -5730,7 +5730,7 @@ packages:
p-limit: 2.3.0
schema-utils: 1.0.0
serialize-javascript: 4.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-log: 2.0.0
dev: true
engines:
@@ -5752,7 +5752,7 @@ packages:
resolution:
integrity:
sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==
/core-js/2.6.12:
- deprecated: 'core-js@<3 is no longer maintained and not recommended for
usage due to the number of issues. Please, upgrade your dependencies to the
actual version of core-js@3.'
+ deprecated: core-js@<3 is no longer maintained and not recommended for
usage due to the number of issues. Please, upgrade your dependencies to the
actual version of core-js@3.
dev: true
requiresBuild: true
resolution:
@@ -5985,7 +5985,7 @@ packages:
postcss-value-parser: 4.1.0
schema-utils: 2.7.1
semver: 6.3.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
engines:
node: '>= 8.9.0'
@@ -6676,7 +6676,7 @@ packages:
/dotenv-webpack/1.8.0_webpack@4.46.0:
dependencies:
dotenv-defaults: 1.1.1
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
peerDependencies:
webpack: ^1 || ^2 || ^3 || ^4
@@ -7603,7 +7603,7 @@ packages:
dependencies:
loader-utils: 2.0.0
schema-utils: 3.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
engines:
node: '>= 10.13.0'
@@ -8679,7 +8679,7 @@ packages:
tapable: 1.1.3
toposort: 1.0.7
util.promisify: 1.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
engines:
node: '>=6.9'
@@ -8698,7 +8698,7 @@ packages:
pretty-error: 2.1.2
tapable: 1.1.3
util.promisify: 1.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
engines:
node: '>=6.9'
@@ -9801,7 +9801,7 @@ packages:
jest-get-type: 26.3.0
jest-jasmine2: 26.6.3
jest-regex-util: 26.0.0
- jest-resolve: 26.6.2_jest-resolve@26.6.2
+ jest-resolve: 26.6.2
jest-util: 26.6.2
jest-validate: 26.6.2
micromatch: 4.0.2
@@ -9974,7 +9974,7 @@ packages:
integrity:
sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==
/jest-pnp-resolver/1.2.2_jest-resolve@26.6.2:
dependencies:
- jest-resolve: 26.6.2_jest-resolve@26.6.2
+ jest-resolve: 26.6.2
dev: true
engines:
node: '>=6'
@@ -10022,7 +10022,7 @@ packages:
node: '>= 10.14.2'
resolution:
integrity:
sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==
- /jest-resolve/26.6.2_jest-resolve@26.6.2:
+ /jest-resolve/26.6.2:
dependencies:
'@jest/types': 26.6.2
chalk: 4.1.0
@@ -10035,8 +10035,6 @@ packages:
dev: true
engines:
node: '>= 10.14.2'
- peerDependencies:
- jest-resolve: '*'
resolution:
integrity:
sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==
/jest-runner/26.6.3:
@@ -10055,7 +10053,7 @@ packages:
jest-haste-map: 26.6.2
jest-leak-detector: 26.6.2
jest-message-util: 26.6.2
- jest-resolve: 26.6.2_jest-resolve@26.6.2
+ jest-resolve: 26.6.2
jest-runtime: 26.6.3
jest-util: 26.6.2
jest-worker: 26.6.2
@@ -10088,7 +10086,7 @@ packages:
jest-message-util: 26.6.2
jest-mock: 26.6.2
jest-regex-util: 26.0.0
- jest-resolve: 26.6.2_jest-resolve@26.6.2
+ jest-resolve: 26.6.2
jest-snapshot: 26.6.2
jest-util: 26.6.2
jest-validate: 26.6.2
@@ -10124,7 +10122,7 @@ packages:
jest-haste-map: 26.6.2
jest-matcher-utils: 26.6.2
jest-message-util: 26.6.2
- jest-resolve: 26.6.2_jest-resolve@26.6.2
+ jest-resolve: 26.6.2
natural-compare: 1.4.0
pretty-format: 26.6.2
semver: 7.3.4
@@ -11132,7 +11130,7 @@ packages:
loader-utils: 1.4.0
normalize-url: 1.9.1
schema-utils: 1.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-sources: 1.4.3
dev: true
engines:
@@ -11795,7 +11793,7 @@ packages:
dependencies:
cssnano: 4.1.10
last-call-webpack-plugin: 3.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
peerDependencies:
webpack: ^4.0.0
@@ -12803,7 +12801,7 @@ packages:
update-notifier: 4.1.3
url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
validate-npm-package-name: 3.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-bundle-analyzer: 3.9.0
webpack-dev-server: 3.11.2_webpack@4.46.0
webpack-fix-style-only-entries: 0.5.2
@@ -12962,7 +12960,7 @@ packages:
dependencies:
chalk: 3.0.0
progress: 2.0.3
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
peerDependencies:
webpack: ^1.3.0 || ^2 || ^3 || ^4 || ^5
@@ -13225,7 +13223,7 @@ packages:
dependencies:
loader-utils: 2.0.0
schema-utils: 3.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
engines:
node: '>= 10.13.0'
@@ -13873,7 +13871,7 @@ packages:
request-promise-core: 1.1.4_request@2.88.2
stealthy-require: 1.1.1
tough-cookie: 2.5.0
- deprecated: 'request-promise-native has been deprecated because it extends
the now deprecated request package, see
https://github.com/request/request/issues/3142'
+ deprecated: request-promise-native has been deprecated because it extends
the now deprecated request package, see
https://github.com/request/request/issues/3142
dev: true
engines:
node: '>=0.12.0'
@@ -13903,7 +13901,7 @@ packages:
tough-cookie: 2.5.0
tunnel-agent: 0.6.0
uuid: 3.4.0
- deprecated: 'request has been deprecated, see
https://github.com/request/request/issues/3142'
+ deprecated: request has been deprecated, see
https://github.com/request/request/issues/3142
dev: true
engines:
node: '>= 6'
@@ -13968,7 +13966,7 @@ packages:
resolution:
integrity:
sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
/resolve-url/0.2.1:
- deprecated: 'https://github.com/lydell/resolve-url#deprecated'
+ deprecated: https://github.com/lydell/resolve-url#deprecated
dev: true
resolution:
integrity: sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
@@ -14552,7 +14550,7 @@ packages:
minimatch: 3.0.4
pretty-bytes: 5.5.0
util.promisify: 1.1.1
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
peerDependencies:
webpack: '*'
@@ -15099,7 +15097,7 @@ packages:
dependencies:
loader-utils: 2.0.0
schema-utils: 2.7.1
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
engines:
node: '>= 8.9.0'
@@ -15321,7 +15319,7 @@ packages:
serialize-javascript: 4.0.0
source-map: 0.6.1
terser: 4.8.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-sources: 1.4.3
worker-farm: 1.7.0
dev: true
@@ -15341,7 +15339,7 @@ packages:
serialize-javascript: 4.0.0
source-map: 0.6.1
terser: 4.8.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-sources: 1.4.3
dev: true
engines:
@@ -16015,7 +16013,7 @@ packages:
resolution:
integrity:
sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
/urix/0.1.0:
- deprecated: 'Please see https://github.com/lydell/urix#deprecated'
+ deprecated: Please see https://github.com/lydell/urix#deprecated
dev: true
resolution:
integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
@@ -16025,7 +16023,7 @@ packages:
loader-utils: 2.0.0
mime-types: 2.1.29
schema-utils: 3.0.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
engines:
node: '>= 10.13.0'
@@ -16368,7 +16366,7 @@ packages:
mime: 2.5.2
mkdirp: 0.5.5
range-parser: 1.2.1
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-log: 2.0.0
dev: true
engines:
@@ -16408,7 +16406,7 @@ packages:
strip-ansi: 3.0.1
supports-color: 6.1.0
url: 0.11.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-dev-middleware: 3.7.3_webpack@4.46.0
webpack-log: 2.0.0
ws: 6.2.1
@@ -16427,7 +16425,7 @@ packages:
integrity:
sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==
/webpack-filter-warnings-plugin/1.2.1_webpack@4.46.0:
dependencies:
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
dev: true
engines:
node: '>= 4.3 < 5.0.0 || >= 5.10'
@@ -16482,7 +16480,7 @@ packages:
dev: true
resolution:
integrity:
sha512-kDUmfm3BZrei0y+1NTHJInejzxfhtU8eDj2M7OKb2IWrPFAeO1SOH2KuQ68MSZu9IGEHcxbkKKR1v18FrUSOmA==
- /webpack/4.46.0_webpack@4.46.0:
+ /webpack/4.46.0:
dependencies:
'@webassemblyjs/ast': 1.9.0
'@webassemblyjs/helper-module-context': 1.9.0
@@ -16506,14 +16504,12 @@ packages:
tapable: 1.1.3
terser-webpack-plugin: 1.4.5_webpack@4.46.0
watchpack: 1.7.5
- webpack: 4.46.0_webpack@4.46.0
webpack-sources: 1.4.3
dev: true
engines:
node: '>=6.11.5'
hasBin: true
peerDependencies:
- webpack: '*'
webpack-cli: '*'
webpack-command: '*'
peerDependenciesMeta:
@@ -16745,7 +16741,7 @@ packages:
fast-json-stable-stringify: 2.1.0
source-map-url: 0.4.1
upath: 1.2.0
- webpack: 4.46.0_webpack@4.46.0
+ webpack: 4.46.0
webpack-sources: 1.4.3
workbox-build: 5.1.4
dev: true
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.