summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-01-10 16:04:53 -0300
committerSebastian <sebasjm@gmail.com>2022-01-10 16:07:27 -0300
commitfb22009ec4799a624f00c228fbd7435b44c1cbac (patch)
treeb1f8427e845bb3687b8a64deb3545eff2290ec67 /packages/taler-wallet-webextension/src/wallet
parent83b9d32b7812e63640a60b5b42a27c96d8053bce (diff)
downloadwallet-core-fb22009ec4799a624f00c228fbd7435b44c1cbac.tar.gz
wallet-core-fb22009ec4799a624f00c228fbd7435b44c1cbac.tar.bz2
wallet-core-fb22009ec4799a624f00c228fbd7435b44c1cbac.zip
deposit design from belen, feature missing: kyc
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddNewActionView.stories.tsx33
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx64
-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
13 files changed, 684 insertions, 257 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/AddNewActionView.stories.tsx b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.stories.tsx
new file mode 100644
index 000000000..54e4eb1f2
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.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 { AddNewActionView as TestedComponent } from "./AddNewActionView";
+
+export default {
+ title: "wallet/add new action",
+ component: TestedComponent,
+ argTypes: {
+ setDeviceName: () => Promise.resolve(),
+ },
+};
+
+export const Initial = createExample(TestedComponent, {});
diff --git a/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx
new file mode 100644
index 000000000..d4158973e
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx
@@ -0,0 +1,64 @@
+import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Button, ButtonSuccess, InputWithLabel } from "../components/styled";
+import { actionForTalerUri } from "../utils/index";
+
+export interface Props {
+ onCancel: () => void;
+}
+
+function buttonLabelByTalerType(type: TalerUriType): string {
+ switch (type) {
+ case TalerUriType.TalerNotifyReserve:
+ return "Open reserve page";
+ case TalerUriType.TalerPay:
+ return "Open pay page";
+ case TalerUriType.TalerRefund:
+ return "Open refund page";
+ case TalerUriType.TalerTip:
+ return "Open tip page";
+ case TalerUriType.TalerWithdraw:
+ return "Open withdraw page";
+ }
+ return "";
+}
+
+export function AddNewActionView({ onCancel }: Props): VNode {
+ const [url, setUrl] = useState("");
+ const uriType = classifyTalerUri(url);
+
+ return (
+ <Fragment>
+ <section>
+ <InputWithLabel
+ invalid={url !== "" && uriType === TalerUriType.Unknown}
+ >
+ <label>GNU Taler URI</label>
+ <div>
+ <input
+ style={{ width: "100%" }}
+ type="text"
+ value={url}
+ placeholder="taler://pay/...."
+ onInput={(e) => setUrl(e.currentTarget.value)}
+ />
+ </div>
+ </InputWithLabel>
+ </section>
+ <footer>
+ <Button onClick={onCancel}>Back</Button>
+ {uriType !== TalerUriType.Unknown && (
+ <ButtonSuccess
+ onClick={() => {
+ // eslint-disable-next-line no-undef
+ chrome.tabs.create({ url: actionForTalerUri(uriType, url) });
+ }}
+ >
+ {buttonLabelByTalerType(uriType)}
+ </ButtonSuccess>
+ )}
+ </footer>
+ </Fragment>
+ );
+}
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,
+];