aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension')
-rw-r--r--packages/taler-wallet-webextension/src/NavigationBar.tsx31
-rw-r--r--packages/taler-wallet-webextension/src/components/Banner.stories.tsx76
-rw-r--r--packages/taler-wallet-webextension/src/components/Banner.tsx40
-rw-r--r--packages/taler-wallet-webextension/src/components/PaymentButtons.tsx153
-rw-r--r--packages/taler-wallet-webextension/src/components/PendingTransactions.tsx102
-rw-r--r--packages/taler-wallet-webextension/src/components/ProductList.tsx89
-rw-r--r--packages/taler-wallet-webextension/src/components/styled/index.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/context/backend.ts2
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Payment/views.tsx251
-rw-r--r--packages/taler-wallet-webextension/src/cta/Refund/views.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx16
-rw-r--r--packages/taler-wallet-webextension/src/popup/Application.tsx246
-rw-r--r--packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts4
-rw-r--r--packages/taler-wallet-webextension/src/stories.tsx8
-rw-r--r--packages/taler-wallet-webextension/src/utils/index.ts2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts1
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts1
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts1
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Application.tsx741
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx33
21 files changed, 997 insertions, 808 deletions
diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx
index ab36af376..1c26450f7 100644
--- a/packages/taler-wallet-webextension/src/NavigationBar.tsx
+++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx
@@ -45,6 +45,7 @@ import warningIcon from "./svg/warning_24px.svg";
* @author sebasjm
*/
+// eslint-disable-next-line @typescript-eslint/ban-types
type PageLocation<DynamicPart extends object> = {
pattern: string;
(params: DynamicPart): string;
@@ -62,6 +63,7 @@ function replaceAll(
return result;
}
+// eslint-disable-next-line @typescript-eslint/ban-types
function pageDefinition<T extends object>(pattern: string): PageLocation<T> {
const patternParams = pattern.match(/(:[\w?]*)/g);
if (!patternParams)
@@ -133,7 +135,8 @@ export const Pages = {
),
};
-export function PopupNavBar({ path = "" }: { path?: string }): VNode {
+export type PopupNavBarOptions = "balance" | "backup" | "dev";
+export function PopupNavBar({ path }: { path?: PopupNavBarOptions }): VNode {
const api = useBackendContext();
const hook = useAsyncAsHook(async () => {
return await api.wallet.call(
@@ -146,13 +149,10 @@ export function PopupNavBar({ path = "" }: { path?: string }): VNode {
const { i18n } = useTranslationContext();
return (
<NavigationHeader>
- <a
- href={Pages.balance}
- class={path.startsWith("/balance") ? "active" : ""}
- >
+ <a href={Pages.balance} class={path === "balance" ? "active" : ""}>
<i18n.Translate>Balance</i18n.Translate>
</a>
- <a href={Pages.backup} class={path.startsWith("/backup") ? "active" : ""}>
+ <a href={Pages.backup} class={path === "backup" ? "active" : ""}>
<i18n.Translate>Backup</i18n.Translate>
</a>
<div style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}>
@@ -185,8 +185,8 @@ export function PopupNavBar({ path = "" }: { path?: string }): VNode {
</NavigationHeader>
);
}
-
-export function WalletNavBar({ path = "" }: { path?: string }): VNode {
+export type WalletNavBarOptions = "balance" | "backup" | "dev";
+export function WalletNavBar({ path }: { path?: WalletNavBarOptions }): VNode {
const { i18n } = useTranslationContext();
const api = useBackendContext();
@@ -196,21 +196,16 @@ export function WalletNavBar({ path = "" }: { path?: string }): VNode {
{},
);
});
- const attentionCount = !hook || hook.hasError ? 0 : hook.response.total;
+ const attentionCount =
+ (!hook || hook.hasError ? 0 : hook.response?.total) ?? 0;
return (
<NavigationHeaderHolder>
<NavigationHeader>
- <a
- href={Pages.balance}
- class={path.startsWith("/balance") ? "active" : ""}
- >
+ <a href={Pages.balance} class={path === "balance" ? "active" : ""}>
<i18n.Translate>Balance</i18n.Translate>
</a>
- <a
- href={Pages.backup}
- class={path.startsWith("/backup") ? "active" : ""}
- >
+ <a href={Pages.backup} class={path === "backup" ? "active" : ""}>
<i18n.Translate>Backup</i18n.Translate>
</a>
@@ -223,7 +218,7 @@ export function WalletNavBar({ path = "" }: { path?: string }): VNode {
)}
<JustInDevMode>
- <a href={Pages.dev} class={path.startsWith("/dev") ? "active" : ""}>
+ <a href={Pages.dev} class={path === "dev" ? "active" : ""}>
<i18n.Translate>Dev</i18n.Translate>
</a>
</JustInDevMode>
diff --git a/packages/taler-wallet-webextension/src/components/Banner.stories.tsx b/packages/taler-wallet-webextension/src/components/Banner.stories.tsx
index 39012480b..60b100478 100644
--- a/packages/taler-wallet-webextension/src/components/Banner.stories.tsx
+++ b/packages/taler-wallet-webextension/src/components/Banner.stories.tsx
@@ -65,23 +65,25 @@ export const BasicExample = (): VNode => (
</a>
</p>
<Banner
- elements={[
- {
- icon: <SignalWifiOffIcon color="gray" />,
- description: (
- <Typography>
- You have lost connection to the internet. This app is offline.
- </Typography>
- ),
- },
- ]}
+ // elements={[
+ // {
+ // icon: <SignalWifiOffIcon color="gray" />,
+ // description: (
+ // <Typography>
+ // You have lost connection to the internet. This app is offline.
+ // </Typography>
+ // ),
+ // },
+ // ]}
confirm={{
label: "turn on wifi",
action: async () => {
return;
},
}}
- />
+ >
+ <div />
+ </Banner>
</Wrapper>
</Fragment>
);
@@ -92,31 +94,33 @@ export const PendingOperation = (): VNode => (
<Banner
title="PENDING TRANSACTIONS"
style={{ backgroundColor: "lightcyan", padding: 8 }}
- elements={[
- {
- icon: (
- <Avatar
- style={{
- border: "solid blue 1px",
- color: "blue",
- boxSizing: "border-box",
- }}
- >
- P
- </Avatar>
- ),
- description: (
- <Fragment>
- <Typography inline bold>
- EUR 37.95
- </Typography>
- &nbsp;
- <Typography inline>- 5 feb 2022</Typography>
- </Fragment>
- ),
- },
- ]}
- />
+ // elements={[
+ // {
+ // icon: (
+ // <Avatar
+ // style={{
+ // border: "solid blue 1px",
+ // color: "blue",
+ // boxSizing: "border-box",
+ // }}
+ // >
+ // P
+ // </Avatar>
+ // ),
+ // description: (
+ // <Fragment>
+ // <Typography inline bold>
+ // EUR 37.95
+ // </Typography>
+ // &nbsp;
+ // <Typography inline>- 5 feb 2022</Typography>
+ // </Fragment>
+ // ),
+ // },
+ // ]}
+ >
+ asd
+ </Banner>
</Wrapper>
</Fragment>
);
diff --git a/packages/taler-wallet-webextension/src/components/Banner.tsx b/packages/taler-wallet-webextension/src/components/Banner.tsx
index f95647d42..a91fd384f 100644
--- a/packages/taler-wallet-webextension/src/components/Banner.tsx
+++ b/packages/taler-wallet-webextension/src/components/Banner.tsx
@@ -13,21 +13,20 @@
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { h, Fragment, VNode, JSX } from "preact";
-import { Divider } from "../mui/Divider.js";
+import { ComponentChildren, Fragment, h, JSX, VNode } from "preact";
import { Button } from "../mui/Button.js";
-import { Typography } from "../mui/Typography.js";
-import { Avatar } from "../mui/Avatar.js";
+import { Divider } from "../mui/Divider.js";
import { Grid } from "../mui/Grid.js";
import { Paper } from "../mui/Paper.js";
interface Props extends JSX.HTMLAttributes<HTMLDivElement> {
titleHead?: VNode;
- elements: {
- icon?: VNode;
- description: VNode;
- action?: () => void;
- }[];
+ children: ComponentChildren;
+ // elements: {
+ // icon?: VNode;
+ // description: VNode;
+ // action?: () => void;
+ // }[];
confirm?: {
label: string;
action: () => Promise<void>;
@@ -36,8 +35,9 @@ interface Props extends JSX.HTMLAttributes<HTMLDivElement> {
export function Banner({
titleHead,
- elements,
+ children,
confirm,
+ href,
...rest
}: Props): VNode {
return (
@@ -49,25 +49,7 @@ export function Banner({
</Grid>
)}
<Grid container columns={1}>
- {elements.map((e, i) => (
- <Grid
- container
- item
- xs={1}
- key={i}
- wrap="nowrap"
- spacing={1}
- alignItems="center"
- onClick={e.action}
- >
- {e.icon && (
- <Grid item xs={"auto"}>
- <Avatar>{e.icon}</Avatar>
- </Grid>
- )}
- <Grid item>{e.description}</Grid>
- </Grid>
- ))}
+ {children}
</Grid>
{confirm && (
<Grid container justifyContent="flex-end" spacing={8}>
diff --git a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx
new file mode 100644
index 000000000..def1e16eb
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx
@@ -0,0 +1,153 @@
+/*
+ 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 {
+ AmountJson,
+ Amounts,
+ PreparePayResult,
+ PreparePayResultType,
+} from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Amount } from "./Amount.js";
+import { Part } from "./Part.js";
+import { QR } from "./QR.js";
+import { LinkSuccess, WarningBox } from "./styled/index.js";
+import { useTranslationContext } from "../context/translation.js";
+import { Button } from "../mui/Button.js";
+import { ButtonHandler } from "../mui/handlers.js";
+import { assertUnreachable } from "../utils/index.js";
+
+interface Props {
+ payStatus: PreparePayResult;
+ payHandler: ButtonHandler | undefined;
+ balance: AmountJson | undefined;
+ uri: string;
+ amount: AmountJson;
+ goToWalletManualWithdraw: (currency: string) => Promise<void>;
+}
+
+export function PaymentButtons({
+ payStatus,
+ uri,
+ payHandler,
+ balance,
+ amount,
+ goToWalletManualWithdraw,
+}: Props): VNode {
+ const { i18n } = useTranslationContext();
+ if (payStatus.status === PreparePayResultType.PaymentPossible) {
+ const privateUri = `${uri}&n=${payStatus.noncePriv}`;
+
+ return (
+ <Fragment>
+ <section>
+ <Button
+ variant="contained"
+ color="success"
+ onClick={payHandler?.onClick}
+ >
+ <i18n.Translate>
+ Pay &nbsp;
+ {<Amount value={amount} />}
+ </i18n.Translate>
+ </Button>
+ </section>
+ <PayWithMobile uri={privateUri} />
+ </Fragment>
+ );
+ }
+
+ if (payStatus.status === PreparePayResultType.InsufficientBalance) {
+ let BalanceMessage = "";
+ if (!balance) {
+ BalanceMessage = i18n.str`You have no balance for this currency. Withdraw digital cash first.`;
+ } else {
+ const balanceShouldBeEnough = Amounts.cmp(balance, amount) !== -1;
+ if (balanceShouldBeEnough) {
+ BalanceMessage = i18n.str`Could not find enough coins to pay. Even if you have enough ${balance.currency} some restriction may apply.`;
+ } else {
+ BalanceMessage = i18n.str`Your current balance is not enough.`;
+ }
+ }
+ const uriPrivate = `${uri}&n=${payStatus.noncePriv}`;
+
+ return (
+ <Fragment>
+ <section>
+ <WarningBox>{BalanceMessage}</WarningBox>
+ </section>
+ <section>
+ <Button
+ variant="contained"
+ color="success"
+ onClick={() => goToWalletManualWithdraw(Amounts.stringify(amount))}
+ >
+ <i18n.Translate>Get digital cash</i18n.Translate>
+ </Button>
+ </section>
+ <PayWithMobile uri={uriPrivate} />
+ </Fragment>
+ );
+ }
+ if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
+ return (
+ <Fragment>
+ <section>
+ {payStatus.paid && payStatus.contractTerms.fulfillment_message && (
+ <Part
+ title={<i18n.Translate>Merchant message</i18n.Translate>}
+ text={payStatus.contractTerms.fulfillment_message}
+ kind="neutral"
+ />
+ )}
+ </section>
+ {!payStatus.paid && <PayWithMobile uri={uri} />}
+ </Fragment>
+ );
+ }
+
+ assertUnreachable(payStatus);
+}
+
+function PayWithMobile({ uri }: { uri: string }): VNode {
+ const { i18n } = useTranslationContext();
+
+ const [showQR, setShowQR] = useState<boolean>(false);
+
+ return (
+ <section>
+ <LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}>
+ {!showQR ? (
+ <i18n.Translate>Pay with a mobile phone</i18n.Translate>
+ ) : (
+ <i18n.Translate>Hide QR</i18n.Translate>
+ )}
+ </LinkSuccess>
+ {showQR && (
+ <div>
+ <QR text={uri} />
+ <i18n.Translate>
+ Scan the QR code or &nbsp;
+ <a href={uri}>
+ <i18n.Translate>click here</i18n.Translate>
+ </a>
+ </i18n.Translate>
+ </div>
+ )}
+ </section>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx
index 85b43fb4e..e41ff2836 100644
--- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx
+++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx
@@ -26,6 +26,7 @@ import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Avatar } from "../mui/Avatar.js";
+import { Grid } from "../mui/Grid.js";
import { Typography } from "../mui/Typography.js";
import Banner from "./Banner.js";
import { Time } from "./Time.js";
@@ -34,6 +35,11 @@ interface Props extends JSX.HTMLAttributes {
goToTransaction: (id: string) => Promise<void>;
}
+/**
+ * this cache will save the tx from the previous render
+ */
+const cache = { tx: [] as Transaction[] };
+
export function PendingTransactions({ goToTransaction }: Props): VNode {
const api = useBackendContext();
const state = useAsyncAsHook(() =>
@@ -49,12 +55,13 @@ export function PendingTransactions({ goToTransaction }: Props): VNode {
const transactions =
!state || state.hasError
- ? []
+ ? cache.tx
: state.response.transactions.filter((t) => t.pending);
- if (!state || state.hasError || !transactions.length) {
+ if (!transactions.length) {
return <Fragment />;
}
+ cache.tx = transactions;
return (
<PendingTransactionsView
goToTransaction={goToTransaction}
@@ -72,46 +79,67 @@ export function PendingTransactionsView({
}): VNode {
const { i18n } = useTranslationContext();
return (
- <Banner
- titleHead={<i18n.Translate>PENDING OPERATIONS</i18n.Translate>}
+ <div
style={{
backgroundColor: "lightcyan",
- maxHeight: 150,
- padding: 8,
- flexGrow: 1,
- maxWidth: 500,
- overflowY: transactions.length > 3 ? "scroll" : "hidden",
+ display: "flex",
+ justifyContent: "center",
}}
- elements={transactions.map((t) => {
- const amount = Amounts.parseOrThrow(t.amountEffective);
- return {
- icon: (
- <Avatar
- style={{
- border: "solid blue 1px",
- color: "blue",
- boxSizing: "border-box",
+ >
+ <Banner
+ titleHead={<i18n.Translate>PENDING OPERATIONS</i18n.Translate>}
+ style={{
+ backgroundColor: "lightcyan",
+ maxHeight: 150,
+ padding: 8,
+ flexGrow: 1,
+ maxWidth: 500,
+ overflowY: transactions.length > 3 ? "scroll" : "hidden",
+ }}
+ >
+ {transactions.map((t, i) => {
+ const amount = Amounts.parseOrThrow(t.amountEffective);
+ return (
+ <Grid
+ container
+ item
+ xs={1}
+ key={i}
+ wrap="nowrap"
+ role="button"
+ spacing={1}
+ alignItems="center"
+ onClick={() => {
+ goToTransaction(t.transactionId);
}}
>
- {t.type.substring(0, 1)}
- </Avatar>
- ),
- action: () => goToTransaction(t.transactionId),
- description: (
- <Fragment>
- <Typography inline bold>
- {amount.currency} {Amounts.stringifyValue(amount)}
- </Typography>
- &nbsp;-&nbsp;
- <Time
- timestamp={AbsoluteTime.fromTimestamp(t.timestamp)}
- format="dd MMMM yyyy"
- />
- </Fragment>
- ),
- };
- })}
- />
+ <Grid item xs={"auto"}>
+ <Avatar
+ style={{
+ border: "solid blue 1px",
+ color: "blue",
+ boxSizing: "border-box",
+ }}
+ >
+ {t.type.substring(0, 1)}
+ </Avatar>
+ </Grid>
+
+ <Grid item>
+ <Typography inline bold>
+ {amount.currency} {Amounts.stringifyValue(amount)}
+ </Typography>
+ &nbsp;-&nbsp;
+ <Time
+ timestamp={AbsoluteTime.fromTimestamp(t.timestamp)}
+ format="dd MMMM yyyy"
+ />
+ </Grid>
+ </Grid>
+ );
+ })}
+ </Banner>
+ </div>
);
}
diff --git a/packages/taler-wallet-webextension/src/components/ProductList.tsx b/packages/taler-wallet-webextension/src/components/ProductList.tsx
new file mode 100644
index 000000000..a78733179
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/ProductList.tsx
@@ -0,0 +1,89 @@
+/*
+ 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, Product } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { SmallLightText } from "./styled/index.js";
+import { useTranslationContext } from "../context/translation.js";
+
+export function ProductList({ products }: { products: Product[] }): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <Fragment>
+ <SmallLightText style={{ margin: ".5em" }}>
+ <i18n.Translate>List of products</i18n.Translate>
+ </SmallLightText>
+ <dl>
+ {products.map((p, i) => {
+ if (p.price) {
+ const pPrice = Amounts.parseOrThrow(p.price);
+ return (
+ <div key={i} style={{ display: "flex", textAlign: "left" }}>
+ <div>
+ <img
+ src={p.image ? p.image : undefined}
+ style={{ width: 32, height: 32 }}
+ />
+ </div>
+ <div>
+ <dt>
+ {p.quantity ?? 1} x {p.description}{" "}
+ <span style={{ color: "gray" }}>
+ {Amounts.stringify(pPrice)}
+ </span>
+ </dt>
+ <dd>
+ <b>
+ {Amounts.stringify(
+ Amounts.mult(pPrice, p.quantity ?? 1).amount,
+ )}
+ </b>
+ </dd>
+ </div>
+ </div>
+ );
+ }
+ return (
+ <div key={i} style={{ display: "flex", textAlign: "left" }}>
+ <div>
+ <img src={p.image} style={{ width: 32, height: 32 }} />
+ </div>
+ <div>
+ <dt>
+ {p.quantity ?? 1} x {p.description}
+ </dt>
+ <dd>
+ <i18n.Translate>Total</i18n.Translate>
+ {` `}
+ {p.price ? (
+ `${Amounts.stringifyValue(
+ Amounts.mult(
+ Amounts.parseOrThrow(p.price),
+ p.quantity ?? 1,
+ ).amount,
+ )} ${p}`
+ ) : (
+ <i18n.Translate>free</i18n.Translate>
+ )}
+ </dd>
+ </div>
+ </div>
+ );
+ })}
+ </dl>
+ </Fragment>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx
index 7a3c27c73..8e98f75eb 100644
--- a/packages/taler-wallet-webextension/src/components/styled/index.tsx
+++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx
@@ -159,7 +159,7 @@ export const Middle = styled.div`
height: 100%;
`;
-export const PopupBox = styled.div<{ noPadding?: boolean; devMode?: boolean }>`
+export const PopupBox = styled.div<{ noPadding?: boolean }>`
height: 290px;
width: 500px;
overflow-y: visible;
diff --git a/packages/taler-wallet-webextension/src/context/backend.ts b/packages/taler-wallet-webextension/src/context/backend.ts
index e00a70080..280fb266d 100644
--- a/packages/taler-wallet-webextension/src/context/backend.ts
+++ b/packages/taler-wallet-webextension/src/context/backend.ts
@@ -29,7 +29,7 @@ const initial = wxApi;
const Context = createContext<Type>(initial);
-type Props = Partial<WxApiType> & {
+type Props = Partial<Type> & {
children: ComponentChildren;
};
diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx
index 8484680bf..a53fa881a 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx
@@ -23,7 +23,7 @@ import { Part } from "../../components/Part.js";
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
import { Time } from "../../components/Time.js";
import { useTranslationContext } from "../../context/translation.js";
-import { ButtonsSection } from "../Payment/views.js";
+import { PaymentButtons } from "../../components/PaymentButtons";
import { State } from "./index.js";
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
@@ -83,7 +83,7 @@ export function ReadyView(
kind="neutral"
/>
</section>
- <ButtonsSection
+ <PaymentButtons
amount={amount}
balance={balance}
payStatus={payStatus}
diff --git a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
index 0f6cb5c28..efc8bcfc4 100644
--- a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
@@ -16,35 +16,17 @@
import {
AbsoluteTime,
- AmountJson,
Amounts,
MerchantContractTerms as ContractTerms,
- PreparePayResult,
PreparePayResultType,
- Product,
} from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { Amount } from "../../components/Amount.js";
-import { ErrorMessage } from "../../components/ErrorMessage.js";
import { LoadingError } from "../../components/LoadingError.js";
-import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js";
-import { QR } from "../../components/QR.js";
-import {
- Link,
- LinkSuccess,
- SmallLightText,
- SubTitle,
- SuccessBox,
- WalletAction,
- WarningBox,
-} from "../../components/styled/index.js";
+import { PaymentButtons } from "../../components/PaymentButtons.js";
+import { Link, SuccessBox, WarningBox } from "../../components/styled/index.js";
import { Time } from "../../components/Time.js";
import { useTranslationContext } from "../../context/translation.js";
-import { Button } from "../../mui/Button.js";
-import { ButtonHandler } from "../../mui/handlers.js";
-import { assertUnreachable } from "../../utils/index.js";
import { MerchantDetails, PurchaseDetails } from "../../wallet/Transaction.js";
import { State } from "./index.js";
@@ -77,44 +59,12 @@ export function BaseView(state: SupportedStates): VNode {
? Amounts.parseOrThrow(state.payStatus.amountEffective)
: state.amount,
};
- // const totalFees = Amounts.sub(price.effective, price.raw).amount;
return (
- <WalletAction>
- <LogoHeader />
-
- <SubTitle>
- <i18n.Translate>Digital cash payment</i18n.Translate>
- </SubTitle>
-
+ <Fragment>
<ShowImportantMessage state={state} />
<section style={{ textAlign: "left" }}>
- {/* {state.payStatus.status !== PreparePayResultType.InsufficientBalance &&
- Amounts.isNonZero(totalFees) && (
- <Part
- big
- title={<i18n.Translate>Total to pay</i18n.Translate>}
- text={<Amount value={state.payStatus.amountEffective} />}
- kind="negative"
- />
- )}
- <Part
- big
- title={<i18n.Translate>Purchase amount</i18n.Translate>}
- text={<Amount value={state.payStatus.amountRaw} />}
- kind="neutral"
- />
- {Amounts.isNonZero(totalFees) && (
- <Fragment>
- <Part
- big
- title={<i18n.Translate>Fee</i18n.Translate>}
- text={<Amount value={totalFees} />}
- kind="negative"
- />
- </Fragment>
- )} */}
<Part
title={<i18n.Translate>Purchase</i18n.Translate>}
text={contractTerms.summary}
@@ -125,9 +75,6 @@ export function BaseView(state: SupportedStates): VNode {
text={<MerchantDetails merchant={contractTerms.merchant} />}
kind="neutral"
/>
- {/* <pre>{JSON.stringify(price)}</pre>
- <hr />
- <pre>{JSON.stringify(state.payStatus, undefined, 2)}</pre> */}
<Part
title={<i18n.Translate>Details</i18n.Translate>}
text={
@@ -166,7 +113,7 @@ export function BaseView(state: SupportedStates): VNode {
/>
)}
</section>
- <ButtonsSection
+ <PaymentButtons
amount={state.amount}
balance={state.balance}
payStatus={state.payStatus}
@@ -179,75 +126,6 @@ export function BaseView(state: SupportedStates): VNode {
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
- </WalletAction>
- );
-}
-
-export function ProductList({ products }: { products: Product[] }): VNode {
- const { i18n } = useTranslationContext();
- return (
- <Fragment>
- <SmallLightText style={{ margin: ".5em" }}>
- <i18n.Translate>List of products</i18n.Translate>
- </SmallLightText>
- <dl>
- {products.map((p, i) => {
- if (p.price) {
- const pPrice = Amounts.parseOrThrow(p.price);
- return (
- <div key={i} style={{ display: "flex", textAlign: "left" }}>
- <div>
- <img
- src={p.image ? p.image : undefined}
- style={{ width: 32, height: 32 }}
- />
- </div>
- <div>
- <dt>
- {p.quantity ?? 1} x {p.description}{" "}
- <span style={{ color: "gray" }}>
- {Amounts.stringify(pPrice)}
- </span>
- </dt>
- <dd>
- <b>
- {Amounts.stringify(
- Amounts.mult(pPrice, p.quantity ?? 1).amount,
- )}
- </b>
- </dd>
- </div>
- </div>
- );
- }
- return (
- <div key={i} style={{ display: "flex", textAlign: "left" }}>
- <div>
- <img src={p.image} style={{ width: 32, height: 32 }} />
- </div>
- <div>
- <dt>
- {p.quantity ?? 1} x {p.description}
- </dt>
- <dd>
- <i18n.Translate>Total</i18n.Translate>
- {` `}
- {p.price ? (
- `${Amounts.stringifyValue(
- Amounts.mult(
- Amounts.parseOrThrow(p.price),
- p.quantity ?? 1,
- ).amount,
- )} ${p}`
- ) : (
- <i18n.Translate>free</i18n.Translate>
- )}
- </dd>
- </div>
- </div>
- );
- })}
- </dl>
</Fragment>
);
}
@@ -284,124 +162,3 @@ function ShowImportantMessage({ state }: { state: SupportedStates }): VNode {
return <Fragment />;
}
-
-export function PayWithMobile({ uri }: { uri: string }): VNode {
- const { i18n } = useTranslationContext();
-
- const [showQR, setShowQR] = useState<boolean>(false);
-
- return (
- <section>
- <LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}>
- {!showQR ? (
- <i18n.Translate>Pay with a mobile phone</i18n.Translate>
- ) : (
- <i18n.Translate>Hide QR</i18n.Translate>
- )}
- </LinkSuccess>
- {showQR && (
- <div>
- <QR text={uri} />
- <i18n.Translate>
- Scan the QR code or &nbsp;
- <a href={uri}>
- <i18n.Translate>click here</i18n.Translate>
- </a>
- </i18n.Translate>
- </div>
- )}
- </section>
- );
-}
-
-interface ButtonSectionProps {
- payStatus: PreparePayResult;
- payHandler: ButtonHandler | undefined;
- balance: AmountJson | undefined;
- uri: string;
- amount: AmountJson;
- goToWalletManualWithdraw: (currency: string) => Promise<void>;
-}
-
-export function ButtonsSection({
- payStatus,
- uri,
- payHandler,
- balance,
- amount,
- goToWalletManualWithdraw,
-}: ButtonSectionProps): VNode {
- const { i18n } = useTranslationContext();
- if (payStatus.status === PreparePayResultType.PaymentPossible) {
- const privateUri = `${uri}&n=${payStatus.noncePriv}`;
-
- return (
- <Fragment>
- <section>
- <Button
- variant="contained"
- color="success"
- onClick={payHandler?.onClick}
- >
- <i18n.Translate>
- Pay &nbsp;
- {<Amount value={amount} />}
- </i18n.Translate>
- </Button>
- </section>
- <PayWithMobile uri={privateUri} />
- </Fragment>
- );
- }
-
- if (payStatus.status === PreparePayResultType.InsufficientBalance) {
- let BalanceMessage = "";
- if (!balance) {
- BalanceMessage = i18n.str`You have no balance for this currency. Withdraw digital cash first.`;
- } else {
- const balanceShouldBeEnough = Amounts.cmp(balance, amount) !== -1;
- if (balanceShouldBeEnough) {
- BalanceMessage = i18n.str`Could not find enough coins to pay. Even if you have enough ${balance.currency} some restriction may apply.`;
- } else {
- BalanceMessage = i18n.str`Your current balance is not enough.`;
- }
- }
- const uriPrivate = `${uri}&n=${payStatus.noncePriv}`;
-
- return (
- <Fragment>
- <section>
- <WarningBox>{BalanceMessage}</WarningBox>
- </section>
- <section>
- <Button
- variant="contained"
- color="success"
- onClick={() => goToWalletManualWithdraw(Amounts.stringify(amount))}
- >
- <i18n.Translate>Get digital cash</i18n.Translate>
- </Button>
- </section>
- <PayWithMobile uri={uriPrivate} />
- </Fragment>
- );
- }
- if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
- return (
- <Fragment>
- <section>
- {payStatus.paid && payStatus.contractTerms.fulfillment_message && (
- <Part
- title={<i18n.Translate>Merchant message</i18n.Translate>}
- text={payStatus.contractTerms.fulfillment_message}
- kind="neutral"
- />
- )}
- </section>
- {!payStatus.paid && <PayWithMobile uri={uri} />}
- </Fragment>
- );
- }
-
- assertUnreachable(payStatus);
-}
diff --git a/packages/taler-wallet-webextension/src/cta/Refund/views.tsx b/packages/taler-wallet-webextension/src/cta/Refund/views.tsx
index 4b5ff70dd..a55bc43dd 100644
--- a/packages/taler-wallet-webextension/src/cta/Refund/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Refund/views.tsx
@@ -23,7 +23,7 @@ import { Part } from "../../components/Part.js";
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.js";
-import { ProductList } from "../Payment/views.js";
+import { ProductList } from "../../components/ProductList.js";
import { State } from "./index.js";
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx
index 5c35151c8..9dbe24b7e 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx
@@ -14,12 +14,12 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { ExchangeTosStatus } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { Amount } from "../../components/Amount.js";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LoadingError } from "../../components/LoadingError.js";
-import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js";
import { QR } from "../../components/QR.js";
import { SelectList } from "../../components/SelectList.js";
@@ -27,17 +27,14 @@ import {
Input,
Link,
LinkSuccess,
- SubTitle,
SvgIcon,
- WalletAction,
} from "../../components/styled/index.js";
+import { TermsOfService } from "../../components/TermsOfService/index.js";
import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.js";
import editIcon from "../../svg/edit_24px.svg";
import { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js";
-import { TermsOfService } from "../../components/TermsOfService/index.js";
import { State } from "./index.js";
-import { ExchangeTosStatus } from "@gnu-taler/taler-util";
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
const { i18n } = useTranslationContext();
@@ -68,12 +65,7 @@ export function SuccessView(state: State.Success): VNode {
const currentTosVersionIsAccepted =
state.currentExchange.tosStatus === ExchangeTosStatus.Accepted;
return (
- <WalletAction>
- <LogoHeader />
- <SubTitle>
- <i18n.Translate>Digital cash withdrawal</i18n.Translate>
- </SubTitle>
-
+ <Fragment>
{state.doWithdrawal.error && (
<ErrorTalerOperation
title={
@@ -161,7 +153,7 @@ export function SuccessView(state: State.Success): VNode {
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
- </WalletAction>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/popup/Application.tsx b/packages/taler-wallet-webextension/src/popup/Application.tsx
index 8186c6790..9cae0d048 100644
--- a/packages/taler-wallet-webextension/src/popup/Application.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Application.tsx
@@ -21,7 +21,7 @@
*/
import { createHashHistory } from "history";
-import { Fragment, h, VNode } from "preact";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
import Router, { route, Route } from "preact-router";
import { Match } from "preact-router/match";
import { useEffect, useState } from "preact/hooks";
@@ -34,15 +34,28 @@ import {
useTranslationContext,
} from "../context/translation.js";
import { useTalerActionURL } from "../hooks/useTalerActionURL.js";
-import { Pages, PopupNavBar } from "../NavigationBar.js";
+import { PopupNavBarOptions, Pages, PopupNavBar } from "../NavigationBar.js";
import { platform } from "../platform/api.js";
import { BackupPage } from "../wallet/BackupPage.js";
import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js";
import { BalancePage } from "./BalancePage.js";
import { TalerActionFound } from "./TalerActionFound.js";
-function CheckTalerActionComponent(): VNode {
- const [action] = useTalerActionURL();
+export function Application(): VNode {
+ return (
+ <TranslationProvider>
+ <DevContextProvider>
+ <IoCProviderForRuntime>
+ <ApplicationView />
+ </IoCProviderForRuntime>
+ </DevContextProvider>
+ </TranslationProvider>
+ );
+}
+function ApplicationView(): VNode {
+ const hash_history = createHashHistory();
+
+ const [action, setDismissed] = useTalerActionURL();
const actionUri = action?.uri;
@@ -52,116 +65,110 @@ function CheckTalerActionComponent(): VNode {
}
}, [actionUri]);
- return <Fragment />;
-}
+ async function redirectToTxInfo(tid: string): Promise<void> {
+ redirectTo(Pages.balanceTransaction({ tid }));
+ }
-export function Application(): VNode {
- const hash_history = createHashHistory();
return (
- <TranslationProvider>
- <DevContextProvider>
- {({ devMode }: { devMode: boolean }) => (
- <IoCProviderForRuntime>
- <PendingTransactions
- goToTransaction={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
+ <Router history={hash_history}>
+ <Route
+ path={Pages.balance}
+ component={() => (
+ <PopupTemplate path="balance" goToTransaction={redirectToTxInfo}>
+ <BalancePage
+ goToWalletManualWithdraw={() => redirectTo(Pages.receiveCash({}))}
+ goToWalletDeposit={(currency: string) =>
+ redirectTo(Pages.sendCash({ amount: `${currency}:0` }))
+ }
+ goToWalletHistory={(currency: string) =>
+ redirectTo(Pages.balanceHistory({ currency }))
}
/>
- <Match>
- {({ path }: { path: string }) => <PopupNavBar path={path} />}
- </Match>
- <CheckTalerActionComponent />
- <PopupBox devMode={devMode}>
- <Router history={hash_history}>
- <Route
- path={Pages.balance}
- component={BalancePage}
- goToWalletManualWithdraw={() =>
- redirectTo(Pages.receiveCash({}))
- }
- goToWalletDeposit={(currency: string) =>
- redirectTo(Pages.sendCash({ amount: `${currency}:0` }))
- }
- goToWalletHistory={(currency: string) =>
- redirectTo(Pages.balanceHistory({ currency }))
- }
- />
-
- <Route
- path={Pages.cta.pattern}
- component={function Action({ action }: { action: string }) {
- const [, setDismissed] = useTalerActionURL();
-
- return (
- <TalerActionFound
- url={decodeURIComponent(action)}
- onDismiss={() => {
- setDismissed(true);
- return redirectTo(Pages.balance);
- }}
- />
- );
- }}
- />
-
- <Route
- path={Pages.backup}
- component={BackupPage}
- onAddProvider={() => redirectTo(Pages.backupProviderAdd)}
- />
- <Route
- path={Pages.backupProviderDetail.pattern}
- component={ProviderDetailPage}
- onBack={() => redirectTo(Pages.backup)}
- />
-
- <Route
- path={Pages.balanceTransaction.pattern}
- component={RedirectToWalletPage}
- />
- <Route
- path={Pages.ctaWithdrawManual.pattern}
- component={RedirectToWalletPage}
- />
- <Route
- path={Pages.balanceDeposit.pattern}
- component={RedirectToWalletPage}
- />
- <Route
- path={Pages.balanceHistory.pattern}
- component={RedirectToWalletPage}
- />
- <Route
- path={Pages.backupProviderAdd}
- component={RedirectToWalletPage}
- />
- <Route
- path={Pages.receiveCash.pattern}
- component={RedirectToWalletPage}
- />
- <Route
- path={Pages.sendCash.pattern}
- component={RedirectToWalletPage}
- />
- <Route path={Pages.qr} component={RedirectToWalletPage} />
- <Route path={Pages.settings} component={RedirectToWalletPage} />
- <Route
- path={Pages.settingsExchangeAdd.pattern}
- component={RedirectToWalletPage}
- />
- <Route path={Pages.dev} component={RedirectToWalletPage} />
- <Route
- path={Pages.notifications}
- component={RedirectToWalletPage}
- />
-
- <Route default component={Redirect} to={Pages.balance} />
- </Router>
- </PopupBox>
- </IoCProviderForRuntime>
+ </PopupTemplate>
)}
- </DevContextProvider>
- </TranslationProvider>
+ />
+
+ <Route
+ path={Pages.cta.pattern}
+ component={function Action({ action }: { action: string }) {
+ // const [, setDismissed] = useTalerActionURL();
+
+ return (
+ <PopupTemplate>
+ <TalerActionFound
+ url={decodeURIComponent(action)}
+ onDismiss={() => {
+ setDismissed(true);
+ return redirectTo(Pages.balance);
+ }}
+ />
+ </PopupTemplate>
+ );
+ }}
+ />
+
+ <Route
+ path={Pages.backup}
+ component={() => (
+ <PopupTemplate path="backup" goToTransaction={redirectToTxInfo}>
+ <BackupPage
+ onAddProvider={() => redirectTo(Pages.backupProviderAdd)}
+ />
+ </PopupTemplate>
+ )}
+ />
+ <Route
+ path={Pages.backupProviderDetail.pattern}
+ component={({ pid }: { pid: string }) => (
+ <PopupTemplate path="backup">
+ <ProviderDetailPage
+ onPayProvider={(uri: string) =>
+ redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
+ }
+ onWithdraw={(amount: string) =>
+ redirectTo(Pages.receiveCash({ amount }))
+ }
+ pid={pid}
+ onBack={() => redirectTo(Pages.backup)}
+ />
+ </PopupTemplate>
+ )}
+ />
+
+ <Route
+ path={Pages.balanceTransaction.pattern}
+ component={RedirectToWalletPage}
+ />
+ <Route
+ path={Pages.ctaWithdrawManual.pattern}
+ component={RedirectToWalletPage}
+ />
+ <Route
+ path={Pages.balanceDeposit.pattern}
+ component={RedirectToWalletPage}
+ />
+ <Route
+ path={Pages.balanceHistory.pattern}
+ component={RedirectToWalletPage}
+ />
+ <Route path={Pages.backupProviderAdd} component={RedirectToWalletPage} />
+ <Route
+ path={Pages.receiveCash.pattern}
+ component={RedirectToWalletPage}
+ />
+ <Route path={Pages.sendCash.pattern} component={RedirectToWalletPage} />
+ <Route path={Pages.ctaPay} component={RedirectToWalletPage} />
+ <Route path={Pages.qr} component={RedirectToWalletPage} />
+ <Route path={Pages.settings} component={RedirectToWalletPage} />
+ <Route
+ path={Pages.settingsExchangeAdd.pattern}
+ component={RedirectToWalletPage}
+ />
+ <Route path={Pages.dev} component={RedirectToWalletPage} />
+ <Route path={Pages.notifications} component={RedirectToWalletPage} />
+
+ <Route default component={Redirect} to={Pages.balance} />
+ </Router>
);
}
@@ -195,3 +202,24 @@ function Redirect({ to }: { to: string }): null {
});
return null;
}
+
+function PopupTemplate({
+ path,
+ children,
+ goToTransaction,
+}: {
+ path?: PopupNavBarOptions;
+ children: ComponentChildren;
+ goToTransaction?: (id: string) => Promise<void>;
+}): VNode {
+ return (
+ <Fragment>
+ {/* <CheckTalerActionComponent /> */}
+ {goToTransaction ? (
+ <PendingTransactions goToTransaction={goToTransaction} />
+ ) : undefined}
+ <PopupNavBar path={path} />
+ <PopupBox>{children}</PopupBox>
+ </Fragment>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
index c9327b8e6..82d11a15a 100644
--- a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
+++ b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
@@ -69,7 +69,7 @@ export class ServiceWorkerHttpLib implements HttpRequestLibrary {
} else if (ArrayBuffer.isView(requestBody)) {
myBody = requestBody;
} else if (typeof requestBody === "object") {
- myBody = JSON.stringify(myBody);
+ myBody = JSON.stringify(requestBody);
} else {
throw Error("unsupported request body type");
}
@@ -127,8 +127,6 @@ export class ServiceWorkerHttpLib implements HttpRequestLibrary {
});
}
- // FIXME: "Content-Type: application/json" goes here,
- // after Sebastian suggestion.
postJson(
url: string,
body: any,
diff --git a/packages/taler-wallet-webextension/src/stories.tsx b/packages/taler-wallet-webextension/src/stories.tsx
index 8834b8084..a7b8a4d06 100644
--- a/packages/taler-wallet-webextension/src/stories.tsx
+++ b/packages/taler-wallet-webextension/src/stories.tsx
@@ -20,7 +20,11 @@
*/
import { Fragment, FunctionComponent, h } from "preact";
import { LogoHeader } from "./components/LogoHeader.js";
-import { PopupBox, WalletBox } from "./components/styled/index.js";
+import {
+ PopupBox,
+ WalletAction,
+ WalletBox,
+} from "./components/styled/index.js";
import { strings } from "./i18n/strings.js";
import { PopupNavBar, WalletNavBar } from "./NavigationBar.js";
@@ -72,7 +76,7 @@ function getWrapperForGroup(group: string): FunctionComponent {
return function WalletWrapper({ children }: any) {
return (
<Fragment>
- <WalletBox>{children}</WalletBox>
+ <WalletAction>{children}</WalletAction>
</Fragment>
);
};
diff --git a/packages/taler-wallet-webextension/src/utils/index.ts b/packages/taler-wallet-webextension/src/utils/index.ts
index c2d7c10a8..ad4eabf15 100644
--- a/packages/taler-wallet-webextension/src/utils/index.ts
+++ b/packages/taler-wallet-webextension/src/utils/index.ts
@@ -74,7 +74,7 @@ export async function queryToSlashKeys<T>(url: string): Promise<T> {
return timeout(3000, query);
}
-export type StateFunc<S> = (p: S) => VNode;
+export type StateFunc<S> = (p: S) => VNode | null;
export type StateViewMap<StateType extends { status: string }> = {
[S in StateType as S["status"]]: StateFunc<S>;
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts
index 94020069b..10fcd84ce 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts
+++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts
@@ -32,7 +32,6 @@ import {
} from "./views.js";
export interface Props {
- currency: string;
onBack: () => Promise<void>;
onComplete: (pid: string) => Promise<void>;
onPaymentRequired: (uri: string) => Promise<void>;
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts
index 32c48be91..1b30ed0cd 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts
@@ -144,7 +144,6 @@ function useUrlState<T>(
}
export function useComponentState({
- currency,
onBack,
onComplete,
onPaymentRequired,
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts
index 9abb672fa..3241a3ab0 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts
+++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts
@@ -26,7 +26,6 @@ import { Props } from "./index.js";
import { useComponentState } from "./state.js";
const props: Props = {
- currency: "KUDOS",
onBack: nullFunction,
onComplete: nullFunction,
onPaymentRequired: nullFunction,
diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx
index d150ebfaf..8b77e152c 100644
--- a/packages/taler-wallet-webextension/src/wallet/Application.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx
@@ -20,352 +20,452 @@
* @author sebasjm
*/
+import { TranslatedString } from "@gnu-taler/taler-util";
import { createHashHistory } from "history";
-import { Fragment, h, VNode } from "preact";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
import Router, { route, Route } from "preact-router";
-import Match from "preact-router/match";
-import { useEffect, useState } from "preact/hooks";
+import { useEffect } from "preact/hooks";
import { LogoHeader } from "../components/LogoHeader.js";
import PendingTransactions from "../components/PendingTransactions.js";
-import { SuccessBox, WalletBox } from "../components/styled/index.js";
+import {
+ SubTitle,
+ WalletAction,
+ WalletBox,
+} from "../components/styled/index.js";
import { DevContextProvider } from "../context/devContext.js";
import { IoCProviderForRuntime } from "../context/iocContext.js";
import {
TranslationProvider,
useTranslationContext,
} from "../context/translation.js";
+import { DepositPage as DepositPageCTA } from "../cta/Deposit/index.js";
+import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js";
+import { InvoicePayPage } from "../cta/InvoicePay/index.js";
import { PaymentPage } from "../cta/Payment/index.js";
+import { RecoveryPage } from "../cta/Recovery/index.js";
import { RefundPage } from "../cta/Refund/index.js";
import { TipPage } from "../cta/Tip/index.js";
+import { TransferCreatePage } from "../cta/TransferCreate/index.js";
+import { TransferPickupPage } from "../cta/TransferPickup/index.js";
import {
WithdrawPageFromParams,
WithdrawPageFromURI,
} from "../cta/Withdraw/index.js";
-import { DepositPage as DepositPageCTA } from "../cta/Deposit/index.js";
-import { Pages, WalletNavBar } from "../NavigationBar.js";
-import { DeveloperPage } from "./DeveloperPage.js";
+import { WalletNavBarOptions, Pages, WalletNavBar } from "../NavigationBar.js";
+import { platform } from "../platform/api.js";
+import { AddBackupProviderPage } from "./AddBackupProvider/index.js";
import { BackupPage } from "./BackupPage.js";
import { DepositPage } from "./DepositPage/index.js";
+import { DestinationSelectionPage } from "./DestinationSelection/index.js";
+import { DeveloperPage } from "./DeveloperPage.js";
import { ExchangeAddPage } from "./ExchangeAddPage.js";
import { HistoryPage } from "./History.js";
+import { NotificationsPage } from "./Notifications/index.js";
import { ProviderDetailPage } from "./ProviderDetailPage.js";
+import { QrReaderPage } from "./QrReader.js";
import { SettingsPage } from "./Settings.js";
import { TransactionPage } from "./Transaction.js";
import { WelcomePage } from "./Welcome.js";
-import { QrReaderPage } from "./QrReader.js";
-import { platform } from "../platform/api.js";
-import { DestinationSelectionPage } from "./DestinationSelection/index.js";
-import { ExchangeSelectionPage } from "./ExchangeSelection/index.js";
-import { TransferCreatePage } from "../cta/TransferCreate/index.js";
-import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js";
-import { TransferPickupPage } from "../cta/TransferPickup/index.js";
-import { InvoicePayPage } from "../cta/InvoicePay/index.js";
-import { RecoveryPage } from "../cta/Recovery/index.js";
-import { AddBackupProviderPage } from "./AddBackupProvider/index.js";
-import { NotificationsPage } from "./Notifications/index.js";
export function Application(): VNode {
- const [globalNotification, setGlobalNotification] = useState<
- VNode | undefined
- >(undefined);
- const hash_history = createHashHistory();
- function clearNotification(): void {
- setGlobalNotification(undefined);
- }
- function clearNotificationWhenMovingOut(): void {
- // const movingOutFromNotification =
- // globalNotification && e.url !== globalNotification.to;
- if (globalNotification) {
- //&& movingOutFromNotification) {
- setGlobalNotification(undefined);
- }
- }
const { i18n } = useTranslationContext();
+ const hash_history = createHashHistory();
+ async function redirectToTxInfo(tid: string): Promise<void> {
+ redirectTo(Pages.balanceTransaction({ tid }));
+ }
return (
<TranslationProvider>
<DevContextProvider>
<IoCProviderForRuntime>
- {/* <Match/> won't work in the first render if <Router /> is not called first */}
- {/* https://github.com/preactjs/preact-router/issues/415 */}
<Router history={hash_history}>
- <Match default>
- {({ path }: { path: string }) => {
- if (path && path.startsWith("/cta")) return;
- return (
- <Fragment>
- <LogoHeader />
- <WalletNavBar path={path} />
- {shouldShowPendingOperations(path) && (
- <div
- style={{
- backgroundColor: "lightcyan",
- display: "flex",
- justifyContent: "center",
- }}
- >
- <PendingTransactions
- goToTransaction={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- </div>
- )}
- </Fragment>
- );
- }}
- </Match>
- </Router>
- <WalletBox>
- {globalNotification && (
- <SuccessBox onClick={clearNotification}>
- <div>{globalNotification}</div>
- </SuccessBox>
- )}
- <Router
- history={hash_history}
- onChange={clearNotificationWhenMovingOut}
- >
- <Route path={Pages.welcome} component={WelcomePage} />
-
- {/**
- * BALANCE
- */}
+ <Route
+ path={Pages.welcome}
+ component={() => (
+ <WalletTemplate>
+ <WelcomePage />
+ </WalletTemplate>
+ )}
+ />
- <Route
- path={Pages.balanceHistory.pattern}
- component={HistoryPage}
- goToWalletDeposit={(currency: string) =>
- redirectTo(Pages.sendCash({ amount: `${currency}:0` }))
- }
- goToWalletManualWithdraw={(currency?: string) =>
- redirectTo(
- Pages.receiveCash({
- amount: !currency ? undefined : `${currency}:0`,
- }),
- )
- }
- />
- <Route path={Pages.exchanges} component={ExchangeSelectionPage} />
- <Route
- path={Pages.sendCash.pattern}
- type="send"
- component={DestinationSelectionPage}
- goToWalletBankDeposit={(amount: string) =>
- redirectTo(Pages.balanceDeposit({ amount }))
- }
- goToWalletWalletSend={(amount: string) =>
- redirectTo(Pages.ctaTransferCreate({ amount }))
- }
- />
- <Route
- path={Pages.receiveCash.pattern}
- type="get"
- component={DestinationSelectionPage}
- goToWalletManualWithdraw={(amount?: string) =>
- redirectTo(Pages.ctaWithdrawManual({ amount }))
- }
- goToWalletWalletInvoice={(amount?: string) =>
- redirectTo(Pages.ctaInvoiceCreate({ amount }))
- }
- />
+ <Route
+ path={Pages.qr}
+ component={() => (
+ <WalletTemplate goToTransaction={redirectToTxInfo}>
+ <QrReaderPage
+ onDetected={(talerActionUrl: string) => {
+ platform.openWalletURIFromPopup(talerActionUrl);
+ }}
+ />
+ </WalletTemplate>
+ )}
+ />
- <Route
- path={Pages.balanceTransaction.pattern}
- component={TransactionPage}
- goToWalletHistory={(currency?: string) =>
- redirectTo(Pages.balanceHistory({ currency }))
- }
- />
+ <Route
+ path={Pages.settings}
+ component={() => (
+ <WalletTemplate goToTransaction={redirectToTxInfo}>
+ <SettingsPage />
+ </WalletTemplate>
+ )}
+ />
+ <Route
+ path={Pages.notifications}
+ component={() => (
+ <WalletTemplate>
+ <NotificationsPage />
+ </WalletTemplate>
+ )}
+ />
+ {/**
+ * SETTINGS
+ */}
+ <Route
+ path={Pages.settingsExchangeAdd.pattern}
+ component={() => (
+ <WalletTemplate>
+ <ExchangeAddPage onBack={() => redirectTo(Pages.balance)} />
+ </WalletTemplate>
+ )}
+ />
- <Route
- path={Pages.balanceDeposit.pattern}
- component={DepositPage}
- onCancel={(currency: string) => {
- redirectTo(Pages.balanceHistory({ currency }));
- }}
- onSuccess={(currency: string) => {
- redirectTo(Pages.balanceHistory({ currency }));
- setGlobalNotification(
- <i18n.Translate>
- All done, your transaction is in progress
- </i18n.Translate>,
- );
- }}
- />
- {/**
- * PENDING
- */}
- <Route
- path={Pages.qr}
- component={QrReaderPage}
- onDetected={(talerActionUrl: string) => {
- platform.openWalletURIFromPopup(talerActionUrl);
- }}
- />
+ <Route
+ path={Pages.balanceHistory.pattern}
+ component={() => (
+ <WalletTemplate
+ path="balance"
+ goToTransaction={redirectToTxInfo}
+ >
+ <HistoryPage
+ goToWalletDeposit={(currency: string) =>
+ redirectTo(Pages.sendCash({ amount: `${currency}:0` }))
+ }
+ goToWalletManualWithdraw={(currency?: string) =>
+ redirectTo(
+ Pages.receiveCash({
+ amount: !currency ? undefined : `${currency}:0`,
+ }),
+ )
+ }
+ />
+ </WalletTemplate>
+ )}
+ />
+ <Route
+ path={Pages.sendCash.pattern}
+ component={({ amount }: { amount?: string }) => (
+ <WalletTemplate path="balance">
+ <DestinationSelectionPage
+ type="send"
+ amount={amount}
+ goToWalletBankDeposit={(amount: string) =>
+ redirectTo(Pages.balanceDeposit({ amount }))
+ }
+ goToWalletWalletSend={(amount: string) =>
+ redirectTo(Pages.ctaTransferCreate({ amount }))
+ }
+ />
+ </WalletTemplate>
+ )}
+ />
+ <Route
+ path={Pages.receiveCash.pattern}
+ component={({ amount }: { amount?: string }) => (
+ <WalletTemplate path="balance">
+ <DestinationSelectionPage
+ type="get"
+ amount={amount}
+ goToWalletManualWithdraw={(amount?: string) =>
+ redirectTo(Pages.ctaWithdrawManual({ amount }))
+ }
+ goToWalletWalletInvoice={(amount?: string) =>
+ redirectTo(Pages.ctaInvoiceCreate({ amount }))
+ }
+ />
+ </WalletTemplate>
+ )}
+ />
- <Route path={Pages.settings} component={SettingsPage} />
- <Route path={Pages.notifications} component={NotificationsPage} />
+ <Route
+ path={Pages.balanceTransaction.pattern}
+ component={({ tid }: { tid: string }) => (
+ <WalletTemplate path="balance">
+ <TransactionPage
+ tid={tid}
+ goToWalletHistory={(currency?: string) =>
+ redirectTo(Pages.balanceHistory({ currency }))
+ }
+ />
+ </WalletTemplate>
+ )}
+ />
- {/**
- * BACKUP
- */}
- <Route
- path={Pages.backup}
- component={BackupPage}
- onAddProvider={() => redirectTo(Pages.backupProviderAdd)}
- />
- <Route
- path={Pages.backupProviderDetail.pattern}
- component={ProviderDetailPage}
- onPayProvider={(uri: string) =>
- redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
- }
- onWithdraw={(amount: string) =>
- redirectTo(Pages.receiveCash({ amount }))
- }
- onBack={() => redirectTo(Pages.backup)}
- />
- <Route
- path={Pages.backupProviderAdd}
- component={AddBackupProviderPage}
- onPaymentRequired={(uri: string) =>
- redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
- }
- onComplete={(pid: string) =>
- redirectTo(Pages.backupProviderDetail({ pid }))
- }
- onBack={() => redirectTo(Pages.backup)}
- />
+ <Route
+ path={Pages.balanceDeposit.pattern}
+ component={() => (
+ <WalletTemplate path="balance">
+ <DepositPage
+ onCancel={(currency: string) => {
+ redirectTo(Pages.balanceHistory({ currency }));
+ }}
+ onSuccess={(currency: string) => {
+ redirectTo(Pages.balanceHistory({ currency }));
+ }}
+ />
+ </WalletTemplate>
+ )}
+ />
- {/**
- * SETTINGS
- */}
- <Route
- path={Pages.settingsExchangeAdd.pattern}
- component={ExchangeAddPage}
- onBack={() => redirectTo(Pages.balance)}
- />
+ <Route
+ path={Pages.backup}
+ component={() => (
+ <WalletTemplate
+ path="backup"
+ goToTransaction={redirectToTxInfo}
+ >
+ <BackupPage
+ onAddProvider={() => redirectTo(Pages.backupProviderAdd)}
+ />
+ </WalletTemplate>
+ )}
+ />
+ <Route
+ path={Pages.backupProviderDetail.pattern}
+ component={({ pid }: { pid: string }) => (
+ <WalletTemplate>
+ <ProviderDetailPage
+ pid={pid}
+ onPayProvider={(uri: string) =>
+ redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
+ }
+ onWithdraw={(amount: string) =>
+ redirectTo(Pages.receiveCash({ amount }))
+ }
+ onBack={() => redirectTo(Pages.backup)}
+ />
+ </WalletTemplate>
+ )}
+ />
+ <Route
+ path={Pages.backupProviderAdd}
+ component={() => (
+ <WalletTemplate>
+ <AddBackupProviderPage
+ onPaymentRequired={(uri: string) =>
+ redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
+ }
+ onComplete={(pid: string) =>
+ redirectTo(Pages.backupProviderDetail({ pid }))
+ }
+ onBack={() => redirectTo(Pages.backup)}
+ />
+ </WalletTemplate>
+ )}
+ />
- {/**
- * DEV
- */}
+ {/**
+ * DEV
+ */}
+ <Route
+ path={Pages.dev}
+ component={() => (
+ <WalletTemplate path="dev" goToTransaction={redirectToTxInfo}>
+ <DeveloperPage />
+ </WalletTemplate>
+ )}
+ />
- <Route path={Pages.dev} component={DeveloperPage} />
+ {/**
+ * CALL TO ACTION
+ */}
+ <Route
+ path={Pages.ctaPay}
+ component={({ talerPayUri }: { talerPayUri: string }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash payment`}>
+ <PaymentPage
+ talerPayUri={talerPayUri}
+ goToWalletManualWithdraw={(amount?: string) =>
+ redirectTo(Pages.receiveCash({ amount }))
+ }
+ cancel={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaRefund}
+ component={({ talerRefundUri }: { talerRefundUri: string }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash refund`}>
+ <RefundPage
+ talerRefundUri={talerRefundUri}
+ cancel={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaTips}
+ component={({ talerTipUri }: { talerTipUri: string }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash tip`}>
+ <TipPage
+ talerTipUri={talerTipUri}
+ onCancel={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaWithdraw}
+ component={({
+ talerWithdrawUri,
+ }: {
+ talerWithdrawUri: string;
+ }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash withdrawal`}>
+ <WithdrawPageFromURI
+ talerWithdrawUri={talerWithdrawUri}
+ cancel={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaWithdrawManual.pattern}
+ component={({ amount }: { amount: string }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash withdrawal`}>
+ <WithdrawPageFromParams
+ amount={amount}
+ cancel={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaDeposit}
+ component={({
+ amountStr,
+ talerDepositUri,
+ }: {
+ amountStr: string;
+ talerDepositUri: string;
+ }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash deposit`}>
+ <DepositPageCTA
+ amountStr={amountStr}
+ talerDepositUri={talerDepositUri}
+ cancel={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaInvoiceCreate.pattern}
+ component={({ amount }: { amount: string }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash invoice`}>
+ <InvoiceCreatePage
+ amount={amount}
+ onClose={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaTransferCreate.pattern}
+ component={({ amount }: { amount: string }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash transfer`}>
+ <TransferCreatePage
+ amount={amount}
+ onClose={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaInvoicePay}
+ component={({ talerPayPullUri }: { talerPayPullUri: string }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash invoice`}>
+ <InvoicePayPage
+ talerPayPullUri={talerPayPullUri}
+ goToWalletManualWithdraw={(amount?: string) =>
+ redirectTo(Pages.receiveCash({ amount }))
+ }
+ onClose={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaTransferPickup}
+ component={({ talerPayPushUri }: { talerPayPushUri: string }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash transfer`}>
+ <TransferPickupPage
+ talerPayPushUri={talerPayPushUri}
+ onClose={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ )}
+ />
+ <Route
+ path={Pages.ctaRecovery}
+ component={({
+ talerRecoveryUri,
+ }: {
+ talerRecoveryUri: string;
+ }) => (
+ <CallToActionTemplate title={i18n.str`Digital cash recovery`}>
+ <RecoveryPage
+ talerRecoveryUri={talerRecoveryUri}
+ onCancel={() => redirectTo(Pages.balance)}
+ onSuccess={() => redirectTo(Pages.backup)}
+ />
+ </CallToActionTemplate>
+ )}
+ />
- {/**
- * CALL TO ACTION
- */}
- <Route
- path={Pages.ctaPay}
- component={PaymentPage}
- goToWalletManualWithdraw={(amount?: string) =>
- redirectTo(Pages.receiveCash({ amount }))
- }
- cancel={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaRefund}
- component={RefundPage}
- cancel={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaTips}
- component={TipPage}
- onCancel={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaWithdraw}
- component={WithdrawPageFromURI}
- cancel={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaWithdrawManual.pattern}
- component={WithdrawPageFromParams}
- cancel={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaDeposit}
- component={DepositPageCTA}
- cancel={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaInvoiceCreate.pattern}
- component={InvoiceCreatePage}
- onClose={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaTransferCreate.pattern}
- component={TransferCreatePage}
- onClose={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaInvoicePay}
- component={InvoicePayPage}
- goToWalletManualWithdraw={(amount?: string) =>
- redirectTo(Pages.receiveCash({ amount }))
- }
- onClose={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaTransferPickup}
- component={TransferPickupPage}
- onClose={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- <Route
- path={Pages.ctaRecovery}
- component={RecoveryPage}
- onCancel={() => redirectTo(Pages.balance)}
- onSuccess={() => redirectTo(Pages.backup)}
- />
+ {/**
+ * NOT FOUND
+ * all redirects should be at the end
+ */}
+ <Route
+ path={Pages.balance}
+ component={() => <Redirect to={Pages.balanceHistory({})} />}
+ />
- {/**
- * NOT FOUND
- * all redirects should be at the end
- */}
- <Route
- path={Pages.balance}
- component={Redirect}
- to={Pages.balanceHistory({})}
- />
-
- <Route
- default
- component={Redirect}
- to={Pages.balanceHistory({})}
- />
- </Router>
- </WalletBox>
+ <Route
+ default
+ component={() => <Redirect to={Pages.balanceHistory({})} />}
+ />
+ </Router>
</IoCProviderForRuntime>
</DevContextProvider>
</TranslationProvider>
@@ -403,3 +503,40 @@ function shouldShowPendingOperations(url: string): boolean {
Pages.backup,
].some((p) => matchesRoute(url, p));
}
+
+function CallToActionTemplate({
+ title,
+ children,
+}: {
+ title: TranslatedString;
+ children: ComponentChildren;
+}): VNode {
+ return (
+ <WalletAction>
+ <LogoHeader />
+ <SubTitle>{title}</SubTitle>
+ {children}
+ </WalletAction>
+ );
+}
+
+function WalletTemplate({
+ path,
+ children,
+ goToTransaction,
+}: {
+ path?: WalletNavBarOptions;
+ children: ComponentChildren;
+ goToTransaction?: (id: string) => Promise<void>;
+}): VNode {
+ return (
+ <Fragment>
+ <LogoHeader />
+ <WalletNavBar path={path} />
+ {goToTransaction ? (
+ <PendingTransactions goToTransaction={goToTransaction} />
+ ) : undefined}
+ <WalletBox>{children}</WalletBox>
+ </Fragment>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx
index 4805c03ca..74e7ce611 100644
--- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx
@@ -92,6 +92,7 @@ type CoinsInfo = CoinDumpJson["coins"];
type CalculatedCoinfInfo = {
ageKeysCount: number | undefined;
denom_value: number;
+ denom_fraction: number;
//remain_value: number;
status: string;
from_refresh: boolean;
@@ -151,7 +152,8 @@ export function View({
}
prev[cur.exchange_base_url].push({
ageKeysCount: cur.ageCommitmentProof?.proof.privateKeys.length,
- denom_value: parseFloat(Amounts.stringifyValue(denom)),
+ denom_value: denom.value,
+ denom_fraction: denom.fraction,
// remain_value: parseFloat(
// Amounts.stringifyValue(Amounts.parseOrThrow(cur.remaining_value)),
// ),
@@ -340,7 +342,10 @@ export function View({
{Object.keys(money_by_exchange).map((ex, idx) => {
const allcoins = money_by_exchange[ex];
allcoins.sort((a, b) => {
- return b.denom_value - a.denom_value;
+ if (b.denom_value !== a.denom_value) {
+ return b.denom_value - a.denom_value;
+ }
+ return b.denom_fraction - a.denom_fraction;
});
const coins = allcoins.reduce(
@@ -407,11 +412,31 @@ function ShowAllCoins({
const { i18n } = useTranslationContext();
const [collapsedSpent, setCollapsedSpent] = useState(true);
const [collapsedUnspent, setCollapsedUnspent] = useState(false);
- const total = coins.usable.reduce((prev, cur) => prev + cur.denom_value, 0);
+ const totalUsable = coins.usable.reduce(
+ (prev, cur) =>
+ Amounts.add(prev, {
+ currency: "NONE",
+ fraction: cur.denom_fraction,
+ value: cur.denom_value,
+ }).amount,
+ Amounts.zeroOfCurrency("NONE"),
+ );
+ const totalSpent = coins.spent.reduce(
+ (prev, cur) =>
+ Amounts.add(prev, {
+ currency: "NONE",
+ fraction: cur.denom_fraction,
+ value: cur.denom_value,
+ }).amount,
+ Amounts.zeroOfCurrency("NONE"),
+ );
return (
<Fragment>
<p>
- <b>{ex}</b>: {total} {currencies[ex]}
+ <b>{ex}</b>: {Amounts.stringifyValue(totalUsable)} {currencies[ex]}
+ </p>
+ <p>
+ spent: {Amounts.stringifyValue(totalSpent)} {currencies[ex]}
</p>
<p onClick={() => setCollapsedUnspent(true)}>
<b>