diff options
author | Sebastian <sebasjm@gmail.com> | 2021-11-16 13:59:53 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-11-16 14:01:38 -0300 |
commit | a994009d2f094c4d9c12da68dac3abb28bdef4b3 (patch) | |
tree | e403a58663f81889982635ffb324f9739e6976b3 /packages/taler-wallet-webextension/src/wallet | |
parent | c33ed919719845f518d6491ef37df6ae16820dd0 (diff) | |
download | wallet-core-a994009d2f094c4d9c12da68dac3abb28bdef4b3.tar.xz |
reserveCreated new design
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
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> </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}> + Copy + </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>←</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>←</FontIcon>{" "} - </i18n.Translate> - </ButtonBox> + <Button onClick={onBack}> + <i18n.Translate> < Back </i18n.Translate> + </Button> <div> {transaction?.error ? ( <ButtonPrimary onClick={onRetry}> <i18n.Translate>retry</i18n.Translate> </ButtonPrimary> ) : null} - <ButtonBoxDestructive onClick={onDelete}> - <i18n.Translate>🗑</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> |