diff options
28 files changed, 851 insertions, 400 deletions
diff --git a/packages/taler-wallet-webextension/.storybook/preview.js b/packages/taler-wallet-webextension/.storybook/preview.js index 169b726f9..02a4e43d4 100644 --- a/packages/taler-wallet-webextension/.storybook/preview.js +++ b/packages/taler-wallet-webextension/.storybook/preview.js @@ -56,7 +56,7 @@ export const decorators = [ // add a fake header so it looks similar return <Fragment> <NavBar path={path} devMode={path === '/dev'} /> - <div style={{ padding: 8, width: 'calc(400px - 16px)', height: 'calc(320px - 34px - 16px)' }}> + <div style={{ width: 400, height: 290 }}> <Story /> </div> </Fragment> diff --git a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts index d8dff72fb..b5c26a7bb 100644 --- a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts +++ b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts @@ -68,6 +68,6 @@ worker.onmessage = (msg: MessageEvent) => { } handleRequest(operation, id, args).catch((e) => { - console.error("error in browsere worker", e); + console.error("error in browser worker", e); }); }; diff --git a/packages/taler-wallet-webextension/src/components/Diagnostics.tsx b/packages/taler-wallet-webextension/src/components/Diagnostics.tsx index 146b0dd3e..b36525dbf 100644 --- a/packages/taler-wallet-webextension/src/components/Diagnostics.tsx +++ b/packages/taler-wallet-webextension/src/components/Diagnostics.tsx @@ -4,30 +4,12 @@ import { PageLink } from "../renderHtml"; import { WalletDiagnostics } from "@gnu-taler/taler-util"; import { JSX } from "preact/jsx-runtime"; +interface Props { + timedOut: boolean; + diagnostics: WalletDiagnostics | undefined +} -export function Diagnostics(): JSX.Element | null { - const [timedOut, setTimedOut] = useState(false); - const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | undefined>( - undefined - ); - - useEffect(() => { - let gotDiagnostics = false; - setTimeout(() => { - if (!gotDiagnostics) { - console.error("timed out"); - setTimedOut(true); - } - }, 1000); - const doFetch = async (): Promise<void> => { - const d = await getDiagnostics(); - console.log("got diagnostics", d); - gotDiagnostics = true; - setDiagnostics(d); - }; - console.log("fetching diagnostics"); - doFetch(); - }, []); +export function Diagnostics({timedOut, diagnostics}: Props): JSX.Element | null { if (timedOut) { return <p>Diagnostics timed out. Could not talk to the wallet backend.</p>; diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index cf7f3e06a..7f709db46 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -11,13 +11,32 @@ export const PaymentStatus = styled.div<{ color: string }>` background-color: ${p => p.color}; ` -export const PopupBox = styled.div` - height: calc(320px - 34px - 16px); +export const WalletPage = styled.section` + border: solid 5px black; + border-radius: 10px; + margin-left: auto; + margin-right: auto; + padding-top: 2em; + max-width: 50%; + padding: 2em; + + margin: auto; + height: 100%; + + & h1:first-child { + margin-top: 0; + } +` + +export const PopupBox = styled.div<{ noPadding?: boolean }>` + height: 290px; display: flex; flex-direction: column; justify-content: space-between; & > section { + padding-left: ${({ noPadding }) => noPadding ? '0px' : '8px'}; + padding-right: ${({ noPadding }) => noPadding ? '0px' : '8px'}; // this margin will send the section up when used with a header margin-bottom: auto; overflow: auto; @@ -35,6 +54,7 @@ export const PopupBox = styled.div` flex-direction: row; justify-content: space-between; display: flex; + padding: 8px; margin-bottom: 5px; & > div { @@ -44,15 +64,23 @@ export const PopupBox = styled.div` & > h3 { margin: 0px; } + + & > .title { + /* margin: 1em; */ + font-size: large; + color: #3c4e92; + } } & > footer { - padding-top: 5px; + padding-top: 8px; + padding-bottom: 8px; flex-direction: row; justify-content: space-between; display: flex; & button { - margin-left: 5px; + margin-right: 8px; + margin-left: 8px; } } @@ -145,6 +173,13 @@ export const Row = styled.div` padding: 0.5em; ` +export const Row2 = styled.div` + display: flex; + /* margin: 0.5em 0; */ + justify-content: space-between; + padding: 0.5em; +` + export const Column = styled.div` display: flex; flex-direction: column; @@ -154,10 +189,15 @@ export const Column = styled.div` export const RowBorderGray = styled(Row)` border: 1px solid gray; - border-radius: 0.5em; + /* border-radius: 0.5em; */ ` -export const HistoryRow = styled(RowBorderGray)` +export const RowLightBorderGray = styled(Row2)` + border: 1px solid lightgray; + /* border-radius: 0.5em; */ +` + +export const HistoryRow = styled(RowLightBorderGray)` & > ${Column}:last-of-type { margin-left: auto; align-self: center; @@ -244,24 +284,24 @@ export const ErrorBox = styled.div` } } ` -export const PopupNavigation = styled.div` - background-color: #033; +export const PopupNavigation = styled.div<{devMode?:boolean}>` + background-color:#0042b2; + height: 35px; & > a { color: #f8faf7; - padding-top: 0.7em; display: inline-block; - width: calc(400px / 5); - padding-bottom: 0.7em; + width: calc(400px / ${({ devMode }) => !devMode ? 4 : 5}); text-align: center; text-decoration: none; + vertical-align: middle; + line-height: 35px; } & > a.active { background-color: #f8faf7; - color: #000; + color: #0042b2; font-weight: bold; - } `; diff --git a/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts new file mode 100644 index 000000000..e2c62f998 --- /dev/null +++ b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts @@ -0,0 +1,29 @@ +import { WalletDiagnostics } from "@gnu-taler/taler-util"; +import { useEffect, useState } from "preact/hooks"; +import * as wxApi from "../wxApi"; + +export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] { + const [timedOut, setTimedOut] = useState(false); + const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | undefined>( + undefined + ); + + useEffect(() => { + let gotDiagnostics = false; + setTimeout(() => { + if (!gotDiagnostics) { + console.error("timed out"); + setTimedOut(true); + } + }, 1000); + const doFetch = async (): Promise<void> => { + const d = await wxApi.getDiagnostics(); + console.log("got diagnostics", d); + gotDiagnostics = true; + setDiagnostics(d); + }; + console.log("fetching diagnostics"); + doFetch(); + }, []); + return [diagnostics, timedOut] +}
\ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts index b884ca943..1c8504a8e 100644 --- a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts +++ b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts @@ -57,12 +57,8 @@ function makeExtensionUrlWithParams( ): string { const innerUrl = new URL(chrome.extension.getURL("/" + url)); if (params) { - for (const key in params) { - const p = params[key]; - if (p) { - innerUrl.searchParams.set(key, p); - } - } + const hParams = Object.keys(params).map(k => `${k}=${params[k]}`).join('&') + innerUrl.hash = innerUrl.hash + '?' + hParams } return innerUrl.href; } diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx index cff17af1a..5a2b9f747 100644 --- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx @@ -43,7 +43,7 @@ export function BalanceView({ balance, Linker }: BalanceViewProps) { <div> <p>{i18n.str`Error: could not retrieve balance information.`}</p> <p> - Click <Linker pageName="welcome.html">here</Linker> for help and + Click <Linker pageName="welcome">here</Linker> for help and diagnostics. </p> </div> diff --git a/packages/taler-wallet-webextension/src/popup/Debug.tsx b/packages/taler-wallet-webextension/src/popup/Debug.tsx index 1f6014e8e..33b82b05b 100644 --- a/packages/taler-wallet-webextension/src/popup/Debug.tsx +++ b/packages/taler-wallet-webextension/src/popup/Debug.tsx @@ -16,10 +16,12 @@ import { JSX } from "preact"; import { Diagnostics } from "../components/Diagnostics"; +import { useDiagnostics } from "../hooks/useDiagnostics.js"; import * as wxApi from "../wxApi"; export function DeveloperPage(props: any): JSX.Element { + const [status, timedOut] = useDiagnostics(); return ( <div> <p>Debug tools:</p> @@ -27,7 +29,7 @@ export function DeveloperPage(props: any): JSX.Element { <br /> <button onClick={confirmReset}>reset</button> <button onClick={reload}>reload chrome extension</button> - <Diagnostics /> + <Diagnostics diagnostics={status} timedOut={timedOut} /> </div> ); } diff --git a/packages/taler-wallet-webextension/src/popup/History.stories.tsx b/packages/taler-wallet-webextension/src/popup/History.stories.tsx index 8eef7dc31..5337a6c1c 100644 --- a/packages/taler-wallet-webextension/src/popup/History.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/History.stories.tsx @@ -30,7 +30,7 @@ import { FunctionalComponent } from 'preact'; import { HistoryView as TestedComponent } from './History'; export default { - title: 'popup/transaction/list', + title: 'popup/history/list', component: TestedComponent, }; @@ -112,12 +112,26 @@ function createExample<Props>(Component: FunctionalComponent<Props>, props: Part } export const Empty = createExample(TestedComponent, { - list: [] + list: [], + balances: [{ + available: 'TESTKUDOS:10', + pendingIncoming: 'TESTKUDOS:0', + pendingOutgoing: 'TESTKUDOS:0', + hasPendingTransactions: false, + requiresUserInput: false, + }] }); export const One = createExample(TestedComponent, { - list: [exampleData.withdraw] + list: [exampleData.withdraw], + balances: [{ + available: 'USD:10', + pendingIncoming: 'USD:0', + pendingOutgoing: 'USD:0', + hasPendingTransactions: false, + requiresUserInput: false, + }] }); export const Several = createExample(TestedComponent, { @@ -130,7 +144,40 @@ export const Several = createExample(TestedComponent, { exampleData.refund, exampleData.tip, exampleData.deposit, - ] + ], + balances: [{ + available: 'TESTKUDOS:10', + pendingIncoming: 'TESTKUDOS:0', + pendingOutgoing: 'TESTKUDOS:0', + hasPendingTransactions: false, + requiresUserInput: false, + }] +}); + +export const SeveralWithTwoCurrencies = createExample(TestedComponent, { + list: [ + exampleData.withdraw, + exampleData.payment, + exampleData.withdraw, + exampleData.payment, + exampleData.refresh, + exampleData.refund, + exampleData.tip, + exampleData.deposit, + ], + balances: [{ + available: 'TESTKUDOS:10', + pendingIncoming: 'TESTKUDOS:0', + pendingOutgoing: 'TESTKUDOS:0', + hasPendingTransactions: false, + requiresUserInput: false, + },{ + available: 'USD:10', + pendingIncoming: 'USD:0', + pendingOutgoing: 'USD:0', + hasPendingTransactions: false, + requiresUserInput: false, + }] }); // export const WithdrawPending = createExample(TestedComponent, { diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx b/packages/taler-wallet-webextension/src/popup/History.tsx index 57fc10c26..b6b65314e 100644 --- a/packages/taler-wallet-webextension/src/popup/History.tsx +++ b/packages/taler-wallet-webextension/src/popup/History.tsx @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountString, Timestamp, Transaction, TransactionsResponse, TransactionType } from "@gnu-taler/taler-util"; +import { AmountJson, Amounts, AmountString, Balance, Timestamp, Transaction, TransactionsResponse, TransactionType } from "@gnu-taler/taler-util"; import { JSX } from "preact"; import { useEffect, useState } from "preact/hooks"; import * as wxApi from "../wxApi"; @@ -25,6 +25,8 @@ export function HistoryPage(props: any): JSX.Element { const [transactions, setTransactions] = useState< TransactionsResponse | undefined >(undefined); + const balance = useBalances() + const balanceWithoutError = balance?.error ? [] : (balance?.response.balances || []) useEffect(() => { const fetchData = async (): Promise<void> => { @@ -38,16 +40,36 @@ export function HistoryPage(props: any): JSX.Element { return <div>Loading ...</div>; } - return <HistoryView list={[...transactions.transactions].reverse()} />; + return <HistoryView balances={balanceWithoutError} list={[...transactions.transactions].reverse()} />; } -export function HistoryView({ list }: { list: Transaction[] }) { - return <PopupBox> +function amountToString(c: AmountString) { + const idx = c.indexOf(':') + return `${c.substring(idx+1)} ${c.substring(0,idx)}` +} + + + +export function HistoryView({ list, balances }: { list: Transaction[], balances: Balance[] }) { + return <PopupBox noPadding> + {balances.length > 0 && <header> + {balances.length === 1 && <div class="title"> + Balance: <span>{amountToString(balances[0].available)}</span> + </div>} + {balances.length > 1 && <div class="title"> + Balance: <ul style={{ margin: 0 }}> + {balances.map(b => <li>{b.available}</li>)} + </ul> + </div>} + </header>} <section> - {list.map((tx, i) => ( + {list.slice(0, 3).map((tx, i) => ( <TransactionItem key={i} tx={tx} /> ))} </section> + <footer style={{ justifyContent: 'space-around' }}> + <a style={{ color: 'darkgreen', textDecoration:'none' }} href={Pages.transaction.replace(':tid', 'asd')}>VIEW MORE TRANSACTIONS</a> + </footer> </PopupBox> } @@ -57,6 +79,8 @@ import imageRefund from '../../static/img/ri-refund-2-line.svg'; import imageHandHeart from '../../static/img/ri-hand-heart-line.svg'; import imageRefresh from '../../static/img/ri-refresh-line.svg'; import { Column, ExtraLargeText, HistoryRow, PopupBox, Row, RowBorderGray, SmallTextLight } from "../components/styled"; +import { useBalances } from "../hooks/useBalances"; +import { formatDistance } from "date-fns"; function TransactionItem(props: { tx: Transaction }): JSX.Element { const tx = props.tx; @@ -144,23 +168,21 @@ function TransactionItem(props: { tx: Transaction }): JSX.Element { function TransactionLayout(props: TransactionLayoutProps): JSX.Element { const date = new Date(props.timestamp.t_ms); - const dateStr = date.toLocaleString([], { - dateStyle: "medium", - timeStyle: "short", - } as any); + const now = new Date(); + const dateStr = formatDistance(date, now, { addSuffix: true }) return ( <HistoryRow> <img src={props.iconPath} /> <Column> - <SmallTextLight>{dateStr}</SmallTextLight> <ExtraLargeText> <a href={Pages.transaction.replace(':tid', props.id)}><span>{props.title}</span></a> {props.pending ? ( <span style={{ color: "darkblue" }}> (Pending)</span> ) : null} </ExtraLargeText> + <SmallTextLight>{dateStr}</SmallTextLight> - <div>{props.subtitle}</div> + {/* <div>{props.subtitle}</div> */} </Column> <TransactionAmount pending={props.pending} @@ -202,7 +224,13 @@ function TransactionAmount(props: TransactionAmountProps): JSX.Element { sign = ""; } return ( - <Column style={{ color: props.pending ? "gray" : undefined }}> + <Column style={{ + color: + props.pending ? "gray" : + (sign === '+' ? 'darkgreen' : + (sign === '-' ? 'darkred' : + undefined)) + }}> <ExtraLargeText> {sign} {amount} diff --git a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx index c92137ee3..707e6c33a 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx @@ -58,13 +58,12 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged return ( <PopupBox> - <header> + {info.backupProblem || info.lastError ? <header> <Error info={info} /> - - </header> + </header> : undefined } <header> <h3>{info.name} <SmallTextLight>{info.syncProviderBaseUrl}</SmallTextLight></h3> - <PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid': 'Unpaid' }</PaymentStatus> + <PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid' : 'Unpaid'}</PaymentStatus> </header> <section> <p><b>Last backup:</b> {lb == null || lb.t_ms == "never" ? "never" : format(lb.t_ms, 'dd MMM yyyy')} </p> diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx b/packages/taler-wallet-webextension/src/popup/Settings.tsx index 18afcd100..40ab51561 100644 --- a/packages/taler-wallet-webextension/src/popup/Settings.tsx +++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx @@ -68,7 +68,7 @@ const names: LangsNames = { export function SettingsView({ lang, changeLang, deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode { return ( <div> - <section style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' }}> + <section style={{ height: 300, overflow: 'auto' }}> <h2><i18n.Translate>Wallet</i18n.Translate></h2> <SelectList value={lang} diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx index 3c0bed6c7..4e63b9242 100644 --- a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx @@ -30,7 +30,7 @@ import { FunctionalComponent } from 'preact'; import { TransactionView as TestedComponent } from './Transaction'; export default { - title: 'popup/transaction/details', + title: 'popup/history/details', component: TestedComponent, argTypes: { onRetry: { action: 'onRetry' }, diff --git a/packages/taler-wallet-webextension/src/popup/popup.tsx b/packages/taler-wallet-webextension/src/popup/popup.tsx index a6be4d192..4aee48fb7 100644 --- a/packages/taler-wallet-webextension/src/popup/popup.tsx +++ b/packages/taler-wallet-webextension/src/popup/popup.tsx @@ -60,7 +60,7 @@ function Tab(props: TabProps): JSX.Element { } export function NavBar({devMode, path}:{path:string, devMode:boolean}) { - return <PopupNavigation> + return <PopupNavigation devMode={devMode}> <Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab> <Tab target="/history" current={path}>{i18n.str`History`}</Tab> <Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab> diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx index 39c25d508..faa5149ac 100644 --- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx +++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx @@ -93,7 +93,7 @@ function Application() { <div> <DevContextProvider> <WalletNavBar /> - <div style={{ padding: 8, width: 'calc(400px - 16px)', height: 'calc(320px - 34px - 16px)' }}> + <div style={{ width: 400, height: 290 }}> <Router history={createHashHistory()}> <Route path={Pages.balance} component={BalancePage} /> <Route path={Pages.settings} component={SettingsPage} /> diff --git a/packages/taler-wallet-webextension/src/wallet/Pay.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Pay.stories.tsx new file mode 100644 index 000000000..0297d6264 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/Pay.stories.tsx @@ -0,0 +1,103 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util'; +import { FunctionalComponent, h } from 'preact'; +import { PaymentRequestView as TestedComponent } from './Pay'; + + +export default { + title: 'wallet/pay', + component: TestedComponent, + argTypes: { + }, +}; + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { + const r = (args: any) => <Component {...args} /> + r.args = props + return r +} + +export const InsufficientBalance = createExample(TestedComponent, { + payStatus: { + status: PreparePayResultType.InsufficientBalance, + proposalId: "proposal1234", + contractTerms: { + merchant: { + name: 'someone' + }, + amount: 'USD:10', + } as Partial<ContractTerms> as any, + amountRaw: 'USD:10', + } +}); + +export const PaymentPossible = createExample(TestedComponent, { + payStatus: { + status: PreparePayResultType.PaymentPossible, + amountEffective: 'USD:10', + amountRaw: 'USD:10', + contractTerms: { + merchant: { + name: 'someone' + }, + amount: 'USD:10', + } as Partial<ContractTerms> as any, + contractTermsHash: '123456', + proposalId: 'proposal1234' + } +}); + +export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, { + payStatus: { + status: PreparePayResultType.AlreadyConfirmed, + amountEffective: 'USD:10', + amountRaw: 'USD:10', + contractTerms: { + merchant: { + name: 'someone' + }, + fulfillment_message: 'congratulations! you are looking at the fulfillment message! ', + amount: 'USD:10', + } as Partial<ContractTerms> as any, + contractTermsHash: '123456', + proposalId: 'proposal1234', + paid: false, + } +}); + +export const AlreadyConfirmedWithoutFullfilment = createExample(TestedComponent, { + payStatus: { + status: PreparePayResultType.AlreadyConfirmed, + amountEffective: 'USD:10', + amountRaw: 'USD:10', + contractTerms: { + merchant: { + name: 'someone' + }, + amount: 'USD:10', + } as Partial<ContractTerms> as any, + contractTermsHash: '123456', + proposalId: 'proposal1234', + paid: false, + } +}); diff --git a/packages/taler-wallet-webextension/src/wallet/Pay.tsx b/packages/taler-wallet-webextension/src/wallet/Pay.tsx index bd06656c7..a5849bb28 100644 --- a/packages/taler-wallet-webextension/src/wallet/Pay.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Pay.tsx @@ -29,7 +29,7 @@ import * as wxApi from "../wxApi"; import { useState, useEffect } from "preact/hooks"; -import { getJsonI18n, i18n } from "@gnu-taler/taler-util"; +import { ConfirmPayResultDone, getJsonI18n, i18n } from "@gnu-taler/taler-util"; import { PreparePayResult, ConfirmPayResult, @@ -45,13 +45,54 @@ interface Props { talerPayUri?: string } +export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) { + const fulfillmentUrl = payStatus.contractTerms.fulfillment_url; + let message; + if (fulfillmentUrl) { + message = ( + <span> + You have already paid for this article. Click{" "} + <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again. + </span> + ); + } else { + message = <span> + You have already paid for this article:{" "} + <em> + {payStatus.contractTerms.fulfillment_message ?? "no message given"} + </em> + </span>; + } + return <section class="main"> + <h1>GNU Taler Wallet</h1> + <article class="fade"> + {message} + </article> + </section> +} + +const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultDone> => { + if (payStatus.status !== "payment-possible") { + throw Error(`invalid state: ${payStatus.status}`); + } + const proposalId = payStatus.proposalId; + const res = await wxApi.confirmPay(proposalId, undefined); + if (res.type !== ConfirmPayResultType.Done) { + throw Error("payment pending"); + } + const fu = res.contractTerms.fulfillment_url; + if (fu) { + document.location.href = fu; + } + return res; +}; + + + export function PayPage({ talerPayUri }: Props): JSX.Element { const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(undefined); const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined); const [payErrMsg, setPayErrMsg] = useState<string | undefined>(""); - const [numTries, setNumTries] = useState(0); - const [loading, setLoading] = useState(false); - let totalFees: AmountJson | undefined = undefined; useEffect(() => { if (!talerPayUri) return; @@ -60,53 +101,67 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { setPayStatus(p); }; doFetch(); - }, [numTries, talerPayUri]); + }, [talerPayUri]); if (!talerPayUri) { return <span>missing pay uri</span> } - + if (!payStatus) { return <span>Loading payment information ...</span>; } - let insufficientBalance = false; - if (payStatus.status == PreparePayResultType.InsufficientBalance) { - insufficientBalance = true; - } - - if (payStatus.status === PreparePayResultType.PaymentPossible) { - const amountRaw = Amounts.parseOrThrow(payStatus.amountRaw); - const amountEffective: AmountJson = Amounts.parseOrThrow( - payStatus.amountEffective, - ); - totalFees = Amounts.sub(amountEffective, amountRaw).amount; - } - - if ( - payStatus.status === PreparePayResultType.AlreadyConfirmed && - numTries === 0 - ) { - const fulfillmentUrl = payStatus.contractTerms.fulfillment_url; - if (fulfillmentUrl) { + if (payResult && payResult.type === ConfirmPayResultType.Done) { + if (payResult.contractTerms.fulfillment_message) { + const obj = { + fulfillment_message: payResult.contractTerms.fulfillment_message, + fulfillment_message_i18n: + payResult.contractTerms.fulfillment_message_i18n, + }; + const msg = getJsonI18n(obj, "fulfillment_message"); return ( - <span> - You have already paid for this article. Click{" "} - <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again. - </span> + <div> + <p>Payment succeeded.</p> + <p>{msg}</p> + </div> ); } else { - <span> - You have already paid for this article:{" "} - <em> - {payStatus.contractTerms.fulfillment_message ?? "no message given"} - </em> - </span>; + return <span>Redirecting ...</span>; } } + const onClick = async () => { + try { + const res = await doPayment(payStatus) + setPayResult(res); + } catch (e) { + console.error(e); + setPayErrMsg(e.message); + } + + } + + return <PaymentRequestView payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} />; +} + +export interface PaymentRequestViewProps { + payStatus: PreparePayResult; + onClick: () => void; + payErrMsg?: string; + +} +export function PaymentRequestView({ payStatus, onClick, payErrMsg }: PaymentRequestViewProps) { + let totalFees: AmountJson | undefined = undefined; + let insufficientBalance = false; + const [loading, setLoading] = useState(false); const contractTerms: ContractTerms = payStatus.contractTerms; + if ( + payStatus.status === PreparePayResultType.AlreadyConfirmed + ) { + return <AlreadyPaid payStatus={payStatus} /> + } + if (!contractTerms) { return ( <span> @@ -115,6 +170,18 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { ); } + if (payStatus.status == PreparePayResultType.InsufficientBalance) { + insufficientBalance = true; + } + + if (payStatus.status === PreparePayResultType.PaymentPossible) { + const amountRaw = Amounts.parseOrThrow(payStatus.amountRaw); + const amountEffective: AmountJson = Amounts.parseOrThrow( + payStatus.amountEffective, + ); + totalFees = Amounts.sub(amountEffective, amountRaw).amount; + } + let merchantName: VNode; if (contractTerms.merchant && contractTerms.merchant.name) { merchantName = <strong>{contractTerms.merchant.name}</strong>; @@ -126,99 +193,61 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { <strong>{renderAmount(Amounts.parseOrThrow(contractTerms.amount))}</strong> ); - const doPayment = async (): Promise<void> => { - if (payStatus.status !== "payment-possible") { - throw Error(`invalid state: ${payStatus.status}`); - } - const proposalId = payStatus.proposalId; - setNumTries(numTries + 1); - try { - setLoading(true); - const res = await wxApi.confirmPay(proposalId, undefined); - if (res.type !== ConfirmPayResultType.Done) { - throw Error("payment pending"); - } - const fu = res.contractTerms.fulfillment_url; - if (fu) { - document.location.href = fu; - } - setPayResult(res); - } catch (e) { - console.error(e); - setPayErrMsg(e.message); - } - }; - - if (payResult && payResult.type === ConfirmPayResultType.Done) { - if (payResult.contractTerms.fulfillment_message) { - const obj = { - fulfillment_message: payResult.contractTerms.fulfillment_message, - fulfillment_message_i18n: - payResult.contractTerms.fulfillment_message_i18n, - }; - const msg = getJsonI18n(obj, "fulfillment_message"); - return ( - <div> - <p>Payment succeeded.</p> - <p>{msg}</p> - </div> - ); - } else { - return <span>Redirecting ...</span>; - } - } - - return ( - <div> - <p> - <i18n.Translate> - The merchant <span>{merchantName}</span> offers you to purchase: - </i18n.Translate> - <div style={{ textAlign: "center" }}> - <strong>{contractTerms.summary}</strong> - </div> - {totalFees ? ( + return <section class="main"> + <h1>GNU Taler Wallet</h1> + <article class="fade"> + <div> + <p> <i18n.Translate> - The total price is <span>{amount} </span> - (plus <span>{renderAmount(totalFees)}</span> fees). - </i18n.Translate> + The merchant <span>{merchantName}</span> offers you to purchase: + </i18n.Translate> + <div style={{ textAlign: "center" }}> + <strong>{contractTerms.summary}</strong> + </div> + {totalFees ? ( + <i18n.Translate> + The total price is <span>{amount} </span> + (plus <span>{renderAmount(totalFees)}</span> fees). + </i18n.Translate> + ) : ( + <i18n.Translate> + The total price is <span>{amount}</span>. + </i18n.Translate> + )} + </p> + + {insufficientBalance ? ( + <div> + <p style={{ color: "red", fontWeight: "bold" }}> + Unable to pay: Your balance is insufficient. + </p> + </div> + ) : null} + + {payErrMsg ? ( + <div> + <p>Payment failed: {payErrMsg}</p> + <button + class="pure-button button-success" + onClick={onClick} + > + {i18n.str`Retry`} + </button> + </div> ) : ( - <i18n.Translate> - The total price is <span>{amount}</span>. - </i18n.Translate> - )} - </p> - - {insufficientBalance ? ( - <div> - <p style={{ color: "red", fontWeight: "bold" }}> - Unable to pay: Your balance is insufficient. - </p> - </div> - ) : null} - - {payErrMsg ? ( - <div> - <p>Payment failed: {payErrMsg}</p> - <button - class="pure-button button-success" - onClick={() => doPayment()} - > - {i18n.str`Retry`} - </button> - </div> - ) : ( - <div> - <ProgressButton - isLoading={loading} - disabled={insufficientBalance} - onClick={() => doPayment()} - > - {i18n.str`Confirm payment`} - </ProgressButton> - </div> - )} - </div> - ); -} - + <div> + <ProgressButton + isLoading={loading} + disabled={insufficientBalance} + onClick={onClick} + > + {i18n.str`Confirm payment`} + </ProgressButton> + </div> + )} + </div> + </article> + </section> + + +}
\ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/wallet/Refund.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Refund.stories.tsx new file mode 100644 index 000000000..044141f0c --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/Refund.stories.tsx @@ -0,0 +1,83 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ContractTerms, OrderShortInfo, PreparePayResultType } from '@gnu-taler/taler-util'; +import { FunctionalComponent, h } from 'preact'; +import { View as TestedComponent } from './Refund'; + + +export default { + title: 'wallet/refund', + component: TestedComponent, + argTypes: { + }, +}; + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { + const r = (args: any) => <Component {...args} /> + r.args = props + return r +} + +export const Complete = createExample(TestedComponent, { + applyResult: { + amountEffectivePaid: 'USD:10', + amountRefundGone: 'USD:0', + amountRefundGranted: 'USD:2', + contractTermsHash: 'QWEASDZXC', + info: { + summary: 'tasty cold beer', + contractTermsHash: 'QWEASDZXC', + } as Partial<OrderShortInfo> as any, + pendingAtExchange: false, + proposalId: "proposal123", + } +}); + +export const Partial = createExample(TestedComponent, { + applyResult: { + amountEffectivePaid: 'USD:10', + amountRefundGone: 'USD:1', + amountRefundGranted: 'USD:2', + contractTermsHash: 'QWEASDZXC', + info: { + summary: 'tasty cold beer', + contractTermsHash: 'QWEASDZXC', + } as Partial<OrderShortInfo> as any, + pendingAtExchange: false, + proposalId: "proposal123", + } +}); + +export const InProgress = createExample(TestedComponent, { + applyResult: { + amountEffectivePaid: 'USD:10', + amountRefundGone: 'USD:1', + amountRefundGranted: 'USD:2', + contractTermsHash: 'QWEASDZXC', + info: { + summary: 'tasty cold beer', + contractTermsHash: 'QWEASDZXC', + } as Partial<OrderShortInfo> as any, + pendingAtExchange: true, + proposalId: "proposal123", + } +}); diff --git a/packages/taler-wallet-webextension/src/wallet/Refund.tsx b/packages/taler-wallet-webextension/src/wallet/Refund.tsx index 702217415..bb26d933b 100644 --- a/packages/taler-wallet-webextension/src/wallet/Refund.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Refund.tsx @@ -32,7 +32,32 @@ import { JSX } from "preact/jsx-runtime"; interface Props { talerRefundUri?: string } - +export interface ViewProps { + applyResult: ApplyRefundResponse; +} +export function View({ applyResult }: ViewProps) { + return <section class="main"> + <h1>GNU Taler Wallet</h1> + <article class="fade"> + <h2>Refund Status</h2> + <p> + The product <em>{applyResult.info.summary}</em> has received a total + effective refund of{" "} + <AmountView amount={applyResult.amountRefundGranted} />. + </p> + {applyResult.pendingAtExchange ? ( + <p>Refund processing is still in progress.</p> + ) : null} + {!Amounts.isZero(applyResult.amountRefundGone) ? ( + <p> + The refund amount of{" "} + <AmountView amount={applyResult.amountRefundGone} />{" "} + could not be applied. + </p> + ) : null} + </article> + </section> +} export function RefundPage({ talerRefundUri }: Props): JSX.Element { const [applyResult, setApplyResult] = useState<ApplyRefundResponse | undefined>(undefined); const [errMsg, setErrMsg] = useState<string | undefined>(undefined); @@ -66,24 +91,5 @@ export function RefundPage({ talerRefundUri }: Props): JSX.Element { return <span>Updating refund status</span>; } - return ( - <> - <h2>Refund Status</h2> - <p> - The product <em>{applyResult.info.summary}</em> has received a total - effective refund of{" "} - <AmountView amount={applyResult.amountRefundGranted} />. - </p> - {applyResult.pendingAtExchange ? ( - <p>Refund processing is still in progress.</p> - ) : null} - {!Amounts.isZero(applyResult.amountRefundGone) ? ( - <p> - The refund amount of{" "} - <AmountView amount={applyResult.amountRefundGone} /> - could not be applied. - </p> - ) : null} - </> - ); + return <View applyResult={applyResult} />; } diff --git a/packages/taler-wallet-webextension/src/wallet/Tip.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Tip.stories.tsx new file mode 100644 index 000000000..ffd976144 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/Tip.stories.tsx @@ -0,0 +1,66 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util'; +import { FunctionalComponent, h } from 'preact'; +import { View as TestedComponent } from './Tip'; + + +export default { + title: 'wallet/tip', + component: TestedComponent, + argTypes: { + }, +}; + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { + const r = (args: any) => <Component {...args} /> + r.args = props + return r +} + +export const Accepted = createExample(TestedComponent, { + prepareTipResult: { + accepted: true, + merchantBaseUrl: '', + exchangeBaseUrl: '', + expirationTimestamp : { + t_ms: 0 + }, + tipAmountEffective: 'USD:10', + tipAmountRaw: 'USD:5', + walletTipId: 'id' + } +}); + +export const NotYetAccepted = createExample(TestedComponent, { + prepareTipResult: { + accepted: false, + merchantBaseUrl: 'http://merchant.url/', + exchangeBaseUrl: 'http://exchange.url/', + expirationTimestamp : { + t_ms: 0 + }, + tipAmountEffective: 'USD:10', + tipAmountRaw: 'USD:5', + walletTipId: 'id' + } +}); diff --git a/packages/taler-wallet-webextension/src/wallet/Tip.tsx b/packages/taler-wallet-webextension/src/wallet/Tip.tsx index 708e8940b..69886668b 100644 --- a/packages/taler-wallet-webextension/src/wallet/Tip.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Tip.tsx @@ -26,8 +26,41 @@ import { AmountView } from "../renderHtml"; import * as wxApi from "../wxApi"; import { JSX } from "preact/jsx-runtime"; -interface Props { - talerTipUri?: string +interface Props { + talerTipUri?: string +} +export interface ViewProps { + prepareTipResult: PrepareTipResult; + onAccept: () => void; + onIgnore: () => void; + +} +export function View({ prepareTipResult, onAccept, onIgnore }: ViewProps) { + return <section class="main"> + <h1>GNU Taler Wallet</h1> + <article class="fade"> + {prepareTipResult.accepted ? ( + <span> + Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. Check + your transactions list for more details. + </span> + ) : ( + <div> + <p> + The merchant <code>{prepareTipResult.merchantBaseUrl}</code> is + offering you a tip of{" "} + <strong> + <AmountView amount={prepareTipResult.tipAmountEffective} /> + </strong>{" "} + via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code> + </p> + <button onClick={onAccept}>Accept tip</button> + <button onClick={onIgnore}>Ignore</button> + </div> + )} + </article> + </section> + } export function TipPage({ talerTipUri }: Props): JSX.Element { @@ -71,27 +104,7 @@ export function TipPage({ talerTipUri }: Props): JSX.Element { return <span>Loading ...</span>; } - if (prepareTipResult.accepted) { - return ( - <span> - Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. Check - your transactions list for more details. - </span> - ); - } else { - return ( - <div> - <p> - The merchant <code>{prepareTipResult.merchantBaseUrl}</code> is - offering you a tip of{" "} - <strong> - <AmountView amount={prepareTipResult.tipAmountEffective} /> - </strong>{" "} - via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code> - </p> - <button onClick={doAccept}>Accept tip</button> - <button onClick={doIgnore}>Ignore</button> - </div> - ); - } + return <View prepareTipResult={prepareTipResult} + onAccept={doAccept} onIgnore={doIgnore} + /> } diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx new file mode 100644 index 000000000..4fa87a137 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx @@ -0,0 +1,56 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { FunctionalComponent, h } from 'preact'; +import { View as TestedComponent } from './Welcome'; + + +export default { + title: 'wallet/welcome', + component: TestedComponent, +}; + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { + const r = (args: any) => <Component {...args} /> + r.args = props + return r +} + +export const Normal = createExample(TestedComponent, { + permissionsEnabled: true, + diagnostics: { + errors: [], + walletManifestVersion: '1.0', + walletManifestDisplayVersion: '1.0', + firefoxIdbProblem: false, + dbOutdated: false, + } +}); + +export const TimedoutDiagnostics = createExample(TestedComponent, { + timedOut: true, + permissionsEnabled: false, +}); + +export const RunningDiagnostics = createExample(TestedComponent, { + permissionsEnabled: false, +}); + diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx index c74384596..4c33e1c72 100644 --- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx @@ -24,13 +24,36 @@ import { JSX } from "preact/jsx-runtime"; import { Checkbox } from "../components/Checkbox"; import { useExtendedPermissions } from "../hooks/useExtendedPermissions"; import { Diagnostics } from "../components/Diagnostics"; +import { WalletPage } from "../components/styled"; +import { useDiagnostics } from "../hooks/useDiagnostics"; +import { WalletDiagnostics } from "@gnu-taler/taler-util"; -export function WelcomePage(): JSX.Element { +export function WelcomePage() { const [permissionsEnabled, togglePermissions] = useExtendedPermissions() - return ( - <> + const [diagnostics, timedOut] = useDiagnostics() + return <View + permissionsEnabled={permissionsEnabled} togglePermissions={togglePermissions} + diagnostics={diagnostics} timedOut={timedOut} + /> +} + +export interface ViewProps { + permissionsEnabled: boolean, + togglePermissions: () => void, + diagnostics: WalletDiagnostics | undefined, + timedOut: boolean, +} +export function View({ permissionsEnabled, togglePermissions, diagnostics, timedOut }: ViewProps): JSX.Element { + return (<WalletPage> + <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;"> + <h1 style="font-family: monospace; font-size: 250%;"> + <span style="color: #aa3939;">❰</span>Taler Wallet<span style="color: #aa3939;">❱</span> + </h1> + </div> + <h1>Browser Extension Installed!</h1> + <div> <p>Thank you for installing the wallet.</p> - <Diagnostics /> + <Diagnostics diagnostics={diagnostics} timedOut={timedOut} /> <h2>Permissions</h2> <Checkbox label="Automatically open wallet based on page content" name="perm" @@ -44,6 +67,7 @@ export function WelcomePage(): JSX.Element { <a href="https://demo.taler.net/" style={{ display: "block" }}> Learn how to top up your wallet balance » </a> - </> + </div> + </WalletPage> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx index 24fb17dfa..fef36b820 100644 --- a/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx @@ -30,27 +30,12 @@ export default { }, }; -export const WithoutURI = (a: any) => <View {...a} />; -WithoutURI.args = { -} as ViewProps - export const WithoutDetails = (a: any) => <View {...a} />; WithoutDetails.args = { - talerWithdrawUri: 'http://something' -} as ViewProps - -export const Cancelled = (a: any) => <View {...a} />; -Cancelled.args = { - talerWithdrawUri: 'http://something', - details: { - amount: 'USD:2', - }, - cancelled: true } as ViewProps export const CompleteWithExchange = (a: any) => <View {...a} />; CompleteWithExchange.args = { - talerWithdrawUri: 'http://something', details: { amount: 'USD:2', }, @@ -59,7 +44,6 @@ CompleteWithExchange.args = { export const CompleteWithoutExchange = (a: any) => <View {...a} />; CompleteWithoutExchange.args = { - talerWithdrawUri: 'http://something', details: { amount: 'USD:2', }, diff --git a/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx b/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx index 4cb8ebfa1..442ee7dae 100644 --- a/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx @@ -32,6 +32,7 @@ import { } from "../wxApi"; import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util"; import { JSX } from "preact/jsx-runtime"; +import { WalletPage } from '../components/styled'; interface Props { talerWithdrawUri?: string; @@ -39,79 +40,72 @@ interface Props { export interface ViewProps { talerWithdrawUri?: string; - details?: WithdrawUriInfoResponse; - cancelled?: boolean; + details: WithdrawUriInfoResponse; selectedExchange?: string; accept: () => Promise<void>; setCancelled: (b: boolean) => void; setSelecting: (b: boolean) => void; }; -export function View({ talerWithdrawUri, details, cancelled, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) { - const [state, setState] = useState(1) - setTimeout(() => { - setState(s => s + 1) - }, 1000); - if (!talerWithdrawUri) { - return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>; - } - - if (!details) { - return <span><i18n.Translate>Loading...</i18n.Translate></span>; - } - - if (cancelled) { - return <span><i18n.Translate>Withdraw operation has been cancelled.{state}</i18n.Translate></span>; - } +export function View({ details, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) { return ( - <div> - <h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1> - <p><i18n.Translate> - You are about to withdraw{" "} - <strong>{renderAmount(details.amount)}</strong> from your bank account - into your wallet. - </i18n.Translate></p> - {selectedExchange ? ( - <p><i18n.Translate> - The exchange <strong>{selectedExchange}</strong> will be used as the - Taler payment service provider. - </i18n.Translate></p> - ) : null} - - <div> - <button - class="pure-button button-success" - disabled={!selectedExchange} - onClick={() => accept()} - > - {i18n.str`Accept fees and withdraw`} - </button> - <p> - <span - role="button" - tabIndex={0} - style={{ textDecoration: "underline", cursor: "pointer" }} - onClick={() => setSelecting(true)} - > - {i18n.str`Chose different exchange provider`} - </span> - <br /> - <span - role="button" - tabIndex={0} - style={{ textDecoration: "underline", cursor: "pointer" }} - onClick={() => setCancelled(true)} - > - {i18n.str`Cancel withdraw operation`} - </span> - </p> + <WalletPage> + <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;"> + <h1 style="font-family: monospace; font-size: 250%;"> + <span style="color: #aa3939;">❰</span>Taler Wallet<span style="color: #aa3939;">❱</span> + </h1> + </div> + <div class="fade"> + <div> + <h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1> + <p><i18n.Translate> + You are about to withdraw{" "} + <strong>{renderAmount(details.amount)}</strong> from your bank account + into your wallet. + </i18n.Translate></p> + {selectedExchange ? ( + <p><i18n.Translate> + The exchange <strong>{selectedExchange}</strong> will be used as the + Taler payment service provider. + </i18n.Translate></p> + ) : null} + + <div> + <button + class="pure-button button-success" + disabled={!selectedExchange} + onClick={() => accept()} + > + {i18n.str`Accept fees and withdraw`} + </button> + <p> + <span + role="button" + tabIndex={0} + style={{ textDecoration: "underline", cursor: "pointer" }} + onClick={() => setSelecting(true)} + > + {i18n.str`Chose different exchange provider`} + </span> + <br /> + <span + role="button" + tabIndex={0} + style={{ textDecoration: "underline", cursor: "pointer" }} + onClick={() => setCancelled(true)} + > + {i18n.str`Cancel withdraw operation`} + </span> + </p> + </div> + </div> </div> - </div> + </WalletPage> ) } -export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element { +export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element { const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(undefined); const [selectedExchange, setSelectedExchange] = useState< string | undefined @@ -120,27 +114,44 @@ export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element { const [selecting, setSelecting] = useState(false); const [errMsg, setErrMsg] = useState<string | undefined>(""); const [updateCounter, setUpdateCounter] = useState(1); + const [state, setState] = useState(1) + + // setTimeout(() => { + // console.log('tick...') + // setState(s => s + 1) + // }, 1000); useEffect(() => { return onUpdateNotification(() => { + console.log('updating...') setUpdateCounter(updateCounter + 1); }); }, []); useEffect(() => { + console.log('on effect yes', talerWithdrawUri) if (!talerWithdrawUri) return const fetchData = async (): Promise<void> => { - const res = await getWithdrawalDetailsForUri({ talerWithdrawUri }); - setDetails(res); - if (res.defaultExchangeBaseUrl) { - setSelectedExchange(res.defaultExchangeBaseUrl); + console.log('que pasa') + try { + const res = await getWithdrawalDetailsForUri({ talerWithdrawUri }); + console.log('res', res) + setDetails(res); + if (res.defaultExchangeBaseUrl) { + setSelectedExchange(res.defaultExchangeBaseUrl); + } + } catch (e) { + console.error(e) } }; fetchData(); - }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]); + }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter, state]); + + if (!talerWithdrawUri) { + return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>; + } const accept = async (): Promise<void> => { - if (!talerWithdrawUri) return if (!selectedExchange) { throw Error("can't accept, no exchange selected"); } @@ -152,10 +163,16 @@ export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element { } }; + if (!details) { + return <span><i18n.Translate>Loading...</i18n.Translate></span>; + } + if (cancelled) { + return <span><i18n.Translate>Withdraw operation has been cancelled.</i18n.Translate></span>; + } + return <View accept={accept} setCancelled={setCancelled} setSelecting={setSelecting} - cancelled={cancelled} details={details} selectedExchange={selectedExchange} - talerWithdrawUri={talerWithdrawUri} + details={details} selectedExchange={selectedExchange} /> } diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx index 004fcc717..f487e54fc 100644 --- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx +++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx @@ -32,7 +32,6 @@ import { RefundPage } from "./wallet/Refund"; import { TipPage } from './wallet/Tip'; import Router, { route, Route } from "preact-router"; - function main(): void { try { const container = document.getElementById("container"); @@ -67,64 +66,15 @@ enum Pages { } function Application() { - const sp = new URL(document.location.href).searchParams - const queryParams: any = {} - sp.forEach((v, k) => { queryParams[k] = v; }); - - return <Router history={createHashHistory()} > - - <Route path={Pages.welcome} component={() => { - return <section class="main"> - <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;"> - <h1 style="font-family: monospace; font-size: 250%;"> - <span style="color: #aa3939;">❰</span>Taler Wallet<span style="color: #aa3939;">❱</span> - </h1> - </div> - <h1>Browser Extension Installed!</h1> - <div> - <WelcomePage /> - </div> - </section> - }} /> - - <Route path={Pages.pay} component={() => { - return <section class="main"> - <h1>GNU Taler Wallet</h1> - <article class="fade"> - <PayPage talerPayUri={queryParams.talerPayUri} /> - </article> - </section> - }} /> - - <Route path={Pages.refund} component={() => { - return <section class="main"> - <h1>GNU Taler Wallet</h1> - <article class="fade"> - <RefundPage talerRefundUri={queryParams.talerRefundUri} /> - </article> - </section> - }} /> - - <Route path={Pages.tips} component={() => { - return <section class="main"> - <h1>GNU Taler Wallet</h1> - <div> - <TipPage talerTipUri={queryParams.talerTipUri} /> - </div> - </section> - }} /> - <Route path={Pages.withdraw} component={() => { - return <section class="main"> - <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;"> - <h1 style="font-family: monospace; font-size: 250%;"> - <span style="color: #aa3939;">❰</span>Taler Wallet<span style="color: #aa3939;">❱</span> - </h1> - </div> - <div class="fade"> - <WithdrawPage talerWithdrawUri={queryParams.talerWithdrawUri} /> - </div> - </section> - }} /> + const h = createHashHistory(); + return <Router history={h} > + + <Route path={Pages.welcome} component={WelcomePage} /> + <Route path={Pages.pay} component={PayPage} /> + <Route path={Pages.refund} component={RefundPage} /> + + <Route path={Pages.tips} component={TipPage} /> + <Route path={Pages.withdraw} component={WithdrawPage} /> <Route path={Pages.reset_required} component={() => <div>no yet implemented</div>} /> <Route path={Pages.payback} component={() => <div>no yet implemented</div>} /> diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index e1517c4cf..c474c940c 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -216,12 +216,8 @@ function makeSyncWalletRedirect( ): Record<string, unknown> { const innerUrl = new URL(chrome.extension.getURL(url)); if (params) { - for (const key in params) { - const p = params[key]; - if (p) { - innerUrl.searchParams.set(key, p); - } - } + const hParams = Object.keys(params).map(k => `${k}=${params[k]}`).join('&') + innerUrl.hash = innerUrl.hash + '?' + hParams } if (isFirefox()) { // Some platforms don't support the sync redirect (yet), so fall back to diff --git a/packages/taler-wallet-webextension/static/wallet.html b/packages/taler-wallet-webextension/static/wallet.html index 2b500b56f..817e8bfb8 100644 --- a/packages/taler-wallet-webextension/static/wallet.html +++ b/packages/taler-wallet-webextension/static/wallet.html @@ -4,6 +4,7 @@ <meta charset="utf-8" /> <link rel="stylesheet" type="text/css" href="/static/style/pure.css" /> <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" /> + <link rel="stylesheet" type="text/css" href="/dist/styles.css" /> <link rel="icon" href="/static/img/icon.png" /> <script src="/dist/walletEntryPoint.js"></script> </head> |