commit 0b4976601fe2ecb0462fe72ae188b5cbba06d9cc
parent d58945c830a33910dd93bc159c1ffe5d490df846
Author: Sebastian <sebasjm@gmail.com>
Date: Wed, 16 Jun 2021 18:21:03 -0300
components renaming to follow react pattern
Diffstat:
26 files changed, 1923 insertions(+), 1932 deletions(-)
diff --git a/packages/taler-wallet-webextension/src/components/DebugCheckbox.tsx b/packages/taler-wallet-webextension/src/components/DebugCheckbox.tsx
@@ -0,0 +1,47 @@
+/*
+ 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 { JSX } from "preact";
+
+export function DebugCheckbox({ enabled, onToggle }: { enabled: boolean; onToggle: () => void; }): JSX.Element {
+ return (
+ <div>
+ <input
+ checked={enabled}
+ onClick={onToggle}
+ type="checkbox"
+ id="checkbox-perm"
+ style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }} />
+ <label
+ htmlFor="checkbox-perm"
+ style={{ marginLeft: "0.5em", fontWeight: "bold" }}
+ >
+ Automatically open wallet based on page content
+ </label>
+ <span
+ style={{
+ color: "#383838",
+ fontSize: "smaller",
+ display: "block",
+ marginLeft: "2em",
+ }}
+ >
+ (Enabling this option below will make using the wallet faster, but
+ requires more permissions from your browser.)
+ </span>
+ </div>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts b/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts
@@ -0,0 +1,53 @@
+import { useState, useEffect } from "preact/hooks";
+import * as wxApi from "../wxApi";
+import { getPermissionsApi } from "../compat";
+import { extendedPermissions } from "../permissions";
+
+
+export function useExtendedPermissions(): [boolean, () => void] {
+ const [enabled, setEnabled] = useState(false);
+
+ const toggle = () => {
+ setEnabled(v => !v);
+ handleExtendedPerm(enabled).then(result => {
+ setEnabled(result);
+ });
+ };
+
+ useEffect(() => {
+ async function getExtendedPermValue(): Promise<void> {
+ const res = await wxApi.getExtendedPermissions();
+ setEnabled(res.newValue);
+ }
+ getExtendedPermValue();
+ }, []);
+ return [enabled, toggle];
+}
+
+async function handleExtendedPerm(isEnabled: boolean): Promise<boolean> {
+ let nextVal: boolean | undefined;
+
+ if (!isEnabled) {
+ const granted = await new Promise<boolean>((resolve, reject) => {
+ // We set permissions here, since apparently FF wants this to be done
+ // as the result of an input event ...
+ getPermissionsApi().request(extendedPermissions, (granted: boolean) => {
+ if (chrome.runtime.lastError) {
+ console.error("error requesting permissions");
+ console.error(chrome.runtime.lastError);
+ reject(chrome.runtime.lastError);
+ return;
+ }
+ console.log("permissions granted:", granted);
+ resolve(granted);
+ });
+ });
+ const res = await wxApi.setExtendedPermissions(granted);
+ nextVal = res.newValue;
+ } else {
+ const res = await wxApi.setExtendedPermissions(false);
+ nextVal = res.newValue;
+ }
+ console.log("new permissions applied:", nextVal ?? false);
+ return nextVal ?? false
+}
+\ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.tsx b/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.tsx
@@ -1,24 +0,0 @@
-import { useState, useEffect } from "preact/hooks";
-import * as wxApi from "../wxApi";
-import { handleExtendedPerm } from "../wallet/welcome";
-
-
-export function useExtendedPermissions(): [boolean, () => void] {
- const [enabled, setEnabled] = useState(false);
-
- const toggle = () => {
- setEnabled(v => !v);
- handleExtendedPerm(enabled).then(result => {
- setEnabled(result);
- });
- };
-
- useEffect(() => {
- async function getExtendedPermValue(): Promise<void> {
- const res = await wxApi.getExtendedPermissions();
- setEnabled(res.newValue);
- }
- getExtendedPermValue();
- }, []);
- return [enabled, toggle];
-}
diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
@@ -0,0 +1,93 @@
+import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
+import { useEffect, useState } from "preact/hooks";
+
+export function useTalerActionURL(): [string | undefined, (s: boolean) => void] {
+ const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
+ undefined
+ );
+ const [dismissed, setDismissed] = useState(false);
+ useEffect(() => {
+ async function check(): Promise<void> {
+ const talerUri = await findTalerUriInActiveTab();
+ if (talerUri) {
+ const actionUrl = actionForTalerUri(talerUri);
+ setTalerActionUrl(actionUrl);
+ }
+ }
+ check();
+ }, []);
+ const url = dismissed ? undefined : talerActionUrl;
+ return [url, setDismissed];
+}
+
+function actionForTalerUri(talerUri: string): string | undefined {
+ const uriType = classifyTalerUri(talerUri);
+ switch (uriType) {
+ case TalerUriType.TalerWithdraw:
+ return makeExtensionUrlWithParams("static/wallet.html#/withdraw", {
+ talerWithdrawUri: talerUri,
+ });
+ case TalerUriType.TalerPay:
+ return makeExtensionUrlWithParams("static/wallet.html#/pay", {
+ talerPayUri: talerUri,
+ });
+ case TalerUriType.TalerTip:
+ return makeExtensionUrlWithParams("static/wallet.html#/tip", {
+ talerTipUri: talerUri,
+ });
+ case TalerUriType.TalerRefund:
+ return makeExtensionUrlWithParams("static/wallet.html#/refund", {
+ talerRefundUri: talerUri,
+ });
+ case TalerUriType.TalerNotifyReserve:
+ // FIXME: implement
+ break;
+ default:
+ console.warn(
+ "Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
+ );
+ break;
+ }
+ return undefined;
+}
+
+function makeExtensionUrlWithParams(
+ url: string,
+ params?: { [name: string]: string | undefined },
+): string {
+ const innerUrl = new URL(chrome.extension.getURL("/" + url));
+ if (params) {
+ for (const key in params) {
+ const p = params[key];
+ if (p) {
+ innerUrl.searchParams.set(key, p);
+ }
+ }
+ }
+ return innerUrl.href;
+}
+
+async function findTalerUriInActiveTab(): Promise<string | undefined> {
+ return new Promise((resolve, reject) => {
+ chrome.tabs.executeScript(
+ {
+ code: `
+ (() => {
+ let x = document.querySelector("a[href^='taler://'") || document.querySelector("a[href^='taler+http://'");
+ return x ? x.href.toString() : null;
+ })();
+ `,
+ allFrames: false,
+ },
+ (result) => {
+ if (chrome.runtime.lastError) {
+ console.error(chrome.runtime.lastError);
+ resolve(undefined);
+ return;
+ }
+ console.log("got result", result);
+ resolve(result[0]);
+ },
+ );
+ });
+}
diff --git a/packages/taler-wallet-webextension/src/popup/Balance.tsx b/packages/taler-wallet-webextension/src/popup/Balance.tsx
@@ -0,0 +1,173 @@
+/*
+ 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 {
+ Amounts,
+ BalancesResponse,
+ Balance, i18n, AmountJson, amountFractionalBase
+} from "@gnu-taler/taler-util";
+import { Component, JSX } from "preact";
+import { PageLink, renderAmount } from "../renderHtml";
+import * as wxApi from "../wxApi";
+
+
+/**
+ * Render an amount as a large number with a small currency symbol.
+ */
+function bigAmount(amount: AmountJson): JSX.Element {
+ const v = amount.value + amount.fraction / amountFractionalBase;
+ return (
+ <span>
+ <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
+ <span>{amount.currency}</span>
+ </span>
+ );
+}
+
+function EmptyBalanceView(): JSX.Element {
+ return (
+ <p><i18n.Translate>
+ You have no balance to show. Need some{" "}
+ <PageLink pageName="/welcome">help</PageLink> getting started?
+ </i18n.Translate></p>
+ );
+}
+
+
+export class BalancePage extends Component<any, any> {
+ private balance?: BalancesResponse;
+ private gotError = false;
+ private canceler: (() => void) | undefined = undefined;
+ private unmount = false;
+ private updateBalanceRunning = false;
+
+ componentWillMount(): void {
+ this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
+ this.updateBalance();
+ }
+
+ componentWillUnmount(): void {
+ console.log("component WalletBalanceView will unmount");
+ if (this.canceler) {
+ this.canceler();
+ }
+ this.unmount = true;
+ }
+
+ async updateBalance(): Promise<void> {
+ if (this.updateBalanceRunning) {
+ return;
+ }
+ this.updateBalanceRunning = true;
+ let balance: BalancesResponse;
+ try {
+ balance = await wxApi.getBalance();
+ } catch (e) {
+ if (this.unmount) {
+ return;
+ }
+ this.gotError = true;
+ console.error("could not retrieve balances", e);
+ this.setState({});
+ return;
+ } finally {
+ this.updateBalanceRunning = false;
+ }
+ if (this.unmount) {
+ return;
+ }
+ this.gotError = false;
+ console.log("got balance", balance);
+ this.balance = balance;
+ this.setState({});
+ }
+
+ formatPending(entry: Balance): JSX.Element {
+ let incoming: JSX.Element | undefined;
+ let payment: JSX.Element | undefined;
+
+ const available = Amounts.parseOrThrow(entry.available);
+ const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
+ const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
+
+ console.log(
+ "available: ",
+ entry.pendingIncoming ? renderAmount(entry.available) : null
+ );
+ console.log(
+ "incoming: ",
+ entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null
+ );
+
+ 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>
+ );
+ }
+
+ render(): JSX.Element {
+ const wallet = this.balance;
+ if (this.gotError) {
+ return (
+ <div className="balance">
+ <p>{i18n.str`Error: could not retrieve balance information.`}</p>
+ <p>
+ Click <PageLink pageName="welcome.html">here</PageLink> for help and
+ diagnostics.
+ </p>
+ </div>
+ );
+ }
+ if (!wallet) {
+ return <span></span>;
+ }
+
+ const listing = wallet.balances.map((entry) => {
+ const av = Amounts.parseOrThrow(entry.available);
+ return (
+ <p key={av.currency}>
+ {bigAmount(av)} {this.formatPending(entry)}
+ </p>
+ );
+ });
+ return listing.length > 0 ? (
+ <div className="balance">{listing}</div>
+ ) : (
+ <EmptyBalanceView />
+ );
+ }
+}
diff --git a/packages/taler-wallet-webextension/src/popup/Debug.tsx b/packages/taler-wallet-webextension/src/popup/Debug.tsx
@@ -0,0 +1,63 @@
+/*
+ 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 { JSX } from "preact";
+import { Diagnostics } from "../components/Diagnostics";
+import * as wxApi from "../wxApi";
+
+
+export function DebugPage(props: any): JSX.Element {
+ return (
+ <div>
+ <p>Debug tools:</p>
+ <button onClick={openExtensionPage("/static/popup.html")}>wallet tab</button>
+ <br />
+ <button onClick={confirmReset}>reset</button>
+ <button onClick={reload}>reload chrome extension</button>
+ <Diagnostics />
+ </div>
+ );
+}
+
+export function reload(): void {
+ try {
+ chrome.runtime.reload();
+ window.close();
+ } catch (e) {
+ // Functionality missing in firefox, ignore!
+ }
+}
+
+export async function confirmReset(): Promise<void> {
+ if (
+ confirm(
+ "Do you want to IRREVOCABLY DESTROY everything inside your" +
+ " wallet and LOSE ALL YOUR COINS?",
+ )
+ ) {
+ await wxApi.resetDb();
+ window.close();
+ }
+}
+
+export function openExtensionPage(page: string) {
+ return () => {
+ chrome.tabs.create({
+ url: chrome.extension.getURL(page),
+ });
+ };
+}
+
diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx b/packages/taler-wallet-webextension/src/popup/History.tsx
@@ -0,0 +1,227 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { AmountString, Timestamp, Transaction, TransactionsResponse, TransactionType } from "@gnu-taler/taler-util";
+import { JSX } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import * as wxApi from "../wxApi";
+import { Pages } from "./popup";
+
+
+export function HistoryPage(props: any): JSX.Element {
+ const [transactions, setTransactions] = useState<
+ TransactionsResponse | undefined
+ >(undefined);
+
+ useEffect(() => {
+ const fetchData = async (): Promise<void> => {
+ const res = await wxApi.getTransactions();
+ setTransactions(res);
+ };
+ fetchData();
+ }, []);
+
+ if (!transactions) {
+ return <div>Loading ...</div>;
+ }
+
+ const txs = [...transactions.transactions].reverse();
+
+ return (
+ <div>
+ {txs.map((tx, i) => (
+ <TransactionItem key={i} tx={tx} />
+ ))}
+ </div>
+ );
+}
+
+function TransactionItem(props: { tx: Transaction }): JSX.Element {
+ const tx = props.tx;
+ switch (tx.type) {
+ case TransactionType.Withdrawal:
+ return (
+ <TransactionLayout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ title="Withdrawal"
+ subtitle={`via ${tx.exchangeBaseUrl}`}
+ timestamp={tx.timestamp}
+ iconPath="/static/img/ri-bank-line.svg"
+ pending={tx.pending}
+ ></TransactionLayout>
+ );
+ case TransactionType.Payment:
+ return (
+ <TransactionLayout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title="Payment"
+ subtitle={tx.info.summary}
+ timestamp={tx.timestamp}
+ iconPath="/static/img/ri-shopping-cart-line.svg"
+ pending={tx.pending}
+ ></TransactionLayout>
+ );
+ case TransactionType.Refund:
+ return (
+ <TransactionLayout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ title="Refund"
+ subtitle={tx.info.summary}
+ timestamp={tx.timestamp}
+ iconPath="/static/img/ri-refund-2-line.svg"
+ pending={tx.pending}
+ ></TransactionLayout>
+ );
+ case TransactionType.Tip:
+ return (
+ <TransactionLayout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ title="Tip"
+ subtitle={`from ${new URL(tx.merchantBaseUrl).hostname}`}
+ timestamp={tx.timestamp}
+ iconPath="/static/img/ri-hand-heart-line.svg"
+ pending={tx.pending}
+ ></TransactionLayout>
+ );
+ case TransactionType.Refresh:
+ return (
+ <TransactionLayout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ title="Refresh"
+ subtitle={`via exchange ${tx.exchangeBaseUrl}`}
+ timestamp={tx.timestamp}
+ iconPath="/static/img/ri-refresh-line.svg"
+ pending={tx.pending}
+ ></TransactionLayout>
+ );
+ case TransactionType.Deposit:
+ return (
+ <TransactionLayout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title="Refresh"
+ subtitle={`to ${tx.targetPaytoUri}`}
+ timestamp={tx.timestamp}
+ iconPath="/static/img/ri-refresh-line.svg"
+ pending={tx.pending}
+ ></TransactionLayout>
+ );
+ }
+}
+
+function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
+ const date = new Date(props.timestamp.t_ms);
+ const dateStr = date.toLocaleString([], {
+ dateStyle: "medium",
+ timeStyle: "short",
+ } as any);
+ return (
+ <div
+ style={{
+ display: "flex",
+ flexDirection: "row",
+ border: "1px solid gray",
+ borderRadius: "0.5em",
+ margin: "0.5em 0",
+ justifyContent: "space-between",
+ padding: "0.5em",
+ }}
+ >
+ <img src={props.iconPath} />
+ <div
+ style={{ display: "flex", flexDirection: "column", marginLeft: "1em" }}
+ >
+ <div style={{ fontSize: "small", color: "gray" }}>{dateStr}</div>
+ <div style={{ fontVariant: "small-caps", fontSize: "x-large" }}>
+ <a href={Pages.transaction.replace(':tid', props.id)}><span>{props.title}</span></a>
+ {props.pending ? (
+ <span style={{ color: "darkblue" }}> (Pending)</span>
+ ) : null}
+ </div>
+
+ <div>{props.subtitle}</div>
+ </div>
+ <TransactionAmount
+ pending={props.pending}
+ amount={props.amount}
+ debitCreditIndicator={props.debitCreditIndicator}
+ />
+ </div>
+ );
+}
+
+interface TransactionLayoutProps {
+ debitCreditIndicator: "debit" | "credit" | "unknown";
+ amount: AmountString | "unknown";
+ timestamp: Timestamp;
+ title: string;
+ id: string;
+ subtitle: string;
+ iconPath: string;
+ pending: boolean;
+}
+
+interface TransactionAmountProps {
+ debitCreditIndicator: "debit" | "credit" | "unknown";
+ amount: AmountString | "unknown";
+ pending: boolean;
+}
+
+function TransactionAmount(props: TransactionAmountProps): JSX.Element {
+ const [currency, amount] = props.amount.split(":");
+ let sign: string;
+ switch (props.debitCreditIndicator) {
+ case "credit":
+ sign = "+";
+ break;
+ case "debit":
+ sign = "-";
+ break;
+ case "unknown":
+ sign = "";
+ }
+ const style: JSX.AllCSSProperties = {
+ marginLeft: "auto",
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ alignSelf: "center"
+ };
+ if (props.pending) {
+ style.color = "gray";
+ }
+ return (
+ <div style={{ ...style }}>
+ <div style={{ fontSize: "x-large" }}>
+ {sign}
+ {amount}
+ </div>
+ <div>{currency}</div>
+ </div>
+ );
+}
+
diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx b/packages/taler-wallet-webextension/src/popup/Settings.tsx
@@ -0,0 +1,34 @@
+/*
+ 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 { PermissionsCheckbox } from "../components/PermissionsCheckbox";
+import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
+
+
+export function SettingsPage() {
+ const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
+ return (
+ <div>
+ <h2>Permissions</h2>
+ <PermissionsCheckbox enabled={permissionsEnabled} onToggle={togglePermissions} />
+ {/*
+ <h2>Developer mode</h2>
+ <DebugCheckbox enabled={permissionsEnabled} onToggle={togglePermissions} />
+ */}
+ </div>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx
@@ -0,0 +1,198 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import {
+ PaymentStatus,
+ TransactionCommon, TransactionDeposit, TransactionPayment,
+ TransactionRefresh, TransactionRefund, TransactionTip, TransactionType,
+ TransactionWithdrawal,
+ WithdrawalType
+} from '@gnu-taler/taler-util';
+import { FunctionalComponent } from 'preact';
+import { TransactionView as TestedComponent } from './Transaction';
+
+export default {
+ title: 'popup/transaction/details',
+ component: TestedComponent,
+ decorators: [
+ (Story: any) => <div>
+ <link key="1" rel="stylesheet" type="text/css" href="/style/pure.css" />
+ <link key="2" rel="stylesheet" type="text/css" href="/style/popup.css" />
+ <link key="3" rel="stylesheet" type="text/css" href="/style/wallet.css" />
+ <div style={{ margin: "1em", width: 400 }}>
+ <Story />
+ </div>
+ </div>
+ ],
+};
+
+const commonTransaction = {
+ amountRaw: 'USD:10',
+ amountEffective: 'USD:9',
+ pending: false,
+ timestamp: {
+ t_ms: new Date().getTime()
+ },
+ transactionId: '12',
+} as TransactionCommon
+
+const exampleData = {
+ withdraw: {
+ ...commonTransaction,
+ type: TransactionType.Withdrawal,
+ exchangeBaseUrl: 'http://exchange.taler',
+ withdrawalDetails: {
+ confirmed: false,
+ exchangePaytoUris: ['payto://x-taler-bank/bank/account'],
+ type: WithdrawalType.ManualTransfer,
+ }
+ } as TransactionWithdrawal,
+ payment: {
+ ...commonTransaction,
+ amountEffective: 'USD:11',
+ type: TransactionType.Payment,
+ info: {
+ contractTermsHash: 'ASDZXCASD',
+ merchant: {
+ name: 'the merchant',
+ },
+ orderId: '#12345',
+ products: [],
+ summary: 'the summary',
+ fulfillmentMessage: '',
+ },
+ proposalId: '1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0',
+ status: PaymentStatus.Accepted,
+ } as TransactionPayment,
+ deposit: {
+ ...commonTransaction,
+ type: TransactionType.Deposit,
+ depositGroupId: '#groupId',
+ targetPaytoUri: 'payto://x-taler-bank/bank/account',
+ } as TransactionDeposit,
+ refresh: {
+ ...commonTransaction,
+ type: TransactionType.Refresh,
+ exchangeBaseUrl: 'http://exchange.taler',
+ } as TransactionRefresh,
+ tip: {
+ ...commonTransaction,
+ type: TransactionType.Tip,
+ merchantBaseUrl: 'http://merchant.taler',
+ } as TransactionTip,
+ refund: {
+ ...commonTransaction,
+ type: TransactionType.Refund,
+ refundedTransactionId: 'payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0',
+ info: {
+ contractTermsHash: 'ASDZXCASD',
+ merchant: {
+ name: 'the merchant',
+ },
+ orderId: '#12345',
+ products: [],
+ summary: 'the summary',
+ fulfillmentMessage: '',
+ },
+ } as TransactionRefund,
+}
+
+function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) {
+ const r = (args: any) => <Component {...args} />
+ r.args = props
+ return r
+}
+
+export const NotYetLoaded = createExample(TestedComponent,{});
+
+export const Withdraw = createExample(TestedComponent,{
+ transaction: exampleData.withdraw
+});
+
+export const WithdrawPending = createExample(TestedComponent,{
+ transaction: { ...exampleData.withdraw, pending: true },
+});
+
+
+export const Payment = createExample(TestedComponent,{
+ transaction: exampleData.payment
+});
+
+export const PaymentPending = createExample(TestedComponent,{
+ transaction: { ...exampleData.payment, pending: true },
+});
+
+export const PaymentWithProducts = createExample(TestedComponent,{
+ transaction: {
+ ...exampleData.payment,
+ info: {
+ ...exampleData.payment.info,
+ products: [{
+ description: 't-shirt',
+ }, {
+ description: 'beer',
+ }]
+ }
+ } as TransactionPayment,
+});
+
+
+export const Deposit = createExample(TestedComponent,{
+ transaction: exampleData.deposit
+});
+
+export const DepositPending = createExample(TestedComponent,{
+ transaction: { ...exampleData.deposit, pending: true }
+});
+
+export const Refresh = createExample(TestedComponent,{
+ transaction: exampleData.refresh
+});
+
+export const Tip = createExample(TestedComponent,{
+ transaction: exampleData.tip
+});
+
+export const TipPending = createExample(TestedComponent,{
+ transaction: { ...exampleData.tip, pending: true }
+});
+
+export const Refund = createExample(TestedComponent,{
+ transaction: exampleData.refund
+});
+
+export const RefundPending = createExample(TestedComponent,{
+ transaction: { ...exampleData.refund, pending: true }
+});
+
+export const RefundWithProducts = createExample(TestedComponent,{
+ transaction: {
+ ...exampleData.refund,
+ info: {
+ ...exampleData.refund.info,
+ products: [{
+ description: 't-shirt',
+ }, {
+ description: 'beer',
+ }]
+ }
+ } as TransactionRefund,
+});
diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.tsx b/packages/taler-wallet-webextension/src/popup/Transaction.tsx
@@ -0,0 +1,327 @@
+/*
+ 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 { Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util";
+import { format } from "date-fns";
+import { JSX } from "preact";
+import { route } from 'preact-router';
+import { useEffect, useState } from "preact/hooks";
+import * as wxApi from "../wxApi";
+import { Pages } from "./popup";
+
+
+export function TransactionPage({ tid }: { tid: string; }): JSX.Element {
+ const [transaction, setTransaction] = useState<
+ Transaction | undefined
+ >(undefined);
+
+ 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();
+ }, []);
+
+ return <TransactionView
+ transaction={transaction}
+ onDelete={() => wxApi.deleteTransaction(tid).then(_ => history.go(-1))}
+ onBack={() => { history.go(-1); }} />;
+}
+
+export interface WalletTransactionProps {
+ transaction?: Transaction,
+ onDelete: () => void,
+ onBack: () => void,
+}
+
+export function TransactionView({ transaction, onDelete, onBack }: WalletTransactionProps) {
+ if (!transaction) {
+ return <div><i18n.Translate>Loading ...</i18n.Translate></div>;
+ }
+
+ function Footer() {
+ return <footer style={{ marginTop: 'auto', display: 'flex' }}>
+ <button onClick={onBack}><i18n.Translate>back</i18n.Translate></button>
+ <div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
+ <button onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button>
+
+ </div>
+
+ </footer>
+ }
+
+ function Pending() {
+ if (!transaction?.pending) return null
+ return <span style={{ fontWeight: 'normal', fontSize: 16, color: 'gray' }}>(pending...)</span>
+ }
+
+ if (transaction.type === TransactionType.Withdrawal) {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
+ <section>
+ <h1>Withdrawal <Pending /></h1>
+ <p>
+ From <b>{transaction.exchangeBaseUrl}</b>
+ </p>
+ <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
+ <tr>
+ <td>Amount subtracted</td>
+ <td>{transaction.amountRaw}</td>
+ </tr>
+ <tr>
+ <td>Amount received</td>
+ <td>{transaction.amountEffective}</td>
+ </tr>
+ <tr>
+ <td>Exchange fee</td>
+ <td>{Amounts.stringify(
+ Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount
+ )}</td>
+ </tr>
+ <tr>
+ <td>When</td>
+ <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+ </tr>
+ </table>
+ </section>
+ <Footer />
+ </div>
+ );
+ }
+
+ if (transaction.type === TransactionType.Payment) {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
+ <section>
+ <h1>Payment ({transaction.proposalId.substring(0, 10)}...) <Pending /></h1>
+ <p>
+ To <b>{transaction.info.merchant.name}</b>
+ </p>
+ <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
+ <tr>
+ <td>Order id</td>
+ <td>{transaction.info.orderId}</td>
+ </tr>
+ <tr>
+ <td>Summary</td>
+ <td>{transaction.info.summary}</td>
+ </tr>
+ {transaction.info.products && transaction.info.products.length > 0 &&
+ <tr>
+ <td>Products</td>
+ <td><ol style={{ margin: 0, textAlign: 'left' }}>
+ {transaction.info.products.map(p =>
+ <li>{p.description}</li>
+ )}</ol></td>
+ </tr>
+ }
+ <tr>
+ <td>Order amount</td>
+ <td>{transaction.amountRaw}</td>
+ </tr>
+ <tr>
+ <td>Order amount and fees</td>
+ <td>{transaction.amountEffective}</td>
+ </tr>
+ <tr>
+ <td>Exchange fee</td>
+ <td>{Amounts.stringify(
+ Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountEffective),
+ Amounts.parseOrThrow(transaction.amountRaw),
+ ).amount
+ )}</td>
+ </tr>
+ <tr>
+ <td>When</td>
+ <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+ </tr>
+ </table>
+ </section>
+ <Footer />
+ </div>
+ );
+ }
+
+ if (transaction.type === TransactionType.Deposit) {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
+ <section>
+ <h1>Deposit ({transaction.depositGroupId}) <Pending /></h1>
+ <p>
+ To <b>{transaction.targetPaytoUri}</b>
+ </p>
+ <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
+ <tr>
+ <td>Amount deposit</td>
+ <td>{transaction.amountRaw}</td>
+ </tr>
+ <tr>
+ <td>Amount deposit and fees</td>
+ <td>{transaction.amountEffective}</td>
+ </tr>
+ <tr>
+ <td>Exchange fee</td>
+ <td>{Amounts.stringify(
+ Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountEffective),
+ Amounts.parseOrThrow(transaction.amountRaw),
+ ).amount
+ )}</td>
+ </tr>
+ <tr>
+ <td>When</td>
+ <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+ </tr>
+ </table>
+ </section>
+ <Footer />
+ </div>
+ );
+ }
+
+ if (transaction.type === TransactionType.Refresh) {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
+ <section>
+ <h1>Refresh <Pending /></h1>
+ <p>
+ From <b>{transaction.exchangeBaseUrl}</b>
+ </p>
+ <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
+ <tr>
+ <td>Amount refreshed</td>
+ <td>{transaction.amountRaw}</td>
+ </tr>
+ <tr>
+ <td>Fees</td>
+ <td>{transaction.amountEffective}</td>
+ </tr>
+ <tr>
+ <td>When</td>
+ <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+ </tr>
+ </table>
+ </section>
+ <Footer />
+ </div>
+ );
+ }
+
+ if (transaction.type === TransactionType.Tip) {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
+ <section>
+ <h1>Tip <Pending /></h1>
+ <p>
+ From <b>{transaction.merchantBaseUrl}</b>
+ </p>
+ <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
+ <tr>
+ <td>Amount deduce</td>
+ <td>{transaction.amountRaw}</td>
+ </tr>
+ <tr>
+ <td>Amount received</td>
+ <td>{transaction.amountEffective}</td>
+ </tr>
+ <tr>
+ <td>Exchange fee</td>
+ <td>{Amounts.stringify(
+ Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount
+ )}</td>
+ </tr>
+ <tr>
+ <td>When</td>
+ <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+ </tr>
+ </table>
+ </section>
+ <Footer />
+ </div>
+ );
+ }
+
+ const TRANSACTION_FROM_REFUND = /[a-z]*:([\w]{10}).*/
+ if (transaction.type === TransactionType.Refund) {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
+ <section>
+ <h1>Refund ({TRANSACTION_FROM_REFUND.exec(transaction.refundedTransactionId)![1]}...) <Pending /></h1>
+ <p>
+ From <b>{transaction.info.merchant.name}</b>
+ </p>
+ <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
+ <tr>
+ <td>Order id</td>
+ <td>{transaction.info.orderId}</td>
+ </tr>
+ <tr>
+ <td>Summary</td>
+ <td>{transaction.info.summary}</td>
+ </tr>
+ {transaction.info.products && transaction.info.products.length > 0 &&
+ <tr>
+ <td>Products</td>
+ <td><ol>
+ {transaction.info.products.map(p =>
+ <li>{p.description}</li>
+ )}</ol></td>
+ </tr>
+ }
+ <tr>
+ <td>Amount deduce</td>
+ <td>{transaction.amountRaw}</td>
+ </tr>
+ <tr>
+ <td>Amount received</td>
+ <td>{transaction.amountEffective}</td>
+ </tr>
+ <tr>
+ <td>Exchange fee</td>
+ <td>{Amounts.stringify(
+ Amounts.sub(
+ Amounts.parseOrThrow(transaction.amountRaw),
+ Amounts.parseOrThrow(transaction.amountEffective),
+ ).amount
+ )}</td>
+ </tr>
+ <tr>
+ <td>When</td>
+ <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+ </tr>
+ </table>
+ </section>
+ <Footer />
+ </div>
+ );
+ }
+
+
+ return <div></div>
+}
diff --git a/packages/taler-wallet-webextension/src/popup/popup.stories.tsx b/packages/taler-wallet-webextension/src/popup/popup.stories.tsx
@@ -1,197 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import {
- PaymentStatus,
- TransactionCommon, TransactionDeposit, TransactionPayment,
- TransactionRefresh, TransactionRefund, TransactionTip, TransactionType,
- TransactionWithdrawal,
- WithdrawalType
-} from '@gnu-taler/taler-util';
-import { WalletTransactionView as Component } from './popup';
-
-export default {
- title: 'popup/transaction details',
- component: Component,
- decorators: [
- (Story: any) => <div>
- <link key="1" rel="stylesheet" type="text/css" href="/style/pure.css" />
- <link key="2" rel="stylesheet" type="text/css" href="/style/popup.css" />
- <link key="3" rel="stylesheet" type="text/css" href="/style/wallet.css" />
- <div style={{ margin: "1em", width: 400 }}>
- <Story />
- </div>
- </div>
- ],
-};
-
-const commonTransaction = {
- amountRaw: 'USD:10',
- amountEffective: 'USD:9',
- pending: false,
- timestamp: {
- t_ms: new Date().getTime()
- },
- transactionId: '12',
-} as TransactionCommon
-
-const exampleData = {
- withdraw: {
- ...commonTransaction,
- type: TransactionType.Withdrawal,
- exchangeBaseUrl: 'http://exchange.taler',
- withdrawalDetails: {
- confirmed: false,
- exchangePaytoUris: ['payto://x-taler-bank/bank/account'],
- type: WithdrawalType.ManualTransfer,
- }
- } as TransactionWithdrawal,
- payment: {
- ...commonTransaction,
- amountEffective: 'USD:11',
- type: TransactionType.Payment,
- info: {
- contractTermsHash: 'ASDZXCASD',
- merchant: {
- name: 'the merchant',
- },
- orderId: '#12345',
- products: [],
- summary: 'the summary',
- fulfillmentMessage: '',
- },
- proposalId: '1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0',
- status: PaymentStatus.Accepted,
- } as TransactionPayment,
- deposit: {
- ...commonTransaction,
- type: TransactionType.Deposit,
- depositGroupId: '#groupId',
- targetPaytoUri: 'payto://x-taler-bank/bank/account',
- } as TransactionDeposit,
- refresh: {
- ...commonTransaction,
- type: TransactionType.Refresh,
- exchangeBaseUrl: 'http://exchange.taler',
- } as TransactionRefresh,
- tip: {
- ...commonTransaction,
- type: TransactionType.Tip,
- merchantBaseUrl: 'http://merchant.taler',
- } as TransactionTip,
- refund: {
- ...commonTransaction,
- type: TransactionType.Refund,
- refundedTransactionId: 'payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0',
- info: {
- contractTermsHash: 'ASDZXCASD',
- merchant: {
- name: 'the merchant',
- },
- orderId: '#12345',
- products: [],
- summary: 'the summary',
- fulfillmentMessage: '',
- },
- } as TransactionRefund,
-}
-
-function dynamic<T>(props: any) {
- const r = (args: any) => <Component {...args} />
- r.args = props
- return r
-}
-
-export const NotYetLoaded = dynamic({});
-
-export const Withdraw = dynamic({
- transaction: exampleData.withdraw
-});
-
-export const WithdrawPending = dynamic({
- transaction: { ...exampleData.withdraw, pending: true },
-});
-
-
-export const Payment = dynamic({
- transaction: exampleData.payment
-});
-
-export const PaymentPending = dynamic({
- transaction: { ...exampleData.payment, pending: true },
-});
-
-export const PaymentWithProducts = dynamic({
- transaction: {
- ...exampleData.payment,
- info: {
- ...exampleData.payment.info,
- products: [{
- description: 't-shirt',
- }, {
- description: 'beer',
- }]
- }
- } as TransactionPayment,
-});
-
-
-export const Deposit = dynamic({
- transaction: exampleData.deposit
-});
-
-export const DepositPending = dynamic({
- transaction: { ...exampleData.deposit, pending: true }
-});
-
-export const Refresh = dynamic({
- transaction: exampleData.refresh
-});
-
-export const Tip = dynamic({
- transaction: exampleData.tip
-});
-
-export const TipPending = dynamic({
- transaction: { ...exampleData.tip, pending: true }
-});
-
-export const Refund = dynamic({
- transaction: exampleData.refund
-});
-
-export const RefundPending = dynamic({
- transaction: { ...exampleData.refund, pending: true }
-});
-
-export const RefundWithProducts = dynamic({
- transaction: {
- ...exampleData.refund,
- info: {
- ...exampleData.refund.info,
- products: [{
- description: 't-shirt',
- }, {
- description: 'beer',
- }]
- }
- } as TransactionRefund,
-});
diff --git a/packages/taler-wallet-webextension/src/popup/popup.tsx b/packages/taler-wallet-webextension/src/popup/popup.tsx
@@ -25,29 +25,9 @@
* Imports.
*/
import {
- AmountJson,
- Amounts,
- BalancesResponse,
- Balance,
- classifyTalerUri,
- TalerUriType,
- TransactionsResponse,
- Transaction,
- TransactionType,
- AmountString,
- Timestamp,
- amountFractionalBase,
- i18n,
+ classifyTalerUri, i18n, TalerUriType
} from "@gnu-taler/taler-util";
-import { format } from "date-fns";
-import { Component, ComponentChildren, Fragment, JSX } from "preact";
-import { route } from 'preact-router';
-import { useEffect, useState } from "preact/hooks";
-import { Diagnostics } from "../components/Diagnostics";
-import { PermissionsCheckbox } from "../components/PermissionsCheckbox";
-import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
-import { PageLink, renderAmount } from "../renderHtml";
-import * as wxApi from "../wxApi";
+import { ComponentChildren, JSX } from "preact";
export enum Pages {
balance = '/balance',
@@ -86,878 +66,3 @@ export function WalletNavBar({ current }: { current?: string }) {
);
}
-/**
- * Render an amount as a large number with a small currency symbol.
- */
-function bigAmount(amount: AmountJson): JSX.Element {
- const v = amount.value + amount.fraction / amountFractionalBase;
- return (
- <span>
- <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
- <span>{amount.currency}</span>
- </span>
- );
-}
-
-function EmptyBalanceView(): JSX.Element {
- return (
- <p><i18n.Translate>
- You have no balance to show. Need some{" "}
- <PageLink pageName="/welcome">help</PageLink> getting started?
- </i18n.Translate></p>
- );
-}
-
-export class WalletBalanceView extends Component<any, any> {
- private balance?: BalancesResponse;
- private gotError = false;
- private canceler: (() => void) | undefined = undefined;
- private unmount = false;
- private updateBalanceRunning = false;
-
- componentWillMount(): void {
- this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
- this.updateBalance();
- }
-
- componentWillUnmount(): void {
- console.log("component WalletBalanceView will unmount");
- if (this.canceler) {
- this.canceler();
- }
- this.unmount = true;
- }
-
- async updateBalance(): Promise<void> {
- if (this.updateBalanceRunning) {
- return;
- }
- this.updateBalanceRunning = true;
- let balance: BalancesResponse;
- try {
- balance = await wxApi.getBalance();
- } catch (e) {
- if (this.unmount) {
- return;
- }
- this.gotError = true;
- console.error("could not retrieve balances", e);
- this.setState({});
- return;
- } finally {
- this.updateBalanceRunning = false;
- }
- if (this.unmount) {
- return;
- }
- this.gotError = false;
- console.log("got balance", balance);
- this.balance = balance;
- this.setState({});
- }
-
- formatPending(entry: Balance): JSX.Element {
- let incoming: JSX.Element | undefined;
- let payment: JSX.Element | undefined;
-
- const available = Amounts.parseOrThrow(entry.available);
- const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
- const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
-
- console.log(
- "available: ",
- entry.pendingIncoming ? renderAmount(entry.available) : null,
- );
- console.log(
- "incoming: ",
- entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null,
- );
-
- 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>
- );
- }
-
- render(): JSX.Element {
- const wallet = this.balance;
- if (this.gotError) {
- return (
- <div className="balance">
- <p>{i18n.str`Error: could not retrieve balance information.`}</p>
- <p>
- Click <PageLink pageName="welcome.html">here</PageLink> for help and
- diagnostics.
- </p>
- </div>
- );
- }
- if (!wallet) {
- return <span></span>;
- }
- console.log(wallet);
- const listing = wallet.balances.map((entry) => {
- const av = Amounts.parseOrThrow(entry.available);
- return (
- <p key={av.currency}>
- {bigAmount(av)} {this.formatPending(entry)}
- </p>
- );
- });
- return listing.length > 0 ? (
- <div className="balance">{listing}</div>
- ) : (
- <EmptyBalanceView />
- );
- }
-}
-
-interface TransactionAmountProps {
- debitCreditIndicator: "debit" | "credit" | "unknown";
- amount: AmountString | "unknown";
- pending: boolean;
-}
-
-function TransactionAmount(props: TransactionAmountProps): JSX.Element {
- const [currency, amount] = props.amount.split(":");
- let sign: string;
- switch (props.debitCreditIndicator) {
- case "credit":
- sign = "+";
- break;
- case "debit":
- sign = "-";
- break;
- case "unknown":
- sign = "";
- }
- const style: JSX.AllCSSProperties = {
- marginLeft: "auto",
- display: "flex",
- flexDirection: "column",
- alignItems: "center",
- alignSelf: "center"
- };
- if (props.pending) {
- style.color = "gray";
- }
- return (
- <div style={{ ...style }}>
- <div style={{ fontSize: "x-large" }}>
- {sign}
- {amount}
- </div>
- <div>{currency}</div>
- </div>
- );
-}
-
-interface TransactionLayoutProps {
- debitCreditIndicator: "debit" | "credit" | "unknown";
- amount: AmountString | "unknown";
- timestamp: Timestamp;
- title: string;
- id: string;
- subtitle: string;
- iconPath: string;
- pending: boolean;
-}
-
-function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
- const date = new Date(props.timestamp.t_ms);
- const dateStr = date.toLocaleString([], {
- dateStyle: "medium",
- timeStyle: "short",
- } as any);
- return (
- <div
- style={{
- display: "flex",
- flexDirection: "row",
- border: "1px solid gray",
- borderRadius: "0.5em",
- margin: "0.5em 0",
- justifyContent: "space-between",
- padding: "0.5em",
- }}
- >
- <img src={props.iconPath} />
- <div
- style={{ display: "flex", flexDirection: "column", marginLeft: "1em" }}
- >
- <div style={{ fontSize: "small", color: "gray" }}>{dateStr}</div>
- <div style={{ fontVariant: "small-caps", fontSize: "x-large" }}>
- <a href={Pages.transaction.replace(':tid', props.id)}><span>{props.title}</span></a>
- {props.pending ? (
- <span style={{ color: "darkblue" }}> (Pending)</span>
- ) : null}
- </div>
-
- <div>{props.subtitle}</div>
- </div>
- <TransactionAmount
- pending={props.pending}
- amount={props.amount}
- debitCreditIndicator={props.debitCreditIndicator}
- />
- </div>
- );
-}
-
-function TransactionItem(props: { tx: Transaction }): JSX.Element {
- const tx = props.tx;
- switch (tx.type) {
- case TransactionType.Withdrawal:
- return (
- <TransactionLayout
- id={tx.transactionId}
- amount={tx.amountEffective}
- debitCreditIndicator={"credit"}
- title="Withdrawal"
- subtitle={`via ${tx.exchangeBaseUrl}`}
- timestamp={tx.timestamp}
- iconPath="/static/img/ri-bank-line.svg"
- pending={tx.pending}
- ></TransactionLayout>
- );
- case TransactionType.Payment:
- return (
- <TransactionLayout
- id={tx.transactionId}
- amount={tx.amountEffective}
- debitCreditIndicator={"debit"}
- title="Payment"
- subtitle={tx.info.summary}
- timestamp={tx.timestamp}
- iconPath="/static/img/ri-shopping-cart-line.svg"
- pending={tx.pending}
- ></TransactionLayout>
- );
- case TransactionType.Refund:
- return (
- <TransactionLayout
- id={tx.transactionId}
- amount={tx.amountEffective}
- debitCreditIndicator={"credit"}
- title="Refund"
- subtitle={tx.info.summary}
- timestamp={tx.timestamp}
- iconPath="/static/img/ri-refund-2-line.svg"
- pending={tx.pending}
- ></TransactionLayout>
- );
- case TransactionType.Tip:
- return (
- <TransactionLayout
- id={tx.transactionId}
- amount={tx.amountEffective}
- debitCreditIndicator={"credit"}
- title="Tip"
- subtitle={`from ${new URL(tx.merchantBaseUrl).hostname}`}
- timestamp={tx.timestamp}
- iconPath="/static/img/ri-hand-heart-line.svg"
- pending={tx.pending}
- ></TransactionLayout>
- );
- case TransactionType.Refresh:
- return (
- <TransactionLayout
- id={tx.transactionId}
- amount={tx.amountEffective}
- debitCreditIndicator={"credit"}
- title="Refresh"
- subtitle={`via exchange ${tx.exchangeBaseUrl}`}
- timestamp={tx.timestamp}
- iconPath="/static/img/ri-refresh-line.svg"
- pending={tx.pending}
- ></TransactionLayout>
- );
- case TransactionType.Deposit:
- return (
- <TransactionLayout
- id={tx.transactionId}
- amount={tx.amountEffective}
- debitCreditIndicator={"debit"}
- title="Refresh"
- subtitle={`to ${tx.targetPaytoUri}`}
- timestamp={tx.timestamp}
- iconPath="/static/img/ri-refresh-line.svg"
- pending={tx.pending}
- ></TransactionLayout>
- );
- }
-}
-
-export function WalletHistory(props: any): JSX.Element {
- const [transactions, setTransactions] = useState<
- TransactionsResponse | undefined
- >(undefined);
-
- useEffect(() => {
- const fetchData = async (): Promise<void> => {
- const res = await wxApi.getTransactions();
- setTransactions(res);
- };
- fetchData();
- }, []);
-
- if (!transactions) {
- return <div>Loading ...</div>;
- }
-
- const txs = [...transactions.transactions].reverse();
-
- return (
- <div>
- {txs.map((tx, i) => (
- <TransactionItem key={i} tx={tx} />
- ))}
- </div>
- );
-}
-
-interface WalletTransactionProps {
- transaction?: Transaction,
- onDelete: () => void,
- onBack: () => void,
-}
-
-export function WalletTransactionView({ transaction, onDelete, onBack }: WalletTransactionProps) {
- if (!transaction) {
- return <div><i18n.Translate>Loading ...</i18n.Translate></div>;
- }
-
- function Footer() {
- return <footer style={{ marginTop: 'auto', display: 'flex' }}>
- <button onClick={onBack}><i18n.Translate>back</i18n.Translate></button>
- <div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
- <button onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button>
-
- </div>
-
- </footer>
- }
-
- function Pending() {
- if (!transaction?.pending) return null
- return <span style={{ fontWeight: 'normal', fontSize: 16, color: 'gray' }}>(pending...)</span>
- }
-
- if (transaction.type === TransactionType.Withdrawal) {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
- <section>
- <h1>Withdrawal <Pending /></h1>
- <p>
- From <b>{transaction.exchangeBaseUrl}</b>
- </p>
- <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
- <tr>
- <td>Amount subtracted</td>
- <td>{transaction.amountRaw}</td>
- </tr>
- <tr>
- <td>Amount received</td>
- <td>{transaction.amountEffective}</td>
- </tr>
- <tr>
- <td>Exchange fee</td>
- <td>{Amounts.stringify(
- Amounts.sub(
- Amounts.parseOrThrow(transaction.amountRaw),
- Amounts.parseOrThrow(transaction.amountEffective),
- ).amount
- )}</td>
- </tr>
- <tr>
- <td>When</td>
- <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
- </tr>
- </table>
- </section>
- <Footer />
- </div>
- );
- }
-
- if (transaction.type === TransactionType.Payment) {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
- <section>
- <h1>Payment ({transaction.proposalId.substring(0, 10)}...) <Pending /></h1>
- <p>
- To <b>{transaction.info.merchant.name}</b>
- </p>
- <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
- <tr>
- <td>Order id</td>
- <td>{transaction.info.orderId}</td>
- </tr>
- <tr>
- <td>Summary</td>
- <td>{transaction.info.summary}</td>
- </tr>
- {transaction.info.products && transaction.info.products.length > 0 &&
- <tr>
- <td>Products</td>
- <td><ol style={{ margin: 0, textAlign: 'left' }}>
- {transaction.info.products.map(p =>
- <li>{p.description}</li>
- )}</ol></td>
- </tr>
- }
- <tr>
- <td>Order amount</td>
- <td>{transaction.amountRaw}</td>
- </tr>
- <tr>
- <td>Order amount and fees</td>
- <td>{transaction.amountEffective}</td>
- </tr>
- <tr>
- <td>Exchange fee</td>
- <td>{Amounts.stringify(
- Amounts.sub(
- Amounts.parseOrThrow(transaction.amountEffective),
- Amounts.parseOrThrow(transaction.amountRaw),
- ).amount
- )}</td>
- </tr>
- <tr>
- <td>When</td>
- <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
- </tr>
- </table>
- </section>
- <Footer />
- </div>
- );
- }
-
- if (transaction.type === TransactionType.Deposit) {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
- <section>
- <h1>Deposit ({transaction.depositGroupId}) <Pending /></h1>
- <p>
- To <b>{transaction.targetPaytoUri}</b>
- </p>
- <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
- <tr>
- <td>Amount deposit</td>
- <td>{transaction.amountRaw}</td>
- </tr>
- <tr>
- <td>Amount deposit and fees</td>
- <td>{transaction.amountEffective}</td>
- </tr>
- <tr>
- <td>Exchange fee</td>
- <td>{Amounts.stringify(
- Amounts.sub(
- Amounts.parseOrThrow(transaction.amountEffective),
- Amounts.parseOrThrow(transaction.amountRaw),
- ).amount
- )}</td>
- </tr>
- <tr>
- <td>When</td>
- <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
- </tr>
- </table>
- </section>
- <Footer />
- </div>
- );
- }
-
- if (transaction.type === TransactionType.Refresh) {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
- <section>
- <h1>Refresh <Pending /></h1>
- <p>
- From <b>{transaction.exchangeBaseUrl}</b>
- </p>
- <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
- <tr>
- <td>Amount refreshed</td>
- <td>{transaction.amountRaw}</td>
- </tr>
- <tr>
- <td>Fees</td>
- <td>{transaction.amountEffective}</td>
- </tr>
- <tr>
- <td>When</td>
- <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
- </tr>
- </table>
- </section>
- <Footer />
- </div>
- );
- }
-
- if (transaction.type === TransactionType.Tip) {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
- <section>
- <h1>Tip <Pending /></h1>
- <p>
- From <b>{transaction.merchantBaseUrl}</b>
- </p>
- <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
- <tr>
- <td>Amount deduce</td>
- <td>{transaction.amountRaw}</td>
- </tr>
- <tr>
- <td>Amount received</td>
- <td>{transaction.amountEffective}</td>
- </tr>
- <tr>
- <td>Exchange fee</td>
- <td>{Amounts.stringify(
- Amounts.sub(
- Amounts.parseOrThrow(transaction.amountRaw),
- Amounts.parseOrThrow(transaction.amountEffective),
- ).amount
- )}</td>
- </tr>
- <tr>
- <td>When</td>
- <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
- </tr>
- </table>
- </section>
- <Footer />
- </div>
- );
- }
-
- const TRANSACTION_FROM_REFUND = /[a-z]*:([\w]{10}).*/
- if (transaction.type === TransactionType.Refund) {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
- <section>
- <h1>Refund ({TRANSACTION_FROM_REFUND.exec(transaction.refundedTransactionId)![1]}...) <Pending /></h1>
- <p>
- From <b>{transaction.info.merchant.name}</b>
- </p>
- <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}>
- <tr>
- <td>Order id</td>
- <td>{transaction.info.orderId}</td>
- </tr>
- <tr>
- <td>Summary</td>
- <td>{transaction.info.summary}</td>
- </tr>
- {transaction.info.products && transaction.info.products.length > 0 &&
- <tr>
- <td>Products</td>
- <td><ol>
- {transaction.info.products.map(p =>
- <li>{p.description}</li>
- )}</ol></td>
- </tr>
- }
- <tr>
- <td>Amount deduce</td>
- <td>{transaction.amountRaw}</td>
- </tr>
- <tr>
- <td>Amount received</td>
- <td>{transaction.amountEffective}</td>
- </tr>
- <tr>
- <td>Exchange fee</td>
- <td>{Amounts.stringify(
- Amounts.sub(
- Amounts.parseOrThrow(transaction.amountRaw),
- Amounts.parseOrThrow(transaction.amountEffective),
- ).amount
- )}</td>
- </tr>
- <tr>
- <td>When</td>
- <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
- </tr>
- </table>
- </section>
- <Footer />
- </div>
- );
- }
-
-
- return <div></div>
-}
-
-export function WalletTransaction({ tid }: { tid: string }): JSX.Element {
- const [transaction, setTransaction] = useState<
- Transaction | undefined
- >(undefined);
-
- 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();
- }, []);
-
- return <WalletTransactionView
- transaction={transaction}
- onDelete={() => wxApi.deleteTransaction(tid).then(_ => history.go(-1))}
- onBack={() => { history.go(-1) }}
- />
-}
-
-export function WalletSettings() {
- const [permissionsEnabled, togglePermissions] = useExtendedPermissions()
- return (
- <div>
- <h2>Permissions</h2>
- <PermissionsCheckbox enabled={permissionsEnabled} onToggle={togglePermissions} />
- {/*
- <h2>Developer mode</h2>
- <DebugCheckbox enabled={permissionsEnabled} onToggle={togglePermissions} />
- */}
- </div>
- );
-}
-
-
-export function DebugCheckbox({ enabled, onToggle }: { enabled: boolean, onToggle: () => void }): JSX.Element {
- return (
- <div>
- <input
- checked={enabled}
- onClick={onToggle}
- type="checkbox"
- id="checkbox-perm"
- style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }}
- />
- <label
- htmlFor="checkbox-perm"
- style={{ marginLeft: "0.5em", fontWeight: "bold" }}
- >
- Automatically open wallet based on page content
- </label>
- <span
- style={{
- color: "#383838",
- fontSize: "smaller",
- display: "block",
- marginLeft: "2em",
- }}
- >
- (Enabling this option below will make using the wallet faster, but
- requires more permissions from your browser.)
- </span>
- </div>
- );
-}
-
-function reload(): void {
- try {
- chrome.runtime.reload();
- window.close();
- } catch (e) {
- // Functionality missing in firefox, ignore!
- }
-}
-
-async function confirmReset(): Promise<void> {
- if (
- confirm(
- "Do you want to IRREVOCABLY DESTROY everything inside your" +
- " wallet and LOSE ALL YOUR COINS?",
- )
- ) {
- await wxApi.resetDb();
- window.close();
- }
-}
-
-export function WalletDebug(props: any): JSX.Element {
- return (
- <div>
- <p>Debug tools:</p>
- <button onClick={openExtensionPage("/static/popup.html")}>wallet tab</button>
- <br />
- <button onClick={confirmReset}>reset</button>
- <button onClick={reload}>reload chrome extension</button>
- <Diagnostics />
- </div>
- );
-}
-
-function openExtensionPage(page: string) {
- return () => {
- chrome.tabs.create({
- url: chrome.extension.getURL(page),
- });
- };
-}
-
-// function openTab(page: string) {
-// return (evt: React.SyntheticEvent<any>) => {
-// evt.preventDefault();
-// chrome.tabs.create({
-// url: page,
-// });
-// };
-// }
-
-function makeExtensionUrlWithParams(
- url: string,
- params?: { [name: string]: string | undefined },
-): string {
- const innerUrl = new URL(chrome.extension.getURL("/" + url));
- if (params) {
- for (const key in params) {
- const p = params[key];
- if (p) {
- innerUrl.searchParams.set(key, p);
- }
- }
- }
- return innerUrl.href;
-}
-
-export function actionForTalerUri(talerUri: string): string | undefined {
- const uriType = classifyTalerUri(talerUri);
- switch (uriType) {
- case TalerUriType.TalerWithdraw:
- return makeExtensionUrlWithParams("static/wallet.html#/withdraw", {
- talerWithdrawUri: talerUri,
- });
- case TalerUriType.TalerPay:
- return makeExtensionUrlWithParams("static/wallet.html#/pay", {
- talerPayUri: talerUri,
- });
- case TalerUriType.TalerTip:
- return makeExtensionUrlWithParams("static/wallet.html#/tip", {
- talerTipUri: talerUri,
- });
- case TalerUriType.TalerRefund:
- return makeExtensionUrlWithParams("static/wallet.html#/refund", {
- talerRefundUri: talerUri,
- });
- case TalerUriType.TalerNotifyReserve:
- // FIXME: implement
- break;
- default:
- console.warn(
- "Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
- );
- break;
- }
- return undefined;
-}
-
-export async function findTalerUriInActiveTab(): Promise<string | undefined> {
- return new Promise((resolve, reject) => {
- chrome.tabs.executeScript(
- {
- code: `
- (() => {
- let x = document.querySelector("a[href^='taler://'") || document.querySelector("a[href^='taler+http://'");
- return x ? x.href.toString() : null;
- })();
- `,
- allFrames: false,
- },
- (result) => {
- if (chrome.runtime.lastError) {
- console.error(chrome.runtime.lastError);
- resolve(undefined);
- return;
- }
- console.log("got result", result);
- resolve(result[0]);
- },
- );
- });
-}
-
-// export function WalletPopup(): JSX.Element {
-// const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
-// undefined,
-// );
-// const [dismissed, setDismissed] = useState(false);
-// useEffect(() => {
-// async function check(): Promise<void> {
-// const talerUri = await findTalerUriInActiveTab();
-// if (talerUri) {
-// const actionUrl = actionForTalerUri(talerUri);
-// setTalerActionUrl(actionUrl);
-// }
-// }
-// check();
-// }, []);
-// if (talerActionUrl && !dismissed) {
-// return (
-// <div style={{ padding: "1em", width: 400 }}>
-// <h1>Taler Action</h1>
-// <p>This page has a Taler action. </p>
-// <p>
-// <button
-// onClick={() => {
-// window.open(talerActionUrl, "_blank");
-// }}
-// >
-// Open
-// </button>
-// </p>
-// <p>
-// <button onClick={() => setDismissed(true)}>Dismiss</button>
-// </p>
-// </div>
-// );
-// }
-// return (
-// <div>
-// <Match>{({ path }: any) => <WalletNavBar current={path} />}</Match>
-// <div style={{ margin: "1em", width: 400 }}>
-// <Router>
-// <Route path={Pages.balance} component={WalletBalanceView} />
-// <Route path={Pages.settings} component={WalletSettings} />
-// <Route path={Pages.debug} component={WalletDebug} />
-// <Route path={Pages.history} component={WalletHistory} />
-// <Route path={Pages.transaction} component={WalletTransaction} />
-// </Router>
-// </div>
-// </div>
-// );
-// }
-
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -23,13 +23,17 @@
import { render } from "preact";
import { setupI18n } from "@gnu-taler/taler-util";
import { strings } from "./i18n/strings";
-import { useEffect, useState } from "preact/hooks";
+import { useEffect } from "preact/hooks";
import {
- actionForTalerUri, findTalerUriInActiveTab, Pages, WalletBalanceView, WalletDebug, WalletHistory,
- WalletNavBar, WalletSettings, WalletTransaction, WalletTransactionView
-} from "./popup/popup";
+ Pages, WalletNavBar} from "./popup/popup";
+import { HistoryPage } from "./popup/History";
+import { DebugPage } from "./popup/Debug";
+import { SettingsPage } from "./popup/Settings";
+import { TransactionPage } from "./popup/Transaction";
+import { BalancePage } from "./popup/Balance";
import Match from "preact-router/match";
import Router, { route, Route } from "preact-router";
+import { useTalerActionURL } from "./hooks/useTalerActionURL";
// import { Application } from "./Application";
function main(): void {
@@ -53,25 +57,6 @@ if (document.readyState === "loading") {
main();
}
-function useTalerActionURL(): [string | undefined, (s: boolean) => void] {
- const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
- undefined,
- );
- const [dismissed, setDismissed] = useState(false);
- useEffect(() => {
- async function check(): Promise<void> {
- const talerUri = await findTalerUriInActiveTab();
- if (talerUri) {
- const actionUrl = actionForTalerUri(talerUri);
- setTalerActionUrl(actionUrl);
- }
- }
- check();
- }, []);
- const url = dismissed ? undefined : talerActionUrl
- return [url, setDismissed]
-}
-
interface Props {
url: string;
onDismiss: (s: boolean) => void;
@@ -105,11 +90,11 @@ function Application() {
<Match>{({ path }: any) => <WalletNavBar current={path} />}</Match >
<div style={{ margin: "1em", width: 400 }}>
<Router>
- <Route path={Pages.balance} component={WalletBalanceView} />
- <Route path={Pages.settings} component={WalletSettings} />
- <Route path={Pages.debug} component={WalletDebug} />
- <Route path={Pages.history} component={WalletHistory} />
- <Route path={Pages.transaction} component={WalletTransaction} />
+ <Route path={Pages.balance} component={BalancePage} />
+ <Route path={Pages.settings} component={SettingsPage} />
+ <Route path={Pages.debug} component={DebugPage} />
+ <Route path={Pages.history} component={HistoryPage} />
+ <Route path={Pages.transaction} component={TransactionPage} />
<Route default component={Redirect} to={Pages.balance} />
</Router>
</div>
@@ -118,8 +103,6 @@ function Application() {
}
-
-
function Redirect({ to }: { to: string }): null {
useEffect(() => {
route(to, true)
diff --git a/packages/taler-wallet-webextension/src/wallet/Pay.tsx b/packages/taler-wallet-webextension/src/wallet/Pay.tsx
@@ -0,0 +1,224 @@
+/*
+ This file is part of TALER
+ (C) 2015 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Page shown to the user to confirm entering
+ * a contract.
+ */
+
+/**
+ * Imports.
+ */
+// import * as i18n from "../i18n";
+
+import { renderAmount, ProgressButton } from "../renderHtml";
+import * as wxApi from "../wxApi";
+
+import { useState, useEffect } from "preact/hooks";
+
+import { getJsonI18n, i18n } from "@gnu-taler/taler-util";
+import {
+ PreparePayResult,
+ ConfirmPayResult,
+ AmountJson,
+ PreparePayResultType,
+ Amounts,
+ ContractTerms,
+ ConfirmPayResultType,
+} from "@gnu-taler/taler-util";
+import { JSX, VNode } from "preact";
+
+interface Props {
+ talerPayUri?: string
+}
+
+export function PayPage({ talerPayUri }: Props): JSX.Element {
+ const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(undefined);
+ const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined);
+ const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
+ const [numTries, setNumTries] = useState(0);
+ const [loading, setLoading] = useState(false);
+ let totalFees: AmountJson | undefined = undefined;
+
+ useEffect(() => {
+ if (!talerPayUri) return;
+ const doFetch = async (): Promise<void> => {
+ const p = await wxApi.preparePay(talerPayUri);
+ setPayStatus(p);
+ };
+ doFetch();
+ }, [numTries, talerPayUri]);
+
+ if (!talerPayUri) {
+ return <span>missing pay uri</span>
+ }
+
+ if (!payStatus) {
+ return <span>Loading payment information ...</span>;
+ }
+
+ let insufficientBalance = false;
+ if (payStatus.status == PreparePayResultType.InsufficientBalance) {
+ insufficientBalance = true;
+ }
+
+ if (payStatus.status === PreparePayResultType.PaymentPossible) {
+ const amountRaw = Amounts.parseOrThrow(payStatus.amountRaw);
+ const amountEffective: AmountJson = Amounts.parseOrThrow(
+ payStatus.amountEffective,
+ );
+ totalFees = Amounts.sub(amountEffective, amountRaw).amount;
+ }
+
+ if (
+ payStatus.status === PreparePayResultType.AlreadyConfirmed &&
+ numTries === 0
+ ) {
+ const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
+ if (fulfillmentUrl) {
+ return (
+ <span>
+ You have already paid for this article. Click{" "}
+ <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again.
+ </span>
+ );
+ } else {
+ <span>
+ You have already paid for this article:{" "}
+ <em>
+ {payStatus.contractTerms.fulfillment_message ?? "no message given"}
+ </em>
+ </span>;
+ }
+ }
+
+ const contractTerms: ContractTerms = payStatus.contractTerms;
+
+ if (!contractTerms) {
+ return (
+ <span>
+ Error: did not get contract terms from merchant or wallet backend.
+ </span>
+ );
+ }
+
+ let merchantName: VNode;
+ if (contractTerms.merchant && contractTerms.merchant.name) {
+ merchantName = <strong>{contractTerms.merchant.name}</strong>;
+ } else {
+ merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
+ }
+
+ const amount = (
+ <strong>{renderAmount(Amounts.parseOrThrow(contractTerms.amount))}</strong>
+ );
+
+ const doPayment = async (): Promise<void> => {
+ if (payStatus.status !== "payment-possible") {
+ throw Error(`invalid state: ${payStatus.status}`);
+ }
+ const proposalId = payStatus.proposalId;
+ setNumTries(numTries + 1);
+ try {
+ setLoading(true);
+ const res = await wxApi.confirmPay(proposalId, undefined);
+ if (res.type !== ConfirmPayResultType.Done) {
+ throw Error("payment pending");
+ }
+ const fu = res.contractTerms.fulfillment_url;
+ if (fu) {
+ document.location.href = fu;
+ }
+ setPayResult(res);
+ } catch (e) {
+ console.error(e);
+ setPayErrMsg(e.message);
+ }
+ };
+
+ if (payResult && payResult.type === ConfirmPayResultType.Done) {
+ if (payResult.contractTerms.fulfillment_message) {
+ const obj = {
+ fulfillment_message: payResult.contractTerms.fulfillment_message,
+ fulfillment_message_i18n:
+ payResult.contractTerms.fulfillment_message_i18n,
+ };
+ const msg = getJsonI18n(obj, "fulfillment_message");
+ return (
+ <div>
+ <p>Payment succeeded.</p>
+ <p>{msg}</p>
+ </div>
+ );
+ } else {
+ return <span>Redirecting ...</span>;
+ }
+ }
+
+ return (
+ <div>
+ <p>
+ <i18n.Translate>
+ The merchant <span>{merchantName}</span> offers you to purchase:
+ </i18n.Translate>
+ <div style={{ textAlign: "center" }}>
+ <strong>{contractTerms.summary}</strong>
+ </div>
+ {totalFees ? (
+ <i18n.Translate>
+ The total price is <span>{amount} </span>
+ (plus <span>{renderAmount(totalFees)}</span> fees).
+ </i18n.Translate>
+ ) : (
+ <i18n.Translate>
+ The total price is <span>{amount}</span>.
+ </i18n.Translate>
+ )}
+ </p>
+
+ {insufficientBalance ? (
+ <div>
+ <p style={{ color: "red", fontWeight: "bold" }}>
+ Unable to pay: Your balance is insufficient.
+ </p>
+ </div>
+ ) : null}
+
+ {payErrMsg ? (
+ <div>
+ <p>Payment failed: {payErrMsg}</p>
+ <button
+ className="pure-button button-success"
+ onClick={() => doPayment()}
+ >
+ {i18n.str`Retry`}
+ </button>
+ </div>
+ ) : (
+ <div>
+ <ProgressButton
+ isLoading={loading}
+ disabled={insufficientBalance}
+ onClick={() => doPayment()}
+ >
+ {i18n.str`Confirm payment`}
+ </ProgressButton>
+ </div>
+ )}
+ </div>
+ );
+}
+
diff --git a/packages/taler-wallet-webextension/src/wallet/Refund.tsx b/packages/taler-wallet-webextension/src/wallet/Refund.tsx
@@ -0,0 +1,89 @@
+/*
+ This file is part of TALER
+ (C) 2015-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/>
+ */
+
+/**
+ * Page that shows refund status for purchases.
+ *
+ * @author Florian Dold
+ */
+
+import * as wxApi from "../wxApi";
+import { AmountView } from "../renderHtml";
+import {
+ ApplyRefundResponse,
+ Amounts,
+} from "@gnu-taler/taler-util";
+import { useEffect, useState } from "preact/hooks";
+import { JSX } from "preact/jsx-runtime";
+
+interface Props {
+ talerRefundUri?: string
+}
+
+export function RefundPage({ talerRefundUri }: Props): JSX.Element {
+ const [applyResult, setApplyResult] = useState<ApplyRefundResponse | undefined>(undefined);
+ const [errMsg, setErrMsg] = useState<string | undefined>(undefined);
+
+ useEffect(() => {
+ if (!talerRefundUri) return;
+ const doFetch = async (): Promise<void> => {
+ try {
+ const result = await wxApi.applyRefund(talerRefundUri);
+ setApplyResult(result);
+ } catch (e) {
+ console.error(e);
+ setErrMsg(e.message);
+ console.log("err message", e.message);
+ }
+ };
+ doFetch();
+ }, [talerRefundUri]);
+
+ console.log("rendering");
+
+ if (!talerRefundUri) {
+ return <span>missing taler refund uri</span>;
+ }
+
+ if (errMsg) {
+ return <span>Error: {errMsg}</span>;
+ }
+
+ if (!applyResult) {
+ return <span>Updating refund status</span>;
+ }
+
+ return (
+ <>
+ <h2>Refund Status</h2>
+ <p>
+ The product <em>{applyResult.info.summary}</em> has received a total
+ effective refund of{" "}
+ <AmountView amount={applyResult.amountRefundGranted} />.
+ </p>
+ {applyResult.pendingAtExchange ? (
+ <p>Refund processing is still in progress.</p>
+ ) : null}
+ {!Amounts.isZero(applyResult.amountRefundGone) ? (
+ <p>
+ The refund amount of{" "}
+ <AmountView amount={applyResult.amountRefundGone} />
+ could not be applied.
+ </p>
+ ) : null}
+ </>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/Tip.tsx b/packages/taler-wallet-webextension/src/wallet/Tip.tsx
@@ -0,0 +1,97 @@
+/*
+ This file is part of TALER
+ (C) 2017 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Page shown to the user to accept or ignore a tip from a merchant.
+ *
+ * @author Florian Dold <dold@taler.net>
+ */
+
+import { useEffect, useState } from "preact/hooks";
+import { PrepareTipResult } from "@gnu-taler/taler-util";
+import { AmountView } from "../renderHtml";
+import * as wxApi from "../wxApi";
+import { JSX } from "preact/jsx-runtime";
+
+interface Props {
+ talerTipUri?: string
+}
+
+export function TipPage({ talerTipUri }: Props): JSX.Element {
+ const [updateCounter, setUpdateCounter] = useState<number>(0);
+ const [prepareTipResult, setPrepareTipResult] = useState<
+ PrepareTipResult | undefined
+ >(undefined);
+
+ const [tipIgnored, setTipIgnored] = useState(false);
+
+ useEffect(() => {
+ if (!talerTipUri) return;
+ const doFetch = async (): Promise<void> => {
+ const p = await wxApi.prepareTip({ talerTipUri });
+ setPrepareTipResult(p);
+ };
+ doFetch();
+ }, [talerTipUri, updateCounter]);
+
+ const doAccept = async () => {
+ if (!prepareTipResult) {
+ return;
+ }
+ await wxApi.acceptTip({ walletTipId: prepareTipResult?.walletTipId });
+ setUpdateCounter(updateCounter + 1);
+ };
+
+ const doIgnore = () => {
+ setTipIgnored(true);
+ };
+
+ if (!talerTipUri) {
+ return <span>missing tip uri</span>;
+ }
+
+ if (tipIgnored) {
+ return <span>You've ignored the tip.</span>;
+ }
+
+ if (!prepareTipResult) {
+ return <span>Loading ...</span>;
+ }
+
+ if (prepareTipResult.accepted) {
+ return (
+ <span>
+ Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. Check
+ your transactions list for more details.
+ </span>
+ );
+ } else {
+ return (
+ <div>
+ <p>
+ The merchant <code>{prepareTipResult.merchantBaseUrl}</code> is
+ offering you a tip of{" "}
+ <strong>
+ <AmountView amount={prepareTipResult.tipAmountEffective} />
+ </strong>{" "}
+ via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code>
+ </p>
+ <button onClick={doAccept}>Accept tip</button>
+ <button onClick={doIgnore}>Ignore</button>
+ </div>
+ );
+ }
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
@@ -0,0 +1,45 @@
+/*
+ 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/>
+ */
+
+/**
+ * Welcome page, shown on first installs.
+ *
+ * @author Florian Dold
+ */
+
+import { JSX } from "preact/jsx-runtime";
+import { PermissionsCheckbox } from "../components/PermissionsCheckbox";
+import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
+import { Diagnostics } from "../components/Diagnostics";
+
+export function WelcomePage(): JSX.Element {
+ const [permissionsEnabled, togglePermissions] = useExtendedPermissions()
+ return (
+ <>
+ <p>Thank you for installing the wallet.</p>
+ <Diagnostics />
+ <h2>Permissions</h2>
+ <PermissionsCheckbox enabled={permissionsEnabled} onToggle={togglePermissions}/>
+ <h2>Next Steps</h2>
+ <a href="https://demo.taler.net/" style={{ display: "block" }}>
+ Try the demo »
+ </a>
+ <a href="https://demo.taler.net/" style={{ display: "block" }}>
+ Learn how to top up your wallet balance »
+ </a>
+ </>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx
@@ -0,0 +1,66 @@
+/*
+ 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 { h } from 'preact';
+import { View, ViewProps } from './Withdraw';
+
+
+export default {
+ title: 'wallet/withdraw',
+ component: View,
+ argTypes: {
+ },
+};
+
+export const WithoutURI = (a: any) => <View {...a} />;
+WithoutURI.args = {
+} as ViewProps
+
+export const WithoutDetails = (a: any) => <View {...a} />;
+WithoutDetails.args = {
+ talerWithdrawUri: 'http://something'
+} as ViewProps
+
+export const Cancelled = (a: any) => <View {...a} />;
+Cancelled.args = {
+ talerWithdrawUri: 'http://something',
+ details: {
+ amount: 'USD:2',
+ },
+ cancelled: true
+} as ViewProps
+
+export const CompleteWithExchange = (a: any) => <View {...a} />;
+CompleteWithExchange.args = {
+ talerWithdrawUri: 'http://something',
+ details: {
+ amount: 'USD:2',
+ },
+ selectedExchange: 'Some exchange'
+} as ViewProps
+
+export const CompleteWithoutExchange = (a: any) => <View {...a} />;
+CompleteWithoutExchange.args = {
+ talerWithdrawUri: 'http://something',
+ details: {
+ amount: 'USD:2',
+ },
+} as ViewProps
diff --git a/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx b/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx
@@ -0,0 +1,161 @@
+/*
+ This file is part of TALER
+ (C) 2015-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/>
+ */
+
+/**
+ * Page shown to the user to confirm creation
+ * of a reserve, usually requested by the bank.
+ *
+ * @author Florian Dold
+ */
+
+import { i18n } from '@gnu-taler/taler-util'
+import { renderAmount } from "../renderHtml";
+
+import { useState, useEffect } from "preact/hooks";
+import {
+ acceptWithdrawal,
+ onUpdateNotification,
+ getWithdrawalDetailsForUri,
+} from "../wxApi";
+import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util";
+import { JSX } from "preact/jsx-runtime";
+
+interface Props {
+ talerWithdrawUri?: string;
+}
+
+export interface ViewProps {
+ talerWithdrawUri?: string;
+ details?: WithdrawUriInfoResponse;
+ cancelled?: boolean;
+ selectedExchange?: string;
+ accept: () => Promise<void>;
+ setCancelled: (b: boolean) => void;
+ setSelecting: (b: boolean) => void;
+};
+
+export function View({ talerWithdrawUri, details, cancelled, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) {
+ const [state, setState] = useState(1)
+ setTimeout(() => {
+ setState(s => s + 1)
+ }, 1000);
+ if (!talerWithdrawUri) {
+ return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
+ }
+
+ if (!details) {
+ return <span><i18n.Translate>Loading...</i18n.Translate></span>;
+ }
+
+ if (cancelled) {
+ return <span><i18n.Translate>Withdraw operation has been cancelled.{state}</i18n.Translate></span>;
+ }
+
+ return (
+ <div>
+ <h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1>
+ <p><i18n.Translate>
+ You are about to withdraw{" "}
+ <strong>{renderAmount(details.amount)}</strong> from your bank account
+ into your wallet.
+ </i18n.Translate></p>
+ {selectedExchange ? (
+ <p><i18n.Translate>
+ The exchange <strong>{selectedExchange}</strong> will be used as the
+ Taler payment service provider.
+ </i18n.Translate></p>
+ ) : null}
+
+ <div>
+ <button
+ className="pure-button button-success"
+ disabled={!selectedExchange}
+ onClick={() => accept()}
+ >
+ {i18n.str`Accept fees and withdraw`}
+ </button>
+ <p>
+ <span
+ role="button"
+ tabIndex={0}
+ style={{ textDecoration: "underline", cursor: "pointer" }}
+ onClick={() => setSelecting(true)}
+ >
+ {i18n.str`Chose different exchange provider`}
+ </span>
+ <br />
+ <span
+ role="button"
+ tabIndex={0}
+ style={{ textDecoration: "underline", cursor: "pointer" }}
+ onClick={() => setCancelled(true)}
+ >
+ {i18n.str`Cancel withdraw operation`}
+ </span>
+ </p>
+ </div>
+ </div>
+ )
+}
+
+export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element {
+ const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(undefined);
+ const [selectedExchange, setSelectedExchange] = useState<
+ string | undefined
+ >(undefined);
+ const [cancelled, setCancelled] = useState(false);
+ const [selecting, setSelecting] = useState(false);
+ const [errMsg, setErrMsg] = useState<string | undefined>("");
+ const [updateCounter, setUpdateCounter] = useState(1);
+
+ useEffect(() => {
+ return onUpdateNotification(() => {
+ setUpdateCounter(updateCounter + 1);
+ });
+ }, []);
+
+ useEffect(() => {
+ if (!talerWithdrawUri) return
+ const fetchData = async (): Promise<void> => {
+ const res = await getWithdrawalDetailsForUri({ talerWithdrawUri });
+ setDetails(res);
+ if (res.defaultExchangeBaseUrl) {
+ setSelectedExchange(res.defaultExchangeBaseUrl);
+ }
+ };
+ fetchData();
+ }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]);
+
+ const accept = async (): Promise<void> => {
+ if (!talerWithdrawUri) return
+ if (!selectedExchange) {
+ throw Error("can't accept, no exchange selected");
+ }
+ console.log("accepting exchange", selectedExchange);
+ const res = await acceptWithdrawal(talerWithdrawUri, selectedExchange);
+ console.log("accept withdrawal response", res);
+ if (res.confirmTransferUrl) {
+ document.location.href = res.confirmTransferUrl;
+ }
+ };
+
+ return <View accept={accept}
+ setCancelled={setCancelled} setSelecting={setSelecting}
+ cancelled={cancelled} details={details} selectedExchange={selectedExchange}
+ talerWithdrawUri={talerWithdrawUri}
+ />
+}
+
diff --git a/packages/taler-wallet-webextension/src/wallet/pay.tsx b/packages/taler-wallet-webextension/src/wallet/pay.tsx
@@ -1,235 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015 GNUnet e.V.
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Page shown to the user to confirm entering
- * a contract.
- */
-
-/**
- * Imports.
- */
-// import * as i18n from "../i18n";
-
-import { renderAmount, ProgressButton } from "../renderHtml";
-import * as wxApi from "../wxApi";
-
-import { useState, useEffect } from "preact/hooks";
-
-import { getJsonI18n, i18n } from "@gnu-taler/taler-util";
-import {
- PreparePayResult,
- ConfirmPayResult,
- AmountJson,
- PreparePayResultType,
- Amounts,
- ContractTerms,
- ConfirmPayResultType,
-} from "@gnu-taler/taler-util";
-import { JSX, VNode } from "preact";
-
-interface Props {
- talerPayUri?: string
-}
-
-export function TalerPayDialog({ talerPayUri }: Props): JSX.Element {
- const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(undefined);
- const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined);
- const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
- const [numTries, setNumTries] = useState(0);
- const [loading, setLoading] = useState(false);
- let totalFees: AmountJson | undefined = undefined;
-
- useEffect(() => {
- if (!talerPayUri) return;
- const doFetch = async (): Promise<void> => {
- const p = await wxApi.preparePay(talerPayUri);
- setPayStatus(p);
- };
- doFetch();
- }, [numTries, talerPayUri]);
-
- if (!talerPayUri) {
- return <span>missing pay uri</span>
- }
-
- if (!payStatus) {
- return <span>Loading payment information ...</span>;
- }
-
- let insufficientBalance = false;
- if (payStatus.status == PreparePayResultType.InsufficientBalance) {
- insufficientBalance = true;
- }
-
- if (payStatus.status === PreparePayResultType.PaymentPossible) {
- const amountRaw = Amounts.parseOrThrow(payStatus.amountRaw);
- const amountEffective: AmountJson = Amounts.parseOrThrow(
- payStatus.amountEffective,
- );
- totalFees = Amounts.sub(amountEffective, amountRaw).amount;
- }
-
- if (
- payStatus.status === PreparePayResultType.AlreadyConfirmed &&
- numTries === 0
- ) {
- const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
- if (fulfillmentUrl) {
- return (
- <span>
- You have already paid for this article. Click{" "}
- <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again.
- </span>
- );
- } else {
- <span>
- You have already paid for this article:{" "}
- <em>
- {payStatus.contractTerms.fulfillment_message ?? "no message given"}
- </em>
- </span>;
- }
- }
-
- const contractTerms: ContractTerms = payStatus.contractTerms;
-
- if (!contractTerms) {
- return (
- <span>
- Error: did not get contract terms from merchant or wallet backend.
- </span>
- );
- }
-
- let merchantName: VNode;
- if (contractTerms.merchant && contractTerms.merchant.name) {
- merchantName = <strong>{contractTerms.merchant.name}</strong>;
- } else {
- merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
- }
-
- const amount = (
- <strong>{renderAmount(Amounts.parseOrThrow(contractTerms.amount))}</strong>
- );
-
- const doPayment = async (): Promise<void> => {
- if (payStatus.status !== "payment-possible") {
- throw Error(`invalid state: ${payStatus.status}`);
- }
- const proposalId = payStatus.proposalId;
- setNumTries(numTries + 1);
- try {
- setLoading(true);
- const res = await wxApi.confirmPay(proposalId, undefined);
- if (res.type !== ConfirmPayResultType.Done) {
- throw Error("payment pending");
- }
- const fu = res.contractTerms.fulfillment_url;
- if (fu) {
- document.location.href = fu;
- }
- setPayResult(res);
- } catch (e) {
- console.error(e);
- setPayErrMsg(e.message);
- }
- };
-
- if (payResult && payResult.type === ConfirmPayResultType.Done) {
- if (payResult.contractTerms.fulfillment_message) {
- const obj = {
- fulfillment_message: payResult.contractTerms.fulfillment_message,
- fulfillment_message_i18n:
- payResult.contractTerms.fulfillment_message_i18n,
- };
- const msg = getJsonI18n(obj, "fulfillment_message");
- return (
- <div>
- <p>Payment succeeded.</p>
- <p>{msg}</p>
- </div>
- );
- } else {
- return <span>Redirecting ...</span>;
- }
- }
-
- return (
- <div>
- <p>
- <i18n.Translate>
- The merchant <span>{merchantName}</span> offers you to purchase:
- </i18n.Translate>
- <div style={{ textAlign: "center" }}>
- <strong>{contractTerms.summary}</strong>
- </div>
- {totalFees ? (
- <i18n.Translate>
- The total price is <span>{amount} </span>
- (plus <span>{renderAmount(totalFees)}</span> fees).
- </i18n.Translate>
- ) : (
- <i18n.Translate>
- The total price is <span>{amount}</span>.
- </i18n.Translate>
- )}
- </p>
-
- {insufficientBalance ? (
- <div>
- <p style={{ color: "red", fontWeight: "bold" }}>
- Unable to pay: Your balance is insufficient.
- </p>
- </div>
- ) : null}
-
- {payErrMsg ? (
- <div>
- <p>Payment failed: {payErrMsg}</p>
- <button
- className="pure-button button-success"
- onClick={() => doPayment()}
- >
- {i18n.str`Retry`}
- </button>
- </div>
- ) : (
- <div>
- <ProgressButton
- isLoading={loading}
- disabled={insufficientBalance}
- onClick={() => doPayment()}
- >
- {i18n.str`Confirm payment`}
- </ProgressButton>
- </div>
- )}
- </div>
- );
-}
-
-/**
- * @deprecated to be removed
- */
-export function createPayPage(): JSX.Element {
- const url = new URL(document.location.href);
- const talerPayUri = url.searchParams.get("talerPayUri");
- if (!talerPayUri) {
- throw Error("invalid parameter");
- }
- return <TalerPayDialog talerPayUri={talerPayUri} />;
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/refund.tsx b/packages/taler-wallet-webextension/src/wallet/refund.tsx
@@ -1,108 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015-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/>
- */
-
-/**
- * Page that shows refund status for purchases.
- *
- * @author Florian Dold
- */
-
-import * as wxApi from "../wxApi";
-import { AmountView } from "../renderHtml";
-import {
- ApplyRefundResponse,
- Amounts,
-} from "@gnu-taler/taler-util";
-import { useEffect, useState } from "preact/hooks";
-import { JSX } from "preact/jsx-runtime";
-
-interface Props {
- talerRefundUri?: string
-}
-
-export function RefundStatusView({ talerRefundUri }: Props): JSX.Element {
- const [applyResult, setApplyResult] = useState<ApplyRefundResponse | undefined>(undefined);
- const [errMsg, setErrMsg] = useState<string | undefined>(undefined);
-
- useEffect(() => {
- if (!talerRefundUri) return;
- const doFetch = async (): Promise<void> => {
- try {
- const result = await wxApi.applyRefund(talerRefundUri);
- setApplyResult(result);
- } catch (e) {
- console.error(e);
- setErrMsg(e.message);
- console.log("err message", e.message);
- }
- };
- doFetch();
- }, [talerRefundUri]);
-
- console.log("rendering");
-
- if (!talerRefundUri) {
- return <span>missing taler refund uri</span>;
- }
-
- if (errMsg) {
- return <span>Error: {errMsg}</span>;
- }
-
- if (!applyResult) {
- return <span>Updating refund status</span>;
- }
-
- return (
- <>
- <h2>Refund Status</h2>
- <p>
- The product <em>{applyResult.info.summary}</em> has received a total
- effective refund of{" "}
- <AmountView amount={applyResult.amountRefundGranted} />.
- </p>
- {applyResult.pendingAtExchange ? (
- <p>Refund processing is still in progress.</p>
- ) : null}
- {!Amounts.isZero(applyResult.amountRefundGone) ? (
- <p>
- The refund amount of{" "}
- <AmountView amount={applyResult.amountRefundGone} />
- could not be applied.
- </p>
- ) : null}
- </>
- );
-}
-
-/**
- * @deprecated to be removed
- */
-export function createRefundPage(): JSX.Element {
- const url = new URL(document.location.href);
-
- const container = document.getElementById("container");
- if (!container) {
- throw Error("fatal: can't mount component, container missing");
- }
-
- const talerRefundUri = url.searchParams.get("talerRefundUri");
- if (!talerRefundUri) {
- throw Error("taler refund URI required");
- }
-
- return <RefundStatusView talerRefundUri={talerRefundUri} />;
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/tip.tsx b/packages/taler-wallet-webextension/src/wallet/tip.tsx
@@ -1,109 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 GNUnet e.V.
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Page shown to the user to accept or ignore a tip from a merchant.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-import { useEffect, useState } from "preact/hooks";
-import { PrepareTipResult } from "@gnu-taler/taler-util";
-import { AmountView } from "../renderHtml";
-import * as wxApi from "../wxApi";
-import { JSX } from "preact/jsx-runtime";
-
-interface Props {
- talerTipUri?: string
-}
-
-export function TalerTipDialog({ talerTipUri }: Props): JSX.Element {
- const [updateCounter, setUpdateCounter] = useState<number>(0);
- const [prepareTipResult, setPrepareTipResult] = useState<
- PrepareTipResult | undefined
- >(undefined);
-
- const [tipIgnored, setTipIgnored] = useState(false);
-
- useEffect(() => {
- if (!talerTipUri) return;
- const doFetch = async (): Promise<void> => {
- const p = await wxApi.prepareTip({ talerTipUri });
- setPrepareTipResult(p);
- };
- doFetch();
- }, [talerTipUri, updateCounter]);
-
- const doAccept = async () => {
- if (!prepareTipResult) {
- return;
- }
- await wxApi.acceptTip({ walletTipId: prepareTipResult?.walletTipId });
- setUpdateCounter(updateCounter + 1);
- };
-
- const doIgnore = () => {
- setTipIgnored(true);
- };
-
- if (!talerTipUri) {
- return <span>missing tip uri</span>;
- }
-
- if (tipIgnored) {
- return <span>You've ignored the tip.</span>;
- }
-
- if (!prepareTipResult) {
- return <span>Loading ...</span>;
- }
-
- if (prepareTipResult.accepted) {
- return (
- <span>
- Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. Check
- your transactions list for more details.
- </span>
- );
- } else {
- return (
- <div>
- <p>
- The merchant <code>{prepareTipResult.merchantBaseUrl}</code> is
- offering you a tip of{" "}
- <strong>
- <AmountView amount={prepareTipResult.tipAmountEffective} />
- </strong>{" "}
- via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code>
- </p>
- <button onClick={doAccept}>Accept tip</button>
- <button onClick={doIgnore}>Ignore</button>
- </div>
- );
- }
-}
-
-/**
- * @deprecated to be removed
- */
-export function createTipPage(): JSX.Element {
- const url = new URL(document.location.href);
- const talerTipUri = url.searchParams.get("talerTipUri");
- if (!talerTipUri) {
- throw Error("invalid parameter");
- }
- return <TalerTipDialog talerTipUri={talerTipUri} />;
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/welcome.tsx b/packages/taler-wallet-webextension/src/wallet/welcome.tsx
@@ -1,83 +0,0 @@
-/*
- 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/>
- */
-
-/**
- * Welcome page, shown on first installs.
- *
- * @author Florian Dold
- */
-
-import * as wxApi from "../wxApi";
-import { getPermissionsApi } from "../compat";
-import { extendedPermissions } from "../permissions";
-import { Fragment, JSX } from "preact/jsx-runtime";
-import { PermissionsCheckbox } from "../components/PermissionsCheckbox";
-import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
-import { Diagnostics } from "../components/Diagnostics";
-
-export async function handleExtendedPerm(isEnabled: boolean): Promise<boolean> {
- let nextVal: boolean | undefined;
-
- if (!isEnabled) {
- const granted = await new Promise<boolean>((resolve, reject) => {
- // We set permissions here, since apparently FF wants this to be done
- // as the result of an input event ...
- getPermissionsApi().request(extendedPermissions, (granted: boolean) => {
- if (chrome.runtime.lastError) {
- console.error("error requesting permissions");
- console.error(chrome.runtime.lastError);
- reject(chrome.runtime.lastError);
- return;
- }
- console.log("permissions granted:", granted);
- resolve(granted);
- });
- });
- const res = await wxApi.setExtendedPermissions(granted);
- nextVal = res.newValue;
- } else {
- const res = await wxApi.setExtendedPermissions(false);
- nextVal = res.newValue;
- }
- console.log("new permissions applied:", nextVal ?? false);
- return nextVal ?? false
-}
-
-export function Welcome(): JSX.Element {
- const [permissionsEnabled, togglePermissions] = useExtendedPermissions()
- return (
- <>
- <p>Thank you for installing the wallet.</p>
- <Diagnostics />
- <h2>Permissions</h2>
- <PermissionsCheckbox enabled={permissionsEnabled} onToggle={togglePermissions}/>
- <h2>Next Steps</h2>
- <a href="https://demo.taler.net/" style={{ display: "block" }}>
- Try the demo »
- </a>
- <a href="https://demo.taler.net/" style={{ display: "block" }}>
- Learn how to top up your wallet balance »
- </a>
- </>
- );
-}
-
-/**
- * @deprecated to be removed
- */
-export function createWelcomePage(): JSX.Element {
- return <Welcome />;
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/withdraw.stories.tsx b/packages/taler-wallet-webextension/src/wallet/withdraw.stories.tsx
@@ -1,66 +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 { h } from 'preact';
-import { View, ViewProps } from './withdraw';
-
-
-export default {
- title: 'wallet/withdraw',
- component: View,
- argTypes: {
- },
-};
-
-export const WithoutURI = (a: any) => <View {...a} />;
-WithoutURI.args = {
-} as ViewProps
-
-export const WithoutDetails = (a: any) => <View {...a} />;
-WithoutDetails.args = {
- talerWithdrawUri: 'http://something'
-} as ViewProps
-
-export const Cancelled = (a: any) => <View {...a} />;
-Cancelled.args = {
- talerWithdrawUri: 'http://something',
- details: {
- amount: 'USD:2',
- },
- cancelled: true
-} as ViewProps
-
-export const CompleteWithExchange = (a: any) => <View {...a} />;
-CompleteWithExchange.args = {
- talerWithdrawUri: 'http://something',
- details: {
- amount: 'USD:2',
- },
- selectedExchange: 'Some exchange'
-} as ViewProps
-
-export const CompleteWithoutExchange = (a: any) => <View {...a} />;
-CompleteWithoutExchange.args = {
- talerWithdrawUri: 'http://something',
- details: {
- amount: 'USD:2',
- },
-} as ViewProps
diff --git a/packages/taler-wallet-webextension/src/wallet/withdraw.tsx b/packages/taler-wallet-webextension/src/wallet/withdraw.tsx
@@ -1,173 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015-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/>
- */
-
-/**
- * Page shown to the user to confirm creation
- * of a reserve, usually requested by the bank.
- *
- * @author Florian Dold
- */
-
-import { i18n } from '@gnu-taler/taler-util'
-import { renderAmount } from "../renderHtml";
-
-import { useState, useEffect } from "preact/hooks";
-import {
- acceptWithdrawal,
- onUpdateNotification,
- getWithdrawalDetailsForUri,
-} from "../wxApi";
-import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util";
-import { JSX } from "preact/jsx-runtime";
-
-interface Props {
- talerWithdrawUri?: string;
-}
-
-export interface ViewProps {
- talerWithdrawUri?: string;
- details?: WithdrawUriInfoResponse;
- cancelled?: boolean;
- selectedExchange?: string;
- accept: () => Promise<void>;
- setCancelled: (b: boolean) => void;
- setSelecting: (b: boolean) => void;
-};
-
-export function View({ talerWithdrawUri, details, cancelled, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) {
- const [state, setState] = useState(1)
- setTimeout(() => {
- setState(s => s + 1)
- }, 1000);
- if (!talerWithdrawUri) {
- return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
- }
-
- if (!details) {
- return <span><i18n.Translate>Loading...</i18n.Translate></span>;
- }
-
- if (cancelled) {
- return <span><i18n.Translate>Withdraw operation has been cancelled.{state}</i18n.Translate></span>;
- }
-
- return (
- <div>
- <h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1>
- <p><i18n.Translate>
- You are about to withdraw{" "}
- <strong>{renderAmount(details.amount)}</strong> from your bank account
- into your wallet.
- </i18n.Translate></p>
- {selectedExchange ? (
- <p><i18n.Translate>
- The exchange <strong>{selectedExchange}</strong> will be used as the
- Taler payment service provider.
- </i18n.Translate></p>
- ) : null}
-
- <div>
- <button
- className="pure-button button-success"
- disabled={!selectedExchange}
- onClick={() => accept()}
- >
- {i18n.str`Accept fees and withdraw`}
- </button>
- <p>
- <span
- role="button"
- tabIndex={0}
- style={{ textDecoration: "underline", cursor: "pointer" }}
- onClick={() => setSelecting(true)}
- >
- {i18n.str`Chose different exchange provider`}
- </span>
- <br />
- <span
- role="button"
- tabIndex={0}
- style={{ textDecoration: "underline", cursor: "pointer" }}
- onClick={() => setCancelled(true)}
- >
- {i18n.str`Cancel withdraw operation`}
- </span>
- </p>
- </div>
- </div>
- )
-}
-
-export function WithdrawalDialog({ talerWithdrawUri }: Props): JSX.Element {
- const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(undefined);
- const [selectedExchange, setSelectedExchange] = useState<
- string | undefined
- >(undefined);
- const [cancelled, setCancelled] = useState(false);
- const [selecting, setSelecting] = useState(false);
- const [errMsg, setErrMsg] = useState<string | undefined>("");
- const [updateCounter, setUpdateCounter] = useState(1);
-
- useEffect(() => {
- return onUpdateNotification(() => {
- setUpdateCounter(updateCounter + 1);
- });
- }, []);
-
- useEffect(() => {
- if (!talerWithdrawUri) return
- const fetchData = async (): Promise<void> => {
- const res = await getWithdrawalDetailsForUri({ talerWithdrawUri });
- setDetails(res);
- if (res.defaultExchangeBaseUrl) {
- setSelectedExchange(res.defaultExchangeBaseUrl);
- }
- };
- fetchData();
- }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]);
-
- const accept = async (): Promise<void> => {
- if (!talerWithdrawUri) return
- if (!selectedExchange) {
- throw Error("can't accept, no exchange selected");
- }
- console.log("accepting exchange", selectedExchange);
- const res = await acceptWithdrawal(talerWithdrawUri, selectedExchange);
- console.log("accept withdrawal response", res);
- if (res.confirmTransferUrl) {
- document.location.href = res.confirmTransferUrl;
- }
- };
-
- return <View accept={accept}
- setCancelled={setCancelled} setSelecting={setSelecting}
- cancelled={cancelled} details={details} selectedExchange={selectedExchange}
- talerWithdrawUri={talerWithdrawUri}
- />
-}
-
-
-/**
- * @deprecated to be removed
- */
-export function createWithdrawPage(): JSX.Element {
- const url = new URL(document.location.href);
- const talerWithdrawUri = url.searchParams.get("talerWithdrawUri");
- if (!talerWithdrawUri) {
- throw Error("withdraw URI required");
- }
- return <WithdrawalDialog talerWithdrawUri={talerWithdrawUri} />;
-}
diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
@@ -25,11 +25,11 @@ import { setupI18n } from "@gnu-taler/taler-util";
import { strings } from "./i18n/strings";
import { createHashHistory } from 'history';
-import { WithdrawalDialog } from "./wallet/withdraw";
-import { Welcome } from "./wallet/welcome";
-import { TalerPayDialog } from "./wallet/pay";
-import { RefundStatusView } from "./wallet/refund";
-import { TalerTipDialog } from './wallet/tip';
+import { WithdrawPage } from "./wallet/Withdraw";
+import { WelcomePage } from "./wallet/Welcome";
+import { PayPage } from "./wallet/Pay";
+import { RefundPage } from "./wallet/Refund";
+import { TipPage } from './wallet/Tip';
import Router, { route, Route } from "preact-router";
@@ -82,7 +82,7 @@ function Application() {
</div>
<h1>Browser Extension Installed!</h1>
<div>
- <Welcome />
+ <WelcomePage />
</div>
</section>
}} />
@@ -91,7 +91,7 @@ function Application() {
return <section id="main">
<h1>GNU Taler Wallet</h1>
<article class="fade">
- <TalerPayDialog talerPayUri={queryParams.talerPayUri} />
+ <PayPage talerPayUri={queryParams.talerPayUri} />
</article>
</section>
}} />
@@ -100,7 +100,7 @@ function Application() {
return <section id="main">
<h1>GNU Taler Wallet</h1>
<article class="fade">
- <RefundStatusView talerRefundUri={queryParams.talerRefundUri} />
+ <RefundPage talerRefundUri={queryParams.talerRefundUri} />
</article>
</section>
}} />
@@ -109,7 +109,7 @@ function Application() {
return <section id="main">
<h1>GNU Taler Wallet</h1>
<div>
- <TalerTipDialog talerTipUri={queryParams.talerTipUri} />
+ <TipPage talerTipUri={queryParams.talerTipUri} />
</div>
</section>
}} />
@@ -121,7 +121,7 @@ function Application() {
</h1>
</div>
<div class="fade">
- <WithdrawalDialog talerWithdrawUri={queryParams.talerWithdrawUri} />
+ <WithdrawPage talerWithdrawUri={queryParams.talerWithdrawUri} />
</div>
</section>
}} />