diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
14 files changed, 393 insertions, 389 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx index f0ae38e0f..0b0af25ab 100644 --- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx @@ -35,7 +35,6 @@ import { RowBorderGray, SmallLightText, SmallText, - WalletBox, } from "../components/styled"; import { useBackupStatus } from "../hooks/useBackupStatus"; import { Pages } from "../NavigationBar"; @@ -70,7 +69,7 @@ export function BackupView({ onSyncAll, }: ViewProps): VNode { return ( - <WalletBox> + <Fragment> <section> {providers.map((provider, idx) => ( <BackupLayout @@ -106,7 +105,7 @@ export function BackupView({ </div> </footer> )} - </WalletBox> + </Fragment> ); } @@ -155,7 +154,7 @@ function BackupLayout(props: TransactionLayoutProps): VNode { ); } -function ExpirationText({ until }: { until: Timestamp }) { +function ExpirationText({ until }: { until: Timestamp }): VNode { return ( <Fragment> <CenteredText> Expires in </CenteredText> @@ -167,14 +166,14 @@ function ExpirationText({ until }: { until: Timestamp }) { ); } -function colorByTimeToExpire(d: Timestamp) { +function colorByTimeToExpire(d: Timestamp): string { if (d.t_ms === "never") return "rgb(28, 184, 65)"; const months = differenceInMonths(d.t_ms, new Date()); return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)"; } -function daysUntil(d: Timestamp) { - if (d.t_ms === "never") return undefined; +function daysUntil(d: Timestamp): string { + if (d.t_ms === "never") return ""; const duration = intervalToDuration({ start: d.t_ms, end: new Date(), diff --git a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx index 9a2847670..04d79a5ea 100644 --- a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx @@ -14,27 +14,23 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { - amountFractionalBase, - Amounts, - Balance, - BalancesResponse, - i18n, -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { ButtonPrimary, Centered, WalletBox } from "../components/styled/index"; -import { BalancesHook, useBalances } from "../hooks/useBalances"; -import { PageLink, renderAmount } from "../renderHtml"; +import { BalancesResponse, i18n } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { BalanceTable } from "../components/BalanceTable"; +import { ButtonPrimary, ErrorBox } from "../components/styled/index"; +import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook"; +import { PageLink } from "../renderHtml"; +import * as wxApi from "../wxApi"; export function BalancePage({ goToWalletManualWithdraw, }: { goToWalletManualWithdraw: () => void; }): VNode { - const balance = useBalances(); + const state = useAsyncAsHook(wxApi.getBalance); return ( <BalanceView - balance={balance} + balance={state} Linker={PageLink} goToWalletManualWithdraw={goToWalletManualWithdraw} /> @@ -42,7 +38,7 @@ export function BalancePage({ } export interface BalanceViewProps { - balance: BalancesHook; + balance: HookResponse<BalancesResponse>; Linker: typeof PageLink; goToWalletManualWithdraw: () => void; } @@ -53,18 +49,18 @@ export function BalanceView({ goToWalletManualWithdraw, }: BalanceViewProps): VNode { if (!balance) { - return <span />; + return <div>Loading...</div>; } if (balance.hasError) { return ( - <div> - <p>{i18n.str`Error: could not retrieve balance information.`}</p> + <Fragment> + <ErrorBox>{balance.message}</ErrorBox> <p> Click <Linker pageName="welcome">here</Linker> for help and diagnostics. </p> - </div> + </Fragment> ); } if (balance.response.balances.length === 0) { @@ -77,81 +73,17 @@ export function BalanceView({ </p> ); } - return ( - <ShowBalances - wallet={balance.response} - onWithdraw={goToWalletManualWithdraw} - /> - ); -} - -function formatPending(entry: Balance): VNode { - let incoming: VNode | undefined; - let payment: VNode | undefined; - - // const available = Amounts.parseOrThrow(entry.available); - const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming); - // const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing); - - if (!Amounts.isZero(pendingIncoming)) { - incoming = ( - <span> - <i18n.Translate> - <span style={{ color: "darkgreen" }}> - {"+"} - {renderAmount(entry.pendingIncoming)} - </span>{" "} - incoming - </i18n.Translate> - </span> - ); - } - - const l = [incoming, payment].filter((x) => x !== undefined); - if (l.length === 0) { - return <span />; - } - - if (l.length === 1) { - return <span>({l})</span>; - } - return ( - <span> - ({l[0]}, {l[1]}) - </span> - ); -} -function ShowBalances({ - wallet, - onWithdraw, -}: { - wallet: BalancesResponse; - onWithdraw: () => void; -}): VNode { return ( - <WalletBox> + <Fragment> <section> - <Centered> - {wallet.balances.map((entry) => { - const av = Amounts.parseOrThrow(entry.available); - const v = av.value + av.fraction / amountFractionalBase; - return ( - <p key={av.currency}> - <span> - <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "} - <span>{av.currency}</span> - </span> - {formatPending(entry)} - </p> - ); - })} - </Centered> + <BalanceTable balances={balance.response.balances} /> </section> - <footer> - <div /> - <ButtonPrimary onClick={onWithdraw}>Withdraw</ButtonPrimary> + <footer style={{ justifyContent: "space-around" }}> + <ButtonPrimary onClick={goToWalletManualWithdraw}> + Withdraw + </ButtonPrimary> </footer> - </WalletBox> + </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx index 300e9cd57..e4955e376 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx @@ -34,6 +34,10 @@ const exchangeList = { "http://exchange.tal": "EUR", }; +export const WithoutAnyExchangeKnown = createExample(TestedComponent, { + exchangeList: {}, +}); + export const InitialState = createExample(TestedComponent, { exchangeList, }); diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx index 140ac2d40..1bceabd20 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx @@ -19,17 +19,19 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { AmountJson, Amounts } from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; +import { AmountJson, Amounts, i18n } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { ErrorMessage } from "../components/ErrorMessage"; import { SelectList } from "../components/SelectList"; import { + BoldLight, ButtonPrimary, + ButtonSuccess, + Centered, Input, InputWithLabel, LightText, - WalletBox, } from "../components/styled"; export interface Props { @@ -82,11 +84,23 @@ export function CreateManualWithdraw({ } if (!initialExchange) { - return <div>There is no known exchange where to withdraw, add one</div>; + return ( + <Centered style={{ marginTop: 100 }}> + <BoldLight>No exchange configured</BoldLight> + <ButtonSuccess + //FIXME: add exchange feature + onClick={() => { + null; + }} + > + <i18n.Translate>Add exchange</i18n.Translate> + </ButtonSuccess> + </Centered> + ); } return ( - <WalletBox> + <Fragment> <section> <ErrorMessage title={error && "Can't create the reserve"} @@ -145,6 +159,6 @@ export function CreateManualWithdraw({ Start withdrawal </ButtonPrimary> </footer> - </WalletBox> + </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx index 9ae3ac3bd..0f471ac30 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx @@ -57,6 +57,7 @@ const exampleData = { type: TransactionType.Withdrawal, exchangeBaseUrl: "http://exchange.demo.taler.net", withdrawalDetails: { + reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", confirmed: false, exchangePaytoUris: ["payto://x-taler-bank/bank/account"], type: WithdrawalType.ManualTransfer, diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index 6b1a21852..bc8ef734a 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -17,42 +17,37 @@ import { AmountString, Balance, + NotificationType, Transaction, - TransactionsResponse, } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; -import { useEffect, useState } from "preact/hooks"; -import { DateSeparator, WalletBox } from "../components/styled"; +import { DateSeparator } from "../components/styled"; import { Time } from "../components/Time"; import { TransactionItem } from "../components/TransactionItem"; -import { useBalances } from "../hooks/useBalances"; +import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import * as wxApi from "../wxApi"; export function HistoryPage(): VNode { - const [transactions, setTransactions] = useState< - TransactionsResponse | undefined - >(undefined); - const balance = useBalances(); + const balance = useAsyncAsHook(wxApi.getBalance); const balanceWithoutError = balance?.hasError ? [] : balance?.response.balances || []; - useEffect(() => { - const fetchData = async (): Promise<void> => { - const res = await wxApi.getTransactions(); - setTransactions(res); - }; - fetchData(); - }, []); + const transactionQuery = useAsyncAsHook(wxApi.getTransactions, [ + NotificationType.WithdrawGroupFinished, + ]); - if (!transactions) { + if (!transactionQuery) { return <div>Loading ...</div>; } + if (transactionQuery.hasError) { + return <div>There was an error loading the transactions.</div>; + } return ( <HistoryView balances={balanceWithoutError} - list={[...transactions.transactions].reverse()} + list={[...transactionQuery.response.transactions].reverse()} /> ); } @@ -87,7 +82,7 @@ export function HistoryView({ const multiCurrency = balances.length > 1; return ( - <WalletBox noPadding> + <Fragment> {balances.length > 0 && ( <header> {balances.length === 1 && ( @@ -128,6 +123,6 @@ export function HistoryView({ ); })} </section> - </WalletBox> + </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx index 1af4e8d8d..88d5f1722 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx @@ -23,9 +23,9 @@ import { AmountJson, Amounts, } from "@gnu-taler/taler-util"; -import { ReserveCreated } from "./ReserveCreated.js"; +import { ReserveCreated } from "./ReserveCreated"; import { route } from "preact-router"; -import { Pages } from "../NavigationBar.js"; +import { Pages } from "../NavigationBar"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; export function ManualWithdrawPage(): VNode { @@ -39,7 +39,7 @@ export function ManualWithdrawPage(): VNode { >(undefined); const [error, setError] = useState<string | undefined>(undefined); - const knownExchangesHook = useAsyncAsHook(() => wxApi.listExchanges()); + const state = useAsyncAsHook(() => wxApi.listExchanges()); async function doCreate( exchangeBaseUrl: string, @@ -75,10 +75,13 @@ export function ManualWithdrawPage(): VNode { ); } - if (!knownExchangesHook || knownExchangesHook.hasError) { - return <div>No Known exchanges</div>; + if (!state) { + return <div>loading...</div>; } - const exchangeList = knownExchangesHook.response.exchanges.reduce( + if (state.hasError) { + return <div>There was an error getting the known exchanges</div>; + } + const exchangeList = state.response.exchanges.reduce( (p, c) => ({ ...p, [c.exchangeBaseUrl]: c.currency, diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx index 1c7fdc829..41852e38c 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx @@ -20,7 +20,7 @@ import { canonicalizeBaseUrl, i18n, } from "@gnu-taler/taler-util"; -import { VNode, h } from "preact"; +import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { Checkbox } from "../components/Checkbox"; import { ErrorMessage } from "../components/ErrorMessage"; @@ -29,7 +29,6 @@ import { ButtonPrimary, Input, LightText, - WalletBox, SmallLightText, } from "../components/styled/index"; import * as wxApi from "../wxApi"; @@ -64,7 +63,7 @@ export function ProviderAddPage({ onBack }: Props): VNode { async function getProviderInfo( url: string, ): Promise<BackupBackupProviderTerms> { - return fetch(`${url}config`) + return fetch(new URL("config", url).href) .catch((e) => { throw new Error(`Network error`); }) @@ -137,7 +136,7 @@ export function SetUrlView({ } }, [value]); return ( - <WalletBox> + <Fragment> <section> <h1> Add backup provider</h1> <ErrorMessage @@ -182,7 +181,7 @@ export function SetUrlView({ <i18n.Translate>Next</i18n.Translate> </ButtonPrimary> </footer> - </WalletBox> + </Fragment> ); } @@ -201,7 +200,7 @@ export function ConfirmProviderView({ const [accepted, setAccepted] = useState(false); return ( - <WalletBox> + <Fragment> <section> <h1>Review terms of service</h1> <div> @@ -239,6 +238,6 @@ export function ConfirmProviderView({ <i18n.Translate>Add provider</i18n.Translate> </ButtonPrimary> </footer> - </WalletBox> + </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx index 1c14c6e0a..d14429ee5 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx @@ -28,34 +28,62 @@ import { ButtonPrimary, PaymentStatus, SmallLightText, - WalletBox, } from "../components/styled"; import { Time } from "../components/Time"; -import { useProviderStatus } from "../hooks/useProviderStatus"; +import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; +import * as wxApi from "../wxApi"; interface Props { pid: string; onBack: () => void; } -export function ProviderDetailPage({ pid, onBack }: Props): VNode { - const status = useProviderStatus(pid); - if (!status) { +export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode { + async function getProviderInfo(): Promise<ProviderInfo | null> { + //create a first list of backup info by currency + const status = await wxApi.getBackupInfo(); + + const providers = status.providers.filter( + (p) => p.syncProviderBaseUrl === providerURL, + ); + return providers.length ? providers[0] : null; + } + + const state = useAsyncAsHook(getProviderInfo); + + if (!state) { return ( <div> <i18n.Translate>Loading...</i18n.Translate> </div> ); } - if (!status.info) { + if (state.hasError) { + return ( + <div> + <i18n.Translate> + There was an error loading the provider detail for "{providerURL}" + </i18n.Translate> + </div> + ); + } + + if (state.response === null) { onBack(); - return <div />; + return ( + <div> + <i18n.Translate> + There is not known provider with url "{providerURL}". Redirecting + back... + </i18n.Translate> + </div> + ); } return ( <ProviderView - info={status.info} - onSync={status.sync} - onDelete={() => status.remove().then(onBack)} + info={state.response} + onSync={async () => wxApi.syncOneProvider(providerURL)} + onDelete={async () => wxApi.syncOneProvider(providerURL).then(onBack)} onBack={onBack} onExtend={() => { null; @@ -84,7 +112,7 @@ export function ProviderView({ info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged; return ( - <WalletBox> + <Fragment> <Error info={info} /> <header> <h3> @@ -167,35 +195,10 @@ export function ProviderView({ </ButtonDestructive> </div> </footer> - </WalletBox> + </Fragment> ); } -// 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 }): VNode { if (info.lastError) { return <ErrorMessage title={info.lastError.hint} />; @@ -234,23 +237,6 @@ function Error({ info }: { info: ProviderInfo }): VNode { return <Fragment />; } -// 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): VNode { switch (status.type) { // return i18n.str`no enough balance to make the payment` diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx index a72026ab8..075126dc8 100644 --- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx @@ -1,18 +1,8 @@ -import { - AmountJson, - Amounts, - parsePaytoUri, - PaytoUri, -} from "@gnu-taler/taler-util"; +import { AmountJson, Amounts, parsePaytoUri } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; -import { useEffect, useState } from "preact/hooks"; +import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType"; import { QR } from "../components/QR"; -import { - ButtonDestructive, - ButtonPrimary, - WalletBox, - WarningBox, -} from "../components/styled"; +import { ButtonDestructive, WarningBox } from "../components/styled"; export interface Props { reservePub: string; payto: string; @@ -21,92 +11,6 @@ export interface Props { onBack: () => void; } -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, @@ -120,11 +24,12 @@ export function ReserveCreated({ return <div>could not parse payto uri from exchange {payto}</div>; } return ( - <WalletBox> + <Fragment> <section> - <h1>Bank transfer details</h1> + <h1>Exchange is ready for withdrawal!</h1> <p> - Please wire <b>{Amounts.stringify(amount)}</b> to: + To complete the process you need to wire{" "} + <b>{Amounts.stringify(amount)}</b> to the exchange bank account </p> <BankDetailsByPaytoType amount={Amounts.stringify(amount)} @@ -132,14 +37,14 @@ export function ReserveCreated({ payto={paytoURI} subject={reservePub} /> - </section> - <section> <p> <WarningBox> Make sure to use the correct subject, otherwise the money will not arrive in this wallet. </WarningBox> </p> + </section> + <section> <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 @@ -149,8 +54,10 @@ export function ReserveCreated({ </section> <footer> <div /> - <ButtonDestructive onClick={onBack}>Cancel withdraw</ButtonDestructive> + <ButtonDestructive onClick={onBack}> + Cancel withdrawal + </ButtonDestructive> </footer> - </WalletBox> + </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index 8d8f3cdbc..586d7b53e 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -15,16 +15,15 @@ */ import { ExchangeListItem, i18n } from "@gnu-taler/taler-util"; -import { VNode, h, Fragment } from "preact"; +import { Fragment, h, VNode } from "preact"; import { Checkbox } from "../components/Checkbox"; -import { EditableText } from "../components/EditableText"; -import { SelectList } from "../components/SelectList"; -import { ButtonPrimary, ButtonSuccess, WalletBox } from "../components/styled"; +import { ButtonPrimary } from "../components/styled"; import { useDevContext } from "../context/devContext"; +import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useBackupDeviceName } from "../hooks/useBackupDeviceName"; import { useExtendedPermissions } from "../hooks/useExtendedPermissions"; -import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useLang } from "../hooks/useLang"; +// import { strings as messages } from "../i18n/strings"; import * as wxApi from "../wxApi"; export function SettingsPage(): VNode { @@ -32,7 +31,7 @@ export function SettingsPage(): VNode { const { devMode, toggleDevMode } = useDevContext(); const { name, update } = useBackupDeviceName(); const [lang, changeLang] = useLang(); - const exchangesHook = useAsyncAsHook(() => wxApi.listExchanges()); + const exchangesHook = useAsyncAsHook(wxApi.listExchanges); return ( <SettingsView @@ -65,34 +64,32 @@ export interface ViewProps { knownExchanges: Array<ExchangeListItem>; } -import { strings as messages } from "../i18n/strings"; - -type LangsNames = { - [P in keyof typeof messages]: string; -}; +// type LangsNames = { +// [P in keyof typeof messages]: string; +// }; -const names: LangsNames = { - es: "Español [es]", - en: "English [en]", - fr: "Français [fr]", - de: "Deutsch [de]", - sv: "Svenska [sv]", - it: "Italiano [it]", -}; +// const names: LangsNames = { +// es: "Español [es]", +// en: "English [en]", +// fr: "Français [fr]", +// de: "Deutsch [de]", +// sv: "Svenska [sv]", +// it: "Italiano [it]", +// }; export function SettingsView({ knownExchanges, - lang, - changeLang, - deviceName, - setDeviceName, + // lang, + // changeLang, + // deviceName, + // setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode, }: ViewProps): VNode { return ( - <WalletBox> + <Fragment> <section> <h2> <i18n.Translate>Known exchanges</i18n.Translate> @@ -100,17 +97,23 @@ export function SettingsView({ {!knownExchanges || !knownExchanges.length ? ( <div>No exchange yet!</div> ) : ( - <table> - {knownExchanges.map((e) => ( - <tr> - <td>{e.currency}</td> - <td> - <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a> - </td> - </tr> - ))} - </table> + <Fragment> + <table> + {knownExchanges.map((e, idx) => ( + <tr key={idx}> + <td>{e.currency}</td> + <td> + <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a> + </td> + </tr> + ))} + </table> + </Fragment> )} + <div style={{ display: "flex", justifyContent: "space-between" }}> + <div /> + <ButtonPrimary>Manage exchange</ButtonPrimary> + </div> <h2> <i18n.Translate>Permissions</i18n.Translate> @@ -131,6 +134,6 @@ export function SettingsView({ onToggle={toggleDeveloperMode} /> </section> - </WalletBox> + </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx index c9a3f47cb..a25e2ca80 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx @@ -61,6 +61,7 @@ const exampleData = { exchangeBaseUrl: "http://exchange.taler", withdrawalDetails: { confirmed: false, + reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", exchangePaytoUris: ["payto://x-taler-bank/bank/account"], type: WithdrawalType.ManualTransfer, }, @@ -134,10 +135,49 @@ export const WithdrawError = createExample(TestedComponent, { }, }); -export const WithdrawPending = createExample(TestedComponent, { - transaction: { ...exampleData.withdraw, pending: true }, +export const WithdrawPendingManual = createExample(TestedComponent, { + transaction: { + ...exampleData.withdraw, + withdrawalDetails: { + type: WithdrawalType.ManualTransfer, + exchangePaytoUris: ["payto://iban/asdasdasd"], + reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", + }, + pending: true, + }, }); +export const WithdrawPendingTalerBankUnconfirmed = createExample( + TestedComponent, + { + transaction: { + ...exampleData.withdraw, + withdrawalDetails: { + type: WithdrawalType.TalerBankIntegrationApi, + confirmed: false, + reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", + bankConfirmationUrl: "http://bank.demo.taler.net", + }, + pending: true, + }, + }, +); + +export const WithdrawPendingTalerBankConfirmed = createExample( + TestedComponent, + { + transaction: { + ...exampleData.withdraw, + withdrawalDetails: { + type: WithdrawalType.TalerBankIntegrationApi, + confirmed: true, + reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", + }, + pending: true, + }, + }, +); + export const Payment = createExample(TestedComponent, { transaction: exampleData.payment, }); diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index 1472efb40..02c78320a 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -18,62 +18,80 @@ import { AmountLike, Amounts, i18n, + NotificationType, + parsePaytoUri, Transaction, TransactionType, + WithdrawalType, } from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; +import { ComponentChildren, Fragment, h, VNode } from "preact"; import { route } from "preact-router"; -import { useEffect, useState } from "preact/hooks"; +import { useState } from "preact/hooks"; import emptyImg from "../../static/img/empty.png"; +import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType"; import { ErrorMessage } from "../components/ErrorMessage"; import { Part } from "../components/Part"; import { Button, ButtonDestructive, ButtonPrimary, + CenteredDialog, + InfoBox, ListOfProducts, + Overlay, RowBorderGray, SmallLightText, - WalletBox, WarningBox, } from "../components/styled"; import { Time } from "../components/Time"; +import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { Pages } from "../NavigationBar"; import * as wxApi from "../wxApi"; export function TransactionPage({ tid }: { tid: string }): VNode { - const [transaction, setTransaction] = useState<Transaction | undefined>( - undefined, - ); + async function getTransaction(): Promise<Transaction> { + const res = await wxApi.getTransactions(); + const ts = res.transactions.filter((t) => t.transactionId === tid); + if (ts.length > 1) throw Error("more than one transaction with this id"); + if (ts.length === 1) { + return ts[0]; + } + throw Error("no transaction found"); + } - useEffect(() => { - const fetchData = async (): Promise<void> => { - const res = await wxApi.getTransactions(); - const ts = res.transactions.filter((t) => t.transactionId === tid); - if (ts.length === 1) { - setTransaction(ts[0]); - } else { - route(Pages.history); - } - }; - fetchData(); - }, [tid]); + const state = useAsyncAsHook(getTransaction, [ + NotificationType.WithdrawGroupFinished, + ]); - if (!transaction) { + if (!state) { return ( <div> <i18n.Translate>Loading ...</i18n.Translate> </div> ); } + + if (state.hasError) { + route(Pages.history); + return ( + <div> + <i18n.Translate> + There was an error. Redirecting into the history page + </i18n.Translate> + </div> + ); + } + + function goToHistory(): void { + route(Pages.history); + } + return ( <TransactionView - transaction={transaction} - onDelete={() => wxApi.deleteTransaction(tid).then(() => history.go(-1))} - onRetry={() => wxApi.retryTransaction(tid).then(() => history.go(-1))} - onBack={() => { - route(Pages.history); - }} + transaction={state.response} + onDelete={() => wxApi.deleteTransaction(tid).then(goToHistory)} + onRetry={() => wxApi.retryTransaction(tid).then(goToHistory)} + onBack={goToHistory} /> ); } @@ -91,16 +109,28 @@ export function TransactionView({ onRetry, onBack, }: WalletTransactionProps): VNode { - function TransactionTemplate({ children }: { children: VNode[] }): VNode { + const [confirmBeforeForget, setConfirmBeforeForget] = useState(false); + function doCheckBeforeForget(): void { + if ( + transaction.pending && + transaction.type === TransactionType.Withdrawal + ) { + setConfirmBeforeForget(true); + } else { + onDelete(); + } + } + function TransactionTemplate({ + children, + }: { + children: ComponentChildren; + }): VNode { return ( - <WalletBox> + <Fragment> <section style={{ padding: 8, textAlign: "center" }}> <ErrorMessage title={transaction?.error?.hint} /> {transaction.pending && ( - <WarningBox> - This transaction is not completed - <a href="">more info...</a> - </WarningBox> + <WarningBox>This transaction is not completed</WarningBox> )} </section> <section> @@ -116,12 +146,12 @@ export function TransactionView({ <i18n.Translate>retry</i18n.Translate> </ButtonPrimary> ) : null} - <ButtonDestructive onClick={onDelete}> + <ButtonDestructive onClick={doCheckBeforeForget}> <i18n.Translate> Forget </i18n.Translate> </ButtonDestructive> </div> </footer> - </WalletBox> + </Fragment> ); } @@ -138,27 +168,119 @@ export function TransactionView({ ).amount; return ( <TransactionTemplate> + {confirmBeforeForget ? ( + <Overlay> + <CenteredDialog> + <header>Caution!</header> + <section> + If you have already wired money to the exchange you will loose + the chance to get the coins form it. + </section> + <footer> + <Button onClick={() => setConfirmBeforeForget(false)}> + <i18n.Translate> Cancel </i18n.Translate> + </Button> + + <ButtonDestructive onClick={onDelete}> + <i18n.Translate> Confirm </i18n.Translate> + </ButtonDestructive> + </footer> + </CenteredDialog> + </Overlay> + ) : undefined} <h2>Withdrawal</h2> <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 - big - title="Exchange fee" - text={amountToString(fee)} - kind="negative" - /> + {transaction.pending ? ( + transaction.withdrawalDetails.type === + WithdrawalType.ManualTransfer ? ( + <Fragment> + <BankDetailsByPaytoType + amount={amountToString(transaction.amountRaw)} + exchangeBaseUrl={transaction.exchangeBaseUrl} + payto={parsePaytoUri( + transaction.withdrawalDetails.exchangePaytoUris[0], + )} + subject={transaction.withdrawalDetails.reservePub} + /> + <p> + <WarningBox> + Make sure to use the correct subject, otherwise the money will + not arrive in this wallet. + </WarningBox> + </p> + <Part + big + title="Total withdrawn" + text={amountToString(transaction.amountEffective)} + kind="positive" + /> + <Part + big + title="Exchange fee" + text={amountToString(fee)} + kind="negative" + /> + </Fragment> + ) : ( + <Fragment> + {!transaction.withdrawalDetails.confirmed && + transaction.withdrawalDetails.bankConfirmationUrl ? ( + <InfoBox> + The bank is waiting for confirmation. Go to the + <a + href={transaction.withdrawalDetails.bankConfirmationUrl} + target="_blank" + rel="noreferrer" + > + bank site + </a> + </InfoBox> + ) : undefined} + {transaction.withdrawalDetails.confirmed && ( + <InfoBox>Waiting for the coins to arrive</InfoBox> + )} + <Part + big + title="Total withdrawn" + text={amountToString(transaction.amountEffective)} + kind="positive" + /> + <Part + big + title="Chosen amount" + text={amountToString(transaction.amountRaw)} + kind="neutral" + /> + <Part + big + title="Exchange fee" + text={amountToString(fee)} + kind="negative" + /> + </Fragment> + ) + ) : ( + <Fragment> + <Part + big + title="Total withdrawn" + text={amountToString(transaction.amountEffective)} + kind="positive" + /> + <Part + big + title="Chosen amount" + text={amountToString(transaction.amountRaw)} + kind="neutral" + /> + <Part + big + title="Exchange fee" + text={amountToString(fee)} + kind="negative" + /> + </Fragment> + )} <Part title="Exchange" text={new URL(transaction.exchangeBaseUrl).hostname} diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx index a6dd040e4..b180fdd05 100644 --- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx @@ -20,13 +20,12 @@ * @author Florian Dold */ +import { WalletDiagnostics } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; 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, VNode } from "preact"; +import { useExtendedPermissions } from "../hooks/useExtendedPermissions"; export function WelcomePage(): VNode { const [permissionsEnabled, togglePermissions] = useExtendedPermissions(); @@ -54,7 +53,7 @@ export function View({ timedOut, }: ViewProps): VNode { return ( - <WalletBox> + <Fragment> <h1>Browser Extension Installed!</h1> <div> <p>Thank you for installing the wallet.</p> @@ -75,6 +74,6 @@ export function View({ Learn how to top up your wallet balance » </a> </div> - </WalletBox> + </Fragment> ); } |