aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-06-16 17:17:12 -0300
committerSebastian <sebasjm@gmail.com>2021-06-16 17:17:18 -0300
commitd58945c830a33910dd93bc159c1ffe5d490df846 (patch)
tree52ee1fd627542ac415401c86e733ec32bf7fca91 /packages/taler-wallet-webextension/src/wallet
parent86636142a2e3fb3373363c2c0c87ce5167975b74 (diff)
downloadwallet-core-d58945c830a33910dd93bc159c1ffe5d490df846.tar.xz
split wallet/popup components. created hooks, components, context folder
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/pay.tsx235
-rw-r--r--packages/taler-wallet-webextension/src/wallet/payback.tsx31
-rw-r--r--packages/taler-wallet-webextension/src/wallet/refund.tsx108
-rw-r--r--packages/taler-wallet-webextension/src/wallet/reset-required.tsx97
-rw-r--r--packages/taler-wallet-webextension/src/wallet/return-coins.tsx30
-rw-r--r--packages/taler-wallet-webextension/src/wallet/tip.tsx109
-rw-r--r--packages/taler-wallet-webextension/src/wallet/welcome.tsx83
-rw-r--r--packages/taler-wallet-webextension/src/wallet/withdraw.stories.tsx66
-rw-r--r--packages/taler-wallet-webextension/src/wallet/withdraw.tsx173
9 files changed, 932 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/pay.tsx b/packages/taler-wallet-webextension/src/wallet/pay.tsx
new file mode 100644
index 000000000..e958cd484
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/pay.tsx
@@ -0,0 +1,235 @@
+/*
+ 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/payback.tsx b/packages/taler-wallet-webextension/src/wallet/payback.tsx
new file mode 100644
index 000000000..4233b1f96
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/payback.tsx
@@ -0,0 +1,31 @@
+/*
+ This file is part of TALER
+ (C) 2017 Inria
+
+ 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/jsx-runtime";
+
+/**
+ * View and edit auditors.
+ *
+ * @author Florian Dold
+ */
+
+/**
+ * Imports.
+ */
+
+export function makePaybackPage(): JSX.Element {
+ return <div>not implemented</div>;
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/refund.tsx b/packages/taler-wallet-webextension/src/wallet/refund.tsx
new file mode 100644
index 000000000..1991bc9d8
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/refund.tsx
@@ -0,0 +1,108 @@
+/*
+ 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/reset-required.tsx b/packages/taler-wallet-webextension/src/wallet/reset-required.tsx
new file mode 100644
index 000000000..0be7c09c5
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/reset-required.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 to inform the user when a database reset is required.
+ *
+ * @author Florian Dold
+ */
+
+import { Component, JSX } from "preact";
+import * as wxApi from "../wxApi";
+
+interface State {
+ /**
+ * Did the user check the confirmation check box?
+ */
+ checked: boolean;
+
+ /**
+ * Do we actually need to reset the db?
+ */
+ resetRequired: boolean;
+}
+
+class ResetNotification extends Component<any, State> {
+ constructor(props: any) {
+ super(props);
+ this.state = { checked: false, resetRequired: true };
+ setInterval(() => this.update(), 500);
+ }
+ async update(): Promise<void> {
+ const res = await wxApi.checkUpgrade();
+ this.setState({ resetRequired: res.dbResetRequired });
+ }
+ render(): JSX.Element {
+ if (this.state.resetRequired) {
+ return (
+ <div>
+ <h1>Manual Reset Required</h1>
+ <p>
+ The wallet&apos;s database in your browser is incompatible with the{" "}
+ currently installed wallet. Please reset manually.
+ </p>
+ <p>
+ Once the database format has stabilized, we will provide automatic
+ upgrades.
+ </p>
+ <input
+ id="check"
+ type="checkbox"
+ checked={this.state.checked}
+ onChange={() => {
+ this.setState(prev => ({ checked: prev.checked }))
+ }}
+ />{" "}
+ <label htmlFor="check">
+ I understand that I will lose all my data
+ </label>
+ <br />
+ <button
+ className="pure-button"
+ disabled={!this.state.checked}
+ onClick={() => wxApi.resetDb()}
+ >
+ Reset
+ </button>
+ </div>
+ );
+ }
+ return (
+ <div>
+ <h1>Everything is fine!</h1>A reset is not required anymore, you can
+ close this page.
+ </div>
+ );
+ }
+}
+
+/**
+ * @deprecated to be removed
+ */
+export function createResetRequiredPage(): JSX.Element {
+ return <ResetNotification />;
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/return-coins.tsx b/packages/taler-wallet-webextension/src/wallet/return-coins.tsx
new file mode 100644
index 000000000..2273d1454
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/return-coins.tsx
@@ -0,0 +1,30 @@
+/*
+ This file is part of TALER
+ (C) 2017 Inria
+
+ 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/jsx-runtime";
+
+/**
+ * Return coins to own bank account.
+ *
+ * @author Florian Dold
+ */
+
+/**
+ * Imports.
+ */
+export function createReturnCoinsPage(): JSX.Element {
+ return <span>Not implemented yet.</span>;
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/tip.tsx b/packages/taler-wallet-webextension/src/wallet/tip.tsx
new file mode 100644
index 000000000..d832976d8
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/tip.tsx
@@ -0,0 +1,109 @@
+/*
+ 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
new file mode 100644
index 000000000..9be62bf8b
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/welcome.tsx
@@ -0,0 +1,83 @@
+/*
+ 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
new file mode 100644
index 000000000..86f0eec90
--- /dev/null
+++ 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
new file mode 100644
index 000000000..cb96fa4df
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/withdraw.tsx
@@ -0,0 +1,173 @@
+/*
+ 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} />;
+}