summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/taler-wallet-webextension/.storybook/preview.js4
-rwxr-xr-xpackages/taler-wallet-webextension/clean_and_build.sh1
-rw-r--r--packages/taler-wallet-webextension/src/NavigationBar.tsx15
-rw-r--r--packages/taler-wallet-webextension/src/components/BalanceTable.tsx28
-rw-r--r--packages/taler-wallet-webextension/src/components/Loading.tsx20
-rw-r--r--packages/taler-wallet-webextension/src/components/MultiActionButton.tsx95
-rw-r--r--packages/taler-wallet-webextension/src/components/styled/index.tsx64
-rw-r--r--packages/taler-wallet-webextension/src/context/devContext.ts1
-rw-r--r--packages/taler-wallet-webextension/src/cta/Deposit.stories.tsx168
-rw-r--r--packages/taler-wallet-webextension/src/cta/Deposit.tsx251
-rw-r--r--packages/taler-wallet-webextension/src/cta/Pay.tsx29
-rw-r--r--packages/taler-wallet-webextension/src/popup/Balance.stories.tsx293
-rw-r--r--packages/taler-wallet-webextension/src/popup/BalancePage.tsx73
-rw-r--r--packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx7
-rw-r--r--packages/taler-wallet-webextension/src/popup/History.stories.tsx213
-rw-r--r--packages/taler-wallet-webextension/src/popup/History.tsx148
-rw-r--r--packages/taler-wallet-webextension/src/popup/index.stories.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/popupEntryPoint.tsx30
-rw-r--r--packages/taler-wallet-webextension/src/renderHtml.tsx7
-rw-r--r--packages/taler-wallet-webextension/src/test-utils.ts3
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddNewActionView.stories.tsx (renamed from packages/taler-wallet-webextension/src/popup/AddNewActionView.stories.tsx)2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx (renamed from packages/taler-wallet-webextension/src/popup/AddNewActionView.tsx)0
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx177
-rw-r--r--packages/taler-wallet-webextension/src/wallet/BalancePage.tsx89
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx12
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage.tsx104
-rw-r--r--packages/taler-wallet-webextension/src/wallet/History.stories.tsx119
-rw-r--r--packages/taler-wallet-webextension/src/wallet/History.tsx227
-rw-r--r--packages/taler-wallet-webextension/src/wallet/LastActivityPage.stories.tsx33
-rw-r--r--packages/taler-wallet-webextension/src/wallet/LastActivityPage.tsx35
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx16
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.tsx13
-rw-r--r--packages/taler-wallet-webextension/src/wallet/index.stories.tsx19
-rw-r--r--packages/taler-wallet-webextension/src/walletEntryPoint.tsx285
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts2
35 files changed, 1521 insertions, 1067 deletions
diff --git a/packages/taler-wallet-webextension/.storybook/preview.js b/packages/taler-wallet-webextension/.storybook/preview.js
index 6331a7fa8..b770d7b63 100644
--- a/packages/taler-wallet-webextension/.storybook/preview.js
+++ b/packages/taler-wallet-webextension/.storybook/preview.js
@@ -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>
diff --git a/packages/taler-wallet-webextension/clean_and_build.sh b/packages/taler-wallet-webextension/clean_and_build.sh
index be20d80d9..0cfbe0946 100755
--- a/packages/taler-wallet-webextension/clean_and_build.sh
+++ b/packages/taler-wallet-webextension/clean_and_build.sh
@@ -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
diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx
index c02e48983..44e8af78e 100644
--- a/packages/taler-wallet-webextension/src/NavigationBar.tsx
+++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx
@@ -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) => {
diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
index 05a7d28dd..c69625cd2 100644
--- a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
+++ b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
@@ -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>
);
diff --git a/packages/taler-wallet-webextension/src/components/Loading.tsx b/packages/taler-wallet-webextension/src/components/Loading.tsx
new file mode 100644
index 000000000..34edac551
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/Loading.tsx
@@ -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>;
+}
diff --git a/packages/taler-wallet-webextension/src/components/MultiActionButton.tsx b/packages/taler-wallet-webextension/src/components/MultiActionButton.tsx
new file mode 100644
index 000000000..70d53640d
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/MultiActionButton.tsx
@@ -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>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx
index 216a1fabc..2d16b496c 100644
--- a/packages/taler-wallet-webextension/src/components/styled/index.tsx
+++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx
@@ -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)
+ );
+ }
+`;
diff --git a/packages/taler-wallet-webextension/src/context/devContext.ts b/packages/taler-wallet-webextension/src/context/devContext.ts
index 7ed6858a7..4b8ba2bcd 100644
--- a/packages/taler-wallet-webextension/src/context/devContext.ts
+++ b/packages/taler-wallet-webextension/src/context/devContext.ts
@@ -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 });
};
diff --git a/packages/taler-wallet-webextension/src/cta/Deposit.stories.tsx b/packages/taler-wallet-webextension/src/cta/Deposit.stories.tsx
new file mode 100644
index 000000000..df5947bb1
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/cta/Deposit.stories.tsx
@@ -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,
+ },
+});
diff --git a/packages/taler-wallet-webextension/src/cta/Deposit.tsx b/packages/taler-wallet-webextension/src/cta/Deposit.tsx
new file mode 100644
index 000000000..3696b0c2d
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/cta/Deposit.tsx
@@ -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>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx
index d7419d410..e61d3a9d6 100644
--- a/packages/taler-wallet-webextension/src/cta/Pay.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx
@@ -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> => {
diff --git a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
index a4988cf2d..1af3b5858 100644
--- a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
@@ -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,
- },
- ],
+export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
+ balances: [
+ {
+ available: "EUR:1",
+ hasPendingTransactions: false,
+ pendingIncoming: "USD:0",
+ 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,
- },
- ],
+ {
+ available: "TESTKUDOS:2000",
+ hasPendingTransactions: false,
+ pendingIncoming: "USD:0",
+ pendingOutgoing: "USD:0",
+ 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,
- },
- ],
+ {
+ available: "JPY:4",
+ hasPendingTransactions: false,
+ pendingIncoming: "EUR:15",
+ pendingOutgoing: "EUR:0",
+ 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,
- },
- ],
+export const NoCoinsInTreeCurrencies = createExample(TestedComponent, {
+ balances: [
+ {
+ available: "EUR:3",
+ hasPendingTransactions: false,
+ pendingIncoming: "USD:0",
+ pendingOutgoing: "USD:0",
+ 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,
- },
- ],
+ {
+ 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,
},
- },
- Linker: NullLink,
+ ],
});
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,
+ },
+ {
+ 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,
},
- },
- Linker: NullLink,
+ ],
});
diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
index 3eb5f4270..014d3b18e 100644
--- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
@@ -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);
+ const balances = !state || state.hasError ? [] : state.response.balances;
+
+ if (!state) {
+ return <Loading />;
+ }
+
+ if (state.hasError) {
+ return (
+ <Fragment>
+ <ErrorBox>{state.message}</ErrorBox>
+ <p>
+ Click <PageLink pageName="welcome">here</PageLink> for help and
+ diagnostics.
+ </p>
+ </Fragment>
+ );
+ }
+
return (
<BalanceView
- balance={state}
- Linker={PageLink}
+ balances={balances}
goToWalletManualWithdraw={goToWalletManualWithdraw}
goToWalletDeposit={goToWalletDeposit}
+ goToWalletHistory={goToWalletHistory}
/>
);
}
export interface BalanceViewProps {
- balance: HookResponse<BalancesResponse>;
- Linker: typeof PageLink;
+ balances: Balance[];
goToWalletManualWithdraw: () => void;
goToWalletDeposit: (currency: string) => void;
+ goToWalletHistory: (currency: string) => void;
}
export function BalanceView({
- balance,
- Linker,
+ balances,
goToWalletManualWithdraw,
goToWalletDeposit,
+ goToWalletHistory,
}: BalanceViewProps): VNode {
- if (!balance) {
- return <div>Loading...</div>;
- }
+ const currencyWithNonZeroAmount = balances
+ .filter((b) => !Amounts.isZero(b.available))
+ .map((b) => b.available.split(":")[0]);
- if (balance.hasError) {
- return (
- <Fragment>
- <ErrorBox>{balance.message}</ErrorBox>
- <p>
- Click <Linker pageName="welcome">here</Linker> for help and
- diagnostics.
- </p>
- </Fragment>
- );
- }
- if (balance.response.balances.length === 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>
);
diff --git a/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx
index aec288dec..840398a44 100644
--- a/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx
@@ -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>
diff --git a/packages/taler-wallet-webextension/src/popup/History.stories.tsx b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
deleted file mode 100644
index 43d39da82..000000000
--- a/packages/taler-wallet-webextension/src/popup/History.stories.tsx
+++ /dev/null
@@ -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,
- },
- ],
-});
diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx b/packages/taler-wallet-webextension/src/popup/History.tsx
deleted file mode 100644
index 2dfddb8c4..000000000
--- a/packages/taler-wallet-webextension/src/popup/History.tsx
+++ /dev/null
@@ -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>
- );
-}
diff --git a/packages/taler-wallet-webextension/src/popup/index.stories.tsx b/packages/taler-wallet-webextension/src/popup/index.stories.tsx
index c3e60c4ed..3abb80021 100644
--- a/packages/taler-wallet-webextension/src/popup/index.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/index.stories.tsx
@@ -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];
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index 27372db5e..908349e89 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -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>
);
}
diff --git a/packages/taler-wallet-webextension/src/renderHtml.tsx b/packages/taler-wallet-webextension/src/renderHtml.tsx
index 15986d5d1..ba98ae237 100644
--- a/packages/taler-wallet-webextension/src/renderHtml.tsx
+++ b/packages/taler-wallet-webextension/src/renderHtml.tsx
@@ -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}
diff --git a/packages/taler-wallet-webextension/src/test-utils.ts b/packages/taler-wallet-webextension/src/test-utils.ts
index fbb7c56ff..8c721a9d5 100644
--- a/packages/taler-wallet-webextension/src/test-utils.ts
+++ b/packages/taler-wallet-webextension/src/test-utils.ts
@@ -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 \ No newline at end of file
+ typeof it === 'undefined' ? nullTestFunction : (typeof window === 'undefined' ? it.skip : it) \ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/popup/AddNewActionView.stories.tsx b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.stories.tsx
index 6ee56ef77..54e4eb1f2 100644
--- a/packages/taler-wallet-webextension/src/popup/AddNewActionView.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.stories.tsx
@@ -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(),
diff --git a/packages/taler-wallet-webextension/src/popup/AddNewActionView.tsx b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx
index d4158973e..d4158973e 100644
--- a/packages/taler-wallet-webextension/src/popup/AddNewActionView.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx
diff --git a/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx
index 2432c31eb..6c670b019 100644
--- a/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx
@@ -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, {
+ balances: [],
});
-export const EmptyBalance = createExample(TestedComponent, {
- balance: {
- hasError: false,
- response: {
- balances: [],
+export const SomeCoins = createExample(TestedComponent, {
+ balances: [
+ {
+ available: "USD:10.5",
+ hasPendingTransactions: false,
+ pendingIncoming: "USD:0",
+ pendingOutgoing: "USD:0",
+ requiresUserInput: false,
},
- },
- Linker: NullLink,
+ ],
});
-export const SomeCoins = createExample(TestedComponent, {
- balance: {
- hasError: false,
- response: {
- balances: [
- {
- available: "USD:10.5",
- hasPendingTransactions: false,
- pendingIncoming: "USD:0",
- pendingOutgoing: "USD:0",
- requiresUserInput: false,
- },
- ],
+export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
+ balances: [
+ {
+ available: "EUR:1",
+ hasPendingTransactions: false,
+ pendingIncoming: "USD:0",
+ pendingOutgoing: "USD:0",
+ requiresUserInput: false,
+ },
+ {
+ available: "TESTKUDOS:2000",
+ hasPendingTransactions: false,
+ pendingIncoming: "USD:0",
+ pendingOutgoing: "USD:0",
+ requiresUserInput: false,
},
- },
- Linker: NullLink,
+ {
+ available: "JPY:4",
+ hasPendingTransactions: false,
+ pendingIncoming: "EUR:15",
+ pendingOutgoing: "EUR:0",
+ requiresUserInput: false,
+ },
+ ],
});
-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 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,
},
- },
- Linker: NullLink,
+ {
+ available: "ARS:1",
+ 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 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,
},
- },
- Linker: NullLink,
+ ],
});
diff --git a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
index 33182a38d..5fa08f8a6 100644
--- a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
@@ -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);
+
+ const balances = !state || state.hasError ? [] : state.response.balances;
+
+ if (!state) {
+ return <Loading />;
+ }
+
+ if (state.hasError) {
+ return (
+ <Fragment>
+ <ErrorBox>{state.message}</ErrorBox>
+ <p>
+ Click <PageLink pageName="welcome">here</PageLink> for help and
+ diagnostics.
+ </p>
+ </Fragment>
+ );
+ }
+
return (
<BalanceView
- balance={state}
- Linker={PageLink}
+ balances={balances}
goToWalletManualWithdraw={goToWalletManualWithdraw}
goToWalletDeposit={goToWalletDeposit}
+ goToWalletHistory={goToWalletHistory}
/>
);
}
export interface BalanceViewProps {
- balance: HookResponse<BalancesResponse>;
- Linker: typeof PageLink;
+ balances: Balance[];
goToWalletManualWithdraw: () => void;
goToWalletDeposit: (currency: string) => void;
+ goToWalletHistory: (currency: string) => void;
}
export function BalanceView({
- balance,
- Linker,
+ balances,
goToWalletManualWithdraw,
goToWalletDeposit,
+ goToWalletHistory,
}: BalanceViewProps): VNode {
- if (!balance) {
- return <div>Loading...</div>;
- }
+ const currencyWithNonZeroAmount = balances
+ .filter((b) => !Amounts.isZero(b.available))
+ .map((b) => b.available.split(":")[0]);
- if (balance.hasError) {
- return (
- <Fragment>
- <ErrorBox>{balance.message}</ErrorBox>
- <p>
- Click <Linker pageName="welcome">here</Linker> for help and
- diagnostics.
- </p>
- </Fragment>
- );
- }
- if (balance.response.balances.length === 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>
);
diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
index 36feeb76e..f32a2aa5c 100644
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
@@ -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] ?? "");
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx
index 5c931394d..9e15daa97 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx
@@ -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>
diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
index ce4b0fb74..3f550175d 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
@@ -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,16 +132,24 @@ export const Empty = createExample(TestedComponent, {
],
});
-export const EmptyWithNoBalance = createExample(TestedComponent, {
- list: [],
- balances: [],
+export const OneSimpleTransaction = createExample(TestedComponent, {
+ transactions: [exampleData.withdraw],
+ balances: [
+ {
+ available: "USD:10",
+ pendingIncoming: "USD:0",
+ pendingOutgoing: "USD:0",
+ hasPendingTransactions: false,
+ requiresUserInput: false,
+ },
+ ],
});
-export const One = createExample(TestedComponent, {
- list: [exampleData.withdraw],
+export const TwoTransactionsAndZeroBalance = createExample(TestedComponent, {
+ transactions: [exampleData.withdraw, exampleData.deposit],
balances: [
{
- available: "USD:10",
+ available: "USD:0",
pendingIncoming: "USD:0",
pendingOutgoing: "USD:0",
hasPendingTransactions: false,
@@ -145,8 +158,8 @@ export const One = createExample(TestedComponent, {
],
});
-export const OnePending = createExample(TestedComponent, {
- list: [
+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,
@@ -183,38 +196,82 @@ export const Several = createExample(TestedComponent, {
],
balances: [
{
- available: "TESTKUDOS:10",
- pendingIncoming: "TESTKUDOS:0",
- pendingOutgoing: "TESTKUDOS:0",
+ available: "USD:10",
+ pendingIncoming: "USD:0",
+ pendingOutgoing: "USD: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,
- ],
+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: "TESTKUDOS:10",
+ 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: "USD:10",
- pendingIncoming: "USD:0",
- pendingOutgoing: "USD:0",
+ 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,
},
diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx
index 58db0360b..7912d169a 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.tsx
@@ -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>
+ <section>
+ <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>
- </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>
- );
- })}
</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>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/LastActivityPage.stories.tsx b/packages/taler-wallet-webextension/src/wallet/LastActivityPage.stories.tsx
new file mode 100644
index 000000000..e729c2982
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/LastActivityPage.stories.tsx
@@ -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,
+});
diff --git a/packages/taler-wallet-webextension/src/wallet/LastActivityPage.tsx b/packages/taler-wallet-webextension/src/wallet/LastActivityPage.tsx
new file mode 100644
index 000000000..8ec4c8759
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/LastActivityPage.tsx
@@ -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>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
index b3e8a2c25..c7958eb8a 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
@@ -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}
/>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 8172e02a2..21bfc943d 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -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 (
diff --git a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
index 644ab1c59..55f350d46 100644
--- a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
@@ -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,
+];
diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
index 938892874..b54d49ded 100644
--- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
@@ -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)}
- />
-
- <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.exchange_add}
- component={withLogoAndNavBar(ExchangeAddPage)}
- onBack={() => {
- route(Pages.balance);
- }}
- />
-
- <Route
- path={Pages.manual_withdraw}
- component={withLogoAndNavBar(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.dev}
- component={withLogoAndNavBar(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} />
-
- <Route default component={Redirect} to={Pages.history} />
- </Router>
- </IoCProviderForRuntime>
+ {({ 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.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={ExchangeAddPage}
+ onBack={() => {
+ route(Pages.balance);
+ }}
+ />
+
+ <Route
+ path={Pages.manual_withdraw}
+ component={ManualWithdrawPage}
+ />
+
+ <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={DeveloperPage} />
+
+ {/** 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.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;
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts
index 5fe30bc4b..dc96efc75 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -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,