wallet-core/packages/taler-wallet-webextension/src/pages/popup.tsx

969 lines
27 KiB
TypeScript
Raw Normal View History

/*
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
2016-07-07 17:59:29 +02:00
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
2016-03-01 19:46:20 +01:00
/**
* Popup shown to the user when they click
* the Taler browser action button.
*
* @author Florian Dold
*/
2017-05-29 15:18:48 +02:00
/**
* Imports.
*/
import {
AmountJson,
Amounts,
2020-08-12 09:11:00 +02:00
BalancesResponse,
Balance,
classifyTalerUri,
TalerUriType,
TransactionsResponse,
Transaction,
TransactionType,
2020-11-18 17:33:02 +01:00
AmountString,
Timestamp,
2021-03-27 14:35:58 +01:00
amountFractionalBase,
i18n,
2021-03-27 14:35:58 +01:00
} from "@gnu-taler/taler-util";
2021-06-03 06:07:29 +02:00
import { format } from "date-fns";
import { Component, ComponentChildren, Fragment, JSX } from "preact";
2021-05-07 23:10:27 +02:00
import { route, Route, Router } from 'preact-router';
import { Match } from 'preact-router/match';
import { useEffect, useState } from "preact/hooks";
import { PageLink, renderAmount } from "../renderHtml";
2017-06-05 03:20:28 +02:00
import * as wxApi from "../wxApi";
import { PermissionsCheckbox, useExtendedPermissions, Diagnostics } from "./welcome";
2016-10-10 00:37:08 +02:00
interface TabProps {
2016-10-10 00:37:08 +02:00
target: string;
2021-05-07 23:10:27 +02:00
current?: string;
2021-05-07 15:38:28 +02:00
children?: ComponentChildren;
2016-10-10 00:37:08 +02:00
}
2020-04-06 20:02:01 +02:00
function Tab(props: TabProps): JSX.Element {
let cssClass = "";
2021-05-07 23:10:27 +02:00
if (props.current === props.target) {
cssClass = "active";
}
2016-10-10 00:37:08 +02:00
return (
2021-05-07 23:10:27 +02:00
<a href={props.target} className={cssClass}>
2016-10-10 00:37:08 +02:00
{props.children}
</a>
);
}
2021-05-07 23:10:27 +02:00
function WalletNavBar({ current }: { current?: string }) {
return (
<div className="nav" id="header">
<Tab target="/popup/balance" current={current}>{i18n.str`Balance`}</Tab>
<Tab target="/popup/history" current={current}>{i18n.str`History`}</Tab>
<Tab target="/popup/settings" current={current}>{i18n.str`Settings`}</Tab>
<Tab target="/popup/debug" current={current}>{i18n.str`Debug`}</Tab>
</div>
);
2016-02-18 23:41:29 +01:00
}
2017-05-29 15:18:48 +02:00
/**
* Render an amount as a large number with a small currency symbol.
*/
function bigAmount(amount: AmountJson): JSX.Element {
2021-03-27 14:35:58 +01:00
const v = amount.value + amount.fraction / amountFractionalBase;
2016-11-28 08:19:06 +01:00
return (
<span>
2020-03-30 12:39:32 +02:00
<span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
2016-11-28 08:19:06 +01:00
<span>{amount.currency}</span>
</span>
);
}
2020-04-06 20:02:01 +02:00
function EmptyBalanceView(): JSX.Element {
return (
<p><i18n.Translate>
You have no balance to show. Need some{" "}
2021-05-07 23:10:27 +02:00
<PageLink pageName="/welcome">help</PageLink> getting started?
</i18n.Translate></p>
2016-11-28 08:19:06 +01:00
);
}
2021-05-07 15:38:28 +02:00
class WalletBalanceView extends Component<any, any> {
2020-08-12 09:11:00 +02:00
private balance?: BalancesResponse;
2017-05-29 15:18:48 +02:00
private gotError = false;
private canceler: (() => void) | undefined = undefined;
private unmount = false;
private updateBalanceRunning = false;
2016-09-14 15:20:18 +02:00
2020-04-06 20:02:01 +02:00
componentWillMount(): void {
this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
2016-10-10 00:37:08 +02:00
this.updateBalance();
}
2016-02-18 23:41:29 +01:00
2020-04-06 20:02:01 +02:00
componentWillUnmount(): void {
console.log("component WalletBalanceView will unmount");
if (this.canceler) {
this.canceler();
}
this.unmount = true;
2016-10-10 00:37:08 +02:00
}
2016-02-18 23:41:29 +01:00
2020-04-06 20:02:01 +02:00
async updateBalance(): Promise<void> {
if (this.updateBalanceRunning) {
return;
}
this.updateBalanceRunning = true;
2020-08-12 09:11:00 +02:00
let balance: BalancesResponse;
try {
balance = await wxApi.getBalance();
} catch (e) {
if (this.unmount) {
return;
}
this.gotError = true;
console.error("could not retrieve balances", e);
2016-10-18 02:40:46 +02:00
this.setState({});
return;
} finally {
this.updateBalanceRunning = false;
}
if (this.unmount) {
return;
}
this.gotError = false;
console.log("got balance", balance);
this.balance = balance;
this.setState({});
2016-02-18 23:41:29 +01:00
}
2020-08-12 09:11:00 +02:00
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" }}>
{"+"}
2017-06-04 17:56:55 +02:00
{renderAmount(entry.pendingIncoming)}
</span>{" "}
incoming
</i18n.Translate></span>
2016-11-23 01:14:45 +01:00
);
}
2020-03-30 12:39:32 +02:00
const l = [incoming, payment].filter((x) => x !== undefined);
2017-05-29 15:18:48 +02:00
if (l.length === 0) {
return <span />;
}
2017-05-29 15:18:48 +02:00
if (l.length === 1) {
return <span>({l})</span>;
}
return (
<span>
({l[0]}, {l[1]})
</span>
);
2016-10-10 03:16:12 +02:00
}
2016-10-10 00:37:08 +02:00
render(): JSX.Element {
2017-05-29 15:18:48 +02:00
const wallet = this.balance;
2016-10-10 00:37:08 +02:00
if (this.gotError) {
return (
2020-03-30 12:39:32 +02:00
<div className="balance">
<p>{i18n.str`Error: could not retrieve balance information.`}</p>
<p>
2019-09-05 16:23:54 +02:00
Click <PageLink pageName="welcome.html">here</PageLink> for help and
diagnostics.
</p>
</div>
);
2016-03-02 00:47:00 +01:00
}
if (!wallet) {
2016-10-20 01:37:00 +02:00
return <span></span>;
}
2016-10-10 00:37:08 +02:00
console.log(wallet);
const listing = wallet.balances.map((entry) => {
const av = Amounts.parseOrThrow(entry.available);
2016-10-19 18:40:29 +02:00
return (
<p key={av.currency}>
{bigAmount(av)} {this.formatPending(entry)}
2016-10-19 18:40:29 +02:00
</p>
);
2016-10-10 00:37:08 +02:00
});
2020-03-30 12:39:32 +02:00
return listing.length > 0 ? (
<div className="balance">{listing}</div>
) : (
<EmptyBalanceView />
);
}
2016-02-18 23:41:29 +01:00
}
2020-11-18 17:33:02 +01:00
interface TransactionAmountProps {
debitCreditIndicator: "debit" | "credit" | "unknown";
amount: AmountString | "unknown";
pending: boolean;
}
2020-11-18 17:33:02 +01:00
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 = "";
}
2021-05-07 15:38:28 +02:00
const style: JSX.AllCSSProperties = {
2020-11-18 17:33:02 +01:00
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;
2021-06-03 06:07:29 +02:00
id: string;
2020-11-18 17:33:02 +01:00
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" }}>
2021-06-03 06:07:29 +02:00
<a href={Pages.transaction.replace(':tid', props.id)}><span>{props.title}</span></a>
2020-11-18 17:33:02 +01:00
{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;
2020-11-18 17:33:02 +01:00
switch (tx.type) {
case TransactionType.Withdrawal:
return (
<TransactionLayout
2021-06-03 06:07:29 +02:00
id={tx.transactionId}
2020-11-18 17:33:02 +01:00
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
2021-06-03 06:07:29 +02:00
id={tx.transactionId}
2020-11-18 17:33:02 +01:00
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
2021-06-03 06:07:29 +02:00
id={tx.transactionId}
2020-11-18 17:33:02 +01:00
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
2021-06-03 06:07:29 +02:00
id={tx.transactionId}
2020-11-18 17:33:02 +01:00
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
2021-06-03 06:07:29 +02:00
id={tx.transactionId}
2020-11-18 17:33:02 +01:00
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>
2021-01-18 23:35:41 +01:00
);
case TransactionType.Deposit:
return (
<TransactionLayout
2021-06-03 06:07:29 +02:00
id={tx.transactionId}
2021-01-18 23:35:41 +01:00
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>
2020-11-18 17:33:02 +01:00
);
}
}
function WalletHistory(props: any): JSX.Element {
const [transactions, setTransactions] = useState<
TransactionsResponse | undefined
2021-05-07 15:38:28 +02:00
>(undefined);
useEffect(() => {
const fetchData = async (): Promise<void> => {
const res = await wxApi.getTransactions();
setTransactions(res);
};
fetchData();
}, []);
if (!transactions) {
return <div>Loading ...</div>;
}
2020-11-18 17:33:02 +01:00
const txs = [...transactions.transactions].reverse();
return (
<div>
2021-05-07 23:10:27 +02:00
{txs.map((tx, i) => (
2021-05-07 15:38:28 +02:00
<TransactionItem key={i} tx={tx} />
))}
</div>
);
}
2021-06-03 06:07:29 +02:00
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>;
2021-06-03 06:07:29 +02:00
}
function Footer() {
return <footer style={{ marginTop: 'auto', display: 'flex' }}>
<button onClick={onBack}><i18n.Translate>back</i18n.Translate></button>
2021-06-03 06:07:29 +02:00
<div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
<button onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button>
2021-06-03 06:07:29 +02:00
</div>
</footer>
}
function Pending() {
if (!transaction?.pending) return null
return <span style={{ fontWeight: 'normal', fontSize: 16, color: 'gray' }}>(pending...)</span>
2021-06-03 06:07:29 +02:00
}
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"}>
2021-06-08 14:17:12 +02:00
<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>
2021-06-03 06:07:29 +02:00
</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>
2021-06-03 06:07:29 +02:00
<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' }}>
2021-06-03 06:07:29 +02:00
{transaction.info.products.map(p =>
<li>{p.description}</li>
)}</ol></td>
</tr>
}
2021-06-08 14:17:12 +02:00
<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>
2021-06-03 06:07:29 +02:00
</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"}>
2021-06-08 14:17:12 +02:00
<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>
2021-06-03 06:07:29 +02:00
</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"}>
2021-06-08 14:17:12 +02:00
<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>
2021-06-03 06:07:29 +02:00
</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"}>
2021-06-08 14:17:12 +02:00
<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>
2021-06-03 06:07:29 +02:00
</table>
</section>
<Footer />
</div>
);
}
const TRANSACTION_FROM_REFUND = /[a-z]*:([\w]{10}).*/
2021-06-03 06:07:29 +02:00
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>
2021-06-03 06:07:29 +02:00
<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>
}
2021-06-08 14:17:12 +02:00
<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>
2021-06-03 06:07:29 +02:00
</table>
</section>
<Footer />
</div>
);
}
return <div></div>
}
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)
2021-06-03 06:07:29 +02:00
}
};
fetchData();
}, []);
return <WalletTransactionView
transaction={transaction}
onDelete={() => wxApi.deleteTransaction(tid).then(_ => history.go(-1))}
2021-06-03 06:07:29 +02:00
onBack={() => { history.go(-1) }}
/>
}
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>
);
2020-05-04 13:46:06 +02:00
}
2020-04-06 20:02:01 +02:00
function reload(): void {
2016-01-26 17:21:17 +01:00
try {
chrome.runtime.reload();
window.close();
} catch (e) {
// Functionality missing in firefox, ignore!
}
}
2021-05-07 15:38:28 +02:00
async function confirmReset(): Promise<void> {
if (
confirm(
"Do you want to IRREVOCABLY DESTROY everything inside your" +
2021-05-07 23:10:27 +02:00
" wallet and LOSE ALL YOUR COINS?",
)
) {
2021-05-07 15:38:28 +02:00
await wxApi.resetDb();
window.close();
}
}
2020-04-06 20:02:01 +02:00
function WalletDebug(props: any): JSX.Element {
return (
<div>
<p>Debug tools:</p>
2020-11-18 17:33:02 +01:00
<button onClick={openExtensionPage("/static/popup.html")}>wallet tab</button>
<br />
<button onClick={confirmReset}>reset</button>
<button onClick={reload}>reload chrome extension</button>
<Diagnostics />
</div>
);
2016-10-10 00:37:08 +02:00
}
2016-09-12 20:25:56 +02:00
function openExtensionPage(page: string) {
2017-05-29 15:18:48 +02:00
return () => {
2016-01-26 17:21:17 +01:00
chrome.tabs.create({
2017-05-29 15:18:48 +02:00
url: chrome.extension.getURL(page),
});
2017-05-29 15:18:48 +02:00
};
}
2016-01-26 17:21:17 +01:00
2021-05-07 15:38:28 +02:00
// 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;
}
function actionForTalerUri(talerUri: string): string | undefined {
2020-08-12 09:11:00 +02:00
const uriType = classifyTalerUri(talerUri);
switch (uriType) {
2020-08-12 09:11:00 +02:00
case TalerUriType.TalerWithdraw:
return makeExtensionUrlWithParams("static/popup.html#/withdraw", {
talerWithdrawUri: talerUri,
});
2020-08-12 09:11:00 +02:00
case TalerUriType.TalerPay:
return makeExtensionUrlWithParams("static/popup.html#/pay", {
talerPayUri: talerUri,
});
2020-08-12 09:11:00 +02:00
case TalerUriType.TalerTip:
return makeExtensionUrlWithParams("static/popup.html#/tip", {
talerTipUri: talerUri,
});
2020-08-12 09:11:00 +02:00
case TalerUriType.TalerRefund:
return makeExtensionUrlWithParams("static/popup.html#/refund", {
talerRefundUri: talerUri,
});
2020-08-12 09:11:00 +02:00
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;
}
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]);
},
);
});
}
2021-05-07 23:10:27 +02:00
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>
);
}
2019-09-05 16:23:54 +02:00
return (
<div>
2021-05-07 23:10:27 +02:00
<Match>{({ path }: any) => <WalletNavBar current={path} />}</Match>
<div style={{ margin: "1em", width: 400 }}>
2019-09-05 16:23:54 +02:00
<Router>
2021-05-07 23:10:27 +02:00
<Route path={Pages.balance} component={WalletBalanceView} />
<Route path={Pages.settings} component={WalletSettings} />
<Route path={Pages.debug} component={WalletDebug} />
<Route path={Pages.history} component={WalletHistory} />
2021-06-03 06:07:29 +02:00
<Route path={Pages.transaction} component={WalletTransaction} />
2019-09-05 16:23:54 +02:00
</Router>
</div>
</div>
2019-09-05 16:23:54 +02:00
);
}
2021-05-07 23:10:27 +02:00
enum Pages {
balance = '/popup/balance',
2021-06-03 06:07:29 +02:00
transaction = '/popup/transaction/:tid',
2021-05-07 23:10:27 +02:00
settings = '/popup/settings',
debug = '/popup/debug',
history = '/popup/history',
}
export function Redirect({ to }: { to: string }): null {
useEffect(() => {
route(to, true)
})
return null
2020-04-07 10:07:32 +02:00
}