fix #7522
This commit is contained in:
parent
7d02e42123
commit
24cac493dd
@ -45,6 +45,7 @@ import warningIcon from "./svg/warning_24px.svg";
|
|||||||
* @author sebasjm
|
* @author sebasjm
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
type PageLocation<DynamicPart extends object> = {
|
type PageLocation<DynamicPart extends object> = {
|
||||||
pattern: string;
|
pattern: string;
|
||||||
(params: DynamicPart): string;
|
(params: DynamicPart): string;
|
||||||
@ -62,6 +63,7 @@ function replaceAll(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
function pageDefinition<T extends object>(pattern: string): PageLocation<T> {
|
function pageDefinition<T extends object>(pattern: string): PageLocation<T> {
|
||||||
const patternParams = pattern.match(/(:[\w?]*)/g);
|
const patternParams = pattern.match(/(:[\w?]*)/g);
|
||||||
if (!patternParams)
|
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 api = useBackendContext();
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
return await api.wallet.call(
|
return await api.wallet.call(
|
||||||
@ -146,13 +149,10 @@ export function PopupNavBar({ path = "" }: { path?: string }): VNode {
|
|||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<NavigationHeader>
|
<NavigationHeader>
|
||||||
<a
|
<a href={Pages.balance} class={path === "balance" ? "active" : ""}>
|
||||||
href={Pages.balance}
|
|
||||||
class={path.startsWith("/balance") ? "active" : ""}
|
|
||||||
>
|
|
||||||
<i18n.Translate>Balance</i18n.Translate>
|
<i18n.Translate>Balance</i18n.Translate>
|
||||||
</a>
|
</a>
|
||||||
<a href={Pages.backup} class={path.startsWith("/backup") ? "active" : ""}>
|
<a href={Pages.backup} class={path === "backup" ? "active" : ""}>
|
||||||
<i18n.Translate>Backup</i18n.Translate>
|
<i18n.Translate>Backup</i18n.Translate>
|
||||||
</a>
|
</a>
|
||||||
<div style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}>
|
<div style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}>
|
||||||
@ -185,8 +185,8 @@ export function PopupNavBar({ path = "" }: { path?: string }): VNode {
|
|||||||
</NavigationHeader>
|
</NavigationHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export type WalletNavBarOptions = "balance" | "backup" | "dev";
|
||||||
export function WalletNavBar({ path = "" }: { path?: string }): VNode {
|
export function WalletNavBar({ path }: { path?: WalletNavBarOptions }): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const api = useBackendContext();
|
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 (
|
return (
|
||||||
<NavigationHeaderHolder>
|
<NavigationHeaderHolder>
|
||||||
<NavigationHeader>
|
<NavigationHeader>
|
||||||
<a
|
<a href={Pages.balance} class={path === "balance" ? "active" : ""}>
|
||||||
href={Pages.balance}
|
|
||||||
class={path.startsWith("/balance") ? "active" : ""}
|
|
||||||
>
|
|
||||||
<i18n.Translate>Balance</i18n.Translate>
|
<i18n.Translate>Balance</i18n.Translate>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a href={Pages.backup} class={path === "backup" ? "active" : ""}>
|
||||||
href={Pages.backup}
|
|
||||||
class={path.startsWith("/backup") ? "active" : ""}
|
|
||||||
>
|
|
||||||
<i18n.Translate>Backup</i18n.Translate>
|
<i18n.Translate>Backup</i18n.Translate>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@ -223,7 +218,7 @@ export function WalletNavBar({ path = "" }: { path?: string }): VNode {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<JustInDevMode>
|
<JustInDevMode>
|
||||||
<a href={Pages.dev} class={path.startsWith("/dev") ? "active" : ""}>
|
<a href={Pages.dev} class={path === "dev" ? "active" : ""}>
|
||||||
<i18n.Translate>Dev</i18n.Translate>
|
<i18n.Translate>Dev</i18n.Translate>
|
||||||
</a>
|
</a>
|
||||||
</JustInDevMode>
|
</JustInDevMode>
|
||||||
|
@ -65,23 +65,25 @@ export const BasicExample = (): VNode => (
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<Banner
|
<Banner
|
||||||
elements={[
|
// elements={[
|
||||||
{
|
// {
|
||||||
icon: <SignalWifiOffIcon color="gray" />,
|
// icon: <SignalWifiOffIcon color="gray" />,
|
||||||
description: (
|
// description: (
|
||||||
<Typography>
|
// <Typography>
|
||||||
You have lost connection to the internet. This app is offline.
|
// You have lost connection to the internet. This app is offline.
|
||||||
</Typography>
|
// </Typography>
|
||||||
),
|
// ),
|
||||||
},
|
// },
|
||||||
]}
|
// ]}
|
||||||
confirm={{
|
confirm={{
|
||||||
label: "turn on wifi",
|
label: "turn on wifi",
|
||||||
action: async () => {
|
action: async () => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<div />
|
||||||
|
</Banner>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
@ -92,31 +94,33 @@ export const PendingOperation = (): VNode => (
|
|||||||
<Banner
|
<Banner
|
||||||
title="PENDING TRANSACTIONS"
|
title="PENDING TRANSACTIONS"
|
||||||
style={{ backgroundColor: "lightcyan", padding: 8 }}
|
style={{ backgroundColor: "lightcyan", padding: 8 }}
|
||||||
elements={[
|
// elements={[
|
||||||
{
|
// {
|
||||||
icon: (
|
// icon: (
|
||||||
<Avatar
|
// <Avatar
|
||||||
style={{
|
// style={{
|
||||||
border: "solid blue 1px",
|
// border: "solid blue 1px",
|
||||||
color: "blue",
|
// color: "blue",
|
||||||
boxSizing: "border-box",
|
// boxSizing: "border-box",
|
||||||
}}
|
// }}
|
||||||
>
|
// >
|
||||||
P
|
// P
|
||||||
</Avatar>
|
// </Avatar>
|
||||||
),
|
// ),
|
||||||
description: (
|
// description: (
|
||||||
<Fragment>
|
// <Fragment>
|
||||||
<Typography inline bold>
|
// <Typography inline bold>
|
||||||
EUR 37.95
|
// EUR 37.95
|
||||||
</Typography>
|
// </Typography>
|
||||||
|
//
|
||||||
<Typography inline>- 5 feb 2022</Typography>
|
// <Typography inline>- 5 feb 2022</Typography>
|
||||||
</Fragment>
|
// </Fragment>
|
||||||
),
|
// ),
|
||||||
},
|
// },
|
||||||
]}
|
// ]}
|
||||||
/>
|
>
|
||||||
|
asd
|
||||||
|
</Banner>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
@ -13,21 +13,20 @@
|
|||||||
You should have received a copy of the GNU General Public License along with
|
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/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
import { h, Fragment, VNode, JSX } from "preact";
|
import { ComponentChildren, Fragment, h, JSX, VNode } from "preact";
|
||||||
import { Divider } from "../mui/Divider.js";
|
|
||||||
import { Button } from "../mui/Button.js";
|
import { Button } from "../mui/Button.js";
|
||||||
import { Typography } from "../mui/Typography.js";
|
import { Divider } from "../mui/Divider.js";
|
||||||
import { Avatar } from "../mui/Avatar.js";
|
|
||||||
import { Grid } from "../mui/Grid.js";
|
import { Grid } from "../mui/Grid.js";
|
||||||
import { Paper } from "../mui/Paper.js";
|
import { Paper } from "../mui/Paper.js";
|
||||||
|
|
||||||
interface Props extends JSX.HTMLAttributes<HTMLDivElement> {
|
interface Props extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||||
titleHead?: VNode;
|
titleHead?: VNode;
|
||||||
elements: {
|
children: ComponentChildren;
|
||||||
icon?: VNode;
|
// elements: {
|
||||||
description: VNode;
|
// icon?: VNode;
|
||||||
action?: () => void;
|
// description: VNode;
|
||||||
}[];
|
// action?: () => void;
|
||||||
|
// }[];
|
||||||
confirm?: {
|
confirm?: {
|
||||||
label: string;
|
label: string;
|
||||||
action: () => Promise<void>;
|
action: () => Promise<void>;
|
||||||
@ -36,8 +35,9 @@ interface Props extends JSX.HTMLAttributes<HTMLDivElement> {
|
|||||||
|
|
||||||
export function Banner({
|
export function Banner({
|
||||||
titleHead,
|
titleHead,
|
||||||
elements,
|
children,
|
||||||
confirm,
|
confirm,
|
||||||
|
href,
|
||||||
...rest
|
...rest
|
||||||
}: Props): VNode {
|
}: Props): VNode {
|
||||||
return (
|
return (
|
||||||
@ -49,25 +49,7 @@ export function Banner({
|
|||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
<Grid container columns={1}>
|
<Grid container columns={1}>
|
||||||
{elements.map((e, i) => (
|
{children}
|
||||||
<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>
|
|
||||||
))}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
{confirm && (
|
{confirm && (
|
||||||
<Grid container justifyContent="flex-end" spacing={8}>
|
<Grid container justifyContent="flex-end" spacing={8}>
|
||||||
|
@ -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
|
||||||
|
{<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
|
||||||
|
<a href={uri}>
|
||||||
|
<i18n.Translate>click here</i18n.Translate>
|
||||||
|
</a>
|
||||||
|
</i18n.Translate>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
@ -26,6 +26,7 @@ import { useBackendContext } from "../context/backend.js";
|
|||||||
import { useTranslationContext } from "../context/translation.js";
|
import { useTranslationContext } from "../context/translation.js";
|
||||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
|
||||||
import { Avatar } from "../mui/Avatar.js";
|
import { Avatar } from "../mui/Avatar.js";
|
||||||
|
import { Grid } from "../mui/Grid.js";
|
||||||
import { Typography } from "../mui/Typography.js";
|
import { Typography } from "../mui/Typography.js";
|
||||||
import Banner from "./Banner.js";
|
import Banner from "./Banner.js";
|
||||||
import { Time } from "./Time.js";
|
import { Time } from "./Time.js";
|
||||||
@ -34,6 +35,11 @@ interface Props extends JSX.HTMLAttributes {
|
|||||||
goToTransaction: (id: string) => Promise<void>;
|
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 {
|
export function PendingTransactions({ goToTransaction }: Props): VNode {
|
||||||
const api = useBackendContext();
|
const api = useBackendContext();
|
||||||
const state = useAsyncAsHook(() =>
|
const state = useAsyncAsHook(() =>
|
||||||
@ -49,12 +55,13 @@ export function PendingTransactions({ goToTransaction }: Props): VNode {
|
|||||||
|
|
||||||
const transactions =
|
const transactions =
|
||||||
!state || state.hasError
|
!state || state.hasError
|
||||||
? []
|
? cache.tx
|
||||||
: state.response.transactions.filter((t) => t.pending);
|
: state.response.transactions.filter((t) => t.pending);
|
||||||
|
|
||||||
if (!state || state.hasError || !transactions.length) {
|
if (!transactions.length) {
|
||||||
return <Fragment />;
|
return <Fragment />;
|
||||||
}
|
}
|
||||||
|
cache.tx = transactions;
|
||||||
return (
|
return (
|
||||||
<PendingTransactionsView
|
<PendingTransactionsView
|
||||||
goToTransaction={goToTransaction}
|
goToTransaction={goToTransaction}
|
||||||
@ -72,46 +79,67 @@ export function PendingTransactionsView({
|
|||||||
}): VNode {
|
}): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<Banner
|
<div
|
||||||
titleHead={<i18n.Translate>PENDING OPERATIONS</i18n.Translate>}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "lightcyan",
|
backgroundColor: "lightcyan",
|
||||||
maxHeight: 150,
|
display: "flex",
|
||||||
padding: 8,
|
justifyContent: "center",
|
||||||
flexGrow: 1,
|
|
||||||
maxWidth: 500,
|
|
||||||
overflowY: transactions.length > 3 ? "scroll" : "hidden",
|
|
||||||
}}
|
}}
|
||||||
elements={transactions.map((t) => {
|
>
|
||||||
const amount = Amounts.parseOrThrow(t.amountEffective);
|
<Banner
|
||||||
return {
|
titleHead={<i18n.Translate>PENDING OPERATIONS</i18n.Translate>}
|
||||||
icon: (
|
style={{
|
||||||
<Avatar
|
backgroundColor: "lightcyan",
|
||||||
style={{
|
maxHeight: 150,
|
||||||
border: "solid blue 1px",
|
padding: 8,
|
||||||
color: "blue",
|
flexGrow: 1,
|
||||||
boxSizing: "border-box",
|
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)}
|
<Grid item xs={"auto"}>
|
||||||
</Avatar>
|
<Avatar
|
||||||
),
|
style={{
|
||||||
action: () => goToTransaction(t.transactionId),
|
border: "solid blue 1px",
|
||||||
description: (
|
color: "blue",
|
||||||
<Fragment>
|
boxSizing: "border-box",
|
||||||
<Typography inline bold>
|
}}
|
||||||
{amount.currency} {Amounts.stringifyValue(amount)}
|
>
|
||||||
</Typography>
|
{t.type.substring(0, 1)}
|
||||||
-
|
</Avatar>
|
||||||
<Time
|
</Grid>
|
||||||
timestamp={AbsoluteTime.fromTimestamp(t.timestamp)}
|
|
||||||
format="dd MMMM yyyy"
|
<Grid item>
|
||||||
/>
|
<Typography inline bold>
|
||||||
</Fragment>
|
{amount.currency} {Amounts.stringifyValue(amount)}
|
||||||
),
|
</Typography>
|
||||||
};
|
-
|
||||||
})}
|
<Time
|
||||||
/>
|
timestamp={AbsoluteTime.fromTimestamp(t.timestamp)}
|
||||||
|
format="dd MMMM yyyy"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Banner>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -159,7 +159,7 @@ export const Middle = styled.div`
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const PopupBox = styled.div<{ noPadding?: boolean; devMode?: boolean }>`
|
export const PopupBox = styled.div<{ noPadding?: boolean }>`
|
||||||
height: 290px;
|
height: 290px;
|
||||||
width: 500px;
|
width: 500px;
|
||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
|
@ -29,7 +29,7 @@ const initial = wxApi;
|
|||||||
|
|
||||||
const Context = createContext<Type>(initial);
|
const Context = createContext<Type>(initial);
|
||||||
|
|
||||||
type Props = Partial<WxApiType> & {
|
type Props = Partial<Type> & {
|
||||||
children: ComponentChildren;
|
children: ComponentChildren;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import { Part } from "../../components/Part.js";
|
|||||||
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
|
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
|
||||||
import { Time } from "../../components/Time.js";
|
import { Time } from "../../components/Time.js";
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { ButtonsSection } from "../Payment/views.js";
|
import { PaymentButtons } from "../../components/PaymentButtons";
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
|
|
||||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||||
@ -83,7 +83,7 @@ export function ReadyView(
|
|||||||
kind="neutral"
|
kind="neutral"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<ButtonsSection
|
<PaymentButtons
|
||||||
amount={amount}
|
amount={amount}
|
||||||
balance={balance}
|
balance={balance}
|
||||||
payStatus={payStatus}
|
payStatus={payStatus}
|
||||||
|
@ -16,35 +16,17 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AbsoluteTime,
|
AbsoluteTime,
|
||||||
AmountJson,
|
|
||||||
Amounts,
|
Amounts,
|
||||||
MerchantContractTerms as ContractTerms,
|
MerchantContractTerms as ContractTerms,
|
||||||
PreparePayResult,
|
|
||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
Product,
|
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { Fragment, h, VNode } from "preact";
|
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 { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
|
||||||
import { Part } from "../../components/Part.js";
|
import { Part } from "../../components/Part.js";
|
||||||
import { QR } from "../../components/QR.js";
|
import { PaymentButtons } from "../../components/PaymentButtons.js";
|
||||||
import {
|
import { Link, SuccessBox, WarningBox } from "../../components/styled/index.js";
|
||||||
Link,
|
|
||||||
LinkSuccess,
|
|
||||||
SmallLightText,
|
|
||||||
SubTitle,
|
|
||||||
SuccessBox,
|
|
||||||
WalletAction,
|
|
||||||
WarningBox,
|
|
||||||
} from "../../components/styled/index.js";
|
|
||||||
import { Time } from "../../components/Time.js";
|
import { Time } from "../../components/Time.js";
|
||||||
import { useTranslationContext } from "../../context/translation.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 { MerchantDetails, PurchaseDetails } from "../../wallet/Transaction.js";
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
|
|
||||||
@ -77,44 +59,12 @@ export function BaseView(state: SupportedStates): VNode {
|
|||||||
? Amounts.parseOrThrow(state.payStatus.amountEffective)
|
? Amounts.parseOrThrow(state.payStatus.amountEffective)
|
||||||
: state.amount,
|
: state.amount,
|
||||||
};
|
};
|
||||||
// const totalFees = Amounts.sub(price.effective, price.raw).amount;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WalletAction>
|
<Fragment>
|
||||||
<LogoHeader />
|
|
||||||
|
|
||||||
<SubTitle>
|
|
||||||
<i18n.Translate>Digital cash payment</i18n.Translate>
|
|
||||||
</SubTitle>
|
|
||||||
|
|
||||||
<ShowImportantMessage state={state} />
|
<ShowImportantMessage state={state} />
|
||||||
|
|
||||||
<section style={{ textAlign: "left" }}>
|
<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
|
<Part
|
||||||
title={<i18n.Translate>Purchase</i18n.Translate>}
|
title={<i18n.Translate>Purchase</i18n.Translate>}
|
||||||
text={contractTerms.summary}
|
text={contractTerms.summary}
|
||||||
@ -125,9 +75,6 @@ export function BaseView(state: SupportedStates): VNode {
|
|||||||
text={<MerchantDetails merchant={contractTerms.merchant} />}
|
text={<MerchantDetails merchant={contractTerms.merchant} />}
|
||||||
kind="neutral"
|
kind="neutral"
|
||||||
/>
|
/>
|
||||||
{/* <pre>{JSON.stringify(price)}</pre>
|
|
||||||
<hr />
|
|
||||||
<pre>{JSON.stringify(state.payStatus, undefined, 2)}</pre> */}
|
|
||||||
<Part
|
<Part
|
||||||
title={<i18n.Translate>Details</i18n.Translate>}
|
title={<i18n.Translate>Details</i18n.Translate>}
|
||||||
text={
|
text={
|
||||||
@ -166,7 +113,7 @@ export function BaseView(state: SupportedStates): VNode {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
<ButtonsSection
|
<PaymentButtons
|
||||||
amount={state.amount}
|
amount={state.amount}
|
||||||
balance={state.balance}
|
balance={state.balance}
|
||||||
payStatus={state.payStatus}
|
payStatus={state.payStatus}
|
||||||
@ -179,75 +126,6 @@ export function BaseView(state: SupportedStates): VNode {
|
|||||||
<i18n.Translate>Cancel</i18n.Translate>
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</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>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -284,124 +162,3 @@ function ShowImportantMessage({ state }: { state: SupportedStates }): VNode {
|
|||||||
|
|
||||||
return <Fragment />;
|
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
|
|
||||||
<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
|
|
||||||
{<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);
|
|
||||||
}
|
|
||||||
|
@ -23,7 +23,7 @@ import { Part } from "../../components/Part.js";
|
|||||||
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
|
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
import { ProductList } from "../Payment/views.js";
|
import { ProductList } from "../../components/ProductList.js";
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
|
|
||||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
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 { Fragment, h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { Amount } from "../../components/Amount.js";
|
import { Amount } from "../../components/Amount.js";
|
||||||
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
||||||
import { LoadingError } from "../../components/LoadingError.js";
|
import { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
|
||||||
import { Part } from "../../components/Part.js";
|
import { Part } from "../../components/Part.js";
|
||||||
import { QR } from "../../components/QR.js";
|
import { QR } from "../../components/QR.js";
|
||||||
import { SelectList } from "../../components/SelectList.js";
|
import { SelectList } from "../../components/SelectList.js";
|
||||||
@ -27,17 +27,14 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Link,
|
Link,
|
||||||
LinkSuccess,
|
LinkSuccess,
|
||||||
SubTitle,
|
|
||||||
SvgIcon,
|
SvgIcon,
|
||||||
WalletAction,
|
|
||||||
} from "../../components/styled/index.js";
|
} from "../../components/styled/index.js";
|
||||||
|
import { TermsOfService } from "../../components/TermsOfService/index.js";
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
import editIcon from "../../svg/edit_24px.svg";
|
import editIcon from "../../svg/edit_24px.svg";
|
||||||
import { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js";
|
import { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js";
|
||||||
import { TermsOfService } from "../../components/TermsOfService/index.js";
|
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
import { ExchangeTosStatus } from "@gnu-taler/taler-util";
|
|
||||||
|
|
||||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
@ -68,12 +65,7 @@ export function SuccessView(state: State.Success): VNode {
|
|||||||
const currentTosVersionIsAccepted =
|
const currentTosVersionIsAccepted =
|
||||||
state.currentExchange.tosStatus === ExchangeTosStatus.Accepted;
|
state.currentExchange.tosStatus === ExchangeTosStatus.Accepted;
|
||||||
return (
|
return (
|
||||||
<WalletAction>
|
<Fragment>
|
||||||
<LogoHeader />
|
|
||||||
<SubTitle>
|
|
||||||
<i18n.Translate>Digital cash withdrawal</i18n.Translate>
|
|
||||||
</SubTitle>
|
|
||||||
|
|
||||||
{state.doWithdrawal.error && (
|
{state.doWithdrawal.error && (
|
||||||
<ErrorTalerOperation
|
<ErrorTalerOperation
|
||||||
title={
|
title={
|
||||||
@ -161,7 +153,7 @@ export function SuccessView(state: State.Success): VNode {
|
|||||||
<i18n.Translate>Cancel</i18n.Translate>
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
</WalletAction>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createHashHistory } from "history";
|
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 Router, { route, Route } from "preact-router";
|
||||||
import { Match } from "preact-router/match";
|
import { Match } from "preact-router/match";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
@ -34,15 +34,28 @@ import {
|
|||||||
useTranslationContext,
|
useTranslationContext,
|
||||||
} from "../context/translation.js";
|
} from "../context/translation.js";
|
||||||
import { useTalerActionURL } from "../hooks/useTalerActionURL.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 { platform } from "../platform/api.js";
|
||||||
import { BackupPage } from "../wallet/BackupPage.js";
|
import { BackupPage } from "../wallet/BackupPage.js";
|
||||||
import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js";
|
import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js";
|
||||||
import { BalancePage } from "./BalancePage.js";
|
import { BalancePage } from "./BalancePage.js";
|
||||||
import { TalerActionFound } from "./TalerActionFound.js";
|
import { TalerActionFound } from "./TalerActionFound.js";
|
||||||
|
|
||||||
function CheckTalerActionComponent(): VNode {
|
export function Application(): VNode {
|
||||||
const [action] = useTalerActionURL();
|
return (
|
||||||
|
<TranslationProvider>
|
||||||
|
<DevContextProvider>
|
||||||
|
<IoCProviderForRuntime>
|
||||||
|
<ApplicationView />
|
||||||
|
</IoCProviderForRuntime>
|
||||||
|
</DevContextProvider>
|
||||||
|
</TranslationProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function ApplicationView(): VNode {
|
||||||
|
const hash_history = createHashHistory();
|
||||||
|
|
||||||
|
const [action, setDismissed] = useTalerActionURL();
|
||||||
|
|
||||||
const actionUri = action?.uri;
|
const actionUri = action?.uri;
|
||||||
|
|
||||||
@ -52,116 +65,110 @@ function CheckTalerActionComponent(): VNode {
|
|||||||
}
|
}
|
||||||
}, [actionUri]);
|
}, [actionUri]);
|
||||||
|
|
||||||
return <Fragment />;
|
async function redirectToTxInfo(tid: string): Promise<void> {
|
||||||
}
|
redirectTo(Pages.balanceTransaction({ tid }));
|
||||||
|
}
|
||||||
|
|
||||||
export function Application(): VNode {
|
|
||||||
const hash_history = createHashHistory();
|
|
||||||
return (
|
return (
|
||||||
<TranslationProvider>
|
<Router history={hash_history}>
|
||||||
<DevContextProvider>
|
<Route
|
||||||
{({ devMode }: { devMode: boolean }) => (
|
path={Pages.balance}
|
||||||
<IoCProviderForRuntime>
|
component={() => (
|
||||||
<PendingTransactions
|
<PopupTemplate path="balance" goToTransaction={redirectToTxInfo}>
|
||||||
goToTransaction={(tid: string) =>
|
<BalancePage
|
||||||
redirectTo(Pages.balanceTransaction({ tid }))
|
goToWalletManualWithdraw={() => redirectTo(Pages.receiveCash({}))}
|
||||||
|
goToWalletDeposit={(currency: string) =>
|
||||||
|
redirectTo(Pages.sendCash({ amount: `${currency}:0` }))
|
||||||
|
}
|
||||||
|
goToWalletHistory={(currency: string) =>
|
||||||
|
redirectTo(Pages.balanceHistory({ currency }))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Match>
|
</PopupTemplate>
|
||||||
{({ 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>
|
|
||||||
)}
|
)}
|
||||||
</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;
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -69,7 +69,7 @@ export class ServiceWorkerHttpLib implements HttpRequestLibrary {
|
|||||||
} else if (ArrayBuffer.isView(requestBody)) {
|
} else if (ArrayBuffer.isView(requestBody)) {
|
||||||
myBody = requestBody;
|
myBody = requestBody;
|
||||||
} else if (typeof requestBody === "object") {
|
} else if (typeof requestBody === "object") {
|
||||||
myBody = JSON.stringify(myBody);
|
myBody = JSON.stringify(requestBody);
|
||||||
} else {
|
} else {
|
||||||
throw Error("unsupported request body type");
|
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(
|
postJson(
|
||||||
url: string,
|
url: string,
|
||||||
body: any,
|
body: any,
|
||||||
|
@ -20,7 +20,11 @@
|
|||||||
*/
|
*/
|
||||||
import { Fragment, FunctionComponent, h } from "preact";
|
import { Fragment, FunctionComponent, h } from "preact";
|
||||||
import { LogoHeader } from "./components/LogoHeader.js";
|
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 { strings } from "./i18n/strings.js";
|
||||||
import { PopupNavBar, WalletNavBar } from "./NavigationBar.js";
|
import { PopupNavBar, WalletNavBar } from "./NavigationBar.js";
|
||||||
|
|
||||||
@ -72,7 +76,7 @@ function getWrapperForGroup(group: string): FunctionComponent {
|
|||||||
return function WalletWrapper({ children }: any) {
|
return function WalletWrapper({ children }: any) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<WalletBox>{children}</WalletBox>
|
<WalletAction>{children}</WalletAction>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -74,7 +74,7 @@ export async function queryToSlashKeys<T>(url: string): Promise<T> {
|
|||||||
return timeout(3000, query);
|
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 }> = {
|
export type StateViewMap<StateType extends { status: string }> = {
|
||||||
[S in StateType as S["status"]]: StateFunc<S>;
|
[S in StateType as S["status"]]: StateFunc<S>;
|
||||||
|
@ -32,7 +32,6 @@ import {
|
|||||||
} from "./views.js";
|
} from "./views.js";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
currency: string;
|
|
||||||
onBack: () => Promise<void>;
|
onBack: () => Promise<void>;
|
||||||
onComplete: (pid: string) => Promise<void>;
|
onComplete: (pid: string) => Promise<void>;
|
||||||
onPaymentRequired: (uri: string) => Promise<void>;
|
onPaymentRequired: (uri: string) => Promise<void>;
|
||||||
|
@ -144,7 +144,6 @@ function useUrlState<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useComponentState({
|
export function useComponentState({
|
||||||
currency,
|
|
||||||
onBack,
|
onBack,
|
||||||
onComplete,
|
onComplete,
|
||||||
onPaymentRequired,
|
onPaymentRequired,
|
||||||
|
@ -26,7 +26,6 @@ import { Props } from "./index.js";
|
|||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
currency: "KUDOS",
|
|
||||||
onBack: nullFunction,
|
onBack: nullFunction,
|
||||||
onComplete: nullFunction,
|
onComplete: nullFunction,
|
||||||
onPaymentRequired: nullFunction,
|
onPaymentRequired: nullFunction,
|
||||||
|
@ -20,352 +20,452 @@
|
|||||||
* @author sebasjm
|
* @author sebasjm
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { TranslatedString } from "@gnu-taler/taler-util";
|
||||||
import { createHashHistory } from "history";
|
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 Router, { route, Route } from "preact-router";
|
||||||
import Match from "preact-router/match";
|
import { useEffect } from "preact/hooks";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
|
||||||
import { LogoHeader } from "../components/LogoHeader.js";
|
import { LogoHeader } from "../components/LogoHeader.js";
|
||||||
import PendingTransactions from "../components/PendingTransactions.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 { DevContextProvider } from "../context/devContext.js";
|
||||||
import { IoCProviderForRuntime } from "../context/iocContext.js";
|
import { IoCProviderForRuntime } from "../context/iocContext.js";
|
||||||
import {
|
import {
|
||||||
TranslationProvider,
|
TranslationProvider,
|
||||||
useTranslationContext,
|
useTranslationContext,
|
||||||
} from "../context/translation.js";
|
} 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 { PaymentPage } from "../cta/Payment/index.js";
|
||||||
|
import { RecoveryPage } from "../cta/Recovery/index.js";
|
||||||
import { RefundPage } from "../cta/Refund/index.js";
|
import { RefundPage } from "../cta/Refund/index.js";
|
||||||
import { TipPage } from "../cta/Tip/index.js";
|
import { TipPage } from "../cta/Tip/index.js";
|
||||||
|
import { TransferCreatePage } from "../cta/TransferCreate/index.js";
|
||||||
|
import { TransferPickupPage } from "../cta/TransferPickup/index.js";
|
||||||
import {
|
import {
|
||||||
WithdrawPageFromParams,
|
WithdrawPageFromParams,
|
||||||
WithdrawPageFromURI,
|
WithdrawPageFromURI,
|
||||||
} from "../cta/Withdraw/index.js";
|
} from "../cta/Withdraw/index.js";
|
||||||
import { DepositPage as DepositPageCTA } from "../cta/Deposit/index.js";
|
import { WalletNavBarOptions, Pages, WalletNavBar } from "../NavigationBar.js";
|
||||||
import { Pages, WalletNavBar } from "../NavigationBar.js";
|
import { platform } from "../platform/api.js";
|
||||||
import { DeveloperPage } from "./DeveloperPage.js";
|
import { AddBackupProviderPage } from "./AddBackupProvider/index.js";
|
||||||
import { BackupPage } from "./BackupPage.js";
|
import { BackupPage } from "./BackupPage.js";
|
||||||
import { DepositPage } from "./DepositPage/index.js";
|
import { DepositPage } from "./DepositPage/index.js";
|
||||||
|
import { DestinationSelectionPage } from "./DestinationSelection/index.js";
|
||||||
|
import { DeveloperPage } from "./DeveloperPage.js";
|
||||||
import { ExchangeAddPage } from "./ExchangeAddPage.js";
|
import { ExchangeAddPage } from "./ExchangeAddPage.js";
|
||||||
import { HistoryPage } from "./History.js";
|
import { HistoryPage } from "./History.js";
|
||||||
|
import { NotificationsPage } from "./Notifications/index.js";
|
||||||
import { ProviderDetailPage } from "./ProviderDetailPage.js";
|
import { ProviderDetailPage } from "./ProviderDetailPage.js";
|
||||||
|
import { QrReaderPage } from "./QrReader.js";
|
||||||
import { SettingsPage } from "./Settings.js";
|
import { SettingsPage } from "./Settings.js";
|
||||||
import { TransactionPage } from "./Transaction.js";
|
import { TransactionPage } from "./Transaction.js";
|
||||||
import { WelcomePage } from "./Welcome.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 {
|
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 { i18n } = useTranslationContext();
|
||||||
|
const hash_history = createHashHistory();
|
||||||
|
|
||||||
|
async function redirectToTxInfo(tid: string): Promise<void> {
|
||||||
|
redirectTo(Pages.balanceTransaction({ tid }));
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<TranslationProvider>
|
<TranslationProvider>
|
||||||
<DevContextProvider>
|
<DevContextProvider>
|
||||||
<IoCProviderForRuntime>
|
<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}>
|
<Router history={hash_history}>
|
||||||
<Match default>
|
<Route
|
||||||
{({ path }: { path: string }) => {
|
path={Pages.welcome}
|
||||||
if (path && path.startsWith("/cta")) return;
|
component={() => (
|
||||||
return (
|
<WalletTemplate>
|
||||||
<Fragment>
|
<WelcomePage />
|
||||||
<LogoHeader />
|
</WalletTemplate>
|
||||||
<WalletNavBar path={path} />
|
)}
|
||||||
{shouldShowPendingOperations(path) && (
|
/>
|
||||||
<div
|
|
||||||
style={{
|
<Route
|
||||||
backgroundColor: "lightcyan",
|
path={Pages.qr}
|
||||||
display: "flex",
|
component={() => (
|
||||||
justifyContent: "center",
|
<WalletTemplate goToTransaction={redirectToTxInfo}>
|
||||||
}}
|
<QrReaderPage
|
||||||
>
|
onDetected={(talerActionUrl: string) => {
|
||||||
<PendingTransactions
|
platform.openWalletURIFromPopup(talerActionUrl);
|
||||||
goToTransaction={(tid: string) =>
|
}}
|
||||||
redirectTo(Pages.balanceTransaction({ tid }))
|
/>
|
||||||
}
|
</WalletTemplate>
|
||||||
/>
|
)}
|
||||||
</div>
|
/>
|
||||||
)}
|
|
||||||
</Fragment>
|
<Route
|
||||||
);
|
path={Pages.settings}
|
||||||
}}
|
component={() => (
|
||||||
</Match>
|
<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.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.balanceTransaction.pattern}
|
||||||
|
component={({ tid }: { tid: string }) => (
|
||||||
|
<WalletTemplate path="balance">
|
||||||
|
<TransactionPage
|
||||||
|
tid={tid}
|
||||||
|
goToWalletHistory={(currency?: string) =>
|
||||||
|
redirectTo(Pages.balanceHistory({ currency }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</WalletTemplate>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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
|
||||||
|
*/}
|
||||||
|
<Route
|
||||||
|
path={Pages.dev}
|
||||||
|
component={() => (
|
||||||
|
<WalletTemplate path="dev" goToTransaction={redirectToTxInfo}>
|
||||||
|
<DeveloperPage />
|
||||||
|
</WalletTemplate>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/**
|
||||||
|
* 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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/**
|
||||||
|
* 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>
|
</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.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.balanceTransaction.pattern}
|
|
||||||
component={TransactionPage}
|
|
||||||
goToWalletHistory={(currency?: string) =>
|
|
||||||
redirectTo(Pages.balanceHistory({ currency }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<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.settings} component={SettingsPage} />
|
|
||||||
<Route path={Pages.notifications} component={NotificationsPage} />
|
|
||||||
|
|
||||||
{/**
|
|
||||||
* 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)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/**
|
|
||||||
* SETTINGS
|
|
||||||
*/}
|
|
||||||
<Route
|
|
||||||
path={Pages.settingsExchangeAdd.pattern}
|
|
||||||
component={ExchangeAddPage}
|
|
||||||
onBack={() => redirectTo(Pages.balance)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/**
|
|
||||||
* DEV
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Route path={Pages.dev} component={DeveloperPage} />
|
|
||||||
|
|
||||||
{/**
|
|
||||||
* 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({})}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
default
|
|
||||||
component={Redirect}
|
|
||||||
to={Pages.balanceHistory({})}
|
|
||||||
/>
|
|
||||||
</Router>
|
|
||||||
</WalletBox>
|
|
||||||
</IoCProviderForRuntime>
|
</IoCProviderForRuntime>
|
||||||
</DevContextProvider>
|
</DevContextProvider>
|
||||||
</TranslationProvider>
|
</TranslationProvider>
|
||||||
@ -403,3 +503,40 @@ function shouldShowPendingOperations(url: string): boolean {
|
|||||||
Pages.backup,
|
Pages.backup,
|
||||||
].some((p) => matchesRoute(url, p));
|
].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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -92,6 +92,7 @@ type CoinsInfo = CoinDumpJson["coins"];
|
|||||||
type CalculatedCoinfInfo = {
|
type CalculatedCoinfInfo = {
|
||||||
ageKeysCount: number | undefined;
|
ageKeysCount: number | undefined;
|
||||||
denom_value: number;
|
denom_value: number;
|
||||||
|
denom_fraction: number;
|
||||||
//remain_value: number;
|
//remain_value: number;
|
||||||
status: string;
|
status: string;
|
||||||
from_refresh: boolean;
|
from_refresh: boolean;
|
||||||
@ -151,7 +152,8 @@ export function View({
|
|||||||
}
|
}
|
||||||
prev[cur.exchange_base_url].push({
|
prev[cur.exchange_base_url].push({
|
||||||
ageKeysCount: cur.ageCommitmentProof?.proof.privateKeys.length,
|
ageKeysCount: cur.ageCommitmentProof?.proof.privateKeys.length,
|
||||||
denom_value: parseFloat(Amounts.stringifyValue(denom)),
|
denom_value: denom.value,
|
||||||
|
denom_fraction: denom.fraction,
|
||||||
// remain_value: parseFloat(
|
// remain_value: parseFloat(
|
||||||
// Amounts.stringifyValue(Amounts.parseOrThrow(cur.remaining_value)),
|
// Amounts.stringifyValue(Amounts.parseOrThrow(cur.remaining_value)),
|
||||||
// ),
|
// ),
|
||||||
@ -340,7 +342,10 @@ export function View({
|
|||||||
{Object.keys(money_by_exchange).map((ex, idx) => {
|
{Object.keys(money_by_exchange).map((ex, idx) => {
|
||||||
const allcoins = money_by_exchange[ex];
|
const allcoins = money_by_exchange[ex];
|
||||||
allcoins.sort((a, b) => {
|
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(
|
const coins = allcoins.reduce(
|
||||||
@ -407,11 +412,31 @@ function ShowAllCoins({
|
|||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const [collapsedSpent, setCollapsedSpent] = useState(true);
|
const [collapsedSpent, setCollapsedSpent] = useState(true);
|
||||||
const [collapsedUnspent, setCollapsedUnspent] = useState(false);
|
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 (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<p>
|
<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>
|
||||||
<p onClick={() => setCollapsedUnspent(true)}>
|
<p onClick={() => setCollapsedUnspent(true)}>
|
||||||
<b>
|
<b>
|
||||||
|
Loading…
Reference in New Issue
Block a user