From 2ac73949e7cb8de44e56f2fecae617efab15671e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 21 Oct 2023 20:25:38 -0300 Subject: more ui --- packages/demobank-ui/src/assets/lang.svg | 48 ++ .../demobank-ui/src/components/Cashouts/views.tsx | 159 +++-- packages/demobank-ui/src/components/CopyButton.tsx | 32 +- .../demobank-ui/src/components/LangSelector.tsx | 3 +- packages/demobank-ui/src/components/Routing.tsx | 208 +++++- .../src/components/Transactions/views.tsx | 12 +- packages/demobank-ui/src/components/app.tsx | 3 +- packages/demobank-ui/src/context/config.ts | 10 +- packages/demobank-ui/src/hooks/access.ts | 24 +- packages/demobank-ui/src/hooks/circuit.ts | 14 +- packages/demobank-ui/src/pages.ts | 44 ++ .../demobank-ui/src/pages/AccountPage/views.tsx | 29 - packages/demobank-ui/src/pages/BankFrame.tsx | 122 ++-- packages/demobank-ui/src/pages/LoginForm.tsx | 12 +- .../demobank-ui/src/pages/OperationState/state.ts | 15 +- packages/demobank-ui/src/pages/PaymentOptions.tsx | 24 +- .../src/pages/PaytoWireTransferForm.tsx | 65 +- .../demobank-ui/src/pages/ProfileNavigation.tsx | 56 ++ .../demobank-ui/src/pages/RegistrationPage.tsx | 16 +- .../demobank-ui/src/pages/ShowAccountDetails.tsx | 123 ++-- .../src/pages/UpdateAccountPassword.tsx | 259 +++++--- .../demobank-ui/src/pages/WithdrawalQRCode.tsx | 1 + packages/demobank-ui/src/pages/admin/Account.tsx | 10 +- .../demobank-ui/src/pages/admin/AccountForm.tsx | 182 ++++- .../demobank-ui/src/pages/admin/AccountList.tsx | 58 +- packages/demobank-ui/src/pages/admin/AdminHome.tsx | 32 + .../src/pages/admin/CashoutListForAccount.tsx | 47 ++ .../src/pages/admin/CreateNewAccount.tsx | 24 +- packages/demobank-ui/src/pages/admin/Home.tsx | 143 ---- .../demobank-ui/src/pages/admin/RemoveAccount.tsx | 3 +- .../src/pages/business/CreateCashout.tsx | 422 ++++++++++++ packages/demobank-ui/src/pages/business/Home.tsx | 736 --------------------- .../src/pages/business/ShowCashoutDetails.tsx | 237 +++++++ packages/demobank-ui/src/route.ts | 167 +++++ 34 files changed, 1932 insertions(+), 1408 deletions(-) create mode 100644 packages/demobank-ui/src/assets/lang.svg create mode 100644 packages/demobank-ui/src/pages.ts create mode 100644 packages/demobank-ui/src/pages/ProfileNavigation.tsx create mode 100644 packages/demobank-ui/src/pages/admin/AdminHome.tsx create mode 100644 packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx delete mode 100644 packages/demobank-ui/src/pages/admin/Home.tsx create mode 100644 packages/demobank-ui/src/pages/business/CreateCashout.tsx delete mode 100644 packages/demobank-ui/src/pages/business/Home.tsx create mode 100644 packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx create mode 100644 packages/demobank-ui/src/route.ts diff --git a/packages/demobank-ui/src/assets/lang.svg b/packages/demobank-ui/src/assets/lang.svg new file mode 100644 index 000000000..dd72ce65e --- /dev/null +++ b/packages/demobank-ui/src/assets/lang.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx b/packages/demobank-ui/src/components/Cashouts/views.tsx index 0602f507e..32fe0aa9e 100644 --- a/packages/demobank-ui/src/components/Cashouts/views.tsx +++ b/packages/demobank-ui/src/components/Cashouts/views.tsx @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ -import { h, VNode } from "preact"; +import { Fragment, h, VNode } from "preact"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { State } from "./index.js"; import { format } from "date-fns"; @@ -33,55 +33,118 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode { export function ReadyView({ cashouts, onSelected }: State.Ready): VNode { const { i18n } = useTranslationContext(); - if (!cashouts.length) { - return ( -
- No cashout at the moment -
- ); - } + if (!cashouts.length) return
+ const txByDate = cashouts.reduce((prev, cur) => { + const d = cur.creation_time.t_s === "never" + ? "" + : format(cur.creation_time.t_s * 1000, "dd/MM/yyyy") + if (!prev[d]) { + prev[d] = [] + } + prev[d].push(cur) + return prev + }, {} as Record) return ( -
- - - - - - - - - - - - - {cashouts.map((item, idx) => { - return ( - - - + + + + + + + + + ) + })} + + + })} + + +
{i18n.str`Created`}{i18n.str`Confirmed`}{i18n.str`Total debit`}{i18n.str`Total credit`}{i18n.str`Status`}{i18n.str`Subject`}
{item.creation_time.t_s === "never" ? i18n.str`never` : format(item.creation_time.t_s, "dd/MM/yyyy HH:mm:ss")} - {item.confirmation_time +
+
+
+

Latest cashouts

+
+
+
+ + + + + + + + + + + + + {Object.entries(txByDate).map(([date, txs], idx) => { + return + + + + {txs.map(item => { + const creationTime = item.creation_time.t_s === "never" ? "" : format(item.creation_time.t_s * 1000, "HH:mm:ss") + const confirmationTime = item.confirmation_time ? item.confirmation_time.t_s === "never" ? i18n.str`never` : format(item.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss") - : "-"} - - - - - - - ); - })} - -
{i18n.str`Created`}
+ {date} +
{item.status} - { - e.preventDefault(); - onSelected(item.id); - }} - > - {item.subject} - -
+ : "-" + return (
+
{creationTime}
+ {/*
+
Amount
+
+ {item.negative ? i18n.str`sent` : i18n.str`received`} {item.amount ? ( + + + + ) : ( + <{i18n.str`invalid value`}> + )}
+ +
Counterpart
+
+ {item.negative ? i18n.str`to` : i18n.str`from`} {item.counterpart} +
+
+
+                            {item.subject}
+                          
+
+
*/} +
+ + {/* */} +
); + // } + } diff --git a/packages/demobank-ui/src/components/CopyButton.tsx b/packages/demobank-ui/src/components/CopyButton.tsx index b36de770e..ca1ceaa8a 100644 --- a/packages/demobank-ui/src/components/CopyButton.tsx +++ b/packages/demobank-ui/src/components/CopyButton.tsx @@ -5,31 +5,21 @@ import { useEffect, useState } from "preact/hooks"; export function CopyIcon(): VNode { return ( - - - + + ) }; export function CopiedIcon(): VNode { return ( - - + + ) }; -export function CopyButton({ getContent }: { getContent: () => string }): VNode { +export function CopyButton({ class: clazz, getContent }: { class: string, getContent: () => string }): VNode { const [copied, setCopied] = useState(false); function copyText(): void { navigator.clipboard.writeText(getContent() || ""); @@ -45,16 +35,14 @@ export function CopyButton({ getContent }: { getContent: () => string }): VNode if (!copied) { return ( -
- -
+ ); -} \ No newline at end of file +} diff --git a/packages/demobank-ui/src/components/LangSelector.tsx b/packages/demobank-ui/src/components/LangSelector.tsx index c1d0f64ef..7cf0300df 100644 --- a/packages/demobank-ui/src/components/LangSelector.tsx +++ b/packages/demobank-ui/src/components/LangSelector.tsx @@ -23,6 +23,7 @@ import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { strings as messages } from "../i18n/strings.js"; +import langIcon from "../assets/lang.svg"; type LangsNames = { [P in keyof typeof messages]: string; @@ -69,7 +70,7 @@ export function LangSelector(): VNode { setHidden((h) => !h); }}> - + {getLangName(lang)} diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx index 04cf96190..1d587fe32 100644 --- a/packages/demobank-ui/src/components/Routing.tsx +++ b/packages/demobank-ui/src/components/Routing.tsx @@ -19,19 +19,26 @@ import { createHashHistory } from "history"; import { Fragment, VNode, h } from "preact"; import { Route, Router, route } from "preact-router"; import { useEffect } from "preact/hooks"; -import { useBackendContext } from "../context/backend.js"; +import { useBackendState } from "../hooks/backend.js"; import { BankFrame } from "../pages/BankFrame.js"; import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js"; import { LoginForm } from "../pages/LoginForm.js"; import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js"; import { RegistrationPage } from "../pages/RegistrationPage.js"; -import { AdminHome } from "../pages/admin/Home.js"; -import { BusinessAccount } from "../pages/business/Home.js"; +import { AdminHome } from "../pages/admin/AdminHome.js"; +import { CreateCashout } from "../pages/business/CreateCashout.js"; import { bankUiSettings } from "../settings.js"; +import { ShowAccountDetails } from "../pages/ShowAccountDetails.js"; +import { UpdateAccountPassword } from "../pages/UpdateAccountPassword.js"; +import { RemoveAccount } from "../pages/admin/RemoveAccount.js"; +import { CreateNewAccount } from "../pages/admin/CreateNewAccount.js"; +import { CashoutListForAccount } from "../pages/admin/CashoutListForAccount.js"; +import { ShowCashoutDetails } from "../pages/business/ShowCashoutDetails.js"; +import { WireTransfer } from "../pages/admin/Account.js"; export function Routing(): VNode { const history = createHashHistory(); - const backend = useBackendContext(); + const backend = useBackendState(); const { i18n } = useTranslationContext(); if (backend.state.status === "loggedOut") { @@ -90,7 +97,7 @@ export function Routing(): VNode { const { isUserAdministrator, username } = backend.state return ( - + } /> + + { + route("/account") + }} + onCreateSuccess={() => { + route("/account") + }} + />} + /> + + ( + { + route("/account") + }} + onClear={() => { + route("/account") + }} + /> + )} + /> + + ( + { + route("/account") + }} + onCancel={() => { + route("/account") + }} + /> + )} + /> + ( + { + route("/account") + }} + onCancel={() => { + route("/account") + }} + /> + )} + /> + + ( + { + route(`/cashout/${cid}`) + }} + onClose={() => { + route("/account") + }} + /> + )} + /> + + ( + { + route("/account") + }} + onClear={() => { + route("/account") + }} + /> + )} + /> + ( + { + route("/account") + }} + onCancel={() => { + route("/account") + }} + /> + )} + /> + + ( + { + route(`/cashout/${cid}`) + }} + onClose={() => { + route("/account"); + }} + /> + )} + /> + + ( + { + route(`/cashout/${cid}`); + }} + onCancel={() => { + route("/account"); + }} + /> + )} + /> + + ( + { + route("/account"); + }} + /> + )} + /> + + + ( + { + route("/account") + }} + onSuccess={() => { + route("/account") + }} + /> + )} + /> + { @@ -115,6 +283,22 @@ export function Routing(): VNode { onRegister={() => { route("/register"); }} + onCreateAccount={() => { + route("/new-account") + }} + onShowAccountDetails={(aid) => { + route(`/profile/${aid}/details`) + }} + onRemoveAccount={(aid) => { + route(`/profile/${aid}/delete`) + }} + onShowCashoutForAccount={(aid) => { + route(`/profile/${aid}/cashouts`) + }} + onUpdateAccountPassword={(aid) => { + route(`/profile/${aid}/change-password`) + + }} />; } else { return - ( - { - route("/account"); - }} - onRegister={() => { - route("/register"); - }} - /> - )} - /> diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx index 5cdb47a0c..47daf8963 100644 --- a/packages/demobank-ui/src/components/Transactions/views.tsx +++ b/packages/demobank-ui/src/components/Transactions/views.tsx @@ -86,11 +86,13 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
Counterpart
- {item.negative ? i18n.str`to` : i18n.str`from`} {item.counterpart} + {item.negative ? i18n.str`to` : i18n.str`from`} + {item.counterpart} +
-                          {item.subject}
+                            {item.subject}
                           
