aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-09-19 00:39:00 -0300
committerSebastian <sebasjm@gmail.com>2023-09-19 00:39:00 -0300
commit0388d31d364139d0a3999126b06d8ac850117ab9 (patch)
tree8fcb0c248c689b70b1e583d0f35d267f8ca5b02f
parent40d2aa0c11e61ea45005c4c212c6ab686162b4b0 (diff)
account page
-rw-r--r--packages/demobank-ui/src/components/CopyButton.tsx60
-rw-r--r--packages/demobank-ui/src/components/ErrorLoading.tsx29
-rw-r--r--packages/demobank-ui/src/components/Routing.tsx (renamed from packages/demobank-ui/src/pages/Routing.tsx)13
-rw-r--r--packages/demobank-ui/src/components/ShowInputErrorLabel.tsx (renamed from packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx)0
-rw-r--r--packages/demobank-ui/src/components/app.tsx2
-rw-r--r--packages/demobank-ui/src/pages/AccountPage.tsx170
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/index.ts91
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/state.ts87
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/stories.tsx29
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/test.ts32
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/views.tsx74
-rw-r--r--packages/demobank-ui/src/pages/AdminPage.tsx2
-rw-r--r--packages/demobank-ui/src/pages/BusinessAccount.tsx2
-rw-r--r--packages/demobank-ui/src/pages/HomePage.tsx2
-rw-r--r--packages/demobank-ui/src/pages/LoginForm.tsx2
-rw-r--r--packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx2
-rw-r--r--packages/demobank-ui/src/pages/RegistrationPage.tsx2
-rw-r--r--packages/demobank-ui/src/pages/WalletWithdrawForm.tsx2
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx2
19 files changed, 417 insertions, 186 deletions
diff --git a/packages/demobank-ui/src/components/CopyButton.tsx b/packages/demobank-ui/src/components/CopyButton.tsx
new file mode 100644
index 000000000..c61083074
--- /dev/null
+++ b/packages/demobank-ui/src/components/CopyButton.tsx
@@ -0,0 +1,60 @@
+import { h, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
+
+
+
+export function CopyIcon(): VNode {
+ return (
+ <svg height="16" viewBox="0 0 16 16" width="16">
+ <path
+ fill-rule="evenodd"
+ d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"
+ />
+ <path
+ fill-rule="evenodd"
+ d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"
+ />
+ </svg>
+ )
+ };
+
+ export function CopiedIcon(): VNode {
+ return (
+ <svg height="16" viewBox="0 0 16 16" width="16">
+ <path
+ fill-rule="evenodd"
+ d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
+ />
+ </svg>
+ )
+ };
+
+export function CopyButton({ getContent }: { getContent: () => string }): VNode {
+ const [copied, setCopied] = useState(false);
+ function copyText(): void {
+ navigator.clipboard.writeText(getContent() || "");
+ setCopied(true);
+ }
+ useEffect(() => {
+ if (copied) {
+ setTimeout(() => {
+ setCopied(false);
+ }, 1000);
+ }
+ }, [copied]);
+
+ if (!copied) {
+ return (
+ <button onClick={copyText} style={{ width: 32, height: 32, fontSize: "initial" }}>
+ <CopyIcon />
+ </button>
+ );
+ }
+ return (
+ <div content="Copied" style={{ display: "inline-block" }}>
+ <button disabled style={{ width: 32, height: 32, fontSize: "initial" }}>
+ <CopiedIcon />
+ </button>
+ </div>
+ );
+ } \ No newline at end of file
diff --git a/packages/demobank-ui/src/components/ErrorLoading.tsx b/packages/demobank-ui/src/components/ErrorLoading.tsx
new file mode 100644
index 000000000..fbc4ffceb
--- /dev/null
+++ b/packages/demobank-ui/src/components/ErrorLoading.tsx
@@ -0,0 +1,29 @@
+/*
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { h, VNode } from "preact";
+
+export function ErrorLoading(): VNode {
+ const { i18n } = useTranslationContext()
+ return (
+ <div><i18n.Translate>
+ Could not complete the request
+ </i18n.Translate>
+ </div>
+ );
+}
diff --git a/packages/demobank-ui/src/pages/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx
index f176c73db..d5ea44e10 100644
--- a/packages/demobank-ui/src/pages/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -14,16 +14,15 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { createHashHistory } from "history";
import { VNode, h } from "preact";
import { Route, Router, route } from "preact-router";
-import { useEffect, useMemo, useState } from "preact/hooks";
-import { BankFrame } from "./BankFrame.js";
-import { BusinessAccount } from "./BusinessAccount.js";
-import { HomePage, WithdrawalOperationPage } from "./HomePage.js";
-import { PublicHistoriesPage } from "./PublicHistoriesPage.js";
-import { RegistrationPage } from "./RegistrationPage.js";
+import { useEffect } from "preact/hooks";
+import { BankFrame } from "../pages/BankFrame.js";
+import { BusinessAccount } from "../pages/BusinessAccount.js";
+import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js";
+import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js";
+import { RegistrationPage } from "../pages/RegistrationPage.js";
export function Routing(): VNode {
const history = createHashHistory();
diff --git a/packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx b/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx
index dacffe20a..dacffe20a 100644
--- a/packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx
+++ b/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx
diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx
index ea86da518..1f7034bc5 100644
--- a/packages/demobank-ui/src/components/app.tsx
+++ b/packages/demobank-ui/src/components/app.tsx
@@ -23,7 +23,7 @@ import { FunctionalComponent, h } from "preact";
import { SWRConfig } from "swr";
import { BackendStateProvider } from "../context/backend.js";
import { strings } from "../i18n/strings.js";
-import { Routing } from "../pages/Routing.js";
+import { Routing } from "./Routing.js";
const WITH_LOCAL_STORAGE_CACHE = false;
diff --git a/packages/demobank-ui/src/pages/AccountPage.tsx b/packages/demobank-ui/src/pages/AccountPage.tsx
deleted file mode 100644
index 820c59984..000000000
--- a/packages/demobank-ui/src/pages/AccountPage.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { Amounts, HttpStatusCode, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
-import {
- ErrorType,
- HttpResponsePaginated,
- useTranslationContext,
-} from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
-import { Transactions } from "../components/Transactions/index.js";
-import { useBackendContext } from "../context/backend.js";
-import { useAccountDetails } from "../hooks/access.js";
-import { LoginForm } from "./LoginForm.js";
-import { PaymentOptions } from "./PaymentOptions.js";
-import { notifyError } from "../hooks/notification.js";
-import { useEffect, useState } from "preact/hooks";
-
-interface Props {
- account: string;
- onLoadNotOk: <T>(
- error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
- ) => VNode;
-}
-
-export const CopyIcon = (): VNode => (
- <svg height="16" viewBox="0 0 16 16" width="16">
- <path
- fill-rule="evenodd"
- d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"
- />
- <path
- fill-rule="evenodd"
- d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"
- />
- </svg>
-);
-
-export const CopiedIcon = (): VNode => (
- <svg height="16" viewBox="0 0 16 16" width="16">
- <path
- fill-rule="evenodd"
- d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
- />
- </svg>
-);
-
-function CopyButton({ getContent }: { getContent: () => string }): VNode {
- const [copied, setCopied] = useState(false);
- function copyText(): void {
- navigator.clipboard.writeText(getContent() || "");
- setCopied(true);
- }
- useEffect(() => {
- if (copied) {
- setTimeout(() => {
- setCopied(false);
- }, 1000);
- }
- }, [copied]);
-
- if (!copied) {
- return (
- <button onClick={copyText} style={{width:32, height:32, fontSize: "initial"}}>
- <CopyIcon />
- </button>
- );
- }
- return (
- <div content="Copied" style={{display:"inline-block"}}>
- <button disabled style={{width:32, height:32 , fontSize: "initial"}}>
- <CopiedIcon />
- </button>
- </div>
- );
-}
-
-
-/**
- * Query account information and show QR code if there is pending withdrawal
- */
-export function AccountPage({ account, onLoadNotOk }: Props): VNode {
- const result = useAccountDetails(account);
- const backend = useBackendContext();
- const { i18n } = useTranslationContext();
-
- if (!result.ok) {
- if (result.loading || result.type === ErrorType.TIMEOUT) {
- return onLoadNotOk(result);
- }
- //logout if there is any error, not if loading
- backend.logOut();
- if (result.status === HttpStatusCode.NotFound) {
- notifyError({
- title: i18n.str`Username or account label "${account}" not found`,
- });
- return <LoginForm />;
- }
- return onLoadNotOk(result);
- }
-
- const { data } = result;
- const balance = Amounts.parseOrThrow(data.balance.amount);
- const debitThreshold = Amounts.parseOrThrow(data.debitThreshold);
- const payto = parsePaytoUri(data.paytoUri);
- if (!payto || !payto.isKnown || payto.targetType !== "iban") {
- return (
- <div>Payto from server is not valid &quot;{data.paytoUri}&quot;</div>
- );
- }
- const balanceIsDebit = data.balance.credit_debit_indicator == "debit";
- const limit = balanceIsDebit
- ? Amounts.sub(debitThreshold, balance).amount
- : Amounts.add(balance, debitThreshold).amount;
- return (
- <Fragment>
- <div>
- <h1 class="nav welcome-text">
- <i18n.Translate>
- Welcome, {account} (<a href={stringifyPaytoUri(payto)}>{payto.iban}</a>)! <CopyButton getContent={() => stringifyPaytoUri(payto)} />
- </i18n.Translate>
- </h1>
- </div>
-
- <section id="assets">
- <div class="asset-summary">
- <h2>{i18n.str`Bank account balance`}</h2>
- {!balance ? (
- <div class="large-amount" style={{ color: "gray" }}>
- Waiting server response...
- </div>
- ) : (
- <div class="large-amount amount">
- {balanceIsDebit ? <b>-</b> : null}
- <span class="value">{`${Amounts.stringifyValue(balance)}`}</span>
- &nbsp;
- <span class="currency">{`${balance.currency}`}</span>
- </div>
- )}
- </div>
- </section>
- <section id="payments">
- <div class="payments">
- <h2>{i18n.str`Payments`}</h2>
- <PaymentOptions limit={limit} />
- </div>
- </section>
-
- <section style={{ marginTop: "2em" }}>
- <div class="active">
- <h3>{i18n.str`Latest transactions`}</h3>
- <Transactions account={account} />
- </div>
- </section>
- </Fragment>
- );
-}
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts
new file mode 100644
index 000000000..28fb7cb0c
--- /dev/null
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -0,0 +1,91 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { HttpError, HttpResponseOk, HttpResponsePaginated, utils } from "@gnu-taler/web-util/browser";
+import { AbsoluteTime, AmountJson, PaytoUriIBAN } from "@gnu-taler/taler-util";
+import { Loading } from "../../components/Loading.js";
+import { useComponentState } from "./state.js";
+import { ReadyView, InvalidIbanView} from "./views.js";
+import { VNode } from "preact";
+import { LoginForm } from "../LoginForm.js";
+import { ErrorLoading } from "../../components/ErrorLoading.js";
+
+export interface Props {
+ account: string;
+ onLoadNotOk: <T>(
+ error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
+ ) => VNode;
+}
+
+export type State = State.Loading | State.LoadingError | State.Ready | State.InvalidIban | State.UserNotFound;
+
+export namespace State {
+ export interface Loading {
+ status: "loading";
+ error: undefined;
+ }
+
+ export interface LoadingError {
+ status: "loading-error";
+ error: HttpError<SandboxBackend.SandboxError>;
+ }
+
+ export interface BaseInfo {
+ error: undefined;
+ }
+
+ export interface Ready extends BaseInfo {
+ status: "ready";
+ error: undefined;
+ account: string,
+ payto: PaytoUriIBAN,
+ balance: AmountJson,
+ balanceIsDebit: boolean,
+ limit: AmountJson,
+ }
+
+ export interface InvalidIban {
+ status: "invalid-iban",
+ error: HttpResponseOk<SandboxBackend.Access.BankAccountBalanceResponse>;
+ }
+
+ export interface UserNotFound {
+ status: "error-user-not-found",
+ error: HttpError<any>;
+ onRegister?: () => void;
+ }
+}
+
+export interface Transaction {
+ negative: boolean;
+ counterpart: string;
+ when: AbsoluteTime;
+ amount: AmountJson | undefined;
+ subject: string;
+}
+
+const viewMapping: utils.StateViewMap<State> = {
+ loading: Loading,
+ "error-user-not-found": LoginForm,
+ "invalid-iban": InvalidIbanView,
+ "loading-error": ErrorLoading,
+ ready: ReadyView,
+};
+
+export const AccountPage = utils.compose(
+ (p: Props) => useComponentState(p),
+ viewMapping,
+);
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts
new file mode 100644
index 000000000..bc59c9374
--- /dev/null
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -0,0 +1,87 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
+import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { useBackendContext } from "../../context/backend.js";
+import { useAccountDetails } from "../../hooks/access.js";
+import { notifyError } from "../../hooks/notification.js";
+import { Props, State } from "./index.js";
+
+export function useComponentState({ account, onLoadNotOk }: Props): State {
+ const result = useAccountDetails(account);
+ const backend = useBackendContext();
+ const { i18n } = useTranslationContext();
+
+ if (result.loading) {
+ 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({
+ title: i18n.str`Username or account label "${account}" not found`,
+ });
+ return {
+ status: "error-user-not-found",
+ error: result,
+ };
+ }
+ return {
+ status: "loading-error",
+ error: result,
+ };
+ }
+
+ const { data } = result;
+ const balance = Amounts.parseOrThrow(data.balance.amount);
+ const debitThreshold = Amounts.parseOrThrow(data.debitThreshold);
+ const payto = parsePaytoUri(data.paytoUri);
+
+ if (!payto || !payto.isKnown || payto.targetType !== "iban") {
+ return {
+ status: "invalid-iban",
+ error: result
+ };
+ }
+
+ const balanceIsDebit = data.balance.credit_debit_indicator == "debit";
+ const limit = balanceIsDebit
+ ? Amounts.sub(debitThreshold, balance).amount
+ : Amounts.add(balance, debitThreshold).amount;
+
+
+ return {
+ status: "ready",
+ error: undefined,
+ account,
+ balance,
+ balanceIsDebit,
+ limit,
+ payto
+ };
+}
diff --git a/packages/demobank-ui/src/pages/AccountPage/stories.tsx b/packages/demobank-ui/src/pages/AccountPage/stories.tsx
new file mode 100644
index 000000000..f3828a5d6
--- /dev/null
+++ b/packages/demobank-ui/src/pages/AccountPage/stories.tsx
@@ -0,0 +1,29 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import * as tests from "@gnu-taler/web-util/testing";
+import { ReadyView } from "./views.js";
+
+export default {
+ title: "account page",
+};
+
+export const Ready = tests.createExample(ReadyView, {});
diff --git a/packages/demobank-ui/src/pages/AccountPage/test.ts b/packages/demobank-ui/src/pages/AccountPage/test.ts
new file mode 100644
index 000000000..588b84c35
--- /dev/null
+++ b/packages/demobank-ui/src/pages/AccountPage/test.ts
@@ -0,0 +1,32 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import * as tests from "@gnu-taler/web-util/testing";
+import { SwrMockEnvironment } from "@gnu-taler/web-util/testing";
+import { expect } from "chai";
+import { CASHOUT_API_EXAMPLE } from "../../endpoints.js";
+import { Props } from "./index.js";
+import { useComponentState } from "./state.js";
+
+describe("Account states", () => {
+ it("should do some tests", async () => {
+ });
+});
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
new file mode 100644
index 000000000..b476759b4
--- /dev/null
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -0,0 +1,74 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, h, VNode } from "preact";
+import { Transactions } from "../../components/Transactions/index.js";
+import { PaymentOptions } from "../PaymentOptions.js";
+import { State } from "./index.js";
+import { CopyButton } from "../../components/CopyButton.js";
+
+export function InvalidIbanView({error}:State.InvalidIban) {
+ return (
+ <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+ );
+}
+
+export function ReadyView({ account, balance, balanceIsDebit, limit, payto }: State.Ready): VNode<{}> {
+ const { i18n } = useTranslationContext();
+ return <Fragment>
+ <div>
+ <h1 class="nav welcome-text">
+ <i18n.Translate>
+ Welcome, {account} (<a href={stringifyPaytoUri(payto)}>{payto.iban}</a>)! <CopyButton getContent={() => stringifyPaytoUri(payto)} />
+ </i18n.Translate>
+ </h1>
+ </div>
+
+ <section id="assets">
+ <div class="asset-summary">
+ <h2>{i18n.str`Bank account balance`}</h2>
+ {!balance ? (
+ <div class="large-amount" style={{ color: "gray" }}>
+ Waiting server response...
+ </div>
+ ) : (
+ <div class="large-amount amount">
+ {balanceIsDebit ? <b>-</b> : null}
+ <span class="value">{`${Amounts.stringifyValue(balance)}`}</span>
+ &nbsp;
+ <span class="currency">{`${balance.currency}`}</span>
+ </div>
+ )}
+ </div>
+ </section>
+ <section id="payments">
+ <div class="payments">
+ <h2>{i18n.str`Payments`}</h2>
+ <PaymentOptions limit={limit} />
+ </div>
+ </section>
+
+ <section style={{ marginTop: "2em" }}>
+ <div class="active">
+ <h3>{i18n.str`Latest transactions`}</h3>
+ <Transactions account={account} />
+ </div>
+ </section>
+ </Fragment>;
+}
+
diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx b/packages/demobank-ui/src/pages/AdminPage.tsx
index ce0feebce..73a4f9ca3 100644
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ b/packages/demobank-ui/src/pages/AdminPage.tsx
@@ -43,7 +43,7 @@ import { ErrorBannerFloat } from "./BankFrame.js";
import { ShowCashoutDetails } from "./BusinessAccount.js";
import { handleNotOkResult } from "./HomePage.js";
import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { ErrorMessage, notifyInfo } from "../hooks/notification.js";
const charset =
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx
index d9aa8fa36..2faf83a1c 100644
--- a/packages/demobank-ui/src/pages/BusinessAccount.tsx
+++ b/packages/demobank-ui/src/pages/BusinessAccount.tsx
@@ -44,7 +44,7 @@ import {
import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js";
import { ErrorBannerFloat } from "./BankFrame.js";
import { LoginForm } from "./LoginForm.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { handleNotOkResult } from "./HomePage.js";
import { ErrorMessage, notifyInfo } from "../hooks/notification.js";
import { Amount } from "./WalletWithdrawForm.js";
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index 93a9bdfae..86e511284 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -32,7 +32,7 @@ import { useBackendContext } from "../context/backend.js";
import { getInitialBackendBaseURL } from "../hooks/backend.js";
import { notifyError, notifyInfo } from "../hooks/notification.js";
import { useSettings } from "../hooks/settings.js";
-import { AccountPage } from "./AccountPage.js";
+import { AccountPage } from "./AccountPage/index.js";
import { AdminPage } from "./AdminPage.js";
import { LoginForm } from "./LoginForm.js";
import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index d2cb1bd8e..f0ae97d60 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -25,7 +25,7 @@ import { bankUiSettings } from "../settings.js";
import { undefinedIfEmpty } from "../utils.js";
import { ErrorBannerFloat } from "./BankFrame.js";
import { USERNAME_REGEX } from "./RegistrationPage.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
/**
* Collect and submit login data.
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index d8c1644b1..d16dc70f8 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -36,7 +36,7 @@ import {
undefinedIfEmpty,
validateIBAN,
} from "../utils.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
const logger = new Logger("PaytoWireTransferForm");
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index ded48564f..e52a5b11b 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -25,7 +25,7 @@ import { useTestingAPI } from "../hooks/access.js";
import { notifyError } from "../hooks/notification.js";
import { bankUiSettings } from "../settings.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
const logger = new Logger("RegistrationPage");
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 4c4a38e57..83be99d6f 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -30,7 +30,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
import { useAccessAPI } from "../hooks/access.js";
import { notifyError } from "../hooks/notification.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { forwardRef } from "preact/compat";
const logger = new Logger("WalletWithdrawForm");
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index cdb612155..2fa8e51b5 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -28,7 +28,7 @@ import { useMemo, useState } from "preact/hooks";
import { useAccessAnonAPI } from "../hooks/access.js";
import { notifyError } from "../hooks/notification.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
const logger = new Logger("WithdrawalConfirmationQuestion");