summaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages')
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/index.ts25
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/state.ts51
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/views.tsx34
-rw-r--r--packages/demobank-ui/src/pages/BankFrame.tsx36
-rw-r--r--packages/demobank-ui/src/pages/HomePage.tsx78
-rw-r--r--packages/demobank-ui/src/pages/LoginForm.tsx104
-rw-r--r--packages/demobank-ui/src/pages/OperationState/index.ts10
-rw-r--r--packages/demobank-ui/src/pages/OperationState/state.ts162
-rw-r--r--packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx77
-rw-r--r--packages/demobank-ui/src/pages/PublicHistoriesPage.tsx25
-rw-r--r--packages/demobank-ui/src/pages/QrCodeSection.tsx50
-rw-r--r--packages/demobank-ui/src/pages/RegistrationPage.tsx110
-rw-r--r--packages/demobank-ui/src/pages/ShowAccountDetails.tsx123
-rw-r--r--packages/demobank-ui/src/pages/UpdateAccountPassword.tsx60
-rw-r--r--packages/demobank-ui/src/pages/WalletWithdrawForm.tsx60
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx77
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalQRCode.tsx98
-rw-r--r--packages/demobank-ui/src/pages/admin/Account.tsx37
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountForm.tsx72
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountList.tsx34
-rw-r--r--packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx80
-rw-r--r--packages/demobank-ui/src/pages/admin/Home.tsx13
-rw-r--r--packages/demobank-ui/src/pages/admin/RemoveAccount.tsx101
-rw-r--r--packages/demobank-ui/src/pages/business/Home.tsx377
24 files changed, 1043 insertions, 851 deletions
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts
index 9230fb6b1..ef6b4fede 100644
--- a/packages/demobank-ui/src/pages/AccountPage/index.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -14,20 +14,17 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { HttpError, HttpResponseOk, HttpResponsePaginated, utils } from "@gnu-taler/web-util/browser";
-import { AbsoluteTime, AmountJson, PaytoUriIBAN, PaytoUriTalerBank } from "@gnu-taler/taler-util";
-import { Loading } from "../../components/Loading.js";
-import { useComponentState } from "./state.js";
-import { ReadyView, InvalidIbanView } from "./views.js";
+import { AbsoluteTime, AmountJson, TalerCorebankApi, TalerError, TalerErrorDetail } from "@gnu-taler/taler-util";
+import { HttpResponsePaginated, utils } from "@gnu-taler/web-util/browser";
import { VNode } from "preact";
-import { LoginForm } from "../LoginForm.js";
import { ErrorLoading } from "../../components/ErrorLoading.js";
+import { Loading } from "../../components/Loading.js";
+import { LoginForm } from "../LoginForm.js";
+import { useComponentState } from "./state.js";
+import { InvalidIbanView, ReadyView } from "./views.js";
export interface Props {
account: string;
- onLoadNotOk: <T>(
- error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
- ) => VNode;
goToBusinessAccount: () => void;
goToConfirmOperation: (id: string) => void;
}
@@ -42,7 +39,7 @@ export namespace State {
export interface LoadingError {
status: "loading-error";
- error: HttpError<SandboxBackend.SandboxError>;
+ error: TalerError;
}
export interface BaseInfo {
@@ -60,12 +57,12 @@ export namespace State {
export interface InvalidIban {
status: "invalid-iban",
- error: HttpResponseOk<SandboxBackend.CoreBank.AccountData>;
+ error: TalerCorebankApi.AccountData;
}
export interface UserNotFound {
- status: "error-user-not-found",
- error: HttpError<any>;
+ status: "login",
+ reason: "not-found" | "forbidden";
onRegister?: () => void;
}
}
@@ -80,7 +77,7 @@ export interface Transaction {
const viewMapping: utils.StateViewMap<State> = {
loading: Loading,
- "error-user-not-found": LoginForm,
+ "login": LoginForm,
"invalid-iban": InvalidIbanView,
"loading-error": ErrorLoading,
ready: ReadyView,
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts
index ca7e1d447..96d45b7bd 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -14,54 +14,47 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
+import { Amounts, HttpStatusCode, TalerError, TalerErrorCode, parsePaytoUri } from "@gnu-taler/taler-util";
import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
-import { useBackendContext } from "../../context/backend.js";
import { useAccountDetails } from "../../hooks/access.js";
import { Props, State } from "./index.js";
+import { assertUnreachable } from "../HomePage.js";
export function useComponentState({ account, goToBusinessAccount, goToConfirmOperation }: Props): State {
const result = useAccountDetails(account);
- const backend = useBackendContext();
const { i18n } = useTranslationContext();
- if (result.loading) {
+ if (!result) {
return {
status: "loading",
error: undefined,
};
}
- if (!result.ok) {
- if (result.loading || result.type === ErrorType.TIMEOUT) {
- return {
- status: "loading-error",
- error: result,
- };
- }
- //logout if there is any error, not if loading
- // backend.logOut();
- if (result.status === HttpStatusCode.NotFound) {
- notifyError(i18n.str`Username or account label "${account}" not found`, undefined);
- return {
- status: "error-user-not-found",
- error: result,
- };
- }
- if (result.status === HttpStatusCode.Unauthorized) {
- notifyError(i18n.str`Authorization denied`, i18n.str`Maybe the session has expired, login again.`);
- return {
- status: "error-user-not-found",
- error: result,
- };
- }
+ if (result instanceof TalerError) {
return {
status: "loading-error",
error: result,
};
}
- const { data } = result;
+ if (result.type === "fail") {
+ switch (result.case) {
+ case "unauthorized": return {
+ status: "login",
+ reason: "forbidden"
+ }
+ case "not-found": return {
+ status: "login",
+ reason: "not-found",
+ }
+ default: {
+ assertUnreachable(result)
+ }
+ }
+ }
+
+ const { body: data } = result;
const balance = Amounts.parseOrThrow(data.balance.amount);
@@ -71,7 +64,7 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe
if (!payto || !payto.isKnown || (payto.targetType !== "iban" && payto.targetType !== "x-taler-bank")) {
return {
status: "invalid-iban",
- error: result
+ error: data
};
}
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 483cb579a..0604001e3 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -18,14 +18,13 @@ 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 { PaymentOptions } from "../PaymentOptions.js";
import { State } from "./index.js";
export function InvalidIbanView({ error }: State.InvalidIban) {
return (
- <div>Payto from server is not valid &quot;{error.data.payto_uri}&quot;</div>
+ <div>Payto from server is not valid &quot;{error.payto_uri}&quot;</div>
);
}
@@ -75,19 +74,20 @@ function MaybeBusinessButton({
onClick: () => void;
}): VNode {
const { i18n } = useTranslationContext();
- const result = useBusinessAccountDetails(account);
- if (!result.ok) return <Fragment />;
- return (
- <div class="w-full flex justify-end">
- <button
- class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- onClick={(e) => {
- e.preventDefault()
- onClick()
- }}
- >
- <i18n.Translate>Business Profile</i18n.Translate>
- </button>
- </div>
- );
+ return <Fragment />
+ // const result = useBusinessAccountDetails(account);
+ // if (!result.ok) return <Fragment />;
+ // return (
+ // <div class="w-full flex justify-end">
+ // <button
+ // class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+ // onClick={(e) => {
+ // e.preventDefault()
+ // onClick()
+ // }}
+ // >
+ // <i18n.Translate>Business Profile</i18n.Translate>
+ // </button>
+ // </div>
+ // );
}
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index 6ab6ba3e4..c75964f8e 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Amounts, Logger, TranslatedString, parsePaytoUri } from "@gnu-taler/taler-util";
+import { Amounts, Logger, TalerError, TranslatedString, parsePaytoUri } from "@gnu-taler/taler-util";
import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, VNode, h } from "preact";
import { useEffect, useErrorBoundary, useState } from "preact/hooks";
@@ -27,6 +27,7 @@ import { useAccountDetails } from "../hooks/access.js";
import { useSettings } from "../hooks/settings.js";
import { bankUiSettings } from "../settings.js";
import { RenderAmount } from "./PaytoWireTransferForm.js";
+import { Loading } from "../components/Loading.js";
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
@@ -237,7 +238,6 @@ export function BankFrame({
</span>
</span>
<button type="button" data-enabled={settings.showDebugInfo} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
-
onClick={() => {
updateSettings("showDebugInfo", !settings.showDebugInfo);
}}>
@@ -346,14 +346,6 @@ function StatusBanner(): VNode {
</div>
}
<MaybeShowDebugInfo info={n.message.debug} />
- {/* <a href="#" class="text-gray-500">
- show debug info
- </a>
- {n.message.debug &&
- <div class="mt-2 text-sm text-red-700 font-mono break-all">
- {n.message.debug}
- </div>
- } */}
</Attention>
case "info":
return <Attention type="success" title={n.message.title} onClose={() => {
@@ -411,16 +403,22 @@ function WelcomeAccount({ account }: { account: string }): VNode {
const { i18n } = useTranslationContext();
const result = useAccountDetails(account);
- if (!result.ok) return <div />
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <div />
+ }
+ if (result.type === "fail") return <div />
- const payto = parsePaytoUri(result.data.payto_uri)
+ const payto = parsePaytoUri(result.body.payto_uri)
if (!payto) return <div />
const accountNumber = !payto.isKnown ? undefined : payto.targetType === "iban" ? payto.iban : payto.targetType === "x-taler-bank" ? payto.account : undefined;
return <i18n.Translate>
Welcome, {account} {accountNumber !== undefined ?
<span>
- (<a href={result.data.payto_uri}>{accountNumber}</a> <CopyButton getContent={() => result.data.payto_uri} />)
+ (<a href={result.body.payto_uri}>{accountNumber}</a> <CopyButton getContent={() => result.body.payto_uri} />)
</span>
: <Fragment />}!
</i18n.Translate>
@@ -429,10 +427,16 @@ function WelcomeAccount({ account }: { account: string }): VNode {
function AccountBalance({ account }: { account: string }): VNode {
const result = useAccountDetails(account);
- if (!result.ok) return <div />
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <div />
+ }
+ if (result.type === "fail") return <div />
return <RenderAmount
- value={Amounts.parseOrThrow(result.data.balance.amount)}
- negative={result.data.balance.credit_debit_indicator === "debit"}
+ value={Amounts.parseOrThrow(result.body.balance.amount)}
+ negative={result.body.balance.credit_debit_indicator === "debit"}
/>
}
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index 95144f086..bd85cea1e 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -31,12 +31,11 @@ import {
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { Loading } from "../components/Loading.js";
-import { getInitialBackendBaseURL } from "../hooks/backend.js";
+import { useBankCoreApiContext } from "../context/config.js";
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");
@@ -68,7 +67,6 @@ export function HomePage({
account={account}
goToConfirmOperation={goToConfirmOperation}
goToBusinessAccount={goToBusinessAccount}
- onLoadNotOk={handleNotOkResult(i18n)}
/>
);
}
@@ -82,9 +80,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 { api } = useBankCoreApiContext()
const uri = stringifyWithdrawUri({
- bankIntegrationApiBaseUrl: `${baseUrl}/taler-integration`,
+ bankIntegrationApiBaseUrl: api.getIntegrationAPI().baseUrl,
withdrawalOperationId: operationId,
});
const parsedUri = parseWithdrawUri(uri);
@@ -110,76 +108,6 @@ export function WithdrawalOperationPage({
);
}
-export function handleNotOkResult(
- i18n: ReturnType<typeof useTranslationContext>["i18n"],
-): <T>(
- result:
- | HttpResponsePaginated<T, SandboxBackend.SandboxError>
- | HttpResponse<T, SandboxBackend.SandboxError>,
-) => VNode {
- return function handleNotOkResult2<T>(
- result:
- | HttpResponsePaginated<T, SandboxBackend.SandboxError | undefined>
- | HttpResponse<T, SandboxBackend.SandboxError | undefined>,
- ): VNode {
- if (result.loading) return <Loading />;
- if (!result.ok) {
- switch (result.type) {
- case ErrorType.TIMEOUT: {
- notifyError(i18n.str`Request timeout, try again later.`, undefined);
- break;
- }
- case ErrorType.CLIENT: {
- if (result.status === HttpStatusCode.Unauthorized) {
- notifyError(i18n.str`Wrong credentials`, undefined);
- return <LoginForm />;
- }
- const errorData = result.payload;
- notify({
- type: "error",
- 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;
- }
- case ErrorType.SERVER: {
- notify({
- type: "error",
- title: i18n.str`Server returned with error`,
- description: result.payload?.error?.description as TranslatedString,
- debug: JSON.stringify(result.payload),
- });
- break;
- }
- case ErrorType.UNREADABLE: {
- notify({
- 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),
- });
- break;
- }
- case ErrorType.UNEXPECTED: {
- notify({
- type: "error",
- title: i18n.str`Unexpected error.`,
- description: i18n.str`Diagnostic from ${result.info?.url} is "${result.message}"`,
- debug: JSON.stringify(result),
- });
- break;
- }
- default: {
- assertUnreachable(result);
- }
- }
- // route("/")
- return <div>error</div>;
- }
- return <div />;
- };
-}
export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index 3ea94b899..a8167cca5 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -14,28 +14,29 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
-import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { HttpStatusCode, TalerAuthentication, TranslatedString } from "@gnu-taler/taler-util";
+import { ErrorType, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
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";
+import { useBankCoreApiContext } from "../context/config.js";
+import { assertUnreachable } from "./HomePage.js";
/**
* Collect and submit login data.
*/
-export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
+export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forbidden", onRegister?: () => void }): VNode {
const backend = useBackendContext();
const currentUser = backend.state.status !== "loggedOut" ? backend.state.username : undefined
const [username, setUsername] = useState<string | undefined>(currentUser);
const [password, setPassword] = useState<string | undefined>();
const { i18n } = useTranslationContext();
- const { requestNewLoginToken, refreshLoginToken } = useCredentialsChecker();
+ const { api } = useBankCoreApiContext();
/**
@@ -70,10 +71,6 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
password: !password ? i18n.str`Missing password` : undefined,
}) ?? busy;
- function saveError({ title, description, debug }: { title: TranslatedString, description?: TranslatedString, debug?: any }) {
- notifyError(title, description, debug)
- }
-
async function doLogout() {
backend.logOut()
}
@@ -81,63 +78,42 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
async function doLogin() {
if (!username || !password) return;
setBusy({})
- const result = await requestNewLoginToken(username, password);
- if (result.valid) {
- backend.logIn({ username, token: result.token });
+ const data: TalerAuthentication.TokenRequest = {
+ // scope: "readwrite" as "write", //FIX: different than merchant
+ scope: "readwrite",
+ duration: {
+ d_us: "forever" //FIX: should return shortest
+ // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
+ },
+ refreshable: true,
+ }
+ const resp = await api.getAuthenticationAPI(username).createAccessToken(password, {
+ // scope: "readwrite" as "write", //FIX: different than merchant
+ scope: "readwrite",
+ duration: {
+ d_us: "forever" //FIX: should return shortest
+ // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
+ },
+ refreshable: true,
+ })
+ if (resp.type === "ok") {
+ backend.logIn({ username, token: resp.body.access_token });
} else {
- const { cause } = result;
- switch (cause.type) {
- case ErrorType.CLIENT: {
- if (cause.status === HttpStatusCode.Unauthorized) {
- saveError({
- title: i18n.str`Wrong credentials for "${username}"`,
- });
- } else
- if (cause.status === HttpStatusCode.NotFound) {
- saveError({
- title: i18n.str`Account not found`,
- });
- } else {
- saveError({
- 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),
- });
- }
- break;
- }
- case ErrorType.SERVER: {
- saveError({
- title: i18n.str`Server had a problem, try again later or report.`,
- // description: cause.payload.error.description,
- debug: JSON.stringify(cause.payload),
- });
- break;
- }
- case ErrorType.TIMEOUT: {
- saveError({
- title: i18n.str`Request timeout, try again later.`,
- });
- break;
- }
- case ErrorType.UNREADABLE: {
- saveError({
- title: i18n.str`Unexpected error.`,
- description: `Response from ${cause.info?.url} is unreadable, http status: ${cause.status}` as TranslatedString,
- debug: JSON.stringify(cause),
- });
- break;
- }
- default: {
- saveError({
- title: i18n.str`Unexpected error, please report.`,
- description: `Diagnostic from ${cause.info?.url} is "${cause.message}"` as TranslatedString,
- debug: JSON.stringify(cause),
- });
- break;
- }
+ switch (resp.case) {
+ case "wrong-credentials": return notify({
+ type: "error",
+ title: i18n.str`Wrong credentials for "${username}"`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Account not found`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
}
- // backend.logOut();
}
setPassword(undefined);
setBusy(undefined)
diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts b/packages/demobank-ui/src/pages/OperationState/index.ts
index b347fd942..bc3555c48 100644
--- a/packages/demobank-ui/src/pages/OperationState/index.ts
+++ b/packages/demobank-ui/src/pages/OperationState/index.ts
@@ -14,8 +14,8 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { AbsoluteTime, AmountJson, WithdrawUriResult } from "@gnu-taler/taler-util";
-import { HttpError, utils } from "@gnu-taler/web-util/browser";
+import { AbsoluteTime, AmountJson, TalerCoreBankErrorsByMethod, TalerError, TalerErrorDetail, TranslatedString, WithdrawUriResult } from "@gnu-taler/taler-util";
+import { utils } from "@gnu-taler/web-util/browser";
import { ErrorLoading } from "../../components/ErrorLoading.js";
import { Loading } from "../../components/Loading.js";
import { useComponentState } from "./state.js";
@@ -44,7 +44,7 @@ export namespace State {
export interface LoadingError {
status: "loading-error";
- error: HttpError<SandboxBackend.SandboxError>;
+ error: TalerError;
}
/**
@@ -61,7 +61,7 @@ export namespace State {
export interface InvalidPayto {
status: "invalid-payto",
error: undefined;
- payto: string | null;
+ payto: string | undefined;
onClose: () => void;
}
export interface InvalidWithdrawal {
@@ -74,7 +74,7 @@ export namespace State {
status: "invalid-reserve",
error: undefined;
onClose: () => void;
- reserve: string | null;
+ reserve: string | undefined;
}
export interface NeedConfirmation {
status: "need-confirmation",
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
index 4be680377..148571ec9 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -14,20 +14,26 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
+import { Amounts, HttpStatusCode, TalerError, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
import { RequestError, notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
import { useEffect, useState } from "preact/hooks";
-import { useAccessAPI, useAccessAnonAPI, useWithdrawalDetails } from "../../hooks/access.js";
-import { getInitialBackendBaseURL } from "../../hooks/backend.js";
+import { useBankCoreApiContext } from "../../context/config.js";
+import { useWithdrawalDetails } from "../../hooks/access.js";
+import { useBackendState } from "../../hooks/backend.js";
import { useSettings } from "../../hooks/settings.js";
import { buildRequestErrorMessage } from "../../utils.js";
import { Props, State } from "./index.js";
+import { assertUnreachable } from "../HomePage.js";
+import { mutate } from "swr";
export function useComponentState({ currency, onClose }: Props): utils.RecursiveState<State> {
const { i18n } = useTranslationContext();
const [settings, updateSettings] = useSettings()
- const { createWithdrawal } = useAccessAPI();
- const { abortWithdrawal, confirmWithdrawal } = useAccessAnonAPI();
+ const { state: credentials } = useBackendState()
+ const creds = credentials.status !== "loggedIn" ? undefined : credentials
+ const { api } = useBankCoreApiContext()
+ // const { createWithdrawal } = useAccessAPI();
+ // const { abortWithdrawal, confirmWithdrawal } = useAccessAnonAPI();
const [busy, setBusy] = useState<Record<string, undefined>>()
const amount = settings.maxWithdrawalAmount
@@ -37,27 +43,33 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
try {
- const result = await createWithdrawal({
+ if (!creds) return;
+ const resp = await api.createWithdrawal(creds, {
amount: Amounts.stringify(parsedAmount),
});
- const uri = parseWithdrawUri(result.data.taler_withdraw_uri);
+ if (resp.type === "fail") {
+ switch (resp.case) {
+ case "insufficient-funds": return notify({
+ type: "error",
+ title: i18n.str`The operation was rejected due to insufficient funds`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ default: assertUnreachable(resp.case)
+ }
+ }
+
+ const uri = parseWithdrawUri(resp.body.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}`);
+ i18n.str`Withdraw URI: ${resp.body.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,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -76,8 +88,6 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
}
}, [settings.fastWithdrawal, amount])
- const baseUrl = getInitialBackendBaseURL()
-
if (!withdrawalOperationId) {
return {
status: "loading",
@@ -90,18 +100,24 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
async function doAbort() {
try {
setBusy({})
- await abortWithdrawal(wid);
- onClose();
+ const resp = await api.abortWithdrawalById(wid);
+ setBusy(undefined)
+ if (resp.type === "ok") {
+ onClose();
+ } else {
+ switch (resp.case) {
+ case "previously-confirmed": return notify({
+ type: "error",
+ title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp.case)
+ }
+ }
} 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,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -111,28 +127,38 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
)
}
}
- setBusy(undefined)
}
async function doConfirm() {
try {
setBusy({})
- await confirmWithdrawal(wid);
- if (!settings.showWithdrawalSuccess) {
- notifyInfo(i18n.str`Wire transfer completed!`)
+ const resp = await api.confirmWithdrawalById(wid);
+ if (resp.type === "ok") {
+ mutate(() => true)//clean withdrawal state
+ if (!settings.showWithdrawalSuccess) {
+ notifyInfo(i18n.str`Wire transfer completed!`)
+ }
+ setBusy(undefined)
+ } else {
+ switch (resp.case) {
+ case "previously-aborted": return notify({
+ type: "error",
+ title: i18n.str`The withdrawal has been aborted previously and can't be confirmed`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "no-exchange-or-reserve-selected": return notify({
+ type: "error",
+ title: i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
}
} 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,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -142,11 +168,10 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
)
}
}
- setBusy(undefined)
}
- const bankIntegrationApiBaseUrl = `${baseUrl}/taler-integration`
+
const uri = stringifyWithdrawUri({
- bankIntegrationApiBaseUrl,
+ bankIntegrationApiBaseUrl: api.getIntegrationAPI().baseUrl,
withdrawalOperationId,
});
const parsedUri = parseWithdrawUri(uri);
@@ -161,32 +186,43 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
return (): utils.RecursiveState<State> => {
const result = useWithdrawalDetails(withdrawalOperationId);
- const shouldCreateNewOperation = !result.ok && !result.loading && result.info.status === HttpStatusCode.NotFound
+ const shouldCreateNewOperation = result && !(result instanceof TalerError)
useEffect(() => {
if (shouldCreateNewOperation) {
doSilentStart()
}
}, [])
- if (!result.ok) {
- if (result.loading) {
- return {
- status: "loading",
- error: undefined
- }
- }
- if (result.info.status === HttpStatusCode.NotFound) {
- return {
- status: "loading",
- error: undefined,
- }
+ if (!result) {
+ return {
+ status: "loading",
+ error: undefined
}
+ }
+ if (result instanceof TalerError) {
return {
status: "loading-error",
error: result
}
}
- const { data } = result;
+
+ if (result.type === "fail") {
+ switch (result.case) {
+ case "not-found": {
+ return {
+ status: "aborted",
+ error: undefined,
+ onClose: async () => {
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose()
+ },
+ }
+ }
+ default: assertUnreachable(result.case)
+ }
+ }
+
+ const { body: data } = result;
if (data.aborted) {
return {
status: "aborted",
@@ -247,8 +283,6 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
}
}
-
- // goToConfirmOperation(withdrawalOperationId)
return {
status: "need-confirmation",
error: undefined,
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 52dbd4ff6..7861bb0b3 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -16,9 +16,11 @@
import {
AmountJson,
+ AmountString,
Amounts,
HttpStatusCode,
Logger,
+ TalerError,
TranslatedString,
buildPayto,
parsePaytoUri,
@@ -30,17 +32,18 @@ import {
notifyError,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
-import { h, VNode, Fragment, Ref } from "preact";
+import { Fragment, Ref, VNode, h } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { useAccessAPI } from "../hooks/access.js";
import {
buildRequestErrorMessage,
undefinedIfEmpty,
validateIBAN,
} from "../utils.js";
-import { useConfigState } from "../hooks/config.js";
-import { useConfigContext } from "../context/config.js";
+import { useBankCoreApiContext } from "../context/config.js";
+import { useBackendState } from "../hooks/backend.js";
+import { assertUnreachable } from "./HomePage.js";
+import { mutate } from "swr";
const logger = new Logger("PaytoWireTransferForm");
@@ -59,6 +62,8 @@ export function PaytoWireTransferForm({
}): VNode {
const [isRawPayto, setIsRawPayto] = useState(false);
// FIXME: remove this
+ const { state: credentials } = useBackendState()
+ const { api } = useBankCoreApiContext();
const [iban, setIban] = useState<string | undefined>();
const [subject, setSubject] = useState<string | undefined>();
const [amount, setAmount] = useState<string | undefined>();
@@ -95,7 +100,7 @@ export function PaytoWireTransferForm({
: undefined,
});
- const { createTransaction } = useAccessAPI();
+ // const { createTransaction } = useAccessAPI();
const parsed = !rawPaytoInput ? undefined : parsePaytoUri(rawPaytoInput);
@@ -119,36 +124,54 @@ export function PaytoWireTransferForm({
async function doSend() {
let payto_uri: string | undefined;
-
+ let sendingAmount: AmountString | undefined;
if (rawPaytoInput) {
- payto_uri = rawPaytoInput
+ const p = parsePaytoUri(rawPaytoInput)
+ if (!p) return;
+ sendingAmount = p.params.amount
+ delete p.params.amount
+ //it should have message
+ payto_uri = stringifyPaytoUri(p)
} else {
if (!iban || !subject) return;
const ibanPayto = buildPayto("iban", iban, undefined);
ibanPayto.params.message = encodeURIComponent(subject);
payto_uri = stringifyPaytoUri(ibanPayto);
+ sendingAmount = `${limit.currency}:${trimmedAmountStr}`
}
try {
- await createTransaction({
+ if (credentials.status !== "loggedIn") return;
+ const res = await api.createTransaction(credentials, {
payto_uri,
- amount: `${limit.currency}:${amount}`,
+ amount: sendingAmount,
});
+ mutate(() => true)
+ if (res.type === "fail") {
+ switch (res.case) {
+ case "invalid-input": return notify({
+ type: "error",
+ title: i18n.str`The request was invalid or the payto://-URI used unacceptable features.`,
+ description: res.detail.hint as TranslatedString,
+ debug: res.detail,
+ })
+ case "unauthorized": return notify({
+ type: "error",
+ title: i18n.str`Not enough permission to complete the operation.`,
+ description: res.detail.hint as TranslatedString,
+ debug: res.detail,
+ })
+ default: assertUnreachable(res)
+ }
+ }
onSuccess();
setAmount(undefined);
setIban(undefined);
setSubject(undefined);
rawPaytoInputSetter(undefined)
} catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.BadRequest
- ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.`
- : undefined,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -179,11 +202,13 @@ export function PaytoWireTransferForm({
if (amount) {
setAmount(Amounts.stringifyValue(amount))
}
- const subject = parsed.params["subject"]
+ const subject = parsed.params["message"]
if (subject) {
setSubject(subject)
}
}
+ //payto://iban/DE9714548806481?amount=LOCAL%3A2&message=011Y8V8KDCPFDEKPDZTHS7KZ14GHX7BVWKRDDPZ1N75TJ90166T0
+ //payto://iban/DE9714548806481?receiver-name=Exchanger&amount=LOCAL%3A2&message=011Y8V8KDCPFDEKPDZTHS7KZ14GHX7BVWKRDDPZ1N75TJ90166T0
setIsRawPayto(false)
}} />
<span class="flex flex-1">
@@ -298,7 +323,7 @@ export function PaytoWireTransferForm({
/>
<ShowInputErrorLabel
message={errorsWire?.amount}
- isDirty={subject !== undefined}
+ isDirty={trimmedAmountStr !== undefined}
/>
<p class="mt-2 text-sm text-gray-500" >amount to transfer</p>
</div>
@@ -394,7 +419,7 @@ export function InputAmount(
},
ref: Ref<HTMLInputElement>,
): VNode {
- const cfg = useConfigContext()
+ const { config } = useBankCoreApiContext()
return (
<div class="mt-2">
<div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
@@ -418,8 +443,8 @@ export function InputAmount(
if (!onChange) return;
const l = e.currentTarget.value.length
const sep_pos = e.currentTarget.value.indexOf(FRAC_SEPARATOR)
- if (sep_pos !== -1 && l - sep_pos - 1 > cfg.currency_fraction_limit) {
- e.currentTarget.value = e.currentTarget.value.substring(0, sep_pos + cfg.currency_fraction_limit + 1)
+ if (sep_pos !== -1 && l - sep_pos - 1 > config.currency.num_fractional_input_digits) {
+ e.currentTarget.value = e.currentTarget.value.substring(0, sep_pos + config.currency.num_fractional_input_digits + 1)
}
onChange(e.currentTarget.value);
}}
@@ -431,11 +456,11 @@ export function InputAmount(
}
export function RenderAmount({ value, negative }: { value: AmountJson, negative?: boolean }): VNode {
- const cfg = useConfigContext()
+ const { config } = useBankCoreApiContext()
const str = Amounts.stringifyValue(value)
const sep_pos = str.indexOf(FRAC_SEPARATOR)
- if (sep_pos !== -1 && str.length - sep_pos - 1 > cfg.currency_fraction_digits) {
- const limit = sep_pos + cfg.currency_fraction_digits + 1
+ if (sep_pos !== -1 && str.length - sep_pos - 1 > config.currency.num_fractional_normal_digits) {
+ const limit = sep_pos + config.currency.num_fractional_normal_digits + 1
const normal = str.substring(0, limit)
const small = str.substring(limit)
return <span class="whitespace-nowrap">
diff --git a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
index 680368919..d33353180 100644
--- a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
+++ b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
@@ -14,35 +14,36 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Logger } from "@gnu-taler/taler-util";
+import { Logger, TalerError } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
+import { Loading } from "../components/Loading.js";
import { Transactions } from "../components/Transactions/index.js";
import { usePublicAccounts } from "../hooks/access.js";
-import { handleNotOkResult } from "./HomePage.js";
-import { Loading } from "../components/Loading.js";
const logger = new Logger("PublicHistoriesPage");
-interface Props {}
+interface Props { }
/**
* Show histories of public accounts.
*/
-export function PublicHistoriesPage({}: Props): VNode {
+export function PublicHistoriesPage({ }: Props): VNode {
const { i18n } = useTranslationContext();
const result = usePublicAccounts();
+ const firstAccount = result && !(result instanceof TalerError) && result.data.public_accounts.length > 0
+ ? result.data.public_accounts[0].account_name
+ : undefined;
- const [showAccount, setShowAccount] = useState(
- result.ok && result.data.public_accounts.length > 0
- ? result.data.public_accounts[0].account_name
- : undefined,
- );
+ const [showAccount, setShowAccount] = useState(firstAccount);
- if (!result.ok) {
- return handleNotOkResult(i18n)(result);
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <Loading />
}
const { data } = result;
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index e07525ab4..109993aae 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -17,6 +17,7 @@
import {
HttpStatusCode,
stringifyWithdrawUri,
+ TalerError,
TranslatedString,
WithdrawUriResult,
} from "@gnu-taler/taler-util";
@@ -29,8 +30,9 @@ import {
import { Fragment, h, VNode } from "preact";
import { useEffect } from "preact/hooks";
import { QR } from "../components/QR.js";
-import { useAccessAnonAPI } from "../hooks/access.js";
import { buildRequestErrorMessage } from "../utils.js";
+import { useBankCoreApiContext } from "../context/config.js";
+import { assertUnreachable } from "./HomePage.js";
export function QrCodeSection({
withdrawUri,
@@ -50,22 +52,30 @@ export function QrCodeSection({
}, []);
const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
- const { abortWithdrawal } = useAccessAnonAPI();
+ const { api } = useBankCoreApiContext()
async function doAbort() {
try {
- await abortWithdrawal(withdrawUri.withdrawalOperationId);
- onAborted();
+ const result = await api.abortWithdrawalById(withdrawUri.withdrawalOperationId);
+ if (result.type === "ok") {
+ onAborted();
+ } else {
+ switch (result.case) {
+ case "previously-confirmed": {
+ notify({
+ type: "info",
+ title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`
+ })
+ break;
+ }
+ default: {
+ assertUnreachable(result.case)
+ }
+ }
+ }
} 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,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -120,13 +130,13 @@ export function QrCodeSection({
</div>
</div>
<div class="flex items-center justify-center gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
- <button type="button"
- // class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md px-3 py-2 text-sm font-semibold text-black shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
- class="text-sm font-semibold leading-6 text-gray-900"
- onClick={doAbort}
- >
- Cancel
- </button>
+ <button type="button"
+ // class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md px-3 py-2 text-sm font-semibold text-black shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
+ class="text-sm font-semibold leading-6 text-gray-900"
+ onClick={doAbort}
+ >
+ Cancel
+ </button>
</div>
</div>
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index 9ac93bb34..fda2d904d 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -13,7 +13,7 @@
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { HttpStatusCode, Logger, TranslatedString } from "@gnu-taler/taler-util";
+import { AccessToken, HttpStatusCode, Logger, TalerError, TranslatedString } from "@gnu-taler/taler-util";
import {
RequestError,
notify,
@@ -23,12 +23,11 @@ import {
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { useBackendContext } from "../context/backend.js";
-import { useTestingAPI } from "../hooks/access.js";
import { bankUiSettings } from "../settings.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { getRandomPassword, getRandomUsername } from "./rnd.js";
-import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js";
+import { useBankCoreApiContext } from "../context/config.js";
const logger = new Logger("RegistrationPage");
@@ -63,9 +62,9 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
const [phone, setPhone] = useState<string | undefined>();
const [email, setEmail] = useState<string | undefined>();
const [repeatPassword, setRepeatPassword] = useState<string | undefined>();
- const { requestNewLoginToken } = useCredentialsChecker()
- const { register } = useTestingAPI();
+ const { api } = useBankCoreApiContext()
+ // const { register } = useTestingAPI();
const { i18n } = useTranslationContext();
const errors = undefinedIfEmpty({
@@ -95,26 +94,77 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
: undefined,
});
+ async function doRegistrationAndLogin(name: string | undefined, username: string, password: string) {
+ const creationResponse = await api.createAccount("" as AccessToken, { name: name ?? "", username, password });
+ if (creationResponse.type === "fail") {
+ switch (creationResponse.case) {
+ case "invalid-input": return notify({
+ type: "error",
+ title: i18n.str`Some of the input fields are invalid.`,
+ description: creationResponse.detail.hint as TranslatedString,
+ debug: creationResponse.detail,
+ })
+ case "unable-to-create": return notify({
+ type: "error",
+ title: i18n.str`Unable to create that account.`,
+ description: creationResponse.detail.hint as TranslatedString,
+ debug: creationResponse.detail,
+ })
+ case "unauthorized": return notify({
+ type: "error",
+ title: i18n.str`No enough permission to create that account.`,
+ description: creationResponse.detail.hint as TranslatedString,
+ debug: creationResponse.detail,
+ })
+ case "already-exist": return notify({
+ type: "error",
+ title: i18n.str`That username is already taken`,
+ description: creationResponse.detail.hint as TranslatedString,
+ debug: creationResponse.detail,
+ })
+ default: assertUnreachable(creationResponse)
+ }
+ }
+ const resp = await api.getAuthenticationAPI(username).createAccessToken(password, {
+ // scope: "readwrite" as "write", //FIX: different than merchant
+ scope: "readwrite",
+ duration: {
+ d_us: "forever" //FIX: should return shortest
+ // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
+ },
+ refreshable: true,
+ })
+
+ if (resp.type === "ok") {
+ backend.logIn({ username, token: resp.body.access_token });
+ } else {
+ switch (resp.case) {
+ case "wrong-credentials": return notify({
+ type: "error",
+ title: i18n.str`Wrong credentials for "${username}"`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Account not found`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
+ }
+ }
+
async function doRegistrationStep() {
if (!username || !password) return;
try {
- await register({ name: name ?? "", username, password });
- const resp = await requestNewLoginToken(username, password)
+ await doRegistrationAndLogin(name, username, password)
setUsername(undefined);
- if (resp.valid) {
- backend.logIn({ username, token: resp.token });
- }
onComplete();
} catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.Conflict
- ? i18n.str`That username is already taken`
- : undefined,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -143,27 +193,11 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
setPassword(undefined);
setRepeatPassword(undefined);
const username = `_${user.first}-${user.second}_`
- await register({ username, name: `${user.first} ${user.second}`, password: pass });
- const resp = await requestNewLoginToken(username, pass)
- if (resp.valid) {
- backend.logIn({ username, token: resp.token });
- }
+ await doRegistrationAndLogin(name, username, pass)
onComplete();
} catch (error) {
- if (error instanceof RequestError) {
- if (tries > 0) {
- await delay(200)
- await doRandomRegistration(tries - 1)
- } else {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.Conflict
- ? i18n.str`Could not create a random user`
- : undefined,
- }),
- );
- }
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
index 6acf0361e..3534f9733 100644
--- a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -1,67 +1,88 @@
-import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { HttpStatusCode, TalerCorebankApi, TalerError, TranslatedString } from "@gnu-taler/taler-util";
+import { 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 { ErrorLoading } from "../components/ErrorLoading.js";
+import { Loading } from "../components/Loading.js";
+import { useBankCoreApiContext } from "../context/config.js";
+import { useAccountDetails } from "../hooks/access.js";
+import { useBackendState } from "../hooks/backend.js";
+import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { assertUnreachable } from "./HomePage.js";
+import { LoginForm } from "./LoginForm.js";
import { AccountForm } from "./admin/AccountForm.js";
export function ShowAccountDetails({
account,
onClear,
onUpdateSuccess,
- onLoadNotOk,
onChangePassword,
}: {
- onLoadNotOk: <T>(
- error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
- ) => VNode;
onClear?: () => void;
onChangePassword: () => void;
onUpdateSuccess: () => void;
account: string;
}): VNode {
const { i18n } = useTranslationContext();
- const result = useBusinessAccountDetails(account);
- const { updateAccount } = useAdminAccountAPI();
+ const { state: credentials } = useBackendState();
+ const creds = credentials.status !== "loggedIn" ? undefined : credentials
+ const { api } = useBankCoreApiContext()
+
const [update, setUpdate] = useState(false);
- const [submitAccount, setSubmitAccount] = useState<
- SandboxBackend.Circuit.CircuitAccountData | undefined
- >();
+ const [submitAccount, setSubmitAccount] = useState<TalerCorebankApi.AccountData | undefined>();
- if (!result.ok) {
- if (result.loading || result.type === ErrorType.TIMEOUT) {
- return onLoadNotOk(result);
- }
- if (result.status === HttpStatusCode.NotFound) {
- return <div>account not found</div>;
+ const result = useAccountDetails(account);
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <ErrorLoading error={result} />
+ }
+ if (result.type === "fail") {
+ switch (result.case) {
+ case "not-found": return <LoginForm reason="not-found" />
+ case "unauthorized": return <LoginForm reason="forbidden" />
+ default: assertUnreachable(result)
}
- return onLoadNotOk(result);
}
async function doUpdate() {
if (!update) {
setUpdate(true);
} else {
- if (!submitAccount) return;
+ if (!submitAccount || !creds) return;
try {
- await updateAccount(account, {
- cashout_address: submitAccount.cashout_address,
- contact_data: submitAccount.contact_data,
+ const resp = await api.updateAccount(creds, {
+ cashout_address: submitAccount.cashout_payto_uri,
+ challenge_contact_data: undefinedIfEmpty({
+ email: submitAccount.contact_data?.email,
+ phone: submitAccount.contact_data?.phone,
+ }),
+ is_exchange: false,
+ name: submitAccount.name,
});
- onUpdateSuccess();
+ if (resp.type === "ok") {
+ onUpdateSuccess();
+ } else {
+ switch (resp.case) {
+ case "unauthorized": return notify({
+ type: "error",
+ title: i18n.str`The rights to change the account are not sufficient`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`The username was not found`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
+ }
} catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.Forbidden
- ? i18n.str`The rights to change the account are not sufficient`
- : status === HttpStatusCode.NotFound
- ? i18n.str`The username was not found`
- : undefined,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -86,24 +107,24 @@ export function ShowAccountDetails({
}
</h2>
<div class="mt-4">
- <div class="flex items-center justify-between">
- <span class="flex flex-grow flex-col">
- <span class="text-sm text-black font-medium leading-6 " id="availability-label">
- <i18n.Translate>change the account details</i18n.Translate>
- </span>
- </span>
- <button type="button" data-enabled={!update} class="bg-indigo-600 data-[enabled=true]:bg-gray-200 relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer rounded-full ring-2 border-gray-600 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
- onClick={() => {
- setUpdate(!update)
- }}>
- <span aria-hidden="true" data-enabled={!update} class="translate-x-5 data-[enabled=true]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
- </button>
- </div>
- </div>
+ <div class="flex items-center justify-between">
+ <span class="flex flex-grow flex-col">
+ <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+ <i18n.Translate>change the account details</i18n.Translate>
+ </span>
+ </span>
+ <button type="button" data-enabled={!update} class="bg-indigo-600 data-[enabled=true]:bg-gray-200 relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer rounded-full ring-2 border-gray-600 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
+ onClick={() => {
+ setUpdate(!update)
+ }}>
+ <span aria-hidden="true" data-enabled={!update} class="translate-x-5 data-[enabled=true]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+ </button>
+ </div>
+ </div>
</div>
<AccountForm
- template={result.data}
+ template={result.body}
purpose={update ? "update" : "show"}
onChange={(a) => setSubmitAccount(a)}
>
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index 46f4fe0ef..ac6e9fa9b 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -1,43 +1,33 @@
-import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
-import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { TalerError, TranslatedString } from "@gnu-taler/taler-util";
+import { HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
-import { useEffect, useRef, useState } from "preact/hooks";
+import { 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";
+import { useBankCoreApiContext } from "../context/config.js";
+import { assertUnreachable } from "./HomePage.js";
+import { useBackendState } from "../hooks/backend.js";
export function UpdateAccountPassword({
account,
onCancel,
onUpdateSuccess,
- onLoadNotOk,
focus,
}: {
- onLoadNotOk: <T>(
- error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
- ) => VNode;
onCancel: () => void;
focus?: boolean,
onUpdateSuccess: () => void;
account: string;
}): VNode {
const { i18n } = useTranslationContext();
- const result = useBusinessAccountDetails(account);
- const { changePassword } = useAdminAccountAPI();
+ const { state: credentials } = useBackendState();
+ const creds = credentials.status !== "loggedIn" ? undefined : credentials
+ const { api } = useBankCoreApiContext();
+
const [password, setPassword] = useState<string | undefined>();
const [repeat, setRepeat] = useState<string | undefined>();
- if (!result.ok) {
- if (result.loading || result.type === ErrorType.TIMEOUT) {
- return onLoadNotOk(result);
- }
- if (result.status === HttpStatusCode.NotFound) {
- return <div>account not found</div>;
- }
- return onLoadNotOk(result);
- }
-
const errors = undefinedIfEmpty({
password: !password ? i18n.str`required` : undefined,
repeat: !repeat
@@ -48,15 +38,35 @@ export function UpdateAccountPassword({
});
async function doChangePassword() {
- if (!!errors || !password) return;
+ if (!!errors || !password || !creds) return;
try {
- const r = await changePassword(account, {
+ const resp = await api.updatePassword(creds, {
new_password: password,
});
- onUpdateSuccess();
+ if (resp.type === "ok") {
+ onUpdateSuccess();
+ } else {
+ switch (resp.case) {
+ case "unauthorized": {
+ notify({
+ type: "error",
+ title: i18n.str`Not authorized to change the password, maybe the session is invalid.`
+ })
+ break;
+ }
+ case "not-found": {
+ notify({
+ type: "error",
+ title: i18n.str`Account not found`
+ })
+ break;
+ }
+ default: assertUnreachable(resp)
+ }
+ }
} catch (error) {
- if (error instanceof RequestError) {
- notify(buildRequestErrorMessage(i18n, error.cause));
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(i18n.str`Operation failed, please report`, (error instanceof Error
? error.message
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index da299b1c8..2d80bad1f 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -19,9 +19,9 @@ import {
Amounts,
HttpStatusCode,
Logger,
+ TalerError,
TranslatedString,
- WithdrawUriResult,
- parseWithdrawUri,
+ parseWithdrawUri
} from "@gnu-taler/taler-util";
import {
RequestError,
@@ -31,13 +31,15 @@ import {
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { forwardRef } from "preact/compat";
-import { useEffect, useRef, useState } from "preact/hooks";
-import { useAccessAPI } from "../hooks/access.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
-import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js";
+import { useState } from "preact/hooks";
+import { Attention } from "../components/Attention.js";
+import { useBankCoreApiContext } from "../context/config.js";
+import { useBackendState } from "../hooks/backend.js";
import { useSettings } from "../hooks/settings.js";
+import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { assertUnreachable } from "./HomePage.js";
import { OperationState } from "./OperationState/index.js";
-import { Attention } from "../components/Attention.js";
+import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js";
const logger = new Logger("WalletWithdrawForm");
const RefAmount = forwardRef(InputAmount);
@@ -52,7 +54,10 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
const { i18n } = useTranslationContext();
const [settings, updateSettings] = useSettings()
- const { createWithdrawal } = useAccessAPI();
+ const { state: credentials } = useBackendState();
+ const creds = credentials.status !== "loggedIn" ? undefined : credentials
+
+ const { api } = useBankCoreApiContext()
const [amountStr, setAmountStr] = useState<string | undefined>(`${settings.maxWithdrawalAmount}`);
if (!!settings.currentWithdrawalOperationId) {
@@ -81,30 +86,33 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
});
async function doStart() {
- if (!parsedAmount) return;
+ if (!parsedAmount || !creds) return;
try {
- const result = await createWithdrawal({
+ const result = await api.createWithdrawal(creds, {
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}`);
+ if (result.type === "ok") {
+ const uri = parseWithdrawUri(result.body.taler_withdraw_uri);
+ if (!uri) {
+ return notifyError(
+ i18n.str`Server responded with an invalid withdraw URI`,
+ i18n.str`Withdraw URI: ${result.body.taler_withdraw_uri}`);
+ } else {
+ updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId)
+ goToConfirmOperation(uri.withdrawalOperationId);
+ }
} else {
- updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId)
- goToConfirmOperation(uri.withdrawalOperationId);
+ switch (result.case) {
+ case "insufficient-funds": {
+ notify({ type: "error", title: i18n.str`The operation was rejected due to insufficient funds` })
+ break;
+ }
+ default: assertUnreachable(result.case)
+ }
}
} 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,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index ddcd2492d..602ec9bd8 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -22,6 +22,7 @@ import {
PaytoUri,
PaytoUriIBAN,
PaytoUriTalerBank,
+ TalerError,
TranslatedString,
WithdrawUriResult
} from "@gnu-taler/taler-util";
@@ -35,10 +36,12 @@ import {
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 { useSettings } from "../hooks/settings.js";
import { RenderAmount } from "./PaytoWireTransferForm.js";
+import { useBankCoreApiContext } from "../context/config.js";
+import { assertUnreachable } from "./HomePage.js";
+import { mutate } from "swr";
const logger = new Logger("WithdrawalConfirmationQuestion");
@@ -70,7 +73,7 @@ export function WithdrawalConfirmationQuestion({
};
}, []);
- const { confirmWithdrawal, abortWithdrawal } = useAccessAnonAPI();
+ const { api } = useBankCoreApiContext()
const [captchaAnswer, setCaptchaAnswer] = useState<string | undefined>();
const answer = parseInt(captchaAnswer ?? "", 10);
const [busy, setBusy] = useState<Record<string, undefined>>()
@@ -87,24 +90,32 @@ export function WithdrawalConfirmationQuestion({
async function doTransfer() {
try {
setBusy({})
- await confirmWithdrawal(
- withdrawUri.withdrawalOperationId,
- );
- if (!settings.showWithdrawalSuccess) {
- notifyInfo(i18n.str`Wire transfer completed!`)
+ const resp = await api.confirmWithdrawalById(withdrawUri.withdrawalOperationId);
+ if (resp.type === "ok") {
+ mutate(() => true)// clean any info that we have
+ if (!settings.showWithdrawalSuccess) {
+ notifyInfo(i18n.str`Wire transfer completed!`)
+ }
+ } else {
+ switch (resp.case) {
+ case "previously-aborted": return notify({
+ type: "error",
+ title: i18n.str`The withdrawal has been aborted previously and can't be confirmed`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "no-exchange-or-reserve-selected": return notify({
+ type: "error",
+ title: i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ default: assertUnreachable(resp)
+ }
}
} 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,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -120,18 +131,26 @@ export function WithdrawalConfirmationQuestion({
async function doCancel() {
try {
setBusy({})
- await abortWithdrawal(withdrawUri.withdrawalOperationId);
- onAborted();
+ const resp = await api.abortWithdrawalById(withdrawUri.withdrawalOperationId);
+ if (resp.type === "ok") {
+ onAborted();
+ } else {
+ switch (resp.case) {
+ case "previously-confirmed": {
+ notify({
+ type: "error",
+ title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`
+ });
+ break;
+ }
+ default: {
+ assertUnreachable(resp.case)
+ }
+ }
+ }
} 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,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 35fb94a6c..15910201e 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -16,17 +16,17 @@
import {
Amounts,
- HttpStatusCode,
Logger,
+ TalerError,
WithdrawUriResult,
parsePaytoUri
} from "@gnu-taler/taler-util";
-import { ErrorType, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
+import { ErrorLoading } from "../components/ErrorLoading.js";
import { Loading } from "../components/Loading.js";
import { useWithdrawalDetails } from "../hooks/access.js";
-import { useSettings } from "../hooks/settings.js";
-import { handleNotOkResult } from "./HomePage.js";
+import { assertUnreachable } from "./HomePage.js";
import { QrCodeSection } from "./QrCodeSection.js";
import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
@@ -48,48 +48,20 @@ export function WithdrawalQRCode({
const { i18n } = useTranslationContext();
const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
- if (!result.ok) {
- if (result.loading) {
- return <Loading />;
- }
- if (result.type === ErrorType.CLIENT && result.status === HttpStatusCode.NotFound) {
- return <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6">
- <div>
- <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100 ">
- <svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
- <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
- </svg>
- </div>
-
- <div class="mt-3 text-center sm:mt-5">
- <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
- <i18n.Translate>Operation not found</i18n.Translate>
- </h3>
- <div class="mt-2">
- <p class="text-sm text-gray-500">
- <i18n.Translate>
- This operation is not known by the server. The operation id is wrong or the
- server deleted the operation information before reaching here.
- </i18n.Translate>
- </p>
- </div>
- </div>
- </div>
- <div class="mt-5 sm:mt-6">
- <button type="button"
- class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- onClick={async (e) => {
- e.preventDefault();
- onClose()
- }}>
- <i18n.Translate>Cotinue to dashboard</i18n.Translate>
- </button>
- </div>
- </div>
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <ErrorLoading error={result} />
+ }
+ if (result.type === "fail") {
+ switch (result.case) {
+ case "not-found": return <OperationNotFound onClose={onClose} />
+ default: assertUnreachable(result.case)
}
- return handleNotOkResult(i18n)(result);
}
- const { data } = result;
+
+ const { body: data } = result;
if (data.aborted) {
return <section id="main" class="content">
@@ -194,3 +166,41 @@ export function WithdrawalQRCode({
/>
);
}
+
+
+function OperationNotFound({ onClose }: { onClose: () => void }): VNode {
+ const { i18n } = useTranslationContext();
+ return <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6">
+ <div>
+ <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100 ">
+ <svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
+ </svg>
+ </div>
+
+ <div class="mt-3 text-center sm:mt-5">
+ <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
+ <i18n.Translate>Operation not found</i18n.Translate>
+ </h3>
+ <div class="mt-2">
+ <p class="text-sm text-gray-500">
+ <i18n.Translate>
+ This operation is not known by the server. The operation id is wrong or the
+ server deleted the operation information before reaching here.
+ </i18n.Translate>
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="mt-5 sm:mt-6">
+ <button type="button"
+ class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+ onClick={async (e) => {
+ e.preventDefault();
+ onClose()
+ }}>
+ <i18n.Translate>Cotinue to dashboard</i18n.Translate>
+ </button>
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx
index 676fc43d0..bf2fa86f0 100644
--- a/packages/demobank-ui/src/pages/admin/Account.tsx
+++ b/packages/demobank-ui/src/pages/admin/Account.tsx
@@ -1,10 +1,13 @@
-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 { Amounts, TalerError } from "@gnu-taler/taler-util";
import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
+import { Fragment, VNode, h } from "preact";
+import { ErrorLoading } from "../../components/ErrorLoading.js";
+import { Loading } from "../../components/Loading.js";
+import { useBackendContext } from "../../context/backend.js";
+import { useAccountDetails } from "../../hooks/access.js";
+import { assertUnreachable } from "../HomePage.js";
+import { LoginForm } from "../LoginForm.js";
+import { PaytoWireTransferForm } from "../PaytoWireTransferForm.js";
export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode {
const { i18n } = useTranslationContext();
@@ -12,15 +15,25 @@ export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode
const account = r.state.status !== "loggedOut" ? r.state.username : "admin";
const result = useAccountDetails(account);
- if (!result.ok) {
- return handleNotOkResult(i18n)(result);
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <ErrorLoading error={result} />
}
- const { data } = result;
+ if (result.type === "fail") {
+ switch (result.case) {
+ case "unauthorized": return <LoginForm reason="forbidden" onRegister={onRegister} />
+ case "not-found": return <LoginForm reason="not-found" onRegister={onRegister} />
+ default: assertUnreachable(result)
+ }
+ }
+ const { body: data } = result;
const balance = Amounts.parseOrThrow(data.balance.amount);
- const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
-
- const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold);
+ const balanceIsDebit = data.balance.credit_debit_indicator == "debit";
+
+ const debitThreshold = Amounts.parseOrThrow(data.debit_threshold);
const limit = balanceIsDebit
? Amounts.sub(debitThreshold, balance).amount
: Amounts.add(balance, debitThreshold).amount;
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index ed8bf610d..8470930bf 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -3,7 +3,7 @@ import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
import { useEffect, useRef, useState } from "preact/hooks";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { buildPayto, parsePaytoUri } from "@gnu-taler/taler-util";
+import { TalerCorebankApi, buildPayto, parsePaytoUri } from "@gnu-taler/taler-util";
import { doAutoFocus } from "../PaytoWireTransferForm.js";
const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
@@ -28,8 +28,8 @@ export function AccountForm({
}: {
focus?: boolean,
children: ComponentChildren,
- template: SandboxBackend.Circuit.CircuitAccountData | undefined;
- onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => void;
+ template: TalerCorebankApi.AccountData | undefined;
+ onChange: (a: TalerCorebankApi.AccountData | undefined) => void;
purpose: "create" | "update" | "show";
}): VNode {
const initial = initializeFromTemplate(template);
@@ -41,12 +41,12 @@ export function AccountForm({
function updateForm(newForm: typeof initial): void {
- const parsed = !newForm.cashout_address
+ const parsed = !newForm.cashout_payto_uri
? undefined
- : buildPayto("iban", newForm.cashout_address, undefined);;
+ : buildPayto("iban", newForm.cashout_payto_uri, undefined);;
const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
- cashout_address: !newForm.cashout_address
+ cashout_payto_uri: !newForm.cashout_payto_uri
? i18n.str`required`
: !parsed
? i18n.str`does not follow the pattern`
@@ -75,7 +75,8 @@ export function AccountForm({
// ? 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,
+
+ // username: !newForm.username ? i18n.str`required` : undefined,
});
setErrors(errors);
setForm(newForm);
@@ -94,7 +95,7 @@ export function AccountForm({
<div class="px-4 py-6 sm:p-8">
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
- <div class="sm:col-span-5">
+ {/* <div class="sm:col-span-5">
<label
class="block text-sm font-medium leading-6 text-gray-900"
for="username"
@@ -127,7 +128,7 @@ export function AccountForm({
<p class="mt-2 text-sm text-gray-500" >
<i18n.Translate>account identification in the bank</i18n.Translate>
</p>
- </div>
+ </div> */}
<div class="sm:col-span-5">
<label
@@ -178,7 +179,7 @@ export function AccountForm({
name="internal-iban"
id="internal-iban"
disabled={true}
- value={form.iban ?? ""}
+ value={form.payto_uri ?? ""}
/>
</div>
<p class="mt-2 text-sm text-gray-500" >
@@ -200,18 +201,20 @@ export function AccountForm({
class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
name="email"
id="email"
- data-error={!!errors?.contact_data?.email && form.contact_data.email !== undefined}
+ data-error={!!errors?.contact_data?.email && form.contact_data?.email !== undefined}
disabled={purpose !== "create"}
- value={form.contact_data.email ?? ""}
+ value={form.contact_data?.email ?? ""}
onChange={(e) => {
- form.contact_data.email = e.currentTarget.value;
- updateForm(structuredClone(form));
+ if (form.contact_data) {
+ form.contact_data.email = e.currentTarget.value;
+ updateForm(structuredClone(form));
+ }
}}
autocomplete="off"
/>
<ShowInputErrorLabel
message={errors?.contact_data?.email}
- isDirty={form.contact_data.email !== undefined}
+ isDirty={form.contact_data?.email !== undefined}
/>
</div>
</div>
@@ -231,18 +234,20 @@ export function AccountForm({
name="phone"
id="phone"
disabled={purpose !== "create"}
- value={form.contact_data.phone ?? ""}
- data-error={!!errors?.contact_data?.phone && form.contact_data.phone !== undefined}
+ value={form.contact_data?.phone ?? ""}
+ data-error={!!errors?.contact_data?.phone && form.contact_data?.phone !== undefined}
onChange={(e) => {
- form.contact_data.phone = e.currentTarget.value;
- updateForm(structuredClone(form));
+ if (form.contact_data) {
+ form.contact_data.phone = e.currentTarget.value;
+ updateForm(structuredClone(form));
+ }
}}
// placeholder=""
autocomplete="off"
/>
<ShowInputErrorLabel
message={errors?.contact_data?.phone}
- isDirty={form.contact_data.phone !== undefined}
+ isDirty={form.contact_data?.phone !== undefined}
/>
</div>
</div>
@@ -259,21 +264,21 @@ export function AccountForm({
<div class="mt-2">
<input
type="text"
- data-error={!!errors?.cashout_address && form.cashout_address !== undefined}
+ data-error={!!errors?.cashout_payto_uri && form.cashout_payto_uri !== undefined}
class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
name="cashout"
id="cashout"
disabled={purpose === "show"}
- value={form.cashout_address ?? ""}
+ value={form.cashout_payto_uri ?? ""}
onChange={(e) => {
- form.cashout_address = e.currentTarget.value;
+ form.cashout_payto_uri = e.currentTarget.value;
updateForm(structuredClone(form));
}}
autocomplete="off"
/>
<ShowInputErrorLabel
- message={errors?.cashout_address}
- isDirty={form.cashout_address !== undefined}
+ message={errors?.cashout_payto_uri}
+ isDirty={form.cashout_payto_uri !== undefined}
/>
</div>
<p class="mt-2 text-sm text-gray-500" >
@@ -289,26 +294,27 @@ export function AccountForm({
}
function initializeFromTemplate(
- account: SandboxBackend.Circuit.CircuitAccountData | undefined,
-): WithIntermediate<SandboxBackend.Circuit.CircuitAccountData> {
+ account: TalerCorebankApi.AccountData | undefined,
+): WithIntermediate<TalerCorebankApi.AccountData> {
const emptyAccount = {
- cashout_address: undefined,
- iban: undefined,
- name: undefined,
- username: undefined,
+ cashout_payto_uri: undefined,
contact_data: undefined,
+ payto_uri: undefined,
+ balance: undefined,
+ debit_threshold: undefined,
+ name: undefined,
};
const emptyContact = {
email: undefined,
phone: undefined,
};
- const initial: PartialButDefined<SandboxBackend.Circuit.CircuitAccountData> =
+ const initial: PartialButDefined<TalerCorebankApi.AccountData> =
structuredClone(account) ?? emptyAccount;
if (typeof initial.contact_data === "undefined") {
initial.contact_data = emptyContact;
}
- initial.contact_data.email;
+ // initial.contact_data.email;
return initial as any;
}
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index a6899e679..8a1e8294a 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -1,10 +1,12 @@
-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 { Amounts, TalerError } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode, h } from "preact";
+import { ErrorLoading } from "../../components/ErrorLoading.js";
+import { Loading } from "../../components/Loading.js";
+import { useBusinessAccounts } from "../../hooks/circuit.js";
+import { assertUnreachable } from "../HomePage.js";
import { RenderAmount } from "../PaytoWireTransferForm.js";
+import { AccountAction } from "./Home.js";
interface Props {
onAction: (type: AccountAction, account: string) => void;
@@ -13,15 +15,23 @@ interface Props {
}
export function AccountList({ account, onAction, onCreateAccount }: Props): VNode {
- const result = useBusinessAccounts({ account });
+ const result = useBusinessAccounts();
const { i18n } = useTranslationContext();
- if (result.loading) return <div />;
- if (!result.ok) {
- return handleNotOkResult(i18n)(result);
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <ErrorLoading error={result} />
+ }
+ if (result.data.type === "fail") {
+ switch (result.data.case) {
+ case "unauthorized": return <div>un auth</div>
+ default: assertUnreachable(result.data.case)
+ }
}
- const { customers } = result.data;
+ const { accounts } = result.data.body;
return <div class="px-4 sm:px-6 lg:px-8">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
@@ -45,7 +55,7 @@ export function AccountList({ account, onAction, onCreateAccount }: Props): VNod
<div class="mt-8 flow-root">
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
- {!customers.length ? (
+ {!accounts.length ? (
<div></div>
) : (
<table class="min-w-full divide-y divide-gray-300">
@@ -60,7 +70,7 @@ export function AccountList({ account, onAction, onCreateAccount }: Props): VNod
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
- {customers.map((item, idx) => {
+ {accounts.map((item, idx) => {
const balance = !item.balance
? undefined
: Amounts.parse(item.balance.amount);
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index 2146fc6f0..f6176e772 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -1,11 +1,14 @@
+import { HttpStatusCode, TalerCorebankApi, TalerError, TranslatedString } from "@gnu-taler/taler-util";
import { RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
-import { VNode, h, Fragment } from "preact";
-import { useAdminAccountAPI } from "../../hooks/circuit.js";
+import { Fragment, VNode, h } from "preact";
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";
+import { useBackendState } from "../../hooks/backend.js";
+import { useBankCoreApiContext } from "../../context/config.js";
+import { assertUnreachable } from "../HomePage.js";
+import { mutate } from "swr";
export function CreateNewAccount({
onCancel,
@@ -15,40 +18,63 @@ export function CreateNewAccount({
onCreateSuccess: (password: string) => void;
}): VNode {
const { i18n } = useTranslationContext();
- const { createAccount } = useAdminAccountAPI();
+ // const { createAccount } = useAdminAccountAPI();
+ const { state: credentials } = useBackendState()
+ const token = credentials.status !== "loggedIn" ? undefined : credentials.token
+ const { api } = useBankCoreApiContext();
+
const [submitAccount, setSubmitAccount] = useState<
- SandboxBackend.Circuit.CircuitAccountData | undefined
+ TalerCorebankApi.AccountData | undefined
>();
async function doCreate() {
- if (!submitAccount) return;
+ if (!submitAccount || !token) return;
try {
- const account: SandboxBackend.Circuit.CircuitAccountRequest =
- {
- cashout_address: submitAccount.cashout_address,
- contact_data: submitAccount.contact_data,
- internal_iban: submitAccount.iban,
+ const account: TalerCorebankApi.RegisterAccountRequest = {
+ cashout_payto_uri: submitAccount.cashout_payto_uri,
+ challenge_contact_data: submitAccount.contact_data,
+ internal_payto_uri: submitAccount.payto_uri,
name: submitAccount.name,
- username: submitAccount.username,
+ username: "",//FIXME: not in account data
password: getRandomPassword(),
};
- await createAccount(account);
- onCreateSuccess(account.password);
+ const resp = await api.createAccount(token, account);
+ if (resp.type === "ok") {
+ mutate(() => true)// clean account list
+ onCreateSuccess(account.password);
+ } else {
+ switch (resp.case) {
+ case "invalid-input": return notify({
+ type: "error",
+ title: i18n.str`Server replied that input data was invalid`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "unable-to-create": return notify({
+ type: "error",
+ title: i18n.str`The account name is registered.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "unauthorized": return notify({
+ type: "error",
+ title: i18n.str`The rights to perform the operation are not sufficient`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "already-exist": return notify({
+ type: "error",
+ title: i18n.str`Account name is already taken`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
+ }
} 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`Server replied that input data was invalid`
- : status === HttpStatusCode.Conflict
- ? i18n.str`At least one registration detail was not available`
- : undefined,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx b/packages/demobank-ui/src/pages/admin/Home.tsx
index d50ff14b4..71ea8ce1b 100644
--- a/packages/demobank-ui/src/pages/admin/Home.tsx
+++ b/packages/demobank-ui/src/pages/admin/Home.tsx
@@ -2,15 +2,14 @@ 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 { Transactions } from "../../components/Transactions/index.js";
import { ShowAccountDetails } from "../ShowAccountDetails.js";
import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
+import { ShowCashoutDetails } from "../business/Home.js";
import { AdminAccount } from "./Account.js";
import { AccountList } from "./AccountList.js";
import { CreateNewAccount } from "./CreateNewAccount.js";
import { RemoveAccount } from "./RemoveAccount.js";
-import { Transactions } from "../../components/Transactions/index.js";
/**
* Query account information and show QR code if there is pending withdrawal
@@ -38,7 +37,6 @@ export function AdminHome({ onRegister }: Props): VNode {
switch (action.type) {
case "show-cashouts-details": return <ShowCashoutDetails
id={action.account}
- onLoadNotOk={handleNotOkResult(i18n)}
onCancel={() => {
setAction(undefined);
}}
@@ -74,7 +72,6 @@ export function AdminHome({ onRegister }: Props): VNode {
)
case "update-password": return <UpdateAccountPassword
account={action.account}
- onLoadNotOk={handleNotOkResult(i18n)}
onUpdateSuccess={() => {
notifyInfo(i18n.str`Password changed`);
setAction(undefined);
@@ -85,7 +82,6 @@ export function AdminHome({ onRegister }: Props): VNode {
/>
case "remove-account": return <RemoveAccount
account={action.account}
- onLoadNotOk={handleNotOkResult(i18n)}
onUpdateSuccess={() => {
notifyInfo(i18n.str`Account removed`);
setAction(undefined);
@@ -96,7 +92,6 @@ export function AdminHome({ onRegister }: Props): VNode {
/>
case "show-details": return <ShowAccountDetails
account={action.account}
- onLoadNotOk={handleNotOkResult(i18n)}
onChangePassword={() => {
setAction({
type: "update-password",
@@ -137,12 +132,12 @@ export function AdminHome({ onRegister }: Props): VNode {
}}
account={undefined}
onAction={(type, account) => setAction({ account, type })}
-
+
/>
<AdminAccount onRegister={onRegister} />
- <Transactions account="admin"/>
+ <Transactions account="admin" />
</Fragment>
);
} \ 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 b323b0d01..ce8a53ca1 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -1,24 +1,25 @@
-import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
-import { VNode, h, Fragment } from "preact";
+import { Amounts, HttpStatusCode, TalerError, TranslatedString } from "@gnu-taler/taler-util";
+import { HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { useState } from "preact/hooks";
+import { Attention } from "../../components/Attention.js";
+import { ErrorLoading } from "../../components/ErrorLoading.js";
+import { Loading } from "../../components/Loading.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
import { useAccountDetails } from "../../hooks/access.js";
-import { useAdminAccountAPI } from "../../hooks/circuit.js";
-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 { assertUnreachable } from "../HomePage.js";
+import { LoginForm } from "../LoginForm.js";
import { doAutoFocus } from "../PaytoWireTransferForm.js";
+import { useBankCoreApiContext } from "../../context/config.js";
+import { useBackendState } from "../../hooks/backend.js";
export function RemoveAccount({
account,
onCancel,
onUpdateSuccess,
- onLoadNotOk,
focus,
}: {
- onLoadNotOk: <T>(
- error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
- ) => VNode;
focus?: boolean;
onCancel: () => void;
onUpdateSuccess: () => void;
@@ -27,18 +28,26 @@ export function RemoveAccount({
const { i18n } = useTranslationContext();
const result = useAccountDetails(account);
const [accountName, setAccountName] = useState<string | undefined>()
- const { deleteAccount } = useAdminAccountAPI();
- if (!result.ok) {
- if (result.loading || result.type === ErrorType.TIMEOUT) {
- return onLoadNotOk(result);
- }
- if (result.status === HttpStatusCode.NotFound) {
- return <div>account not found</div>;
+ const { state } = useBackendState();
+ const token = state.status !== "loggedIn" ? undefined : state.token
+ const { api } = useBankCoreApiContext()
+
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <ErrorLoading error={result} />
+ }
+ if (result.type === "fail") {
+ switch (result.case) {
+ case "unauthorized": return <LoginForm reason="forbidden" />
+ case "not-found": return <LoginForm reason="not-found" />
+ default: assertUnreachable(result)
}
- return onLoadNotOk(result);
}
- const balance = Amounts.parse(result.data.balance.amount);
+
+ const balance = Amounts.parse(result.body.balance.amount);
if (!balance) {
return <div>there was an error reading the balance</div>;
}
@@ -50,23 +59,45 @@ export function RemoveAccount({
}
async function doRemove() {
+ if (!token) return;
try {
- const r = await deleteAccount(account);
- onUpdateSuccess();
+ const resp = await api.deleteAccount({ username: account, token });
+ if (resp.type === "ok") {
+ onUpdateSuccess();
+ } else {
+ switch (resp.case) {
+ case "unauthorized": return notify({
+ type: "error",
+ title: i18n.str`No enough permission to delete the account.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`The username was not found.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "unable-to-delete": return notify({
+ type: "error",
+ title: i18n.str`The administrator specified a institutional username.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "balance-not-zero": return notify({
+ type: "error",
+ title: i18n.str`Can't delete an account with balance different than zero.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: {
+ assertUnreachable(resp)
+ }
+ }
+ }
} catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.Forbidden
- ? i18n.str`The administrator specified a institutional username`
- : status === HttpStatusCode.NotFound
- ? i18n.str`The username was not found`
- : status === HttpStatusCode.PreconditionFailed
- ? i18n.str`Balance was not zero`
- : undefined,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(i18n.str`Operation failed, please report`,
(error instanceof Error
diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx
index 1a84effcd..03d7895e3 100644
--- a/packages/demobank-ui/src/pages/business/Home.tsx
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -16,26 +16,28 @@
import {
AmountJson,
Amounts,
- HttpStatusCode,
+ TalerError,
TranslatedString
} from "@gnu-taler/taler-util";
import {
- HttpResponse,
- HttpResponsePaginated,
- RequestError,
notify,
notifyError,
notifyInfo,
- useTranslationContext,
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
+import { format } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
+import { mutate } from "swr";
import { Cashouts } from "../../components/Cashouts/index.js";
+import { ErrorLoading } from "../../components/ErrorLoading.js";
+import { Loading } from "../../components/Loading.js";
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { useBankCoreApiContext } from "../../context/config.js";
import { useAccountDetails } from "../../hooks/access.js";
+import { useBackendState } from "../../hooks/backend.js";
import {
useCashoutDetails,
- useCircuitAccountAPI,
useEstimator,
useRatiosAndFeeConfig,
} from "../../hooks/circuit.js";
@@ -44,7 +46,7 @@ import {
buildRequestErrorMessage,
undefinedIfEmpty,
} from "../../utils.js";
-import { handleNotOkResult } from "../HomePage.js";
+import { LoginForm } from "../LoginForm.js";
import { InputAmount } from "../PaytoWireTransferForm.js";
import { ShowAccountDetails } from "../ShowAccountDetails.js";
import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
@@ -53,12 +55,10 @@ interface Props {
account: string,
onClose: () => void;
onRegister: () => void;
- onLoadNotOk: () => void;
}
export function BusinessAccount({
onClose,
account,
- onLoadNotOk,
onRegister,
}: Props): VNode {
const { i18n } = useTranslationContext();
@@ -68,12 +68,10 @@ export function BusinessAccount({
string | undefined
>();
-
if (newCashout) {
return (
<CreateCashout
account={account}
- onLoadNotOk={handleNotOkResult(i18n)}
onCancel={() => {
setNewcashout(false);
}}
@@ -91,7 +89,6 @@ export function BusinessAccount({
return (
<ShowCashoutDetails
id={showCashoutDetails}
- onLoadNotOk={handleNotOkResult(i18n)}
onCancel={() => {
setShowCashoutDetails(undefined);
}}
@@ -102,7 +99,6 @@ export function BusinessAccount({
return (
<UpdateAccountPassword
account={account}
- onLoadNotOk={handleNotOkResult(i18n)}
onUpdateSuccess={() => {
notifyInfo(i18n.str`Password changed`);
setUpdatePassword(false);
@@ -117,7 +113,6 @@ export function BusinessAccount({
<div>
<ShowAccountDetails
account={account}
- onLoadNotOk={handleNotOkResult(i18n)}
onUpdateSuccess={() => {
notifyInfo(i18n.str`Account updated`);
}}
@@ -158,11 +153,6 @@ interface PropsCashout {
account: string;
onComplete: (id: string) => void;
onCancel: () => void;
- onLoadNotOk: <T>(
- error:
- | HttpResponsePaginated<T, SandboxBackend.SandboxError>
- | HttpResponse<T, SandboxBackend.SandboxError>,
- ) => VNode;
}
type FormType = {
@@ -175,88 +165,78 @@ type ErrorFrom<T> = {
[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,
+ account: accountName,
onComplete,
onCancel,
- onLoadNotOk,
}: PropsCashout): VNode {
const { i18n } = useTranslationContext();
- const ratiosResult = useRatiosAndFeeConfig();
- const result = useAccountDetails(account);
+ const resultRatios = useRatiosAndFeeConfig();
+ const resultAccount = useAccountDetails(accountName);
const {
estimateByCredit: calculateFromCredit,
estimateByDebit: calculateFromDebit,
} = useEstimator();
+ const { state } = useBackendState()
+ const creds = state.status !== "loggedIn" ? undefined : state
+ const { api, config } = useBankCoreApiContext()
const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
- const { createCashout } = useCircuitAccountAPI();
- if (!result.ok) return onLoadNotOk(result);
- if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
- const config = ratiosResult.data;
+ if (!resultAccount || !resultRatios) {
+ return <Loading />
+ }
+ if (resultAccount instanceof TalerError) {
+ return <ErrorLoading error={resultAccount} />
+ }
+ if (resultRatios instanceof TalerError) {
+ return <ErrorLoading error={resultRatios} />
+ }
+ if (resultAccount.type === "fail") {
+ switch (resultAccount.case) {
+ case "unauthorized": return <LoginForm reason="forbidden" />
+ case "not-found": return <LoginForm reason="not-found" />
+ default: assertUnreachable(resultAccount)
+ }
+ }
+
+ if (resultRatios.type === "fail") {
+ switch (resultRatios.case) {
+ case "not-supported": return <div>cashout operations are not supported</div>
+ default: assertUnreachable(resultRatios.case)
+ }
+ }
+ if (!config.fiat_currency) {
+ return <div>cashout operations are not supported</div>
+ }
- const balance = Amounts.parseOrThrow(result.data.balance.amount);
- const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
+ const ratio = resultRatios.body
- const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold);
- const zero = Amounts.zeroOfCurrency(balance.currency);
- const limit = balanceIsDebit
- ? Amounts.sub(debitThreshold, balance).amount
- : Amounts.add(balance, debitThreshold).amount;
+ const account = {
+ balance: Amounts.parseOrThrow(resultAccount.body.balance.amount),
+ balanceIsDebit: resultAccount.body.balance.credit_debit_indicator == "debit",
+ debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold)
+ }
+
+ const zero = Amounts.zeroOfCurrency(account.balance.currency);
+ const limit = account.balanceIsDebit
+ ? Amounts.sub(account.debitThreshold, account.balance).amount
+ : Amounts.add(account.balance, account.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
+
+ const sellRate = ratio.sell_at_ratio;
+ const sellFee = !ratio.sell_out_fee
? zero
: Amounts.parseOrThrow(
- `${balance.currency}:${config.ratios_and_fees.sell_out_fee}`,
+ `${account.balance.currency}:${ratio.sell_out_fee}`,
);
- const fiatCurrency = config.fiat_currency;
if (!sellRate || sellRate < 0) return <div>error rate</div>;
const amount = Amounts.parseOrThrow(
- `${!form.isDebit ? fiatCurrency : balance.currency}:${!form.amount ? "0" : form.amount
+ `${!form.isDebit ? config.fiat_currency.name : account.balance.currency}:${!form.amount ? "0" : form.amount
}`,
);
@@ -267,15 +247,16 @@ function CreateCashout({
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
- },
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
});
} else {
calculateFromCredit(amount, sellFee, sellRate)
@@ -283,20 +264,21 @@ function CreateCashout({
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,
- },
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
});
}
}, [form.amount, form.isDebit]);
- const balanceAfter = Amounts.sub(balance, calc.debit).amount;
+ const balanceAfter = Amounts.sub(account.balance, calc.debit).amount;
function updateForm(newForm: typeof form): void {
setForm(newForm);
@@ -374,8 +356,8 @@ function CreateCashout({
<label for="balance-now">{i18n.str`Balance now`}</label>
<InputAmount
name="banace-now"
- currency={balance.currency}
- value={Amounts.stringifyValue(balance)}
+ currency={account.balance.currency}
+ value={Amounts.stringifyValue(account.balance)}
/>
</fieldset>
<fieldset>
@@ -384,7 +366,7 @@ function CreateCashout({
>{i18n.str`Total cost`}</label>
<InputAmount
name="total-cost"
- currency={balance.currency}
+ currency={account.balance.currency}
value={Amounts.stringifyValue(calc.debit)}
/>
</fieldset>
@@ -392,7 +374,7 @@ function CreateCashout({
<label for="balance-after">{i18n.str`Balance after`}</label>
<InputAmount
name="balance-after"
- currency={balance.currency}
+ currency={account.balance.currency}
value={balanceAfter ? Amounts.stringifyValue(balanceAfter) : ""}
/>
</fieldset>{" "}
@@ -402,7 +384,7 @@ function CreateCashout({
<label for="amount-conversiojn">{i18n.str`Amount after conversion`}</label>
<InputAmount
name="amount-conversion"
- currency={fiatCurrency}
+ currency={config.fiat_currency.name}
value={Amounts.stringifyValue(calc.beforeFee)}
/>
</fieldset>
@@ -411,7 +393,7 @@ function CreateCashout({
<label form="cashout-fee">{i18n.str`Cashout fee`}</label>
<InputAmount
name="cashout-fee"
- currency={fiatCurrency}
+ currency={config.fiat_currency.name}
value={Amounts.stringifyValue(sellFee)}
/>
</fieldset>
@@ -423,7 +405,7 @@ function CreateCashout({
>{i18n.str`Total cashout transfer`}</label>
<InputAmount
name="total"
- currency={fiatCurrency}
+ currency={config.fiat_currency.name}
value={Amounts.stringifyValue(calc.credit)}
/>
</fieldset>
@@ -501,35 +483,55 @@ function CreateCashout({
onClick={async (e) => {
e.preventDefault();
- if (errors) return;
+ if (errors || !creds) return;
try {
- const res = await createCashout({
+ const resp = await api.createCashout(creds, {
amount_credit: Amounts.stringify(calc.credit),
amount_debit: Amounts.stringify(calc.debit),
subject: form.subject,
tan_channel: form.channel,
});
- onComplete(res.data.uuid);
+ if (resp.type === "ok") {
+ mutate(() => true)// clean cashout list
+ onComplete(resp.body.cashout_id);
+ } else {
+ switch (resp.case) {
+ case "incorrect-exchange-rate": return notify({
+ type: "error",
+ title: i18n.str`The exchange rate was incorrectly applied`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "no-allowed": return notify({
+ type: "error",
+ title: i18n.str`This user is not allowed to make a cashout`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "no-contact-info": return notify({
+ type: "error",
+ title: i18n.str`Need a contact data where to send the TAN`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "no-enough-balance": return notify({
+ type: "error",
+ title: i18n.str`The account does not have sufficient funds`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "tan-not-supported": return notify({
+ type: "error",
+ title: i18n.str`The bank does not support the TAN channel for this operation`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ default: assertUnreachable(resp)
+ }
+ }
} catch (error) {
- if (error instanceof RequestError) {
- notify(
- buildRequestErrorMessage(i18n, error.cause, {
- onClientError: (status) =>
- status === HttpStatusCode.BadRequest
- ? i18n.str`The exchange rate was incorrectly applied`
- : status === HttpStatusCode.Forbidden
- ? i18n.str`A institutional user tried the operation`
- : status === HttpStatusCode.Conflict
- ? i18n.str`Need a contact data where to send the TAN`
- : status === HttpStatusCode.PreconditionFailed
- ? i18n.str`The account does not have sufficient funds`
- : undefined,
- onServerError: (status) =>
- status === HttpStatusCode.ServiceUnavailable
- ? i18n.str`The bank does not support the TAN channel for this operation`
- : undefined,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -552,24 +554,34 @@ function CreateCashout({
interface ShowCashoutProps {
id: string;
onCancel: () => void;
- onLoadNotOk: <T>(
- error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
- ) => VNode;
}
export function ShowCashoutDetails({
id,
onCancel,
- onLoadNotOk,
}: ShowCashoutProps): VNode {
const { i18n } = useTranslationContext();
+ const { state } = useBackendState();
+ const creds = state.status !== "loggedIn" ? undefined : state
+ const { api } = useBankCoreApiContext()
const result = useCashoutDetails(id);
- const { abortCashout, confirmCashout } = useCircuitAccountAPI();
const [code, setCode] = useState<string | undefined>(undefined);
- if (!result.ok) return onLoadNotOk(result);
+
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <ErrorLoading error={result} />
+ }
+ if (result.type === "fail") {
+ switch (result.case) {
+ case "already-aborted": return <div>this cashout is already aborted</div>
+ default: assertUnreachable(result.case)
+ }
+ }
const errors = undefinedIfEmpty({
code: !code ? i18n.str`required` : undefined,
});
- const isPending = String(result.data.status).toUpperCase() === "PENDING";
+ const isPending = String(result.body.status).toUpperCase() === "PENDING";
return (
<div>
<h1>Cashout details {id}</h1>
@@ -578,43 +590,47 @@ export function ShowCashoutDetails({
<label>
<i18n.Translate>Subject</i18n.Translate>
</label>
- <input readOnly value={result.data.subject} />
+ <input readOnly value={result.body.subject} />
</fieldset>
<fieldset>
<label>
<i18n.Translate>Created</i18n.Translate>
</label>
- <input readOnly value={result.data.creation_time ?? ""} />
+ <input readOnly value={result.body.creation_time.t_s === "never" ? i18n.str`never` : format(result.body.creation_time.t_s, "dd/MM/yyyy HH:mm:ss")} />
</fieldset>
<fieldset>
<label>
<i18n.Translate>Confirmed</i18n.Translate>
</label>
- <input readOnly value={result.data.confirmation_time ?? ""} />
+ <input readOnly value={result.body.confirmation_time === undefined ? "-" :
+ (result.body.confirmation_time.t_s === "never" ?
+ i18n.str`never` :
+ format(result.body.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss"))
+ } />
</fieldset>
<fieldset>
<label>
<i18n.Translate>Debited</i18n.Translate>
</label>
- <input readOnly value={result.data.amount_debit} />
+ <input readOnly value={result.body.amount_debit} />
</fieldset>
<fieldset>
<label>
<i18n.Translate>Credit</i18n.Translate>
</label>
- <input readOnly value={result.data.amount_credit} />
+ <input readOnly value={result.body.amount_credit} />
</fieldset>
<fieldset>
<label>
<i18n.Translate>Status</i18n.Translate>
</label>
- <input readOnly value={result.data.status} />
+ <input readOnly value={result.body.status} />
</fieldset>
<fieldset>
<label>
<i18n.Translate>Destination</i18n.Translate>
</label>
- <input readOnly value={result.data.cashout_address} />
+ <input readOnly value={result.body.credit_payto_uri} />
</fieldset>
{isPending ? (
<fieldset>
@@ -652,21 +668,33 @@ export function ShowCashoutDetails({
class="pure-button pure-button-primary button-error"
onClick={async (e) => {
e.preventDefault();
+ if (!creds) return;
try {
- await abortCashout(id);
- onCancel();
+ const resp = await api.abortCashoutById(creds, id);
+ if (resp.type === "ok") {
+ onCancel();
+ } else {
+ switch (resp.case) {
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Cashout not found. It may be also mean that it was already aborted.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "already-confirmed": return notify({
+ type: "error",
+ title: i18n.str`Cashout was already confimed.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: {
+ assertUnreachable(resp)
+ }
+ }
+ }
} 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,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -687,27 +715,40 @@ export function ShowCashoutDetails({
class="pure-button pure-button-primary "
onClick={async (e) => {
e.preventDefault();
+ if (!creds) return;
try {
if (!code) return;
- const rest = await confirmCashout(id, {
+ const resp = await api.confirmCashoutById(creds, id, {
tan: code,
});
+ if (resp.type === "ok") {
+ mutate(() => true)//clean cashout state
+ } else {
+ switch (resp.case) {
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Cashout not found. It may be also mean that it was already aborted.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "wrong-tan-or-credential": return notify({
+ type: "error",
+ title: i18n.str`Invalid code or credentials.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "cashout-address-changed": return notify({
+ type: "error",
+ title: i18n.str`The cash-out address between the creation and the confirmation changed.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
+ }
} 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,
- }),
- );
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
} else {
notifyError(
i18n.str`Operation failed, please report`,