summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-11-16 13:59:53 -0300
committerSebastian <sebasjm@gmail.com>2021-11-16 14:01:38 -0300
commita994009d2f094c4d9c12da68dac3abb28bdef4b3 (patch)
treee403a58663f81889982635ffb324f9739e6976b3 /packages/taler-wallet-webextension/src/wallet
parentc33ed919719845f518d6491ef37df6ae16820dd0 (diff)
downloadwallet-core-a994009d2f094c4d9c12da68dac3abb28bdef4b3.tar.gz
wallet-core-a994009d2f094c4d9c12da68dac3abb28bdef4b3.tar.bz2
wallet-core-a994009d2f094c4d9c12da68dac3abb28bdef4b3.zip
reserveCreated new design
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/BackupPage.tsx12
-rw-r--r--packages/taler-wallet-webextension/src/wallet/BalancePage.tsx18
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx21
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx89
-rw-r--r--packages/taler-wallet-webextension/src/wallet/History.tsx37
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx55
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx119
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx26
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx183
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.tsx92
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Welcome.tsx7
11 files changed, 396 insertions, 263 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
index c3be0203e..f0ae38e0f 100644
--- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
@@ -24,18 +24,17 @@ import {
formatDuration,
intervalToDuration,
} from "date-fns";
-import { Fragment, JSX, VNode, h } from "preact";
+import { Fragment, h, VNode } from "preact";
import {
BoldLight,
ButtonPrimary,
ButtonSuccess,
Centered,
- CenteredText,
CenteredBoldText,
- PopupBox,
+ CenteredText,
RowBorderGray,
- SmallText,
SmallLightText,
+ SmallText,
WalletBox,
} from "../components/styled";
import { useBackupStatus } from "../hooks/useBackupStatus";
@@ -73,8 +72,9 @@ export function BackupView({
return (
<WalletBox>
<section>
- {providers.map((provider) => (
+ {providers.map((provider, idx) => (
<BackupLayout
+ key={idx}
status={provider.paymentStatus}
timestamp={provider.lastSuccessfulBackupTimestamp}
id={provider.syncProviderBaseUrl}
@@ -118,7 +118,7 @@ interface TransactionLayoutProps {
active: boolean;
}
-function BackupLayout(props: TransactionLayoutProps): JSX.Element {
+function BackupLayout(props: TransactionLayoutProps): VNode {
const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
const dateStr = date?.toLocaleString([], {
dateStyle: "medium",
diff --git a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
index f3c08a3e8..9a2847670 100644
--- a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
@@ -21,7 +21,7 @@ import {
BalancesResponse,
i18n,
} from "@gnu-taler/taler-util";
-import { JSX, h } from "preact";
+import { h, VNode } from "preact";
import { ButtonPrimary, Centered, WalletBox } from "../components/styled/index";
import { BalancesHook, useBalances } from "../hooks/useBalances";
import { PageLink, renderAmount } from "../renderHtml";
@@ -30,7 +30,7 @@ export function BalancePage({
goToWalletManualWithdraw,
}: {
goToWalletManualWithdraw: () => void;
-}) {
+}): VNode {
const balance = useBalances();
return (
<BalanceView
@@ -51,7 +51,7 @@ export function BalanceView({
balance,
Linker,
goToWalletManualWithdraw,
-}: BalanceViewProps) {
+}: BalanceViewProps): VNode {
if (!balance) {
return <span />;
}
@@ -85,13 +85,13 @@ export function BalanceView({
);
}
-function formatPending(entry: Balance): JSX.Element {
- let incoming: JSX.Element | undefined;
- let payment: JSX.Element | undefined;
+function formatPending(entry: Balance): VNode {
+ let incoming: VNode | undefined;
+ let payment: VNode | undefined;
- const available = Amounts.parseOrThrow(entry.available);
+ // const available = Amounts.parseOrThrow(entry.available);
const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
- const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
+ // const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
if (!Amounts.isZero(pendingIncoming)) {
incoming = (
@@ -128,7 +128,7 @@ function ShowBalances({
}: {
wallet: BalancesResponse;
onWithdraw: () => void;
-}) {
+}): VNode {
return (
<WalletBox>
<section>
diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
index 6eab8dc3a..300e9cd57 100644
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
@@ -28,26 +28,27 @@ export default {
argTypes: {},
};
-export const InitialState = createExample(TestedComponent, {});
+// ,
+const exchangeList = {
+ "http://exchange.taler:8081": "COL",
+ "http://exchange.tal": "EUR",
+};
-export const WithExchangeFilled = createExample(TestedComponent, {
- currency: "COL",
- initialExchange: "http://exchange.taler:8081",
+export const InitialState = createExample(TestedComponent, {
+ exchangeList,
});
-export const WithExchangeAndAmountFilled = createExample(TestedComponent, {
- currency: "COL",
- initialExchange: "http://exchange.taler:8081",
+export const WithAmountInitialized = createExample(TestedComponent, {
initialAmount: "10",
+ exchangeList,
});
export const WithExchangeError = createExample(TestedComponent, {
- initialExchange: "http://exchange.tal",
error: "The exchange url seems invalid",
+ exchangeList,
});
export const WithAmountError = createExample(TestedComponent, {
- currency: "COL",
- initialExchange: "http://exchange.taler:8081",
initialAmount: "e",
+ exchangeList,
});
diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
index b48dcbaf2..140ac2d40 100644
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
@@ -20,9 +20,10 @@
*/
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
-import { useEffect, useRef, useState } from "preact/hooks";
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
import { ErrorMessage } from "../components/ErrorMessage";
+import { SelectList } from "../components/SelectList";
import {
ButtonPrimary,
Input,
@@ -33,32 +34,56 @@ import {
export interface Props {
error: string | undefined;
- currency: string | undefined;
- initialExchange?: string;
initialAmount?: string;
- onExchangeChange: (exchange: string) => void;
+ exchangeList: Record<string, string>;
onCreate: (exchangeBaseUrl: string, amount: AmountJson) => Promise<void>;
}
export function CreateManualWithdraw({
- onExchangeChange,
- initialExchange,
initialAmount,
+ exchangeList,
error,
- currency,
onCreate,
}: Props): VNode {
+ const exchangeSelectList = Object.keys(exchangeList);
+ const currencySelectList = Object.values(exchangeList);
+ const exchangeMap = exchangeSelectList.reduce(
+ (p, c) => ({ ...p, [c]: `${c} (${exchangeList[c]})` }),
+ {} as Record<string, string>,
+ );
+ const currencyMap = currencySelectList.reduce(
+ (p, c) => ({ ...p, [c]: c }),
+ {} as Record<string, string>,
+ );
+
+ const initialExchange =
+ exchangeSelectList.length > 0 ? exchangeSelectList[0] : "";
+
const [exchange, setExchange] = useState(initialExchange || "");
+ const [currency, setCurrency] = useState(exchangeList[initialExchange] ?? "");
+
const [amount, setAmount] = useState(initialAmount || "");
const parsedAmount = Amounts.parse(`${currency}:${amount}`);
- let timeout = useRef<number | undefined>(undefined);
- useEffect(() => {
- if (timeout) window.clearTimeout(timeout.current);
- timeout.current = window.setTimeout(async () => {
- onExchangeChange(exchange);
- }, 1000);
- }, [exchange]);
+ function changeExchange(exchange: string): void {
+ setExchange(exchange);
+ setCurrency(exchangeList[exchange]);
+ }
+
+ function changeCurrency(currency: string): void {
+ setCurrency(currency);
+ const found = Object.entries(exchangeList).find((e) => e[1] === currency);
+
+ if (found) {
+ setExchange(found[0]);
+ } else {
+ setExchange("");
+ }
+ }
+
+ if (!initialExchange) {
+ return <div>There is no known exchange where to withdraw, add one</div>;
+ }
return (
<WalletBox>
@@ -73,26 +98,38 @@ export function CreateManualWithdraw({
withdraw the coins
</LightText>
<p>
- <Input invalid={!!exchange && !currency}>
- <label>Exchange</label>
- <input
- type="text"
- placeholder="https://"
+ <Input>
+ <SelectList
+ label="Currency"
+ list={currencyMap}
+ name="currency"
+ value={currency}
+ onChange={changeCurrency}
+ />
+ </Input>
+ <Input>
+ <SelectList
+ label="Exchange"
+ list={exchangeMap}
+ name="currency"
value={exchange}
- onChange={(e) => setExchange(e.currentTarget.value)}
+ onChange={changeExchange}
/>
- <small>http://exchange.taler:8081</small>
</Input>
+ {/* <p style={{ display: "flex", justifyContent: "right" }}>
+ <a href="" style={{ marginLeft: "auto" }}>
+ Add new exchange
+ </a>
+ </p> */}
{currency && (
<InputWithLabel invalid={!!amount && !parsedAmount}>
<label>Amount</label>
<div>
- <div>{currency}</div>
+ <span>{currency}</span>
<input
type="number"
- style={{ paddingLeft: `${currency.length}em` }}
value={amount}
- onChange={(e) => setAmount(e.currentTarget.value)}
+ onInput={(e) => setAmount(e.currentTarget.value)}
/>
</div>
</InputWithLabel>
@@ -105,7 +142,7 @@ export function CreateManualWithdraw({
disabled={!parsedAmount || !exchange}
onClick={() => onCreate(exchange, parsedAmount!)}
>
- Create
+ Start withdrawal
</ButtonPrimary>
</footer>
</WalletBox>
diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx
index aabe50a29..6b1a21852 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.tsx
@@ -20,15 +20,15 @@ import {
Transaction,
TransactionsResponse,
} from "@gnu-taler/taler-util";
-import { format } from "date-fns";
-import { Fragment, h, JSX } from "preact";
+import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { DateSeparator, WalletBox } from "../components/styled";
+import { Time } from "../components/Time";
import { TransactionItem } from "../components/TransactionItem";
import { useBalances } from "../hooks/useBalances";
import * as wxApi from "../wxApi";
-export function HistoryPage(props: any): JSX.Element {
+export function HistoryPage(): VNode {
const [transactions, setTransactions] = useState<
TransactionsResponse | undefined
>(undefined);
@@ -57,24 +57,30 @@ export function HistoryPage(props: any): JSX.Element {
);
}
-function amountToString(c: AmountString) {
+function amountToString(c: AmountString): string {
const idx = c.indexOf(":");
return `${c.substring(idx + 1)} ${c.substring(0, idx)}`;
}
+const term = 1000 * 60 * 60 * 24;
+function normalizeToDay(x: number): number {
+ return Math.round(x / term) * term;
+}
+
export function HistoryView({
list,
balances,
}: {
list: Transaction[];
balances: Balance[];
-}) {
- const byDate = list.reduce(function (rv, x) {
+}): VNode {
+ const byDate = list.reduce((rv, x) => {
const theDate =
- x.timestamp.t_ms === "never"
- ? "never"
- : format(x.timestamp.t_ms, "dd MMMM yyyy");
- (rv[theDate] = rv[theDate] || []).push(x);
+ x.timestamp.t_ms === "never" ? 0 : normalizeToDay(x.timestamp.t_ms);
+ if (theDate) {
+ (rv[theDate] = rv[theDate] || []).push(x);
+ }
+
return rv;
}, {} as { [x: string]: Transaction[] });
@@ -93,8 +99,8 @@ export function HistoryView({
<div class="title">
Balance:{" "}
<ul style={{ margin: 0 }}>
- {balances.map((b) => (
- <li>{b.available}</li>
+ {balances.map((b, i) => (
+ <li key={i}>{b.available}</li>
))}
</ul>
</div>
@@ -105,7 +111,12 @@ export function HistoryView({
{Object.keys(byDate).map((d, i) => {
return (
<Fragment key={i}>
- <DateSeparator>{d}</DateSeparator>
+ <DateSeparator>
+ <Time
+ timestamp={{ t_ms: Number.parseInt(d, 10) }}
+ format="dd MMMM yyyy"
+ />
+ </DateSeparator>
{byDate[d].map((tx, i) => (
<TransactionItem
key={i}
diff --git a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
index 102978f9e..1af4e8d8d 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
@@ -26,44 +26,31 @@ import {
import { ReserveCreated } from "./ReserveCreated.js";
import { route } from "preact-router";
import { Pages } from "../NavigationBar.js";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
-interface Props {}
-
-export function ManualWithdrawPage({}: Props): VNode {
+export function ManualWithdrawPage(): VNode {
const [success, setSuccess] = useState<
- AcceptManualWithdrawalResult | undefined
+ | {
+ response: AcceptManualWithdrawalResult;
+ exchangeBaseUrl: string;
+ amount: AmountJson;
+ }
+ | undefined
>(undefined);
- const [currency, setCurrency] = useState<string | undefined>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
- async function onExchangeChange(exchange: string | undefined): Promise<void> {
- if (!exchange) return;
- try {
- const r = await fetch(`${exchange}/keys`);
- const j = await r.json();
- if (j.currency) {
- await wxApi.addExchange({
- exchangeBaseUrl: `${exchange}/`,
- forceUpdate: true,
- });
- setCurrency(j.currency);
- }
- } catch (e) {
- setError("The exchange url seems invalid");
- setCurrency(undefined);
- }
- }
+ const knownExchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
async function doCreate(
exchangeBaseUrl: string,
amount: AmountJson,
): Promise<void> {
try {
- const resp = await wxApi.acceptManualWithdrawal(
+ const response = await wxApi.acceptManualWithdrawal(
exchangeBaseUrl,
Amounts.stringify(amount),
);
- setSuccess(resp);
+ setSuccess({ exchangeBaseUrl, response, amount });
} catch (e) {
if (e instanceof Error) {
setError(e.message);
@@ -77,8 +64,10 @@ export function ManualWithdrawPage({}: Props): VNode {
if (success) {
return (
<ReserveCreated
- reservePub={success.reservePub}
- paytos={success.exchangePaytoUris}
+ reservePub={success.response.reservePub}
+ payto={success.response.exchangePaytoUris[0]}
+ exchangeBaseUrl={success.exchangeBaseUrl}
+ amount={success.amount}
onBack={() => {
route(Pages.balance);
}}
@@ -86,12 +75,22 @@ export function ManualWithdrawPage({}: Props): VNode {
);
}
+ if (!knownExchangesHook || knownExchangesHook.hasError) {
+ return <div>No Known exchanges</div>;
+ }
+ const exchangeList = knownExchangesHook.response.exchanges.reduce(
+ (p, c) => ({
+ ...p,
+ [c.exchangeBaseUrl]: c.currency,
+ }),
+ {} as Record<string, string>,
+ );
+
return (
<CreateManualWithdraw
error={error}
- currency={currency}
+ exchangeList={exchangeList}
onCreate={doCreate}
- onExchangeChange={onExchangeChange}
/>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
index bd64b0760..1c14c6e0a 100644
--- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
@@ -14,23 +14,23 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { i18n, Timestamp } from "@gnu-taler/taler-util";
+import { i18n } from "@gnu-taler/taler-util";
import {
ProviderInfo,
ProviderPaymentStatus,
ProviderPaymentType,
} from "@gnu-taler/taler-wallet-core";
-import { format, formatDuration, intervalToDuration } from "date-fns";
-import { Fragment, VNode, h } from "preact";
+import { Fragment, h, VNode } from "preact";
import { ErrorMessage } from "../components/ErrorMessage";
import {
Button,
ButtonDestructive,
ButtonPrimary,
PaymentStatus,
- WalletBox,
SmallLightText,
+ WalletBox,
} from "../components/styled";
+import { Time } from "../components/Time";
import { useProviderStatus } from "../hooks/useProviderStatus";
interface Props {
@@ -97,10 +97,7 @@ export function ProviderView({
</header>
<section>
<p>
- <b>Last backup:</b>{" "}
- {lb == null || lb.t_ms == "never"
- ? "never"
- : format(lb.t_ms, "dd MMM yyyy")}{" "}
+ <b>Last backup:</b> <Time timestamp={lb} format="dd MMMM yyyy" />
</p>
<ButtonPrimary onClick={onSync}>
<i18n.Translate>Back up</i18n.Translate>
@@ -128,7 +125,7 @@ export function ProviderView({
<table>
<thead>
<tr>
- <td></td>
+ <td>&nbsp;</td>
<td>
<i18n.Translate>old</i18n.Translate>
</td>
@@ -174,32 +171,32 @@ export function ProviderView({
);
}
-function daysSince(d?: Timestamp) {
- if (!d || d.t_ms === "never") return "never synced";
- const duration = intervalToDuration({
- start: d.t_ms,
- end: new Date(),
- });
- const str = formatDuration(duration, {
- delimiter: ", ",
- format: [
- duration?.years
- ? i18n.str`years`
- : duration?.months
- ? i18n.str`months`
- : duration?.days
- ? i18n.str`days`
- : duration?.hours
- ? i18n.str`hours`
- : duration?.minutes
- ? i18n.str`minutes`
- : i18n.str`seconds`,
- ],
- });
- return `synced ${str} ago`;
-}
+// function daysSince(d?: Timestamp): string {
+// if (!d || d.t_ms === "never") return "never synced";
+// const duration = intervalToDuration({
+// start: d.t_ms,
+// end: new Date(),
+// });
+// const str = formatDuration(duration, {
+// delimiter: ", ",
+// format: [
+// duration?.years
+// ? i18n.str`years`
+// : duration?.months
+// ? i18n.str`months`
+// : duration?.days
+// ? i18n.str`days`
+// : duration?.hours
+// ? i18n.str`hours`
+// : duration?.minutes
+// ? i18n.str`minutes`
+// : i18n.str`seconds`,
+// ],
+// });
+// return `synced ${str} ago`;
+// }
-function Error({ info }: { info: ProviderInfo }) {
+function Error({ info }: { info: ProviderInfo }): VNode {
if (info.lastError) {
return <ErrorMessage title={info.lastError.hint} />;
}
@@ -234,45 +231,45 @@ function Error({ info }: { info: ProviderInfo }) {
);
}
}
- return null;
+ return <Fragment />;
}
-function colorByStatus(status: ProviderPaymentType) {
- switch (status) {
- case ProviderPaymentType.InsufficientBalance:
- return "rgb(223, 117, 20)";
- case ProviderPaymentType.Unpaid:
- return "rgb(202, 60, 60)";
- case ProviderPaymentType.Paid:
- return "rgb(28, 184, 65)";
- case ProviderPaymentType.Pending:
- return "gray";
- case ProviderPaymentType.InsufficientBalance:
- return "rgb(202, 60, 60)";
- case ProviderPaymentType.TermsChanged:
- return "rgb(202, 60, 60)";
- }
-}
+// function colorByStatus(status: ProviderPaymentType): string {
+// switch (status) {
+// case ProviderPaymentType.InsufficientBalance:
+// return "rgb(223, 117, 20)";
+// case ProviderPaymentType.Unpaid:
+// return "rgb(202, 60, 60)";
+// case ProviderPaymentType.Paid:
+// return "rgb(28, 184, 65)";
+// case ProviderPaymentType.Pending:
+// return "gray";
+// // case ProviderPaymentType.InsufficientBalance:
+// // return "rgb(202, 60, 60)";
+// case ProviderPaymentType.TermsChanged:
+// return "rgb(202, 60, 60)";
+// }
+// }
-function descriptionByStatus(status: ProviderPaymentStatus) {
+function descriptionByStatus(status: ProviderPaymentStatus): VNode {
switch (status.type) {
// return i18n.str`no enough balance to make the payment`
// return i18n.str`not paid yet`
case ProviderPaymentType.Paid:
case ProviderPaymentType.TermsChanged:
if (status.paidUntil.t_ms === "never") {
- return i18n.str`service paid`;
- } else {
- return (
- <Fragment>
- <b>Backup valid until:</b>{" "}
- {format(status.paidUntil.t_ms, "dd MMM yyyy")}
- </Fragment>
- );
+ return <span>{i18n.str`service paid`}</span>;
}
+ return (
+ <Fragment>
+ <b>Backup valid until:</b>{" "}
+ <Time timestamp={status.paidUntil} format="dd MMM yyyy" />
+ </Fragment>
+ );
+
case ProviderPaymentType.Unpaid:
case ProviderPaymentType.InsufficientBalance:
case ProviderPaymentType.Pending:
- return "";
+ return <span />;
}
}
diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx
index c552b19ba..8d7b65b3c 100644
--- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx
@@ -28,10 +28,26 @@ export default {
argTypes: {},
};
-export const InitialState = createExample(TestedComponent, {
- reservePub: "ASLKDJQWLKEJASLKDJSADLKASJDLKSADJ",
- paytos: [
+export const TalerBank = createExample(TestedComponent, {
+ reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+ payto:
"payto://x-taler-bank/bank.taler:5882/exchangeminator?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
- "payto://x-taler-bank/international-bank.com/myaccount?amount=COL%3A1&message=Taler+Withdrawal+TYQTE7VA4M9GZQ4TR06YBNGA05AJGMFNSK4Q62NXR2FKNDB1J4EX",
- ],
+ amount: {
+ currency: "USD",
+ value: 10,
+ fraction: 0,
+ },
+ exchangeBaseUrl: "https://exchange.demo.taler.net",
+});
+
+export const IBAN = createExample(TestedComponent, {
+ reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+ payto:
+ "payto://iban/ASDQWEASDZXCASDQWE?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+ amount: {
+ currency: "USD",
+ value: 10,
+ fraction: 0,
+ },
+ exchangeBaseUrl: "https://exchange.demo.taler.net",
});
diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
index 9008e9751..a72026ab8 100644
--- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
@@ -1,66 +1,155 @@
-import { h, Fragment, VNode } from "preact";
-import { useState } from "preact/hooks";
+import {
+ AmountJson,
+ Amounts,
+ parsePaytoUri,
+ PaytoUri,
+} from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
import { QR } from "../components/QR";
-import { ButtonBox, FontIcon, WalletBox } from "../components/styled";
+import {
+ ButtonDestructive,
+ ButtonPrimary,
+ WalletBox,
+ WarningBox,
+} from "../components/styled";
export interface Props {
reservePub: string;
- paytos: string[];
+ payto: string;
+ exchangeBaseUrl: string;
+ amount: AmountJson;
onBack: () => void;
}
-export function ReserveCreated({ reservePub, paytos, onBack }: Props): VNode {
- const [opened, setOpened] = useState(-1);
+interface BankDetailsProps {
+ payto: PaytoUri;
+ exchangeBaseUrl: string;
+ subject: string;
+ amount: string;
+}
+
+function Row({
+ name,
+ value,
+ literal,
+}: {
+ name: string;
+ value: string;
+ literal?: boolean;
+}): VNode {
+ const [copied, setCopied] = useState(false);
+ function copyText(): void {
+ navigator.clipboard.writeText(value);
+ setCopied(true);
+ }
+ useEffect(() => {
+ setTimeout(() => {
+ setCopied(false);
+ }, 1000);
+ }, [copied]);
+ return (
+ <tr>
+ <td>
+ {!copied ? (
+ <ButtonPrimary small onClick={copyText}>
+ &nbsp; Copy &nbsp;
+ </ButtonPrimary>
+ ) : (
+ <ButtonPrimary small disabled>
+ Copied
+ </ButtonPrimary>
+ )}
+ </td>
+ <td>
+ <b>{name}</b>
+ </td>
+ {literal ? (
+ <td>
+ <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
+ {value}
+ </pre>
+ </td>
+ ) : (
+ <td>{value}</td>
+ )}
+ </tr>
+ );
+}
+
+function BankDetailsByPaytoType({
+ payto,
+ subject,
+ exchangeBaseUrl,
+ amount,
+}: BankDetailsProps): VNode {
+ const firstPart = !payto.isKnown ? (
+ <Fragment>
+ <Row name="Account" value={payto.targetPath} />
+ <Row name="Exchange" value={exchangeBaseUrl} />
+ </Fragment>
+ ) : payto.targetType === "x-taler-bank" ? (
+ <Fragment>
+ <Row name="Bank host" value={payto.host} />
+ <Row name="Bank account" value={payto.account} />
+ <Row name="Exchange" value={exchangeBaseUrl} />
+ </Fragment>
+ ) : payto.targetType === "iban" ? (
+ <Fragment>
+ <Row name="IBAN" value={payto.iban} />
+ <Row name="Exchange" value={exchangeBaseUrl} />
+ </Fragment>
+ ) : undefined;
+ return (
+ <table>
+ {firstPart}
+ <Row name="Amount" value={amount} />
+ <Row name="Subject" value={subject} literal />
+ </table>
+ );
+}
+export function ReserveCreated({
+ reservePub,
+ payto,
+ onBack,
+ exchangeBaseUrl,
+ amount,
+}: Props): VNode {
+ const paytoURI = parsePaytoUri(payto);
+ // const url = new URL(paytoURI?.targetPath);
+ if (!paytoURI) {
+ return <div>could not parse payto uri from exchange {payto}</div>;
+ }
return (
<WalletBox>
<section>
- <h2>Reserve created!</h2>
- <p>
- Now you need to send money to the exchange to one of the following
- accounts
- </p>
+ <h1>Bank transfer details</h1>
<p>
- To complete the setup of the reserve, you must now initiate a wire
- transfer using the given wire transfer subject and crediting the
- specified amount to the indicated account of the exchange.
+ Please wire <b>{Amounts.stringify(amount)}</b> to:
</p>
+ <BankDetailsByPaytoType
+ amount={Amounts.stringify(amount)}
+ exchangeBaseUrl={exchangeBaseUrl}
+ payto={paytoURI}
+ subject={reservePub}
+ />
</section>
<section>
- <ul>
- {paytos.map((href, idx) => {
- const url = new URL(href);
- return (
- <li key={idx}>
- <p>
- <a
- href=""
- onClick={(e) => {
- setOpened((o) => (o === idx ? -1 : idx));
- e.preventDefault();
- }}
- >
- {url.pathname}
- </a>
- {opened === idx && (
- <Fragment>
- <p>
- If your system supports RFC 8905, you can do this by
- opening <a href={href}>this URI</a> or scan the QR with
- your wallet
- </p>
- <QR text={href} />
- </Fragment>
- )}
- </p>
- </li>
- );
- })}
- </ul>
+ <p>
+ <WarningBox>
+ Make sure to use the correct subject, otherwise the money will not
+ arrive in this wallet.
+ </WarningBox>
+ </p>
+ <p>
+ Alternative, you can also scan this QR code or open{" "}
+ <a href={payto}>this link</a> if you have a banking app installed that
+ supports RFC 8905
+ </p>
+ <QR text={payto} />
</section>
<footer>
- <ButtonBox onClick={onBack}>
- <FontIcon>&#x2190;</FontIcon>
- </ButtonBox>
<div />
+ <ButtonDestructive onClick={onBack}>Cancel withdraw</ButtonDestructive>
</footer>
</WalletBox>
);
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 7de6982e7..1472efb40 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -21,28 +21,27 @@ import {
Transaction,
TransactionType,
} from "@gnu-taler/taler-util";
-import { format } from "date-fns";
-import { JSX, VNode, h } from "preact";
+import { h, VNode } from "preact";
import { route } from "preact-router";
import { useEffect, useState } from "preact/hooks";
import emptyImg from "../../static/img/empty.png";
import { ErrorMessage } from "../components/ErrorMessage";
import { Part } from "../components/Part";
import {
- ButtonBox,
- ButtonBoxDestructive,
+ Button,
+ ButtonDestructive,
ButtonPrimary,
- FontIcon,
ListOfProducts,
RowBorderGray,
SmallLightText,
WalletBox,
WarningBox,
} from "../components/styled";
+import { Time } from "../components/Time";
import { Pages } from "../NavigationBar";
import * as wxApi from "../wxApi";
-export function TransactionPage({ tid }: { tid: string }): JSX.Element {
+export function TransactionPage({ tid }: { tid: string }): VNode {
const [transaction, setTransaction] = useState<Transaction | undefined>(
undefined,
);
@@ -70,8 +69,8 @@ export function TransactionPage({ tid }: { tid: string }): JSX.Element {
return (
<TransactionView
transaction={transaction}
- onDelete={() => wxApi.deleteTransaction(tid).then((_) => history.go(-1))}
- onRetry={() => wxApi.retryTransaction(tid).then((_) => history.go(-1))}
+ onDelete={() => wxApi.deleteTransaction(tid).then(() => history.go(-1))}
+ onRetry={() => wxApi.retryTransaction(tid).then(() => history.go(-1))}
onBack={() => {
route(Pages.history);
}}
@@ -91,42 +90,42 @@ export function TransactionView({
onDelete,
onRetry,
onBack,
-}: WalletTransactionProps) {
- function TransactionTemplate({ children }: { children: VNode[] }) {
+}: WalletTransactionProps): VNode {
+ function TransactionTemplate({ children }: { children: VNode[] }): VNode {
return (
<WalletBox>
<section style={{ padding: 8, textAlign: "center" }}>
<ErrorMessage title={transaction?.error?.hint} />
{transaction.pending && (
- <WarningBox>This transaction is not completed</WarningBox>
+ <WarningBox>
+ This transaction is not completed
+ <a href="">more info...</a>
+ </WarningBox>
)}
</section>
<section>
<div style={{ textAlign: "center" }}>{children}</div>
</section>
<footer>
- <ButtonBox onClick={onBack}>
- <i18n.Translate>
- {" "}
- <FontIcon>&#x2190;</FontIcon>{" "}
- </i18n.Translate>
- </ButtonBox>
+ <Button onClick={onBack}>
+ <i18n.Translate> &lt; Back </i18n.Translate>
+ </Button>
<div>
{transaction?.error ? (
<ButtonPrimary onClick={onRetry}>
<i18n.Translate>retry</i18n.Translate>
</ButtonPrimary>
) : null}
- <ButtonBoxDestructive onClick={onDelete}>
- <i18n.Translate>&#x1F5D1;</i18n.Translate>
- </ButtonBoxDestructive>
+ <ButtonDestructive onClick={onDelete}>
+ <i18n.Translate> Forget </i18n.Translate>
+ </ButtonDestructive>
</div>
</footer>
</WalletBox>
);
}
- function amountToString(text: AmountLike) {
+ function amountToString(text: AmountLike): string {
const aj = Amounts.jsonifyAmount(text);
const amount = Amounts.stringifyValue(aj);
return `${amount} ${aj.currency}`;
@@ -140,23 +139,26 @@ export function TransactionView({
return (
<TransactionTemplate>
<h2>Withdrawal</h2>
- <div>
- {transaction.timestamp.t_ms === "never"
- ? "never"
- : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
- </div>
+ <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
<br />
<Part
+ big
title="Total withdrawn"
text={amountToString(transaction.amountEffective)}
kind="positive"
/>
<Part
+ big
title="Chosen amount"
text={amountToString(transaction.amountRaw)}
kind="neutral"
/>
- <Part title="Exchange fee" text={amountToString(fee)} kind="negative" />
+ <Part
+ big
+ title="Exchange fee"
+ text={amountToString(fee)}
+ kind="negative"
+ />
<Part
title="Exchange"
text={new URL(transaction.exchangeBaseUrl).hostname}
@@ -166,7 +168,9 @@ export function TransactionView({
);
}
- const showLargePic = () => {};
+ const showLargePic = (): void => {
+ return;
+ };
if (transaction.type === TransactionType.Payment) {
const fee = Amounts.sub(
@@ -177,11 +181,7 @@ export function TransactionView({
return (
<TransactionTemplate>
<h2>Payment </h2>
- <div>
- {transaction.timestamp.t_ms === "never"
- ? "never"
- : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
- </div>
+ <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
<br />
<Part
big
@@ -241,11 +241,7 @@ export function TransactionView({
return (
<TransactionTemplate>
<h2>Deposit </h2>
- <div>
- {transaction.timestamp.t_ms === "never"
- ? "never"
- : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
- </div>
+ <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
<br />
<Part
big
@@ -272,11 +268,7 @@ export function TransactionView({
return (
<TransactionTemplate>
<h2>Refresh</h2>
- <div>
- {transaction.timestamp.t_ms === "never"
- ? "never"
- : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
- </div>
+ <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
<br />
<Part
big
@@ -303,11 +295,7 @@ export function TransactionView({
return (
<TransactionTemplate>
<h2>Tip</h2>
- <div>
- {transaction.timestamp.t_ms === "never"
- ? "never"
- : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
- </div>
+ <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
<br />
<Part
big
@@ -334,11 +322,7 @@ export function TransactionView({
return (
<TransactionTemplate>
<h2>Refund</h2>
- <div>
- {transaction.timestamp.t_ms === "never"
- ? "never"
- : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
- </div>
+ <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
<br />
<Part
big
@@ -391,5 +375,5 @@ export function TransactionView({
);
}
- return <div></div>;
+ return <div />;
}
diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
index 0b8e5c609..a6dd040e4 100644
--- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
@@ -20,16 +20,15 @@
* @author Florian Dold
*/
-import { JSX } from "preact/jsx-runtime";
import { Checkbox } from "../components/Checkbox";
import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
import { Diagnostics } from "../components/Diagnostics";
import { WalletBox } from "../components/styled";
import { useDiagnostics } from "../hooks/useDiagnostics";
import { WalletDiagnostics } from "@gnu-taler/taler-util";
-import { h } from "preact";
+import { h, VNode } from "preact";
-export function WelcomePage() {
+export function WelcomePage(): VNode {
const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
const [diagnostics, timedOut] = useDiagnostics();
return (
@@ -53,7 +52,7 @@ export function View({
togglePermissions,
diagnostics,
timedOut,
-}: ViewProps): JSX.Element {
+}: ViewProps): VNode {
return (
<WalletBox>
<h1>Browser Extension Installed!</h1>