summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-11-19 14:51:27 -0300
committerSebastian <sebasjm@gmail.com>2021-11-19 14:51:35 -0300
commita35604fd562a72e4e266bf6a4255d89d3c1374a1 (patch)
treed0c4df01a89dc78c412be6da3aba3cec343937ff /packages
parent60cfb0e78f3afed92f315c1394da717329db9564 (diff)
downloadwallet-core-a35604fd562a72e4e266bf6a4255d89d3c1374a1.tar.gz
wallet-core-a35604fd562a72e4e266bf6a4255d89d3c1374a1.tar.bz2
wallet-core-a35604fd562a72e4e266bf6a4255d89d3c1374a1.zip
some changes:
- simplify design to reuse more components (from wallet instead of popup) - simplify hooks (useAsyncAsHook) - updateNotification from backend now filter events by type - new balance design proposed by Belen - more information when the withdrawal is in process - manual withdrawal implementation - some bugs killed
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-wallet-webextension/.storybook/preview.js12
-rw-r--r--packages/taler-wallet-webextension/src/components/BalanceTable.tsx48
-rw-r--r--packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx109
-rw-r--r--packages/taler-wallet-webextension/src/components/styled/index.tsx167
-rw-r--r--packages/taler-wallet-webextension/src/cta/Pay.tsx4
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts10
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useBalances.ts53
-rw-r--r--packages/taler-wallet-webextension/src/popup/Backup.stories.tsx198
-rw-r--r--packages/taler-wallet-webextension/src/popup/BackupPage.tsx197
-rw-r--r--packages/taler-wallet-webextension/src/popup/Balance.stories.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/popup/BalancePage.tsx195
-rw-r--r--packages/taler-wallet-webextension/src/popup/Debug.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/popup/History.stories.tsx1
-rw-r--r--packages/taler-wallet-webextension/src/popup/History.tsx22
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx51
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx244
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx51
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx235
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx278
-rw-r--r--packages/taler-wallet-webextension/src/popup/Settings.tsx83
-rw-r--r--packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx12
-rw-r--r--packages/taler-wallet-webextension/src/popupEntryPoint.tsx13
-rw-r--r--packages/taler-wallet-webextension/src/svg/index.tsx40
-rw-r--r--packages/taler-wallet-webextension/src/wallet/BackupPage.tsx13
-rw-r--r--packages/taler-wallet-webextension/src/wallet/BalancePage.tsx110
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx4
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx26
-rw-r--r--packages/taler-wallet-webextension/src/wallet/History.stories.tsx1
-rw-r--r--packages/taler-wallet-webextension/src/wallet/History.tsx33
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx15
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx13
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx94
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx119
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Settings.tsx73
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx44
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.tsx226
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Welcome.tsx11
-rw-r--r--packages/taler-wallet-webextension/src/walletEntryPoint.tsx46
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts49
-rw-r--r--packages/taler-wallet-webextension/src/wxBackend.ts10
40 files changed, 953 insertions, 1961 deletions
diff --git a/packages/taler-wallet-webextension/.storybook/preview.js b/packages/taler-wallet-webextension/.storybook/preview.js
index 0efb96308..25f9f46ba 100644
--- a/packages/taler-wallet-webextension/.storybook/preview.js
+++ b/packages/taler-wallet-webextension/.storybook/preview.js
@@ -18,7 +18,7 @@ import { h, Fragment } from "preact"
import { NavBar } from '../src/NavigationBar'
import { LogoHeader } from '../src/components/LogoHeader'
import { TranslationProvider } from '../src/context/translation'
-
+import { PopupBox, WalletBox } from '../src/components/styled'
export const parameters = {
controls: { expanded: true },
actions: { argTypesRegex: "^on[A-Z].*" },
@@ -58,9 +58,9 @@ export const decorators = [
// add a fake header so it looks similar
return <Fragment>
<NavBar path={path} devMode={path === '/dev'} />
- <div style={{ width: 400, height: 290 }}>
+ <PopupBox>
<Story />
- </div>
+ </PopupBox>
</Fragment>
}
@@ -125,7 +125,7 @@ export const decorators = [
<link key="1" rel="stylesheet" type="text/css" href="/static/style/pure.css" />
<link key="2" rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
<Story />
- </div>
+ </div>
}
if (kind.startsWith('wallet')) {
const path = /wallet(\/.*).*/.exec(kind)[1];
@@ -157,7 +157,9 @@ export const decorators = [
</style>
<LogoHeader />
<NavBar path={path} devMode={path === '/dev'} />
- <Story />
+ <WalletBox>
+ <Story />
+ </WalletBox>
</div>
}
return <div>
diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
new file mode 100644
index 000000000..e1c19cc23
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
@@ -0,0 +1,48 @@
+/*
+ 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 { amountFractionalBase, Amounts, Balance } from "@gnu-taler/taler-util";
+import { h, VNode } from "preact";
+import { TableWithRoundRows as TableWithRoundedRows } from "./styled/index";
+
+export function BalanceTable({ balances }: { balances: Balance[] }): 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}>
+ <td>{av.currency}</td>
+ <td
+ style={{
+ fontSize: "2em",
+ textAlign: "right",
+ width: "100%",
+ }}
+ >
+ {v}
+ </td>
+ </tr>
+ );
+ })}
+ </TableWithRoundedRows>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
new file mode 100644
index 000000000..71365e089
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
@@ -0,0 +1,109 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { PaytoUri } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import { CopiedIcon, CopyIcon } from "../svg";
+import { ButtonBox, TooltipRight } from "./styled";
+
+export interface BankDetailsProps {
+ payto: PaytoUri | undefined;
+ exchangeBaseUrl: string;
+ subject: string;
+ amount: string;
+}
+
+export function BankDetailsByPaytoType({
+ payto,
+ subject,
+ exchangeBaseUrl,
+ amount,
+}: BankDetailsProps): VNode {
+ const firstPart = !payto ? undefined : !payto.isKnown ? (
+ <Row name="Account" value={payto.targetPath} />
+ ) : payto.targetType === "x-taler-bank" ? (
+ <Fragment>
+ <Row name="Bank host" value={payto.host} />
+ <Row name="Bank account" value={payto.account} />
+ </Fragment>
+ ) : payto.targetType === "iban" ? (
+ <Row name="IBAN" value={payto.iban} />
+ ) : undefined;
+ return (
+ <div style={{ textAlign: "left" }}>
+ <p>Bank transfer details</p>
+ <table>
+ {firstPart}
+ <Row name="Exchange" value={exchangeBaseUrl} />
+ <Row name="Chosen amount" value={amount} />
+ <Row name="Subject" value={subject} literal />
+ </table>
+ </div>
+ );
+}
+
+function Row({
+ name,
+ value,
+ literal,
+}: {
+ name: string;
+ value: string;
+ literal?: boolean;
+}): VNode {
+ const [copied, setCopied] = useState(false);
+ function copyText(): void {
+ navigator.clipboard.writeText(value);
+ setCopied(true);
+ }
+ useEffect(() => {
+ if (copied) {
+ setTimeout(() => {
+ setCopied(false);
+ }, 1000);
+ }
+ }, [copied]);
+ return (
+ <tr>
+ <td>
+ {!copied ? (
+ <ButtonBox onClick={copyText}>
+ <CopyIcon />
+ </ButtonBox>
+ ) : (
+ <TooltipRight content="Copied">
+ <ButtonBox disabled>
+ <CopiedIcon />
+ </ButtonBox>
+ </TooltipRight>
+ )}
+ </td>
+ <td>
+ <b>{name}</b>
+ </td>
+ {literal ? (
+ <td>
+ <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
+ {value}
+ </pre>
+ </td>
+ ) : (
+ <td>{value}</td>
+ )}
+ </tr>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx
index 2db7c61f8..b2ca13801 100644
--- a/packages/taler-wallet-webextension/src/components/styled/index.tsx
+++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx
@@ -15,6 +15,8 @@
*/
// need to import linaria types, otherwise compiler will complain
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+// eslint-disable-next-line no-unused-vars
import type * as Linaria from "@linaria/core";
import { styled } from "@linaria/react";
@@ -78,9 +80,8 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
width: 400px;
}
& > section {
- padding-left: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
- padding-right: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
- // this margin will send the section up when used with a header
+ padding: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
+
margin-bottom: auto;
overflow: auto;
@@ -202,6 +203,152 @@ export const PopupBox = styled.div<{ noPadding?: boolean }>`
}
`;
+export const TableWithRoundRows = styled.table`
+ border-collapse: separate;
+ border-spacing: 0px 10px;
+ margin-top: -10px;
+
+ td {
+ border: solid 1px #000;
+ border-style: solid none;
+ padding: 10px;
+ }
+ td:first-child {
+ border-left-style: solid;
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+ }
+ td:last-child {
+ border-right-style: solid;
+ border-bottom-right-radius: 5px;
+ border-top-right-radius: 5px;
+ }
+`;
+
+const Tooltip = styled.div<{ content: string }>`
+ display: block;
+ position: relative;
+
+ ::before {
+ position: absolute;
+ z-index: 1000001;
+ width: 0;
+ height: 0;
+ color: darkgray;
+ pointer-events: none;
+ content: "";
+ border: 6px solid transparent;
+
+ border-bottom-color: darkgray;
+ }
+
+ ::after {
+ position: absolute;
+ z-index: 1000001;
+ padding: 0.5em 0.75em;
+ font: normal normal 11px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
+ -webkit-font-smoothing: subpixel-antialiased;
+ color: white;
+ text-align: center;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: break-word;
+ white-space: pre;
+ pointer-events: none;
+ content: attr(content);
+ background: darkgray;
+ border-radius: 6px;
+ }
+`;
+
+export const TooltipBottom = styled(Tooltip)`
+ ::before {
+ top: auto;
+ right: 50%;
+ bottom: -7px;
+ margin-right: -6px;
+ }
+ ::after {
+ top: 100%;
+ right: -50%;
+ margin-top: 6px;
+ }
+`;
+
+export const TooltipRight = styled(Tooltip)`
+ ::before {
+ top: 0px;
+ left: 16px;
+ transform: rotate(-90deg);
+ }
+ ::after {
+ top: -50%;
+ left: 28px;
+ margin-top: 6px;
+ }
+`;
+
+export const Overlay = styled.div`
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 2;
+ cursor: pointer;
+`;
+
+export const CenteredDialog = styled.div`
+ position: absolute;
+ text-align: left;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+
+ top: 50%;
+ left: 50%;
+ /* font-size: 50px; */
+ color: black;
+ transform: translate(-50%, -50%);
+ -ms-transform: translate(-50%, -50%);
+ cursor: initial;
+ background-color: white;
+ border-radius: 10px;
+
+ max-height: 70%;
+
+ & > header {
+ border-top-right-radius: 6px;
+ border-top-left-radius: 6px;
+ padding: 10px;
+ background-color: #f5f5f5;
+ border-bottom: 1px solid #dbdbdb;
+ font-weight: bold;
+ }
+ & > section {
+ padding: 10px;
+ flex-grow: 1;
+ flex-shrink: 1;
+ overflow: auto;
+ }
+ & > footer {
+ border-top: 1px solid #dbdbdb;
+ border-bottom-right-radius: 6px;
+ border-bottom-left-radius: 6px;
+ padding: 10px;
+ background-color: #f5f5f5;
+ display: flex;
+ justify-content: space-between;
+ }
+`;
+
export const Button = styled.button<{ upperCased?: boolean }>`
display: inline-block;
zoom: 1;
@@ -217,7 +364,7 @@ export const Button = styled.button<{ upperCased?: boolean }>`
font-family: inherit;
font-size: 100%;
padding: 0.5em 1em;
- color: #444; /* rgba not supported (IE 8) */
+ /* 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*/
@@ -305,8 +452,7 @@ export const FontIcon = styled.div`
`;
export const ButtonBox = styled(Button)`
padding: 0.5em;
- width: fit-content;
- height: 2em;
+ font-size: x-small;
& > ${FontIcon} {
width: 1em;
@@ -320,6 +466,8 @@ export const ButtonBox = styled(Button)`
border-radius: 4px;
border-color: black;
color: black;
+ /* -webkit-border-horizontal-spacing: 0px;
+ -webkit-border-vertical-spacing: 0px; */
`;
const ButtonVariant = styled(Button)`
@@ -377,6 +525,7 @@ export const Centered = styled.div`
margin-top: 15px;
}
`;
+
export const Row = styled.div`
display: flex;
margin: 0.5em 0;
@@ -566,6 +715,12 @@ export const ErrorBox = styled.div`
}
`;
+export const InfoBox = styled(ErrorBox)`
+ color: black;
+ background-color: #d1e7dd;
+ border-color: #badbcc;
+`;
+
export const SuccessBox = styled(ErrorBox)`
color: #0f5132;
background-color: #d1e7dd;
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx
index d5861c47c..30b571f01 100644
--- a/packages/taler-wallet-webextension/src/cta/Pay.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx
@@ -49,7 +49,7 @@ import {
WalletAction,
WarningBox,
} from "../components/styled";
-import { useBalances } from "../hooks/useBalances";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import * as wxApi from "../wxApi";
interface Props {
@@ -109,7 +109,7 @@ export function PayPage({ talerPayUri }: Props): VNode {
);
const [payErrMsg, setPayErrMsg] = useState<string | undefined>(undefined);
- const balance = useBalances();
+ const balance = useAsyncAsHook(wxApi.getBalance);
const balanceWithoutError = balance?.hasError
? []
: balance?.response.balances || [];
diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
index aa6695c3e..38ec5bf72 100644
--- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
@@ -13,7 +13,7 @@
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 { ExchangesListRespose } from "@gnu-taler/taler-util";
+import { ExchangesListRespose, NotificationType } from "@gnu-taler/taler-util";
import { useEffect, useState } from "preact/hooks";
import * as wxApi from "../wxApi";
@@ -29,7 +29,8 @@ interface HookError {
export type HookResponse<T> = HookOk<T> | HookError | undefined;
-export function useAsyncAsHook<T>(fn: () => Promise<T>): HookResponse<T> {
+//"withdraw-group-finished"
+export function useAsyncAsHook<T>(fn: () => Promise<T>, updateOnNotification?: Array<NotificationType>): HookResponse<T> {
const [result, setHookResponse] = useState<HookResponse<T>>(undefined);
useEffect(() => {
async function doAsync() {
@@ -43,6 +44,11 @@ export function useAsyncAsHook<T>(fn: () => Promise<T>): HookResponse<T> {
}
}
doAsync();
+ if (updateOnNotification && updateOnNotification.length > 0) {
+ return wxApi.onUpdateNotification(updateOnNotification, () => {
+ doAsync()
+ });
+ }
}, []);
return result;
}
diff --git a/packages/taler-wallet-webextension/src/hooks/useBalances.ts b/packages/taler-wallet-webextension/src/hooks/useBalances.ts
deleted file mode 100644
index 403ce7b87..000000000
--- a/packages/taler-wallet-webextension/src/hooks/useBalances.ts
+++ /dev/null
@@ -1,53 +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 { BalancesResponse } from "@gnu-taler/taler-util";
-import { useEffect, useState } from "preact/hooks";
-import * as wxApi from "../wxApi";
-
-interface BalancesHookOk {
- hasError: false;
- response: BalancesResponse;
-}
-
-interface BalancesHookError {
- hasError: true;
- message: string;
-}
-
-export type BalancesHook = BalancesHookOk | BalancesHookError | undefined;
-
-export function useBalances(): BalancesHook {
- const [balance, setBalance] = useState<BalancesHook>(undefined);
- useEffect(() => {
- async function checkBalance() {
- try {
- const response = await wxApi.getBalance();
- console.log("got balance", balance);
- setBalance({ hasError: false, response });
- } catch (e) {
- console.error("could not retrieve balances", e);
- if (e instanceof Error) {
- setBalance({ hasError: true, message: e.message });
- }
- }
- }
- checkBalance();
- return wxApi.onUpdateNotification(checkBalance);
- }, []);
-
- return balance;
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx b/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx
deleted file mode 100644
index 232b0da73..000000000
--- a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx
+++ /dev/null
@@ -1,198 +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 { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
-import { addDays } from "date-fns";
-import { BackupView as TestedComponent } from "./BackupPage";
-import { createExample } from "../test-utils";
-
-export default {
- title: "popup/backup/list",
- component: TestedComponent,
- argTypes: {
- onRetry: { action: "onRetry" },
- onDelete: { action: "onDelete" },
- onBack: { action: "onBack" },
- },
-};
-
-export const LotOfProviders = createExample(TestedComponent, {
- providers: [
- {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp: {
- t_ms: 1625063925078,
- },
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: {
- t_ms: 1656599921000,
- },
- },
- terms: {
- annualFee: "ARS:1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp: {
- t_ms: 1625063925078,
- },
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: {
- t_ms: addDays(new Date(), 13).getTime(),
- },
- },
- terms: {
- annualFee: "ARS:1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Pending,
- },
- terms: {
- annualFee: "KUDOS:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.InsufficientBalance,
- },
- terms: {
- annualFee: "KUDOS:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.TermsChanged,
- newTerms: {
- annualFee: "USD:2",
- storageLimitInMegabytes: 8,
- supportedProtocolVersion: "2",
- },
- oldTerms: {
- annualFee: "USD:1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "1",
- },
- paidUntil: {
- t_ms: "never",
- },
- },
- terms: {
- annualFee: "KUDOS:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Unpaid,
- },
- terms: {
- annualFee: "KUDOS:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Unpaid,
- },
- terms: {
- annualFee: "KUDOS:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- ],
-});
-
-export const OneProvider = createExample(TestedComponent, {
- providers: [
- {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp: {
- t_ms: 1625063925078,
- },
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: {
- t_ms: 1656599921000,
- },
- },
- terms: {
- annualFee: "ARS:1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- ],
-});
-
-export const Empty = createExample(TestedComponent, {
- providers: [],
-});
diff --git a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx
deleted file mode 100644
index ae93f8a40..000000000
--- a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx
+++ /dev/null
@@ -1,197 +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 { i18n, Timestamp } from "@gnu-taler/taler-util";
-import {
- ProviderInfo,
- ProviderPaymentStatus,
-} from "@gnu-taler/taler-wallet-core";
-import {
- differenceInMonths,
- formatDuration,
- intervalToDuration,
-} from "date-fns";
-import { Fragment, h, VNode } from "preact";
-import {
- BoldLight,
- ButtonPrimary,
- ButtonSuccess,
- Centered,
- CenteredBoldText,
- CenteredText,
- PopupBox,
- RowBorderGray,
- SmallLightText,
- SmallText,
-} from "../components/styled";
-import { useBackupStatus } from "../hooks/useBackupStatus";
-import { Pages } from "../NavigationBar";
-
-interface Props {
- onAddProvider: () => void;
-}
-
-export function BackupPage({ onAddProvider }: Props): VNode {
- const status = useBackupStatus();
- if (!status) {
- return <div>Loading...</div>;
- }
- return (
- <BackupView
- providers={status.providers}
- onAddProvider={onAddProvider}
- onSyncAll={status.sync}
- />
- );
-}
-
-export interface ViewProps {
- providers: ProviderInfo[];
- onAddProvider: () => void;
- onSyncAll: () => Promise<void>;
-}
-
-export function BackupView({
- providers,
- onAddProvider,
- onSyncAll,
-}: ViewProps): VNode {
- return (
- <PopupBox>
- <section>
- {providers.map((provider, idx) => (
- <BackupLayout
- key={idx}
- status={provider.paymentStatus}
- timestamp={provider.lastSuccessfulBackupTimestamp}
- id={provider.syncProviderBaseUrl}
- active={provider.active}
- title={provider.name}
- />
- ))}
- {!providers.length && (
- <Centered style={{ marginTop: 100 }}>
- <BoldLight>No backup providers configured</BoldLight>
- <ButtonSuccess onClick={onAddProvider}>
- <i18n.Translate>Add provider</i18n.Translate>
- </ButtonSuccess>
- </Centered>
- )}
- </section>
- {!!providers.length && (
- <footer>
- <div />
- <div>
- <ButtonPrimary onClick={onSyncAll}>
- {providers.length > 1 ? (
- <i18n.Translate>Sync all backups</i18n.Translate>
- ) : (
- <i18n.Translate>Sync now</i18n.Translate>
- )}
- </ButtonPrimary>
- <ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess>
- </div>
- </footer>
- )}
- </PopupBox>
- );
-}
-
-interface TransactionLayoutProps {
- status: ProviderPaymentStatus;
- timestamp?: Timestamp;
- title: string;
- id: string;
- active: boolean;
-}
-
-function BackupLayout(props: TransactionLayoutProps): VNode {
- const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
- const dateStr = date?.toLocaleString([], {
- dateStyle: "medium",
- timeStyle: "short",
- } as any);
-
- return (
- <RowBorderGray>
- <div style={{ color: !props.active ? "grey" : undefined }}>
- <a
- href={Pages.provider_detail.replace(
- ":pid",
- encodeURIComponent(props.id),
- )}
- >
- <span>{props.title}</span>
- </a>
-
- {dateStr && (
- <SmallText style={{ marginTop: 5 }}>Last synced: {dateStr}</SmallText>
- )}
- {!dateStr && (
- <SmallLightText style={{ marginTop: 5 }}>Not synced</SmallLightText>
- )}
- </div>
- <div>
- {props.status?.type === "paid" ? (
- <ExpirationText until={props.status.paidUntil} />
- ) : (
- <div>{props.status.type}</div>
- )}
- </div>
- </RowBorderGray>
- );
-}
-
-function ExpirationText({ until }: { until: Timestamp }) {
- return (
- <Fragment>
- <CenteredText> Expires in </CenteredText>
- <CenteredBoldText {...{ color: colorByTimeToExpire(until) }}>
- {" "}
- {daysUntil(until)}{" "}
- </CenteredBoldText>
- </Fragment>
- );
-}
-
-function colorByTimeToExpire(d: Timestamp) {
- if (d.t_ms === "never") return "rgb(28, 184, 65)";
- const months = differenceInMonths(d.t_ms, new Date());
- return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)";
-}
-
-function daysUntil(d: Timestamp) {
- if (d.t_ms === "never") return undefined;
- const duration = intervalToDuration({
- start: d.t_ms,
- end: new Date(),
- });
- const str = formatDuration(duration, {
- delimiter: ", ",
- format: [
- duration?.years
- ? "years"
- : duration?.months
- ? "months"
- : duration?.days
- ? "days"
- : duration.hours
- ? "hours"
- : "minutes",
- ],
- });
- return `${str}`;
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
index 80203f6d3..a4988cf2d 100644
--- a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
@@ -158,7 +158,7 @@ export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
requiresUserInput: false,
},
{
- available: "COL:2000",
+ available: "TESTKUDOS:2000",
hasPendingTransactions: false,
pendingIncoming: "USD:0",
pendingOutgoing: "USD:0",
diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
index a23c81cd1..008f30cb6 100644
--- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
@@ -14,194 +14,77 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import {
- amountFractionalBase,
- Amounts,
- Balance,
- i18n,
-} from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import {
- ButtonPrimary,
- ErrorBox,
- Middle,
- PopupBox,
-} from "../components/styled/index";
-import { BalancesHook, useBalances } from "../hooks/useBalances";
-import { PageLink, renderAmount } from "../renderHtml";
+import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { BalanceTable } from "../components/BalanceTable";
+import { ButtonPrimary, ErrorBox } from "../components/styled/index";
+import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
+import { PageLink } from "../renderHtml";
+import * as wxApi from "../wxApi";
export function BalancePage({
goToWalletManualWithdraw,
}: {
goToWalletManualWithdraw: () => void;
}): VNode {
- const balance = useBalances();
+ const state = useAsyncAsHook(wxApi.getBalance);
return (
<BalanceView
- balance={balance}
+ balance={state}
Linker={PageLink}
goToWalletManualWithdraw={goToWalletManualWithdraw}
/>
);
}
export interface BalanceViewProps {
- balance: BalancesHook;
+ balance: HookResponse<BalancesResponse>;
Linker: typeof PageLink;
goToWalletManualWithdraw: () => void;
}
-function formatPending(entry: Balance): VNode {
- let incoming: VNode | undefined;
- let payment: VNode | undefined;
-
- // const available = Amounts.parseOrThrow(entry.available);
- const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
- const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
-
- if (!Amounts.isZero(pendingIncoming)) {
- incoming = (
- <span>
- <i18n.Translate>
- <span style={{ color: "darkgreen" }} title="incoming amount">
- {"+"}
- {renderAmount(entry.pendingIncoming)}
- </span>{" "}
- </i18n.Translate>
- </span>
- );
- }
- if (!Amounts.isZero(pendingOutgoing)) {
- payment = (
- <span>
- <i18n.Translate>
- <span style={{ color: "darkred" }} title="outgoing amount">
- {"-"}
- {renderAmount(entry.pendingOutgoing)}
- </span>{" "}
- </i18n.Translate>
- </span>
- );
- }
-
- const l = [incoming, payment].filter((x) => x !== undefined);
- if (l.length === 0) {
- return <span />;
- }
-
- if (l.length === 1) {
- return <span>{l}</span>;
- }
- return (
- <span>
- {l[0]}, {l[1]}
- </span>
- );
-}
-
export function BalanceView({
balance,
Linker,
goToWalletManualWithdraw,
}: BalanceViewProps): VNode {
- function Content(): VNode {
- if (!balance) {
- return <span />;
- }
+ if (!balance) {
+ return <div>Loading...</div>;
+ }
- if (balance.hasError) {
- return (
- <section>
- <ErrorBox>{balance.message}</ErrorBox>
- <p>
- Click <Linker pageName="welcome">here</Linker> for help and
- diagnostics.
- </p>
- </section>
- );
- }
- if (balance.response.balances.length === 0) {
- return (
- <section data-expanded>
- <Middle>
- <p>
- <i18n.Translate>
- You have no balance to show. Need some{" "}
- <Linker pageName="/welcome">help</Linker> getting started?
- </i18n.Translate>
- </p>
- </Middle>
- </section>
- );
- }
+ if (balance.hasError) {
return (
- <section data-expanded data-centered>
- <table style={{ width: "100%" }}>
- {balance.response.balances.map((entry, idx) => {
- const av = Amounts.parseOrThrow(entry.available);
- // Create our number formatter.
- let formatter;
- try {
- formatter = new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: av.currency,
- currencyDisplay: "symbol",
- // These options are needed to round to whole numbers if that's what you want.
- //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
- //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501)
- });
- } catch {
- formatter = new Intl.NumberFormat("en-US", {
- // style: 'currency',
- // currency: av.currency,
- // These options are needed to round to whole numbers if that's what you want.
- //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
- //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501)
- });
- }
-
- const v = formatter.format(
- av.value + av.fraction / amountFractionalBase,
- );
- const fontSize =
- v.length < 8 ? "3em" : v.length < 13 ? "2em" : "1em";
- return (
- <tr key={idx}>
- <td
- style={{
- height: 50,
- fontSize,
- width: "60%",
- textAlign: "right",
- padding: 0,
- }}
- >
- {v}
- </td>
- <td style={{ maxWidth: "2em", overflowX: "hidden" }}>
- {av.currency}
- </td>
- <td style={{ fontSize: "small", color: "gray" }}>
- {formatPending(entry)}
- </td>
- </tr>
- );
- })}
- </table>
- </section>
+ <Fragment>
+ <ErrorBox>{balance.message}</ErrorBox>
+ <p>
+ Click <Linker pageName="welcome">here</Linker> for help and
+ diagnostics.
+ </p>
+ </Fragment>
+ );
+ }
+ if (balance.response.balances.length === 0) {
+ return (
+ <Fragment>
+ <p>
+ <i18n.Translate>
+ You have no balance to show. Need some{" "}
+ <Linker pageName="/welcome">help</Linker> getting started?
+ </i18n.Translate>
+ </p>
+ </Fragment>
);
}
return (
- <PopupBox>
- {/* <section> */}
- <Content />
- {/* </section> */}
- <footer>
- <div />
+ <Fragment>
+ <section>
+ <BalanceTable balances={balance.response.balances} />
+ </section>
+ <footer style={{ justifyContent: "space-around" }}>
<ButtonPrimary onClick={goToWalletManualWithdraw}>
Withdraw
</ButtonPrimary>
</footer>
- </PopupBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/popup/Debug.tsx b/packages/taler-wallet-webextension/src/popup/Debug.tsx
index b0e8543fc..8b5d41657 100644
--- a/packages/taler-wallet-webextension/src/popup/Debug.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Debug.tsx
@@ -16,7 +16,7 @@
import { h, VNode } from "preact";
import { Diagnostics } from "../components/Diagnostics";
-import { useDiagnostics } from "../hooks/useDiagnostics.js";
+import { useDiagnostics } from "../hooks/useDiagnostics";
import * as wxApi from "../wxApi";
export function DeveloperPage(): VNode {
diff --git a/packages/taler-wallet-webextension/src/popup/History.stories.tsx b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
index 95f4a547a..43d39da82 100644
--- a/packages/taler-wallet-webextension/src/popup/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
@@ -55,6 +55,7 @@ const exampleData = {
type: TransactionType.Withdrawal,
exchangeBaseUrl: "http://exchange.demo.taler.net",
withdrawalDetails: {
+ reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
confirmed: false,
exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
type: WithdrawalType.ManualTransfer,
diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx b/packages/taler-wallet-webextension/src/popup/History.tsx
index 2228271dc..b23b4781f 100644
--- a/packages/taler-wallet-webextension/src/popup/History.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.tsx
@@ -21,18 +21,18 @@ import {
Transaction,
TransactionsResponse,
} from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { PopupBox } from "../components/styled";
import { TransactionItem } from "../components/TransactionItem";
-import { useBalances } from "../hooks/useBalances";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import * as wxApi from "../wxApi";
export function HistoryPage(): VNode {
const [transactions, setTransactions] = useState<
TransactionsResponse | undefined
>(undefined);
- const balance = useBalances();
+ const balance = useAsyncAsHook(wxApi.getBalance);
const balanceWithoutError = balance?.hasError
? []
: balance?.response.balances || [];
@@ -57,7 +57,7 @@ export function HistoryPage(): VNode {
);
}
-function amountToString(c: AmountString) {
+function amountToString(c: AmountString): string {
const idx = c.indexOf(":");
return `${c.substring(idx + 1)} ${c.substring(0, idx)}`;
}
@@ -68,18 +68,18 @@ export function HistoryView({
}: {
list: Transaction[];
balances: Balance[];
-}) {
+}): VNode {
const multiCurrency = balances.length > 1;
return (
- <PopupBox noPadding>
+ <Fragment>
{balances.length > 0 && (
<header>
{multiCurrency ? (
<div class="title">
Balance:{" "}
<ul style={{ margin: 0 }}>
- {balances.map((b) => (
- <li>{b.available}</li>
+ {balances.map((b, i) => (
+ <li key={i}>{b.available}</li>
))}
</ul>
</div>
@@ -113,8 +113,10 @@ export function HistoryView({
rel="noopener noreferrer"
style={{ color: "darkgreen", textDecoration: "none" }}
href={
+ // eslint-disable-next-line no-undef
chrome.extension
- ? chrome.extension.getURL(`/static/wallet.html#/history`)
+ ? // eslint-disable-next-line no-undef
+ chrome.extension.getURL(`/static/wallet.html#/history`)
: "#"
}
>
@@ -122,6 +124,6 @@ export function HistoryView({
</a>
)}
</footer>
- </PopupBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx b/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx
deleted file mode 100644
index 0cff7f75f..000000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx
+++ /dev/null
@@ -1,51 +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 { createExample } from "../test-utils";
-import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage";
-
-export default {
- title: "popup/backup/confirm",
- component: TestedComponent,
- argTypes: {
- onRetry: { action: "onRetry" },
- onDelete: { action: "onDelete" },
- onBack: { action: "onBack" },
- },
-};
-
-export const DemoService = createExample(TestedComponent, {
- url: "https://sync.demo.taler.net/",
- provider: {
- annual_fee: "KUDOS:0.1",
- storage_limit_in_megabytes: 20,
- supported_protocol_version: "1",
- },
-});
-
-export const FreeService = createExample(TestedComponent, {
- url: "https://sync.taler:9667/",
- provider: {
- annual_fee: "ARS:0",
- storage_limit_in_megabytes: 20,
- supported_protocol_version: "1",
- },
-});
diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx
deleted file mode 100644
index 55686ee97..000000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx
+++ /dev/null
@@ -1,244 +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/>
- */
-
-import {
- Amounts,
- BackupBackupProviderTerms,
- canonicalizeBaseUrl,
- i18n,
-} from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { Checkbox } from "../components/Checkbox";
-import { ErrorMessage } from "../components/ErrorMessage";
-import {
- Button,
- ButtonPrimary,
- Input,
- LightText,
- PopupBox,
- SmallLightText,
-} from "../components/styled/index";
-import * as wxApi from "../wxApi";
-
-interface Props {
- currency: string;
- onBack: () => void;
-}
-
-function getJsonIfOk(r: Response) {
- if (r.ok) {
- return r.json();
- } else {
- if (r.status >= 400 && r.status < 500) {
- throw new Error(`URL may not be right: (${r.status}) ${r.statusText}`);
- } else {
- throw new Error(
- `Try another server: (${r.status}) ${
- r.statusText || "internal server error"
- }`,
- );
- }
- }
-}
-
-export function ProviderAddPage({ onBack }: Props): VNode {
- const [verifying, setVerifying] = useState<
- | { url: string; name: string; provider: BackupBackupProviderTerms }
- | undefined
- >(undefined);
-
- async function getProviderInfo(
- url: string,
- ): Promise<BackupBackupProviderTerms> {
- return fetch(`${url}config`)
- .catch((e) => {
- throw new Error(`Network error`);
- })
- .then(getJsonIfOk);
- }
-
- if (!verifying) {
- return (
- <SetUrlView
- onCancel={onBack}
- onVerify={(url) => getProviderInfo(url)}
- onConfirm={(url, name) =>
- getProviderInfo(url)
- .then((provider) => {
- setVerifying({ url, name, provider });
- })
- .catch((e) => e.message)
- }
- />
- );
- }
- return (
- <ConfirmProviderView
- provider={verifying.provider}
- url={verifying.url}
- onCancel={() => {
- setVerifying(undefined);
- }}
- onConfirm={() => {
- wxApi.addBackupProvider(verifying.url, verifying.name).then(onBack);
- }}
- />
- );
-}
-
-export interface SetUrlViewProps {
- initialValue?: string;
- onCancel: () => void;
- onVerify: (s: string) => Promise<BackupBackupProviderTerms | undefined>;
- onConfirm: (url: string, name: string) => Promise<string | undefined>;
- withError?: string;
-}
-
-export function SetUrlView({
- initialValue,
- onCancel,
- onVerify,
- onConfirm,
- withError,
-}: SetUrlViewProps) {
- const [value, setValue] = useState<string>(initialValue || "");
- const [urlError, setUrlError] = useState(false);
- const [name, setName] = useState<string | undefined>(undefined);
- const [error, setError] = useState<string | undefined>(withError);
- useEffect(() => {
- try {
- const url = canonicalizeBaseUrl(value);
- onVerify(url)
- .then((r) => {
- setUrlError(false);
- setName(new URL(url).hostname);
- })
- .catch(() => {
- setUrlError(true);
- setName(undefined);
- });
- } catch {
- setUrlError(true);
- setName(undefined);
- }
- }, [value]);
- return (
- <PopupBox>
- <section>
- <h1> Add backup provider</h1>
- <ErrorMessage
- title={error && "Could not get provider information"}
- description={error}
- />
- <LightText> Backup providers may charge for their service</LightText>
- <p>
- <Input invalid={urlError}>
- <label>URL</label>
- <input
- type="text"
- placeholder="https://"
- value={value}
- onChange={(e) => setValue(e.currentTarget.value)}
- />
- </Input>
- <Input>
- <label>Name</label>
- <input
- type="text"
- disabled={name === undefined}
- value={name}
- onChange={(e) => setName(e.currentTarget.value)}
- />
- </Input>
- </p>
- </section>
- <footer>
- <Button onClick={onCancel}>
- <i18n.Translate> &lt; Back</i18n.Translate>
- </Button>
- <ButtonPrimary
- disabled={!value && !urlError}
- onClick={() => {
- const url = canonicalizeBaseUrl(value);
- return onConfirm(url, name!).then((r) =>
- r ? setError(r) : undefined,
- );
- }}
- >
- <i18n.Translate>Next</i18n.Translate>
- </ButtonPrimary>
- </footer>
- </PopupBox>
- );
-}
-
-export interface ConfirmProviderViewProps {
- provider: BackupBackupProviderTerms;
- url: string;
- onCancel: () => void;
- onConfirm: () => void;
-}
-export function ConfirmProviderView({
- url,
- provider,
- onCancel,
- onConfirm,
-}: ConfirmProviderViewProps) {
- const [accepted, setAccepted] = useState(false);
-
- return (
- <PopupBox>
- <section>
- <h1>Review terms of service</h1>
- <div>
- Provider URL:{" "}
- <a href={url} target="_blank">
- {url}
- </a>
- </div>
- <SmallLightText>
- Please review and accept this provider's terms of service
- </SmallLightText>
- <h2>1. Pricing</h2>
- <p>
- {Amounts.isZero(provider.annual_fee)
- ? "free of charge"
- : `${provider.annual_fee} per year of service`}
- </p>
- <h2>2. Storage</h2>
- <p>
- {provider.storage_limit_in_megabytes} megabytes of storage per year of
- service
- </p>
- <Checkbox
- label="Accept terms of service"
- name="terms"
- onToggle={() => setAccepted((old) => !old)}
- enabled={accepted}
- />
- </section>
- <footer>
- <Button onClick={onCancel}>
- <i18n.Translate> &lt; Back</i18n.Translate>
- </Button>
- <ButtonPrimary disabled={!accepted} onClick={onConfirm}>
- <i18n.Translate>Add provider</i18n.Translate>
- </ButtonPrimary>
- </footer>
- </PopupBox>
- );
-}
diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx b/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx
deleted file mode 100644
index 9a2f97051..000000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx
+++ /dev/null
@@ -1,51 +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 { createExample } from "../test-utils";
-import { SetUrlView as TestedComponent } from "./ProviderAddPage";
-
-export default {
- title: "popup/backup/add",
- component: TestedComponent,
- argTypes: {
- onRetry: { action: "onRetry" },
- onDelete: { action: "onDelete" },
- onBack: { action: "onBack" },
- },
-};
-
-export const Initial = createExample(TestedComponent, {});
-
-export const WithValue = createExample(TestedComponent, {
- initialValue: "sync.demo.taler.net",
-});
-
-export const WithConnectionError = createExample(TestedComponent, {
- withError: "Network error",
-});
-
-export const WithClientError = createExample(TestedComponent, {
- withError: "URL may not be right: (404) Not Found",
-});
-
-export const WithServerError = createExample(TestedComponent, {
- withError: "Try another server: (500) Internal Server Error",
-});
diff --git a/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx b/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx
deleted file mode 100644
index fab21398a..000000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx
+++ /dev/null
@@ -1,235 +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 { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
-import { createExample } from "../test-utils";
-import { ProviderView as TestedComponent } from "./ProviderDetailPage";
-
-export default {
- title: "popup/backup/details",
- component: TestedComponent,
- argTypes: {
- onRetry: { action: "onRetry" },
- onDelete: { action: "onDelete" },
- onBack: { action: "onBack" },
- },
-};
-
-export const Active = createExample(TestedComponent, {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp: {
- t_ms: 1625063925078,
- },
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: {
- t_ms: 1656599921000,
- },
- },
- terms: {
- annualFee: "EUR:1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const ActiveErrorSync = createExample(TestedComponent, {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp: {
- t_ms: 1625063925078,
- },
- lastAttemptedBackupTimestamp: {
- t_ms: 1625063925078,
- },
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: {
- t_ms: 1656599921000,
- },
- },
- lastError: {
- code: 2002,
- details: "details",
- hint: "error hint from the server",
- message: "message",
- },
- terms: {
- annualFee: "EUR:1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const ActiveBackupProblemUnreadable = createExample(TestedComponent, {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp: {
- t_ms: 1625063925078,
- },
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: {
- t_ms: 1656599921000,
- },
- },
- backupProblem: {
- type: "backup-unreadable",
- },
- terms: {
- annualFee: "EUR:1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const ActiveBackupProblemDevice = createExample(TestedComponent, {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp: {
- t_ms: 1625063925078,
- },
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: {
- t_ms: 1656599921000,
- },
- },
- backupProblem: {
- type: "backup-conflicting-device",
- myDeviceId: "my-device-id",
- otherDeviceId: "other-device-id",
- backupTimestamp: {
- t_ms: 1656599921000,
- },
- },
- terms: {
- annualFee: "EUR:1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const InactiveUnpaid = createExample(TestedComponent, {
- info: {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Unpaid,
- },
- terms: {
- annualFee: "EUR:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const InactiveInsufficientBalance = createExample(TestedComponent, {
- info: {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.InsufficientBalance,
- },
- terms: {
- annualFee: "EUR:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const InactivePending = createExample(TestedComponent, {
- info: {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Pending,
- },
- terms: {
- annualFee: "EUR:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const ActiveTermsChanged = createExample(TestedComponent, {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.TermsChanged,
- paidUntil: {
- t_ms: 1656599921000,
- },
- newTerms: {
- annualFee: "EUR:10",
- storageLimitInMegabytes: 8,
- supportedProtocolVersion: "0.0",
- },
- oldTerms: {
- annualFee: "EUR:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- terms: {
- annualFee: "EUR:0.1",
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
diff --git a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
deleted file mode 100644
index 9617c9a41..000000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
+++ /dev/null
@@ -1,278 +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 { i18n, Timestamp } from "@gnu-taler/taler-util";
-import {
- ProviderInfo,
- ProviderPaymentStatus,
- ProviderPaymentType,
-} from "@gnu-taler/taler-wallet-core";
-import { format, formatDuration, intervalToDuration } from "date-fns";
-import { Fragment, VNode, h } from "preact";
-import { ErrorMessage } from "../components/ErrorMessage";
-import {
- Button,
- ButtonDestructive,
- ButtonPrimary,
- PaymentStatus,
- PopupBox,
- SmallLightText,
-} from "../components/styled";
-import { useProviderStatus } from "../hooks/useProviderStatus";
-
-interface Props {
- pid: string;
- onBack: () => void;
-}
-
-export function ProviderDetailPage({ pid, onBack }: Props): VNode {
- const status = useProviderStatus(pid);
- if (!status) {
- return (
- <div>
- <i18n.Translate>Loading...</i18n.Translate>
- </div>
- );
- }
- if (!status.info) {
- onBack();
- return <div />;
- }
- return (
- <ProviderView
- info={status.info}
- onSync={status.sync}
- onDelete={() => status.remove().then(onBack)}
- onBack={onBack}
- onExtend={() => {
- null;
- }}
- />
- );
-}
-
-export interface ViewProps {
- info: ProviderInfo;
- onDelete: () => void;
- onSync: () => void;
- onBack: () => void;
- onExtend: () => void;
-}
-
-export function ProviderView({
- info,
- onDelete,
- onSync,
- onBack,
- onExtend,
-}: ViewProps): VNode {
- const lb = info?.lastSuccessfulBackupTimestamp;
- const isPaid =
- info.paymentStatus.type === ProviderPaymentType.Paid ||
- info.paymentStatus.type === ProviderPaymentType.TermsChanged;
- return (
- <PopupBox>
- <Error info={info} />
- <header>
- <h3>
- {info.name}{" "}
- <SmallLightText>{info.syncProviderBaseUrl}</SmallLightText>
- </h3>
- <PaymentStatus color={isPaid ? "rgb(28, 184, 65)" : "rgb(202, 60, 60)"}>
- {isPaid ? "Paid" : "Unpaid"}
- </PaymentStatus>
- </header>
- <section>
- <p>
- <b>Last backup:</b>{" "}
- {lb == null || lb.t_ms == "never"
- ? "never"
- : format(lb.t_ms, "dd MMM yyyy")}{" "}
- </p>
- <ButtonPrimary onClick={onSync}>
- <i18n.Translate>Back up</i18n.Translate>
- </ButtonPrimary>
- {info.terms && (
- <Fragment>
- <p>
- <b>Provider fee:</b> {info.terms && info.terms.annualFee} per year
- </p>
- </Fragment>
- )}
- <p>{descriptionByStatus(info.paymentStatus)}</p>
- <ButtonPrimary disabled onClick={onExtend}>
- <i18n.Translate>Extend</i18n.Translate>
- </ButtonPrimary>
-
- {info.paymentStatus.type === ProviderPaymentType.TermsChanged && (
- <div>
- <p>
- <i18n.Translate>
- terms has changed, extending the service will imply accepting
- the new terms of service
- </i18n.Translate>
- </p>
- <table>
- <thead>
- <tr>
- <td></td>
- <td>
- <i18n.Translate>old</i18n.Translate>
- </td>
- <td> -&gt;</td>
- <td>
- <i18n.Translate>new</i18n.Translate>
- </td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>
- <i18n.Translate>fee</i18n.Translate>
- </td>
- <td>{info.paymentStatus.oldTerms.annualFee}</td>
- <td>-&gt;</td>
- <td>{info.paymentStatus.newTerms.annualFee}</td>
- </tr>
- <tr>
- <td>
- <i18n.Translate>storage</i18n.Translate>
- </td>
- <td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td>
- <td>-&gt;</td>
- <td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td>
- </tr>
- </tbody>
- </table>
- </div>
- )}
- </section>
- <footer>
- <Button onClick={onBack}>
- <i18n.Translate> &lt; back</i18n.Translate>
- </Button>
- <div>
- <ButtonDestructive onClick={onDelete}>
- <i18n.Translate>remove provider</i18n.Translate>
- </ButtonDestructive>
- </div>
- </footer>
- </PopupBox>
- );
-}
-
-function daysSince(d?: Timestamp) {
- if (!d || d.t_ms === "never") return "never synced";
- const duration = intervalToDuration({
- start: d.t_ms,
- end: new Date(),
- });
- const str = formatDuration(duration, {
- delimiter: ", ",
- format: [
- duration?.years
- ? i18n.str`years`
- : duration?.months
- ? i18n.str`months`
- : duration?.days
- ? i18n.str`days`
- : duration?.hours
- ? i18n.str`hours`
- : duration?.minutes
- ? i18n.str`minutes`
- : i18n.str`seconds`,
- ],
- });
- return `synced ${str} ago`;
-}
-
-function Error({ info }: { info: ProviderInfo }) {
- if (info.lastError) {
- return <ErrorMessage title={info.lastError.hint} />;
- }
- if (info.backupProblem) {
- switch (info.backupProblem.type) {
- case "backup-conflicting-device":
- return (
- <ErrorMessage
- title={
- <Fragment>
- <i18n.Translate>
- There is conflict with another backup from{" "}
- <b>{info.backupProblem.otherDeviceId}</b>
- </i18n.Translate>
- </Fragment>
- }
- />
- );
- case "backup-unreadable":
- return <ErrorMessage title="Backup is not readable" />;
- default:
- return (
- <ErrorMessage
- title={
- <Fragment>
- <i18n.Translate>
- Unknown backup problem: {JSON.stringify(info.backupProblem)}
- </i18n.Translate>
- </Fragment>
- }
- />
- );
- }
- }
- return null;
-}
-
-function colorByStatus(status: ProviderPaymentType) {
- switch (status) {
- case ProviderPaymentType.InsufficientBalance:
- return "rgb(223, 117, 20)";
- case ProviderPaymentType.Unpaid:
- return "rgb(202, 60, 60)";
- case ProviderPaymentType.Paid:
- return "rgb(28, 184, 65)";
- case ProviderPaymentType.Pending:
- return "gray";
- case ProviderPaymentType.InsufficientBalance:
- return "rgb(202, 60, 60)";
- case ProviderPaymentType.TermsChanged:
- return "rgb(202, 60, 60)";
- }
-}
-
-function descriptionByStatus(status: ProviderPaymentStatus) {
- switch (status.type) {
- // return i18n.str`no enough balance to make the payment`
- // return i18n.str`not paid yet`
- case ProviderPaymentType.Paid:
- case ProviderPaymentType.TermsChanged:
- if (status.paidUntil.t_ms === "never") {
- return i18n.str`service paid`;
- } else {
- return (
- <Fragment>
- <b>Backup valid until:</b>{" "}
- {format(status.paidUntil.t_ms, "dd MMM yyyy")}
- </Fragment>
- );
- }
- case ProviderPaymentType.Unpaid:
- case ProviderPaymentType.InsufficientBalance:
- case ProviderPaymentType.Pending:
- return "";
- }
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx b/packages/taler-wallet-webextension/src/popup/Settings.tsx
index 3b83f0762..0a3f777d5 100644
--- a/packages/taler-wallet-webextension/src/popup/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx
@@ -14,26 +14,34 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { i18n } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
+import { ExchangeListItem, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
import { Checkbox } from "../components/Checkbox";
-import { EditableText } from "../components/EditableText";
-import { SelectList } from "../components/SelectList";
-import { PopupBox } from "../components/styled";
+import { ButtonPrimary } from "../components/styled";
import { useDevContext } from "../context/devContext";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
import { useLang } from "../hooks/useLang";
+// import { strings as messages } from "../i18n/strings";
+import * as wxApi from "../wxApi";
export function SettingsPage(): VNode {
const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
const { devMode, toggleDevMode } = useDevContext();
const { name, update } = useBackupDeviceName();
const [lang, changeLang] = useLang();
+ const exchangesHook = useAsyncAsHook(wxApi.listExchanges);
+
return (
<SettingsView
lang={lang}
changeLang={changeLang}
+ knownExchanges={
+ !exchangesHook || exchangesHook.hasError
+ ? []
+ : exchangesHook.response.exchanges
+ }
deviceName={name}
setDeviceName={update}
permissionsEnabled={permissionsEnabled}
@@ -53,36 +61,59 @@ export interface ViewProps {
togglePermissions: () => void;
developerMode: boolean;
toggleDeveloperMode: () => void;
+ knownExchanges: Array<ExchangeListItem>;
}
-import { strings as messages } from "../i18n/strings";
-
-type LangsNames = {
- [P in keyof typeof messages]: string;
-};
+// type LangsNames = {
+// [P in keyof typeof messages]: string;
+// };
-const names: LangsNames = {
- es: "Español [es]",
- en: "English [en]",
- fr: "Français [fr]",
- de: "Deutsch [de]",
- sv: "Svenska [sv]",
- it: "Italiano [it]",
-};
+// const names: LangsNames = {
+// es: "Español [es]",
+// en: "English [en]",
+// fr: "Français [fr]",
+// de: "Deutsch [de]",
+// sv: "Svenska [sv]",
+// it: "Italiano [it]",
+// };
export function SettingsView({
- lang,
- changeLang,
- deviceName,
- setDeviceName,
+ knownExchanges,
+ // lang,
+ // changeLang,
+ // deviceName,
+ // setDeviceName,
permissionsEnabled,
togglePermissions,
developerMode,
toggleDeveloperMode,
}: ViewProps): VNode {
return (
- <PopupBox>
+ <Fragment>
<section>
+ <h2>
+ <i18n.Translate>Known exchanges</i18n.Translate>
+ </h2>
+ {!knownExchanges || !knownExchanges.length ? (
+ <div>No exchange yet!</div>
+ ) : (
+ <Fragment>
+ <table>
+ {knownExchanges.map((e, idx) => (
+ <tr key={idx}>
+ <td>{e.currency}</td>
+ <td>
+ <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a>
+ </td>
+ </tr>
+ ))}
+ </table>
+ </Fragment>
+ )}
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
+ <div />
+ <ButtonPrimary>Manage exchange</ButtonPrimary>
+ </div>
{/* <h2><i18n.Translate>Wallet</i18n.Translate></h2> */}
{/* <SelectList
value={lang}
@@ -124,14 +155,16 @@ export function SettingsView({
rel="noopener noreferrer"
style={{ color: "darkgreen", textDecoration: "none" }}
href={
+ // eslint-disable-next-line no-undef
chrome.extension
- ? chrome.extension.getURL(`/static/wallet.html#/settings`)
+ ? // eslint-disable-next-line no-undef
+ chrome.extension.getURL(`/static/wallet.html#/settings`)
: "#"
}
>
VIEW MORE SETTINGS
</a>
</footer>
- </PopupBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
index cbdcbeb15..b2220e37b 100644
--- a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
+++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
@@ -20,12 +20,8 @@
*/
import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
-import {
- ButtonPrimary,
- ButtonSuccess,
- PopupBox,
-} from "../components/styled/index";
-import { h } from "preact";
+import { Fragment, h } from "preact";
+import { ButtonPrimary, ButtonSuccess } from "../components/styled/index";
export interface Props {
url: string;
@@ -35,7 +31,7 @@ export interface Props {
export function TalerActionFound({ url, onDismiss }: Props) {
const uriType = classifyTalerUri(url);
return (
- <PopupBox>
+ <Fragment>
<section>
<h1>Taler Action </h1>
{uriType === TalerUriType.TalerPay && (
@@ -109,7 +105,7 @@ export function TalerActionFound({ url, onDismiss }: Props) {
<div />
<ButtonPrimary onClick={() => onDismiss()}> Dismiss </ButtonPrimary>
</footer>
- </PopupBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index a5723ccb5..d0c79f6d4 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -25,16 +25,17 @@ import { createHashHistory } from "history";
import { render, h } from "preact";
import Router, { route, Route } from "preact-router";
import { useEffect } from "preact/hooks";
+import { PopupBox } from "./components/styled";
import { DevContextProvider } from "./context/devContext";
import { useTalerActionURL } from "./hooks/useTalerActionURL";
import { strings } from "./i18n/strings";
import { Pages, WalletNavBar } from "./NavigationBar";
-import { BackupPage } from "./popup/BackupPage";
+import { BackupPage } from "./wallet/BackupPage";
import { BalancePage } from "./popup/BalancePage";
import { DeveloperPage } from "./popup/Debug";
import { HistoryPage } from "./popup/History";
-import { ProviderAddPage } from "./popup/ProviderAddPage";
-import { ProviderDetailPage } from "./popup/ProviderDetailPage";
+import { ProviderAddPage } from "./wallet/ProviderAddPage";
+import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
import { SettingsPage } from "./popup/Settings";
import { TalerActionFound } from "./popup/TalerActionFound";
@@ -72,7 +73,7 @@ function Application() {
<div>
<DevContextProvider>
<WalletNavBar />
- <div style={{ width: 400, height: 290 }}>
+ <PopupBox>
<Router history={createHashHistory()}>
<Route path={Pages.dev} component={DeveloperPage} />
@@ -128,15 +129,17 @@ function Application() {
/>
<Route default component={Redirect} to={Pages.balance} />
</Router>
- </div>
+ </PopupBox>
</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;
diff --git a/packages/taler-wallet-webextension/src/svg/index.tsx b/packages/taler-wallet-webextension/src/svg/index.tsx
new file mode 100644
index 000000000..34ff7767c
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/svg/index.tsx
@@ -0,0 +1,40 @@
+import { h, VNode } from "preact";
+
+export const CopyIcon = (): VNode => (
+ <svg
+ aria-hidden="true"
+ height="10"
+ viewBox="0 0 16 16"
+ version="1.1"
+ width="10"
+ data-view-component="true"
+ class="octicon octicon-copy"
+ style="display: inline-block;"
+ >
+ <path
+ fill-rule="evenodd"
+ d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"
+ />
+ <path
+ fill-rule="evenodd"
+ d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"
+ />
+ </svg>
+);
+
+export const CopiedIcon = (): VNode => (
+ <svg
+ aria-hidden="true"
+ height="8"
+ viewBox="0 0 16 16"
+ version="1.1"
+ width="8"
+ data-view-component="true"
+ class="octicon octicon-check color-fg-success"
+ >
+ <path
+ fill-rule="evenodd"
+ d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
+ />
+ </svg>
+);
diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
index f0ae38e0f..0b0af25ab 100644
--- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
@@ -35,7 +35,6 @@ import {
RowBorderGray,
SmallLightText,
SmallText,
- WalletBox,
} from "../components/styled";
import { useBackupStatus } from "../hooks/useBackupStatus";
import { Pages } from "../NavigationBar";
@@ -70,7 +69,7 @@ export function BackupView({
onSyncAll,
}: ViewProps): VNode {
return (
- <WalletBox>
+ <Fragment>
<section>
{providers.map((provider, idx) => (
<BackupLayout
@@ -106,7 +105,7 @@ export function BackupView({
</div>
</footer>
)}
- </WalletBox>
+ </Fragment>
);
}
@@ -155,7 +154,7 @@ function BackupLayout(props: TransactionLayoutProps): VNode {
);
}
-function ExpirationText({ until }: { until: Timestamp }) {
+function ExpirationText({ until }: { until: Timestamp }): VNode {
return (
<Fragment>
<CenteredText> Expires in </CenteredText>
@@ -167,14 +166,14 @@ function ExpirationText({ until }: { until: Timestamp }) {
);
}
-function colorByTimeToExpire(d: Timestamp) {
+function colorByTimeToExpire(d: Timestamp): string {
if (d.t_ms === "never") return "rgb(28, 184, 65)";
const months = differenceInMonths(d.t_ms, new Date());
return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)";
}
-function daysUntil(d: Timestamp) {
- if (d.t_ms === "never") return undefined;
+function daysUntil(d: Timestamp): string {
+ if (d.t_ms === "never") return "";
const duration = intervalToDuration({
start: d.t_ms,
end: new Date(),
diff --git a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
index 9a2847670..04d79a5ea 100644
--- a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
@@ -14,27 +14,23 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import {
- amountFractionalBase,
- Amounts,
- Balance,
- BalancesResponse,
- i18n,
-} from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import { ButtonPrimary, Centered, WalletBox } from "../components/styled/index";
-import { BalancesHook, useBalances } from "../hooks/useBalances";
-import { PageLink, renderAmount } from "../renderHtml";
+import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { BalanceTable } from "../components/BalanceTable";
+import { ButtonPrimary, ErrorBox } from "../components/styled/index";
+import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
+import { PageLink } from "../renderHtml";
+import * as wxApi from "../wxApi";
export function BalancePage({
goToWalletManualWithdraw,
}: {
goToWalletManualWithdraw: () => void;
}): VNode {
- const balance = useBalances();
+ const state = useAsyncAsHook(wxApi.getBalance);
return (
<BalanceView
- balance={balance}
+ balance={state}
Linker={PageLink}
goToWalletManualWithdraw={goToWalletManualWithdraw}
/>
@@ -42,7 +38,7 @@ export function BalancePage({
}
export interface BalanceViewProps {
- balance: BalancesHook;
+ balance: HookResponse<BalancesResponse>;
Linker: typeof PageLink;
goToWalletManualWithdraw: () => void;
}
@@ -53,18 +49,18 @@ export function BalanceView({
goToWalletManualWithdraw,
}: BalanceViewProps): VNode {
if (!balance) {
- return <span />;
+ return <div>Loading...</div>;
}
if (balance.hasError) {
return (
- <div>
- <p>{i18n.str`Error: could not retrieve balance information.`}</p>
+ <Fragment>
+ <ErrorBox>{balance.message}</ErrorBox>
<p>
Click <Linker pageName="welcome">here</Linker> for help and
diagnostics.
</p>
- </div>
+ </Fragment>
);
}
if (balance.response.balances.length === 0) {
@@ -77,81 +73,17 @@ export function BalanceView({
</p>
);
}
- return (
- <ShowBalances
- wallet={balance.response}
- onWithdraw={goToWalletManualWithdraw}
- />
- );
-}
-
-function formatPending(entry: Balance): VNode {
- let incoming: VNode | undefined;
- let payment: VNode | undefined;
-
- // const available = Amounts.parseOrThrow(entry.available);
- const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
- // const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
-
- if (!Amounts.isZero(pendingIncoming)) {
- incoming = (
- <span>
- <i18n.Translate>
- <span style={{ color: "darkgreen" }}>
- {"+"}
- {renderAmount(entry.pendingIncoming)}
- </span>{" "}
- incoming
- </i18n.Translate>
- </span>
- );
- }
-
- const l = [incoming, payment].filter((x) => x !== undefined);
- if (l.length === 0) {
- return <span />;
- }
-
- if (l.length === 1) {
- return <span>({l})</span>;
- }
- return (
- <span>
- ({l[0]}, {l[1]})
- </span>
- );
-}
-function ShowBalances({
- wallet,
- onWithdraw,
-}: {
- wallet: BalancesResponse;
- onWithdraw: () => void;
-}): VNode {
return (
- <WalletBox>
+ <Fragment>
<section>
- <Centered>
- {wallet.balances.map((entry) => {
- const av = Amounts.parseOrThrow(entry.available);
- const v = av.value + av.fraction / amountFractionalBase;
- return (
- <p key={av.currency}>
- <span>
- <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
- <span>{av.currency}</span>
- </span>
- {formatPending(entry)}
- </p>
- );
- })}
- </Centered>
+ <BalanceTable balances={balance.response.balances} />
</section>
- <footer>
- <div />
- <ButtonPrimary onClick={onWithdraw}>Withdraw</ButtonPrimary>
+ <footer style={{ justifyContent: "space-around" }}>
+ <ButtonPrimary onClick={goToWalletManualWithdraw}>
+ Withdraw
+ </ButtonPrimary>
</footer>
- </WalletBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
index 300e9cd57..e4955e376 100644
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
@@ -34,6 +34,10 @@ const exchangeList = {
"http://exchange.tal": "EUR",
};
+export const WithoutAnyExchangeKnown = createExample(TestedComponent, {
+ exchangeList: {},
+});
+
export const InitialState = createExample(TestedComponent, {
exchangeList,
});
diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
index 140ac2d40..1bceabd20 100644
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
@@ -19,17 +19,19 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { AmountJson, Amounts } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { AmountJson, Amounts, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { ErrorMessage } from "../components/ErrorMessage";
import { SelectList } from "../components/SelectList";
import {
+ BoldLight,
ButtonPrimary,
+ ButtonSuccess,
+ Centered,
Input,
InputWithLabel,
LightText,
- WalletBox,
} from "../components/styled";
export interface Props {
@@ -82,11 +84,23 @@ export function CreateManualWithdraw({
}
if (!initialExchange) {
- return <div>There is no known exchange where to withdraw, add one</div>;
+ return (
+ <Centered style={{ marginTop: 100 }}>
+ <BoldLight>No exchange configured</BoldLight>
+ <ButtonSuccess
+ //FIXME: add exchange feature
+ onClick={() => {
+ null;
+ }}
+ >
+ <i18n.Translate>Add exchange</i18n.Translate>
+ </ButtonSuccess>
+ </Centered>
+ );
}
return (
- <WalletBox>
+ <Fragment>
<section>
<ErrorMessage
title={error && "Can't create the reserve"}
@@ -145,6 +159,6 @@ export function CreateManualWithdraw({
Start withdrawal
</ButtonPrimary>
</footer>
- </WalletBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
index 9ae3ac3bd..0f471ac30 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
@@ -57,6 +57,7 @@ const exampleData = {
type: TransactionType.Withdrawal,
exchangeBaseUrl: "http://exchange.demo.taler.net",
withdrawalDetails: {
+ reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
confirmed: false,
exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
type: WithdrawalType.ManualTransfer,
diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx
index 6b1a21852..bc8ef734a 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.tsx
@@ -17,42 +17,37 @@
import {
AmountString,
Balance,
+ NotificationType,
Transaction,
- TransactionsResponse,
} from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { DateSeparator, WalletBox } from "../components/styled";
+import { DateSeparator } from "../components/styled";
import { Time } from "../components/Time";
import { TransactionItem } from "../components/TransactionItem";
-import { useBalances } from "../hooks/useBalances";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import * as wxApi from "../wxApi";
export function HistoryPage(): VNode {
- const [transactions, setTransactions] = useState<
- TransactionsResponse | undefined
- >(undefined);
- const balance = useBalances();
+ 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 transactionQuery = useAsyncAsHook(wxApi.getTransactions, [
+ NotificationType.WithdrawGroupFinished,
+ ]);
- if (!transactions) {
+ if (!transactionQuery) {
return <div>Loading ...</div>;
}
+ if (transactionQuery.hasError) {
+ return <div>There was an error loading the transactions.</div>;
+ }
return (
<HistoryView
balances={balanceWithoutError}
- list={[...transactions.transactions].reverse()}
+ list={[...transactionQuery.response.transactions].reverse()}
/>
);
}
@@ -87,7 +82,7 @@ export function HistoryView({
const multiCurrency = balances.length > 1;
return (
- <WalletBox noPadding>
+ <Fragment>
{balances.length > 0 && (
<header>
{balances.length === 1 && (
@@ -128,6 +123,6 @@ export function HistoryView({
);
})}
</section>
- </WalletBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
index 1af4e8d8d..88d5f1722 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
@@ -23,9 +23,9 @@ import {
AmountJson,
Amounts,
} from "@gnu-taler/taler-util";
-import { ReserveCreated } from "./ReserveCreated.js";
+import { ReserveCreated } from "./ReserveCreated";
import { route } from "preact-router";
-import { Pages } from "../NavigationBar.js";
+import { Pages } from "../NavigationBar";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
export function ManualWithdrawPage(): VNode {
@@ -39,7 +39,7 @@ export function ManualWithdrawPage(): VNode {
>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
- const knownExchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
+ const state = useAsyncAsHook(() => wxApi.listExchanges());
async function doCreate(
exchangeBaseUrl: string,
@@ -75,10 +75,13 @@ export function ManualWithdrawPage(): VNode {
);
}
- if (!knownExchangesHook || knownExchangesHook.hasError) {
- return <div>No Known exchanges</div>;
+ if (!state) {
+ return <div>loading...</div>;
}
- const exchangeList = knownExchangesHook.response.exchanges.reduce(
+ if (state.hasError) {
+ return <div>There was an error getting the known exchanges</div>;
+ }
+ const exchangeList = state.response.exchanges.reduce(
(p, c) => ({
...p,
[c.exchangeBaseUrl]: c.currency,
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
index 1c7fdc829..41852e38c 100644
--- a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
@@ -20,7 +20,7 @@ import {
canonicalizeBaseUrl,
i18n,
} from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
+import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { Checkbox } from "../components/Checkbox";
import { ErrorMessage } from "../components/ErrorMessage";
@@ -29,7 +29,6 @@ import {
ButtonPrimary,
Input,
LightText,
- WalletBox,
SmallLightText,
} from "../components/styled/index";
import * as wxApi from "../wxApi";
@@ -64,7 +63,7 @@ export function ProviderAddPage({ onBack }: Props): VNode {
async function getProviderInfo(
url: string,
): Promise<BackupBackupProviderTerms> {
- return fetch(`${url}config`)
+ return fetch(new URL("config", url).href)
.catch((e) => {
throw new Error(`Network error`);
})
@@ -137,7 +136,7 @@ export function SetUrlView({
}
}, [value]);
return (
- <WalletBox>
+ <Fragment>
<section>
<h1> Add backup provider</h1>
<ErrorMessage
@@ -182,7 +181,7 @@ export function SetUrlView({
<i18n.Translate>Next</i18n.Translate>
</ButtonPrimary>
</footer>
- </WalletBox>
+ </Fragment>
);
}
@@ -201,7 +200,7 @@ export function ConfirmProviderView({
const [accepted, setAccepted] = useState(false);
return (
- <WalletBox>
+ <Fragment>
<section>
<h1>Review terms of service</h1>
<div>
@@ -239,6 +238,6 @@ export function ConfirmProviderView({
<i18n.Translate>Add provider</i18n.Translate>
</ButtonPrimary>
</footer>
- </WalletBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
index 1c14c6e0a..d14429ee5 100644
--- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
@@ -28,34 +28,62 @@ import {
ButtonPrimary,
PaymentStatus,
SmallLightText,
- WalletBox,
} from "../components/styled";
import { Time } from "../components/Time";
-import { useProviderStatus } from "../hooks/useProviderStatus";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
+import * as wxApi from "../wxApi";
interface Props {
pid: string;
onBack: () => void;
}
-export function ProviderDetailPage({ pid, onBack }: Props): VNode {
- const status = useProviderStatus(pid);
- if (!status) {
+export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode {
+ async function getProviderInfo(): Promise<ProviderInfo | null> {
+ //create a first list of backup info by currency
+ const status = await wxApi.getBackupInfo();
+
+ const providers = status.providers.filter(
+ (p) => p.syncProviderBaseUrl === providerURL,
+ );
+ return providers.length ? providers[0] : null;
+ }
+
+ const state = useAsyncAsHook(getProviderInfo);
+
+ if (!state) {
return (
<div>
<i18n.Translate>Loading...</i18n.Translate>
</div>
);
}
- if (!status.info) {
+ if (state.hasError) {
+ return (
+ <div>
+ <i18n.Translate>
+ There was an error loading the provider detail for "{providerURL}"
+ </i18n.Translate>
+ </div>
+ );
+ }
+
+ if (state.response === null) {
onBack();
- return <div />;
+ return (
+ <div>
+ <i18n.Translate>
+ There is not known provider with url "{providerURL}". Redirecting
+ back...
+ </i18n.Translate>
+ </div>
+ );
}
return (
<ProviderView
- info={status.info}
- onSync={status.sync}
- onDelete={() => status.remove().then(onBack)}
+ info={state.response}
+ onSync={async () => wxApi.syncOneProvider(providerURL)}
+ onDelete={async () => wxApi.syncOneProvider(providerURL).then(onBack)}
onBack={onBack}
onExtend={() => {
null;
@@ -84,7 +112,7 @@ export function ProviderView({
info.paymentStatus.type === ProviderPaymentType.Paid ||
info.paymentStatus.type === ProviderPaymentType.TermsChanged;
return (
- <WalletBox>
+ <Fragment>
<Error info={info} />
<header>
<h3>
@@ -167,35 +195,10 @@ export function ProviderView({
</ButtonDestructive>
</div>
</footer>
- </WalletBox>
+ </Fragment>
);
}
-// function daysSince(d?: Timestamp): string {
-// if (!d || d.t_ms === "never") return "never synced";
-// const duration = intervalToDuration({
-// start: d.t_ms,
-// end: new Date(),
-// });
-// const str = formatDuration(duration, {
-// delimiter: ", ",
-// format: [
-// duration?.years
-// ? i18n.str`years`
-// : duration?.months
-// ? i18n.str`months`
-// : duration?.days
-// ? i18n.str`days`
-// : duration?.hours
-// ? i18n.str`hours`
-// : duration?.minutes
-// ? i18n.str`minutes`
-// : i18n.str`seconds`,
-// ],
-// });
-// return `synced ${str} ago`;
-// }
-
function Error({ info }: { info: ProviderInfo }): VNode {
if (info.lastError) {
return <ErrorMessage title={info.lastError.hint} />;
@@ -234,23 +237,6 @@ function Error({ info }: { info: ProviderInfo }): VNode {
return <Fragment />;
}
-// function colorByStatus(status: ProviderPaymentType): string {
-// switch (status) {
-// case ProviderPaymentType.InsufficientBalance:
-// return "rgb(223, 117, 20)";
-// case ProviderPaymentType.Unpaid:
-// return "rgb(202, 60, 60)";
-// case ProviderPaymentType.Paid:
-// return "rgb(28, 184, 65)";
-// case ProviderPaymentType.Pending:
-// return "gray";
-// // case ProviderPaymentType.InsufficientBalance:
-// // return "rgb(202, 60, 60)";
-// case ProviderPaymentType.TermsChanged:
-// return "rgb(202, 60, 60)";
-// }
-// }
-
function descriptionByStatus(status: ProviderPaymentStatus): VNode {
switch (status.type) {
// return i18n.str`no enough balance to make the payment`
diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
index a72026ab8..075126dc8 100644
--- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
@@ -1,18 +1,8 @@
-import {
- AmountJson,
- Amounts,
- parsePaytoUri,
- PaytoUri,
-} from "@gnu-taler/taler-util";
+import { AmountJson, Amounts, parsePaytoUri } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
+import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType";
import { QR } from "../components/QR";
-import {
- ButtonDestructive,
- ButtonPrimary,
- WalletBox,
- WarningBox,
-} from "../components/styled";
+import { ButtonDestructive, WarningBox } from "../components/styled";
export interface Props {
reservePub: string;
payto: string;
@@ -21,92 +11,6 @@ export interface Props {
onBack: () => void;
}
-interface BankDetailsProps {
- payto: PaytoUri;
- exchangeBaseUrl: string;
- subject: string;
- amount: string;
-}
-
-function Row({
- name,
- value,
- literal,
-}: {
- name: string;
- value: string;
- literal?: boolean;
-}): VNode {
- const [copied, setCopied] = useState(false);
- function copyText(): void {
- navigator.clipboard.writeText(value);
- setCopied(true);
- }
- useEffect(() => {
- setTimeout(() => {
- setCopied(false);
- }, 1000);
- }, [copied]);
- return (
- <tr>
- <td>
- {!copied ? (
- <ButtonPrimary small onClick={copyText}>
- &nbsp; Copy &nbsp;
- </ButtonPrimary>
- ) : (
- <ButtonPrimary small disabled>
- Copied
- </ButtonPrimary>
- )}
- </td>
- <td>
- <b>{name}</b>
- </td>
- {literal ? (
- <td>
- <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
- {value}
- </pre>
- </td>
- ) : (
- <td>{value}</td>
- )}
- </tr>
- );
-}
-
-function BankDetailsByPaytoType({
- payto,
- subject,
- exchangeBaseUrl,
- amount,
-}: BankDetailsProps): VNode {
- const firstPart = !payto.isKnown ? (
- <Fragment>
- <Row name="Account" value={payto.targetPath} />
- <Row name="Exchange" value={exchangeBaseUrl} />
- </Fragment>
- ) : payto.targetType === "x-taler-bank" ? (
- <Fragment>
- <Row name="Bank host" value={payto.host} />
- <Row name="Bank account" value={payto.account} />
- <Row name="Exchange" value={exchangeBaseUrl} />
- </Fragment>
- ) : payto.targetType === "iban" ? (
- <Fragment>
- <Row name="IBAN" value={payto.iban} />
- <Row name="Exchange" value={exchangeBaseUrl} />
- </Fragment>
- ) : undefined;
- return (
- <table>
- {firstPart}
- <Row name="Amount" value={amount} />
- <Row name="Subject" value={subject} literal />
- </table>
- );
-}
export function ReserveCreated({
reservePub,
payto,
@@ -120,11 +24,12 @@ export function ReserveCreated({
return <div>could not parse payto uri from exchange {payto}</div>;
}
return (
- <WalletBox>
+ <Fragment>
<section>
- <h1>Bank transfer details</h1>
+ <h1>Exchange is ready for withdrawal!</h1>
<p>
- Please wire <b>{Amounts.stringify(amount)}</b> to:
+ To complete the process you need to wire{" "}
+ <b>{Amounts.stringify(amount)}</b> to the exchange bank account
</p>
<BankDetailsByPaytoType
amount={Amounts.stringify(amount)}
@@ -132,14 +37,14 @@ export function ReserveCreated({
payto={paytoURI}
subject={reservePub}
/>
- </section>
- <section>
<p>
<WarningBox>
Make sure to use the correct subject, otherwise the money will not
arrive in this wallet.
</WarningBox>
</p>
+ </section>
+ <section>
<p>
Alternative, you can also scan this QR code or open{" "}
<a href={payto}>this link</a> if you have a banking app installed that
@@ -149,8 +54,10 @@ export function ReserveCreated({
</section>
<footer>
<div />
- <ButtonDestructive onClick={onBack}>Cancel withdraw</ButtonDestructive>
+ <ButtonDestructive onClick={onBack}>
+ Cancel withdrawal
+ </ButtonDestructive>
</footer>
- </WalletBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
index 8d8f3cdbc..586d7b53e 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -15,16 +15,15 @@
*/
import { ExchangeListItem, i18n } from "@gnu-taler/taler-util";
-import { VNode, h, Fragment } from "preact";
+import { Fragment, h, VNode } from "preact";
import { Checkbox } from "../components/Checkbox";
-import { EditableText } from "../components/EditableText";
-import { SelectList } from "../components/SelectList";
-import { ButtonPrimary, ButtonSuccess, WalletBox } from "../components/styled";
+import { ButtonPrimary } from "../components/styled";
import { useDevContext } from "../context/devContext";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
-import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { useLang } from "../hooks/useLang";
+// import { strings as messages } from "../i18n/strings";
import * as wxApi from "../wxApi";
export function SettingsPage(): VNode {
@@ -32,7 +31,7 @@ export function SettingsPage(): VNode {
const { devMode, toggleDevMode } = useDevContext();
const { name, update } = useBackupDeviceName();
const [lang, changeLang] = useLang();
- const exchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
+ const exchangesHook = useAsyncAsHook(wxApi.listExchanges);
return (
<SettingsView
@@ -65,34 +64,32 @@ export interface ViewProps {
knownExchanges: Array<ExchangeListItem>;
}
-import { strings as messages } from "../i18n/strings";
-
-type LangsNames = {
- [P in keyof typeof messages]: string;
-};
+// type LangsNames = {
+// [P in keyof typeof messages]: string;
+// };
-const names: LangsNames = {
- es: "Español [es]",
- en: "English [en]",
- fr: "Français [fr]",
- de: "Deutsch [de]",
- sv: "Svenska [sv]",
- it: "Italiano [it]",
-};
+// const names: LangsNames = {
+// es: "Español [es]",
+// en: "English [en]",
+// fr: "Français [fr]",
+// de: "Deutsch [de]",
+// sv: "Svenska [sv]",
+// it: "Italiano [it]",
+// };
export function SettingsView({
knownExchanges,
- lang,
- changeLang,
- deviceName,
- setDeviceName,
+ // lang,
+ // changeLang,
+ // deviceName,
+ // setDeviceName,
permissionsEnabled,
togglePermissions,
developerMode,
toggleDeveloperMode,
}: ViewProps): VNode {
return (
- <WalletBox>
+ <Fragment>
<section>
<h2>
<i18n.Translate>Known exchanges</i18n.Translate>
@@ -100,17 +97,23 @@ export function SettingsView({
{!knownExchanges || !knownExchanges.length ? (
<div>No exchange yet!</div>
) : (
- <table>
- {knownExchanges.map((e) => (
- <tr>
- <td>{e.currency}</td>
- <td>
- <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a>
- </td>
- </tr>
- ))}
- </table>
+ <Fragment>
+ <table>
+ {knownExchanges.map((e, idx) => (
+ <tr key={idx}>
+ <td>{e.currency}</td>
+ <td>
+ <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a>
+ </td>
+ </tr>
+ ))}
+ </table>
+ </Fragment>
)}
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
+ <div />
+ <ButtonPrimary>Manage exchange</ButtonPrimary>
+ </div>
<h2>
<i18n.Translate>Permissions</i18n.Translate>
@@ -131,6 +134,6 @@ export function SettingsView({
onToggle={toggleDeveloperMode}
/>
</section>
- </WalletBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
index c9a3f47cb..a25e2ca80 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
@@ -61,6 +61,7 @@ const exampleData = {
exchangeBaseUrl: "http://exchange.taler",
withdrawalDetails: {
confirmed: false,
+ reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
type: WithdrawalType.ManualTransfer,
},
@@ -134,10 +135,49 @@ export const WithdrawError = createExample(TestedComponent, {
},
});
-export const WithdrawPending = createExample(TestedComponent, {
- transaction: { ...exampleData.withdraw, pending: true },
+export const WithdrawPendingManual = createExample(TestedComponent, {
+ transaction: {
+ ...exampleData.withdraw,
+ withdrawalDetails: {
+ type: WithdrawalType.ManualTransfer,
+ exchangePaytoUris: ["payto://iban/asdasdasd"],
+ reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+ },
+ pending: true,
+ },
});
+export const WithdrawPendingTalerBankUnconfirmed = createExample(
+ TestedComponent,
+ {
+ transaction: {
+ ...exampleData.withdraw,
+ withdrawalDetails: {
+ type: WithdrawalType.TalerBankIntegrationApi,
+ confirmed: false,
+ reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+ bankConfirmationUrl: "http://bank.demo.taler.net",
+ },
+ pending: true,
+ },
+ },
+);
+
+export const WithdrawPendingTalerBankConfirmed = createExample(
+ TestedComponent,
+ {
+ transaction: {
+ ...exampleData.withdraw,
+ withdrawalDetails: {
+ type: WithdrawalType.TalerBankIntegrationApi,
+ confirmed: true,
+ reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+ },
+ pending: true,
+ },
+ },
+);
+
export const Payment = createExample(TestedComponent, {
transaction: exampleData.payment,
});
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 1472efb40..02c78320a 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -18,62 +18,80 @@ import {
AmountLike,
Amounts,
i18n,
+ NotificationType,
+ parsePaytoUri,
Transaction,
TransactionType,
+ WithdrawalType,
} from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
import { route } from "preact-router";
-import { useEffect, useState } from "preact/hooks";
+import { useState } from "preact/hooks";
import emptyImg from "../../static/img/empty.png";
+import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType";
import { ErrorMessage } from "../components/ErrorMessage";
import { Part } from "../components/Part";
import {
Button,
ButtonDestructive,
ButtonPrimary,
+ CenteredDialog,
+ InfoBox,
ListOfProducts,
+ Overlay,
RowBorderGray,
SmallLightText,
- WalletBox,
WarningBox,
} from "../components/styled";
import { Time } from "../components/Time";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { Pages } from "../NavigationBar";
import * as wxApi from "../wxApi";
export function TransactionPage({ tid }: { tid: string }): VNode {
- const [transaction, setTransaction] = useState<Transaction | undefined>(
- undefined,
- );
+ async function getTransaction(): Promise<Transaction> {
+ const res = await wxApi.getTransactions();
+ const ts = res.transactions.filter((t) => t.transactionId === tid);
+ if (ts.length > 1) throw Error("more than one transaction with this id");
+ if (ts.length === 1) {
+ return ts[0];
+ }
+ throw Error("no transaction found");
+ }
- useEffect(() => {
- const fetchData = async (): Promise<void> => {
- const res = await wxApi.getTransactions();
- const ts = res.transactions.filter((t) => t.transactionId === tid);
- if (ts.length === 1) {
- setTransaction(ts[0]);
- } else {
- route(Pages.history);
- }
- };
- fetchData();
- }, [tid]);
+ const state = useAsyncAsHook(getTransaction, [
+ NotificationType.WithdrawGroupFinished,
+ ]);
- if (!transaction) {
+ if (!state) {
return (
<div>
<i18n.Translate>Loading ...</i18n.Translate>
</div>
);
}
+
+ if (state.hasError) {
+ route(Pages.history);
+ return (
+ <div>
+ <i18n.Translate>
+ There was an error. Redirecting into the history page
+ </i18n.Translate>
+ </div>
+ );
+ }
+
+ function goToHistory(): void {
+ route(Pages.history);
+ }
+
return (
<TransactionView
- transaction={transaction}
- onDelete={() => wxApi.deleteTransaction(tid).then(() => history.go(-1))}
- onRetry={() => wxApi.retryTransaction(tid).then(() => history.go(-1))}
- onBack={() => {
- route(Pages.history);
- }}
+ transaction={state.response}
+ onDelete={() => wxApi.deleteTransaction(tid).then(goToHistory)}
+ onRetry={() => wxApi.retryTransaction(tid).then(goToHistory)}
+ onBack={goToHistory}
/>
);
}
@@ -91,16 +109,28 @@ export function TransactionView({
onRetry,
onBack,
}: WalletTransactionProps): VNode {
- function TransactionTemplate({ children }: { children: VNode[] }): VNode {
+ const [confirmBeforeForget, setConfirmBeforeForget] = useState(false);
+ function doCheckBeforeForget(): void {
+ if (
+ transaction.pending &&
+ transaction.type === TransactionType.Withdrawal
+ ) {
+ setConfirmBeforeForget(true);
+ } else {
+ onDelete();
+ }
+ }
+ function TransactionTemplate({
+ children,
+ }: {
+ children: ComponentChildren;
+ }): VNode {
return (
- <WalletBox>
+ <Fragment>
<section style={{ padding: 8, textAlign: "center" }}>
<ErrorMessage title={transaction?.error?.hint} />
{transaction.pending && (
- <WarningBox>
- This transaction is not completed
- <a href="">more info...</a>
- </WarningBox>
+ <WarningBox>This transaction is not completed</WarningBox>
)}
</section>
<section>
@@ -116,12 +146,12 @@ export function TransactionView({
<i18n.Translate>retry</i18n.Translate>
</ButtonPrimary>
) : null}
- <ButtonDestructive onClick={onDelete}>
+ <ButtonDestructive onClick={doCheckBeforeForget}>
<i18n.Translate> Forget </i18n.Translate>
</ButtonDestructive>
</div>
</footer>
- </WalletBox>
+ </Fragment>
);
}
@@ -138,27 +168,119 @@ export function TransactionView({
).amount;
return (
<TransactionTemplate>
+ {confirmBeforeForget ? (
+ <Overlay>
+ <CenteredDialog>
+ <header>Caution!</header>
+ <section>
+ If you have already wired money to the exchange you will loose
+ the chance to get the coins form it.
+ </section>
+ <footer>
+ <Button onClick={() => setConfirmBeforeForget(false)}>
+ <i18n.Translate> Cancel </i18n.Translate>
+ </Button>
+
+ <ButtonDestructive onClick={onDelete}>
+ <i18n.Translate> Confirm </i18n.Translate>
+ </ButtonDestructive>
+ </footer>
+ </CenteredDialog>
+ </Overlay>
+ ) : undefined}
<h2>Withdrawal</h2>
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
- <br />
- <Part
- big
- title="Total withdrawn"
- text={amountToString(transaction.amountEffective)}
- kind="positive"
- />
- <Part
- big
- title="Chosen amount"
- text={amountToString(transaction.amountRaw)}
- kind="neutral"
- />
- <Part
- big
- title="Exchange fee"
- text={amountToString(fee)}
- kind="negative"
- />
+ {transaction.pending ? (
+ transaction.withdrawalDetails.type ===
+ WithdrawalType.ManualTransfer ? (
+ <Fragment>
+ <BankDetailsByPaytoType
+ amount={amountToString(transaction.amountRaw)}
+ exchangeBaseUrl={transaction.exchangeBaseUrl}
+ payto={parsePaytoUri(
+ transaction.withdrawalDetails.exchangePaytoUris[0],
+ )}
+ subject={transaction.withdrawalDetails.reservePub}
+ />
+ <p>
+ <WarningBox>
+ Make sure to use the correct subject, otherwise the money will
+ not arrive in this wallet.
+ </WarningBox>
+ </p>
+ <Part
+ big
+ title="Total withdrawn"
+ text={amountToString(transaction.amountEffective)}
+ kind="positive"
+ />
+ <Part
+ big
+ title="Exchange fee"
+ text={amountToString(fee)}
+ kind="negative"
+ />
+ </Fragment>
+ ) : (
+ <Fragment>
+ {!transaction.withdrawalDetails.confirmed &&
+ transaction.withdrawalDetails.bankConfirmationUrl ? (
+ <InfoBox>
+ The bank is waiting for confirmation. Go to the
+ <a
+ href={transaction.withdrawalDetails.bankConfirmationUrl}
+ target="_blank"
+ rel="noreferrer"
+ >
+ bank site
+ </a>
+ </InfoBox>
+ ) : undefined}
+ {transaction.withdrawalDetails.confirmed && (
+ <InfoBox>Waiting for the coins to arrive</InfoBox>
+ )}
+ <Part
+ big
+ title="Total withdrawn"
+ text={amountToString(transaction.amountEffective)}
+ kind="positive"
+ />
+ <Part
+ big
+ title="Chosen amount"
+ text={amountToString(transaction.amountRaw)}
+ kind="neutral"
+ />
+ <Part
+ big
+ title="Exchange fee"
+ text={amountToString(fee)}
+ kind="negative"
+ />
+ </Fragment>
+ )
+ ) : (
+ <Fragment>
+ <Part
+ big
+ title="Total withdrawn"
+ text={amountToString(transaction.amountEffective)}
+ kind="positive"
+ />
+ <Part
+ big
+ title="Chosen amount"
+ text={amountToString(transaction.amountRaw)}
+ kind="neutral"
+ />
+ <Part
+ big
+ title="Exchange fee"
+ text={amountToString(fee)}
+ kind="negative"
+ />
+ </Fragment>
+ )}
<Part
title="Exchange"
text={new URL(transaction.exchangeBaseUrl).hostname}
diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
index a6dd040e4..b180fdd05 100644
--- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
@@ -20,13 +20,12 @@
* @author Florian Dold
*/
+import { WalletDiagnostics } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
import { Checkbox } from "../components/Checkbox";
-import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
import { Diagnostics } from "../components/Diagnostics";
-import { WalletBox } from "../components/styled";
import { useDiagnostics } from "../hooks/useDiagnostics";
-import { WalletDiagnostics } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
export function WelcomePage(): VNode {
const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
@@ -54,7 +53,7 @@ export function View({
timedOut,
}: ViewProps): VNode {
return (
- <WalletBox>
+ <Fragment>
<h1>Browser Extension Installed!</h1>
<div>
<p>Thank you for installing the wallet.</p>
@@ -75,6 +74,6 @@ export function View({
Learn how to top up your wallet balance »
</a>
</div>
- </WalletBox>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
index f097d58b5..a17550ff9 100644
--- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
@@ -22,7 +22,7 @@
import { setupI18n } from "@gnu-taler/taler-util";
import { createHashHistory } from "history";
-import { Fragment, h, render } from "preact";
+import { Fragment, h, render, VNode } from "preact";
import Router, { route, Route } from "preact-router";
import { useEffect } from "preact/hooks";
import { LogoHeader } from "./components/LogoHeader";
@@ -39,8 +39,11 @@ import { SettingsPage } from "./wallet/Settings";
import { TransactionPage } from "./wallet/Transaction";
import { WelcomePage } from "./wallet/Welcome";
import { BackupPage } from "./wallet/BackupPage";
-import { DeveloperPage } from "./popup/Debug.js";
-import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage.js";
+import { DeveloperPage } from "./popup/Debug";
+import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage";
+import { WalletBox } from "./components/styled";
+import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
+import { ProviderAddPage } from "./wallet/ProviderAddPage";
function main(): void {
try {
@@ -66,16 +69,20 @@ if (document.readyState === "loading") {
}
function withLogoAndNavBar(Component: any) {
- return (props: any) => (
- <Fragment>
- <LogoHeader />
- <WalletNavBar />
- <Component {...props} />
- </Fragment>
- );
+ return function withLogoAndNavBarComponent(props: any): VNode {
+ return (
+ <Fragment>
+ <LogoHeader />
+ <WalletNavBar />
+ <WalletBox>
+ <Component {...props} />
+ </WalletBox>
+ </Fragment>
+ );
+ };
}
-function Application() {
+function Application(): VNode {
return (
<div>
<DevContextProvider>
@@ -105,6 +112,23 @@ function Application() {
<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
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts
index 90cfd3ed6..be0fed883 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -22,39 +22,21 @@
* Imports.
*/
import {
- CoreApiResponse,
- ConfirmPayResult,
- BalancesResponse,
- TransactionsResponse,
- ApplyRefundResponse,
- PreparePayResult,
- AcceptWithdrawalResponse,
- WalletDiagnostics,
- GetWithdrawalDetailsForUriRequest,
- WithdrawUriInfoResponse,
- PrepareTipRequest,
- PrepareTipResult,
- AcceptTipRequest,
- DeleteTransactionRequest,
- RetryTransactionRequest,
- SetWalletDeviceIdRequest,
- GetExchangeWithdrawalInfo,
AcceptExchangeTosRequest,
- AcceptManualWithdrawalResult,
- AcceptManualWithdrawalRequest,
- AmountJson,
- ExchangesListRespose,
- AddExchangeRequest,
- GetExchangeTosResult,
+ AcceptManualWithdrawalResult, AcceptTipRequest, AcceptWithdrawalResponse,
+ AddExchangeRequest, ApplyRefundResponse, BalancesResponse, ConfirmPayResult,
+ CoreApiResponse, DeleteTransactionRequest, ExchangesListRespose,
+ GetExchangeTosResult, GetExchangeWithdrawalInfo,
+ GetWithdrawalDetailsForUriRequest, NotificationType, PreparePayResult, PrepareTipRequest,
+ PrepareTipResult, RetryTransactionRequest,
+ SetWalletDeviceIdRequest, TransactionsResponse, WalletDiagnostics, WithdrawUriInfoResponse
} from "@gnu-taler/taler-util";
import {
- AddBackupProviderRequest,
- BackupProviderState,
- OperationFailedError,
- RemoveBackupProviderRequest,
+ AddBackupProviderRequest, BackupInfo, OperationFailedError,
+ RemoveBackupProviderRequest
} from "@gnu-taler/taler-wallet-core";
-import { BackupInfo } from "@gnu-taler/taler-wallet-core";
import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw";
+import { MessageFromBackend } from "./wxBackend.js";
export interface ExtendedPermissionsResponse {
newValue: boolean;
@@ -83,7 +65,9 @@ export interface UpgradeResponse {
async function callBackend(operation: string, payload: any): Promise<any> {
return new Promise<any>((resolve, reject) => {
+ // eslint-disable-next-line no-undef
chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => {
+ // eslint-disable-next-line no-undef
if (chrome.runtime.lastError) {
console.log("Error calling backend");
reject(
@@ -366,10 +350,13 @@ export function acceptTip(req: AcceptTipRequest): Promise<void> {
return callBackend("acceptTip", req);
}
-export function onUpdateNotification(f: () => void): () => void {
+export function onUpdateNotification(messageType: Array<NotificationType>, doCallback: () => void): () => void {
+ // eslint-disable-next-line no-undef
const port = chrome.runtime.connect({ name: "notifications" });
- const listener = (): void => {
- f();
+ const listener = (message: MessageFromBackend): void => {
+ if (messageType.includes(message.type)) {
+ doCallback();
+ }
};
port.onMessage.addListener(listener);
return () => {
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts
index 4004f04f6..df3115246 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -39,6 +39,7 @@ import {
classifyTalerUri,
CoreApiResponse,
CoreApiResponseSuccess,
+ NotificationType,
TalerErrorCode,
TalerUriType,
WalletDiagnostics,
@@ -237,6 +238,10 @@ function makeSyncWalletRedirect(
return { redirectUrl: innerUrl.href };
}
+export type MessageFromBackend = {
+ type: NotificationType
+}
+
async function reinitWallet(): Promise<void> {
if (currentWallet) {
currentWallet.stop();
@@ -266,9 +271,10 @@ async function reinitWallet(): Promise<void> {
return;
}
wallet.addNotificationListener((x) => {
- for (const x of notificationPorts) {
+ for (const notif of notificationPorts) {
+ const message: MessageFromBackend = { type: x.type };
try {
- x.postMessage({ type: "notification" });
+ notif.postMessage(message);
} catch (e) {
console.error(e);
}