@@ -102,7 +104,11 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode <{i18n.str`invalid value`}> )} - {item.counterpart} + + + {item.counterpart} + + {item.subject} ) })} diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index beb24da57..55e1178fe 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -27,6 +27,7 @@ import { BankCoreApiProvider } from "../context/config.js"; import { strings } from "../i18n/strings.js"; import { bankUiSettings } from "../settings.js"; import { Routing } from "./Routing.js"; +import { BankFrame } from "../pages/BankFrame.js"; const WITH_LOCAL_STORAGE_CACHE = false; const App: FunctionalComponent = () => { @@ -34,7 +35,7 @@ const App: FunctionalComponent = () => { return ( - + , }): VNode => { const [checked, setChecked] = useState() const { i18n } = useTranslationContext(); @@ -68,13 +70,13 @@ export const BankCoreApiProvider = ({ }, []); if (checked === undefined) { - return h("div", {}, "loading...") + return h(frameOnError, { children: h("div", {}, "loading...") }) } if (checked.type === "error") { - return h(ErrorLoading, { error: checked.error, showDetail: true }) + return h(frameOnError, { children: h(ErrorLoading, { error: checked.error, showDetail: true }) }) } if (checked.type === "incompatible") { - return h("div", {}, i18n.str`the bank backend is not supported. supported version "${checked.supported}", server version "${checked.result.version}"`) + return h(frameOnError, { children: h("div", {}, i18n.str`the bank backend is not supported. supported version "${checked.supported}", server version "${checked.result.version}"`) }) } const value: Type = { url, config: checked.config, api diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/access.ts index 7023b8803..da9812ffa 100644 --- a/packages/demobank-ui/src/hooks/access.ts +++ b/packages/demobank-ui/src/hooks/access.ts @@ -61,7 +61,7 @@ export function useWithdrawalDetails(wid: string) { // const { state: credentials } = useBackendState(); const { api } = useBankCoreApiContext(); - async function fetcher(wid: string) { + async function fetcher([wid]: [string]) { return await api.getWithdrawalById(wid) } @@ -114,7 +114,7 @@ export function usePublicAccounts(initial?: number) { const [offset, setOffset] = useState(initial); const { api } = useBankCoreApiContext(); - async function fetcher(txid: number | undefined) { + async function fetcher([txid]: [number | undefined]) { return await api.getPublicAccounts({ limit: MAX_RESULT_SIZE, offset: txid ? String(txid) : undefined, @@ -124,16 +124,16 @@ export function usePublicAccounts(initial?: number) { const { data, error } = useSWR, TalerHttpError>( [offset, "getPublicAccounts"], fetcher, { - refreshInterval: 0, - refreshWhenHidden: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - refreshWhenOffline: false, - errorRetryCount: 0, - errorRetryInterval: 1, - shouldRetryOnError: false, - keepPreviousData: true, - }); + refreshInterval: 0, + refreshWhenHidden: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshWhenOffline: false, + errorRetryCount: 0, + errorRetryInterval: 1, + shouldRetryOnError: false, + keepPreviousData: true, + }); const isLastPage = data && data.body.public_accounts.length < PAGE_SIZE; diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts index 0f7af5fe5..06e068d6d 100644 --- a/packages/demobank-ui/src/hooks/circuit.ts +++ b/packages/demobank-ui/src/hooks/circuit.ts @@ -15,16 +15,15 @@ */ import { useState } from "preact/hooks"; -import { useBackendContext } from "../context/backend.js"; import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js"; import { useBackendState } from "./backend.js"; -// FIX default import https://github.com/microsoft/TypeScript/issues/49189 import { AccessToken, AmountJson, Amounts, OperationOk, TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } from "@gnu-taler/taler-util"; import _useSWR, { SWRHook } from "swr"; import { useBankCoreApiContext } from "../context/config.js"; import { assertUnreachable } from "../pages/HomePage.js"; +// FIX default import https://github.com/microsoft/TypeScript/issues/49189 const useSWR = _useSWR as unknown as SWRHook; export type TransferCalculation = { @@ -44,12 +43,8 @@ type CashoutEstimators = { }; export function useEstimator(): CashoutEstimators { - const { state } = useBackendContext(); + const { state } = useBackendState(); const { api } = useBankCoreApiContext(); - const creds = - state.status !== "loggedIn" - ? undefined - : state.token; return { estimateByCredit: async (amount, fee, rate) => { const resp = await api.getCashoutRate({ @@ -101,13 +96,14 @@ export function useEstimator(): CashoutEstimators { } export function useRatiosAndFeeConfig() { - const { api } = useBankCoreApiContext(); + const { api, config } = useBankCoreApiContext(); + function fetcher() { return api.getConversionRates() } const { data, error } = useSWR, TalerHttpError>( - [, "getConversionRates"], fetcher, { + !config.have_cashout || !config.fiat_currency ? false : [, "getConversionRates"], fetcher, { refreshInterval: 60 * 1000, refreshWhenHidden: false, revalidateOnFocus: false, diff --git a/packages/demobank-ui/src/pages.ts b/packages/demobank-ui/src/pages.ts new file mode 100644 index 000000000..c78240a02 --- /dev/null +++ b/packages/demobank-ui/src/pages.ts @@ -0,0 +1,44 @@ +import { WithdrawalOperationPage } from "./pages/HomePage.js"; +import { PageEntry, pageDefinition } from "./route.js"; + +// const operationById: PageEntry<{ operationId: string }> = { +// url: pageDefinition("#/operation/:operationId"), +// view: WithdrawalOperationPage, +// }; + + +// const home: PageEntry = { +// url: "#/", +// view: Home, +// }; +// const cases: PageEntry = { +// url: "#/cases", +// view: Cases, +// }; + +// const newFormEntry: PageEntry<{ account?: string; type?: string }> = { +// url: pageDefinition("#/account/:account/new/:type?"), +// view: NewFormEntry, +// }; + +// const settings: PageEntry = { +// url: "#/settings", +// view: Settings, +// }; +// const officer: PageEntry = { +// url: "#/officer", +// view: Officer, +// }; +// const welcome: PageEntry<{ asd?: string; name?: string }> = { +// url: pageDefinition("#/welcome/:name?"), +// view: Welcome, +// }; +// const form: PageEntry<{ number?: string }> = { +// url: pageDefinition("#/form/:number?"), +// view: AntiMoneyLaunderingForm, +// }; + +export const Pages = { + // operationById, + +}; diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx index 0604001e3..00643ec3e 100644 --- a/packages/demobank-ui/src/pages/AccountPage/views.tsx +++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx @@ -54,40 +54,11 @@ function ShowDemoInfo(): VNode { } export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> { - const { i18n } = useTranslationContext(); return - - - ; } -function MaybeBusinessButton({ - account, - onClick, -}: { - account: string; - onClick: () => void; -}): VNode { - const { i18n } = useTranslationContext(); - return - // const result = useBusinessAccountDetails(account); - // if (!result.ok) return ; - // return ( - //
- // - //
- // ); -} diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index 96ce9c317..c0babd0c9 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -23,8 +23,8 @@ import { Attention } from "../components/Attention.js"; import { CopyButton } from "../components/CopyButton.js"; import { LangSelector } from "../components/LangSelector.js"; import { Loading } from "../components/Loading.js"; -import { useBackendContext } from "../context/backend.js"; import { useAccountDetails } from "../hooks/access.js"; +import { useBackendState } from "../hooks/backend.js"; import { getAllBooleanSettings, getLabelForSetting, useSettings } from "../hooks/settings.js"; import { bankUiSettings } from "../settings.js"; import { RenderAmount } from "./PaytoWireTransferForm.js"; @@ -49,7 +49,7 @@ export function BankFrame({ children: ComponentChildren; }): VNode { const { i18n } = useTranslationContext(); - const backend = useBackendContext(); + const backend = useBackendState(); const [settings, updateSettings] = useSettings(); const [open, setOpen] = useState(false) @@ -80,9 +80,9 @@ export function BankFrame({ return (