[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] branch master updated: some features
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] branch master updated: some features |
Date: |
Thu, 18 Mar 2021 05:04:40 +0100 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository merchant-backoffice.
The following commit(s) were added to refs/heads/master by this push:
new 0e36596 some features
0e36596 is described below
commit 0e365961bee9abf78fa7a0dc3eaae91a8985af3e
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Mar 18 01:04:22 2021 -0300
some features
---
CHANGELOG.md | 12 +-
packages/frontend/src/AdminRoutes.tsx | 15 +-
packages/frontend/src/ApplicationReadyRoutes.tsx | 90 ++++---
packages/frontend/src/InstanceRoutes.tsx | 290 +++++++++++++++++----
.../frontend/src/components/exception/loading.tsx | 8 +-
.../frontend/src/components/exception/login.tsx | 12 +-
packages/frontend/src/components/menu/index.tsx | 190 ++++++++------
packages/frontend/src/hooks/backend.ts | 26 +-
.../login/index.tsx => hooks/notification.ts} | 32 ++-
packages/frontend/src/index.tsx | 52 ++--
packages/frontend/src/messages/en.po | 8 +-
.../frontend/src/paths/admin/create/CreatePage.tsx | 4 +-
packages/frontend/src/paths/admin/create/index.tsx | 104 +++++++-
packages/frontend/src/paths/admin/list/index.tsx | 15 +-
.../frontend/src/paths/instance/details/index.tsx | 16 +-
.../src/paths/instance/orders/list/index.tsx | 6 +-
.../src/paths/instance/products/list/index.tsx | 13 +-
.../src/paths/instance/tips/list/index.tsx | 14 +-
.../src/paths/instance/transfers/list/index.tsx | 13 +-
.../frontend/src/paths/instance/update/index.tsx | 15 +-
packages/frontend/src/paths/login/index.tsx | 5 +-
packages/frontend/src/scss/main.scss | 65 +++++
22 files changed, 694 insertions(+), 311 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3235e21..03f65f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,16 +22,10 @@ and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0
- configure eslint
- configure prettier
- prune scss styles to reduce size
- - create a loading page to be use when the data is not ready
- some way to copy the url of a created instance
- fix mobile: some things are still on the left
- - instance id in instance list should be clickable
- edit button to go to instance settings
- - notifications should tale place between title and content, and not disapear
- - confirmation page when creating instances
- - if there is enough space for tables in mobile, make the scrollables
-
- - create default instance if it is not already
+ - check if there is a way to remove auto async for /routes
/components/{async,routes} so it can be turned on when building
non-single-bundle
- product: main action => refund
@@ -55,6 +49,10 @@ deletiing product
- update title with: Taler Backoffice: $PAGE_TITLE (#6790)
- paths should be /orders instead of /o (same others)
- if there is enough space for tables in mobile, make the scrollables (#6789)
+ - show create default instance if it is not already
+ - notifications should tale place between title and content, and not disapear
+ - create a loading page to be use when the data is not ready (test at
#/loading)
+ - confirmation page when creating instances
## [0.0.4] - 2021-03-11
- prevent letters to be input in numbers
diff --git a/packages/frontend/src/AdminRoutes.tsx
b/packages/frontend/src/AdminRoutes.tsx
index 8bd8d48..aefe6a0 100644
--- a/packages/frontend/src/AdminRoutes.tsx
+++ b/packages/frontend/src/AdminRoutes.tsx
@@ -20,7 +20,6 @@ import { Notification } from "./utils/types";
import InstanceListPage from './paths/admin/list';
import InstanceCreatePage from "./paths/admin/create";
-import NotFoundPage from './paths/notfound';
export enum AdminPaths {
list_instances = '/instances',
@@ -29,10 +28,9 @@ export enum AdminPaths {
}
interface Props {
- pushNotification: (n: Notification) => void;
- // instances: MerchantBackend.Instances.Instance[]
+ // pushNotification: (n: Notification) => void;
}
-export function AdminRoutes({ pushNotification }: Props): VNode {
+export function AdminRoutes({ }: Props): VNode {
const i18n = useMessageTemplate();
return <Router>
@@ -54,17 +52,14 @@ export function AdminRoutes({ pushNotification }: Props):
VNode {
onBack={() => route(AdminPaths.list_instances)}
onConfirm={() => {
- pushNotification({ message: i18n`create_success`, type: 'SUCCESS' });
- route(AdminPaths.list_instances);
+ // route(AdminPaths.list_instances);
}}
- onError={(error: any) => {
- pushNotification({ message: i18n`create_error`, type: 'ERROR' });
- }}
+ // onError={(error: any) => {
+ // }}
/>
- {/* <Route default component={undefined} /> */}
</Router>
}
\ No newline at end of file
diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx
b/packages/frontend/src/ApplicationReadyRoutes.tsx
index 62ed149..e36a2b5 100644
--- a/packages/frontend/src/ApplicationReadyRoutes.tsx
+++ b/packages/frontend/src/ApplicationReadyRoutes.tsx
@@ -22,16 +22,15 @@ import { Fragment, h, VNode } from 'preact';
import { route } from 'preact-router';
import { Notification } from "./utils/types";
import { useBackendContext } from './context/backend';
-import { useBackendInstances } from "./hooks/backend";
+import { useBackendInstancesTestForAdmin } from "./hooks/backend";
import { InstanceRoutes } from "./InstanceRoutes";
import LoginPage from './paths/login';
import { INSTANCE_ID_LOOKUP } from './utils/constants';
-import { NotYetReadyAppMenu, Menu } from './components/menu';
+import { NotYetReadyAppMenu, Menu, NotificationCard } from './components/menu';
import { useMessageTemplate } from 'preact-messages';
interface Props {
- pushNotification: (n: Notification) => void;
}
-export function ApplicationReadyRoutes({ pushNotification }: Props): VNode {
+export function ApplicationReadyRoutes({ }: Props): VNode {
const i18n = useMessageTemplate();
const { url: currentBaseUrl, changeBackend, updateToken, clearAllTokens } =
useBackendContext();
@@ -39,19 +38,26 @@ export function ApplicationReadyRoutes({ pushNotification
}: Props): VNode {
changeBackend(url);
if (token) updateToken(token);
};
- const list = useBackendInstances()
+ const list = useBackendInstancesTestForAdmin()
+
+ const clearTokenAndGoToRoot = () => {
+ clearAllTokens();
+ route('/')
+ }
if (!list.data) {
if (list.unauthorized) {
return <Fragment>
- <NotYetReadyAppMenu title="Login" onLogout={() => {
- clearAllTokens();
- route('/')
- }} />
- <LoginPage
- withMessage={{ message: i18n`Access denied`, description: i18n`Check
your token is valid`, type: 'ERROR', }}
- onConfirm={updateLoginStatus}
+ <NotYetReadyAppMenu title="Login"
+ onLogout={clearTokenAndGoToRoot}
+ />
+ <NotificationCard notification={{
+ message: i18n`Access denied`,
+ description: i18n`Check your token is valid`,
+ type: 'ERROR'
+ }}
/>
+ <LoginPage onConfirm={updateLoginStatus} />
</Fragment>
}
if (list.notfound) {
@@ -62,45 +68,43 @@ export function ApplicationReadyRoutes({ pushNotification
}: Props): VNode {
// query to /config is ok but the URL
// doest not match with our pattern
return <Fragment>
- <NotYetReadyAppMenu title="Error" onLogout={() => {
- clearAllTokens();
- route('/')
- }} />
- <LoginPage
- withMessage={{ message: i18n`Couldnt access the server`,
description: i18n`Could not infer instance id from url ${currentBaseUrl}`,
type: 'ERROR', }}
- onConfirm={updateLoginStatus}
+ <NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} />
+ <NotificationCard notification={{
+ message: i18n`Couldnt access the server`,
+ description: i18n`Could not infer instance id from url
${currentBaseUrl}`,
+ type: 'ERROR',
+ }}
/>
+ <LoginPage onConfirm={updateLoginStatus} />
</Fragment>
}
return <Fragment>
- <Menu instance={match[1]} onLogout={() => {
- clearAllTokens();
- route('/')
+ <Menu instance={match[1]} onLogout={clearTokenAndGoToRoot} />
+ <InstanceRoutes id={match[1]} />
+ </Fragment>
+ }
+
+ if (list.error) {
+ return <Fragment>
+ <NotYetReadyAppMenu title="Error" />
+ <NotificationCard notification={{
+ message: i18n`Couldnt access the server`,
+ description: list.error.message,
+ type: 'ERROR'
}} />
- <InstanceRoutes id={match[1]} pushNotification={pushNotification} />
+ <LoginPage onConfirm={updateLoginStatus} />
</Fragment>
- }
+ }
- if (list.error) {
- return <Fragment>
- <NotYetReadyAppMenu title="Error" />
- <LoginPage
- withMessage={{ message: i18n`Couldnt access the server`, description:
list.error.message, type: 'ERROR', }}
- onConfirm={updateLoginStatus}
- />
- </Fragment>
+ // is loading
+ return <NotYetReadyAppMenu title="Loading..." />
}
- // is loading
- return <NotYetReadyAppMenu title="Loading..." />
-}
-
-return <Fragment>
- <Menu instance="default" admin onLogout={() => {
- clearAllTokens();
- route('/')
- }} />
- <InstanceRoutes admin id="default" pushNotification={pushNotification} />
-</Fragment>
+ return <Fragment>
+ <Menu instance="default" admin
+ onLogout={clearTokenAndGoToRoot}
+ />
+ <InstanceRoutes admin id="default" />
+ </Fragment>
}
diff --git a/packages/frontend/src/InstanceRoutes.tsx
b/packages/frontend/src/InstanceRoutes.tsx
index aec17d6..1804ca7 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -26,11 +26,11 @@ import { useMessageTemplate } from 'preact-messages';
import { createHashHistory } from 'history';
import { useBackendInstanceToken } from './hooks';
import { InstanceContextProvider, useBackendContext } from './context/backend';
-import { SwrError } from "./hooks/backend";
-import { Notification } from './utils/types';
+import { SwrError, useInstanceDetails } from "./hooks/backend";
+// import { Notification } from './utils/types';
import LoginPage from './paths/login';
-import InstanceUpdatePage from "./paths/instance/update";
+import InstanceUpdatePage, { Props as InstanceUpdatePageProps } from
"./paths/instance/update";
import DetailPage from './paths/instance/details';
import NotFoundPage from './paths/notfound';
@@ -51,6 +51,8 @@ import TransferCreatePage from
'./paths/instance/transfers/create'
import InstanceListPage from './paths/admin/list';
import InstanceCreatePage from "./paths/admin/create";
+import { NotificationCard } from './components/menu';
+import { Loading } from './components/exception/loading';
export enum InstancePaths {
details = '/',
@@ -75,18 +77,45 @@ export enum InstancePaths {
export enum AdminPaths {
list_instances = '/instances',
new_instance = '/instance/new',
- instance_id_route = '/instance/:id/:rest*',
+ update_instance = '/instance/:id/update',
}
export interface Props {
id: string;
- pushNotification: (n: Notification) => void;
admin?: boolean;
}
-// const rootPath = typeof window !== 'undefined' ? window.location.pathname :
'/'
+function AdminInstanceUpdatePage({ id, ...rest }: { id: string } &
InstanceUpdatePageProps) {
+ const [token, updateToken] = useBackendInstanceToken(id);
+ const value = useMemo(() => ({ id, token, admin: true }), [id, token])
+ const { changeBackend } = useBackendContext();
+ const updateLoginStatus = (url: string, token?: string) => {
+ changeBackend(url);
+ if (token)
+ updateToken(token);
+ };
+ const i18n = useMessageTemplate('');
+ return <InstanceContextProvider value={value}>
+ <InstanceUpdatePage {...rest}
+ onLoadError={(error: SwrError) => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Problem reaching the
server`, description: i18n`Got message: ${error.message} from: ${error.backend}
(hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
+ onUnauthorized={() => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Access denied`,
description: i18n`Check your token is valid`, type: 'ERROR', }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
+ />
+ </InstanceContextProvider>
+}
-export function InstanceRoutes({ id, pushNotification, admin }: Props): VNode {
+export function InstanceRoutes({ id, admin }: Props): VNode {
const [token, updateToken] = useBackendInstanceToken(id);
const { changeBackend, addTokenCleaner } = useBackendContext();
const cleaner = useCallback(() => { updateToken(undefined); }, [id]);
@@ -117,14 +146,20 @@ export function InstanceRoutes({ id, pushNotification,
admin }: Props): VNode {
route(`/instance/${id}/update`);
}}
- onUnauthorized={() => <LoginPage
- withMessage={{ message: i18n`Access denied`, description:
i18n`Check your token is valid`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
-
- onLoadError={(error: SwrError) => <LoginPage
- withMessage={{ message: i18n`Problem reaching the server`,
description: i18n`Got message: ${error.message} from: ${error.backend}
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
-
+ onUnauthorized={() => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Access denied`,
description: i18n`Check your token is valid`, type: 'ERROR' }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
+ onLoadError={(error: SwrError) => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Problem reaching
the server`, description: i18n`Got message: ${error.message} from:
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
/>
}
@@ -134,12 +169,29 @@ export function InstanceRoutes({ id, pushNotification,
admin }: Props): VNode {
onBack={() => route(AdminPaths.list_instances)}
onConfirm={() => {
- pushNotification({ message: i18n`create_success`, type: 'SUCCESS'
});
+ // pushNotification({ message: i18n`create_success`, type:
'SUCCESS' });
route(AdminPaths.list_instances);
}}
onError={(error: any) => {
- pushNotification({ message: i18n`create_error`, type: 'ERROR' });
+ // pushNotification({ message: i18n`create_error`, type: 'ERROR'
});
+ }}
+
+ />
+ }
+
+ {admin &&
+ <Route path={AdminPaths.update_instance}
component={AdminInstanceUpdatePage}
+
+ onBack={() => route(AdminPaths.list_instances)}
+
+ onConfirm={() => {
+ // pushNotification({ message: i18n`create_success`, type:
'SUCCESS' });
+ route(AdminPaths.list_instances);
+ }}
+
+ onUpdateError={(e: Error) => {
+ // pushNotification({ message: i18n`update_error`, type: 'ERROR'
});
}}
/>
@@ -148,49 +200,110 @@ export function InstanceRoutes({ id, pushNotification,
admin }: Props): VNode {
<Route path={InstancePaths.details}
component={DetailPage}
- onUnauthorized={() => <LoginPage
- withMessage={{ message: i18n`Access denied`, description: i18n`Check
your token is valid`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
+ onUnauthorized={() => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Access denied`,
description: i18n`Check your token is valid`, type: 'ERROR', }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
+ onNotFound={() => {
+ if (admin) {
+ return <Fragment>
+ <NotificationCard notification={{
+ message: 'No default instance',
+ description: 'in order to use merchant backend, you should
create the default instance',
+ type: 'INFO'
+ }} />
+ <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ </Fragment>
+ }
+ return <NotFoundPage />
+ }}
- onLoadError={(error: SwrError) => <LoginPage
- withMessage={{ message: i18n`Problem reaching the server`,
description: i18n`Got message: ${error.message} from: ${error.backend}
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
+ onLoadError={(error: SwrError) => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Problem reaching
the server`, description: i18n`Got message: ${error.message} from:
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
/>
<Route path={InstancePaths.update}
component={InstanceUpdatePage}
- onUnauthorized={() => <LoginPage
- withMessage={{ message: i18n`Access denied`, description: i18n`Check
your token is valid`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
+ onUnauthorized={() => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Access denied`,
description: i18n`Check your token is valid`, type: 'ERROR', }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
- onLoadError={(error: SwrError) => <LoginPage
- withMessage={{ message: i18n`Problem reaching the server`,
description: i18n`Got message: ${error.message} from: ${error.backend}
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
+ onLoadError={(error: SwrError) => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Problem reaching
the server`, description: i18n`Got message: ${error.message} from:
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
+ onNotFound={() => {
+ if (admin) {
+ return <Fragment>
+ <NotificationCard notification={{
+ message: 'No default instance',
+ description: 'in order to use merchant backend, you should
create the default instance',
+ type: 'INFO'
+ }} />
+ <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ </Fragment>
+ }
+ return <NotFoundPage />
+ }}
onBack={() => {
route(`/`);
}}
onConfirm={() => {
- pushNotification({ message: i18n`create_success`, type: 'SUCCESS' });
+ // pushNotification({ message: i18n`create_success`, type: 'SUCCESS'
});
route(`/`);
}}
onUpdateError={(e: Error) => {
- pushNotification({ message: i18n`update_error`, type: 'ERROR' });
+ // pushNotification({ message: i18n`update_error`, type: 'ERROR' });
}}
/>
<Route path={InstancePaths.product_list}
component={ProductListPage}
- onUnauthorized={() => <LoginPage
- withMessage={{ message: i18n`Access denied`, description: i18n`Check
your token is valid`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
- onLoadError={(error: SwrError) => <LoginPage
- withMessage={{ message: i18n`Problem reaching the server`,
description: i18n`Got message: ${error.message} from: ${error.backend}
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
+ onUnauthorized={() => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Access denied`,
description: i18n`Check your token is valid`, type: 'ERROR', }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
+ onNotFound={() => {
+ if (admin) {
+ return <Fragment>
+ <NotificationCard notification={{
+ message: 'No default instance',
+ description: 'in order to use merchant backend, you should
create the default instance',
+ type: 'INFO'
+ }} />
+ <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ </Fragment>
+ }
+ return <NotFoundPage />
+ }}
+
+ onLoadError={(error: SwrError) => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Problem reaching
the server`, description: i18n`Got message: ${error.message} from:
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
/>
<Route path={InstancePaths.product_update}
component={ProductUpdatePage}
@@ -201,13 +314,34 @@ export function InstanceRoutes({ id, pushNotification,
admin }: Props): VNode {
<Route path={InstancePaths.order_list}
component={OrderListPage}
- onUnauthorized={() => <LoginPage
- withMessage={{ message: i18n`Access denied`, description: i18n`Check
your token is valid`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
- onLoadError={(error: SwrError) => <LoginPage
- withMessage={{ message: i18n`Problem reaching the server`,
description: i18n`Got message: ${error.message} from: ${error.backend}
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
+ onUnauthorized={() => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Access denied`,
description: i18n`Check your token is valid`, type: 'ERROR', }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
+ onNotFound={() => {
+ if (admin) {
+ return <Fragment>
+ <NotificationCard notification={{
+ message: 'No default instance',
+ description: 'in order to use merchant backend, you should
create the default instance',
+ type: 'INFO'
+ }} />
+ <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ </Fragment>
+ }
+ return <NotFoundPage />
+ }}
+
+ onLoadError={(error: SwrError) => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Problem reaching
the server`, description: i18n`Got message: ${error.message} from:
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
/>
<Route path={InstancePaths.order_update}
component={OrderUpdatePage}
@@ -218,13 +352,34 @@ export function InstanceRoutes({ id, pushNotification,
admin }: Props): VNode {
<Route path={InstancePaths.tips_list}
component={TipListPage}
- onUnauthorized={() => <LoginPage
- withMessage={{ message: i18n`Access denied`, description: i18n`Check
your token is valid`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
- onLoadError={(error: SwrError) => <LoginPage
- withMessage={{ message: i18n`Problem reaching the server`,
description: i18n`Got message: ${error.message} from: ${error.backend}
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
+ onUnauthorized={() => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Access denied`,
description: i18n`Check your token is valid`, type: 'ERROR', }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
+ onNotFound={() => {
+ if (admin) {
+ return <Fragment>
+ <NotificationCard notification={{
+ message: 'No default instance',
+ description: 'in order to use merchant backend, you should
create the default instance',
+ type: 'INFO'
+ }} />
+ <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ </Fragment>
+ }
+ return <NotFoundPage />
+ }}
+
+ onLoadError={(error: SwrError) => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Problem reaching
the server`, description: i18n`Got message: ${error.message} from:
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
/>
<Route path={InstancePaths.tips_update}
component={TipUpdatePage}
@@ -235,19 +390,42 @@ export function InstanceRoutes({ id, pushNotification,
admin }: Props): VNode {
<Route path={InstancePaths.transfers_list}
component={TransferListPage}
- onUnauthorized={() => <LoginPage
- withMessage={{ message: i18n`Access denied`, description: i18n`Check
your token is valid`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
- onLoadError={(error: SwrError) => <LoginPage
- withMessage={{ message: i18n`Problem reaching the server`,
description: i18n`Got message: ${error.message} from: ${error.backend}
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
- onConfirm={updateLoginStatus} />}
+ onUnauthorized={() => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Access denied`,
description: i18n`Check your token is valid`, type: 'ERROR', }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
+
+ onNotFound={() => {
+ if (admin) {
+ return <Fragment>
+ <NotificationCard notification={{
+ message: 'No default instance',
+ description: 'in order to use merchant backend, you should
create the default instance',
+ type: 'INFO'
+ }} />
+ <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ </Fragment>
+ }
+ return <NotFoundPage />
+ }}
+
+ onLoadError={(error: SwrError) => {
+ return <Fragment>
+ <NotificationCard notification={{ message: i18n`Problem reaching
the server`, description: i18n`Got message: ${error.message} from:
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+ <LoginPage onConfirm={updateLoginStatus} />
+ </Fragment>
+ }}
/>
<Route path={InstancePaths.transfers_new}
component={TransferCreatePage}
/>
- <Route default component={NotFoundPage} />
+ {/* example of loading page*/}
+ <Route path="/loading" component={Loading} />
+ <Route default component={NotFoundPage} />
</Router>
</InstanceContextProvider>;
diff --git a/packages/frontend/src/components/exception/loading.tsx
b/packages/frontend/src/components/exception/loading.tsx
index 0cd56cb..aa73453 100644
--- a/packages/frontend/src/components/exception/loading.tsx
+++ b/packages/frontend/src/components/exception/loading.tsx
@@ -1,5 +1,9 @@
import { h, VNode } from "preact";
-export function Loading():VNode {
- return <div>loading...</div>
+export function Loading(): VNode {
+ return <div class="columns is-centered is-vcentered"
style={{height:'calc(100% - 3rem)',position:'absolute', width:'100%'}}>
+ <div class="column is-one-fifth">
+ <div class="lds-ring"><div></div><div></div><div></div><div></div></div>
+ </div>
+ </div>
}
\ No newline at end of file
diff --git a/packages/frontend/src/components/exception/login.tsx
b/packages/frontend/src/components/exception/login.tsx
index 10854b1..fde6698 100644
--- a/packages/frontend/src/components/exception/login.tsx
+++ b/packages/frontend/src/components/exception/login.tsx
@@ -21,7 +21,7 @@
import { h, VNode } from "preact";
import { useMessageTemplate } from "preact-messages";
-import { useContext, useState } from "preact/hooks";
+import { useState } from "preact/hooks";
import { useBackendContext } from "../../context/backend";
import { Notification } from "../../utils/types";
@@ -39,16 +39,6 @@ export function LoginModal({ onConfirm, withMessage }:
Props): VNode {
return <div class="columns is-centered">
<div class="column is-two-thirds " >
<div class="modal-card" style={{ width: '100%', margin: 0 }}>
- {withMessage && <div class={withMessage.type === 'ERROR' ?
"notification is-danger" : "notification is-info"}>
- <div class="columns is-vcentered">
- <div class="column is-12">
- <div>
- <p><strong>{withMessage.message}</strong></p>
- {withMessage.description}
- </div>
- </div>
- </div>
- </div>}
<header class="modal-card-head" style={{ border: '1px solid',
borderBottom: 0 }}>
<p class="modal-card-title">{i18n`Login required`}</p>
</header>
diff --git a/packages/frontend/src/components/menu/index.tsx
b/packages/frontend/src/components/menu/index.tsx
index 7754177..5fa1494 100644
--- a/packages/frontend/src/components/menu/index.tsx
+++ b/packages/frontend/src/components/menu/index.tsx
@@ -14,91 +14,115 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
- import { ComponentChildren, Fragment, h, VNode } from "preact";
- import { useEffect, useState } from "preact/hooks";
- import { AdminPaths } from "../../AdminRoutes";
- import { InstancePaths } from "../../InstanceRoutes";
- import { NavigationBar } from "./NavigationBar";
- import { Sidebar } from "./SideBar";
- import Match from 'preact-router/match';
-
-
- function getInstanceTitle(path: string, id: string): string {
-
- switch (path) {
- case InstancePaths.details: return `${id}`
- case InstancePaths.update: return `${id}: Settings`
- case InstancePaths.order_list: return `${id}: Orders`
- case InstancePaths.order_new: return `${id}: New order`
- case InstancePaths.order_update: return `${id}: Update order`
- case InstancePaths.product_list: return `${id}: Products`
- case InstancePaths.product_new: return `${id}: New product`
- case InstancePaths.product_update: return `${id}: Update product`
- case InstancePaths.tips_list: return `${id}: Tips`
- case InstancePaths.tips_new: return `${id}: New tip`
- case InstancePaths.tips_update: return `${id}: Update tip`
- case InstancePaths.transfers_list: return `${id}: Transfers`
- case InstancePaths.transfers_new: return `${id}: New Transfer`
- default: return '';
- }
- }
-
- const INSTANCE_ID_LOOKUP = /^\/instance\/([^/]*)\//
- function getAdminTitle(path: string) {
- if (path === AdminPaths.new_instance) return `New instance`
- if (path === AdminPaths.list_instances) return `Instances`
- const match = INSTANCE_ID_LOOKUP.exec(path)
- if (match && match[1]) return
getInstanceTitle(path.replace(INSTANCE_ID_LOOKUP, '/'), match[1]);
- return getInstanceTitle(path, 'default')
- }
-
- interface MenuProps {
- title?: string;
- instance: string;
- admin?: boolean;
- onLogout?: () => void;
- }
-
-function WithTitle({title,children}:{title:string,
children:ComponentChildren}):VNode {
+import { ComponentChildren, Fragment, h, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import { AdminPaths } from "../../AdminRoutes";
+import { InstancePaths } from "../../InstanceRoutes";
+import { NavigationBar } from "./NavigationBar";
+import { Sidebar } from "./SideBar";
+import Match from 'preact-router/match';
+import { Notification } from "../../utils/types";
+
+
+function getInstanceTitle(path: string, id: string): string {
+
+ switch (path) {
+ case InstancePaths.details: return `${id}`
+ case InstancePaths.update: return `${id}: Settings`
+ case InstancePaths.order_list: return `${id}: Orders`
+ case InstancePaths.order_new: return `${id}: New order`
+ case InstancePaths.order_update: return `${id}: Update order`
+ case InstancePaths.product_list: return `${id}: Products`
+ case InstancePaths.product_new: return `${id}: New product`
+ case InstancePaths.product_update: return `${id}: Update product`
+ case InstancePaths.tips_list: return `${id}: Tips`
+ case InstancePaths.tips_new: return `${id}: New tip`
+ case InstancePaths.tips_update: return `${id}: Update tip`
+ case InstancePaths.transfers_list: return `${id}: Transfers`
+ case InstancePaths.transfers_new: return `${id}: New Transfer`
+ default: return '';
+ }
+}
+
+const INSTANCE_ID_LOOKUP = /^\/instance\/([^/]*)\//
+function getAdminTitle(path: string) {
+ if (path === AdminPaths.new_instance) return `New instance`
+ if (path === AdminPaths.list_instances) return `Instances`
+ const match = INSTANCE_ID_LOOKUP.exec(path)
+ if (match && match[1]) return
getInstanceTitle(path.replace(INSTANCE_ID_LOOKUP, '/'), match[1]);
+ return getInstanceTitle(path, 'default')
+}
+
+interface MenuProps {
+ title?: string;
+ instance: string;
+ admin?: boolean;
+ onLogout?: () => void;
+}
+
+function WithTitle({ title, children }: { title: string, children:
ComponentChildren }): VNode {
useEffect(() => {
document.title = `Taler Backoffice: ${title}`
}, [title])
return <Fragment>{children}</Fragment>
}
-
- export function Menu({ onLogout, title, instance, admin }: MenuProps): VNode {
- const [mobileOpen, setMobileOpen] = useState(false)
-
- return <Match>{({ path }: any) => {
- const titleWithSubtitle = title ? title : (!admin ?
getInstanceTitle(path, instance) : getAdminTitle(path))
-
- return (<WithTitle title={titleWithSubtitle}>
- <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={()
=> setMobileOpen(false)}>
- <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)}
title={titleWithSubtitle} />
- {onLogout && <Sidebar onLogout={onLogout} admin={admin}
instance={instance} mobile={mobileOpen} />}
- </div>
- </WithTitle>
- )
- }}</Match>
-
- }
-
- interface NotYetReadyAppMenuProps {
- title: string;
- onLogout?: () => void;
- }
-
- export function NotYetReadyAppMenu({ onLogout, title }:
NotYetReadyAppMenuProps): VNode {
- const [mobileOpen, setMobileOpen] = useState(false)
-
- useEffect(() => {
- document.title = `Taler Backoffice: ${title}`
- }, [title])
-
- return <div class={mobileOpen ? "has-aside-mobile-expanded" : ""}
onClick={() => setMobileOpen(false)}>
- <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)}
title={title} />
- {onLogout && <Sidebar onLogout={onLogout} instance="" mobile={mobileOpen}
/>}
- </div>
-
- }
-
+
+export function Menu({ onLogout, title, instance, admin }: MenuProps): VNode {
+ const [mobileOpen, setMobileOpen] = useState(false)
+
+ return <Match>{({ path }: any) => {
+ const titleWithSubtitle = title ? title : (!admin ? getInstanceTitle(path,
instance) : getAdminTitle(path))
+
+ return (<WithTitle title={titleWithSubtitle}>
+ <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={()
=> setMobileOpen(false)}>
+ <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)}
title={titleWithSubtitle} />
+ {onLogout && <Sidebar onLogout={onLogout} admin={admin}
instance={instance} mobile={mobileOpen} />}
+ </div>
+ </WithTitle>
+ )
+ }}</Match>
+
+}
+
+interface NotYetReadyAppMenuProps {
+ title: string;
+ onLogout?: () => void;
+}
+
+interface NotifProps {
+ notification?: Notification;
+}
+export function NotificationCard({ notification:n }: NotifProps) {
+ // const [n, setNotif] = useState(notification)
+ if (!n) return null
+ return <div class="notification">
+ <div class="columns is-vcentered">
+ <div class="column is-12">
+ <article class={n.type === 'ERROR' ? "message is-danger" : "message
is-info"}>
+ <div class="message-header">
+ <p>{n.message}</p>
+ {/* {n.type !== 'ERROR' && <button class="delete"
aria-label="delete" onClick={() => setNotif(undefined)}></button> } */}
+ </div>
+ <div class="message-body">
+ {n.description}
+ </div>
+ </article>
+ </div>
+ </div>
+ </div>
+}
+
+export function NotYetReadyAppMenu({ onLogout, title }:
NotYetReadyAppMenuProps): VNode {
+ const [mobileOpen, setMobileOpen] = useState(false)
+
+ useEffect(() => {
+ document.title = `Taler Backoffice: ${title}`
+ }, [title])
+
+ return <div class={mobileOpen ? "has-aside-mobile-expanded" : ""}
onClick={() => setMobileOpen(false)}>
+ <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)}
title={title} />
+ {onLogout && <Sidebar onLogout={onLogout} instance="" mobile={mobileOpen}
/>}
+ </div>
+
+}
+
diff --git a/packages/frontend/src/hooks/backend.ts
b/packages/frontend/src/hooks/backend.ts
index 20e056a..a9160c9 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -23,6 +23,7 @@ import useSWR, { mutate, cache } from 'swr';
import axios from 'axios'
import { MerchantBackend } from '../declaration';
import { useBackendContext, useInstanceContext } from '../context/backend';
+import { useEffect, useState } from 'preact/hooks';
function mutateAll(re: RegExp) {
cache.keys().filter(key => re.test(key)).forEach(key => mutate(key, null))
@@ -32,6 +33,8 @@ type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError;
interface HttpResponseOk<T> {
data: T;
+ unauthorized: boolean;
+ notfound: boolean;
}
export interface SwrError {
@@ -107,6 +110,7 @@ interface AdminMutateAPI {
createInstance: (data:
MerchantBackend.Instances.InstanceConfigurationMessage) => Promise<void>;
deleteInstance: (id: string) => Promise<void>;
}
+
export function useAdminMutateAPI(): AdminMutateAPI {
const { url, token } = useBackendContext()
@@ -121,7 +125,7 @@ export function useAdminMutateAPI(): AdminMutateAPI {
}
const deleteInstance = async (id: string): Promise<void> => {
- await request(`${url}/private/`, {
+ await request(`${url}/private/instances/${id}`, {
method: 'delete',
token,
})
@@ -395,6 +399,26 @@ export function useInstanceMutateAPI(): InstaceMutateAPI {
return { updateInstance, deleteInstance, setNewToken, clearToken }
}
+export function useBackendInstancesTestForAdmin():
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
+ const { url, token } = useBackendContext()
+ interface Result {
+ data?: MerchantBackend.Instances.InstancesResponse;
+ error?: SwrError;
+ }
+ const [result, setResult] = useState<Result|undefined>(undefined)
+
+ useEffect(() => {
+ request(`${url}/private/instances`, { token })
+ .then(data => setResult({data}))
+ .catch(error => setResult({error}))
+ },[url, token])
+
+ const data = result?.data
+ const error = result?.error
+
+ return { data, unauthorized: error?.status === 401, notfound: error?.status
=== 404, error }
+}
+
export function useBackendInstances():
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
const { url, token } = useBackendContext()
const { data, error } = useSWR<MerchantBackend.Instances.InstancesResponse,
SwrError>(['/private/instances', token, url], fetcher)
diff --git a/packages/frontend/src/paths/login/index.tsx
b/packages/frontend/src/hooks/notification.ts
similarity index 54%
copy from packages/frontend/src/paths/login/index.tsx
copy to packages/frontend/src/hooks/notification.ts
index 91dc94c..d1dfbff 100644
--- a/packages/frontend/src/paths/login/index.tsx
+++ b/packages/frontend/src/hooks/notification.ts
@@ -14,18 +14,30 @@
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 { LoginModal } from '../../components/exception/login';
-import { Notification } from "../../utils/types";
-interface Props {
- withMessage?: Notification;
- onConfirm: (url: string, token?: string) => void;
+import { useCallback, useState } from "preact/hooks";
+import { Notification } from '../utils/types';
+
+interface Result {
+ notification?: Notification;
+ pushNotification: (n: Notification) => void;
+ removeNotification: () => void;
+}
+
+export function useNotification(): Result {
+ const [notification, setNotifications] =
useState<Notification|undefined>(undefined)
+
+ const pushNotification = useCallback((n: Notification): void => {
+ setNotifications(n)
+ },[])
+
+ const removeNotification = useCallback(() => {
+ setNotifications(undefined)
+ },[])
+
+ return { notification, pushNotification, removeNotification }
}
-export default function LoginPage({ onConfirm, withMessage }: Props): VNode {
- return <LoginModal onConfirm={onConfirm} withMessage={withMessage} />
-}
\ No newline at end of file
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index 90625c7..57e2cc2 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -22,21 +22,19 @@
import "./scss/main.scss"
import { h, VNode } from 'preact';
-import { useCallback, useContext, useEffect, useMemo, useState } from
"preact/hooks";
-import { Route, route } from 'preact-router';
+import { useEffect, useMemo } from "preact/hooks";
+import { route } from 'preact-router';
import { MessageProvider, useMessageTemplate } from 'preact-messages';
-import { Notifications } from './components/notifications';
import * as messages from './messages'
import { useBackendContextState } from './hooks';
-import { useNotifications } from "./hooks/notifications";
import { BackendContextProvider, ConfigContextProvider, useBackendContext }
from './context/backend';
import { useBackendConfig } from "./hooks/backend";
import { hasKey, onTranslationError } from "./utils/functions";
import LoginPage from './paths/login';
import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes";
-import { NotYetReadyAppMenu } from "./components/menu";
+import { NotificationCard, NotYetReadyAppMenu } from "./components/menu";
export function Redirect({ to }: { to: string }): null {
useEffect(() => {
@@ -58,7 +56,6 @@ export default function Application(): VNode {
}
function ApplicationStatusRoutes(): VNode {
- const { notifications, pushNotification, removeNotification } =
useNotifications()
const { changeBackend, triedToLog, updateToken, resetBackend } =
useBackendContext()
const backendConfig = useBackendConfig();
const i18n = useMessageTemplate()
@@ -66,16 +63,16 @@ function ApplicationStatusRoutes(): VNode {
const v = `${backendConfig.data?.currency} ${backendConfig.data?.version}`
const ctx = useMemo(() => ({ currency: backendConfig.data?.currency || '',
version: backendConfig.data?.version || '' }), [v])
+ const updateLoginInfoAndGoToRoot = (url: string, token?: string) => {
+ changeBackend(url)
+ if (token) updateToken(token)
+ route('/')
+ }
+
if (!triedToLog) {
return <div id="app">
<NotYetReadyAppMenu title="Welcome!" />
- <LoginPage
- onConfirm={(url: string, token?: string) => {
- changeBackend(url)
- if (token) updateToken(token)
- route('/')
- }}
- />
+ <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
</div>
}
@@ -86,37 +83,24 @@ function ApplicationStatusRoutes(): VNode {
if (backendConfig.unauthorized) {
return <div id="app">
<NotYetReadyAppMenu title="Login" />
- <LoginPage
- onConfirm={(url: string, token?: string) => {
- changeBackend(url)
- if (token) updateToken(token)
- route('/')
- }}
- />
+ <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
</div>
}
return <div id="app">
<NotYetReadyAppMenu title="Error" />
- <LoginPage
- withMessage={{
- message: i18n`Couldnt access the server`,
- type: 'ERROR',
- description: i18n`Got message: ${backendConfig.error.message} from:
${backendConfig.error.backend} (hasToken: ${backendConfig.error.hasToken})`,
- }}
- onConfirm={(url: string, token?: string) => {
- changeBackend(url)
- if (token) updateToken(token)
- route('/')
- }}
- />
+ <NotificationCard notification={{
+ message: i18n`Couldnt access the server`,
+ type: 'ERROR',
+ description: i18n`Got message: ${backendConfig.error.message} from:
${backendConfig.error.backend} (hasToken: ${backendConfig.error.hasToken})`,
+ }} />
+ <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
</div>
}
return <div id="app" class="has-navbar-fixed-top">
<ConfigContextProvider value={ctx}>
- <Notifications notifications={notifications}
removeNotification={removeNotification} />
- <Route default component={ApplicationReadyRoutes}
pushNotification={pushNotification} />
+ <ApplicationReadyRoutes />
</ConfigContextProvider>
</div>
}
diff --git a/packages/frontend/src/messages/en.po
b/packages/frontend/src/messages/en.po
index c22199a..49ecd68 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -276,4 +276,10 @@ msgstr ""
# msgstr ""
msgid "fields.instance.wired.label"
-msgstr "Wired"
\ No newline at end of file
+msgstr "Wired"
+
+msgid "create_success"
+msgstr "Creation succeed"
+
+msgid "create_error"
+msgstr "Creation failed"
diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx
b/packages/frontend/src/paths/admin/create/CreatePage.tsx
index ae76796..a286ba4 100644
--- a/packages/frontend/src/paths/admin/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx
@@ -40,7 +40,7 @@ type Entity =
MerchantBackend.Instances.InstanceConfigurationMessage & {auth_tok
interface Props {
onCreate: (d: Entity) => void;
isLoading: boolean;
- onBack: () => void;
+ onBack?: () => void;
}
interface KeyValue {
@@ -114,7 +114,7 @@ export function CreatePage({ onCreate, isLoading, onBack }:
Props): VNode {
</FormProvider>
<div class="buttons is-right mt-5">
- <button class="button" onClick={onBack} ><Message id="Cancel"
/></button>
+ { onBack && <button class="button" onClick={onBack} ><Message
id="Cancel" /></button> }
<button class="button is-success" onClick={submit} ><Message
id="Confirm" /></button>
</div>
diff --git a/packages/frontend/src/paths/admin/create/index.tsx
b/packages/frontend/src/paths/admin/create/index.tsx
index f5591dd..7aeba5f 100644
--- a/packages/frontend/src/paths/admin/create/index.tsx
+++ b/packages/frontend/src/paths/admin/create/index.tsx
@@ -13,24 +13,112 @@
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 { useState } from "preact/hooks";
+import { Loading } from "../../../components/exception/loading";
+import { FormProvider } from "../../../components/form/Field";
+import { Input } from "../../../components/form/Input";
+import { NotificationCard } from "../../../components/menu";
import { MerchantBackend } from "../../../declaration";
import { useAdminMutateAPI } from "../../../hooks/backend";
+import { Notification } from "../../../utils/types";
import { CreatePage } from "./CreatePage";
interface Props {
- onBack: () => void;
+ onBack?: () => void;
onConfirm: () => void;
onError: (error: any) => void;
}
+type Entity = MerchantBackend.Instances.InstanceConfigurationMessage;
export default function Create({ onBack, onConfirm, onError }: Props): VNode {
const { createInstance } = useAdminMutateAPI();
+ const [notif, setNotif] = useState<Notification | undefined>(undefined)
+ const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined);
- return <CreatePage
- onBack={onBack}
- isLoading={false}
- onCreate={(d: MerchantBackend.Instances.InstanceConfigurationMessage):
Promise<void> => {
- return createInstance(d).then(onConfirm).catch(onError)
- }} />
+ if (createdOk) {
+ return <div class="columns is-fullwidth is-vcentered content-full-size">
+ <div class="column" />
+ <div class="column is-half">
+ <div class="card">
+ <header class="card-header has-background-success">
+ <p class="card-header-title has-text-white-ter">
+ Instance created successfully
+ </p>
+ </header>
+ <div class="card-content">
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">ID</label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input class="input" readonly value={createdOk.id}
disabled />
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">Business Name</label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input class="input" readonly value={createdOk.name}
disabled />
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">Token</label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input class="input" readonly value={createdOk.auth.token}
disabled />
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ <footer class="card-footer">
+ <p class="card-footer-item" style={{ border: 'none' }}>
+ <span>
+ </span>
+ </p>
+ <p class="card-footer-item" style={{ border: 'none' }}>
+ <span>
+ </span>
+ </p>
+ <p class="card-footer-item">
+ <button class="button is-info"
onClick={onConfirm}>Continue</button>
+ </p>
+ </footer>
+ </div>
+ </div>
+ <div class="column" />
+ </div>
+ }
+
+ return <Fragment>
+ <NotificationCard notification={notif} />
+
+ <CreatePage
+ onBack={onBack}
+ isLoading={false}
+ onCreate={(d: MerchantBackend.Instances.InstanceConfigurationMessage) =>
{
+ createInstance(d).then((r) => {
+ setCreatedOk(d)
+ }).catch((error) => {
+ setNotif({
+ message: 'could not create instance',
+ type: "ERROR",
+ description: error.message
+ })
+ })
+ }} />
+ </Fragment>
}
\ No newline at end of file
diff --git a/packages/frontend/src/paths/admin/list/index.tsx
b/packages/frontend/src/paths/admin/list/index.tsx
index 0fe63ab..a96c5b7 100644
--- a/packages/frontend/src/paths/admin/list/index.tsx
+++ b/packages/frontend/src/paths/admin/list/index.tsx
@@ -26,9 +26,10 @@ import { useState } from 'preact/hooks';
import { MerchantBackend } from '../../../declaration';
import { Notification } from '../../../utils/types';
import { DeleteModal } from '../../../components/modal';
+import { Loading } from '../../../components/exception/loading';
interface Props {
- pushNotification: (n: Notification) => void;
+ // pushNotification: (n: Notification) => void;
onCreate: () => void;
onUpdate: (id: string) => void;
instances: MerchantBackend.Instances.Instance[];
@@ -36,17 +37,15 @@ interface Props {
onLoadError: (e: SwrError) => VNode;
}
-export default function Instances({ pushNotification, onUnauthorized,
onLoadError, onCreate, onUpdate }: Props): VNode {
+export default function Instances({ onUnauthorized, onLoadError, onCreate,
onUpdate }: Props): VNode {
const result = useBackendInstances()
const [deleting, setDeleting] = useState<MerchantBackend.Instances.Instance
| null>(null)
const { deleteInstance } = useAdminMutateAPI()
+ if (result.unauthorized) return onUnauthorized()
if (!result.data) {
- if (result.unauthorized) return onUnauthorized()
if (result.error) return onLoadError(result.error)
- return <div>
- loading ....
- </div>
+ return <Loading />
}
return <Fragment>
@@ -63,9 +62,9 @@ export default function Instances({ pushNotification,
onUnauthorized, onLoadErro
onConfirm={async (): Promise<void> => {
try {
await deleteInstance(deleting.id)
- pushNotification({ message: 'delete_success', type: 'SUCCESS' })
+ // pushNotification({ message: 'delete_success', type: 'SUCCESS' })
} catch (e) {
- pushNotification({ message: 'delete_error', type: 'ERROR' })
+ // pushNotification({ message: 'delete_error', type: 'ERROR' })
}
setDeleting(null)
}}
diff --git a/packages/frontend/src/paths/instance/details/index.tsx
b/packages/frontend/src/paths/instance/details/index.tsx
index 492878c..5771cac 100644
--- a/packages/frontend/src/paths/instance/details/index.tsx
+++ b/packages/frontend/src/paths/instance/details/index.tsx
@@ -20,28 +20,28 @@ import { Notification } from "../../../utils/types";
import { useInstanceDetails, useInstanceMutateAPI, SwrError } from
"../../../hooks/backend";
import { DetailPage } from "./DetailPage";
import { DeleteModal } from "../../../components/modal";
+import { Loading } from "../../../components/exception/loading";
interface Props {
onUnauthorized: () => VNode;
onLoadError: (e: SwrError) => VNode;
onUpdate: () => void;
+ onNotFound: () => VNode;
onDelete: () => void;
- pushNotification: (n: Notification) => void;
}
-export default function Detail({ onUpdate, onLoadError, onUnauthorized,
pushNotification, onDelete }: Props): VNode {
+export default function Detail({ onUpdate, onLoadError, onUnauthorized,
onDelete, onNotFound }: Props): VNode {
const { id } = useInstanceContext()
const result = useInstanceDetails()
const [deleting, setDeleting] = useState<boolean>(false)
const { deleteInstance } = useInstanceMutateAPI()
+ if (result.unauthorized) return onUnauthorized()
+ if (result.notfound) return onNotFound();
if (!result.data) {
- if (result.unauthorized) return onUnauthorized()
if (result.error) return onLoadError(result.error)
- return <div>
- loading ....
- </div>
+ return <Loading />
}
return <Fragment>
@@ -56,10 +56,10 @@ export default function Detail({ onUpdate, onLoadError,
onUnauthorized, pushNoti
onConfirm={async (): Promise<void> => {
try {
await deleteInstance()
- pushNotification({ message: 'delete_success', type: 'SUCCESS' })
+ // pushNotification({ message: 'delete_success', type: 'SUCCESS' })
onDelete()
} catch (error) {
- pushNotification({ message: 'delete_error', type: 'ERROR' })
+ // pushNotification({ message: 'delete_error', type: 'ERROR' })
}
setDeleting(false)
}}
diff --git a/packages/frontend/src/paths/instance/orders/list/index.tsx
b/packages/frontend/src/paths/instance/orders/list/index.tsx
index 13ea48e..c0dde74 100644
--- a/packages/frontend/src/paths/instance/orders/list/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/index.tsx
@@ -10,6 +10,7 @@ import { InputBoolean } from
"../../../../components/form/InputBoolean";
interface Props {
onUnauthorized: () => VNode;
onLoadError: (e: SwrError) => VNode;
+ onNotFound: () => VNode;
onCreate: () => void;
}
@@ -18,7 +19,7 @@ const fromBooleanToYesAndNo = {
toBoolean: (b: string) => b === 'yes' ? true : (b === 'no' ? false :
undefined)
}
-export default function ({ onUnauthorized, onLoadError, onCreate }: Props):
VNode {
+export default function ({ onUnauthorized, onLoadError, onCreate, onNotFound
}: Props): VNode {
const [filter, setFilter] = useState<InstanceOrderFilter>({})
const result = useInstanceOrders(filter)
const { createOrder, deleteOrder } = useOrderMutateAPI()
@@ -26,8 +27,9 @@ export default function ({ onUnauthorized, onLoadError,
onCreate }: Props): VNod
let instances: (MerchantBackend.Orders.OrderHistoryEntry & {id: string})[];
+ if (result.unauthorized) return onUnauthorized()
+ if (result.notfound) return onNotFound()
if (!result.data) {
- if (result.unauthorized) return onUnauthorized()
if (result.error) return onLoadError(result.error)
//if loading asume empty list
instances = []
diff --git a/packages/frontend/src/paths/instance/products/list/index.tsx
b/packages/frontend/src/paths/instance/products/list/index.tsx
index a7f271e..df6a973 100644
--- a/packages/frontend/src/paths/instance/products/list/index.tsx
+++ b/packages/frontend/src/paths/instance/products/list/index.tsx
@@ -5,21 +5,24 @@ import { CardTable } from './Table';
import logo from '../../../../assets/logo.jpeg';
import { useConfigContext } from '../../../../context/backend';
import { MerchantBackend } from '../../../../declaration';
+import { Loading } from '../../../../components/exception/loading';
interface Props {
onUnauthorized: () => VNode;
+ onNotFound: () => VNode;
onLoadError: (e: SwrError) => VNode;
}
-export default function ({ onUnauthorized, onLoadError }: Props): VNode {
+export default function ({ onUnauthorized, onLoadError, onNotFound }: Props):
VNode {
const result = useInstanceProducts()
const { createProduct, deleteProduct } = useProductMutateAPI()
const { currency } = useConfigContext()
+
+ if (result.unauthorized) return onUnauthorized()
+ if (result.notfound) return onNotFound()
+
if (!result.data) {
- if (result.unauthorized) return onUnauthorized()
if (result.error) return onLoadError(result.error)
- return <div>
- loading ....
- </div>
+ return <Loading />
}
return <section class="section is-main-section">
<CardTable instances={result.data.products.map(o => ({ ...o, id:
o.product_id }))}
diff --git a/packages/frontend/src/paths/instance/tips/list/index.tsx
b/packages/frontend/src/paths/instance/tips/list/index.tsx
index c68f7f9..c8a2dc7 100644
--- a/packages/frontend/src/paths/instance/tips/list/index.tsx
+++ b/packages/frontend/src/paths/instance/tips/list/index.tsx
@@ -1,4 +1,5 @@
import { h, VNode } from 'preact';
+import { Loading } from '../../../../components/exception/loading';
import { useConfigContext } from '../../../../context/backend';
import { MerchantBackend } from '../../../../declaration';
import { SwrError, useInstanceMutateAPI, useInstanceTips, useTipsMutateAPI }
from '../../../../hooks/backend';
@@ -7,18 +8,21 @@ import { CardTable } from './Table';
interface Props {
onUnauthorized: () => VNode;
onLoadError: (e: SwrError) => VNode;
+ onNotFound: () => VNode;
}
-export default function ({ onUnauthorized, onLoadError }: Props): VNode {
+export default function ({ onUnauthorized, onLoadError, onNotFound }: Props):
VNode {
const result = useInstanceTips()
const { createReserve, deleteReserve } = useTipsMutateAPI()
const { currency } = useConfigContext()
+
+ if (result.unauthorized) return onUnauthorized()
+ if (result.notfound) return onNotFound()
+
if (!result.data) {
- if (result.unauthorized) return onUnauthorized()
if (result.error) return onLoadError(result.error)
- return <div>
- loading ....
- </div>
+ return <Loading />
}
+
return <section class="section is-main-section">
<CardTable instances={result.data.reserves.filter(r => r.active).map(o =>
({ ...o, id: o.reserve_pub }))}
onCreate={() => createReserve({
diff --git a/packages/frontend/src/paths/instance/transfers/list/index.tsx
b/packages/frontend/src/paths/instance/transfers/list/index.tsx
index 488130c..bc0ffca 100644
--- a/packages/frontend/src/paths/instance/transfers/list/index.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/index.tsx
@@ -1,4 +1,5 @@
import { h, VNode } from 'preact';
+import { Loading } from '../../../../components/exception/loading';
import { useConfigContext } from '../../../../context/backend';
import { SwrError, useInstanceTransfers, useTransferMutateAPI } from
'../../../../hooks/backend';
import { CardTable } from './Table';
@@ -6,17 +7,19 @@ import { CardTable } from './Table';
interface Props {
onUnauthorized: () => VNode;
onLoadError: (e: SwrError) => VNode;
+ onNotFound: () => VNode;
}
-export default function ({ onUnauthorized, onLoadError }: Props): VNode {
+export default function ({ onUnauthorized, onLoadError, onNotFound }: Props):
VNode {
const result = useInstanceTransfers()
const { informTransfer } = useTransferMutateAPI()
const { currency } = useConfigContext()
+
+ if (result.unauthorized) return onUnauthorized()
+ if (result.notfound) return onNotFound();
+
if (!result.data) {
- if (result.unauthorized) return onUnauthorized()
if (result.error) return onLoadError(result.error)
- return <div>
- loading ....
- </div>
+ return <Loading />
}
return <section class="section is-main-section">
<CardTable instances={result.data.transfers.map(o => ({ ...o, id:
String(o.transfer_serial_id) }))}
diff --git a/packages/frontend/src/paths/instance/update/index.tsx
b/packages/frontend/src/paths/instance/update/index.tsx
index 3bed0cf..3e76172 100644
--- a/packages/frontend/src/paths/instance/update/index.tsx
+++ b/packages/frontend/src/paths/instance/update/index.tsx
@@ -15,33 +15,34 @@
*/
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
+import { Loading } from "../../../components/exception/loading";
import { UpdateTokenModal } from "../../../components/modal";
import { useInstanceContext } from "../../../context/backend";
import { MerchantBackend } from "../../../declaration";
import { SwrError, useInstanceDetails, useInstanceMutateAPI } from
"../../../hooks/backend";
import { UpdatePage } from "./UpdatePage";
-interface Props {
+export interface Props {
onBack: () => void;
onConfirm: () => void;
- pushNotification: (n: Notification) => void;
onUnauthorized: () => VNode;
+ onNotFound: () => VNode;
onLoadError: (e: SwrError) => VNode;
onUpdateError: (e: Error) => void;
}
-export default function Update({ onBack, onConfirm, onLoadError,
onUpdateError, onUnauthorized }: Props): VNode {
+export default function Update({ onBack, onConfirm, onLoadError, onNotFound,
onUpdateError, onUnauthorized }: Props): VNode {
const { updateInstance } = useInstanceMutateAPI();
const details = useInstanceDetails()
+ if (details.unauthorized) return onUnauthorized()
+ if (details.notfound) return onNotFound();
+
if (!details.data) {
- if (details.unauthorized) return onUnauthorized()
if (details.error) return onLoadError(details.error)
- return <div>
- loading ....
- </div>
+ return <Loading />
}
return <Fragment>
diff --git a/packages/frontend/src/paths/login/index.tsx
b/packages/frontend/src/paths/login/index.tsx
index 91dc94c..d6274e8 100644
--- a/packages/frontend/src/paths/login/index.tsx
+++ b/packages/frontend/src/paths/login/index.tsx
@@ -23,9 +23,8 @@ import { LoginModal } from '../../components/exception/login';
import { Notification } from "../../utils/types";
interface Props {
- withMessage?: Notification;
onConfirm: (url: string, token?: string) => void;
}
-export default function LoginPage({ onConfirm, withMessage }: Props): VNode {
- return <LoginModal onConfirm={onConfirm} withMessage={withMessage} />
+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 8c4a353..fe99db8 100644
--- a/packages/frontend/src/scss/main.scss
+++ b/packages/frontend/src/scss/main.scss
@@ -93,6 +93,10 @@ input[type=checkbox]:indeterminate + .check {
background-color: $white;
}
+.right-sticky .buttons {
+ flex-wrap: nowrap
+}
+
.table.is-striped tbody tr:not(.is-selected):nth-child(even) .right-sticky {
background-color: #fafafa;
}
@@ -103,3 +107,64 @@ tr:hover .right-sticky {
.table.is-striped tbody tr:nth-child(even):hover .right-sticky {
background-color: hsl(0, 0%, 95%);
}
+
+.lds-ring {
+ display: inline-block;
+ position: relative;
+ width: 80px;
+ height: 80px;
+}
+.lds-ring div {
+ box-sizing: border-box;
+ display: block;
+ position: absolute;
+ width: 64px;
+ height: 64px;
+ margin: 8px;
+ border: 8px solid black;
+ border-radius: 50%;
+ animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
+ border-color: black transparent transparent transparent;
+}
+.lds-ring div:nth-child(1) {
+ animation-delay: -0.45s;
+}
+.lds-ring div:nth-child(2) {
+ animation-delay: -0.3s;
+}
+.lds-ring div:nth-child(3) {
+ animation-delay: -0.15s;
+}
+@keyframes lds-ring {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.content-full-size {
+ height: calc(100% - 3rem);
+ position: absolute;
+ width: calc(100% - 14rem);
+ display:flex;
+}
+
+.content-full-size .column .card {
+ min-width: 200px;
+
+}
+
+@include touch {
+ .content-full-size {
+ height: 100%;
+ position: absolute;
+ width: 100%;
+ }
+}
+
+.column.is-half {
+ flex: none;
+ width: 50%;
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-merchant-backoffice] branch master updated: some features,
gnunet <=