From e39d5c488e2e425bd7febf694caadc17d5126401 Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Wed, 20 Sep 2023 15:18:36 -0300
Subject: more ui
---
.../demobank-ui/src/pages/WithdrawalQRCode.tsx | 23 ++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
(limited to 'packages/demobank-ui/src/pages/WithdrawalQRCode.tsx')
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 80fdac3c8..3b983c2d4 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -15,15 +15,16 @@
*/
import {
+ Amounts,
HttpStatusCode,
Logger,
WithdrawUriResult,
+ parsePaytoUri,
} from "@gnu-taler/taler-util";
-import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { ErrorType, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { Loading } from "../components/Loading.js";
import { useWithdrawalDetails } from "../hooks/access.js";
-import { notifyInfo } from "../hooks/notification.js";
import { useSettings } from "../hooks/settings.js";
import { handleNotOkResult } from "./HomePage.js";
import { QrCodeSection } from "./QrCodeSection.js";
@@ -127,6 +128,19 @@ export function WithdrawalQRCode({
}
+ if (!data.selected_reserve_pub) {
+ return
+ the exchange is selcted but no reserve pub
+
+ }
+
+ const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account)
+
+ if (!account) {
+ return
+ the exchange is selcted but no account
+
+ }
if (!data.selection_done) {
return (
@@ -144,6 +158,11 @@ export function WithdrawalQRCode({
return (
{
notifyInfo(i18n.str`Operation canceled`);
clearCurrentWithdrawal()
--
cgit v1.2.3
From 7d4c5a71aaa6c4e781af124fe821d8be4ed101ed Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Wed, 20 Sep 2023 16:10:32 -0300
Subject: more ui
---
packages/demobank-ui/src/hooks/backend.ts | 16 +-
packages/demobank-ui/src/hooks/settings.ts | 10 ++
packages/demobank-ui/src/pages/BankFrame.tsx | 50 +++++--
packages/demobank-ui/src/pages/HomePage.tsx | 9 +-
.../src/pages/WithdrawalConfirmationQuestion.tsx | 165 +++++++++++----------
.../demobank-ui/src/pages/WithdrawalQRCode.tsx | 117 ++++++++++-----
6 files changed, 221 insertions(+), 146 deletions(-)
(limited to 'packages/demobank-ui/src/pages/WithdrawalQRCode.tsx')
diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts
index 4b60d1b6c..c05ab33e9 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -85,18 +85,26 @@ export function getInitialBackendBaseURL(): string {
typeof localStorage !== "undefined"
? localStorage.getItem("bank-base-url")
: undefined;
+ let result: string;
if (!overrideUrl) {
//normal path
if (!bankUiSettings.backendBaseURL) {
console.error(
"ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'",
);
- return canonicalizeBaseUrl(window.origin);
+ result = window.origin
}
- return canonicalizeBaseUrl(bankUiSettings.backendBaseURL);
+ result = bankUiSettings.backendBaseURL;
+ } else {
+ // testing/development path
+ result = overrideUrl
+ }
+ try {
+ return canonicalizeBaseUrl(result)
+ } catch (e) {
+ //fall back
+ return canonicalizeBaseUrl(window.origin)
}
- // testing/development path
- return canonicalizeBaseUrl(overrideUrl);
}
export const defaultState: BackendState = {
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index 46b31bf2a..43e803726 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -15,8 +15,12 @@
*/
import {
+ AmountString,
Codec,
buildCodecForObject,
+ codecForAmountString,
+ codecForBoolean,
+ codecForNumber,
codecForString,
codecOptional,
} from "@gnu-taler/taler-util";
@@ -24,15 +28,21 @@ import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
interface Settings {
currentWithdrawalOperationId: string | undefined;
+ showWithdrawalSuccess: boolean;
+ maxWithdrawalAmount: number;
}
export const codecForSettings = (): Codec =>
buildCodecForObject()
.property("currentWithdrawalOperationId", codecOptional(codecForString()))
+ .property("showWithdrawalSuccess", (codecForBoolean()))
+ .property("maxWithdrawalAmount", codecForNumber())
.build("Settings");
const defaultSettings: Settings = {
currentWithdrawalOperationId: undefined,
+ showWithdrawalSuccess: true,
+ maxWithdrawalAmount: 25
};
const DEMOBANK_SETTINGS_KEY = buildStorageKey(
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index d234845a0..e682085ae 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -183,8 +183,28 @@ export function BankFrame({
{/* 5 */}
+
+
+
+
+ Show withdrawal confirmation
+
+
+ {
+ console.log(settings.showWithdrawalSuccess)
+ updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
+ }}>
+
+
+
+
+
+
+
Sites
@@ -343,14 +363,14 @@ function StatusBanner(): VNode {
switch (n.message.type) {
case "error":
return
-
-
-
-
{n.message.title}
+
+
+
+
{n.message.title}
{
@@ -363,15 +383,15 @@ function StatusBanner(): VNode {
-
+
+
+ {n.message.description &&
+
+ {n.message.description}
+
+ }
- {n.message.description &&
-
- {n.message.description}
-
- }
-
case "info":
return
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index e00daf278..e82e46eb2 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -95,8 +95,9 @@ export function WithdrawalOperationPage({
}): VNode {
//FIXME: libeufin sandbox should return show to create the integration api endpoint
//or return withdrawal uri from response
+ const baseUrl = getInitialBackendBaseURL()
const uri = stringifyWithdrawUri({
- bankIntegrationApiBaseUrl: `${getInitialBackendBaseURL()}/integration-api`,
+ bankIntegrationApiBaseUrl: `${baseUrl}/integration-api`,
withdrawalOperationId: operationId,
});
const parsedUri = parseWithdrawUri(uri);
@@ -155,7 +156,7 @@ export function handleNotOkResult(
}
case ErrorType.SERVER: {
notify({
- type: "error",
+ type: "error",
title: i18n.str`Server returned with error`,
description: result.payload.error.description as TranslatedString,
debug: JSON.stringify(result.payload),
@@ -164,7 +165,7 @@ export function handleNotOkResult(
}
case ErrorType.UNREADABLE: {
notify({
- type:"error",
+ type: "error",
title: i18n.str`Unexpected error.`,
description: i18n.str`Response from ${result.info?.url} is unreadable, http status: ${result.status}`,
debug: JSON.stringify(result),
@@ -173,7 +174,7 @@ export function handleNotOkResult(
}
case ErrorType.UNEXPECTED: {
notify({
- type:"error",
+ type: "error",
title: i18n.str`Unexpected error.`,
description: i18n.str`Diagnostic from ${result.info?.url} is "${result.message}"`,
debug: JSON.stringify(result),
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 28f00169d..80e7a78ac 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -20,24 +20,23 @@ import {
HttpStatusCode,
Logger,
PaytoUri,
- PaytoUriGeneric,
PaytoUriIBAN,
PaytoUriTalerBank,
TranslatedString,
- WithdrawUriResult,
+ WithdrawUriResult
} from "@gnu-taler/taler-util";
import {
RequestError,
notify,
notifyError,
+ notifyInfo,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useMemo, useState } from "preact/hooks";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { useAccessAnonAPI } from "../hooks/access.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { Amount } from "./PaytoWireTransferForm.js";
const logger = new Logger("WithdrawalConfirmationQuestion");
@@ -71,6 +70,7 @@ export function WithdrawalConfirmationQuestion({
const { confirmWithdrawal, abortWithdrawal } = useAccessAnonAPI();
const [captchaAnswer, setCaptchaAnswer] = useState
();
const answer = parseInt(captchaAnswer ?? "", 10);
+ const [busy, setBusy] = useState>()
const errors = undefinedIfEmpty({
answer: !captchaAnswer
? i18n.str`Answer the question before continue`
@@ -79,13 +79,15 @@ export function WithdrawalConfirmationQuestion({
: answer !== captchaNumbers.a + captchaNumbers.b
? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.`
: undefined,
- });
+ }) ?? busy;
async function doTransfer() {
try {
+ setBusy({})
await confirmWithdrawal(
withdrawUri.withdrawalOperationId,
);
+ notifyInfo(i18n.str`Wire transfer completed!`)
} catch (error) {
if (error instanceof RequestError) {
notify(
@@ -107,10 +109,12 @@ export function WithdrawalConfirmationQuestion({
)
}
}
+ setBusy(undefined)
}
async function doCancel() {
try {
+ setBusy({})
await abortWithdrawal(withdrawUri.withdrawalOperationId);
onAborted();
} catch (error) {
@@ -132,6 +136,7 @@ export function WithdrawalConfirmationQuestion({
)
}
}
+ setBusy(undefined)
}
return (
@@ -142,68 +147,6 @@ export function WithdrawalConfirmationQuestion({
Confirm the withdrawal operation
-
-
-
-
Wire transfer details
-
-
-
- {((): VNode => {
- switch (details.account.targetType) {
- case "iban": {
- const p = details.account as PaytoUriIBAN
- const name = p.params["receiver-name"]
- return
-
-
Exchange account
- {p.iban}
-
- {name &&
-
-
Exchange name
- {p.params["receiver-name"]}
-
- }
-
- }
- case "x-taler-bank": {
- const p = details.account as PaytoUriTalerBank
- const name = p.params["receiver-name"]
- return
-
-
Exchange account
- {p.account}
-
- {name &&
-
-
Exchange name
- {p.params["receiver-name"]}
-
- }
-
- }
- default:
- return
-
Exchange account
- {details.account.targetPath}
-
-
- }
- })()}
-
-
Withdrawal identification
- {details.reserve}
-
-
-
Amount
- {Amounts.stringifyValue(details.amount)}
-
-
-
-
-
-
@@ -285,36 +228,32 @@ export function WithdrawalConfirmationQuestion({
aria-describedby="answer"
autoFocus
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
- // value={username ?? ""}
+ value={captchaAnswer ?? ""}
required
name="answer"
id="answer"
autocomplete="off"
- // value={value ?? ""}
- // disabled={!onChange}
- // onInput={(e): void => {
- // if (onChange) {
- // onChange(e.currentTarget.value);
- // }
- // }}
+ onChange={(e): void => {
+ setCaptchaAnswer(e.currentTarget.value)
+ }}
/>
-
+
Cancel
{
- // e.preventDefault()
- // doStart()
- // }}
+ disabled={!!errors}
+ onClick={(e) => {
+ e.preventDefault()
+ doTransfer()
+ }}
>
Transfer
@@ -323,6 +262,68 @@ export function WithdrawalConfirmationQuestion({
+
+
+
+
Wire transfer details
+
+
+
+ {((): VNode => {
+ switch (details.account.targetType) {
+ case "iban": {
+ const p = details.account as PaytoUriIBAN
+ const name = p.params["receiver-name"]
+ return
+
+
Exchange account
+ {p.iban}
+
+ {name &&
+
+
Exchange name
+ {p.params["receiver-name"]}
+
+ }
+
+ }
+ case "x-taler-bank": {
+ const p = details.account as PaytoUriTalerBank
+ const name = p.params["receiver-name"]
+ return
+
+
Exchange account
+ {p.account}
+
+ {name &&
+
+
Exchange name
+ {p.params["receiver-name"]}
+
+ }
+
+ }
+ default:
+ return
+
Exchange account
+ {details.account.targetPath}
+
+
+ }
+ })()}
+
+
Withdrawal identification
+ {details.reserve}
+
+
+
Amount
+ {Amounts.stringifyValue(details.amount)}
+
+
+
+
+
+
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 3b983c2d4..b48e3b1dc 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -62,9 +62,10 @@ export function WithdrawalQRCode({
result.type === ErrorType.CLIENT &&
result.status === HttpStatusCode.NotFound
) {
+ clearCurrentWithdrawal()
return operation not found
;
}
- onLoadNotOk();
+ // onLoadNotOk();
return handleNotOkResult(i18n)(result);
}
const { data } = result;
@@ -85,12 +86,12 @@ export function WithdrawalQRCode({
{
e.preventDefault();
clearCurrentWithdrawal()
onContinue()
- }}>
+ }}>
{i18n.str`Continue`}
@@ -99,49 +100,69 @@ export function WithdrawalQRCode({
}
if (data.confirmation_done) {
- return
- {i18n.str`Operation completed`}
-
-
-
-
- The wire transfer to the GNU Taler Exchange bank's account is completed, now the
- exchange will send the requested amount into your GNU Taler wallet.
-
-
-
-
- You can close this page now or continue to the account page.
-
-
-
+ if (!settings.showWithdrawalSuccess) {
+ clearCurrentWithdrawal()
+ onContinue()
+ }
+ return
+
+
+
+
+ Withdrawal OK
+
+
+
+
+ The wire transfer to the Taler exchange bank's account is completed, now the
+ exchange will send the requested amount into your GNU Taler wallet.
+
+
+
+
+
+
+ You can close this page now or continue to the account page.
+
+
+
+
+
+
+
+
+
+ Do not show this again
+
+
+ {
- e.preventDefault();
- clearCurrentWithdrawal()
- onContinue()
+ onClick={() => {
+ updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
}}>
- {i18n.str`Continue`}
-
+
+
-
-
- }
- if (!data.selected_reserve_pub) {
- return
- the exchange is selcted but no reserve pub
+
+
+ {
+ e.preventDefault();
+ clearCurrentWithdrawal()
+ onContinue()
+ }}>
+ Continue
+
+
- }
- const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account)
- if (!account) {
- return
- the exchange is selcted but no account
-
}
-
if (!data.selection_done) {
return (
);
}
+ if (!data.selected_reserve_pub) {
+ return
+ the exchange is selcted but no reserve pub
+
+ }
+
+ const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account)
+
+ if (!account) {
+ return
+ the exchange is selcted but no account
+
+ }
+
return (
);
}
\ No newline at end of file
--
cgit v1.2.3
From 062939d9cc016a186a282f7a48492c3e01cd740c Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Thu, 21 Sep 2023 10:31:10 -0300
Subject: admin refactor
---
packages/demobank-ui/src/components/Routing.tsx | 13 +-
packages/demobank-ui/src/pages/AdminPage.tsx | 1042 --------------------
packages/demobank-ui/src/pages/BusinessAccount.tsx | 758 --------------
packages/demobank-ui/src/pages/HomePage.tsx | 19 +-
.../demobank-ui/src/pages/ShowAccountDetails.tsx | 143 +++
.../src/pages/UpdateAccountPassword.tsx | 131 +++
.../src/pages/WithdrawalConfirmationQuestion.tsx | 3 +-
.../demobank-ui/src/pages/WithdrawalQRCode.tsx | 4 -
packages/demobank-ui/src/pages/admin/Account.tsx | 56 ++
.../demobank-ui/src/pages/admin/AccountForm.tsx | 219 ++++
.../demobank-ui/src/pages/admin/AccountList.tsx | 120 +++
.../src/pages/admin/CreateNewAccount.tsx | 107 ++
packages/demobank-ui/src/pages/admin/Home.tsx | 162 +++
.../demobank-ui/src/pages/admin/RemoveAccount.tsx | 112 +++
packages/demobank-ui/src/pages/business/Home.tsx | 757 ++++++++++++++
15 files changed, 1823 insertions(+), 1823 deletions(-)
delete mode 100644 packages/demobank-ui/src/pages/AdminPage.tsx
delete mode 100644 packages/demobank-ui/src/pages/BusinessAccount.tsx
create mode 100644 packages/demobank-ui/src/pages/ShowAccountDetails.tsx
create mode 100644 packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
create mode 100644 packages/demobank-ui/src/pages/admin/Account.tsx
create mode 100644 packages/demobank-ui/src/pages/admin/AccountForm.tsx
create mode 100644 packages/demobank-ui/src/pages/admin/AccountList.tsx
create mode 100644 packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
create mode 100644 packages/demobank-ui/src/pages/admin/Home.tsx
create mode 100644 packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
create mode 100644 packages/demobank-ui/src/pages/business/Home.tsx
(limited to 'packages/demobank-ui/src/pages/WithdrawalQRCode.tsx')
diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx
index 890058a9b..ef11af76e 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -19,14 +19,14 @@ import { VNode, h } from "preact";
import { Route, Router, route } from "preact-router";
import { useEffect } from "preact/hooks";
import { BankFrame } from "../pages/BankFrame.js";
-import { BusinessAccount } from "../pages/BusinessAccount.js";
+import { BusinessAccount } from "../pages/business/Home.js";
import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js";
import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js";
import { RegistrationPage } from "../pages/RegistrationPage.js";
import { Test } from "../pages/Test.js";
import { useBackendContext } from "../context/backend.js";
import { LoginForm } from "../pages/LoginForm.js";
-import { AdminPage } from "../pages/AdminPage.js";
+import { AdminHome } from "../pages/admin/Home.js";
export function Routing(): VNode {
const history = createHashHistory();
@@ -34,6 +34,7 @@ export function Routing(): VNode {
if (backend.state.status === "loggedOut") {
return {
route("/business");
}}
@@ -63,7 +64,7 @@ export function Routing(): VNode {
}
- const isAdmin = backend.state.isUserAdministrator
+ const { isUserAdministrator, username } = backend.state
return (
{
- if (isAdmin) {
- return {
route("/register");
}}
/>;
} else {
return {
route(`/operation/${wopid}`);
}}
@@ -130,6 +132,7 @@ export function Routing(): VNode {
path="/business"
component={() => (
{
route("/account");
}}
diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx b/packages/demobank-ui/src/pages/AdminPage.tsx
deleted file mode 100644
index 18462bdc3..000000000
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ /dev/null
@@ -1,1042 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- */
-
-import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri } from "@gnu-taler/taler-util";
-import {
- ErrorType,
- HttpResponsePaginated,
- RequestError,
- notify,
- notifyError,
- notifyInfo,
- useTranslationContext,
-} from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { Cashouts } from "../components/Cashouts/index.js";
-import { useBackendContext } from "../context/backend.js";
-import { useAccountDetails } from "../hooks/access.js";
-import {
- useAdminAccountAPI,
- useBusinessAccountDetails,
- useBusinessAccounts,
-} from "../hooks/circuit.js";
-import {
- buildRequestErrorMessage,
- PartialButDefined,
- RecursivePartial,
- undefinedIfEmpty,
- validateIBAN,
- WithIntermediate,
-} from "../utils.js";
-import { ShowCashoutDetails } from "./BusinessAccount.js";
-import { handleNotOkResult } from "./HomePage.js";
-import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-
-const charset =
- "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-const upperIdx = charset.indexOf("A");
-
-function randomPassword(): string {
- const random = Array.from({ length: 16 }).map(() => {
- return charset.charCodeAt(Math.random() * charset.length);
- });
- // first char can't be upper
- const charIdx = charset.indexOf(String.fromCharCode(random[0]));
- random[0] =
- charIdx > upperIdx ? charset.charCodeAt(charIdx - upperIdx) : random[0];
- return String.fromCharCode(...random);
-}
-
-interface Props {
- onRegister: () => void;
-}
-/**
- * Query account information and show QR code if there is pending withdrawal
- */
-export function AdminPage({ onRegister }: Props): VNode {
- const [account, setAccount] = useState();
- const [showDetails, setShowDetails] = useState();
- const [showCashouts, setShowCashouts] = useState();
- const [updatePassword, setUpdatePassword] = useState();
- const [removeAccount, setRemoveAccount] = useState();
- const [showCashoutDetails, setShowCashoutDetails] = useState<
- string | undefined
- >();
-
- const [createAccount, setCreateAccount] = useState(false);
-
- const result = useBusinessAccounts({ account });
- const { i18n } = useTranslationContext();
-
- if (result.loading) return
;
- if (!result.ok) {
- return handleNotOkResult(i18n, onRegister)(result);
- }
-
- const { customers } = result.data;
-
- if (showCashoutDetails) {
- return (
- {
- setShowCashoutDetails(undefined);
- }}
- />
- );
- }
-
- if (showCashouts) {
- return (
-
-
-
- Cashout for account {showCashouts}
-
-
-
{
- setShowCashouts(id);
- setShowCashouts(undefined);
- }}
- />
-
- {
- e.preventDefault();
- setShowCashouts(undefined);
- }}
- />
-
-
- );
- }
-
- if (showDetails) {
- return (
- {
- setUpdatePassword(showDetails);
- setShowDetails(undefined);
- }}
- onUpdateSuccess={() => {
- notifyInfo(i18n.str`Account updated`);
- setShowDetails(undefined);
- }}
- onClear={() => {
- setShowDetails(undefined);
- }}
- />
- );
- }
- if (removeAccount) {
- return (
- {
- notifyInfo(i18n.str`Account removed`);
- setRemoveAccount(undefined);
- }}
- onClear={() => {
- setRemoveAccount(undefined);
- }}
- />
- );
- }
- if (updatePassword) {
- return (
- {
- notifyInfo(i18n.str`Password changed`);
- setUpdatePassword(undefined);
- }}
- onClear={() => {
- setUpdatePassword(undefined);
- }}
- />
- );
- }
- if (createAccount) {
- return (
- setCreateAccount(false)}
- onCreateSuccess={(password) => {
- notifyInfo(
- i18n.str`Account created with password "${password}". The user must change the password on the next login.`,
- );
- setCreateAccount(false);
- }}
- />
- );
- }
-
- return (
-
-
-
- Admin panel
-
-
-
-
-
-
-
- {
- e.preventDefault();
-
- setCreateAccount(true);
- }}
- />
-
-
-
-
-
-
- {!customers.length ? (
-
- ) : (
-
- {i18n.str`Accounts:`}
-
-
- )}
-
-
- );
-}
-
-function AdminAccount({ onRegister }: { onRegister: () => void }): VNode {
- const { i18n } = useTranslationContext();
- const r = useBackendContext();
- const account = r.state.status === "loggedIn" ? r.state.username : "admin";
- const result = useAccountDetails(account);
-
- if (!result.ok) {
- return handleNotOkResult(i18n, onRegister)(result);
- }
- const { data } = result;
- const balance = Amounts.parseOrThrow(data.balance.amount);
- const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
- const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
- const limit = balanceIsDebit
- ? Amounts.sub(debitThreshold, balance).amount
- : Amounts.add(balance, debitThreshold).amount;
- if (!balance) return ;
- return (
-
-
-
-
{i18n.str`Bank account balance`}
- {!balance ? (
-
- Waiting server response...
-
- ) : (
-
- {balanceIsDebit ? - : null}
- {`${Amounts.stringifyValue(balance)}`}
-
- {`${balance.currency}`}
-
- )}
-
-
- {
- notifyInfo(i18n.str`Wire transfer created!`);
- }}
- onCancel={undefined}
- />
-
- );
-}
-
-const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
-const EMAIL_REGEX =
- /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
-const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
-
-function initializeFromTemplate(
- account: SandboxBackend.Circuit.CircuitAccountData | undefined,
-): WithIntermediate {
- const emptyAccount = {
- cashout_address: undefined,
- iban: undefined,
- name: undefined,
- username: undefined,
- contact_data: undefined,
- };
- const emptyContact = {
- email: undefined,
- phone: undefined,
- };
-
- const initial: PartialButDefined =
- structuredClone(account) ?? emptyAccount;
- if (typeof initial.contact_data === "undefined") {
- initial.contact_data = emptyContact;
- }
- initial.contact_data.email;
- return initial as any;
-}
-
-export function UpdateAccountPassword({
- account,
- onClear,
- onUpdateSuccess,
- onLoadNotOk,
-}: {
- onLoadNotOk: (
- error: HttpResponsePaginated,
- ) => VNode;
- onClear: () => void;
- onUpdateSuccess: () => void;
- account: string;
-}): VNode {
- const { i18n } = useTranslationContext();
- const result = useBusinessAccountDetails(account);
- const { changePassword } = useAdminAccountAPI();
- const [password, setPassword] = useState();
- const [repeat, setRepeat] = useState();
-
- if (!result.ok) {
- if (result.loading || result.type === ErrorType.TIMEOUT) {
- return onLoadNotOk(result);
- }
- if (result.status === HttpStatusCode.NotFound) {
- return account not found
;
- }
- return onLoadNotOk(result);
- }
-
- const errors = undefinedIfEmpty({
- password: !password ? i18n.str`required` : undefined,
- repeat: !repeat
- ? i18n.str`required`
- : password !== repeat
- ? i18n.str`password doesn't match`
- : undefined,
- });
-
- return (
-
-
-
- Update password for {account}
-
-
-
-
-
- );
-}
-
-function CreateNewAccount({
- onClose,
- onCreateSuccess,
-}: {
- onClose: () => void;
- onCreateSuccess: (password: string) => void;
-}): VNode {
- const { i18n } = useTranslationContext();
- const { createAccount } = useAdminAccountAPI();
- const [submitAccount, setSubmitAccount] = useState<
- SandboxBackend.Circuit.CircuitAccountData | undefined
- >();
- return (
-
-
-
- New account
-
-
-
-
-
{
- setSubmitAccount(a);
- }}
- />
-
-
-
-
- {
- e.preventDefault();
- onClose();
- }}
- />
-
-
- {
- e.preventDefault();
-
- if (!submitAccount) return;
- try {
- const account: SandboxBackend.Circuit.CircuitAccountRequest =
- {
- cashout_address: submitAccount.cashout_address,
- contact_data: submitAccount.contact_data,
- internal_iban: submitAccount.iban,
- name: submitAccount.name,
- username: submitAccount.username,
- password: randomPassword(),
- };
-
- await createAccount(account);
- onCreateSuccess(account.password);
- } catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.Forbidden
- ? i18n.str`The rights to perform the operation are not sufficient`
- : status === HttpStatusCode.BadRequest
- ? i18n.str`Input data was invalid`
- : status === HttpStatusCode.Conflict
- ? i18n.str`At least one registration detail was not available`
- : undefined,
- }),
- );
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
- }}
- />
-
-
-
-
-
- );
-}
-
-export function ShowAccountDetails({
- account,
- onClear,
- onUpdateSuccess,
- onLoadNotOk,
- onChangePassword,
-}: {
- onLoadNotOk: (
- error: HttpResponsePaginated,
- ) => VNode;
- onClear?: () => void;
- onChangePassword: () => void;
- onUpdateSuccess: () => void;
- account: string;
-}): VNode {
- const { i18n } = useTranslationContext();
- const result = useBusinessAccountDetails(account);
- const { updateAccount } = useAdminAccountAPI();
- const [update, setUpdate] = useState(false);
- const [submitAccount, setSubmitAccount] = useState<
- SandboxBackend.Circuit.CircuitAccountData | undefined
- >();
-
- if (!result.ok) {
- if (result.loading || result.type === ErrorType.TIMEOUT) {
- return onLoadNotOk(result);
- }
- if (result.status === HttpStatusCode.NotFound) {
- return account not found
;
- }
- return onLoadNotOk(result);
- }
-
- return (
-
-
-
- Business account details
-
-
-
-
setSubmitAccount(a)}
- />
-
-
-
-
- {onClear ? (
- {
- e.preventDefault();
- onClear();
- }}
- />
- ) : undefined}
-
-
-
-
-
-
- );
-}
-
-function RemoveAccount({
- account,
- onClear,
- onUpdateSuccess,
- onLoadNotOk,
-}: {
- onLoadNotOk: (
- error: HttpResponsePaginated,
- ) => VNode;
- onClear: () => void;
- onUpdateSuccess: () => void;
- account: string;
-}): VNode {
- const { i18n } = useTranslationContext();
- const result = useAccountDetails(account);
- const { deleteAccount } = useAdminAccountAPI();
-
- if (!result.ok) {
- if (result.loading || result.type === ErrorType.TIMEOUT) {
- return onLoadNotOk(result);
- }
- if (result.status === HttpStatusCode.NotFound) {
- return account not found
;
- }
- return onLoadNotOk(result);
- }
-
- const balance = Amounts.parse(result.data.balance.amount);
- if (!balance) {
- return there was an error reading the balance
;
- }
- const isBalanceEmpty = Amounts.isZero(balance);
- return (
-
-
-
- Remove account: {account}
-
-
- {/* {FXME: SHOW WARNING} */}
- {/* {!isBalanceEmpty && (
-
saveError(undefined)}
- />
- )} */}
-
-
-
-
-
- );
-}
-/**
- * Create valid account object to update or create
- * Take template as initial values for the form
- * Purpose indicate if all field al read only (show), part of them (update)
- * or none (create)
- * @param param0
- * @returns
- */
-function AccountForm({
- template,
- purpose,
- onChange,
-}: {
- template: SandboxBackend.Circuit.CircuitAccountData | undefined;
- onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => void;
- purpose: "create" | "update" | "show";
-}): VNode {
- const initial = initializeFromTemplate(template);
- const [form, setForm] = useState(initial);
- const [errors, setErrors] = useState<
- RecursivePartial | undefined
- >(undefined);
- const { i18n } = useTranslationContext();
-
- function updateForm(newForm: typeof initial): void {
- const parsed = !newForm.cashout_address
- ? undefined
- : parsePaytoUri(newForm.cashout_address);
-
- const errors = undefinedIfEmpty>({
- cashout_address: !newForm.cashout_address
- ? i18n.str`required`
- : !parsed
- ? i18n.str`does not follow the pattern`
- : !parsed.isKnown || parsed.targetType !== "iban"
- ? i18n.str`only "IBAN" target are supported`
- : !IBAN_REGEX.test(parsed.iban)
- ? i18n.str`IBAN should have just uppercased letters and numbers`
- : validateIBAN(parsed.iban, i18n),
- contact_data: undefinedIfEmpty({
- email: !newForm.contact_data?.email
- ? i18n.str`required`
- : !EMAIL_REGEX.test(newForm.contact_data.email)
- ? i18n.str`it should be an email`
- : undefined,
- phone: !newForm.contact_data?.phone
- ? i18n.str`required`
- : !newForm.contact_data.phone.startsWith("+")
- ? i18n.str`should start with +`
- : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
- ? i18n.str`phone number can't have other than numbers`
- : undefined,
- }),
- iban: !newForm.iban
- ? undefined //optional field
- : !IBAN_REGEX.test(newForm.iban)
- ? i18n.str`IBAN should have just uppercased letters and numbers`
- : validateIBAN(newForm.iban, i18n),
- name: !newForm.name ? i18n.str`required` : undefined,
- username: !newForm.username ? i18n.str`required` : undefined,
- });
- setErrors(errors);
- setForm(newForm);
- onChange(errors === undefined ? (newForm as any) : undefined);
- }
-
- return (
-
- );
-}
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx
deleted file mode 100644
index ec71ceca6..000000000
--- a/packages/demobank-ui/src/pages/BusinessAccount.tsx
+++ /dev/null
@@ -1,758 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- */
-import {
- AmountJson,
- Amounts,
- HttpStatusCode,
- TranslatedString
-} from "@gnu-taler/taler-util";
-import {
- HttpResponse,
- HttpResponsePaginated,
- RequestError,
- notify,
- notifyError,
- notifyInfo,
- useTranslationContext,
-} from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { Cashouts } from "../components/Cashouts/index.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { useBackendContext } from "../context/backend.js";
-import { useAccountDetails } from "../hooks/access.js";
-import {
- useCashoutDetails,
- useCircuitAccountAPI,
- useEstimator,
- useRatiosAndFeeConfig,
-} from "../hooks/circuit.js";
-import {
- TanChannel,
- buildRequestErrorMessage,
- undefinedIfEmpty,
-} from "../utils.js";
-import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js";
-import { handleNotOkResult } from "./HomePage.js";
-import { LoginForm } from "./LoginForm.js";
-import { Amount } from "./PaytoWireTransferForm.js";
-
-interface Props {
- onClose: () => void;
- onRegister: () => void;
- onLoadNotOk: () => void;
-}
-export function BusinessAccount({
- onClose,
- onLoadNotOk,
- onRegister,
-}: Props): VNode {
- const { i18n } = useTranslationContext();
- const backend = useBackendContext();
- const [updatePassword, setUpdatePassword] = useState(false);
- const [newCashout, setNewcashout] = useState(false);
- const [showCashoutDetails, setShowCashoutDetails] = useState<
- string | undefined
- >();
-
- if (backend.state.status === "loggedOut") {
- return ;
- }
-
- if (newCashout) {
- return (
- {
- setNewcashout(false);
- }}
- onComplete={(id) => {
- notifyInfo(
- i18n.str`Cashout created. You need to confirm the operation to complete the transaction.`,
- );
- setNewcashout(false);
- setShowCashoutDetails(id);
- }}
- />
- );
- }
- if (showCashoutDetails) {
- return (
- {
- setShowCashoutDetails(undefined);
- }}
- />
- );
- }
- if (updatePassword) {
- return (
- {
- notifyInfo(i18n.str`Password changed`);
- setUpdatePassword(false);
- }}
- onClear={() => {
- setUpdatePassword(false);
- }}
- />
- );
- }
- return (
-
-
{
- notifyInfo(i18n.str`Account updated`);
- }}
- onChangePassword={() => {
- setUpdatePassword(true);
- }}
- onClear={onClose}
- />
-
-
-
{i18n.str`Latest cashouts`}
- {
- setShowCashoutDetails(id);
- }}
- />
-
-
-
-
-
{
- e.preventDefault();
- setNewcashout(true);
- }}
- />
-
-
-
- );
-}
-
-interface PropsCashout {
- account: string;
- onComplete: (id: string) => void;
- onCancel: () => void;
- onLoadNotOk: (
- error:
- | HttpResponsePaginated
- | HttpResponse,
- ) => VNode;
-}
-
-type FormType = {
- isDebit: boolean;
- amount: string;
- subject: string;
- channel: TanChannel;
-};
-type ErrorFrom = {
- [P in keyof T]+?: string;
-};
-
-// check #7719
-function useRatiosAndFeeConfigWithChangeDetection(): HttpResponse<
- SandboxBackend.Circuit.Config & { hasChanged?: boolean },
- SandboxBackend.SandboxError
-> {
- const result = useRatiosAndFeeConfig();
- const [oldResult, setOldResult] = useState<
- SandboxBackend.Circuit.Config | undefined
- >(undefined);
- const dataFromBackend = result.ok ? result.data : undefined;
- useEffect(() => {
- // save only the first result of /config to the backend
- if (!dataFromBackend || oldResult !== undefined) return;
- setOldResult(dataFromBackend);
- }, [dataFromBackend]);
-
- if (!result.ok) return result;
-
- const data = !oldResult ? result.data : oldResult;
- const hasChanged =
- oldResult &&
- (result.data.name !== oldResult.name ||
- result.data.version !== oldResult.version ||
- result.data.ratios_and_fees.buy_at_ratio !==
- oldResult.ratios_and_fees.buy_at_ratio ||
- result.data.ratios_and_fees.buy_in_fee !==
- oldResult.ratios_and_fees.buy_in_fee ||
- result.data.ratios_and_fees.sell_at_ratio !==
- oldResult.ratios_and_fees.sell_at_ratio ||
- result.data.ratios_and_fees.sell_out_fee !==
- oldResult.ratios_and_fees.sell_out_fee ||
- result.data.fiat_currency !== oldResult.fiat_currency);
-
- return {
- ...result,
- data: { ...data, hasChanged },
- };
-}
-
-function CreateCashout({
- account,
- onComplete,
- onCancel,
- onLoadNotOk,
-}: PropsCashout): VNode {
- const { i18n } = useTranslationContext();
- const ratiosResult = useRatiosAndFeeConfig();
- const result = useAccountDetails(account);
- const {
- estimateByCredit: calculateFromCredit,
- estimateByDebit: calculateFromDebit,
- } = useEstimator();
- const [form, setForm] = useState>({ isDebit: true });
-
- const { createCashout } = useCircuitAccountAPI();
- if (!result.ok) return onLoadNotOk(result);
- if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
- const config = ratiosResult.data;
-
- const balance = Amounts.parseOrThrow(result.data.balance.amount);
- const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
- const zero = Amounts.zeroOfCurrency(balance.currency);
- const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
- const limit = balanceIsDebit
- ? Amounts.sub(debitThreshold, balance).amount
- : Amounts.add(balance, debitThreshold).amount;
-
- const zeroCalc = { debit: zero, credit: zero, beforeFee: zero };
- const [calc, setCalc] = useState(zeroCalc);
- const sellRate = config.ratios_and_fees.sell_at_ratio;
- const sellFee = !config.ratios_and_fees.sell_out_fee
- ? zero
- : Amounts.parseOrThrow(
- `${balance.currency}:${config.ratios_and_fees.sell_out_fee}`,
- );
- const fiatCurrency = config.fiat_currency;
-
- if (!sellRate || sellRate < 0) return error rate
;
-
- const amount = Amounts.parseOrThrow(
- `${!form.isDebit ? fiatCurrency : balance.currency}:${
- !form.amount ? "0" : form.amount
- }`,
- );
-
- useEffect(() => {
- if (form.isDebit) {
- calculateFromDebit(amount, sellFee, sellRate)
- .then((r) => {
- setCalc(r);
- })
- .catch((error) => {
- notify(
- error instanceof RequestError
- ? buildRequestErrorMessage(i18n, error.cause)
- : {
- type: "error",
- title: i18n.str`Could not estimate the cashout`,
- description: error.message as TranslatedString
- },
- );
- });
- } else {
- calculateFromCredit(amount, sellFee, sellRate)
- .then((r) => {
- setCalc(r);
- })
- .catch((error) => {
- notify(
- error instanceof RequestError
- ? buildRequestErrorMessage(i18n, error.cause)
- : {
- type: "error",
- title: i18n.str`Could not estimate the cashout`,
- description: error.message,
- },
- );
- });
- }
- }, [form.amount, form.isDebit]);
-
- const balanceAfter = Amounts.sub(balance, calc.debit).amount;
-
- function updateForm(newForm: typeof form): void {
- setForm(newForm);
- }
- const errors = undefinedIfEmpty>({
- amount: !form.amount
- ? i18n.str`required`
- : !amount
- ? i18n.str`could not be parsed`
- : Amounts.cmp(limit, calc.debit) === -1
- ? i18n.str`balance is not enough`
- : Amounts.cmp(calc.beforeFee, sellFee) === -1
- ? i18n.str`the total amount to transfer does not cover the fees`
- : Amounts.isZero(calc.credit)
- ? i18n.str`the total transfer at destination will be zero`
- : undefined,
- channel: !form.channel ? i18n.str`required` : undefined,
- });
-
- return (
-
- );
-}
-
-interface ShowCashoutProps {
- id: string;
- onCancel: () => void;
- onLoadNotOk: (
- error: HttpResponsePaginated,
- ) => VNode;
-}
-export function ShowCashoutDetails({
- id,
- onCancel,
- onLoadNotOk,
-}: ShowCashoutProps): VNode {
- const { i18n } = useTranslationContext();
- const result = useCashoutDetails(id);
- const { abortCashout, confirmCashout } = useCircuitAccountAPI();
- const [code, setCode] = useState(undefined);
- if (!result.ok) return onLoadNotOk(result);
- const errors = undefinedIfEmpty({
- code: !code ? i18n.str`required` : undefined,
- });
- const isPending = String(result.data.status).toUpperCase() === "PENDING";
- return (
-
-
Cashout details {id}
-
-
-
-
{
- e.preventDefault();
- onCancel();
- }}
- >
- {i18n.str`Back`}
-
- {isPending ? (
-
- {
- e.preventDefault();
- try {
- await abortCashout(id);
- onCancel();
- } catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.NotFound
- ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
- : status === HttpStatusCode.PreconditionFailed
- ? i18n.str`Cashout was already confimed`
- : undefined,
- }),
- );
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
- }}
- >
- {i18n.str`Abort`}
-
-
- {
- e.preventDefault();
- try {
- if (!code) return;
- const rest = await confirmCashout(id, {
- tan: code,
- });
- } catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.NotFound
- ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
- : status === HttpStatusCode.PreconditionFailed
- ? i18n.str`Cashout was already confimed`
- : status === HttpStatusCode.Conflict
- ? i18n.str`Confirmation failed. Maybe the user changed their cash-out address between the creation and the confirmation`
- : status === HttpStatusCode.Forbidden
- ? i18n.str`Invalid code`
- : undefined,
- }),
- );
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
- }}
- >
- {i18n.str`Confirm`}
-
-
- ) : (
-
- )}
-
-
- );
-}
-
-const MAX_AMOUNT_DIGIT = 2;
-/**
- * Truncate the amount of digits to display
- * in the form based on the fee calculations
- *
- * Backend must have the same truncation
- * @param a
- * @returns
- */
-function truncate(a: AmountJson): AmountJson {
- const str = Amounts.stringify(a);
- const idx = str.indexOf(".");
- if (idx === -1) {
- return a;
- }
- const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT);
- return Amounts.parseOrThrow(truncated);
-}
-
-export function assertUnreachable(x: never): never {
- throw new Error("Didn't expect to get here");
-}
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index e82e46eb2..a911f347c 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -35,7 +35,7 @@ import { useBackendContext } from "../context/backend.js";
import { getInitialBackendBaseURL } from "../hooks/backend.js";
import { useSettings } from "../hooks/settings.js";
import { AccountPage } from "./AccountPage/index.js";
-import { AdminPage } from "./AdminPage.js";
+import { AdminHome } from "./admin/Home.js";
import { LoginForm } from "./LoginForm.js";
import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
import { error } from "console";
@@ -54,31 +54,24 @@ const logger = new Logger("AccountPage");
*/
export function HomePage({
onRegister,
+ account,
onPendingOperationFound,
}: {
+ account: string,
onPendingOperationFound: (id: string) => void;
onRegister: () => void;
}): VNode {
- const backend = useBackendContext();
const [settings] = useSettings();
const { i18n } = useTranslationContext();
- if (backend.state.status === "loggedOut") {
- return ;
- }
-
if (settings.currentWithdrawalOperationId) {
onPendingOperationFound(settings.currentWithdrawalOperationId);
return ;
}
- if (backend.state.isUserAdministrator) {
- return ;
- }
-
return (
);
@@ -105,8 +98,8 @@ export function WithdrawalOperationPage({
if (!parsedUri) {
notifyError(
- i18n.str`The Withdrawal URI is not valid: "${uri}"`,
- undefined
+ i18n.str`The Withdrawal URI is not valid`,
+ uri as TranslatedString
);
return ;
}
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
new file mode 100644
index 000000000..91b50b84c
--- /dev/null
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -0,0 +1,143 @@
+import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode,h } from "preact";
+import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js";
+import { useState } from "preact/hooks";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { buildRequestErrorMessage } from "../utils.js";
+import { AccountForm } from "./admin/AccountForm.js";
+
+export function ShowAccountDetails({
+ account,
+ onClear,
+ onUpdateSuccess,
+ onLoadNotOk,
+ onChangePassword,
+ }: {
+ onLoadNotOk: (
+ error: HttpResponsePaginated,
+ ) => VNode;
+ onClear?: () => void;
+ onChangePassword: () => void;
+ onUpdateSuccess: () => void;
+ account: string;
+ }): VNode {
+ const { i18n } = useTranslationContext();
+ const result = useBusinessAccountDetails(account);
+ const { updateAccount } = useAdminAccountAPI();
+ const [update, setUpdate] = useState(false);
+ const [submitAccount, setSubmitAccount] = useState<
+ SandboxBackend.Circuit.CircuitAccountData | undefined
+ >();
+
+ if (!result.ok) {
+ if (result.loading || result.type === ErrorType.TIMEOUT) {
+ return onLoadNotOk(result);
+ }
+ if (result.status === HttpStatusCode.NotFound) {
+ return account not found
;
+ }
+ return onLoadNotOk(result);
+ }
+
+ return (
+
+
+
+ Business account details
+
+
+
+
setSubmitAccount(a)}
+ />
+
+
+
+
+ {onClear ? (
+ {
+ e.preventDefault();
+ onClear();
+ }}
+ />
+ ) : undefined}
+
+
+
+
+
+
+ );
+ }
+
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
new file mode 100644
index 000000000..084a5b643
--- /dev/null
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -0,0 +1,131 @@
+import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js";
+import { useState } from "preact/hooks";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { VNode,h ,Fragment} from "preact";
+import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+
+export function UpdateAccountPassword({
+ account,
+ onClear,
+ onUpdateSuccess,
+ onLoadNotOk,
+ }: {
+ onLoadNotOk: (
+ error: HttpResponsePaginated,
+ ) => VNode;
+ onClear: () => void;
+ onUpdateSuccess: () => void;
+ account: string;
+ }): VNode {
+ const { i18n } = useTranslationContext();
+ const result = useBusinessAccountDetails(account);
+ const { changePassword } = useAdminAccountAPI();
+ const [password, setPassword] = useState();
+ const [repeat, setRepeat] = useState();
+
+ if (!result.ok) {
+ if (result.loading || result.type === ErrorType.TIMEOUT) {
+ return onLoadNotOk(result);
+ }
+ if (result.status === HttpStatusCode.NotFound) {
+ return account not found
;
+ }
+ return onLoadNotOk(result);
+ }
+
+ const errors = undefinedIfEmpty({
+ password: !password ? i18n.str`required` : undefined,
+ repeat: !repeat
+ ? i18n.str`required`
+ : password !== repeat
+ ? i18n.str`password doesn't match`
+ : undefined,
+ });
+
+ return (
+
+
+
+ Update password for {account}
+
+
+
+
+
+ );
+ }
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index ced152feb..30fcbdff7 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -317,7 +317,8 @@ export function WithdrawalConfirmationQuestion({
Amount
- {Amounts.stringifyValue(details.amount)}
+ To be added
+ {/* Amounts.stringifyValue(details.amount) */}
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index b48e3b1dc..2a3a1ec2c 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -100,10 +100,6 @@ export function WithdrawalQRCode({
}
if (data.confirmation_done) {
- if (!settings.showWithdrawalSuccess) {
- clearCurrentWithdrawal()
- onContinue()
- }
return
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx
new file mode 100644
index 000000000..8ab3e1323
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/Account.tsx
@@ -0,0 +1,56 @@
+import { Amounts } from "@gnu-taler/taler-util";
+import { PaytoWireTransferForm } from "../PaytoWireTransferForm.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { useAccountDetails } from "../../hooks/access.js";
+import { useBackendContext } from "../../context/backend.js";
+import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, h, VNode } from "preact";
+
+export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode {
+ const { i18n } = useTranslationContext();
+ const r = useBackendContext();
+ const account = r.state.status === "loggedIn" ? r.state.username : "admin";
+ const result = useAccountDetails(account);
+
+ if (!result.ok) {
+ return handleNotOkResult(i18n, onRegister)(result);
+ }
+ const { data } = result;
+ const balance = Amounts.parseOrThrow(data.balance.amount);
+ const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
+ const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
+ const limit = balanceIsDebit
+ ? Amounts.sub(debitThreshold, balance).amount
+ : Amounts.add(balance, debitThreshold).amount;
+ if (!balance) return
;
+ return (
+
+
+
+
{i18n.str`Bank account balance`}
+ {!balance ? (
+
+ Waiting server response...
+
+ ) : (
+
+ {balanceIsDebit ? - : null}
+ {`${Amounts.stringifyValue(balance)}`}
+
+ {`${balance.currency}`}
+
+ )}
+
+
+ {
+ notifyInfo(i18n.str`Wire transfer created!`);
+ }}
+ onCancel={undefined}
+ />
+
+ );
+ }
+
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
new file mode 100644
index 000000000..9ca0323a1
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -0,0 +1,219 @@
+import { VNode,h } from "preact";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
+import { useState } from "preact/hooks";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { parsePaytoUri } from "@gnu-taler/taler-util";
+
+const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
+const EMAIL_REGEX =
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
+
+/**
+ * Create valid account object to update or create
+ * Take template as initial values for the form
+ * Purpose indicate if all field al read only (show), part of them (update)
+ * or none (create)
+ * @param param0
+ * @returns
+ */
+export function AccountForm({
+ template,
+ purpose,
+ onChange,
+ }: {
+ template: SandboxBackend.Circuit.CircuitAccountData | undefined;
+ onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => void;
+ purpose: "create" | "update" | "show";
+ }): VNode {
+ const initial = initializeFromTemplate(template);
+ const [form, setForm] = useState(initial);
+ const [errors, setErrors] = useState<
+ RecursivePartial
| undefined
+ >(undefined);
+ const { i18n } = useTranslationContext();
+
+ function updateForm(newForm: typeof initial): void {
+ const parsed = !newForm.cashout_address
+ ? undefined
+ : parsePaytoUri(newForm.cashout_address);
+
+ const errors = undefinedIfEmpty>({
+ cashout_address: !newForm.cashout_address
+ ? i18n.str`required`
+ : !parsed
+ ? i18n.str`does not follow the pattern`
+ : !parsed.isKnown || parsed.targetType !== "iban"
+ ? i18n.str`only "IBAN" target are supported`
+ : !IBAN_REGEX.test(parsed.iban)
+ ? i18n.str`IBAN should have just uppercased letters and numbers`
+ : validateIBAN(parsed.iban, i18n),
+ contact_data: undefinedIfEmpty({
+ email: !newForm.contact_data?.email
+ ? i18n.str`required`
+ : !EMAIL_REGEX.test(newForm.contact_data.email)
+ ? i18n.str`it should be an email`
+ : undefined,
+ phone: !newForm.contact_data?.phone
+ ? i18n.str`required`
+ : !newForm.contact_data.phone.startsWith("+")
+ ? i18n.str`should start with +`
+ : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
+ ? i18n.str`phone number can't have other than numbers`
+ : undefined,
+ }),
+ iban: !newForm.iban
+ ? undefined //optional field
+ : !IBAN_REGEX.test(newForm.iban)
+ ? i18n.str`IBAN should have just uppercased letters and numbers`
+ : validateIBAN(newForm.iban, i18n),
+ name: !newForm.name ? i18n.str`required` : undefined,
+ username: !newForm.username ? i18n.str`required` : undefined,
+ });
+ setErrors(errors);
+ setForm(newForm);
+ onChange(errors === undefined ? (newForm as any) : undefined);
+ }
+
+ return (
+
+ );
+ }
+
+ function initializeFromTemplate(
+ account: SandboxBackend.Circuit.CircuitAccountData | undefined,
+ ): WithIntermediate {
+ const emptyAccount = {
+ cashout_address: undefined,
+ iban: undefined,
+ name: undefined,
+ username: undefined,
+ contact_data: undefined,
+ };
+ const emptyContact = {
+ email: undefined,
+ phone: undefined,
+ };
+
+ const initial: PartialButDefined =
+ structuredClone(account) ?? emptyAccount;
+ if (typeof initial.contact_data === "undefined") {
+ initial.contact_data = emptyContact;
+ }
+ initial.contact_data.email;
+ return initial as any;
+ }
+
+
+
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx
new file mode 100644
index 000000000..56b15818b
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -0,0 +1,120 @@
+import { h, VNode } from "preact";
+import { useBusinessAccounts } from "../../hooks/circuit.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { AccountAction } from "./Home.js";
+import { Amounts } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+
+interface Props {
+ onAction: (type: AccountAction, account: string) => void;
+ account: string | undefined;
+ onRegister: () => void;
+
+}
+
+export function AccountList({ account, onAction, onRegister }: Props): VNode {
+ const result = useBusinessAccounts({ account });
+ const { i18n } = useTranslationContext();
+
+ if (result.loading) return
;
+ if (!result.ok) {
+ return handleNotOkResult(i18n, onRegister)(result);
+ }
+
+ const { customers } = result.data;
+ return
+ {!customers.length ? (
+
+ ) : (
+
+ {i18n.str`Accounts:`}
+
+
+ )}
+
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
new file mode 100644
index 000000000..90835d52b
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -0,0 +1,107 @@
+import { RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode, h, Fragment } from "preact";
+import { useAdminAccountAPI } from "../../hooks/circuit.js";
+import { useState } from "preact/hooks";
+import { buildRequestErrorMessage } from "../../utils.js";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { getRandomPassword } from "../rnd.js";
+import { AccountForm } from "./AccountForm.js";
+
+export function CreateNewAccount({
+ onClose,
+ onCreateSuccess,
+}: {
+ onClose: () => void;
+ onCreateSuccess: (password: string) => void;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const { createAccount } = useAdminAccountAPI();
+ const [submitAccount, setSubmitAccount] = useState<
+ SandboxBackend.Circuit.CircuitAccountData | undefined
+ >();
+ return (
+
+
+
+ New account
+
+
+
+
+
{
+ setSubmitAccount(a);
+ }}
+ />
+
+
+
+
+ {
+ e.preventDefault();
+ onClose();
+ }}
+ />
+
+
+ {
+ e.preventDefault();
+
+ if (!submitAccount) return;
+ try {
+ const account: SandboxBackend.Circuit.CircuitAccountRequest =
+ {
+ cashout_address: submitAccount.cashout_address,
+ contact_data: submitAccount.contact_data,
+ internal_iban: submitAccount.iban,
+ name: submitAccount.name,
+ username: submitAccount.username,
+ password: getRandomPassword(),
+ };
+
+ await createAccount(account);
+ onCreateSuccess(account.password);
+ } catch (error) {
+ if (error instanceof RequestError) {
+ notify(
+ buildRequestErrorMessage(i18n, error.cause, {
+ onClientError: (status) =>
+ status === HttpStatusCode.Forbidden
+ ? i18n.str`The rights to perform the operation are not sufficient`
+ : status === HttpStatusCode.BadRequest
+ ? i18n.str`Input data was invalid`
+ : status === HttpStatusCode.Conflict
+ ? i18n.str`At least one registration detail was not available`
+ : undefined,
+ }),
+ );
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
+ }
+ }}
+ />
+
+
+
+
+
+ );
+}
diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx b/packages/demobank-ui/src/pages/admin/Home.tsx
new file mode 100644
index 000000000..e1ec6cfe0
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/Home.tsx
@@ -0,0 +1,162 @@
+import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { useState } from "preact/hooks";
+import { Cashouts } from "../../components/Cashouts/index.js";
+import { ShowCashoutDetails } from "../business/Home.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { ShowAccountDetails } from "../ShowAccountDetails.js";
+import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
+import { AdminAccount } from "./Account.js";
+import { AccountList } from "./AccountList.js";
+import { CreateNewAccount } from "./CreateNewAccount.js";
+import { RemoveAccount } from "./RemoveAccount.js";
+
+/**
+ * Query account information and show QR code if there is pending withdrawal
+ */
+interface Props {
+ onRegister: () => void;
+}
+export type AccountAction = "show-details" |
+ "show-cashout" |
+ "update-password" |
+ "remove-account" |
+ "show-cashouts-details";
+
+export function AdminHome({ onRegister }: Props): VNode {
+ const [action, setAction] = useState<{
+ type: AccountAction,
+ account: string
+ }>()
+
+ const [createAccount, setCreateAccount] = useState(false);
+
+ const { i18n } = useTranslationContext();
+
+ if (action) {
+ switch (action.type) {
+ case "show-details": return {
+ setAction(undefined);
+ }}
+ />
+ case "show-cashout": return (
+
+
+
+ Cashout for account {action.account}
+
+
+
{
+ setAction({
+ type: "show-cashouts-details",
+ account: action.account
+ });
+ }}
+ />
+
+ {
+ e.preventDefault();
+ setAction(undefined);
+ }}
+ />
+
+
+ )
+ case "update-password": return {
+ notifyInfo(i18n.str`Password changed`);
+ setAction(undefined);
+ }}
+ onClear={() => {
+ setAction(undefined);
+ }}
+ />
+ case "remove-account": return {
+ notifyInfo(i18n.str`Account removed`);
+ setAction(undefined);
+ }}
+ onClear={() => {
+ setAction(undefined);
+ }}
+ />
+ case "show-cashouts-details": return {
+ setAction({
+ type: "update-password",
+ account: action.account,
+ })
+ }}
+ onUpdateSuccess={() => {
+ notifyInfo(i18n.str`Account updated`);
+ setAction(undefined);
+ }}
+ onClear={() => {
+ setAction(undefined);
+ }}
+ />
+ }
+ }
+
+ if (createAccount) {
+ return (
+ setCreateAccount(false)}
+ onCreateSuccess={(password) => {
+ notifyInfo(
+ i18n.str`Account created with password "${password}". The user must change the password on the next login.`,
+ );
+ setCreateAccount(false);
+ }}
+ />
+ );
+ }
+
+ return (
+
+
+
+ Admin panel
+
+
+
+
+
+
+
+ {
+ e.preventDefault();
+
+ setCreateAccount(true);
+ }}
+ />
+
+
+
+
+
+
+ setAction({account, type})} onRegister={onRegister}/>
+
+
+ );
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
new file mode 100644
index 000000000..2900db9d2
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -0,0 +1,112 @@
+import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode,h,Fragment } from "preact";
+import { useAccountDetails } from "../../hooks/access.js";
+import { useAdminAccountAPI } from "../../hooks/circuit.js";
+import { Amounts, HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { buildRequestErrorMessage } from "../../utils.js";
+
+export function RemoveAccount({
+ account,
+ onClear,
+ onUpdateSuccess,
+ onLoadNotOk,
+ }: {
+ onLoadNotOk: (
+ error: HttpResponsePaginated,
+ ) => VNode;
+ onClear: () => void;
+ onUpdateSuccess: () => void;
+ account: string;
+ }): VNode {
+ const { i18n } = useTranslationContext();
+ const result = useAccountDetails(account);
+ const { deleteAccount } = useAdminAccountAPI();
+
+ if (!result.ok) {
+ if (result.loading || result.type === ErrorType.TIMEOUT) {
+ return onLoadNotOk(result);
+ }
+ if (result.status === HttpStatusCode.NotFound) {
+ return account not found
;
+ }
+ return onLoadNotOk(result);
+ }
+
+ const balance = Amounts.parse(result.data.balance.amount);
+ if (!balance) {
+ return there was an error reading the balance
;
+ }
+ const isBalanceEmpty = Amounts.isZero(balance);
+ return (
+
+
+
+ Remove account: {account}
+
+
+ {/* {FXME: SHOW WARNING} */}
+ {/* {!isBalanceEmpty && (
+
saveError(undefined)}
+ />
+ )} */}
+
+
+
+
+
+ );
+ }
+
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx
new file mode 100644
index 000000000..8beea640a
--- /dev/null
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -0,0 +1,757 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 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
+ */
+import {
+ AmountJson,
+ Amounts,
+ HttpStatusCode,
+ TranslatedString
+} from "@gnu-taler/taler-util";
+import {
+ HttpResponse,
+ HttpResponsePaginated,
+ RequestError,
+ notify,
+ notifyError,
+ notifyInfo,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import { Cashouts } from "../../components/Cashouts/index.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { useBackendContext } from "../../context/backend.js";
+import { useAccountDetails } from "../../hooks/access.js";
+import {
+ useCashoutDetails,
+ useCircuitAccountAPI,
+ useEstimator,
+ useRatiosAndFeeConfig,
+} from "../../hooks/circuit.js";
+import {
+ TanChannel,
+ buildRequestErrorMessage,
+ undefinedIfEmpty,
+} from "../../utils.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { LoginForm } from "../LoginForm.js";
+import { Amount } from "../PaytoWireTransferForm.js";
+import { ShowAccountDetails } from "../ShowAccountDetails.js";
+import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
+
+interface Props {
+ account: string,
+ onClose: () => void;
+ onRegister: () => void;
+ onLoadNotOk: () => void;
+}
+export function BusinessAccount({
+ onClose,
+ account,
+ onLoadNotOk,
+ onRegister,
+}: Props): VNode {
+ const { i18n } = useTranslationContext();
+ const [updatePassword, setUpdatePassword] = useState(false);
+ const [newCashout, setNewcashout] = useState(false);
+ const [showCashoutDetails, setShowCashoutDetails] = useState<
+ string | undefined
+ >();
+
+
+ if (newCashout) {
+ return (
+ {
+ setNewcashout(false);
+ }}
+ onComplete={(id) => {
+ notifyInfo(
+ i18n.str`Cashout created. You need to confirm the operation to complete the transaction.`,
+ );
+ setNewcashout(false);
+ setShowCashoutDetails(id);
+ }}
+ />
+ );
+ }
+ if (showCashoutDetails) {
+ return (
+ {
+ setShowCashoutDetails(undefined);
+ }}
+ />
+ );
+ }
+ if (updatePassword) {
+ return (
+ {
+ notifyInfo(i18n.str`Password changed`);
+ setUpdatePassword(false);
+ }}
+ onClear={() => {
+ setUpdatePassword(false);
+ }}
+ />
+ );
+ }
+ return (
+
+
{
+ notifyInfo(i18n.str`Account updated`);
+ }}
+ onChangePassword={() => {
+ setUpdatePassword(true);
+ }}
+ onClear={onClose}
+ />
+
+
+
{i18n.str`Latest cashouts`}
+ {
+ setShowCashoutDetails(id);
+ }}
+ />
+
+
+
+
+
{
+ e.preventDefault();
+ setNewcashout(true);
+ }}
+ />
+
+
+
+ );
+}
+
+interface PropsCashout {
+ account: string;
+ onComplete: (id: string) => void;
+ onCancel: () => void;
+ onLoadNotOk: (
+ error:
+ | HttpResponsePaginated
+ | HttpResponse,
+ ) => VNode;
+}
+
+type FormType = {
+ isDebit: boolean;
+ amount: string;
+ subject: string;
+ channel: TanChannel;
+};
+type ErrorFrom = {
+ [P in keyof T]+?: string;
+};
+
+// check #7719
+function useRatiosAndFeeConfigWithChangeDetection(): HttpResponse<
+ SandboxBackend.Circuit.Config & { hasChanged?: boolean },
+ SandboxBackend.SandboxError
+> {
+ const result = useRatiosAndFeeConfig();
+ const [oldResult, setOldResult] = useState<
+ SandboxBackend.Circuit.Config | undefined
+ >(undefined);
+ const dataFromBackend = result.ok ? result.data : undefined;
+ useEffect(() => {
+ // save only the first result of /config to the backend
+ if (!dataFromBackend || oldResult !== undefined) return;
+ setOldResult(dataFromBackend);
+ }, [dataFromBackend]);
+
+ if (!result.ok) return result;
+
+ const data = !oldResult ? result.data : oldResult;
+ const hasChanged =
+ oldResult &&
+ (result.data.name !== oldResult.name ||
+ result.data.version !== oldResult.version ||
+ result.data.ratios_and_fees.buy_at_ratio !==
+ oldResult.ratios_and_fees.buy_at_ratio ||
+ result.data.ratios_and_fees.buy_in_fee !==
+ oldResult.ratios_and_fees.buy_in_fee ||
+ result.data.ratios_and_fees.sell_at_ratio !==
+ oldResult.ratios_and_fees.sell_at_ratio ||
+ result.data.ratios_and_fees.sell_out_fee !==
+ oldResult.ratios_and_fees.sell_out_fee ||
+ result.data.fiat_currency !== oldResult.fiat_currency);
+
+ return {
+ ...result,
+ data: { ...data, hasChanged },
+ };
+}
+
+function CreateCashout({
+ account,
+ onComplete,
+ onCancel,
+ onLoadNotOk,
+}: PropsCashout): VNode {
+ const { i18n } = useTranslationContext();
+ const ratiosResult = useRatiosAndFeeConfig();
+ const result = useAccountDetails(account);
+ const {
+ estimateByCredit: calculateFromCredit,
+ estimateByDebit: calculateFromDebit,
+ } = useEstimator();
+ const [form, setForm] = useState>({ isDebit: true });
+
+ const { createCashout } = useCircuitAccountAPI();
+ if (!result.ok) return onLoadNotOk(result);
+ if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
+ const config = ratiosResult.data;
+
+ const balance = Amounts.parseOrThrow(result.data.balance.amount);
+ const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
+ const zero = Amounts.zeroOfCurrency(balance.currency);
+ const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
+ const limit = balanceIsDebit
+ ? Amounts.sub(debitThreshold, balance).amount
+ : Amounts.add(balance, debitThreshold).amount;
+
+ const zeroCalc = { debit: zero, credit: zero, beforeFee: zero };
+ const [calc, setCalc] = useState(zeroCalc);
+ const sellRate = config.ratios_and_fees.sell_at_ratio;
+ const sellFee = !config.ratios_and_fees.sell_out_fee
+ ? zero
+ : Amounts.parseOrThrow(
+ `${balance.currency}:${config.ratios_and_fees.sell_out_fee}`,
+ );
+ const fiatCurrency = config.fiat_currency;
+
+ if (!sellRate || sellRate < 0) return error rate
;
+
+ const amount = Amounts.parseOrThrow(
+ `${!form.isDebit ? fiatCurrency : balance.currency}:${
+ !form.amount ? "0" : form.amount
+ }`,
+ );
+
+ useEffect(() => {
+ if (form.isDebit) {
+ calculateFromDebit(amount, sellFee, sellRate)
+ .then((r) => {
+ setCalc(r);
+ })
+ .catch((error) => {
+ notify(
+ error instanceof RequestError
+ ? buildRequestErrorMessage(i18n, error.cause)
+ : {
+ type: "error",
+ title: i18n.str`Could not estimate the cashout`,
+ description: error.message as TranslatedString
+ },
+ );
+ });
+ } else {
+ calculateFromCredit(amount, sellFee, sellRate)
+ .then((r) => {
+ setCalc(r);
+ })
+ .catch((error) => {
+ notify(
+ error instanceof RequestError
+ ? buildRequestErrorMessage(i18n, error.cause)
+ : {
+ type: "error",
+ title: i18n.str`Could not estimate the cashout`,
+ description: error.message,
+ },
+ );
+ });
+ }
+ }, [form.amount, form.isDebit]);
+
+ const balanceAfter = Amounts.sub(balance, calc.debit).amount;
+
+ function updateForm(newForm: typeof form): void {
+ setForm(newForm);
+ }
+ const errors = undefinedIfEmpty>({
+ amount: !form.amount
+ ? i18n.str`required`
+ : !amount
+ ? i18n.str`could not be parsed`
+ : Amounts.cmp(limit, calc.debit) === -1
+ ? i18n.str`balance is not enough`
+ : Amounts.cmp(calc.beforeFee, sellFee) === -1
+ ? i18n.str`the total amount to transfer does not cover the fees`
+ : Amounts.isZero(calc.credit)
+ ? i18n.str`the total transfer at destination will be zero`
+ : undefined,
+ channel: !form.channel ? i18n.str`required` : undefined,
+ });
+
+ return (
+
+ );
+}
+
+interface ShowCashoutProps {
+ id: string;
+ onCancel: () => void;
+ onLoadNotOk: (
+ error: HttpResponsePaginated,
+ ) => VNode;
+}
+export function ShowCashoutDetails({
+ id,
+ onCancel,
+ onLoadNotOk,
+}: ShowCashoutProps): VNode {
+ const { i18n } = useTranslationContext();
+ const result = useCashoutDetails(id);
+ const { abortCashout, confirmCashout } = useCircuitAccountAPI();
+ const [code, setCode] = useState(undefined);
+ if (!result.ok) return onLoadNotOk(result);
+ const errors = undefinedIfEmpty({
+ code: !code ? i18n.str`required` : undefined,
+ });
+ const isPending = String(result.data.status).toUpperCase() === "PENDING";
+ return (
+
+
Cashout details {id}
+
+
+
+
{
+ e.preventDefault();
+ onCancel();
+ }}
+ >
+ {i18n.str`Back`}
+
+ {isPending ? (
+
+ {
+ e.preventDefault();
+ try {
+ await abortCashout(id);
+ onCancel();
+ } catch (error) {
+ if (error instanceof RequestError) {
+ notify(
+ buildRequestErrorMessage(i18n, error.cause, {
+ onClientError: (status) =>
+ status === HttpStatusCode.NotFound
+ ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
+ : status === HttpStatusCode.PreconditionFailed
+ ? i18n.str`Cashout was already confimed`
+ : undefined,
+ }),
+ );
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
+ }
+ }}
+ >
+ {i18n.str`Abort`}
+
+
+ {
+ e.preventDefault();
+ try {
+ if (!code) return;
+ const rest = await confirmCashout(id, {
+ tan: code,
+ });
+ } catch (error) {
+ if (error instanceof RequestError) {
+ notify(
+ buildRequestErrorMessage(i18n, error.cause, {
+ onClientError: (status) =>
+ status === HttpStatusCode.NotFound
+ ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
+ : status === HttpStatusCode.PreconditionFailed
+ ? i18n.str`Cashout was already confimed`
+ : status === HttpStatusCode.Conflict
+ ? i18n.str`Confirmation failed. Maybe the user changed their cash-out address between the creation and the confirmation`
+ : status === HttpStatusCode.Forbidden
+ ? i18n.str`Invalid code`
+ : undefined,
+ }),
+ );
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
+ }
+ }}
+ >
+ {i18n.str`Confirm`}
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+const MAX_AMOUNT_DIGIT = 2;
+/**
+ * Truncate the amount of digits to display
+ * in the form based on the fee calculations
+ *
+ * Backend must have the same truncation
+ * @param a
+ * @returns
+ */
+function truncate(a: AmountJson): AmountJson {
+ const str = Amounts.stringify(a);
+ const idx = str.indexOf(".");
+ if (idx === -1) {
+ return a;
+ }
+ const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT);
+ return Amounts.parseOrThrow(truncated);
+}
+
+export function assertUnreachable(x: never): never {
+ throw new Error("Didn't expect to get here");
+}
--
cgit v1.2.3
From 56a6f47c7daae088c2017c0d9781ddcf7cee175b Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Thu, 21 Sep 2023 15:44:17 -0300
Subject: more ui
---
packages/demobank-ui/src/components/Routing.tsx | 12 +-
packages/demobank-ui/src/hooks/settings.ts | 8 +-
.../demobank-ui/src/pages/AccountPage/views.tsx | 50 ++++++
packages/demobank-ui/src/pages/BankFrame.tsx | 133 ++++++++-------
packages/demobank-ui/src/pages/HomePage.tsx | 20 +--
packages/demobank-ui/src/pages/PaymentOptions.tsx | 137 +++++++--------
packages/demobank-ui/src/pages/QrCodeSection.tsx | 106 ++++++++++++
.../demobank-ui/src/pages/WalletWithdrawForm.tsx | 183 +++++++++++----------
.../demobank-ui/src/pages/WithdrawalQRCode.tsx | 159 +++++++++++++++---
.../demobank-ui/src/pages/admin/RemoveAccount.tsx | 2 +-
10 files changed, 553 insertions(+), 257 deletions(-)
(limited to 'packages/demobank-ui/src/pages/WithdrawalQRCode.tsx')
diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx
index b8e39948b..e1fd93737 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -76,9 +76,9 @@ export function Routing(): VNode {
onContinue={() => {
route("/account");
}}
- onLoadNotOk={() => {
- route("/account");
- }}
+ // onLoadNotOk={() => {
+ // route("/account");
+ // }}
/>
)}
/>
@@ -108,9 +108,9 @@ export function Routing(): VNode {
} else {
return {
- route(`/operation/${wopid}`);
- }}
+ // onPendingOperationFound={(wopid) => {
+ // route(`/operation/${wopid}`);
+ // }}
goToBusinessAccount={() => {
route("/business");
}}
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index 43e803726..c2fd93a0c 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -29,20 +29,26 @@ import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
interface Settings {
currentWithdrawalOperationId: string | undefined;
showWithdrawalSuccess: boolean;
+ showDemoDescription: boolean;
maxWithdrawalAmount: number;
+ fastWithdrawal: boolean;
}
export const codecForSettings = (): Codec =>
buildCodecForObject()
.property("currentWithdrawalOperationId", codecOptional(codecForString()))
.property("showWithdrawalSuccess", (codecForBoolean()))
+ .property("showDemoDescription", (codecForBoolean()))
+ .property("fastWithdrawal", (codecForBoolean()))
.property("maxWithdrawalAmount", codecForNumber())
.build("Settings");
const defaultSettings: Settings = {
currentWithdrawalOperationId: undefined,
showWithdrawalSuccess: true,
- maxWithdrawalAmount: 25
+ showDemoDescription: true,
+ maxWithdrawalAmount: 25,
+ fastWithdrawal: false,
};
const DEMOBANK_SETTINGS_KEY = buildStorageKey(
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index abd14848f..0187989af 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -23,6 +23,7 @@ import { State } from "./index.js";
import { CopyButton } from "../../components/CopyButton.js";
import { bankUiSettings } from "../../settings.js";
import { useBusinessAccountDetails } from "../../hooks/circuit.js";
+import { useSettings } from "../../hooks/settings.js";
export function InvalidIbanView({ error }: State.InvalidIban) {
return (
@@ -78,9 +79,58 @@ function ImportantMessage(): VNode {
}
+function ShowDemoInfo():VNode {
+ const { i18n } = useTranslationContext();
+ const [settings, updateSettings] = useSettings();
+ if (!settings.showDemoDescription) return
+ return
+
+
+
+
+ This is a demo bank!
+
+
+
+
+ This part of the demo shows how a bank that supports Taler
+ directly would work. In addition to using your own bank
+ account, you can also see the transaction history of some{" "}
+ Public Accounts .
+
+
+
+ {
+ e.preventDefault();
+ updateSettings("showDemoDescription", false);
+ }}
+ >
+ Close
+
+
+
+
+
+
+
+
+
+
+}
+
export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> {
+ const { i18n } = useTranslationContext();
+
return
+
+
+
;
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index 4b23686d6..d1c94135b 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -27,7 +27,6 @@ import { CopyButton, CopyIcon } from "../components/CopyButton.js";
import logo from "../assets/logo-2021.svg";
import { useAccountDetails } from "../hooks/access.js";
-const IS_PUBLIC_ACCOUNT_ENABLED = false;
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
@@ -142,24 +141,40 @@ export function BankFrame({
@@ -240,11 +269,6 @@ export function BankFrame({
// >
// {i18n.str`Skip to main content`}
//
- //
// {maybeDemoContent(
//
// {IS_PUBLIC_ACCOUNT_ENABLED ? (
@@ -298,33 +322,6 @@ export function BankFrame({
);
}
-// function maybeDemoContent(content: VNode): VNode {
-// if (bankUiSettings.showDemoNav) {
-// return content;
-// }
-// return ;
-// }
-
-// export function ErrorBannerFloat({
-// error,
-// onClear,
-// }: {
-// error: ErrorMessage;
-// onClear?: () => void;
-// }): VNode {
-// return (
-//
-//
-//
-// );
-// }
function StatusBanner(): VNode {
const notifs = useNotifications()
@@ -469,5 +466,5 @@ function AccountBalance({ account }: { account: string }): VNode {
{Amounts.currencyOf(result.data.balance.amount)}
{result.data.balance.credit_debit_indicator === "debit" ? "-" : ""}
{Amounts.stringifyValue(result.data.balance.amount)}
-
+
}
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index 40cc147a6..2acfc9b57 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -52,21 +52,21 @@ const logger = new Logger("AccountPage");
export function HomePage({
onRegister,
account,
- onPendingOperationFound,
+ // onPendingOperationFound,
goToBusinessAccount,
}: {
account: string,
- onPendingOperationFound: (id: string) => void;
+ // onPendingOperationFound: (id: string) => void;
onRegister: () => void;
goToBusinessAccount: () => void;
}): VNode {
const [settings] = useSettings();
const { i18n } = useTranslationContext();
- if (settings.currentWithdrawalOperationId) {
- onPendingOperationFound(settings.currentWithdrawalOperationId);
- return
;
- }
+ // if (settings.currentWithdrawalOperationId) {
+ // onPendingOperationFound(settings.currentWithdrawalOperationId);
+ // return
;
+ // }
return (
void;
onContinue: () => void;
}): VNode {
//FIXME: libeufin sandbox should return show to create the integration api endpoint
@@ -95,6 +93,7 @@ export function WithdrawalOperationPage({
});
const parsedUri = parseWithdrawUri(uri);
const { i18n } = useTranslationContext();
+ const [settings, updateSettings] = useSettings();
if (!parsedUri) {
notifyError(
@@ -107,8 +106,9 @@ export function WithdrawalOperationPage({
return (
{
+ updateSettings("currentWithdrawalOperationId", undefined)
+ }}
/>
);
}
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 1728074a3..573f8c769 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -34,80 +34,83 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
// const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined);
return (
-
-
- Send money to
-
+
-
- {/* */}
-
- {
- setTab("charge-wallet")
- }} />
-
- 💵
-
-
- a Taler wallet
-
-
- Withdraw digital money into your mobile wallet or browser extension
+
+
+ Send money to
+
+
+
- {tab === "charge-wallet" && (
- {
- updateSettings("currentWithdrawalOperationId", id);
- }}
- onCancel={() => {
- setTab(undefined)
- }}
- />
- )}
- {tab === "wire-transfer" && (
- {
- notifyInfo(i18n.str`Wire transfer created!`);
- }}
- onCancel={() => {
- setTab(undefined)
- }}
- />
- )}
+
+
+
+
+
+ {tab === "charge-wallet" && (
+
{
+ updateSettings("currentWithdrawalOperationId", id);
+ }}
+ onCancel={() => {
+ setTab(undefined)
+ }}
+ />
+ )}
+ {tab === "wire-transfer" && (
+ {
+ notifyInfo(i18n.str`Wire transfer created!`);
+ }}
+ onCancel={() => {
+ setTab(undefined)
+ }}
+ />
+ )}
-
+
+
)
}
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 7c1b3bdc5..416c714e2 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -135,3 +135,109 @@ export function QrCodeSection({
);
}
+
+
+export function QrCodeSectionSimpler({
+ withdrawUri,
+ onAborted,
+}: {
+ withdrawUri: WithdrawUriResult;
+ onAborted: () => void;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ useEffect(() => {
+ //Taler Wallet WebExtension is listening to headers response and tab updates.
+ //In the SPA there is no header response with the Taler URI so
+ //this hack manually triggers the tab update after the QR is in the DOM.
+ // WebExtension will be using
+ // https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
+ document.title = `${document.title} ${withdrawUri.withdrawalOperationId}`;
+ }, []);
+ const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
+
+ const { abortWithdrawal } = useAccessAnonAPI();
+
+ async function doAbort() {
+ try {
+ await abortWithdrawal(withdrawUri.withdrawalOperationId);
+ onAborted();
+ } catch (error) {
+ if (error instanceof RequestError) {
+ notify(
+ buildRequestErrorMessage(i18n, error.cause, {
+ onClientError: (status) =>
+ status === HttpStatusCode.Conflict
+ ? i18n.str`The reserve operation has been confirmed previously and can't be aborted`
+ : undefined,
+ }),
+ );
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
+ }
+ }
+
+ return (
+
+
+
+
+ If you have a Taler wallet installed in this device
+
+
+
+
+ You will see the details of the operation in your wallet including the fees (if applies).
+ If you still one you can install it from here .
+
+
+
+
+
+ Cancel withdrawal
+
+
+
+
+
+
+
+
+ Or if you have the wallet in another device
+
+
+ Scan the QR below to start the withdrawal
+
+
+
+
+
+
+
+ Cancel withdrawal
+
+
+
+
+
+ );
+}
+
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 8c41f7576..08f706919 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -20,6 +20,7 @@ import {
HttpStatusCode,
Logger,
TranslatedString,
+ WithdrawUriResult,
parseWithdrawUri,
} from "@gnu-taler/taler-util";
import {
@@ -34,6 +35,9 @@ import { useEffect, useRef, useState } from "preact/hooks";
import { useAccessAPI } from "../hooks/access.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
import { Amount } from "./PaytoWireTransferForm.js";
+import { useSettings } from "../hooks/settings.js";
+import { WithdrawalOperationState } from "./WithdrawalQRCode.js";
+import { Loading } from "../components/Loading.js";
const logger = new Logger("WalletWithdrawForm");
const RefAmount = forwardRef(Amount);
@@ -51,8 +55,9 @@ export function WalletWithdrawForm({
}): VNode {
const { i18n } = useTranslationContext();
const { createWithdrawal } = useAccessAPI();
+ const [settings, updateSettings] = useSettings()
- const [amountStr, setAmountStr] = useState("5.00");
+ const [amountStr, setAmountStr] = useState(`${settings.maxWithdrawalAmount}`);
const ref = useRef(null);
useEffect(() => {
if (focus) ref.current?.focus();
@@ -78,7 +83,6 @@ export function WalletWithdrawForm({
async function doStart() {
if (!parsedAmount) return;
try {
- console.log("ASDASD")
const result = await createWithdrawal({
amount: Amounts.stringify(parsedAmount),
});
@@ -109,7 +113,6 @@ export function WalletWithdrawForm({
)
}
}
-
}
return (
@@ -122,91 +125,103 @@ export function WalletWithdrawForm({
After using your wallet you will be redirected here to confirm or cancel the operation.
-
--
cgit v1.2.3
From a59df74fb2b4374fd58f68fd4abaffe623cd54d6 Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Fri, 22 Sep 2023 15:29:19 -0300
Subject: more ui
---
packages/demobank-ui/src/components/Routing.tsx | 27 +-
packages/demobank-ui/src/hooks/settings.ts | 3 +
.../demobank-ui/src/pages/AccountPage/index.ts | 2 +
.../demobank-ui/src/pages/AccountPage/state.ts | 3 +-
.../demobank-ui/src/pages/AccountPage/views.tsx | 4 +-
packages/demobank-ui/src/pages/BankFrame.tsx | 15 +
packages/demobank-ui/src/pages/HomePage.tsx | 17 +-
.../demobank-ui/src/pages/OperationState/index.ts | 10 +-
.../demobank-ui/src/pages/OperationState/state.ts | 109 ++++++-
.../demobank-ui/src/pages/OperationState/views.tsx | 363 ++++++++++++++++++++-
packages/demobank-ui/src/pages/PaymentOptions.tsx | 17 +-
packages/demobank-ui/src/pages/QrCodeSection.tsx | 104 ------
.../demobank-ui/src/pages/WalletWithdrawForm.tsx | 273 ++++++++++------
.../demobank-ui/src/pages/WithdrawalQRCode.tsx | 163 +--------
packages/demobank-ui/src/pages/business/Home.tsx | 1 -
15 files changed, 693 insertions(+), 418 deletions(-)
(limited to 'packages/demobank-ui/src/pages/WithdrawalQRCode.tsx')
diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx
index e1fd93737..90d2d4c48 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -45,6 +45,20 @@ export function Routing(): VNode {
/>
)}
/>
+ (
+ {
+ route("/account");
+ }}
+ // onLoadNotOk={() => {
+ // route("/account");
+ // }}
+ />
+ )}
+ />
(
@@ -64,10 +78,6 @@ export function Routing(): VNode {
return (
-
(
@@ -76,9 +86,6 @@ export function Routing(): VNode {
onContinue={() => {
route("/account");
}}
- // onLoadNotOk={() => {
- // route("/account");
- // }}
/>
)}
/>
@@ -108,9 +115,9 @@ export function Routing(): VNode {
} else {
return {
- // route(`/operation/${wopid}`);
- // }}
+ goToConfirmOperation={(wopid) => {
+ route(`/operation/${wopid}`);
+ }}
goToBusinessAccount={() => {
route("/business");
}}
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index c2fd93a0c..5f004c6d4 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -30,6 +30,7 @@ interface Settings {
currentWithdrawalOperationId: string | undefined;
showWithdrawalSuccess: boolean;
showDemoDescription: boolean;
+ showInstallWallet: boolean;
maxWithdrawalAmount: number;
fastWithdrawal: boolean;
}
@@ -39,6 +40,7 @@ export const codecForSettings = (): Codec =>
.property("currentWithdrawalOperationId", codecOptional(codecForString()))
.property("showWithdrawalSuccess", (codecForBoolean()))
.property("showDemoDescription", (codecForBoolean()))
+ .property("showInstallWallet", (codecForBoolean()))
.property("fastWithdrawal", (codecForBoolean()))
.property("maxWithdrawalAmount", codecForNumber())
.build("Settings");
@@ -47,6 +49,7 @@ const defaultSettings: Settings = {
currentWithdrawalOperationId: undefined,
showWithdrawalSuccess: true,
showDemoDescription: true,
+ showInstallWallet: true,
maxWithdrawalAmount: 25,
fastWithdrawal: false,
};
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts
index 128a6d30f..81eeb4a03 100644
--- a/packages/demobank-ui/src/pages/AccountPage/index.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -29,6 +29,7 @@ export interface Props {
error: HttpResponsePaginated,
) => VNode;
goToBusinessAccount: () => void;
+ goToConfirmOperation: (id:string) => void;
}
export type State = State.Loading | State.LoadingError | State.Ready | State.InvalidIban | State.UserNotFound;
@@ -54,6 +55,7 @@ export namespace State {
account: string,
limit: AmountJson,
goToBusinessAccount: () => void;
+ goToConfirmOperation: (id:string) => void;
}
export interface InvalidIban {
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts
index a57e19901..1a1475c0d 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -20,7 +20,7 @@ import { useBackendContext } from "../../context/backend.js";
import { useAccountDetails } from "../../hooks/access.js";
import { Props, State } from "./index.js";
-export function useComponentState({ account, goToBusinessAccount }: Props): State {
+export function useComponentState({ account, goToBusinessAccount, goToConfirmOperation }: Props): State {
const result = useAccountDetails(account);
const backend = useBackendContext();
const { i18n } = useTranslationContext();
@@ -75,6 +75,7 @@ export function useComponentState({ account, goToBusinessAccount }: Props): Stat
return {
status: "ready",
goToBusinessAccount,
+ goToConfirmOperation,
error: undefined,
account,
limit,
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 0187989af..23a815bd8 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -123,7 +123,7 @@ function ShowDemoInfo():VNode {
}
-export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> {
+export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> {
const { i18n } = useTranslationContext();
return
@@ -131,7 +131,7 @@ export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready):
-
+
;
}
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index d1c94135b..5bfaa63ec 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -206,6 +206,21 @@ export function BankFrame({
+
+
+
+
+ Show install wallet first
+
+
+ {
+ updateSettings("showInstallWallet", !settings.showInstallWallet);
+ }}>
+
+
+
+
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index 2acfc9b57..8d5e1f3b9 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -36,6 +36,7 @@ import { useSettings } from "../hooks/settings.js";
import { AccountPage } from "./AccountPage/index.js";
import { LoginForm } from "./LoginForm.js";
import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
+import { route } from "preact-router";
const logger = new Logger("AccountPage");
@@ -52,25 +53,20 @@ const logger = new Logger("AccountPage");
export function HomePage({
onRegister,
account,
- // onPendingOperationFound,
+ goToConfirmOperation,
goToBusinessAccount,
}: {
account: string,
- // onPendingOperationFound: (id: string) => void;
onRegister: () => void;
goToBusinessAccount: () => void;
+ goToConfirmOperation: (id:string) => void;
}): VNode {
- const [settings] = useSettings();
const { i18n } = useTranslationContext();
- // if (settings.currentWithdrawalOperationId) {
- // onPendingOperationFound(settings.currentWithdrawalOperationId);
- // return ;
- // }
-
return (
@@ -102,12 +98,13 @@ export function WithdrawalOperationPage({
);
return ;
}
-
+
return (
{
updateSettings("currentWithdrawalOperationId", undefined)
+ onContinue()
}}
/>
);
@@ -178,7 +175,7 @@ export function handleNotOkResult(
assertUnreachable(result);
}
}
-
+ route("/")
return error
;
}
return
;
diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts b/packages/demobank-ui/src/pages/OperationState/index.ts
index 254fcba5f..32302f272 100644
--- a/packages/demobank-ui/src/pages/OperationState/index.ts
+++ b/packages/demobank-ui/src/pages/OperationState/index.ts
@@ -26,6 +26,7 @@ import { ErrorLoading } from "../../components/ErrorLoading.js";
export interface Props {
currency: string;
onClose: () => void;
+ goToConfirmOperation: (id: string) => void;
}
export type State = State.Loading |
@@ -57,26 +58,33 @@ export namespace State {
error: undefined;
uri: WithdrawUriResult,
onClose: () => void;
+ onAbort: () => void;
}
export interface InvalidPayto {
status: "invalid-payto",
error: undefined;
payto: string | null;
+ onClose: () => void;
}
export interface InvalidWithdrawal {
status: "invalid-withdrawal",
error: undefined;
+ onClose: () => void;
uri: string,
}
export interface InvalidReserve {
status: "invalid-reserve",
error: undefined;
+ onClose: () => void;
reserve: string | null;
}
export interface NeedConfirmation {
status: "need-confirmation",
+ onAbort: () => void;
+ onConfirm: () => void;
error: undefined;
+ busy: boolean,
}
export interface Aborted {
status: "aborted",
@@ -111,7 +119,7 @@ const viewMapping: utils.StateViewMap = {
ready: ReadyView,
};
-export const AccountPage = utils.compose(
+export const OperationState = utils.compose(
(p: Props) => useComponentState(p),
viewMapping,
);
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
index 6fb7bb28f..ae03ed529 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -15,21 +15,24 @@
*/
import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
-import { ErrorType, RequestError, notify, notifyError, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
+import { ErrorType, RequestError, notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
import { useBackendContext } from "../../context/backend.js";
-import { useAccessAPI, useAccountDetails, useWithdrawalDetails } from "../../hooks/access.js";
+import { useAccessAPI, useAccessAnonAPI, useAccountDetails, useWithdrawalDetails } from "../../hooks/access.js";
import { Props, State } from "./index.js";
import { useSettings } from "../../hooks/settings.js";
-import { buildRequestErrorMessage } from "../../utils.js";
-import { useEffect } from "preact/hooks";
+import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
+import { useEffect, useMemo, useState } from "preact/hooks";
import { getInitialBackendBaseURL } from "../../hooks/backend.js";
-export function useComponentState({ currency, onClose }: Props): utils.RecursiveState {
+export function useComponentState({ currency, onClose,goToConfirmOperation }: Props): utils.RecursiveState {
const { i18n } = useTranslationContext();
const [settings, updateSettings] = useSettings()
const { createWithdrawal } = useAccessAPI();
+ const { abortWithdrawal, confirmWithdrawal } = useAccessAnonAPI();
+ const [busy, setBusy] = useState>()
const amount = settings.maxWithdrawalAmount
+
async function doSilentStart() {
//FIXME: if amount is not enough use balance
const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
@@ -67,12 +70,14 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
}
}
+ const withdrawalOperationId = settings.currentWithdrawalOperationId
useEffect(() => {
- doSilentStart()
+ if (withdrawalOperationId === undefined) {
+ doSilentStart()
+ }
}, [settings.fastWithdrawal, amount])
const baseUrl = getInitialBackendBaseURL()
- const withdrawalOperationId = settings.currentWithdrawalOperationId
if (!withdrawalOperationId) {
return {
@@ -81,6 +86,63 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
}
}
+ const wid = withdrawalOperationId
+
+ async function doAbort() {
+ try {
+ setBusy({})
+ await abortWithdrawal(wid);
+ onClose();
+ } catch (error) {
+ if (error instanceof RequestError) {
+ notify(
+ buildRequestErrorMessage(i18n, error.cause, {
+ onClientError: (status) =>
+ status === HttpStatusCode.Conflict
+ ? i18n.str`The reserve operation has been confirmed previously and can't be aborted`
+ : undefined,
+ }),
+ );
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
+ }
+ setBusy(undefined)
+ }
+
+ async function doConfirm() {
+ try {
+ setBusy({})
+ await confirmWithdrawal(wid);
+ notifyInfo(i18n.str`Wire transfer completed!`)
+ } catch (error) {
+ if (error instanceof RequestError) {
+ notify(
+ buildRequestErrorMessage(i18n, error.cause, {
+ onClientError: (status) =>
+ status === HttpStatusCode.Conflict
+ ? i18n.str`The withdrawal has been aborted previously and can't be confirmed`
+ : status === HttpStatusCode.UnprocessableEntity
+ ? i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`
+ : undefined,
+ }),
+ );
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
+ }
+ setBusy(undefined)
+ }
const bankIntegrationApiBaseUrl = `${baseUrl}/integration-api`
const uri = stringifyWithdrawUri({
bankIntegrationApiBaseUrl,
@@ -92,11 +154,13 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
status: "invalid-withdrawal",
error: undefined,
uri,
+ onClose,
}
}
return (): utils.RecursiveState => {
const result = useWithdrawalDetails(withdrawalOperationId);
+
if (!result.ok) {
if (result.loading) {
return {
@@ -119,10 +183,17 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
}
if (data.confirmation_done) {
+ if (!settings.showWithdrawalSuccess) {
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose()
+ }
return {
status: "confirmed",
error: undefined,
- onClose,
+ onClose: async () => {
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose()
+ },
}
}
@@ -131,7 +202,12 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
status: "ready",
error: undefined,
uri: parsedUri,
- onClose
+ onClose: async () => {
+ await doAbort()
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose()
+ },
+ onAbort: doAbort,
}
}
@@ -139,7 +215,8 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
return {
status: "invalid-reserve",
error: undefined,
- reserve: data.selected_reserve_pub
+ reserve: data.selected_reserve_pub,
+ onClose,
}
}
@@ -149,13 +226,23 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
return {
status: "invalid-payto",
error: undefined,
- payto: data.selected_exchange_account
+ payto: data.selected_exchange_account,
+ onClose,
}
}
+
+ // goToConfirmOperation(withdrawalOperationId)
return {
status: "need-confirmation",
error: undefined,
+ onAbort: async () => {
+ await doAbort()
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose()
+ },
+ busy: !!busy,
+ onConfirm: doConfirm
}
}
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx
index db25eaf61..17f1d8457 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see
*/
-import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util";
+import { Amounts, stringifyPaytoUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { Transactions } from "../../components/Transactions/index.js";
@@ -24,42 +24,375 @@ import { CopyButton } from "../../components/CopyButton.js";
import { bankUiSettings } from "../../settings.js";
import { useBusinessAccountDetails } from "../../hooks/circuit.js";
import { useSettings } from "../../hooks/settings.js";
+import { useEffect, useMemo, useState } from "preact/hooks";
+import { undefinedIfEmpty } from "../../utils.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { QR } from "../../components/QR.js";
-export function InvalidPaytoView({ error }: State.InvalidPayto) {
+export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) {
return (
- Payto from server is not valid "{error.data.paytoUri}"
+ Payto from server is not valid "{payto}"
);
}
-export function InvalidWithdrawalView({ error }: State.InvalidWithdrawal) {
+export function InvalidWithdrawalView({ uri, onClose }: State.InvalidWithdrawal) {
return (
- Payto from server is not valid "{error.data.paytoUri}"
+ Withdrawal uri from server is not valid "{uri}"
);
}
-export function InvalidReserveView({ error }: State.InvalidReserve) {
+export function InvalidReserveView({ reserve, onClose }: State.InvalidReserve) {
return (
- Payto from server is not valid "{error.data.paytoUri}"
+ Reserve from server is not valid "{reserve}"
);
}
-export function NeedConfirmationView({ error }: State.NeedConfirmation) {
+export function NeedConfirmationView({ error, onAbort, onConfirm, busy }: State.NeedConfirmation) {
+ const { i18n } = useTranslationContext()
+
+ const captchaNumbers = useMemo(() => {
+ return {
+ a: Math.floor(Math.random() * 10),
+ b: Math.floor(Math.random() * 10),
+ };
+ }, []);
+ const [captchaAnswer, setCaptchaAnswer] = useState();
+ const answer = parseInt(captchaAnswer ?? "", 10);
+ const errors = undefinedIfEmpty({
+ answer: !captchaAnswer
+ ? i18n.str`Answer the question before continue`
+ : Number.isNaN(answer)
+ ? i18n.str`The answer should be a number`
+ : answer !== captchaNumbers.a + captchaNumbers.b
+ ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.`
+ : undefined,
+ }) ?? (busy ? {} as Record : undefined);
+
return (
- Payto from server is not valid "{error.data.paytoUri}"
+
+
+
+ Confirm the withdrawal operation
+
+
+
+
+
{
+ e.preventDefault()
+ }}
+ >
+
+
{i18n.str`What is`}
+
+ {captchaNumbers.a} + {captchaNumbers.b}
+
+ ?
+
+
+
+ {
+ setCaptchaAnswer(e.currentTarget.value)
+ }}
+ />
+
+
+
+
+
+
+ Cancel
+ {
+ e.preventDefault()
+ onConfirm()
+ }}
+ >
+ Transfer
+
+
+
+
+
+
+ {/*
+
+
Wire transfer details
+
+
+
+ {((): VNode => {
+ switch (details.account.targetType) {
+ case "iban": {
+ const p = details.account as PaytoUriIBAN
+ const name = p.params["receiver-name"]
+ return
+
+
Exchange account
+ {p.iban}
+
+ {name &&
+
+
Exchange name
+ {p.params["receiver-name"]}
+
+ }
+
+ }
+ case "x-taler-bank": {
+ const p = details.account as PaytoUriTalerBank
+ const name = p.params["receiver-name"]
+ return
+
+
Exchange account
+ {p.account}
+
+ {name &&
+
+
Exchange name
+ {p.params["receiver-name"]}
+
+ }
+
+ }
+ default:
+ return
+
Exchange account
+ {details.account.targetPath}
+
+
+ }
+ })()}
+
+
Withdrawal identification
+ {details.reserve}
+
+
+
Amount
+ To be added
+ // {/* Amounts.stringifyValue(details.amount)
+
+
+
+
*/}
+
+
+
+
+
);
}
-export function AbortedView({ error }: State.Aborted) {
+export function AbortedView({ error, onClose }: State.Aborted) {
return (
- Payto from server is not valid "{error.data.paytoUri}"
+ aborted
);
}
-export function ConfirmedView({ error }: State.Confirmed) {
+export function ConfirmedView({ error, onClose }: State.Confirmed) {
+ const { i18n } = useTranslationContext();
+ const [settings, updateSettings] = useSettings()
return (
- Payto from server is not valid "{error.data.paytoUri}"
+
+
+
+
+
+
+
+ Withdrawal OK
+
+
+
+
+ The wire transfer to the Taler exchange bank's account is completed, now the
+ exchange will send the requested amount into your GNU Taler wallet.
+
+
+
+
+
+
+
+
+
+ Do not show this again
+
+
+ {
+ updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
+ }}>
+
+
+
+
+
+ {
+ e.preventDefault();
+ onClose()
+ }}>
+ Close
+
+
+
+
);
}
-export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> {
+export function ReadyView({ uri, onClose }: State.Ready): VNode<{}> {
const { i18n } = useTranslationContext();
- return
+ useEffect(() => {
+ //Taler Wallet WebExtension is listening to headers response and tab updates.
+ //In the SPA there is no header response with the Taler URI so
+ //this hack manually triggers the tab update after the QR is in the DOM.
+ // WebExtension will be using
+ // https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
+ document.title = `${document.title} ${uri.withdrawalOperationId}`;
+ }, []);
+ const talerWithdrawUri = stringifyWithdrawUri(uri);
+ const [show, setShow] = useState(false)
+ return
+
+
+
+
+ On this device
+
+
+
+
+ If you are using a desktop browser you can open the popup now or click the link if you have the "Inject Taler support" option enabled.
+
+
+
+
+
+
+
+
+
+ On a mobile phone
+
+
+
+
+ Scan the QR code with your mobile device.
+
+
+
+ {
+ setShow(!show)
+ }}
+ >
+ {!show ?
+ Show QR
+ :
+ Hide QR
+ }
+
+
+
+ {show &&
+
+
+
+ }
+
+
+
+
+ {
+ onClose()
+ }}
+ >
+ Cancel
+
+
+
}
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 573f8c769..2830f5c1e 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -26,12 +26,11 @@ import { useSettings } from "../hooks/settings.js";
* Let the user choose a payment option,
* then specify the details trigger the action.
*/
-export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
+export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJson, goToConfirmOperation: (id: string) => void }): VNode {
const { i18n } = useTranslationContext();
- const [settings, updateSettings] = useSettings();
+ const [settings] = useSettings();
const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>();
- // const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined);
return (
@@ -56,6 +55,14 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
Withdraw digital money into your mobile wallet or browser extension
+ {!!settings.currentWithdrawalOperationId &&
+
+
+
+
+ Operation in progress
+
+ }
@@ -88,9 +95,7 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
{
- updateSettings("currentWithdrawalOperationId", id);
- }}
+ goToConfirmOperation={goToConfirmOperation}
onCancel={() => {
setTab(undefined)
}}
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 416c714e2..0a5a386ae 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -137,107 +137,3 @@ export function QrCodeSection({
}
-export function QrCodeSectionSimpler({
- withdrawUri,
- onAborted,
-}: {
- withdrawUri: WithdrawUriResult;
- onAborted: () => void;
-}): VNode {
- const { i18n } = useTranslationContext();
- useEffect(() => {
- //Taler Wallet WebExtension is listening to headers response and tab updates.
- //In the SPA there is no header response with the Taler URI so
- //this hack manually triggers the tab update after the QR is in the DOM.
- // WebExtension will be using
- // https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
- document.title = `${document.title} ${withdrawUri.withdrawalOperationId}`;
- }, []);
- const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
-
- const { abortWithdrawal } = useAccessAnonAPI();
-
- async function doAbort() {
- try {
- await abortWithdrawal(withdrawUri.withdrawalOperationId);
- onAborted();
- } catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.Conflict
- ? i18n.str`The reserve operation has been confirmed previously and can't be aborted`
- : undefined,
- }),
- );
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
- }
-
- return (
-
-
-
-
- If you have a Taler wallet installed in this device
-
-
-
-
- You will see the details of the operation in your wallet including the fees (if applies).
- If you still one you can install it from here .
-
-
-
-
-
- Cancel withdrawal
-
-
-
-
-
-
-
-
- Or if you have the wallet in another device
-
-
- Scan the QR below to start the withdrawal
-
-
-
-
-
-
-
- Cancel withdrawal
-
-
-
-
-
- );
-}
-
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 08f706919..8dbdd9da6 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -36,33 +36,52 @@ import { useAccessAPI } from "../hooks/access.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
import { Amount } from "./PaytoWireTransferForm.js";
import { useSettings } from "../hooks/settings.js";
-import { WithdrawalOperationState } from "./WithdrawalQRCode.js";
-import { Loading } from "../components/Loading.js";
+import { OperationState } from "./OperationState/index.js";
const logger = new Logger("WalletWithdrawForm");
const RefAmount = forwardRef(Amount);
-export function WalletWithdrawForm({
- focus,
- limit,
- onSuccess,
- onCancel,
-}: {
+
+function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
limit: AmountJson;
focus?: boolean;
- onSuccess: (operationId: string) => void;
+ goToConfirmOperation: (operationId: string) => void;
onCancel: () => void;
}): VNode {
const { i18n } = useTranslationContext();
- const { createWithdrawal } = useAccessAPI();
const [settings, updateSettings] = useSettings()
+ const { createWithdrawal } = useAccessAPI();
const [amountStr, setAmountStr] = useState(`${settings.maxWithdrawalAmount}`);
const ref = useRef(null);
useEffect(() => {
if (focus) ref.current?.focus();
}, [focus]);
+ if (!!settings.currentWithdrawalOperationId) {
+ return
+
+
+
+
+ There is an operation already
+
+
+
+
+ To complete or cancel the operation click here
+
+
+
+
+
+
+ }
+
const trimmedAmountStr = amountStr?.trim();
const parsedAmount = trimmedAmountStr
@@ -92,7 +111,8 @@ export function WalletWithdrawForm({
i18n.str`Server responded with an invalid withdraw URI`,
i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`);
} else {
- onSuccess(uri.withdrawalOperationId);
+ updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId)
+ goToConfirmOperation(uri.withdrawalOperationId);
}
} catch (error) {
if (error instanceof RequestError) {
@@ -115,113 +135,168 @@ export function WalletWithdrawForm({
}
}
+ return {
+ e.preventDefault()
+ }}
+ >
+
+
+
+ {i18n.str`Amount`}
+ {
+ setAmountStr(v);
+ }}
+ error={errors?.amount}
+ ref={ref}
+ />
+
+
+
+ {
+ e.preventDefault();
+ setAmountStr("50.00")
+ }}
+ >
+ 50.00
+
+ {
+ e.preventDefault();
+ setAmountStr("25.00")
+ }}
+ >
+
+ 25.00
+
+ {
+ e.preventDefault();
+ setAmountStr("10.00")
+ }}
+ >
+ 10.00
+
+ {
+ e.preventDefault();
+ setAmountStr("5.00")
+ }}
+ >
+ 5.00
+
+
+
+
+
+
+
+
+ Cancel
+ {
+ e.preventDefault()
+ doStart()
+ }}
+ >
+ Continue
+
+
+
+
+}
+
+
+export function WalletWithdrawForm({
+ focus,
+ limit,
+ onCancel,
+ goToConfirmOperation,
+}: {
+ limit: AmountJson;
+ focus?: boolean;
+ goToConfirmOperation: (operationId: string) => void;
+ onCancel: () => void;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const [settings, updateSettings] = useSettings()
+
return (
Prepare your wallet
- Upon starting you will receive the money in your digital wallet, if you don't have one please install one from here .
-
-
- After using your wallet you will be redirected here to confirm or cancel the operation.
+ After using your wallet you will confirm or cancel the operation.
- {!settings.fastWithdrawal ?
-
{
- e.preventDefault()
- }}
- >
-
-
-
- {i18n.str`Amount`}
- {
- setAmountStr(v);
- }}
- error={errors?.amount}
- ref={ref}
- />
-
-
-
-
+ {settings.showInstallWallet &&
+
+
+
+
+ You need a GNU Taler Wallet
+
+
+
+
+ If you dont have one yet you can follow the instruction here
+
+
+
+ {
e.preventDefault();
- setAmountStr("50.00")
+ updateSettings("showInstallWallet", false);
}}
>
- 50.00
+ I know
+
+
+
- {
- e.preventDefault();
- setAmountStr("25.00")
- }}
- >
+
- 25.00
-
-
{
- e.preventDefault();
- setAmountStr("10.00")
- }}
- >
- 10.00
-
-
{
- e.preventDefault();
- setAmountStr("5.00")
- }}
- >
- 5.00
-
-
-
-
-
- Cancel
- {
- e.preventDefault()
- doStart()
- }}
- >
- Continue
-
-
+
}
-
- : settings.currentWithdrawalOperationId === undefined ?
- :
-
+ :
+ {
- onCancel()
- }}
+ onClose={onCancel}
+ goToConfirmOperation={goToConfirmOperation}
/>
- }
+ }
+
);
}
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 9976babdb..25c571e28 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -18,23 +18,17 @@ import {
Amounts,
HttpStatusCode,
Logger,
- TranslatedString,
WithdrawUriResult,
- parsePaytoUri,
- parseWithdrawUri,
- stringifyWithdrawUri,
+ parsePaytoUri
} from "@gnu-taler/taler-util";
-import { ErrorType, RequestError, notify, notifyError, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { ErrorType, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { Loading } from "../components/Loading.js";
-import { useAccessAPI, useWithdrawalDetails } from "../hooks/access.js";
+import { useWithdrawalDetails } from "../hooks/access.js";
import { useSettings } from "../hooks/settings.js";
import { handleNotOkResult } from "./HomePage.js";
-import { QrCodeSection, QrCodeSectionSimpler } from "./QrCodeSection.js";
+import { QrCodeSection } from "./QrCodeSection.js";
import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
-import { useEffect, useState } from "preact/hooks";
-import { buildRequestErrorMessage } from "../utils.js";
-import { getInitialBackendBaseURL } from "../hooks/backend.js";
const logger = new Logger("WithdrawalQRCode");
@@ -54,18 +48,11 @@ export function WithdrawalQRCode({
const [settings, updateSettings] = useSettings();
const { i18n } = useTranslationContext();
const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
+
if (!result.ok) {
if (result.loading) {
return
;
}
- if (
- result.type === ErrorType.CLIENT &&
- result.status === HttpStatusCode.NotFound
- ) {
- onClose()
- return
operation not found
;
- }
- // onLoadNotOk();
return handleNotOkResult(i18n)(result);
}
const { data } = result;
@@ -127,22 +114,6 @@ export function WithdrawalQRCode({
-
-
-
-
- Do not show this again
-
-
- {
- updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
- }}>
-
-
-
-
}
-
return (
);
}
-
-
-export function WithdrawalOperationState({
- currency,
- currentOperation,
- onClose,
-}: {currency:string, currentOperation: string, onClose: () => void}): VNode {
- const { i18n } = useTranslationContext();
- const [settings, updateSettings] = useSettings()
- const { createWithdrawal } = useAccessAPI();
-
- const amount = settings.maxWithdrawalAmount
- async function doSilentStart() {
- //FIXME: if amount is not enough use balance
- const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
-
- try {
- const result = await createWithdrawal({
- amount: Amounts.stringify(parsedAmount),
- });
- const uri = parseWithdrawUri(result.data.taler_withdraw_uri);
- if (!uri) {
- return notifyError(
- i18n.str`Server responded with an invalid withdraw URI`,
- i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`);
- } else {
- updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId)
- }
- } catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.Forbidden
- ? i18n.str`The operation was rejected due to insufficient funds`
- : undefined,
- }),
- );
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
- }
-
- useEffect(() => {
- doSilentStart()
- }, [settings.fastWithdrawal, amount])
-
- const result = useWithdrawalDetails(currentOperation);
- if (!result.ok) {
- if (result.loading) {
- return ;
- }
- if (
- result.type === ErrorType.CLIENT &&
- result.status === HttpStatusCode.NotFound
- ) {
- onClose()
- return operation not found
;
- }
- // onLoadNotOk();
- return handleNotOkResult(i18n)(result);
- }
- const { data } = result;
-
- const baseUrl = getInitialBackendBaseURL()
- const uri = stringifyWithdrawUri({
- bankIntegrationApiBaseUrl: `${baseUrl}/integration-api`,
- withdrawalOperationId: currentOperation,
- });
- const parsedUri = parseWithdrawUri(uri);
-
- if (data.aborted) {
- return
- the operation was aborted, you can create another one
-
- }
-
- if (data.confirmation_done) {
- return
- the wire transfer is made, you coin should arrive shortly
-
- }
- if (!parsedUri) {
- return
- the operation is not valid, create another one
-
- }
- if (!data.selection_done) {
- return (
- {
- notifyInfo(i18n.str`Operation canceled`);
- onClose()
- }}
- />
- );
- }
-
- if (!data.selected_reserve_pub) {
- return
- the exchange is selcted but no reserve pub
-
- }
-
- const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account)
-
- if (!account) {
- return
- the exchange is selected but no account
-
- }
-
- return
- the operation is wating for the question to be answered
-
;
-}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx
index 318a4cfda..f5f77a3ea 100644
--- a/packages/demobank-ui/src/pages/business/Home.tsx
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -360,7 +360,6 @@ function CreateCashout({
type="checkbox"
name="asd"
onChange={(e): void => {
- console.log("asdasd", form.isDebit);
form.isDebit = !form.isDebit;
updateForm(structuredClone(form));
}}
--
cgit v1.2.3
From ea0738ccd585445d7e2080d9009025dde9cf22c5 Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Mon, 25 Sep 2023 14:49:47 -0300
Subject: better /config error
---
.../demobank-ui/src/components/ErrorLoading.tsx | 3 ++
.../src/components/Transactions/state.ts | 2 +-
packages/demobank-ui/src/components/app.tsx | 18 +++++++---
packages/demobank-ui/src/declaration.d.ts | 3 +-
packages/demobank-ui/src/hooks/config.ts | 40 +++++++++-------------
.../demobank-ui/src/pages/AccountPage/state.ts | 4 +--
packages/demobank-ui/src/pages/BankFrame.tsx | 14 ++++++--
.../demobank-ui/src/pages/WithdrawalQRCode.tsx | 1 -
packages/web-util/src/hooks/index.ts | 1 +
packages/web-util/src/hooks/useNotifications.ts | 11 ++++++
10 files changed, 60 insertions(+), 37 deletions(-)
(limited to 'packages/demobank-ui/src/pages/WithdrawalQRCode.tsx')
diff --git a/packages/demobank-ui/src/components/ErrorLoading.tsx b/packages/demobank-ui/src/components/ErrorLoading.tsx
index a4faa4d5d..f83b61234 100644
--- a/packages/demobank-ui/src/components/ErrorLoading.tsx
+++ b/packages/demobank-ui/src/components/ErrorLoading.tsx
@@ -32,6 +32,9 @@ export function ErrorLoading({ error }: { error: HttpError{error.message}
+
+
Got status "{error.info.status}" on {error.info.url}
+
);
diff --git a/packages/demobank-ui/src/components/Transactions/state.ts b/packages/demobank-ui/src/components/Transactions/state.ts
index 30c48aa45..4b62b005e 100644
--- a/packages/demobank-ui/src/components/Transactions/state.ts
+++ b/packages/demobank-ui/src/components/Transactions/state.ts
@@ -44,7 +44,7 @@ export function useComponentState({ account }: Props): State {
cp.targetType === "bitcoin" ? `${cp.targetPath.substring(0, 6)}...` : undefined) ??
"unkown";
- const when = AbsoluteTime.fromMilliseconds(tx.date / 1000);
+ const when = AbsoluteTime.fromProtocolTimestamp(tx.date);
const amount = Amounts.parse(tx.amount);
const subject = tx.subject;
return {
diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx
index ebda31035..a587c6f1e 100644
--- a/packages/demobank-ui/src/components/app.tsx
+++ b/packages/demobank-ui/src/components/app.tsx
@@ -29,6 +29,8 @@ import { useEffect, useState } from "preact/hooks";
import { Loading } from "./Loading.js";
import { getInitialBackendBaseURL } from "../hooks/backend.js";
import { BANK_INTEGRATION_PROTOCOL_VERSION, useConfigState } from "../hooks/config.js";
+import { ErrorLoading } from "./ErrorLoading.js";
+import { BankFrame } from "../pages/BankFrame.js";
const WITH_LOCAL_STORAGE_CACHE = false;
/**
@@ -76,12 +78,18 @@ function VersionCheck({ children }: { children: ComponentChildren }): VNode {
if (checked === undefined) {
return
}
- if (checked === false) {
- return
- the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}"
-
+ if (typeof checked === "string") {
+ return
+ the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}", server version "{checked}"
+
}
- return {children}
+ if (checked === true) {
+ return {children}
+ }
+
+ return
+
+
}
function localStorageProvider(): Map {
diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts
index 8d729c1f7..d3d9e02ef 100644
--- a/packages/demobank-ui/src/declaration.d.ts
+++ b/packages/demobank-ui/src/declaration.d.ts
@@ -205,8 +205,7 @@ namespace SandboxBackend {
// Transaction unique ID. Matches
// $transaction_id from the URI.
row_id: number;
- date: number;
- // date: Timestamp;
+ date: Timestamp;
}
interface CreateBankAccountTransactionCreate {
diff --git a/packages/demobank-ui/src/hooks/config.ts b/packages/demobank-ui/src/hooks/config.ts
index 4b22e8ad3..4cf677d35 100644
--- a/packages/demobank-ui/src/hooks/config.ts
+++ b/packages/demobank-ui/src/hooks/config.ts
@@ -1,5 +1,5 @@
import { LibtoolVersion } from "@gnu-taler/taler-util";
-import { useApiContext } from "@gnu-taler/web-util/browser";
+import { ErrorType, HttpError, HttpResponseServerError, RequestError, useApiContext } from "@gnu-taler/web-util/browser";
import { useEffect, useState } from "preact/hooks";
import { getInitialBackendBaseURL } from "./backend.js";
@@ -12,38 +12,32 @@ export const BANK_INTEGRATION_PROTOCOL_VERSION = "0:0:0";
async function getConfigState(
request: ReturnType["request"],
-): Promise {
- try {
- const url = getInitialBackendBaseURL();
- const result = await request(
- url,
- `config`,
- );
- return result.data;
- } catch (error) {
- return undefined;
- }
+): Promise {
+ const url = getInitialBackendBaseURL();
+ const result = await request(url, `config`);
+ return result.data;
}
-export function useConfigState(): boolean | undefined {
- const [checked, setChecked] = useState()
+export function useConfigState(): undefined | true | string | HttpError {
+ const [checked, setChecked] = useState>()
const { request } = useApiContext();
useEffect(() => {
-
getConfigState(request)
- .then((result) => {
- if (!result) {
- setChecked(false)
+ .then((s) => {
+ const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, s.version)
+ if (r?.compatible) {
+ setChecked(true);
} else {
- const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, result.version)
- setChecked(r?.compatible);
+ setChecked(s.version)
}
})
- .catch((error) => {
- setChecked(false);
+ .catch((error: unknown) => {
+ if (error instanceof RequestError) {
+ setChecked(error.cause);
+ }
});
- });
+ }, []);
return checked;
}
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts
index c212e7484..ca7e1d447 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -75,9 +75,7 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe
};
}
- // FIXME: balance
- const balanceIsDebit = true;
- // data.balance.credit_debit_indicator == "debit";
+ const balanceIsDebit = data.balance.credit_debit_indicator == "debit";
const limit = balanceIsDebit
? Amounts.sub(debitThreshold, balance).amount
: Amounts.add(balance, debitThreshold).amount;
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index c4f872679..5c43d2c3e 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -15,7 +15,7 @@
*/
import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
-import { notifyError, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks";
import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js";
@@ -54,7 +54,12 @@ export function BankFrame({
useEffect(() => {
if (error) {
- notifyError(i18n.str`Internal error, please report.`, (error instanceof Error ? error.message : String(error)) as TranslatedString)
+ const desc = (error instanceof Error ? error.stack : String(error)) as TranslatedString
+ if (error instanceof Error) {
+ notifyException(i18n.str`Internal error, please report.`, error)
+ } else {
+ notifyError(i18n.str`Internal error, please report.`, String(error) as TranslatedString)
+ }
resetError()
}
}, [error])
@@ -386,6 +391,11 @@ function StatusBanner(): VNode {
{n.message.description}
}
+ {n.message.debug &&
+
+ {n.message.debug}
+
+ }
case "info":
return
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 25c571e28..8f4e175f6 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -45,7 +45,6 @@ export function WithdrawalQRCode({
withdrawUri,
onClose,
}: Props): VNode {
- const [settings, updateSettings] = useSettings();
const { i18n } = useTranslationContext();
const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
diff --git a/packages/web-util/src/hooks/index.ts b/packages/web-util/src/hooks/index.ts
index c29de9023..cc3267dbd 100644
--- a/packages/web-util/src/hooks/index.ts
+++ b/packages/web-util/src/hooks/index.ts
@@ -4,6 +4,7 @@ export { useMemoryStorage } from "./useMemoryStorage.js";
export {
useNotifications,
notifyError,
+ notifyException,
notifyInfo,
notify,
ErrorNotification,
diff --git a/packages/web-util/src/hooks/useNotifications.ts b/packages/web-util/src/hooks/useNotifications.ts
index 2f9df24f9..792095b06 100644
--- a/packages/web-util/src/hooks/useNotifications.ts
+++ b/packages/web-util/src/hooks/useNotifications.ts
@@ -36,6 +36,17 @@ export function notifyError(
debug,
});
}
+export function notifyException(
+ title: TranslatedString,
+ ex: Error,
+) {
+ notify({
+ type: "error" as const,
+ title,
+ description: ex.message as TranslatedString,
+ debug: ex.stack,
+ });
+}
export function notifyInfo(title: TranslatedString) {
notify({
type: "info" as const,
--
cgit v1.2.3
From 1708d49a2d5da1f325173a030695223e5a24e75c Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Fri, 29 Sep 2023 16:02:15 -0300
Subject: more ui
---
packages/demobank-ui/dev.mjs | 2 +-
packages/demobank-ui/src/components/Attention.tsx | 59 +++++
.../demobank-ui/src/components/ErrorLoading.tsx | 22 +-
.../src/components/Transactions/views.tsx | 51 ++--
packages/demobank-ui/src/demobank-ui-settings.js | 21 ++
packages/demobank-ui/src/hooks/access.ts | 3 +-
packages/demobank-ui/src/hooks/circuit.ts | 2 +-
packages/demobank-ui/src/hooks/settings.ts | 3 +
.../demobank-ui/src/pages/AccountPage/views.tsx | 62 ++---
packages/demobank-ui/src/pages/BankFrame.tsx | 104 ++++----
packages/demobank-ui/src/pages/HomePage.tsx | 6 +-
packages/demobank-ui/src/pages/LoginForm.tsx | 8 +-
.../demobank-ui/src/pages/OperationState/state.ts | 4 +-
.../demobank-ui/src/pages/OperationState/views.tsx | 5 +-
packages/demobank-ui/src/pages/PaymentOptions.tsx | 5 +-
.../src/pages/PaytoWireTransferForm.tsx | 268 ++++++++++++---------
packages/demobank-ui/src/pages/QrCodeSection.tsx | 1 -
.../src/pages/UpdateAccountPassword.tsx | 8 +-
.../demobank-ui/src/pages/WalletWithdrawForm.tsx | 183 +++++---------
.../src/pages/WithdrawalConfirmationQuestion.tsx | 14 +-
.../demobank-ui/src/pages/WithdrawalQRCode.tsx | 12 +-
.../demobank-ui/src/pages/admin/AccountForm.tsx | 8 +-
packages/demobank-ui/src/pages/admin/Home.tsx | 2 +
.../demobank-ui/src/pages/admin/RemoveAccount.tsx | 65 +----
24 files changed, 448 insertions(+), 470 deletions(-)
create mode 100644 packages/demobank-ui/src/components/Attention.tsx
create mode 100644 packages/demobank-ui/src/demobank-ui-settings.js
(limited to 'packages/demobank-ui/src/pages/WithdrawalQRCode.tsx')
diff --git a/packages/demobank-ui/dev.mjs b/packages/demobank-ui/dev.mjs
index 9c09e5716..f29a05e49 100755
--- a/packages/demobank-ui/dev.mjs
+++ b/packages/demobank-ui/dev.mjs
@@ -18,7 +18,7 @@
import { serve } from "@gnu-taler/web-util/node";
import { initializeDev } from "@gnu-taler/web-util/build";
-const devEntryPoints = ["src/stories.tsx", "src/index.tsx"];
+const devEntryPoints = ["src/stories.tsx", "src/index.tsx", "src/demobank-ui-settings.js"];
const build = initializeDev({
type: "development",
diff --git a/packages/demobank-ui/src/components/Attention.tsx b/packages/demobank-ui/src/components/Attention.tsx
new file mode 100644
index 000000000..3313e5796
--- /dev/null
+++ b/packages/demobank-ui/src/components/Attention.tsx
@@ -0,0 +1,59 @@
+import { TranslatedString } from "@gnu-taler/taler-util";
+import { ComponentChildren, Fragment, VNode, h } from "preact";
+import { assertUnreachable } from "./Routing.js";
+
+interface Props {
+ type?: "info" | "success" | "warning" | "danger",
+ onClose?: () => void,
+ title: TranslatedString,
+ children?: ComponentChildren ,
+}
+export function Attention({ type = "info", title, children, onClose }: Props): VNode {
+ return
+
+
+
+
+ {(() => {
+ switch (type) {
+ case "info":
+ return
+ case "warning":
+ return
+ case "danger":
+ return
+ case "success":
+ return
+ default:
+ assertUnreachable(type)
+ }
+ })()}
+
+
+
+
+ {title}
+
+
+ {children}
+
+
+ {onClose &&
+
+
{
+ e.preventDefault();
+ onClose();
+ }}
+ >
+
+
+
+
+
+ }
+
+
+
+
+}
diff --git a/packages/demobank-ui/src/components/ErrorLoading.tsx b/packages/demobank-ui/src/components/ErrorLoading.tsx
index f83b61234..ee62671ce 100644
--- a/packages/demobank-ui/src/components/ErrorLoading.tsx
+++ b/packages/demobank-ui/src/components/ErrorLoading.tsx
@@ -17,25 +17,13 @@
import { HttpError, useTranslationContext } from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
+import { Attention } from "./Attention.js";
+import { TranslatedString } from "@gnu-taler/taler-util";
export function ErrorLoading({ error }: { error: HttpError }): VNode {
const { i18n } = useTranslationContext()
- return (
-
-
-
-
Got status "{error.info.status}" on {error.info.url}
-
-
-
+ return (
+ Got status "{error.info.status}" on {error.info.url}
+
);
}
diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx
index f8b2e3113..f92c874f3 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -19,6 +19,7 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { State } from "./index.js";
import { format, isToday } from "date-fns";
import { Amounts } from "@gnu-taler/taler-util";
+import { useEffect, useRef } from "preact/hooks";
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
const { i18n } = useTranslationContext();
@@ -55,9 +56,9 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
{i18n.str`Date`}
- {i18n.str`Amount`}
- {i18n.str`Counterpart`}
- {i18n.str`Subject`}
+ {i18n.str`Amount`}
+ {i18n.str`Counterpart`}
+ {i18n.str`Subject`}
@@ -69,22 +70,38 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
{txs.map(item => {
+ const time = item.when.t_ms === "never" ? "" : format(item.when.t_ms, "HH:mm:ss")
+ const amount =
+ {item.negative ? "-" : ""}
+ {item.amount ? (
+ `${Amounts.stringifyValue(item.amount)} ${item.amount.currency
+ }`
+ ) : (
+ <{i18n.str`invalid value`}>
+ )}
+
return (
- {item.when.t_ms === "never"
- ? ""
- : format(item.when.t_ms, "HH:mm:ss")}
+ {time}
+
+ Amount
+
+ {item.negative ? i18n.str`sent` : i18n.str`received`} {item.amount ? (
+ `${Amounts.stringifyValue(item.amount)}`
+ ) : (
+ <{i18n.str`invalid value`}>
+ )}
+ Counterpart
+
+ {item.negative ? i18n.str`to` : i18n.str`from`} {item.counterpart}
+
+
- {item.negative ? "-" : ""}
- {item.amount ? (
- `${Amounts.stringifyValue(item.amount)} ${item.amount.currency
- }`
- ) : (
- <{i18n.str`invalid value`}>
- )}
- {item.counterpart}
+ class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 data-[negative=false]:text-green-600 data-[negative=true]:text-red-600">
+ {amount}
+
+ {item.counterpart}
{item.subject}
)
})}
@@ -94,8 +111,8 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
-
-
+
+
> => {
@@ -382,7 +382,6 @@ export function useTransactions(
loadMore: () => {
if (!afterData || isReachingEnd) return;
// if (afterData.data.transactions.length < MAX_RESULT_SIZE) {
- // console.log("load more", page)
const l = afterData.data.transactions[afterData.data.transactions.length-1]
setStart(String(l.row_id));
// }
diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts
index 82caafdf2..5dba60951 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -435,7 +435,7 @@ export function useBusinessAccounts(
HttpResponseOk,
RequestError
>(
- [`circuit-api/accounts`, args?.page, PAGE_SIZE, args?.account],
+ [`accounts`, args?.page, PAGE_SIZE, args?.account],
sandboxAccountsFetcher,
{
refreshInterval: 0,
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index 5f004c6d4..ad853f9d7 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -33,6 +33,7 @@ interface Settings {
showInstallWallet: boolean;
maxWithdrawalAmount: number;
fastWithdrawal: boolean;
+ showDebugInfo: boolean;
}
export const codecForSettings = (): Codec =>
@@ -42,6 +43,7 @@ export const codecForSettings = (): Codec =>
.property("showDemoDescription", (codecForBoolean()))
.property("showInstallWallet", (codecForBoolean()))
.property("fastWithdrawal", (codecForBoolean()))
+ .property("showDebugInfo", (codecForBoolean()))
.property("maxWithdrawalAmount", codecForNumber())
.build("Settings");
@@ -52,6 +54,7 @@ const defaultSettings: Settings = {
showInstallWallet: true,
maxWithdrawalAmount: 25,
fastWithdrawal: false,
+ showDebugInfo: false,
};
const DEMOBANK_SETTINGS_KEY = buildStorageKey(
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 83846be90..483cb579a 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -16,10 +16,10 @@
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
+import { Attention } from "../../components/Attention.js";
import { Transactions } from "../../components/Transactions/index.js";
import { useBusinessAccountDetails } from "../../hooks/circuit.js";
import { useSettings } from "../../hooks/settings.js";
-import { bankUiSettings } from "../../settings.js";
import { PaymentOptions } from "../PaymentOptions.js";
import { State } from "./index.js";
@@ -31,53 +31,27 @@ export function InvalidIbanView({ error }: State.InvalidIban) {
const IS_PUBLIC_ACCOUNT_ENABLED = false
-
function ShowDemoInfo(): VNode {
const { i18n } = useTranslationContext();
const [settings, updateSettings] = useSettings();
if (!settings.showDemoDescription) return
- return
-
-
-
-
- This is a demo bank!
-
-
- {IS_PUBLIC_ACCOUNT_ENABLED ? (
-
- This part of the demo shows how a bank that supports Taler
- directly would work. In addition to using your own bank
- account, you can also see the transaction history of some{" "}
- Public Accounts .
-
- ) : (
-
- This part of the demo shows how a bank that supports Taler
- directly would work.
-
- )}
-
- {
- e.preventDefault();
- updateSettings("showDemoDescription", false);
- }}
- >
-
-
-
-
-
-
-
-
-
-
+ return {
+ updateSettings("showDemoDescription", false);
+ }}>
+ {IS_PUBLIC_ACCOUNT_ENABLED ? (
+
+ This part of the demo shows how a bank that supports Taler
+ directly would work. In addition to using your own bank
+ account, you can also see the transaction history of some{" "}
+ Public Accounts .
+
+ ) : (
+
+ This part of the demo shows how a bank that supports Taler
+ directly would work.
+
+ )}
+
}
export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> {
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index 15ef8a036..29334cae4 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -15,7 +15,7 @@
*/
import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
-import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { NotificationMessage, notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks";
import { LangSelector } from "../components/LangSelector.js";
@@ -26,6 +26,7 @@ import { useSettings } from "../hooks/settings.js";
import { CopyButton, CopyIcon } from "../components/CopyButton.js";
import logo from "../assets/logo-2021.svg";
import { useAccountDetails } from "../hooks/access.js";
+import { Attention } from "../components/Attention.js";
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
@@ -108,7 +109,7 @@ export function BankFrame({
setOpen(!open)
}}>
- Open main menu
+ Open settings
@@ -227,6 +228,22 @@ export function BankFrame({
+
+
+
+
+ Show debug info
+
+
+ {
+ updateSettings("showDebugInfo", !settings.showDebugInfo);
+ }}>
+
+
+
+
@@ -286,10 +303,10 @@ export function BankFrame({
}
+
@@ -301,79 +318,46 @@ export function BankFrame({
);
}
+function MaybeShowDebugInfo({ info }: { info: any }): VNode {
+ const [settings] = useSettings()
+ if (settings.showDebugInfo) {
+ return
+ {info}
+
+ }
+ return
+}
+
function StatusBanner(): VNode {
const notifs = useNotifications()
- return {
+ if (notifs.length === 0) return
+ return
{
notifs.map(n => {
switch (n.message.type) {
case "error":
- return
-
-
-
-
-
- {
- e.preventDefault();
- n.remove()
- }}
- >
- Close
-
-
-
-
-
-
-
+ return
{
+ n.remove()
+ }}>
{n.message.description &&
{n.message.description}
}
+
+ {/*
+ show debug info
+
{n.message.debug &&
{n.message.debug}
- }
-
+ } */}
+
case "info":
- return
-
-
-
-
{n.message.title}
-
-
- {
- e.preventDefault();
- n.remove();
- }}
- >
-
-
-
-
-
-
-
-
-
+ return
{
+ n.remove();
+ }} />
}
})}
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index d945d80d1..95144f086 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -137,8 +137,8 @@ export function handleNotOkResult(
const errorData = result.payload;
notify({
type: "error",
- title: i18n.str`Could not load due to a client error`,
- description: errorData?.error?.description as TranslatedString,
+ title: i18n.str`Could not load due to a request error`,
+ description: i18n.str`Request to url "${result.info.url}" returned ${result.info.status}`,
debug: JSON.stringify(result),
});
break;
@@ -174,7 +174,7 @@ export function handleNotOkResult(
assertUnreachable(result);
}
}
- route("/")
+ // route("/")
return
error
;
}
return
;
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index 14d261622..3ea94b899 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -23,6 +23,7 @@ import { useBackendContext } from "../context/backend.js";
import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js";
import { bankUiSettings } from "../settings.js";
import { undefinedIfEmpty } from "../utils.js";
+import { doAutoFocus } from "./PaytoWireTransferForm.js";
/**
@@ -98,8 +99,8 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
});
} else {
saveError({
- title: i18n.str`Could not load due to a client error`,
- // description: cause.payload.error.description,
+ title: i18n.str`Could not load due to a request error`,
+ description: i18n.str`Request to url "${cause.info.url}" returned ${cause.info.status}`,
debug: JSON.stringify(cause.payload),
});
}
@@ -159,8 +160,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
- Withdrawal OK
+ Withdrawal confirmed
- The wire transfer to the Taler exchange bank's account is completed, now the
- exchange will send the requested amount into your GNU Taler wallet.
+ The wire transfer to the Taler operator has been initiated. You will soon receive the requested amount in your Taler wallet.
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 49419d0dc..fef272831 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -30,7 +30,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
const { i18n } = useTranslationContext();
const [settings] = useSettings();
- const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>();
+ const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>("wire-transfer");
return (
@@ -82,7 +82,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
another bank account
- Make a wire transfer to an account which you know the address.
+ Make a wire transfer to an account which you know the bank account number
@@ -108,6 +108,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
limit={limit}
onSuccess={() => {
notifyInfo(i18n.str`Wire transfer created!`);
+ setTab(undefined)
}}
onCancel={() => {
setTab(undefined)
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 5f5a6ce3b..785dc4264 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -55,10 +55,11 @@ export function PaytoWireTransferForm({
onCancel: (() => void) | undefined;
limit: AmountJson;
}): VNode {
- const [isRawPayto, setIsRawPayto] = useState(false);
- const [iban, setIban] = useState
(undefined);
- const [subject, setSubject] = useState(undefined);
- const [amount, setAmount] = useState(undefined);
+ const [isRawPayto, setIsRawPayto] = useState(true);
+ // FIXME: remove this
+ const [iban, setIban] = useState("DE4745461198061");
+ const [subject, setSubject] = useState("ASD");
+ const [amount, setAmount] = useState("1.00001");
const [rawPaytoInput, rawPaytoInputSetter] = useState(
undefined,
@@ -76,17 +77,17 @@ export function PaytoWireTransferForm({
const errorsWire = undefinedIfEmpty({
iban: !iban
- ? i18n.str`Missing IBAN`
+ ? i18n.str`required`
: !IBAN_REGEX.test(iban)
? i18n.str`IBAN should have just uppercased letters and numbers`
: validateIBAN(iban, i18n),
- subject: !subject ? i18n.str`Missing subject` : undefined,
+ subject: !subject ? i18n.str`required` : undefined,
amount: !trimmedAmountStr
- ? i18n.str`Missing amount`
+ ? i18n.str`required`
: !parsedAmount
- ? i18n.str`Amount is not valid`
+ ? i18n.str`not valid`
: Amounts.isZero(parsedAmount)
- ? i18n.str`Should be greater than 0`
+ ? i18n.str`should be greater than 0`
: Amounts.cmp(limit, parsedAmount) === -1
? i18n.str`balance is not enough`
: undefined,
@@ -101,14 +102,14 @@ export function PaytoWireTransferForm({
? i18n.str`required`
: !parsed
? i18n.str`does not follow the pattern`
- : !parsed.params.amount
- ? i18n.str`use the "amount" parameter to specify the amount to be transferred`
- : Amounts.parse(parsed.params.amount) === undefined
- ? i18n.str`the amount is not valid`
- : !parsed.params.message
- ? i18n.str`use the "message" parameter to specify a reference text for the transfer`
- : !parsed.isKnown || parsed.targetType !== "iban"
- ? i18n.str`only "IBAN" target are supported`
+ : !parsed.isKnown || parsed.targetType !== "iban"
+ ? i18n.str`only "IBAN" target are supported`
+ : !parsed.params.amount
+ ? i18n.str`use the "amount" parameter to specify the amount to be transferred`
+ : Amounts.parse(parsed.params.amount) === undefined
+ ? i18n.str`the amount is not valid`
+ : !parsed.params.message
+ ? i18n.str`use the "message" parameter to specify a reference text for the transfer`
: !IBAN_REGEX.test(parsed.iban)
? i18n.str`IBAN should have just uppercased letters and numbers`
: validateIBAN(parsed.iban, i18n),
@@ -159,6 +160,9 @@ export function PaytoWireTransferForm({
}
return (
+ {/**
+ * FIXME: Scan a qr code
+ */}
{title}
@@ -167,6 +171,17 @@ export function PaytoWireTransferForm({
{
+ if (parsed && parsed.isKnown && parsed.targetType === "iban") {
+ setIban(parsed.iban)
+ const amount = Amounts.parse(parsed.params["amount"])
+ if (amount) {
+ setAmount(Amounts.stringifyValue(amount))
+ }
+ const subject = parsed.params["subject"]
+ if (subject) {
+ setSubject(subject)
+ }
+ }
setIsRawPayto(false)
}} />
@@ -180,12 +195,22 @@ export function PaytoWireTransferForm({
{
+ if (iban) {
+ const payto = buildPayto("iban", iban, undefined)
+ if (parsedAmount) {
+ payto.params["amount"] = Amounts.stringify(parsedAmount)
+ }
+ if (subject) {
+ payto.params["message"] = subject
+ }
+ rawPaytoInputSetter(stringifyPaytoUri(payto))
+ }
setIsRawPayto(true)
}} />
- using the payto:// format
+ Import payto:// URI
@@ -195,7 +220,7 @@ export function PaytoWireTransferForm({
{
@@ -203,105 +228,106 @@ export function PaytoWireTransferForm({
}}
>
-
- {!isRawPayto ?
-
-
-
-
{i18n.str`Account number`}
-
- {
- setIban(e.currentTarget.value);
- }}
- />
-
-
-
the receiver of the money
-
+ {!isRawPayto ?
+
-
-
{i18n.str`Transfer subject`}
-
- {
- setSubject(e.currentTarget.value);
- }}
- />
-
-
-
some text to identify the transfer
+
+
{i18n.str`Recipient`}
+
+ {
+ setIban(e.currentTarget.value.toUpperCase());
+ }}
+ />
+
+
+ IBAN of the recipient's account
+
+
-
-
{i18n.str`Amount`}
-
{
- setAmount(d)
+
+
{i18n.str`Transfer subject`}
+
+
{
+ setSubject(e.currentTarget.value);
}}
/>
-
amount to transfer
+
some text to identify the transfer
+
- :
-
-
-
{i18n.str`payto URI:`}
-
- {
- rawPaytoInputSetter(e.currentTarget.value);
- }}
- />
-
-
-
+
+
{i18n.str`Amount`}
+
{
+ setAmount(d)
+ }}
+ />
+
+ amount to transfer
+
-
- }
-
+
:
+
+
+
{i18n.str`payto URI:`}
+
+ {
+ rawPaytoInputSetter(e.currentTarget.value);
+ }}
+ />
+
+
+
+
+ }
{onCancel ?
@@ -328,17 +354,37 @@ export function PaytoWireTransferForm({
)
}
+
+/**
+ * Show the element when the load ended
+ * @param element
+ */
+export function doAutoFocus(element: HTMLElement | null) {
+ if (element) {
+ window.requestIdleCallback(() => {
+ element.focus()
+ element.scrollIntoView({
+ behavior: "smooth",
+ block: "center",
+ inline: "center"
+ })
+ })
+ }
+}
+
export function Amount(
{
currency,
name,
value,
error,
+ left,
onChange,
}: {
error?: string;
currency: string;
name: string;
+ left?: boolean | undefined,
value: string | undefined;
onChange?: (s: string) => void;
},
@@ -346,13 +392,16 @@ export function Amount(
): VNode {
return (
-
-
+
+
{currency}
);
}
+
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 0a5a386ae..6a50d4ef3 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -86,7 +86,6 @@ export function QrCodeSection({
Click here to start
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index d19c411f3..46f4fe0ef 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { doAutoFocus } from "./PaytoWireTransferForm.js";
export function UpdateAccountPassword({
account,
@@ -27,11 +28,6 @@ export function UpdateAccountPassword({
const [password, setPassword] = useState();
const [repeat, setRepeat] = useState();
- const ref = useRef(null);
- useEffect(() => {
- if (focus) ref.current?.focus();
- }, [focus]);
-
if (!result.ok) {
if (result.loading || result.type === ErrorType.TIMEOUT) {
return onLoadNotOk(result);
@@ -96,7 +92,7 @@ export function UpdateAccountPassword({
(`${settings.maxWithdrawalAmount}`);
- const ref = useRef
(null);
- useEffect(() => {
- if (focus) ref.current?.focus();
- }, [focus]);
if (!!settings.currentWithdrawalOperationId) {
- return
-
-
-
-
-
-
- There is an operation already
-
-
-
-
- To complete or cancel the operation click here
-
-
-
-
-
-
-
-
- {
- updateSettings("currentWithdrawalOperationId", undefined)
- onCancel()
- }}
- >
- Cancel
-
-
-
+ return
+
+ To complete or cancel the operation click here
+
+
}
const trimmedAmountStr = amountStr?.trim();
@@ -157,8 +124,8 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
e.preventDefault()
}}
>
-
-
+
+
{i18n.str`Amount`}
-
-
- {
- e.preventDefault();
- setAmountStr("50.00")
- }}
- >
- 50.00
-
- {
- e.preventDefault();
- setAmountStr("25.00")
- }}
- >
+
+
+
- 25.00
-
- {
- e.preventDefault();
- setAmountStr("10.00")
- }}
- >
- 10.00
-
- {
- e.preventDefault();
- setAmountStr("5.00")
- }}
- >
- 5.00
-
-
-
+
{
+ e.preventDefault();
+ setAmountStr("50.00")
+ }}
+ >
+ 50.00
+
+
{
+ e.preventDefault();
+ setAmountStr("25.00")
+ }}
+ >
+ 25.00
+
+
+
+ {
+ e.preventDefault();
+ setAmountStr("10.00")
+ }}
+ >
+ 10.00
+
+ {
+ e.preventDefault();
+ setAmountStr("5.00")
+ }}
+ >
+ 5.00
+
+
@@ -255,46 +224,20 @@ export function WalletWithdrawForm({
Prepare your wallet
- After using your wallet you will confirm or cancel the operation.
+ After using your wallet you will need to confirm or cancel the operation on this site.
- {settings.showInstallWallet &&
-
-
-
-
- You need a GNU Taler Wallet
-
-
-
-
- If you dont have one yet you can follow the instruction here
-
-
-
- {
- e.preventDefault();
- updateSettings("showInstallWallet", false);
- }}
- >
- I know
-
-
-
-
-
-
-
-
-
-
}
+ {settings.showInstallWallet &&
+
{
+ updateSettings("showInstallWallet", false);
+ }}>
+
+ If you don't have one yet you can follow the instruction here
+
+
+ }
{!settings.fastWithdrawal ?
{
return {
@@ -87,7 +89,9 @@ export function WithdrawalConfirmationQuestion({
await confirmWithdrawal(
withdrawUri.withdrawalOperationId,
);
- notifyInfo(i18n.str`Wire transfer completed!`)
+ if (!settings.showWithdrawalSuccess) {
+ notifyInfo(i18n.str`Wire transfer completed!`)
+ }
} catch (error) {
if (error instanceof RequestError) {
notify(
@@ -203,7 +207,7 @@ export function WithdrawalConfirmationQuestion({
-
Answer the next question to authorize the wire transfer
+ Answer the next question to authorize the wire transfer.
- Withdrawal identification
- {details.reserve}
-
Amount
- {Amounts.stringifyValue(details.amount)}
+ {Amounts.currencyOf(details.amount)} {Amounts.stringifyValue(details.amount)}
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 8f4e175f6..c8efc033b 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -94,20 +94,12 @@ export function WithdrawalQRCode({
- Withdrawal OK
+ Withdrawal confirmed
- The wire transfer to the Taler exchange bank's account is completed, now the
- exchange will send the requested amount into your GNU Taler wallet.
-
-
-
-
-
-
- You can close this page now or continue to the account page.
+ The wire transfer to the Taler operator has been initiated. You will soon receive the requested amount in your Taler wallet.
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index 02df824a2..ed8bf610d 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -4,6 +4,7 @@ import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty
import { useEffect, useRef, useState } from "preact/hooks";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { buildPayto, parsePaytoUri } from "@gnu-taler/taler-util";
+import { doAutoFocus } from "../PaytoWireTransferForm.js";
const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
const EMAIL_REGEX =
@@ -37,10 +38,6 @@ export function AccountForm({
RecursivePartial
| undefined
>(undefined);
const { i18n } = useTranslationContext();
- const ref = useRef(null);
- useEffect(() => {
- if (focus) ref.current?.focus();
- }, [focus]);
function updateForm(newForm: typeof initial): void {
@@ -97,7 +94,6 @@ export function AccountForm({
-
+
);
}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index 1e5370afc..b323b0d01 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -6,6 +6,8 @@ import { Amounts, HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util
import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
import { useEffect, useRef, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { Attention } from "../../components/Attention.js";
+import { doAutoFocus } from "../PaytoWireTransferForm.js";
export function RemoveAccount({
account,
@@ -36,47 +38,15 @@ export function RemoveAccount({
}
return onLoadNotOk(result);
}
- const ref = useRef
(null);
- useEffect(() => {
- if (focus) ref.current?.focus();
- }, [focus]);
-
const balance = Amounts.parse(result.data.balance.amount);
if (!balance) {
return there was an error reading the balance
;
}
const isBalanceEmpty = Amounts.isZero(balance);
if (!isBalanceEmpty) {
- return
-
-
-
-
-
- Can't delete the account
-
-
-
- The account can't be delete while still holding some balance. First make sure that the owner make a complete cashout.
-
-
-
-
-
-
-
- {
- onCancel()
- }}>
- Go back
-
-
-
+ return
+ The account can't be delete while still holding some balance. First make sure that the owner make a complete cashout.
+
}
async function doRemove() {
@@ -117,26 +87,9 @@ export function RemoveAccount({
return (
-
-
-
-
-
- You are going to remove the account
-
-
-
- This step can't be undone.
-
-
-
-
-
-
+
+ This step can't be undone.
+
@@ -164,7 +117,7 @@ export function RemoveAccount({
Date: Tue, 3 Oct 2023 10:11:21 -0300
Subject: more ui, removed some testing values
---
.../src/components/Transactions/views.tsx | 4 +--
.../src/pages/PaytoWireTransferForm.tsx | 8 ++---
packages/demobank-ui/src/pages/QrCodeSection.tsx | 35 +++++++++++-----------
.../demobank-ui/src/pages/WithdrawalQRCode.tsx | 2 +-
4 files changed, 23 insertions(+), 26 deletions(-)
(limited to 'packages/demobank-ui/src/pages/WithdrawalQRCode.tsx')
diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx
index 69f4369a2..696fb59f3 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -81,9 +81,7 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
Amount
- {item.negative ? i18n.str`sent` : i18n.str`received`}
-
- {item.amount ? (
+ {item.negative ? i18n.str`sent` : i18n.str`received`} {item.amount ? (
) : (
<{i18n.str`invalid value`}>
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index b3fd51670..69a9a07b2 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -59,9 +59,9 @@ export function PaytoWireTransferForm({
}): VNode {
const [isRawPayto, setIsRawPayto] = useState(false);
// FIXME: remove this
- const [iban, setIban] = useState("DE4745461198061");
- const [subject, setSubject] = useState("ASD");
- const [amount, setAmount] = useState("1.00001");
+ const [iban, setIban] = useState();
+ const [subject, setSubject] = useState();
+ const [amount, setAmount] = useState();
const [rawPaytoInput, rawPaytoInputSetter] = useState(
undefined,
@@ -189,7 +189,7 @@ export function PaytoWireTransferForm({
- using a form
+ Using a form
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 6a50d4ef3..e07525ab4 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -84,27 +84,25 @@ export function QrCodeSection({
If you have a Taler wallet installed in this device
-
-
+
You will see the details of the operation in your wallet including the fees (if applies).
- If you still one you can install it from here .
+ If you still don't have one you can install it from here .
@@ -122,12 +120,13 @@ export function QrCodeSection({
-
- Cancel withdrawal
-
+
+ Cancel
+
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index c8efc033b..2d90b410c 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -112,7 +112,7 @@ export function WithdrawalQRCode({
e.preventDefault();
onClose()
}}>
- Continue
+ Done
--
cgit v1.2.3
From 851b2da39c3297ede3d267f3d2534cac213261c1 Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Wed, 4 Oct 2023 14:36:03 -0300
Subject: fixing issues reported by Christian, wip
---
.../src/pages/PaytoWireTransferForm.tsx | 4 +-
.../demobank-ui/src/pages/WithdrawalQRCode.tsx | 2 +-
.../merchant-backoffice-ui/src/Application.tsx | 4 +-
.../merchant-backoffice-ui/src/InstanceRoutes.tsx | 30 ++++++-------
.../src/components/form/InputStock.tsx | 5 +--
.../src/components/menu/SideBar.tsx | 29 ++++++++----
.../src/components/menu/index.tsx | 8 ++--
.../src/components/product/ProductForm.tsx | 6 +--
.../merchant-backoffice-ui/src/context/backend.ts | 26 +++++++++--
.../merchant-backoffice-ui/src/declaration.d.ts | 4 +-
packages/merchant-backoffice-ui/src/hooks/index.ts | 3 +-
packages/merchant-backoffice-ui/src/hooks/otp.ts | 4 +-
.../merchant-backoffice-ui/src/hooks/product.ts | 17 ++++++-
.../src/paths/instance/products/list/Table.tsx | 13 +++---
.../src/paths/instance/products/list/index.tsx | 52 +++++++++++++++++++++-
.../paths/instance/reserves/create/CreatePage.tsx | 27 +++++++----
.../instance/validators/create/CreatePage.tsx | 6 +--
.../validators/create/CreatedSuccessfully.tsx | 2 +-
.../instance/validators/update/UpdatePage.tsx | 2 +-
.../src/paths/instance/validators/update/index.tsx | 2 +-
.../src/paths/login/index.tsx | 17 +++----
.../merchant-backoffice-ui/src/schemas/index.ts | 3 +-
.../merchant-backoffice-ui/src/utils/constants.ts | 2 +-
packages/web-util/src/index.build.ts | 2 +-
24 files changed, 185 insertions(+), 85 deletions(-)
(limited to 'packages/demobank-ui/src/pages/WithdrawalQRCode.tsx')
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 69a9a07b2..52dbd4ff6 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -363,14 +363,14 @@ export function PaytoWireTransferForm({
*/
export function doAutoFocus(element: HTMLElement | null) {
if (element) {
- window.requestIdleCallback(() => {
+ setTimeout(() => {
element.focus()
element.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "center"
})
- })
+ }, 100)
}
}
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 2d90b410c..91c5da718 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -150,7 +150,7 @@ export function WithdrawalQRCode({
details={{
account,
reserve: data.selected_reserve_pub,
- amount: Amounts.parseOrThrow("usd:10.00")
+ amount: Amounts.parseOrThrow(data.amount)
}}
onAborted={() => {
notifyInfo(i18n.str`Operation canceled`);
diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx
index 1a7617643..f0a7de53b 100644
--- a/packages/merchant-backoffice-ui/src/Application.tsx
+++ b/packages/merchant-backoffice-ui/src/Application.tsx
@@ -60,7 +60,7 @@ export function Application(): VNode {
* @returns
*/
function ApplicationStatusRoutes(): VNode {
- const { url: backendURL, updateToken, changeBackend } = useBackendContext();
+ const { changeBackend, selected: backendSelected } = useBackendContext();
const result = useBackendConfig();
const { i18n } = useTranslationContext();
@@ -69,7 +69,7 @@ function ApplicationStatusRoutes(): VNode {
: { currency: "unknown", version: "unknown" };
const ctx = useMemo(() => ({ currency, version }), [currency, version]);
- if (!backendURL) {
+ if (!backendSelected) {
return (
diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
index c2a9d3b18..f5372db8d 100644
--- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
@@ -87,9 +87,9 @@ export enum InstancePaths {
bank_update = "/bank/:bid/update",
bank_new = "/bank/new",
- product_list = "/products",
- product_update = "/product/:pid/update",
- product_new = "/product/new",
+ inventory_list = "/inventory",
+ inventory_update = "/inventory/:pid/update",
+ inventory_new = "/inventory/new",
order_list = "/orders",
order_new = "/order/new",
@@ -347,42 +347,42 @@ export function InstanceRoutes({
onLoadError={ServerErrorRedirectTo(InstancePaths.error)}
/>
{/**
- * Product pages
+ * Inventory pages
*/}
{
- route(InstancePaths.product_new);
+ route(InstancePaths.inventory_new);
}}
onSelect={(id: string) => {
- route(InstancePaths.product_update.replace(":pid", id));
+ route(InstancePaths.inventory_update.replace(":pid", id));
}}
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
/>
{
- route(InstancePaths.product_list);
+ route(InstancePaths.inventory_list);
}}
onBack={() => {
- route(InstancePaths.product_list);
+ route(InstancePaths.inventory_list);
}}
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
/>
{
- route(InstancePaths.product_list);
+ route(InstancePaths.inventory_list);
}}
onBack={() => {
- route(InstancePaths.product_list);
+ route(InstancePaths.inventory_list);
}}
/>
{/**
@@ -405,7 +405,7 @@ export function InstanceRoutes({
path={InstancePaths.bank_update}
component={BankAccountUpdatePage}
onUnauthorized={LoginPageAccessDenied}
- onLoadError={ServerErrorRedirectTo(InstancePaths.product_list)}
+ onLoadError={ServerErrorRedirectTo(InstancePaths.inventory_list)}
onConfirm={() => {
route(InstancePaths.bank_list);
}}
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx
index 012d14977..1d18685c5 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx
@@ -212,10 +212,7 @@ export function InputStock({
withTimestampSupport
/>
-
- name="address"
- label={i18n.str`Delivery address`}
- >
+ name="address" label={i18n.str`Warehouse address`}>
diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
index 3d5f20c85..402134096 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
@@ -49,7 +49,7 @@ export function Sidebar({
isPasswordOk
}: Props): VNode {
const config = useConfigContext();
- const { url: backendURL } = useBackendContext()
+ const { url: backendURL, resetBackend } = useBackendContext()
const { i18n } = useTranslationContext();
const kycStatus = useInstanceKYCDetails();
const needKYC = kycStatus.ok && kycStatus.data.type === "redirect";
@@ -80,7 +80,7 @@ export function Sidebar({
- {isPasswordOk && instance ? (
+ {instance ? (
- {isPasswordOk && admin && !mimic && (
+ {admin && !mimic && (
)}
- {isPasswordOk &&
+ {isPasswordOk ?
Log out
-
- }
+ :
+
+ resetBackend()}
+ >
+
+
+
+
+
+
+ }
diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
index cb318906f..b8ac2c9ab 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
@@ -30,11 +30,11 @@ function getInstanceTitle(path: string, id: string): string {
return `${id}: Orders`;
case InstancePaths.order_new:
return `${id}: New order`;
- case InstancePaths.product_list:
- return `${id}: Products`;
- case InstancePaths.product_new:
+ case InstancePaths.inventory_list:
+ return `${id}: Inventory`;
+ case InstancePaths.inventory_new:
return `${id}: New product`;
- case InstancePaths.product_update:
+ case InstancePaths.inventory_update:
return `${id}: Update product`;
case InstancePaths.reserves_new:
return `${id}: New reserve`;
diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
index 8bebbd298..e91e8c876 100644
--- a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
+++ b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
@@ -146,9 +146,9 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
/>
name="minimum_age"
- label={i18n.str`Age restricted`}
+ label={i18n.str`Age restriction`}
tooltip={i18n.str`is this product restricted for customer below certain age?`}
- help={i18n.str`can be overridden by the order configuration`}
+ help={i18n.str`minimum age of the buyer`}
/>
name="unit"
@@ -165,7 +165,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
name="stock"
label={i18n.str`Stock`}
alreadyExist={alreadyExist}
- tooltip={i18n.str`product inventory for products with finite supply (for internal use only)`}
+ tooltip={i18n.str`inventory for products with finite supply (for internal use only)`}
/>
name="taxes"
diff --git a/packages/merchant-backoffice-ui/src/context/backend.ts b/packages/merchant-backoffice-ui/src/context/backend.ts
index 056f9a192..d4a9abd5f 100644
--- a/packages/merchant-backoffice-ui/src/context/backend.ts
+++ b/packages/merchant-backoffice-ui/src/context/backend.ts
@@ -20,35 +20,55 @@
*/
import { createContext, h, VNode } from "preact";
-import { useContext } from "preact/hooks";
+import { useContext, useState } from "preact/hooks";
import { LoginToken } from "../declaration.js";
import { useBackendDefaultToken, useBackendURL } from "../hooks/index.js";
+import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
+import { codecForBoolean } from "@gnu-taler/taler-util";
interface BackendContextType {
url: string,
+ selected: boolean;
token?: LoginToken;
updateToken: (token: LoginToken | undefined) => void;
changeBackend: (url: string) => void;
+ resetBackend: () => void;
}
const BackendContext = createContext({
url: "",
+ selected: false,
token: undefined,
updateToken: () => null,
changeBackend: () => null,
+ resetBackend: () => null,
});
+const BACKEND_SELECTED = buildStorageKey("backend-selected", codecForBoolean());
+
function useBackendContextState(
defaultUrl?: string,
): BackendContextType {
- const [url, changeBackend] = useBackendURL(defaultUrl);
+ const [url, changeBackend2] = useBackendURL(defaultUrl);
const [token, updateToken] = useBackendDefaultToken();
+ const {value, update} = useLocalStorage(BACKEND_SELECTED)
+
+ function changeBackend(s:string) {
+ changeBackend2(s)
+ update(true)
+ }
+
+ function resetBackend() {
+ update(false)
+ }
return {
url,
token,
+ selected: value ?? false,
updateToken,
- changeBackend
+ changeBackend,
+ resetBackend
};
}
diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts b/packages/merchant-backoffice-ui/src/declaration.d.ts
index c3e6ea3da..dc53e3e83 100644
--- a/packages/merchant-backoffice-ui/src/declaration.d.ts
+++ b/packages/merchant-backoffice-ui/src/declaration.d.ts
@@ -1327,7 +1327,7 @@ export namespace MerchantBackend {
otp_device_id: string;
// Human-readable description for the device.
- otp_description: string;
+ otp_device_description: string;
// A base64-encoded key
otp_key: string;
@@ -1341,7 +1341,7 @@ export namespace MerchantBackend {
interface OtpDevicePatchDetails {
// Human-readable description for the device.
- otp_description: string;
+ otp_device_description: string;
// A base64-encoded key
otp_key: string | undefined;
diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts b/packages/merchant-backoffice-ui/src/hooks/index.ts
index ee696779f..498e4eb78 100644
--- a/packages/merchant-backoffice-ui/src/hooks/index.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/index.ts
@@ -31,7 +31,8 @@ const calculateRootPath = () => {
typeof window !== undefined
? window.location.origin + window.location.pathname
: "/";
- return rootPath;
+
+ return rootPath.replace("webui/","");
};
const loginTokenCodec = buildCodecForObject()
diff --git a/packages/merchant-backoffice-ui/src/hooks/otp.ts b/packages/merchant-backoffice-ui/src/hooks/otp.ts
index 3544b4881..93eefeea5 100644
--- a/packages/merchant-backoffice-ui/src/hooks/otp.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/otp.ts
@@ -30,13 +30,13 @@ const useSWR = _useSWR as unknown as SWRHook;
const MOCKED_DEVICES: Record = {
"1": {
- otp_description: "first device",
+ otp_device_description: "first device",
otp_algorithm: 1,
otp_device_id: "1",
otp_key: "123",
},
"2": {
- otp_description: "second device",
+ otp_device_description: "second device",
otp_algorithm: 0,
otp_device_id: "2",
otp_key: "456",
diff --git a/packages/merchant-backoffice-ui/src/hooks/product.ts b/packages/merchant-backoffice-ui/src/hooks/product.ts
index 8ecaefaa6..e06ea8ed8 100644
--- a/packages/merchant-backoffice-ui/src/hooks/product.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/product.ts
@@ -26,6 +26,9 @@ import _useSWR, { SWRHook, useSWRConfig } from "swr";
const useSWR = _useSWR as unknown as SWRHook;
export interface ProductAPI {
+ getProduct: (
+ id: string,
+ ) => Promise;
createProduct: (
data: MerchantBackend.Products.ProductAddDetail,
) => Promise;
@@ -66,7 +69,7 @@ export function useProductAPI(): ProductAPI {
data,
});
- return await mutateAll(/.*"\/private\/products.*/);
+ return await mutateAll(/.*\/private\/products.*/);
};
const deleteProduct = async (productId: string): Promise => {
@@ -88,7 +91,17 @@ export function useProductAPI(): ProductAPI {
return await mutateAll(/.*"\/private\/products.*/);
};
- return { createProduct, updateProduct, deleteProduct, lockProduct };
+ const getProduct = async (
+ productId: string,
+ ): Promise => {
+ await request(`/private/products/${productId}`, {
+ method: "GET",
+ });
+
+ return
+ };
+
+ return { createProduct, updateProduct, deleteProduct, lockProduct, getProduct };
}
export function useInstanceProducts(): HttpResponse<
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
index cbfe1d573..db73217ed 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
@@ -66,7 +66,7 @@ export function CardTable({
- Products
+ Inventory