deposit design from belen, feature missing: kyc
This commit is contained in:
parent
83b9d32b78
commit
fb22009ec4
@ -48,7 +48,7 @@ export const decorators = [
|
||||
const isTestingHeader = (/.*\/header\/?.*/.test(kind));
|
||||
if (isTestingHeader) {
|
||||
// simple box with correct width and height
|
||||
return <div style={{ width: 400, height: 320 }}>
|
||||
return <div style={{ width: "fit-content" }}>
|
||||
<Story />
|
||||
</div>
|
||||
}
|
||||
@ -90,7 +90,7 @@ export const decorators = [
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}`}
|
||||
</style>
|
||||
<div style={{ width: 400, border: 'black solid 1px' }}>
|
||||
<div style={{ border: 'black solid 1px', width: "fit-content" }}>
|
||||
<Body />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# This file is in the public domain.
|
||||
set -e
|
||||
[ "also-wallet" == "$1" ] && { pnpm -C ../taler-wallet-core/ compile || exit 1; }
|
||||
[ "also-util" == "$1" ] && { pnpm -C ../taler-util/ prepare || exit 1; }
|
||||
pnpm clean && pnpm compile && rm -rf extension/ && ./pack.sh
|
||||
|
@ -28,18 +28,18 @@ import { i18n } from "@gnu-taler/taler-util";
|
||||
import { ComponentChildren, h, VNode } from "preact";
|
||||
import Match from "preact-router/match";
|
||||
import { PopupNavigation } from "./components/styled";
|
||||
import { useDevContext } from "./context/devContext";
|
||||
|
||||
export enum Pages {
|
||||
welcome = "/welcome",
|
||||
balance = "/balance",
|
||||
manual_withdraw = "/manual-withdraw",
|
||||
balance_history = "/balance/history/:currency",
|
||||
manual_withdraw = "/manual-withdraw/:currency?",
|
||||
deposit = "/deposit/:currency",
|
||||
settings = "/settings",
|
||||
dev = "/dev",
|
||||
cta = "/cta/:action",
|
||||
backup = "/backup",
|
||||
history = "/history",
|
||||
last_activity = "/last-activity",
|
||||
transaction = "/transaction/:tid",
|
||||
provider_detail = "/provider/:pid",
|
||||
provider_add = "/provider/add",
|
||||
@ -78,7 +78,10 @@ export function NavBar({ devMode, path }: { path: string; devMode: boolean }) {
|
||||
<PopupNavigation devMode={devMode}>
|
||||
<div>
|
||||
<Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
|
||||
<Tab target="/history" current={path}>{i18n.str`History`}</Tab>
|
||||
<Tab
|
||||
target="/last-activity"
|
||||
current={path}
|
||||
>{i18n.str`Last Activity`}</Tab>
|
||||
<Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab>
|
||||
<Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab>
|
||||
{devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>}
|
||||
@ -87,8 +90,8 @@ export function NavBar({ devMode, path }: { path: string; devMode: boolean }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function WalletNavBar() {
|
||||
const { devMode } = useDevContext();
|
||||
export function WalletNavBar({ devMode }: { devMode: boolean }) {
|
||||
// const { devMode } = useDevContext();
|
||||
return (
|
||||
<Match>
|
||||
{({ path }: any) => {
|
||||
|
@ -14,31 +14,28 @@
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { amountFractionalBase, Amounts, Balance } from "@gnu-taler/taler-util";
|
||||
import { Amounts, amountToPretty, Balance } from "@gnu-taler/taler-util";
|
||||
import { h, VNode } from "preact";
|
||||
import {
|
||||
ButtonPrimary,
|
||||
TableWithRoundRows as TableWithRoundedRows,
|
||||
} from "./styled";
|
||||
import { TableWithRoundRows as TableWithRoundedRows } from "./styled";
|
||||
|
||||
export function BalanceTable({
|
||||
balances,
|
||||
goToWalletDeposit,
|
||||
goToWalletHistory,
|
||||
}: {
|
||||
balances: Balance[];
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
goToWalletHistory: (currency: string) => void;
|
||||
}): VNode {
|
||||
const currencyFormatter = new Intl.NumberFormat("en-US");
|
||||
return (
|
||||
<TableWithRoundedRows>
|
||||
{balances.map((entry, idx) => {
|
||||
const av = Amounts.parseOrThrow(entry.available);
|
||||
|
||||
const v = currencyFormatter.format(
|
||||
av.value + av.fraction / amountFractionalBase,
|
||||
);
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<tr
|
||||
key={idx}
|
||||
onClick={() => goToWalletHistory(av.currency)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<td>{av.currency}</td>
|
||||
<td
|
||||
style={{
|
||||
@ -47,12 +44,7 @@ export function BalanceTable({
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{v}
|
||||
</td>
|
||||
<td>
|
||||
<ButtonPrimary onClick={() => goToWalletDeposit(av.currency)}>
|
||||
Deposit
|
||||
</ButtonPrimary>
|
||||
{Amounts.stringifyValue(av)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
(C) 2016 GNUnet e.V.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
export function Loading(): VNode {
|
||||
return <div>Loading...</div>;
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
import { h, VNode } from "preact";
|
||||
import arrowDown from "../../static/img/chevron-down.svg";
|
||||
import { ButtonBoxPrimary, ButtonPrimary, ParagraphClickable } from "./styled";
|
||||
import { useState } from "preact/hooks";
|
||||
|
||||
export interface Props {
|
||||
label: (s: string) => string;
|
||||
actions: string[];
|
||||
onClick: (s: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* functionality: it will receive a list of actions, take the first actions as
|
||||
* the first chosen action
|
||||
* the user may change the chosen action
|
||||
* when the user click the button it will call onClick with the chosen action
|
||||
* as argument
|
||||
*
|
||||
* visually: it is a primary button with a select handler on the right
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export function MultiActionButton({
|
||||
label,
|
||||
actions,
|
||||
onClick: doClick,
|
||||
}: Props): VNode {
|
||||
const defaultAction = actions.length > 0 ? actions[0] : "";
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [selected, setSelected] = useState<string>(defaultAction);
|
||||
|
||||
const canChange = actions.length > 1;
|
||||
const options = canChange ? actions.filter((a) => a !== selected) : [];
|
||||
function select(m: string): void {
|
||||
setSelected(m);
|
||||
setOpened(false);
|
||||
}
|
||||
|
||||
if (!canChange) {
|
||||
return (
|
||||
<ButtonPrimary onClick={() => doClick(selected)}>
|
||||
{label(selected)}
|
||||
</ButtonPrimary>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative", display: "inline-block" }}>
|
||||
{opened && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 32 + 5,
|
||||
right: 0,
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
borderRadius: 5,
|
||||
border: "1px solid blue",
|
||||
background: "white",
|
||||
boxShadow: "0px 8px 16px 0px rgba(0,0,0,0.2)",
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{options.map((m) => (
|
||||
<ParagraphClickable key={m} onClick={() => select(m)}>
|
||||
{label(m)}
|
||||
</ParagraphClickable>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<ButtonPrimary
|
||||
onClick={() => doClick(selected)}
|
||||
style={{
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
marginRight: 0,
|
||||
}}
|
||||
>
|
||||
{label(selected)}
|
||||
</ButtonPrimary>
|
||||
|
||||
<ButtonBoxPrimary
|
||||
onClick={() => setOpened((s) => !s)}
|
||||
style={{
|
||||
marginLeft: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
}}
|
||||
>
|
||||
<img style={{ height: 14 }} src={arrowDown} />
|
||||
</ButtonBoxPrimary>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -43,7 +43,7 @@ export const WalletAction = styled.div`
|
||||
}
|
||||
section {
|
||||
margin-bottom: 2em;
|
||||
& button {
|
||||
button {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
@ -92,6 +92,10 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
|
||||
border-bottom: 1px solid black;
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
button {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
& > header {
|
||||
@ -123,7 +127,7 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
background-color: #f7f7f7;
|
||||
& button {
|
||||
button {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
@ -136,9 +140,9 @@ export const Middle = styled.div`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const PopupBox = styled.div<{ noPadding?: boolean }>`
|
||||
export const PopupBox = styled.div<{ noPadding?: boolean; devMode: boolean }>`
|
||||
height: 290px;
|
||||
width: 400px;
|
||||
width: ${({ devMode }) => (!devMode ? "400px" : "500px")};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
@ -156,6 +160,10 @@ export const PopupBox = styled.div<{ noPadding?: boolean }>`
|
||||
border-bottom: 1px solid black;
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
button {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
& > section[data-expanded] {
|
||||
@ -196,7 +204,7 @@ export const PopupBox = styled.div<{ noPadding?: boolean }>`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
& button {
|
||||
button {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
@ -363,11 +371,11 @@ export const CenteredDialog = styled.div`
|
||||
|
||||
export const Button = styled.button<{ upperCased?: boolean }>`
|
||||
display: inline-block;
|
||||
zoom: 1;
|
||||
/* zoom: 1; */
|
||||
line-height: normal;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
vertical-align: middle; //check this
|
||||
/* text-align: center; */
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
box-sizing: border-box;
|
||||
@ -379,7 +387,7 @@ export const Button = styled.button<{ upperCased?: boolean }>`
|
||||
/* color: #444; rgba not supported (IE 8) */
|
||||
color: rgba(0, 0, 0, 0.8); /* rgba supported */
|
||||
border: 1px solid #999; /*IE 6/7/8*/
|
||||
border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/
|
||||
/* border: none rgba(0, 0, 0, 0); IE9 + everything else */
|
||||
background-color: "#e6e6e6";
|
||||
text-decoration: none;
|
||||
border-radius: 2px;
|
||||
@ -401,11 +409,11 @@ export const Button = styled.button<{ upperCased?: boolean }>`
|
||||
}
|
||||
|
||||
:hover {
|
||||
filter: alpha(opacity=90);
|
||||
filter: alpha(opacity=80);
|
||||
background-image: linear-gradient(
|
||||
transparent,
|
||||
rgba(0, 0, 0, 0.05) 40%,
|
||||
rgba(0, 0, 0, 0.1)
|
||||
rgba(0, 0, 0, 0.1) 40%,
|
||||
rgba(0, 0, 0, 0.2)
|
||||
);
|
||||
}
|
||||
`;
|
||||
@ -415,7 +423,7 @@ export const Link = styled.a<{ upperCased?: boolean }>`
|
||||
zoom: 1;
|
||||
line-height: normal;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
/* vertical-align: middle; */
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
@ -463,8 +471,8 @@ export const FontIcon = styled.div`
|
||||
/* vertical-align: text-top; */
|
||||
`;
|
||||
export const ButtonBox = styled(Button)`
|
||||
padding: 0.5em;
|
||||
font-size: x-small;
|
||||
padding: 8px;
|
||||
/* font-size: small; */
|
||||
|
||||
& > ${FontIcon} {
|
||||
width: 1em;
|
||||
@ -472,12 +480,13 @@ export const ButtonBox = styled(Button)`
|
||||
display: inline;
|
||||
line-height: 0px;
|
||||
}
|
||||
background-color: transparent;
|
||||
background-color: #f7f7f7;
|
||||
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
border-color: black;
|
||||
color: black;
|
||||
/* text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); */
|
||||
/* -webkit-border-horizontal-spacing: 0px;
|
||||
-webkit-border-vertical-spacing: 0px; */
|
||||
`;
|
||||
@ -499,6 +508,7 @@ export const LinkPrimary = styled(Link)`
|
||||
export const ButtonPrimary = styled(ButtonVariant)<{ small?: boolean }>`
|
||||
font-size: ${({ small }) => (small ? "small" : "inherit")};
|
||||
background-color: rgb(66, 184, 221);
|
||||
border-color: rgb(66, 184, 221);
|
||||
`;
|
||||
export const ButtonBoxPrimary = styled(ButtonBox)`
|
||||
color: rgb(66, 184, 221);
|
||||
@ -714,6 +724,7 @@ export const InputWithLabel = styled.div<{ invalid?: boolean }>`
|
||||
border-top-right-radius: 0.25em;
|
||||
border-color: ${({ invalid }) => (!invalid ? "lightgray" : "red")};
|
||||
}
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
export const ErrorText = styled.div`
|
||||
@ -772,13 +783,13 @@ export const PopupNavigation = styled.div<{ devMode?: boolean }>`
|
||||
display: flex;
|
||||
|
||||
& > div {
|
||||
width: 400px;
|
||||
width: ${({ devMode }) => (!devMode ? "400px" : "500px")};
|
||||
}
|
||||
|
||||
& > div > a {
|
||||
color: #f8faf7;
|
||||
display: inline-block;
|
||||
width: calc(400px / ${({ devMode }) => (!devMode ? 4 : 5)});
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
@ -804,10 +815,9 @@ export const NiceSelect = styled.div`
|
||||
box-shadow: none;
|
||||
|
||||
background-image: ${image};
|
||||
background-position: right 0.5rem center;
|
||||
background-position: right 8px center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5em 1.5em;
|
||||
padding-right: 2.5rem;
|
||||
|
||||
background-color: white;
|
||||
|
||||
@ -967,3 +977,17 @@ export const StyledCheckboxLabel = styled.div`
|
||||
box-shadow: 0 0 0 0.05em #fff, 0 0 0.15em 0.1em currentColor;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ParagraphClickable = styled.p`
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
padding: 8px 16px;
|
||||
:hover {
|
||||
filter: alpha(opacity=80);
|
||||
background-image: linear-gradient(
|
||||
transparent,
|
||||
rgba(0, 0, 0, 0.1) 40%,
|
||||
rgba(0, 0, 0, 0.2)
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
@ -42,5 +42,6 @@ export const DevContextProvider = ({ children }: { children: any }): VNode => {
|
||||
const [value, setter] = useLocalStorage("devMode");
|
||||
const devMode = value === "true";
|
||||
const toggleDevMode = () => setter((v) => (!v ? "true" : undefined));
|
||||
children = children.length === 1 && typeof children === "function" ? children({ devMode }) : children;
|
||||
return h(Context.Provider, { value: { devMode, toggleDevMode }, children });
|
||||
};
|
||||
|
168
packages/taler-wallet-webextension/src/cta/Deposit.stories.tsx
Normal file
168
packages/taler-wallet-webextension/src/cta/Deposit.stories.tsx
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2021 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { ContractTerms, PreparePayResultType } from "@gnu-taler/taler-util";
|
||||
import { createExample } from "../test-utils";
|
||||
import { PaymentRequestView as TestedComponent } from "./Deposit";
|
||||
|
||||
export default {
|
||||
title: "cta/deposit",
|
||||
component: TestedComponent,
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
export const NoBalance = createExample(TestedComponent, {
|
||||
payStatus: {
|
||||
status: PreparePayResultType.InsufficientBalance,
|
||||
noncePriv: "",
|
||||
proposalId: "proposal1234",
|
||||
contractTerms: {
|
||||
merchant: {
|
||||
name: "someone",
|
||||
},
|
||||
summary: "some beers",
|
||||
amount: "USD:10",
|
||||
} as Partial<ContractTerms> as any,
|
||||
amountRaw: "USD:10",
|
||||
},
|
||||
});
|
||||
|
||||
export const NoEnoughBalance = createExample(TestedComponent, {
|
||||
payStatus: {
|
||||
status: PreparePayResultType.InsufficientBalance,
|
||||
noncePriv: "",
|
||||
proposalId: "proposal1234",
|
||||
contractTerms: {
|
||||
merchant: {
|
||||
name: "someone",
|
||||
},
|
||||
summary: "some beers",
|
||||
amount: "USD:10",
|
||||
} as Partial<ContractTerms> as any,
|
||||
amountRaw: "USD:10",
|
||||
},
|
||||
balance: {
|
||||
currency: "USD",
|
||||
fraction: 40000000,
|
||||
value: 9,
|
||||
},
|
||||
});
|
||||
|
||||
export const PaymentPossible = createExample(TestedComponent, {
|
||||
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
|
||||
payStatus: {
|
||||
status: PreparePayResultType.PaymentPossible,
|
||||
amountEffective: "USD:10",
|
||||
amountRaw: "USD:10",
|
||||
noncePriv: "",
|
||||
contractTerms: {
|
||||
nonce: "123213123",
|
||||
merchant: {
|
||||
name: "someone",
|
||||
},
|
||||
amount: "USD:10",
|
||||
summary: "some beers",
|
||||
} as Partial<ContractTerms> as any,
|
||||
contractTermsHash: "123456",
|
||||
proposalId: "proposal1234",
|
||||
},
|
||||
});
|
||||
|
||||
export const PaymentPossibleWithFee = createExample(TestedComponent, {
|
||||
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
|
||||
payStatus: {
|
||||
status: PreparePayResultType.PaymentPossible,
|
||||
amountEffective: "USD:10.20",
|
||||
amountRaw: "USD:10",
|
||||
noncePriv: "",
|
||||
contractTerms: {
|
||||
nonce: "123213123",
|
||||
merchant: {
|
||||
name: "someone",
|
||||
},
|
||||
amount: "USD:10",
|
||||
summary: "some beers",
|
||||
} as Partial<ContractTerms> as any,
|
||||
contractTermsHash: "123456",
|
||||
proposalId: "proposal1234",
|
||||
},
|
||||
});
|
||||
|
||||
export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, {
|
||||
payStatus: {
|
||||
status: PreparePayResultType.AlreadyConfirmed,
|
||||
amountEffective: "USD:10",
|
||||
amountRaw: "USD:10",
|
||||
contractTerms: {
|
||||
merchant: {
|
||||
name: "someone",
|
||||
},
|
||||
fulfillment_message:
|
||||
"congratulations! you are looking at the fulfillment message! ",
|
||||
summary: "some beers",
|
||||
amount: "USD:10",
|
||||
} as Partial<ContractTerms> as any,
|
||||
contractTermsHash: "123456",
|
||||
proposalId: "proposal1234",
|
||||
paid: false,
|
||||
},
|
||||
});
|
||||
|
||||
export const AlreadyConfirmedWithoutFullfilment = createExample(
|
||||
TestedComponent,
|
||||
{
|
||||
payStatus: {
|
||||
status: PreparePayResultType.AlreadyConfirmed,
|
||||
amountEffective: "USD:10",
|
||||
amountRaw: "USD:10",
|
||||
contractTerms: {
|
||||
merchant: {
|
||||
name: "someone",
|
||||
},
|
||||
summary: "some beers",
|
||||
amount: "USD:10",
|
||||
} as Partial<ContractTerms> as any,
|
||||
contractTermsHash: "123456",
|
||||
proposalId: "proposal1234",
|
||||
paid: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const AlreadyPaid = createExample(TestedComponent, {
|
||||
payStatus: {
|
||||
status: PreparePayResultType.AlreadyConfirmed,
|
||||
amountEffective: "USD:10",
|
||||
amountRaw: "USD:10",
|
||||
contractTerms: {
|
||||
merchant: {
|
||||
name: "someone",
|
||||
},
|
||||
fulfillment_message:
|
||||
"congratulations! you are looking at the fulfillment message! ",
|
||||
summary: "some beers",
|
||||
amount: "USD:10",
|
||||
} as Partial<ContractTerms> as any,
|
||||
contractTermsHash: "123456",
|
||||
proposalId: "proposal1234",
|
||||
paid: true,
|
||||
},
|
||||
});
|
251
packages/taler-wallet-webextension/src/cta/Deposit.tsx
Normal file
251
packages/taler-wallet-webextension/src/cta/Deposit.tsx
Normal file
@ -0,0 +1,251 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
(C) 2015 GNUnet e.V.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Page shown to the user to confirm entering
|
||||
* a contract.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
// import * as i18n from "../i18n";
|
||||
|
||||
import {
|
||||
AmountJson,
|
||||
Amounts,
|
||||
amountToPretty,
|
||||
ConfirmPayResult,
|
||||
ConfirmPayResultDone,
|
||||
ConfirmPayResultType,
|
||||
ContractTerms,
|
||||
i18n,
|
||||
NotificationType,
|
||||
PreparePayResult,
|
||||
PreparePayResultType,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { OperationFailedError } from "@gnu-taler/taler-wallet-core";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { ErrorTalerOperation } from "../components/ErrorTalerOperation";
|
||||
import { LogoHeader } from "../components/LogoHeader";
|
||||
import { Part } from "../components/Part";
|
||||
import {
|
||||
ErrorBox,
|
||||
SuccessBox,
|
||||
WalletAction,
|
||||
WarningBox,
|
||||
} from "../components/styled";
|
||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||
import * as wxApi from "../wxApi";
|
||||
|
||||
interface Props {
|
||||
talerPayUri?: string;
|
||||
goBack: () => void;
|
||||
}
|
||||
|
||||
export function DepositPage({ talerPayUri, goBack }: Props): VNode {
|
||||
const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [payErrMsg, setPayErrMsg] = useState<
|
||||
OperationFailedError | string | undefined
|
||||
>(undefined);
|
||||
|
||||
const balance = useAsyncAsHook(wxApi.getBalance, [
|
||||
NotificationType.CoinWithdrawn,
|
||||
]);
|
||||
const balanceWithoutError = balance?.hasError
|
||||
? []
|
||||
: balance?.response.balances || [];
|
||||
|
||||
const foundBalance = balanceWithoutError.find(
|
||||
(b) =>
|
||||
payStatus &&
|
||||
Amounts.parseOrThrow(b.available).currency ===
|
||||
Amounts.parseOrThrow(payStatus?.amountRaw).currency,
|
||||
);
|
||||
const foundAmount = foundBalance
|
||||
? Amounts.parseOrThrow(foundBalance.available)
|
||||
: undefined;
|
||||
// We use a string here so that dependency tracking for useEffect works properly
|
||||
const foundAmountStr = foundAmount
|
||||
? Amounts.stringify(foundAmount)
|
||||
: undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (!talerPayUri) return;
|
||||
const doFetch = async (): Promise<void> => {
|
||||
try {
|
||||
const p = await wxApi.preparePay(talerPayUri);
|
||||
setPayStatus(p);
|
||||
} catch (e) {
|
||||
console.log("Got error while trying to pay", e);
|
||||
if (e instanceof OperationFailedError) {
|
||||
setPayErrMsg(e);
|
||||
}
|
||||
if (e instanceof Error) {
|
||||
setPayErrMsg(e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
doFetch();
|
||||
}, [talerPayUri, foundAmountStr]);
|
||||
|
||||
if (!talerPayUri) {
|
||||
return <span>missing pay uri</span>;
|
||||
}
|
||||
|
||||
if (!payStatus) {
|
||||
if (payErrMsg instanceof OperationFailedError) {
|
||||
return (
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
<h2>{i18n.str`Digital cash payment`}</h2>
|
||||
<section>
|
||||
<ErrorTalerOperation
|
||||
title="Could not get the payment information for this order"
|
||||
error={payErrMsg?.operationError}
|
||||
/>
|
||||
</section>
|
||||
</WalletAction>
|
||||
);
|
||||
}
|
||||
if (payErrMsg) {
|
||||
return (
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
<h2>{i18n.str`Digital cash payment`}</h2>
|
||||
<section>
|
||||
<p>Could not get the payment information for this order</p>
|
||||
<ErrorBox>{payErrMsg}</ErrorBox>
|
||||
</section>
|
||||
</WalletAction>
|
||||
);
|
||||
}
|
||||
return <span>Loading payment information ...</span>;
|
||||
}
|
||||
|
||||
const onClick = async (): Promise<void> => {
|
||||
// try {
|
||||
// const res = await doPayment(payStatus);
|
||||
// setPayResult(res);
|
||||
// } catch (e) {
|
||||
// console.error(e);
|
||||
// if (e instanceof Error) {
|
||||
// setPayErrMsg(e.message);
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
return (
|
||||
<PaymentRequestView
|
||||
uri={talerPayUri}
|
||||
payStatus={payStatus}
|
||||
payResult={payResult}
|
||||
onClick={onClick}
|
||||
balance={foundAmount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export interface PaymentRequestViewProps {
|
||||
payStatus: PreparePayResult;
|
||||
payResult?: ConfirmPayResult;
|
||||
onClick: () => void;
|
||||
payErrMsg?: string;
|
||||
uri: string;
|
||||
balance: AmountJson | undefined;
|
||||
}
|
||||
export function PaymentRequestView({
|
||||
uri,
|
||||
payStatus,
|
||||
payResult,
|
||||
onClick,
|
||||
balance,
|
||||
}: PaymentRequestViewProps): VNode {
|
||||
let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw);
|
||||
const contractTerms: ContractTerms = payStatus.contractTerms;
|
||||
|
||||
return (
|
||||
<WalletAction>
|
||||
<LogoHeader />
|
||||
|
||||
<h2>{i18n.str`Digital cash deposit`}</h2>
|
||||
{payStatus.status === PreparePayResultType.AlreadyConfirmed &&
|
||||
(payStatus.paid ? (
|
||||
<SuccessBox> Already paid </SuccessBox>
|
||||
) : (
|
||||
<WarningBox> Already claimed </WarningBox>
|
||||
))}
|
||||
{payResult && payResult.type === ConfirmPayResultType.Done && (
|
||||
<SuccessBox>
|
||||
<h3>Payment complete</h3>
|
||||
<p>
|
||||
{!payResult.contractTerms.fulfillment_message
|
||||
? "You will now be sent back to the merchant you came from."
|
||||
: payResult.contractTerms.fulfillment_message}
|
||||
</p>
|
||||
</SuccessBox>
|
||||
)}
|
||||
<section>
|
||||
{payStatus.status !== PreparePayResultType.InsufficientBalance &&
|
||||
Amounts.isNonZero(totalFees) && (
|
||||
<Part
|
||||
big
|
||||
title="Total to pay"
|
||||
text={amountToPretty(
|
||||
Amounts.parseOrThrow(payStatus.amountEffective),
|
||||
)}
|
||||
kind="negative"
|
||||
/>
|
||||
)}
|
||||
<Part
|
||||
big
|
||||
title="Purchase amount"
|
||||
text={amountToPretty(Amounts.parseOrThrow(payStatus.amountRaw))}
|
||||
kind="neutral"
|
||||
/>
|
||||
{Amounts.isNonZero(totalFees) && (
|
||||
<Fragment>
|
||||
<Part
|
||||
big
|
||||
title="Fee"
|
||||
text={amountToPretty(totalFees)}
|
||||
kind="negative"
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
<Part
|
||||
title="Merchant"
|
||||
text={contractTerms.merchant.name}
|
||||
kind="neutral"
|
||||
/>
|
||||
<Part title="Purchase" text={contractTerms.summary} kind="neutral" />
|
||||
{contractTerms.order_id && (
|
||||
<Part
|
||||
title="Receipt"
|
||||
text={`#${contractTerms.order_id}`}
|
||||
kind="neutral"
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
</WalletAction>
|
||||
);
|
||||
}
|
@ -57,35 +57,10 @@ import * as wxApi from "../wxApi";
|
||||
|
||||
interface Props {
|
||||
talerPayUri?: string;
|
||||
goToWalletManualWithdraw: () => void;
|
||||
goToWalletManualWithdraw: (currency?: string) => void;
|
||||
goBack: () => void;
|
||||
}
|
||||
|
||||
// export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) {
|
||||
// const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
|
||||
// let message;
|
||||
// if (fulfillmentUrl) {
|
||||
// message = (
|
||||
// <span>
|
||||
// You have already paid for this article. Click{" "}
|
||||
// <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again.
|
||||
// </span>
|
||||
// );
|
||||
// } else {
|
||||
// message = <span>
|
||||
// You have already paid for this article:{" "}
|
||||
// <em>
|
||||
// {payStatus.contractTerms.fulfillment_message ?? "no message given"}
|
||||
// </em>
|
||||
// </span>;
|
||||
// }
|
||||
// return <section class="main">
|
||||
// <h1>GNU Taler Wallet</h1>
|
||||
// <article class="fade">
|
||||
// {message}
|
||||
// </article>
|
||||
// </section>
|
||||
// }
|
||||
|
||||
const doPayment = async (
|
||||
payStatus: PreparePayResult,
|
||||
): Promise<ConfirmPayResultDone> => {
|
||||
|
@ -19,7 +19,7 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { createExample, NullLink } from "../test-utils";
|
||||
import { createExample } from "../test-utils";
|
||||
import { BalanceView as TestedComponent } from "./BalancePage";
|
||||
|
||||
export default {
|
||||
@ -28,211 +28,124 @@ export default {
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
export const NotYetLoaded = createExample(TestedComponent, {});
|
||||
|
||||
export const GotError = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: true,
|
||||
message: "Network error",
|
||||
},
|
||||
Linker: NullLink,
|
||||
});
|
||||
|
||||
export const EmptyBalance = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [],
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
balances: [],
|
||||
});
|
||||
|
||||
export const SomeCoins = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:10.5",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:10.5",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
});
|
||||
|
||||
export const SomeCoinsAndIncomingMoney = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:2.23",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:5.11",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
});
|
||||
|
||||
export const SomeCoinsAndOutgoingMoney = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:2.23",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:5.11",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
});
|
||||
|
||||
export const SomeCoinsAndMovingMoney = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:2.23",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:2",
|
||||
pendingOutgoing: "USD:5.11",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
});
|
||||
|
||||
export const SomeCoinsInTwoCurrencies = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:2",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:5.1",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "EUR:4",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:3.01",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
],
|
||||
});
|
||||
|
||||
export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:1",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "TESTKUDOS:2000",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "EUR:4",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:15",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "EUR:1",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
{
|
||||
available: "TESTKUDOS:2000",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "JPY:4",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:15",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const NoCoinsInTreeCurrencies = createExample(TestedComponent, {
|
||||
balances: [
|
||||
{
|
||||
available: "EUR:3",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "USD:2",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "ARS:1",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:15",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const SomeCoinsInFiveCurrencies = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:13451",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "EUR:202.02",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "ARS:30",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "JPY:51223233",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "JPY:51223233",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "DEMOKUDOS:6",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "TESTKUDOS:6",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:5",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:0",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
{
|
||||
available: "ARS:13451",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "EUR:202.02",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "JPY:0",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "JPY:51223233",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "DEMOKUDOS:6",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "TESTKUDOS:6",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:5",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -14,70 +14,81 @@
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
|
||||
import { Amounts, Balance, i18n } from "@gnu-taler/taler-util";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { BalanceTable } from "../components/BalanceTable";
|
||||
import { ButtonPrimary, ErrorBox } from "../components/styled";
|
||||
import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||
import { PageLink } from "../renderHtml";
|
||||
import * as wxApi from "../wxApi";
|
||||
import { MultiActionButton } from "../components/MultiActionButton";
|
||||
import { Loading } from "../components/Loading";
|
||||
|
||||
interface Props {
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
goToWalletHistory: (currency: string) => void;
|
||||
goToWalletManualWithdraw: () => void;
|
||||
}
|
||||
export function BalancePage({
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletDeposit,
|
||||
goToWalletHistory,
|
||||
}: Props): VNode {
|
||||
const state = useAsyncAsHook(wxApi.getBalance);
|
||||
return (
|
||||
<BalanceView
|
||||
balance={state}
|
||||
Linker={PageLink}
|
||||
goToWalletManualWithdraw={goToWalletManualWithdraw}
|
||||
goToWalletDeposit={goToWalletDeposit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export interface BalanceViewProps {
|
||||
balance: HookResponse<BalancesResponse>;
|
||||
Linker: typeof PageLink;
|
||||
goToWalletManualWithdraw: () => void;
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
}
|
||||
const balances = !state || state.hasError ? [] : state.response.balances;
|
||||
|
||||
export function BalanceView({
|
||||
balance,
|
||||
Linker,
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletDeposit,
|
||||
}: BalanceViewProps): VNode {
|
||||
if (!balance) {
|
||||
return <div>Loading...</div>;
|
||||
if (!state) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (balance.hasError) {
|
||||
if (state.hasError) {
|
||||
return (
|
||||
<Fragment>
|
||||
<ErrorBox>{balance.message}</ErrorBox>
|
||||
<ErrorBox>{state.message}</ErrorBox>
|
||||
<p>
|
||||
Click <Linker pageName="welcome">here</Linker> for help and
|
||||
Click <PageLink pageName="welcome">here</PageLink> for help and
|
||||
diagnostics.
|
||||
</p>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
if (balance.response.balances.length === 0) {
|
||||
|
||||
return (
|
||||
<BalanceView
|
||||
balances={balances}
|
||||
goToWalletManualWithdraw={goToWalletManualWithdraw}
|
||||
goToWalletDeposit={goToWalletDeposit}
|
||||
goToWalletHistory={goToWalletHistory}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export interface BalanceViewProps {
|
||||
balances: Balance[];
|
||||
goToWalletManualWithdraw: () => void;
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
goToWalletHistory: (currency: string) => void;
|
||||
}
|
||||
|
||||
export function BalanceView({
|
||||
balances,
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletDeposit,
|
||||
goToWalletHistory,
|
||||
}: BalanceViewProps): VNode {
|
||||
const currencyWithNonZeroAmount = balances
|
||||
.filter((b) => !Amounts.isZero(b.available))
|
||||
.map((b) => b.available.split(":")[0]);
|
||||
|
||||
if (balances.length === 0) {
|
||||
return (
|
||||
<Fragment>
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
You have no balance to show. Need some{" "}
|
||||
<Linker pageName="/welcome">help</Linker> getting started?
|
||||
<PageLink pageName="/welcome">help</PageLink> getting started?
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
<footer style={{ justifyContent: "space-between" }}>
|
||||
<div />
|
||||
<ButtonPrimary onClick={goToWalletManualWithdraw}>
|
||||
Withdraw
|
||||
</ButtonPrimary>
|
||||
@ -90,15 +101,21 @@ export function BalanceView({
|
||||
<Fragment>
|
||||
<section>
|
||||
<BalanceTable
|
||||
balances={balance.response.balances}
|
||||
goToWalletDeposit={goToWalletDeposit}
|
||||
balances={balances}
|
||||
goToWalletHistory={goToWalletHistory}
|
||||
/>
|
||||
</section>
|
||||
<footer style={{ justifyContent: "space-between" }}>
|
||||
<div />
|
||||
<ButtonPrimary onClick={goToWalletManualWithdraw}>
|
||||
Withdraw
|
||||
</ButtonPrimary>
|
||||
{currencyWithNonZeroAmount.length > 0 && (
|
||||
<MultiActionButton
|
||||
label={(s) => `Deposit ${s}`}
|
||||
actions={currencyWithNonZeroAmount}
|
||||
onClick={(c) => goToWalletDeposit(c)}
|
||||
/>
|
||||
)}
|
||||
</footer>
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -86,10 +86,6 @@ export function View({
|
||||
return (
|
||||
<div>
|
||||
<p>Debug tools:</p>
|
||||
<button onClick={openExtensionPage("/static/popup.html")}>
|
||||
wallet tab
|
||||
</button>
|
||||
|
||||
<button onClick={confirmReset}>reset</button>
|
||||
<br />
|
||||
<button onClick={onExportDatabase}>export database</button>
|
||||
@ -109,7 +105,8 @@ export function View({
|
||||
"yyyy/MM/dd_HH:mm",
|
||||
)}.json`}
|
||||
>
|
||||
click here
|
||||
{" "}
|
||||
click here{" "}
|
||||
</a>
|
||||
to download
|
||||
</div>
|
||||
|
@ -1,213 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2021 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import {
|
||||
PaymentStatus,
|
||||
TransactionCommon,
|
||||
TransactionDeposit,
|
||||
TransactionPayment,
|
||||
TransactionRefresh,
|
||||
TransactionRefund,
|
||||
TransactionTip,
|
||||
TransactionType,
|
||||
TransactionWithdrawal,
|
||||
WithdrawalType,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { createExample } from "../test-utils";
|
||||
import { HistoryView as TestedComponent } from "./History";
|
||||
|
||||
export default {
|
||||
title: "popup/history/list",
|
||||
component: TestedComponent,
|
||||
};
|
||||
|
||||
const commonTransaction = {
|
||||
amountRaw: "USD:10",
|
||||
amountEffective: "USD:9",
|
||||
pending: false,
|
||||
timestamp: {
|
||||
t_ms: new Date().getTime(),
|
||||
},
|
||||
transactionId: "12",
|
||||
} as TransactionCommon;
|
||||
|
||||
const exampleData = {
|
||||
withdraw: {
|
||||
...commonTransaction,
|
||||
type: TransactionType.Withdrawal,
|
||||
exchangeBaseUrl: "http://exchange.demo.taler.net",
|
||||
withdrawalDetails: {
|
||||
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
|
||||
confirmed: false,
|
||||
exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
|
||||
type: WithdrawalType.ManualTransfer,
|
||||
},
|
||||
} as TransactionWithdrawal,
|
||||
payment: {
|
||||
...commonTransaction,
|
||||
amountEffective: "USD:11",
|
||||
type: TransactionType.Payment,
|
||||
info: {
|
||||
contractTermsHash: "ASDZXCASD",
|
||||
merchant: {
|
||||
name: "the merchant",
|
||||
},
|
||||
orderId: "2021.167-03NPY6MCYMVGT",
|
||||
products: [],
|
||||
summary: "the summary",
|
||||
fulfillmentMessage: "",
|
||||
},
|
||||
proposalId: "1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0",
|
||||
status: PaymentStatus.Accepted,
|
||||
} as TransactionPayment,
|
||||
deposit: {
|
||||
...commonTransaction,
|
||||
type: TransactionType.Deposit,
|
||||
depositGroupId: "#groupId",
|
||||
targetPaytoUri: "payto://x-taler-bank/bank/account",
|
||||
} as TransactionDeposit,
|
||||
refresh: {
|
||||
...commonTransaction,
|
||||
type: TransactionType.Refresh,
|
||||
exchangeBaseUrl: "http://exchange.taler",
|
||||
} as TransactionRefresh,
|
||||
tip: {
|
||||
...commonTransaction,
|
||||
type: TransactionType.Tip,
|
||||
merchantBaseUrl: "http://merchant.taler",
|
||||
} as TransactionTip,
|
||||
refund: {
|
||||
...commonTransaction,
|
||||
type: TransactionType.Refund,
|
||||
refundedTransactionId:
|
||||
"payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0",
|
||||
info: {
|
||||
contractTermsHash: "ASDZXCASD",
|
||||
merchant: {
|
||||
name: "the merchant",
|
||||
},
|
||||
orderId: "2021.167-03NPY6MCYMVGT",
|
||||
products: [],
|
||||
summary: "the summary",
|
||||
fulfillmentMessage: "",
|
||||
},
|
||||
} as TransactionRefund,
|
||||
};
|
||||
|
||||
export const EmptyWithBalance = createExample(TestedComponent, {
|
||||
list: [],
|
||||
balances: [
|
||||
{
|
||||
available: "TESTKUDOS:10",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const EmptyWithNoBalance = createExample(TestedComponent, {
|
||||
list: [],
|
||||
balances: [],
|
||||
});
|
||||
|
||||
export const One = createExample(TestedComponent, {
|
||||
list: [exampleData.withdraw],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:10",
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const OnePending = createExample(TestedComponent, {
|
||||
list: [
|
||||
{
|
||||
...exampleData.withdraw,
|
||||
pending: true,
|
||||
},
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:10",
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const Several = createExample(TestedComponent, {
|
||||
list: [
|
||||
exampleData.withdraw,
|
||||
exampleData.payment,
|
||||
exampleData.withdraw,
|
||||
exampleData.payment,
|
||||
exampleData.refresh,
|
||||
exampleData.refund,
|
||||
exampleData.tip,
|
||||
exampleData.deposit,
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "TESTKUDOS:10",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const SeveralWithTwoCurrencies = createExample(TestedComponent, {
|
||||
list: [
|
||||
exampleData.withdraw,
|
||||
exampleData.payment,
|
||||
exampleData.withdraw,
|
||||
exampleData.payment,
|
||||
exampleData.refresh,
|
||||
exampleData.refund,
|
||||
exampleData.tip,
|
||||
exampleData.deposit,
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "TESTKUDOS:10",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "USD:10",
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
@ -1,148 +0,0 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
(C) 2016 GNUnet e.V.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import {
|
||||
AmountString,
|
||||
Balance,
|
||||
i18n,
|
||||
Transaction,
|
||||
TransactionsResponse,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { ButtonPrimary } from "../components/styled";
|
||||
import { TransactionItem } from "../components/TransactionItem";
|
||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||
import * as wxApi from "../wxApi";
|
||||
import { AddNewActionView } from "./AddNewActionView";
|
||||
|
||||
export function HistoryPage(): VNode {
|
||||
const [transactions, setTransactions] = useState<
|
||||
TransactionsResponse | undefined
|
||||
>(undefined);
|
||||
const balance = useAsyncAsHook(wxApi.getBalance);
|
||||
const balanceWithoutError = balance?.hasError
|
||||
? []
|
||||
: balance?.response.balances || [];
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async (): Promise<void> => {
|
||||
const res = await wxApi.getTransactions();
|
||||
setTransactions(res);
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const [addingAction, setAddingAction] = useState(false);
|
||||
|
||||
if (addingAction) {
|
||||
return <AddNewActionView onCancel={() => setAddingAction(false)} />;
|
||||
}
|
||||
|
||||
if (!transactions) {
|
||||
return <div>Loading ...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<HistoryView
|
||||
balances={balanceWithoutError}
|
||||
list={[...transactions.transactions].reverse()}
|
||||
onAddNewAction={() => setAddingAction(true)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function amountToString(c: AmountString): string {
|
||||
const idx = c.indexOf(":");
|
||||
return `${c.substring(idx + 1)} ${c.substring(0, idx)}`;
|
||||
}
|
||||
|
||||
export function HistoryView({
|
||||
list,
|
||||
balances,
|
||||
onAddNewAction,
|
||||
}: {
|
||||
list: Transaction[];
|
||||
balances: Balance[];
|
||||
onAddNewAction: () => void;
|
||||
}): VNode {
|
||||
const multiCurrency = balances.length > 1;
|
||||
return (
|
||||
<Fragment>
|
||||
<header>
|
||||
{balances.length > 0 ? (
|
||||
<Fragment>
|
||||
{multiCurrency ? (
|
||||
<div class="title">
|
||||
Balance:{" "}
|
||||
<ul style={{ margin: 0 }}>
|
||||
{balances.map((b, i) => (
|
||||
<li key={i}>{b.available}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<div class="title">
|
||||
Balance: <span>{amountToString(balances[0].available)}</span>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<div>
|
||||
<ButtonPrimary onClick={onAddNewAction}>
|
||||
<b>+</b>
|
||||
</ButtonPrimary>
|
||||
</div>
|
||||
</header>
|
||||
{list.length === 0 ? (
|
||||
<section data-expanded data-centered>
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
You have no history yet, here you will be able to check your last
|
||||
transactions.
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
</section>
|
||||
) : (
|
||||
<section>
|
||||
{list.slice(0, 3).map((tx, i) => (
|
||||
<TransactionItem key={i} tx={tx} multiCurrency={multiCurrency} />
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
<footer style={{ justifyContent: "space-around" }}>
|
||||
{list.length > 0 && (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: "darkgreen", textDecoration: "none" }}
|
||||
href={
|
||||
// eslint-disable-next-line no-undef
|
||||
typeof chrome !== "undefined" && chrome.extension
|
||||
? // eslint-disable-next-line no-undef
|
||||
chrome.extension.getURL(`/static/wallet.html#/history`)
|
||||
: "#"
|
||||
}
|
||||
>
|
||||
VIEW MORE TRANSACTIONS
|
||||
</a>
|
||||
)}
|
||||
</footer>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
@ -19,11 +19,10 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import * as a1 from "./AddNewActionView.stories";
|
||||
import * as a1 from "../wallet/AddNewActionView.stories";
|
||||
import * as a2 from "./Balance.stories";
|
||||
import * as a3 from "./DeveloperPage.stories";
|
||||
import * as a4 from "./History.stories";
|
||||
import * as a5 from "./Popup.stories";
|
||||
import * as a6 from "./TalerActionFound.stories";
|
||||
|
||||
export default [a1, a2, a3, a4, a5, a6];
|
||||
export default [a1, a2, a3, a5, a6];
|
||||
|
@ -33,13 +33,13 @@ import { Pages, WalletNavBar } from "./NavigationBar";
|
||||
import { BackupPage } from "./wallet/BackupPage";
|
||||
import { BalancePage } from "./popup/BalancePage";
|
||||
import { DeveloperPage } from "./popup/DeveloperPage";
|
||||
import { HistoryPage } from "./popup/History";
|
||||
import { ProviderAddPage } from "./wallet/ProviderAddPage";
|
||||
import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
|
||||
import { SettingsPage } from "./popup/Settings";
|
||||
import { TalerActionFound } from "./popup/TalerActionFound";
|
||||
import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
|
||||
import { IoCProviderForRuntime } from "./context/iocContext";
|
||||
import { LastActivityPage } from "./wallet/LastActivityPage";
|
||||
|
||||
function main(): void {
|
||||
try {
|
||||
@ -77,12 +77,13 @@ function CheckTalerActionComponent(): VNode {
|
||||
|
||||
function Application() {
|
||||
return (
|
||||
<div>
|
||||
<DevContextProvider>
|
||||
// <div>
|
||||
<DevContextProvider>
|
||||
{({ devMode }: { devMode: boolean }) => (
|
||||
<IoCProviderForRuntime>
|
||||
<WalletNavBar />
|
||||
<WalletNavBar devMode={devMode} />
|
||||
<CheckTalerActionComponent />
|
||||
<PopupBox>
|
||||
<PopupBox devMode={devMode}>
|
||||
<Router history={createHashHistory()}>
|
||||
<Route path={Pages.dev} component={DeveloperPage} />
|
||||
|
||||
@ -90,10 +91,14 @@ function Application() {
|
||||
path={Pages.balance}
|
||||
component={BalancePage}
|
||||
goToWalletManualWithdraw={() =>
|
||||
goToWalletPage(Pages.manual_withdraw)
|
||||
goToWalletPage(
|
||||
Pages.manual_withdraw.replace(":currency?", ""),
|
||||
)
|
||||
}
|
||||
goToWalletDeposit={(currency: string) =>
|
||||
goToWalletPage(Pages.deposit.replace(":currency", currency))
|
||||
goToWalletHistory={(currency: string) =>
|
||||
goToWalletPage(
|
||||
Pages.balance_history.replace(":currency", currency),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route path={Pages.settings} component={SettingsPage} />
|
||||
@ -114,6 +119,8 @@ function Application() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route path={Pages.last_activity} component={LastActivityPage} />
|
||||
|
||||
<Route
|
||||
path={Pages.transaction}
|
||||
component={({ tid }: { tid: string }) =>
|
||||
@ -121,8 +128,6 @@ function Application() {
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path={Pages.history} component={HistoryPage} />
|
||||
|
||||
<Route
|
||||
path={Pages.backup}
|
||||
component={BackupPage}
|
||||
@ -157,8 +162,9 @@ function Application() {
|
||||
</Router>
|
||||
</PopupBox>
|
||||
</IoCProviderForRuntime>
|
||||
</DevContextProvider>
|
||||
</div>
|
||||
)}
|
||||
</DevContextProvider>
|
||||
// </div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -162,7 +162,12 @@ export function PageLink(props: {
|
||||
children?: ComponentChildren;
|
||||
}): VNode {
|
||||
// eslint-disable-next-line no-undef
|
||||
const url = chrome.extension.getURL(`/static/wallet.html#/${props.pageName}`);
|
||||
|
||||
const url =
|
||||
typeof chrome === "undefined"
|
||||
? undefined
|
||||
: // eslint-disable-next-line no-undef
|
||||
chrome.extension?.getURL(`/static/wallet.html#/${props.pageName}`);
|
||||
return (
|
||||
<a class="actionLink" href={url} target="_blank" rel="noopener noreferrer">
|
||||
{props.children}
|
||||
|
@ -117,5 +117,6 @@ export function mountBrowser<T>(callback: () => T, Context?: ({ children }: { ch
|
||||
}
|
||||
}
|
||||
|
||||
const nullTestFunction = {} as TestFunction
|
||||
export const justBrowser_it: PendingTestFunction | TestFunction =
|
||||
typeof window === 'undefined' ? it.skip : it
|
||||
typeof it === 'undefined' ? nullTestFunction : (typeof window === 'undefined' ? it.skip : it)
|
@ -23,7 +23,7 @@ import { createExample } from "../test-utils";
|
||||
import { AddNewActionView as TestedComponent } from "./AddNewActionView";
|
||||
|
||||
export default {
|
||||
title: "popup/add new action",
|
||||
title: "wallet/add new action",
|
||||
component: TestedComponent,
|
||||
argTypes: {
|
||||
setDeviceName: () => Promise.resolve(),
|
@ -19,7 +19,7 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { createExample, NullLink } from "../test-utils";
|
||||
import { createExample } from "../test-utils";
|
||||
import { BalanceView as TestedComponent } from "./BalancePage";
|
||||
|
||||
export default {
|
||||
@ -28,83 +28,124 @@ export default {
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
export const NotYetLoaded = createExample(TestedComponent, {});
|
||||
|
||||
export const GotError = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: true,
|
||||
message: "Network error",
|
||||
},
|
||||
Linker: NullLink,
|
||||
});
|
||||
|
||||
export const EmptyBalance = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [],
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
balances: [],
|
||||
});
|
||||
|
||||
export const SomeCoins = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:10.5",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:10.5",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
],
|
||||
});
|
||||
|
||||
export const SomeCoinsAndIncomingMoney = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:2.23",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:5.11",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
|
||||
balances: [
|
||||
{
|
||||
available: "EUR:1",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
{
|
||||
available: "TESTKUDOS:2000",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "JPY:4",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:15",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const SomeCoinsInTwoCurrencies = createExample(TestedComponent, {
|
||||
balance: {
|
||||
hasError: false,
|
||||
response: {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:2",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:5",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "EUR:4",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:5",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
export const NoCoinsInTreeCurrencies = createExample(TestedComponent, {
|
||||
balances: [
|
||||
{
|
||||
available: "EUR:3",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
},
|
||||
Linker: NullLink,
|
||||
{
|
||||
available: "USD:2",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "ARS:1",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:15",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const SomeCoinsInFiveCurrencies = createExample(TestedComponent, {
|
||||
balances: [
|
||||
{
|
||||
available: "USD:0",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "ARS:13451",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "EUR:202.02",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "JPY:0",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "JPY:51223233",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "EUR:0",
|
||||
pendingOutgoing: "EUR:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "DEMOKUDOS:6",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "TESTKUDOS:6",
|
||||
hasPendingTransactions: false,
|
||||
pendingIncoming: "USD:5",
|
||||
pendingOutgoing: "USD:0",
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -14,68 +14,87 @@
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
|
||||
import { Amounts, Balance, i18n } from "@gnu-taler/taler-util";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { BalanceTable } from "../components/BalanceTable";
|
||||
import { ButtonPrimary, Centered, ErrorBox } from "../components/styled";
|
||||
import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||
import { Loading } from "../components/Loading";
|
||||
import { MultiActionButton } from "../components/MultiActionButton";
|
||||
import {
|
||||
ButtonPrimary,
|
||||
Centered,
|
||||
ErrorBox,
|
||||
SuccessBox,
|
||||
} from "../components/styled";
|
||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||
import { PageLink } from "../renderHtml";
|
||||
import * as wxApi from "../wxApi";
|
||||
|
||||
interface Props {
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
goToWalletHistory: (currency: string) => void;
|
||||
goToWalletManualWithdraw: () => void;
|
||||
}
|
||||
|
||||
export function BalancePage({
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletDeposit,
|
||||
}: {
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
goToWalletManualWithdraw: () => void;
|
||||
}): VNode {
|
||||
goToWalletHistory,
|
||||
}: Props): VNode {
|
||||
const state = useAsyncAsHook(wxApi.getBalance);
|
||||
return (
|
||||
<BalanceView
|
||||
balance={state}
|
||||
Linker={PageLink}
|
||||
goToWalletManualWithdraw={goToWalletManualWithdraw}
|
||||
goToWalletDeposit={goToWalletDeposit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export interface BalanceViewProps {
|
||||
balance: HookResponse<BalancesResponse>;
|
||||
Linker: typeof PageLink;
|
||||
goToWalletManualWithdraw: () => void;
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
}
|
||||
const balances = !state || state.hasError ? [] : state.response.balances;
|
||||
|
||||
export function BalanceView({
|
||||
balance,
|
||||
Linker,
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletDeposit,
|
||||
}: BalanceViewProps): VNode {
|
||||
if (!balance) {
|
||||
return <div>Loading...</div>;
|
||||
if (!state) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (balance.hasError) {
|
||||
if (state.hasError) {
|
||||
return (
|
||||
<Fragment>
|
||||
<ErrorBox>{balance.message}</ErrorBox>
|
||||
<ErrorBox>{state.message}</ErrorBox>
|
||||
<p>
|
||||
Click <Linker pageName="welcome">here</Linker> for help and
|
||||
Click <PageLink pageName="welcome">here</PageLink> for help and
|
||||
diagnostics.
|
||||
</p>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
if (balance.response.balances.length === 0) {
|
||||
|
||||
return (
|
||||
<BalanceView
|
||||
balances={balances}
|
||||
goToWalletManualWithdraw={goToWalletManualWithdraw}
|
||||
goToWalletDeposit={goToWalletDeposit}
|
||||
goToWalletHistory={goToWalletHistory}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export interface BalanceViewProps {
|
||||
balances: Balance[];
|
||||
goToWalletManualWithdraw: () => void;
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
goToWalletHistory: (currency: string) => void;
|
||||
}
|
||||
|
||||
export function BalanceView({
|
||||
balances,
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletDeposit,
|
||||
goToWalletHistory,
|
||||
}: BalanceViewProps): VNode {
|
||||
const currencyWithNonZeroAmount = balances
|
||||
.filter((b) => !Amounts.isZero(b.available))
|
||||
.map((b) => b.available.split(":")[0]);
|
||||
|
||||
if (balances.length === 0) {
|
||||
return (
|
||||
<Fragment>
|
||||
<p>
|
||||
<Centered style={{ marginTop: 100 }}>
|
||||
<i18n.Translate>
|
||||
You have no balance to show. Need some{" "}
|
||||
<Linker pageName="/welcome">help</Linker> getting started?
|
||||
<PageLink pageName="/welcome">help</PageLink> getting started?
|
||||
</i18n.Translate>
|
||||
</Centered>
|
||||
</p>
|
||||
@ -93,15 +112,21 @@ export function BalanceView({
|
||||
<Fragment>
|
||||
<section>
|
||||
<BalanceTable
|
||||
balances={balance.response.balances}
|
||||
goToWalletDeposit={goToWalletDeposit}
|
||||
balances={balances}
|
||||
goToWalletHistory={goToWalletHistory}
|
||||
/>
|
||||
</section>
|
||||
<footer style={{ justifyContent: "space-between" }}>
|
||||
<div />
|
||||
<ButtonPrimary onClick={goToWalletManualWithdraw}>
|
||||
Withdraw
|
||||
</ButtonPrimary>
|
||||
{currencyWithNonZeroAmount.length > 0 && (
|
||||
<MultiActionButton
|
||||
label={(s) => `Deposit ${s}`}
|
||||
actions={currencyWithNonZeroAmount}
|
||||
onClick={(c) => goToWalletDeposit(c)}
|
||||
/>
|
||||
)}
|
||||
</footer>
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -41,12 +41,14 @@ export interface Props {
|
||||
exchangeList: Record<string, string>;
|
||||
onCreate: (exchangeBaseUrl: string, amount: AmountJson) => Promise<void>;
|
||||
onAddExchange: () => void;
|
||||
initialCurrency?: string;
|
||||
}
|
||||
|
||||
export function CreateManualWithdraw({
|
||||
initialAmount,
|
||||
exchangeList,
|
||||
error,
|
||||
initialCurrency,
|
||||
onCreate,
|
||||
onAddExchange,
|
||||
}: Props): VNode {
|
||||
@ -61,8 +63,16 @@ export function CreateManualWithdraw({
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
|
||||
const foundExchangeForCurrency = exchangeSelectList.findIndex(
|
||||
(e) => exchangeList[e] === initialCurrency,
|
||||
);
|
||||
|
||||
const initialExchange =
|
||||
exchangeSelectList.length > 0 ? exchangeSelectList[0] : "";
|
||||
foundExchangeForCurrency !== -1
|
||||
? exchangeSelectList[foundExchangeForCurrency]
|
||||
: exchangeSelectList.length > 0
|
||||
? exchangeSelectList[0]
|
||||
: "";
|
||||
|
||||
const [exchange, setExchange] = useState(initialExchange || "");
|
||||
const [currency, setCurrency] = useState(exchangeList[initialExchange] ?? "");
|
||||
|
@ -23,23 +23,24 @@ import {
|
||||
import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { Part } from "../components/Part";
|
||||
import { Loading } from "../components/Loading";
|
||||
import { SelectList } from "../components/SelectList";
|
||||
import {
|
||||
ButtonBoxWarning,
|
||||
ButtonPrimary,
|
||||
ErrorText,
|
||||
Input,
|
||||
InputWithLabel,
|
||||
WarningBox,
|
||||
} from "../components/styled";
|
||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||
import * as wxApi from "../wxApi";
|
||||
|
||||
interface Props {
|
||||
currency: string;
|
||||
onSuccess: (currency: string) => void;
|
||||
}
|
||||
export function DepositPage({ currency }: Props): VNode {
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
export function DepositPage({ currency, onSuccess }: Props): VNode {
|
||||
const state = useAsyncAsHook(async () => {
|
||||
const balance = await wxApi.getBalance();
|
||||
const bs = balance.balances.filter((b) => b.available.startsWith(currency));
|
||||
@ -63,7 +64,7 @@ export function DepositPage({ currency }: Props): VNode {
|
||||
|
||||
async function doSend(account: string, amount: AmountString): Promise<void> {
|
||||
await wxApi.createDepositGroup(account, amount);
|
||||
setSuccess(true);
|
||||
onSuccess(currency);
|
||||
}
|
||||
|
||||
async function getFeeForAmount(
|
||||
@ -73,8 +74,8 @@ export function DepositPage({ currency }: Props): VNode {
|
||||
return await wxApi.getFeeForDeposit(account, amount);
|
||||
}
|
||||
|
||||
if (accounts.length === 0) return <div>loading..</div>;
|
||||
if (success) return <div>deposit created</div>;
|
||||
if (accounts.length === 0) return <Loading />;
|
||||
|
||||
return (
|
||||
<View
|
||||
knownBankAccounts={accounts}
|
||||
@ -105,8 +106,17 @@ export function View({
|
||||
const [accountIdx, setAccountIdx] = useState(0);
|
||||
const [amount, setAmount] = useState<number | undefined>(undefined);
|
||||
const [fee, setFee] = useState<DepositFee | undefined>(undefined);
|
||||
function updateAmount(num: number | undefined) {
|
||||
setAmount(num);
|
||||
setFee(undefined);
|
||||
}
|
||||
const feeHasBeenCalculated = fee !== undefined;
|
||||
const currency = balance.currency;
|
||||
const amountStr: AmountString = `${currency}:${amount}`;
|
||||
const feeSum =
|
||||
fee !== undefined
|
||||
? Amounts.sum([fee.wire, fee.coin, fee.refresh]).amount
|
||||
: Amounts.getZero(currency);
|
||||
|
||||
const account = knownBankAccounts.length
|
||||
? knownBankAccounts[accountIdx]
|
||||
@ -126,7 +136,12 @@ export function View({
|
||||
return <div>no balance</div>;
|
||||
}
|
||||
if (!knownBankAccounts || !knownBankAccounts.length) {
|
||||
return <div>there is no known bank account to send money to</div>;
|
||||
return (
|
||||
<WarningBox>
|
||||
<p>There is no known bank account to send money to</p>
|
||||
<ButtonBoxWarning>Withdraw</ButtonBoxWarning>
|
||||
</WarningBox>
|
||||
);
|
||||
}
|
||||
const parsedAmount =
|
||||
amount === undefined ? undefined : Amounts.parse(amountStr);
|
||||
@ -136,9 +151,16 @@ export function View({
|
||||
: !parsedAmount
|
||||
? "Invalid amount"
|
||||
: Amounts.cmp(balance, parsedAmount) === -1
|
||||
? `To much, your current balance is ${balance.value}`
|
||||
? `To much, your current balance is ${Amounts.stringifyValue(balance)}`
|
||||
: undefined;
|
||||
|
||||
const totalToDeposit = parsedAmount
|
||||
? Amounts.sub(parsedAmount, feeSum).amount
|
||||
: Amounts.getZero(currency);
|
||||
|
||||
const unableToDeposit =
|
||||
Amounts.isZero(totalToDeposit) && feeHasBeenCalculated;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h2>Send {currency} to your account</h2>
|
||||
@ -153,7 +175,7 @@ export function View({
|
||||
/>
|
||||
</Input>
|
||||
<InputWithLabel invalid={!!error}>
|
||||
<label>Amount to send</label>
|
||||
<label>Amount</label>
|
||||
<div>
|
||||
<span>{currency}</span>
|
||||
<input
|
||||
@ -161,11 +183,10 @@ export function View({
|
||||
value={amount}
|
||||
onInput={(e) => {
|
||||
const num = parseFloat(e.currentTarget.value);
|
||||
console.log(num);
|
||||
if (!Number.isNaN(num)) {
|
||||
setAmount(num);
|
||||
updateAmount(num);
|
||||
} else {
|
||||
setAmount(undefined);
|
||||
updateAmount(undefined);
|
||||
setFee(undefined);
|
||||
}
|
||||
}}
|
||||
@ -173,40 +194,41 @@ export function View({
|
||||
</div>
|
||||
{error && <ErrorText>{error}</ErrorText>}
|
||||
</InputWithLabel>
|
||||
{!error && fee && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Part
|
||||
title="Exchange fee"
|
||||
text={Amounts.stringify(Amounts.sum([fee.wire, fee.coin]).amount)}
|
||||
kind="negative"
|
||||
/>
|
||||
<Part
|
||||
title="Change cost"
|
||||
text={Amounts.stringify(fee.refresh)}
|
||||
kind="negative"
|
||||
/>
|
||||
{parsedAmount && (
|
||||
<Part
|
||||
title="Total received"
|
||||
text={Amounts.stringify(
|
||||
Amounts.sub(
|
||||
parsedAmount,
|
||||
Amounts.sum([fee.wire, fee.coin]).amount,
|
||||
).amount,
|
||||
)}
|
||||
kind="positive"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
<Fragment>
|
||||
<InputWithLabel>
|
||||
<label>Deposit fee</label>
|
||||
<div>
|
||||
<span>{currency}</span>
|
||||
<input
|
||||
type="number"
|
||||
disabled
|
||||
value={Amounts.stringifyValue(feeSum)}
|
||||
/>
|
||||
</div>
|
||||
</InputWithLabel>
|
||||
|
||||
<InputWithLabel>
|
||||
<label>Total deposit</label>
|
||||
<div>
|
||||
<span>{currency}</span>
|
||||
<input
|
||||
type="number"
|
||||
disabled
|
||||
value={Amounts.stringifyValue(totalToDeposit)}
|
||||
/>
|
||||
</div>
|
||||
</InputWithLabel>
|
||||
</Fragment>
|
||||
}
|
||||
</section>
|
||||
<footer>
|
||||
<div />
|
||||
<ButtonPrimary
|
||||
disabled={!parsedAmount}
|
||||
disabled={unableToDeposit}
|
||||
onClick={() => onSend(accountURI, amountStr)}
|
||||
>
|
||||
Send
|
||||
Deposit {Amounts.stringifyValue(totalToDeposit)} {currency}
|
||||
</ButtonPrimary>
|
||||
</footer>
|
||||
</Fragment>
|
||||
|
@ -35,7 +35,7 @@ import { HistoryView as TestedComponent } from "./History";
|
||||
import { createExample } from "../test-utils";
|
||||
|
||||
export default {
|
||||
title: "wallet/history/list",
|
||||
title: "wallet/balance/history",
|
||||
component: TestedComponent,
|
||||
};
|
||||
|
||||
@ -114,8 +114,13 @@ const exampleData = {
|
||||
} as TransactionRefund,
|
||||
};
|
||||
|
||||
export const Empty = createExample(TestedComponent, {
|
||||
list: [],
|
||||
export const NoBalance = createExample(TestedComponent, {
|
||||
transactions: [],
|
||||
balances: [],
|
||||
});
|
||||
|
||||
export const SomeBalanceWithNoTransactions = createExample(TestedComponent, {
|
||||
transactions: [],
|
||||
balances: [
|
||||
{
|
||||
available: "TESTKUDOS:10",
|
||||
@ -127,13 +132,8 @@ export const Empty = createExample(TestedComponent, {
|
||||
],
|
||||
});
|
||||
|
||||
export const EmptyWithNoBalance = createExample(TestedComponent, {
|
||||
list: [],
|
||||
balances: [],
|
||||
});
|
||||
|
||||
export const One = createExample(TestedComponent, {
|
||||
list: [exampleData.withdraw],
|
||||
export const OneSimpleTransaction = createExample(TestedComponent, {
|
||||
transactions: [exampleData.withdraw],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:10",
|
||||
@ -145,8 +145,21 @@ export const One = createExample(TestedComponent, {
|
||||
],
|
||||
});
|
||||
|
||||
export const OnePending = createExample(TestedComponent, {
|
||||
list: [
|
||||
export const TwoTransactionsAndZeroBalance = createExample(TestedComponent, {
|
||||
transactions: [exampleData.withdraw, exampleData.deposit],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:0",
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const OneTransactionPending = createExample(TestedComponent, {
|
||||
transactions: [
|
||||
{
|
||||
...exampleData.withdraw,
|
||||
pending: true,
|
||||
@ -163,8 +176,8 @@ export const OnePending = createExample(TestedComponent, {
|
||||
],
|
||||
});
|
||||
|
||||
export const Several = createExample(TestedComponent, {
|
||||
list: [
|
||||
export const SomeTransactions = createExample(TestedComponent, {
|
||||
transactions: [
|
||||
exampleData.withdraw,
|
||||
exampleData.payment,
|
||||
exampleData.withdraw,
|
||||
@ -182,35 +195,6 @@ export const Several = createExample(TestedComponent, {
|
||||
exampleData.deposit,
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "TESTKUDOS:10",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const SeveralWithTwoCurrencies = createExample(TestedComponent, {
|
||||
list: [
|
||||
exampleData.withdraw,
|
||||
exampleData.payment,
|
||||
exampleData.withdraw,
|
||||
exampleData.payment,
|
||||
exampleData.refresh,
|
||||
exampleData.refund,
|
||||
exampleData.tip,
|
||||
exampleData.deposit,
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "TESTKUDOS:10",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "USD:10",
|
||||
pendingIncoming: "USD:0",
|
||||
@ -220,3 +204,76 @@ export const SeveralWithTwoCurrencies = createExample(TestedComponent, {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const SomeTransactionsWithTwoCurrencies = createExample(
|
||||
TestedComponent,
|
||||
{
|
||||
transactions: [
|
||||
exampleData.withdraw,
|
||||
exampleData.payment,
|
||||
exampleData.withdraw,
|
||||
exampleData.payment,
|
||||
exampleData.refresh,
|
||||
exampleData.refund,
|
||||
exampleData.tip,
|
||||
exampleData.deposit,
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:0",
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "TESTKUDOS:10",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
export const FiveOfficialCurrencies = createExample(TestedComponent, {
|
||||
transactions: [exampleData.withdraw],
|
||||
balances: [
|
||||
{
|
||||
available: "USD:1000",
|
||||
pendingIncoming: "USD:0",
|
||||
pendingOutgoing: "USD:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "EUR:881",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "COL:4043000.5",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "JPY:11564450.6",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
{
|
||||
available: "GBP:736",
|
||||
pendingIncoming: "TESTKUDOS:0",
|
||||
pendingOutgoing: "TESTKUDOS:0",
|
||||
hasPendingTransactions: false,
|
||||
requiresUserInput: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -15,21 +15,38 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AmountString,
|
||||
Amounts,
|
||||
Balance,
|
||||
NotificationType,
|
||||
Transaction,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { ButtonPrimary, DateSeparator } from "../components/styled";
|
||||
import { Loading } from "../components/Loading";
|
||||
import {
|
||||
ButtonBoxPrimary,
|
||||
ButtonBoxWarning,
|
||||
ButtonPrimary,
|
||||
DateSeparator,
|
||||
ErrorBox,
|
||||
NiceSelect,
|
||||
WarningBox,
|
||||
} from "../components/styled";
|
||||
import { Time } from "../components/Time";
|
||||
import { TransactionItem } from "../components/TransactionItem";
|
||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||
import { AddNewActionView } from "../popup/AddNewActionView";
|
||||
import * as wxApi from "../wxApi";
|
||||
|
||||
export function HistoryPage(): VNode {
|
||||
interface Props {
|
||||
currency?: string;
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
goToWalletManualWithdraw: (currency?: string) => void;
|
||||
}
|
||||
export function HistoryPage({
|
||||
currency,
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletDeposit,
|
||||
}: Props): VNode {
|
||||
const balance = useAsyncAsHook(wxApi.getBalance);
|
||||
const balanceWithoutError = balance?.hasError
|
||||
? []
|
||||
@ -39,110 +56,166 @@ export function HistoryPage(): VNode {
|
||||
NotificationType.WithdrawGroupFinished,
|
||||
]);
|
||||
|
||||
const [addingAction, setAddingAction] = useState(false);
|
||||
|
||||
if (addingAction) {
|
||||
return <AddNewActionView onCancel={() => setAddingAction(false)} />;
|
||||
if (!transactionQuery || !balance) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!transactionQuery) {
|
||||
return <div>Loading ...</div>;
|
||||
}
|
||||
if (transactionQuery.hasError) {
|
||||
return <div>There was an error loading the transactions.</div>;
|
||||
return (
|
||||
<Fragment>
|
||||
<ErrorBox>{transactionQuery.message}</ErrorBox>
|
||||
<p>There was an error loading the transactions.</p>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HistoryView
|
||||
balances={balanceWithoutError}
|
||||
list={[...transactionQuery.response.transactions].reverse()}
|
||||
onAddNewAction={() => setAddingAction(true)}
|
||||
defaultCurrency={currency}
|
||||
goToWalletManualWithdraw={goToWalletManualWithdraw}
|
||||
goToWalletDeposit={goToWalletDeposit}
|
||||
transactions={[...transactionQuery.response.transactions].reverse()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function amountToString(c: AmountString): string {
|
||||
const idx = c.indexOf(":");
|
||||
return `${c.substring(idx + 1)} ${c.substring(0, idx)}`;
|
||||
}
|
||||
|
||||
const term = 1000 * 60 * 60 * 24;
|
||||
function normalizeToDay(x: number): number {
|
||||
return Math.round(x / term) * term;
|
||||
}
|
||||
|
||||
export function HistoryView({
|
||||
list,
|
||||
defaultCurrency,
|
||||
transactions,
|
||||
balances,
|
||||
onAddNewAction,
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletDeposit,
|
||||
}: {
|
||||
list: Transaction[];
|
||||
goToWalletDeposit: (currency: string) => void;
|
||||
goToWalletManualWithdraw: (currency?: string) => void;
|
||||
defaultCurrency?: string;
|
||||
transactions: Transaction[];
|
||||
balances: Balance[];
|
||||
onAddNewAction: () => void;
|
||||
}): VNode {
|
||||
const byDate = list.reduce((rv, x) => {
|
||||
const theDate =
|
||||
x.timestamp.t_ms === "never" ? 0 : normalizeToDay(x.timestamp.t_ms);
|
||||
if (theDate) {
|
||||
(rv[theDate] = rv[theDate] || []).push(x);
|
||||
}
|
||||
const currencies = balances.map((b) => b.available.split(":")[0]);
|
||||
|
||||
return rv;
|
||||
}, {} as { [x: string]: Transaction[] });
|
||||
const defaultCurrencyIndex = currencies.findIndex(
|
||||
(c) => c === defaultCurrency,
|
||||
);
|
||||
const [currencyIndex, setCurrencyIndex] = useState(
|
||||
defaultCurrencyIndex === -1 ? 0 : defaultCurrencyIndex,
|
||||
);
|
||||
const selectedCurrency =
|
||||
currencies.length > 0 ? currencies[currencyIndex] : undefined;
|
||||
|
||||
const currencyAmount = balances[currencyIndex]
|
||||
? Amounts.jsonifyAmount(balances[currencyIndex].available)
|
||||
: undefined;
|
||||
|
||||
const byDate = transactions
|
||||
.filter((t) => t.amountRaw.split(":")[0] === selectedCurrency)
|
||||
.reduce((rv, x) => {
|
||||
const theDate =
|
||||
x.timestamp.t_ms === "never" ? 0 : normalizeToDay(x.timestamp.t_ms);
|
||||
if (theDate) {
|
||||
(rv[theDate] = rv[theDate] || []).push(x);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}, {} as { [x: string]: Transaction[] });
|
||||
const datesWithTransaction = Object.keys(byDate);
|
||||
|
||||
const multiCurrency = balances.length > 1;
|
||||
|
||||
if (balances.length === 0 || !selectedCurrency) {
|
||||
return (
|
||||
<WarningBox>
|
||||
<p>
|
||||
You have <b>no balance</b>. Withdraw some founds into your wallet
|
||||
</p>
|
||||
<ButtonBoxWarning onClick={() => goToWalletManualWithdraw()}>
|
||||
Withdraw
|
||||
</ButtonBoxWarning>
|
||||
</WarningBox>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
<header>
|
||||
{balances.length > 0 ? (
|
||||
<Fragment>
|
||||
{balances.length === 1 && (
|
||||
<div class="title">
|
||||
Balance: <span>{amountToString(balances[0].available)}</span>
|
||||
</div>
|
||||
)}
|
||||
{balances.length > 1 && (
|
||||
<div class="title">
|
||||
Balance:{" "}
|
||||
<ul style={{ margin: 0 }}>
|
||||
{balances.map((b, i) => (
|
||||
<li key={i}>{b.available}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<div>
|
||||
<ButtonPrimary onClick={onAddNewAction}>
|
||||
<b>+</b>
|
||||
</ButtonPrimary>
|
||||
</div>
|
||||
</header>
|
||||
<section>
|
||||
{Object.keys(byDate).map((d, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<DateSeparator>
|
||||
<Time
|
||||
timestamp={{ t_ms: Number.parseInt(d, 10) }}
|
||||
format="dd MMMM yyyy"
|
||||
/>
|
||||
</DateSeparator>
|
||||
{byDate[d].map((tx, i) => (
|
||||
<TransactionItem
|
||||
key={i}
|
||||
tx={tx}
|
||||
multiCurrency={multiCurrency}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<p
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{currencies.length === 1 ? (
|
||||
<div style={{ fontSize: "large" }}>{selectedCurrency}</div>
|
||||
) : (
|
||||
<NiceSelect>
|
||||
<select
|
||||
value={currencyIndex}
|
||||
onChange={(e) => {
|
||||
setCurrencyIndex(Number(e.currentTarget.value));
|
||||
}}
|
||||
>
|
||||
{currencies.map((currency, index) => {
|
||||
return (
|
||||
<option value={index} key={currency}>
|
||||
{currency}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</NiceSelect>
|
||||
)}
|
||||
{currencyAmount && (
|
||||
<h2 style={{ margin: 0 }}>
|
||||
{Amounts.stringifyValue(currencyAmount)}
|
||||
</h2>
|
||||
)}
|
||||
</p>
|
||||
<div style={{ marginLeft: "auto", width: "fit-content" }}>
|
||||
<ButtonPrimary
|
||||
onClick={() => goToWalletManualWithdraw(selectedCurrency)}
|
||||
>
|
||||
Withdraw
|
||||
</ButtonPrimary>
|
||||
{currencyAmount && Amounts.isNonZero(currencyAmount) && (
|
||||
<ButtonBoxPrimary
|
||||
onClick={() => goToWalletDeposit(selectedCurrency)}
|
||||
>
|
||||
Deposit
|
||||
</ButtonBoxPrimary>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
{datesWithTransaction.length === 0 ? (
|
||||
<section>There is no history for this currency</section>
|
||||
) : (
|
||||
<section>
|
||||
{datesWithTransaction.map((d, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<DateSeparator>
|
||||
<Time
|
||||
timestamp={{ t_ms: Number.parseInt(d, 10) }}
|
||||
format="dd MMMM yyyy"
|
||||
/>
|
||||
</DateSeparator>
|
||||
{byDate[d].map((tx, i) => (
|
||||
<TransactionItem
|
||||
key={i}
|
||||
tx={tx}
|
||||
multiCurrency={multiCurrency}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2021 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { createExample } from "../test-utils";
|
||||
import { queryToSlashKeys } from "../utils/index";
|
||||
import { LastActivityPage as TestedComponent } from "./LastActivityPage";
|
||||
|
||||
export default {
|
||||
title: "wallet/last activity",
|
||||
component: TestedComponent,
|
||||
};
|
||||
|
||||
export const InitialState = createExample(TestedComponent, {
|
||||
onVerify: queryToSlashKeys,
|
||||
});
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
(C) 2016 GNUnet e.V.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { ButtonPrimary } from "../components/styled";
|
||||
import { AddNewActionView } from "./AddNewActionView";
|
||||
|
||||
export function LastActivityPage(): VNode {
|
||||
const [addingAction, setAddingAction] = useState(false);
|
||||
|
||||
if (addingAction) {
|
||||
return <AddNewActionView onCancel={() => setAddingAction(false)} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div />
|
||||
<ButtonPrimary onClick={() => setAddingAction(true)}>+</ButtonPrimary>
|
||||
</section>
|
||||
);
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { VNode, h } from "preact";
|
||||
import { VNode, h, Fragment } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { CreateManualWithdraw } from "./CreateManualWithdraw";
|
||||
import * as wxApi from "../wxApi";
|
||||
@ -29,8 +29,10 @@ import { route } from "preact-router";
|
||||
import { Pages } from "../NavigationBar";
|
||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||
import { ExchangeAddPage } from "./ExchangeAddPage";
|
||||
import { Loading } from "../components/Loading";
|
||||
import { ErrorBox } from "../components/styled";
|
||||
|
||||
export function ManualWithdrawPage(): VNode {
|
||||
export function ManualWithdrawPage({ currency }: { currency?: string }): VNode {
|
||||
const [success, setSuccess] = useState<
|
||||
| {
|
||||
response: AcceptManualWithdrawalResult;
|
||||
@ -86,10 +88,15 @@ export function ManualWithdrawPage(): VNode {
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
return <div>loading...</div>;
|
||||
return <Loading />;
|
||||
}
|
||||
if (state.hasError) {
|
||||
return <div>There was an error getting the known exchanges</div>;
|
||||
return (
|
||||
<Fragment>
|
||||
<ErrorBox>{state.message}</ErrorBox>
|
||||
<p>There was an error getting the known exchanges</p>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
const exchangeList = state.response.exchanges.reduce(
|
||||
(p, c) => ({
|
||||
@ -105,6 +112,7 @@ export function ManualWithdrawPage(): VNode {
|
||||
error={error}
|
||||
exchangeList={exchangeList}
|
||||
onCreate={doCreate}
|
||||
initialCurrency={currency}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ export function TransactionPage({ tid }: { tid: string }): VNode {
|
||||
}
|
||||
|
||||
if (state.hasError) {
|
||||
route(Pages.history);
|
||||
route(Pages.balance);
|
||||
return (
|
||||
<div>
|
||||
<i18n.Translate>
|
||||
@ -84,7 +84,16 @@ export function TransactionPage({ tid }: { tid: string }): VNode {
|
||||
}
|
||||
|
||||
function goToHistory(): void {
|
||||
route(Pages.history);
|
||||
const currency =
|
||||
state !== undefined && !state.hasError
|
||||
? Amounts.parseOrThrow(state.response.amountRaw).currency
|
||||
: undefined;
|
||||
|
||||
if (currency) {
|
||||
route(Pages.balance_history.replace(":currency", currency));
|
||||
} else {
|
||||
route(Pages.balance);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -33,5 +33,22 @@ import * as a11 from "./ReserveCreated.stories";
|
||||
import * as a12 from "./Settings.stories";
|
||||
import * as a13 from "./Transaction.stories";
|
||||
import * as a14 from "./Welcome.stories";
|
||||
import * as a15 from "./AddNewActionView.stories";
|
||||
|
||||
export default [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14];
|
||||
export default [
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
a4,
|
||||
a5,
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
a13,
|
||||
a14,
|
||||
a15,
|
||||
];
|
||||
|
@ -22,31 +22,32 @@
|
||||
|
||||
import { setupI18n } from "@gnu-taler/taler-util";
|
||||
import { createHashHistory } from "history";
|
||||
import { Fragment, h, render, VNode } from "preact";
|
||||
import { h, render, VNode } from "preact";
|
||||
import Router, { route, Route } from "preact-router";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { LogoHeader } from "./components/LogoHeader";
|
||||
import { SuccessBox, WalletBox } from "./components/styled";
|
||||
import { DevContextProvider } from "./context/devContext";
|
||||
import { IoCProviderForRuntime } from "./context/iocContext";
|
||||
import { PayPage } from "./cta/Pay";
|
||||
import { RefundPage } from "./cta/Refund";
|
||||
import { TipPage } from "./cta/Tip";
|
||||
import { WithdrawPage } from "./cta/Withdraw";
|
||||
import { strings } from "./i18n/strings";
|
||||
import { Pages, WalletNavBar } from "./NavigationBar";
|
||||
import { DeveloperPage } from "./popup/DeveloperPage";
|
||||
import { BackupPage } from "./wallet/BackupPage";
|
||||
import { BalancePage } from "./wallet/BalancePage";
|
||||
import { DepositPage } from "./wallet/DepositPage";
|
||||
import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
|
||||
import { HistoryPage } from "./wallet/History";
|
||||
import { LastActivityPage } from "./wallet/LastActivityPage";
|
||||
import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage";
|
||||
import { ProviderAddPage } from "./wallet/ProviderAddPage";
|
||||
import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
|
||||
import { SettingsPage } from "./wallet/Settings";
|
||||
import { TransactionPage } from "./wallet/Transaction";
|
||||
import { WelcomePage } from "./wallet/Welcome";
|
||||
import { BackupPage } from "./wallet/BackupPage";
|
||||
import { DeveloperPage } from "./popup/DeveloperPage";
|
||||
import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage";
|
||||
import { WalletBox } from "./components/styled";
|
||||
import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
|
||||
import { ProviderAddPage } from "./wallet/ProviderAddPage";
|
||||
import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
|
||||
import { DepositPage } from "./wallet/DepositPage";
|
||||
import { IoCProviderForRuntime } from "./context/iocContext";
|
||||
|
||||
function main(): void {
|
||||
try {
|
||||
@ -71,140 +72,156 @@ if (document.readyState === "loading") {
|
||||
main();
|
||||
}
|
||||
|
||||
function withLogoAndNavBar(Component: any) {
|
||||
return function withLogoAndNavBarComponent(props: any): VNode {
|
||||
return (
|
||||
<Fragment>
|
||||
<LogoHeader />
|
||||
<WalletNavBar />
|
||||
<WalletBox>
|
||||
<Component {...props} />
|
||||
</WalletBox>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function Application(): VNode {
|
||||
const [globalNotification, setGlobalNotification] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
return (
|
||||
<div>
|
||||
<DevContextProvider>
|
||||
<IoCProviderForRuntime>
|
||||
<Router history={createHashHistory()}>
|
||||
<Route
|
||||
path={Pages.welcome}
|
||||
component={withLogoAndNavBar(WelcomePage)}
|
||||
/>
|
||||
{({ devMode }: { devMode: boolean }) => (
|
||||
<IoCProviderForRuntime>
|
||||
<LogoHeader />
|
||||
<WalletNavBar devMode={devMode} />
|
||||
<WalletBox>
|
||||
{globalNotification && (
|
||||
<SuccessBox onClick={() => setGlobalNotification(undefined)}>
|
||||
<div>{globalNotification}</div>
|
||||
</SuccessBox>
|
||||
)}
|
||||
<Router history={createHashHistory()}>
|
||||
<Route path={Pages.welcome} component={WelcomePage} />
|
||||
|
||||
<Route
|
||||
path={Pages.history}
|
||||
component={withLogoAndNavBar(HistoryPage)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.transaction}
|
||||
component={withLogoAndNavBar(TransactionPage)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.balance}
|
||||
component={withLogoAndNavBar(BalancePage)}
|
||||
goToWalletManualWithdraw={() => route(Pages.manual_withdraw)}
|
||||
goToWalletDeposit={(currency: string) =>
|
||||
route(Pages.deposit.replace(":currency", currency))
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.settings}
|
||||
component={withLogoAndNavBar(SettingsPage)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.backup}
|
||||
component={withLogoAndNavBar(BackupPage)}
|
||||
onAddProvider={() => {
|
||||
route(Pages.provider_add);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.provider_detail}
|
||||
component={withLogoAndNavBar(ProviderDetailPage)}
|
||||
onBack={() => {
|
||||
route(Pages.backup);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.provider_add}
|
||||
component={withLogoAndNavBar(ProviderAddPage)}
|
||||
onBack={() => {
|
||||
route(Pages.backup);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.balance}
|
||||
component={BalancePage}
|
||||
goToWalletManualWithdraw={() =>
|
||||
route(Pages.manual_withdraw.replace(":currency?", ""))
|
||||
}
|
||||
goToWalletDeposit={(currency: string) =>
|
||||
route(Pages.deposit.replace(":currency", currency))
|
||||
}
|
||||
goToWalletHistory={(currency: string) =>
|
||||
route(Pages.balance_history.replace(":currency", currency))
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.balance_history}
|
||||
component={HistoryPage}
|
||||
goToWalletDeposit={(currency: string) =>
|
||||
route(Pages.deposit.replace(":currency", currency))
|
||||
}
|
||||
goToWalletManualWithdraw={(currency?: string) =>
|
||||
route(
|
||||
Pages.manual_withdraw.replace(
|
||||
":currency?",
|
||||
currency || "",
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.last_activity}
|
||||
component={LastActivityPage}
|
||||
/>
|
||||
<Route path={Pages.transaction} component={TransactionPage} />
|
||||
<Route path={Pages.settings} component={SettingsPage} />
|
||||
<Route
|
||||
path={Pages.backup}
|
||||
component={BackupPage}
|
||||
onAddProvider={() => {
|
||||
route(Pages.provider_add);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.provider_detail}
|
||||
component={ProviderDetailPage}
|
||||
onBack={() => {
|
||||
route(Pages.backup);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.provider_add}
|
||||
component={ProviderAddPage}
|
||||
onBack={() => {
|
||||
route(Pages.backup);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Pages.exchange_add}
|
||||
component={withLogoAndNavBar(ExchangeAddPage)}
|
||||
onBack={() => {
|
||||
route(Pages.balance);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.exchange_add}
|
||||
component={ExchangeAddPage}
|
||||
onBack={() => {
|
||||
route(Pages.balance);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Pages.manual_withdraw}
|
||||
component={withLogoAndNavBar(ManualWithdrawPage)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.manual_withdraw}
|
||||
component={ManualWithdrawPage}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Pages.deposit}
|
||||
component={withLogoAndNavBar(DepositPage)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.reset_required}
|
||||
component={() => <div>no yet implemented</div>}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.payback}
|
||||
component={() => <div>no yet implemented</div>}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.return_coins}
|
||||
component={() => <div>no yet implemented</div>}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.deposit}
|
||||
component={DepositPage}
|
||||
onSuccess={(currency: string) => {
|
||||
route(Pages.balance_history.replace(":currency", currency));
|
||||
setGlobalNotification(
|
||||
"All done, your transaction is in progress",
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.reset_required}
|
||||
component={() => <div>no yet implemented</div>}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.payback}
|
||||
component={() => <div>no yet implemented</div>}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.return_coins}
|
||||
component={() => <div>no yet implemented</div>}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Pages.dev}
|
||||
component={withLogoAndNavBar(DeveloperPage)}
|
||||
/>
|
||||
<Route path={Pages.dev} component={DeveloperPage} />
|
||||
|
||||
{/** call to action */}
|
||||
<Route
|
||||
path={Pages.pay}
|
||||
component={PayPage}
|
||||
goToWalletManualWithdraw={() =>
|
||||
goToWalletPage(Pages.manual_withdraw)
|
||||
}
|
||||
/>
|
||||
<Route path={Pages.refund} component={RefundPage} />
|
||||
<Route path={Pages.tips} component={TipPage} />
|
||||
<Route path={Pages.withdraw} component={WithdrawPage} />
|
||||
{/** call to action */}
|
||||
<Route
|
||||
path={Pages.pay}
|
||||
component={PayPage}
|
||||
goToWalletManualWithdraw={(currency?: string) =>
|
||||
route(
|
||||
Pages.manual_withdraw.replace(
|
||||
":currency?",
|
||||
currency || "",
|
||||
),
|
||||
)
|
||||
}
|
||||
goBack={() => route(Pages.balance)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.pay}
|
||||
component={PayPage}
|
||||
goBack={() => route(Pages.balance)}
|
||||
/>
|
||||
<Route path={Pages.refund} component={RefundPage} />
|
||||
<Route path={Pages.tips} component={TipPage} />
|
||||
<Route path={Pages.withdraw} component={WithdrawPage} />
|
||||
|
||||
<Route default component={Redirect} to={Pages.history} />
|
||||
</Router>
|
||||
</IoCProviderForRuntime>
|
||||
<Route default component={Redirect} to={Pages.balance} />
|
||||
</Router>
|
||||
</WalletBox>
|
||||
</IoCProviderForRuntime>
|
||||
)}
|
||||
</DevContextProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function goToWalletPage(page: Pages | string): null {
|
||||
// eslint-disable-next-line no-undef
|
||||
chrome.tabs.create({
|
||||
active: true,
|
||||
// eslint-disable-next-line no-undef
|
||||
url: chrome.extension.getURL(`/static/wallet.html#${page}`),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
function Redirect({ to }: { to: string }): null {
|
||||
useEffect(() => {
|
||||
console.log("go some wrong route");
|
||||
route(to, true);
|
||||
});
|
||||
return null;
|
||||
|
@ -24,7 +24,7 @@
|
||||
import {
|
||||
AcceptExchangeTosRequest,
|
||||
AcceptManualWithdrawalResult, AcceptTipRequest, AcceptWithdrawalResponse,
|
||||
AddExchangeRequest, AmountJson, AmountString, ApplyRefundResponse, BalancesResponse, ConfirmPayResult,
|
||||
AddExchangeRequest, AmountString, ApplyRefundResponse, BalancesResponse, ConfirmPayResult,
|
||||
CoreApiResponse, CreateDepositGroupRequest, CreateDepositGroupResponse, DeleteTransactionRequest, ExchangesListRespose,
|
||||
GetExchangeTosResult, GetExchangeWithdrawalInfo,
|
||||
GetFeeForDepositRequest,
|
||||
|
Loading…
Reference in New Issue
Block a user