diff options
-rw-r--r-- | packages/taler-wallet-webextension/.storybook/preview.js | 5 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/package.json | 1 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/rollup.config.js | 2 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/custom.d.ts | 27 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/popup/History.stories.tsx | 256 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/popup/History.tsx | 34 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx | 76 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/popup/Transaction.tsx | 308 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/popupEntryPoint.tsx | 2 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/static/img/empty.png | bin | 0 -> 2785 bytes | |||
-rw-r--r-- | packages/taler-wallet-webextension/static/style/popup.css | 33 | ||||
-rw-r--r-- | pnpm-lock.yaml | 18 |
12 files changed, 531 insertions, 231 deletions
diff --git a/packages/taler-wallet-webextension/.storybook/preview.js b/packages/taler-wallet-webextension/.storybook/preview.js index e669398db..6a7c8e867 100644 --- a/packages/taler-wallet-webextension/.storybook/preview.js +++ b/packages/taler-wallet-webextension/.storybook/preview.js @@ -15,7 +15,10 @@ */ import { setupI18n } from "@gnu-taler/taler-util" -import { strings } from '../src/i18n' +import { strings } from '../src/i18n/strings.ts' +import '../static/style/pure.css' +import '../static/style/popup.css' +import '../static/style/wallet.css' const mockConfig = { backendURL: 'http://demo.taler.net', diff --git a/packages/taler-wallet-webextension/package.json b/packages/taler-wallet-webextension/package.json index 60a2ea5d4..206ce8c1b 100644 --- a/packages/taler-wallet-webextension/package.json +++ b/packages/taler-wallet-webextension/package.json @@ -28,6 +28,7 @@ "@babel/plugin-transform-react-jsx-source": "^7.12.13", "@babel/preset-typescript": "^7.13.0", "@rollup/plugin-commonjs": "^17.0.0", + "@rollup/plugin-image": "^2.0.6", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^11.1.0", "@rollup/plugin-replace": "^2.3.4", diff --git a/packages/taler-wallet-webextension/rollup.config.js b/packages/taler-wallet-webextension/rollup.config.js index c5bb936cf..80b4f6eec 100644 --- a/packages/taler-wallet-webextension/rollup.config.js +++ b/packages/taler-wallet-webextension/rollup.config.js @@ -5,6 +5,7 @@ import json from "@rollup/plugin-json"; import builtins from "builtin-modules"; import replace from "@rollup/plugin-replace"; import ignore from "rollup-plugin-ignore" +import image from '@rollup/plugin-image'; const makePlugins = () => [ ignore(["module", "os"]), @@ -29,6 +30,7 @@ const makePlugins = () => [ }), json(), + image(), ]; diff --git a/packages/taler-wallet-webextension/src/custom.d.ts b/packages/taler-wallet-webextension/src/custom.d.ts new file mode 100644 index 000000000..1981067d4 --- /dev/null +++ b/packages/taler-wallet-webextension/src/custom.d.ts @@ -0,0 +1,27 @@ +/* + 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/> + */ +declare module "*.jpeg" { + const content: any; + export default content; +} +declare module "*.png" { + const content: any; + export default content; +} +declare module '*.svg' { + const content: any; + export default content; +} diff --git a/packages/taler-wallet-webextension/src/popup/History.stories.tsx b/packages/taler-wallet-webextension/src/popup/History.stories.tsx new file mode 100644 index 000000000..a73b7ea3f --- /dev/null +++ b/packages/taler-wallet-webextension/src/popup/History.stories.tsx @@ -0,0 +1,256 @@ +/* + 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 { + PaymentStatus, + TransactionCommon, TransactionDeposit, TransactionPayment, + TransactionRefresh, TransactionRefund, TransactionTip, TransactionType, + TransactionWithdrawal, + WithdrawalType +} from '@gnu-taler/taler-util'; +import { FunctionalComponent } from 'preact'; +import { HistoryView as TestedComponent } from './History'; + +export default { + title: 'popup/transaction/list', + component: TestedComponent, + decorators: [ + (Story: any) => <div> + <link key="1" rel="stylesheet" type="text/css" href="/style/pure.css" /> + <link key="2" rel="stylesheet" type="text/css" href="/style/popup.css" /> + <link key="3" rel="stylesheet" type="text/css" href="/style/wallet.css" /> + <div style={{ margin: "1em", width: 400, display: 'flex', padding: '0.5em', height: 'calc(20rem - 34px)', border: 'black solid 1px' }}> + <Story /> + </div> + </div> + ], +}; + +const commonTransaction = { + amountRaw: 'USD:10', + amountEffective: 'USD:9', + pending: false, + timestamp: { + t_ms: new Date().getTime() + }, + transactionId: '12', +} as TransactionCommon + +const exampleData = { + withdraw: { + ...commonTransaction, + type: TransactionType.Withdrawal, + exchangeBaseUrl: 'http://exchange.taler', + withdrawalDetails: { + confirmed: false, + exchangePaytoUris: ['payto://x-taler-bank/bank/account'], + type: WithdrawalType.ManualTransfer, + } + } as TransactionWithdrawal, + payment: { + ...commonTransaction, + amountEffective: 'USD:11', + type: TransactionType.Payment, + info: { + contractTermsHash: 'ASDZXCASD', + merchant: { + name: 'the merchant', + }, + orderId: '2021.167-03NPY6MCYMVGT', + products: [], + summary: 'the summary', + fulfillmentMessage: '', + }, + proposalId: '1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0', + status: PaymentStatus.Accepted, + } as TransactionPayment, + deposit: { + ...commonTransaction, + type: TransactionType.Deposit, + depositGroupId: '#groupId', + targetPaytoUri: 'payto://x-taler-bank/bank/account', + } as TransactionDeposit, + refresh: { + ...commonTransaction, + type: TransactionType.Refresh, + exchangeBaseUrl: 'http://exchange.taler', + } as TransactionRefresh, + tip: { + ...commonTransaction, + type: TransactionType.Tip, + merchantBaseUrl: 'http://merchant.taler', + } as TransactionTip, + refund: { + ...commonTransaction, + type: TransactionType.Refund, + refundedTransactionId: 'payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0', + info: { + contractTermsHash: 'ASDZXCASD', + merchant: { + name: 'the merchant', + }, + orderId: '2021.167-03NPY6MCYMVGT', + products: [], + summary: 'the summary', + fulfillmentMessage: '', + }, + } as TransactionRefund, +} + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { + const r = (args: any) => <Component {...args} /> + r.args = props + return r +} + +export const Empty = createExample(TestedComponent, { + list: [] +}); + + +export const One = createExample(TestedComponent, { + list: [exampleData.withdraw] +}); + +export const Several = createExample(TestedComponent, { + list: [ + exampleData.withdraw, + exampleData.payment, + exampleData.withdraw, + exampleData.payment, + exampleData.refresh, + exampleData.refund, + exampleData.tip, + exampleData.deposit, + ] +}); + +// export const WithdrawPending = createExample(TestedComponent, { +// transaction: { ...exampleData.withdraw, pending: true }, +// }); + + +// export const Payment = createExample(TestedComponent, { +// transaction: exampleData.payment +// }); + +// export const PaymentWithoutFee = createExample(TestedComponent, { +// transaction: { +// ...exampleData.payment, +// amountRaw: 'USD:11', + +// } +// }); + +// export const PaymentPending = createExample(TestedComponent, { +// transaction: { ...exampleData.payment, pending: true }, +// }); + +// export const PaymentWithProducts = createExample(TestedComponent, { +// transaction: { +// ...exampleData.payment, +// info: { +// ...exampleData.payment.info, +// summary: 'this order has 5 products', +// products: [{ +// description: 't-shirt', +// unit: 'shirts', +// quantity: 1, +// }, { +// description: 't-shirt', +// unit: 'shirts', +// quantity: 1, +// }, { +// description: 'e-book', +// }, { +// description: 'beer', +// unit: 'pint', +// quantity: 15, +// }, { +// description: 'beer', +// unit: 'pint', +// quantity: 15, +// }] +// } +// } as TransactionPayment, +// }); + +// export const PaymentWithLongSummary = createExample(TestedComponent, { +// transaction: { +// ...exampleData.payment, +// info: { +// ...exampleData.payment.info, +// summary: 'this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, ', +// products: [{ +// description: 'an xl sized t-shirt with some drawings on it, color pink', +// unit: 'shirts', +// quantity: 1, +// }, { +// description: 'beer', +// unit: 'pint', +// quantity: 15, +// }] +// } +// } as TransactionPayment, +// }); + + +// export const Deposit = createExample(TestedComponent, { +// transaction: exampleData.deposit +// }); + +// export const DepositPending = createExample(TestedComponent, { +// transaction: { ...exampleData.deposit, pending: true } +// }); + +// export const Refresh = createExample(TestedComponent, { +// transaction: exampleData.refresh +// }); + +// export const Tip = createExample(TestedComponent, { +// transaction: exampleData.tip +// }); + +// export const TipPending = createExample(TestedComponent, { +// transaction: { ...exampleData.tip, pending: true } +// }); + +// export const Refund = createExample(TestedComponent, { +// transaction: exampleData.refund +// }); + +// export const RefundPending = createExample(TestedComponent, { +// transaction: { ...exampleData.refund, pending: true } +// }); + +// export const RefundWithProducts = createExample(TestedComponent, { +// transaction: { +// ...exampleData.refund, +// info: { +// ...exampleData.refund.info, +// products: [{ +// description: 't-shirt', +// }, { +// description: 'beer', +// }] +// } +// } as TransactionRefund, +// }); diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx b/packages/taler-wallet-webextension/src/popup/History.tsx index ffcec5e41..1a70f00ff 100644 --- a/packages/taler-wallet-webextension/src/popup/History.tsx +++ b/packages/taler-wallet-webextension/src/popup/History.tsx @@ -38,17 +38,23 @@ export function HistoryPage(props: any): JSX.Element { return <div>Loading ...</div>; } - const txs = [...transactions.transactions].reverse(); + return <HistoryView list={[...transactions.transactions].reverse()} />; +} - return ( - <div> - {txs.map((tx, i) => ( - <TransactionItem key={i} tx={tx} /> - ))} - </div> - ); +export function HistoryView({ list }: { list: Transaction[] }) { + return <div style={{ height: 'calc(20rem - 34px )', overflow: 'auto', width: '100%' }}> + {list.map((tx, i) => ( + <TransactionItem key={i} tx={tx} /> + ))} + </div> } +import imageBank from '../../static/img/ri-bank-line.svg'; +import imageShoppingCart from '../../static/img/ri-shopping-cart-line.svg'; +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'; + function TransactionItem(props: { tx: Transaction }): JSX.Element { const tx = props.tx; switch (tx.type) { @@ -61,7 +67,7 @@ function TransactionItem(props: { tx: Transaction }): JSX.Element { title="Withdrawal" subtitle={`via ${tx.exchangeBaseUrl}`} timestamp={tx.timestamp} - iconPath="/static/img/ri-bank-line.svg" + iconPath={imageBank} pending={tx.pending} ></TransactionLayout> ); @@ -74,7 +80,7 @@ function TransactionItem(props: { tx: Transaction }): JSX.Element { title="Payment" subtitle={tx.info.summary} timestamp={tx.timestamp} - iconPath="/static/img/ri-shopping-cart-line.svg" + iconPath={imageShoppingCart} pending={tx.pending} ></TransactionLayout> ); @@ -87,7 +93,7 @@ function TransactionItem(props: { tx: Transaction }): JSX.Element { title="Refund" subtitle={tx.info.summary} timestamp={tx.timestamp} - iconPath="/static/img/ri-refund-2-line.svg" + iconPath={imageRefund} pending={tx.pending} ></TransactionLayout> ); @@ -100,7 +106,7 @@ function TransactionItem(props: { tx: Transaction }): JSX.Element { title="Tip" subtitle={`from ${new URL(tx.merchantBaseUrl).hostname}`} timestamp={tx.timestamp} - iconPath="/static/img/ri-hand-heart-line.svg" + iconPath={imageHandHeart} pending={tx.pending} ></TransactionLayout> ); @@ -113,7 +119,7 @@ function TransactionItem(props: { tx: Transaction }): JSX.Element { title="Refresh" subtitle={`via exchange ${tx.exchangeBaseUrl}`} timestamp={tx.timestamp} - iconPath="/static/img/ri-refresh-line.svg" + iconPath={imageRefresh} pending={tx.pending} ></TransactionLayout> ); @@ -126,7 +132,7 @@ function TransactionItem(props: { tx: Transaction }): JSX.Element { title="Refresh" subtitle={`to ${tx.targetPaytoUri}`} timestamp={tx.timestamp} - iconPath="/static/img/ri-refresh-line.svg" + iconPath={imageRefresh} pending={tx.pending} ></TransactionLayout> ); diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx index 3df2687fd..bf090cad7 100644 --- a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx @@ -37,7 +37,7 @@ export default { <link key="1" rel="stylesheet" type="text/css" href="/style/pure.css" /> <link key="2" rel="stylesheet" type="text/css" href="/style/popup.css" /> <link key="3" rel="stylesheet" type="text/css" href="/style/wallet.css" /> - <div style={{ margin: "1em", width: 400 }}> + <div style={{ margin: "1em", width: 400, display: 'flex', padding: '0.5em', height: 'calc(20rem - 34px)', border: 'black solid 1px' }}> <Story /> </div> </div> @@ -74,7 +74,7 @@ const exampleData = { merchant: { name: 'the merchant', }, - orderId: '#12345', + orderId: '2021.167-03NPY6MCYMVGT', products: [], summary: 'the summary', fulfillmentMessage: '', @@ -107,7 +107,7 @@ const exampleData = { merchant: { name: 'the merchant', }, - orderId: '#12345', + orderId: '2021.167-03NPY6MCYMVGT', products: [], summary: 'the summary', fulfillmentMessage: '', @@ -121,69 +121,111 @@ function createExample<Props>(Component: FunctionalComponent<Props>, props: Part return r } -export const NotYetLoaded = createExample(TestedComponent,{}); +export const NotYetLoaded = createExample(TestedComponent, {}); -export const Withdraw = createExample(TestedComponent,{ +export const Withdraw = createExample(TestedComponent, { transaction: exampleData.withdraw }); -export const WithdrawPending = createExample(TestedComponent,{ +export const WithdrawPending = createExample(TestedComponent, { transaction: { ...exampleData.withdraw, pending: true }, }); -export const Payment = createExample(TestedComponent,{ +export const Payment = createExample(TestedComponent, { transaction: exampleData.payment }); -export const PaymentPending = createExample(TestedComponent,{ +export const PaymentWithoutFee = createExample(TestedComponent, { + transaction: { + ...exampleData.payment, + amountRaw: 'USD:11', + + } +}); + +export const PaymentPending = createExample(TestedComponent, { transaction: { ...exampleData.payment, pending: true }, }); -export const PaymentWithProducts = createExample(TestedComponent,{ +export const PaymentWithProducts = createExample(TestedComponent, { transaction: { ...exampleData.payment, info: { ...exampleData.payment.info, + summary: 'this order has 5 products', products: [{ description: 't-shirt', + unit: 'shirts', + quantity: 1, + }, { + description: 't-shirt', + unit: 'shirts', + quantity: 1, + }, { + description: 'e-book', + }, { + description: 'beer', + unit: 'pint', + quantity: 15, + }, { + description: 'beer', + unit: 'pint', + quantity: 15, + }] + } + } as TransactionPayment, +}); + +export const PaymentWithLongSummary = createExample(TestedComponent, { + transaction: { + ...exampleData.payment, + info: { + ...exampleData.payment.info, + summary: 'this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, ', + products: [{ + description: 'an xl sized t-shirt with some drawings on it, color pink', + unit: 'shirts', + quantity: 1, }, { description: 'beer', + unit: 'pint', + quantity: 15, }] } } as TransactionPayment, }); -export const Deposit = createExample(TestedComponent,{ +export const Deposit = createExample(TestedComponent, { transaction: exampleData.deposit }); -export const DepositPending = createExample(TestedComponent,{ +export const DepositPending = createExample(TestedComponent, { transaction: { ...exampleData.deposit, pending: true } }); -export const Refresh = createExample(TestedComponent,{ +export const Refresh = createExample(TestedComponent, { transaction: exampleData.refresh }); -export const Tip = createExample(TestedComponent,{ +export const Tip = createExample(TestedComponent, { transaction: exampleData.tip }); -export const TipPending = createExample(TestedComponent,{ +export const TipPending = createExample(TestedComponent, { transaction: { ...exampleData.tip, pending: true } }); -export const Refund = createExample(TestedComponent,{ +export const Refund = createExample(TestedComponent, { transaction: exampleData.refund }); -export const RefundPending = createExample(TestedComponent,{ +export const RefundPending = createExample(TestedComponent, { transaction: { ...exampleData.refund, pending: true } }); -export const RefundWithProducts = createExample(TestedComponent,{ +export const RefundWithProducts = createExample(TestedComponent, { transaction: { ...exampleData.refund, info: { diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.tsx b/packages/taler-wallet-webextension/src/popup/Transaction.tsx index b1179228e..6939a08c3 100644 --- a/packages/taler-wallet-webextension/src/popup/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/popup/Transaction.tsx @@ -14,14 +14,14 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util"; +import { AmountJson, Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util"; import { format } from "date-fns"; -import { JSX } from "preact"; +import { Fragment, JSX } from "preact"; import { route } from 'preact-router'; import { useEffect, useState } from "preact/hooks"; import * as wxApi from "../wxApi"; import { Pages } from "./popup"; - +import emptyImg from "../../static/img/empty.png" export function TransactionPage({ tid }: { tid: string; }): JSX.Element { const [transaction, setTransaction] = useState< @@ -59,11 +59,10 @@ export function TransactionView({ transaction, onDelete, onBack }: WalletTransac } function Footer() { - return <footer style={{ marginTop: 'auto', display: 'flex' }}> + return <footer style={{ marginTop: 'auto', display: 'flex', flexShrink: 0 }}> <button onClick={onBack}><i18n.Translate>back</i18n.Translate></button> <div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}> - <button onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button> - + <button class="pure-button button-destructive" onClick={onDelete}><i18n.Translate>forget</i18n.Translate></button> </div> </footer> @@ -74,91 +73,66 @@ export function TransactionView({ transaction, onDelete, onBack }: WalletTransac return <span style={{ fontWeight: 'normal', fontSize: 16, color: 'gray' }}>(pending...)</span> } + const Fee = ({ value }: { value: AmountJson }) => Amounts.isNonZero(value) ? + <span style="font-size: 16px;font-weight: normal;color: gray;">(fee {Amounts.stringify(value)})</span> : null + if (transaction.type === TransactionType.Withdrawal) { + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount return ( - <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} > - <section> - <h1>Withdrawal <Pending /></h1> - <p> + <div style={{ display: 'flex', flexDirection: 'column', width: '100%' }} > + <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(20rem - 34px - 45px)', overflow: 'auto' }}> + <span style="flat: left; font-size:small; color:gray">{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</span> + <span style="float: right; font-size:small; color:gray"> From <b>{transaction.exchangeBaseUrl}</b> - </p> - <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}> - <tr> - <td>Amount subtracted</td> - <td>{transaction.amountRaw}</td> - </tr> - <tr> - <td>Amount received</td> - <td>{transaction.amountEffective}</td> - </tr> - <tr> - <td>Exchange fee</td> - <td>{Amounts.stringify( - Amounts.sub( - Amounts.parseOrThrow(transaction.amountRaw), - Amounts.parseOrThrow(transaction.amountEffective), - ).amount - )}</td> - </tr> - <tr> - <td>When</td> - <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td> - </tr> - </table> + </span> + <h3>Withdraw <Pending /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> </section> <Footer /> </div> ); } + const showLargePic = () => { + + } + if (transaction.type === TransactionType.Payment) { + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountEffective), + Amounts.parseOrThrow(transaction.amountRaw), + ).amount + return ( - <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} > - <section> - <h1>Payment ({transaction.proposalId.substring(0, 10)}...) <Pending /></h1> - <p> + <div style={{ display: 'flex', flexDirection: 'column', width: '100%' }} > + <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(20rem - 34px - 45px)', overflow: 'auto' }}> + <span style="flat: left; font-size:small; color:gray">{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</span> + <span style="float: right; font-size:small; color:gray"> To <b>{transaction.info.merchant.name}</b> + </span> + <h3>Payment <Pending /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + <span style="font-size:small; color:gray">#{transaction.info.orderId}</span> + <p> + {transaction.info.summary} </p> - <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}> - <tr> - <td>Order id</td> - <td>{transaction.info.orderId}</td> - </tr> - <tr> - <td>Summary</td> - <td>{transaction.info.summary}</td> - </tr> - {transaction.info.products && transaction.info.products.length > 0 && - <tr> - <td>Products</td> - <td><ol style={{ margin: 0, textAlign: 'left' }}> - {transaction.info.products.map(p => - <li>{p.description}</li> - )}</ol></td> - </tr> + <div> + {transaction.info.products && transaction.info.products.length > 0 && <div> + {transaction.info.products.map(p => <div style="display: flex; flex-direction: row; border: 1px solid gray; border-radius: 0.5em; margin: 0.5em 0px; justify-content: left; padding: 0.5em;"> + <a href="#" onClick={showLargePic}> + <img class="pure-img" style="display:inline-block" src={p.image ? p.image : emptyImg} width="32" height="32" /> + </a> + <div style="display: block; margin-left: 1em;"> + {p.quantity && p.quantity > 0 && <div style="font-size: small; color: gray;">x {p.quantity} {p.unit}</div>} + <div style={{ textOverflow: 'ellipsis', overflow: 'hidden', width: 'calc(20rem - 32px - 32px - 8px - 1em)', whiteSpace: 'nowrap' }}>{p.description}</div> + </div> + </div>)} + </div> } - <tr> - <td>Order amount</td> - <td>{transaction.amountRaw}</td> - </tr> - <tr> - <td>Order amount and fees</td> - <td>{transaction.amountEffective}</td> - </tr> - <tr> - <td>Exchange fee</td> - <td>{Amounts.stringify( - Amounts.sub( - Amounts.parseOrThrow(transaction.amountEffective), - Amounts.parseOrThrow(transaction.amountRaw), - ).amount - )}</td> - </tr> - <tr> - <td>When</td> - <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td> - </tr> - </table> + </div> </section> <Footer /> </div> @@ -166,36 +140,19 @@ export function TransactionView({ transaction, onDelete, onBack }: WalletTransac } if (transaction.type === TransactionType.Deposit) { + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount return ( - <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} > - <section> - <h1>Deposit ({transaction.depositGroupId}) <Pending /></h1> - <p> + <div style={{ display: 'flex', flexDirection: 'column', width: '100%' }} > + <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(20rem - 34px - 45px)', overflow: 'auto' }}> + <span style="flat: left; font-size:small; color:gray">{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</span> + <span style="float: right; font-size:small; color:gray"> To <b>{transaction.targetPaytoUri}</b> - </p> - <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}> - <tr> - <td>Amount deposit</td> - <td>{transaction.amountRaw}</td> - </tr> - <tr> - <td>Amount deposit and fees</td> - <td>{transaction.amountEffective}</td> - </tr> - <tr> - <td>Exchange fee</td> - <td>{Amounts.stringify( - Amounts.sub( - Amounts.parseOrThrow(transaction.amountEffective), - Amounts.parseOrThrow(transaction.amountRaw), - ).amount - )}</td> - </tr> - <tr> - <td>When</td> - <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td> - </tr> - </table> + </span> + <h3>Deposit <Pending /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> </section> <Footer /> </div> @@ -203,27 +160,19 @@ export function TransactionView({ transaction, onDelete, onBack }: WalletTransac } if (transaction.type === TransactionType.Refresh) { + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount return ( - <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} > - <section> - <h1>Refresh <Pending /></h1> - <p> + <div style={{ display: 'flex', flexDirection: 'column', width: '100%' }} > + <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(20rem - 34px - 45px)', overflow: 'auto' }}> + <span style="flat: left; font-size:small; color:gray">{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</span> + <span style="float: right; font-size:small; color:gray"> From <b>{transaction.exchangeBaseUrl}</b> - </p> - <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}> - <tr> - <td>Amount refreshed</td> - <td>{transaction.amountRaw}</td> - </tr> - <tr> - <td>Fees</td> - <td>{transaction.amountEffective}</td> - </tr> - <tr> - <td>When</td> - <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td> - </tr> - </table> + </span> + <h3>Refresh <Pending /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> </section> <Footer /> </div> @@ -231,91 +180,58 @@ export function TransactionView({ transaction, onDelete, onBack }: WalletTransac } if (transaction.type === TransactionType.Tip) { + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount return ( - <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} > - <section> - <h1>Tip <Pending /></h1> - <p> + <div style={{ display: 'flex', flexDirection: 'column', width: '100%' }} > + <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(20rem - 34px - 45px)', overflow: 'auto' }}> + <span style="flat: left; font-size:small; color:gray">{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</span> + <span style="float: right; font-size:small; color:gray"> From <b>{transaction.merchantBaseUrl}</b> - </p> - <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}> - <tr> - <td>Amount deduce</td> - <td>{transaction.amountRaw}</td> - </tr> - <tr> - <td>Amount received</td> - <td>{transaction.amountEffective}</td> - </tr> - <tr> - <td>Exchange fee</td> - <td>{Amounts.stringify( - Amounts.sub( - Amounts.parseOrThrow(transaction.amountRaw), - Amounts.parseOrThrow(transaction.amountEffective), - ).amount - )}</td> - </tr> - <tr> - <td>When</td> - <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td> - </tr> - </table> + </span> + <h3>Tip <Pending /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> </section> <Footer /> </div> ); } - const TRANSACTION_FROM_REFUND = /[a-z]*:([\w]{10}).*/ if (transaction.type === TransactionType.Refund) { + const fee = Amounts.sub( + Amounts.parseOrThrow(transaction.amountRaw), + Amounts.parseOrThrow(transaction.amountEffective), + ).amount return ( - <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} > - <section> - <h1>Refund ({TRANSACTION_FROM_REFUND.exec(transaction.refundedTransactionId)![1]}...) <Pending /></h1> - <p> + <div style={{ display: 'flex', flexDirection: 'column', width: '100%' }} > + <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(20rem - 34px - 45px)', overflow: 'auto' }}> + <span style="flat: left; font-size:small; color:gray">{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</span> + <span style="float: right; font-size:small; color:gray"> From <b>{transaction.info.merchant.name}</b> + </span> + <h3>Refund <Pending /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + <span style="font-size:small; color:gray">#{transaction.info.orderId}</span> + <p> + {transaction.info.summary} </p> - <table class={transaction.pending ? "detailsTable pending" : "detailsTable"}> - <tr> - <td>Order id</td> - <td>{transaction.info.orderId}</td> - </tr> - <tr> - <td>Summary</td> - <td>{transaction.info.summary}</td> - </tr> - {transaction.info.products && transaction.info.products.length > 0 && - <tr> - <td>Products</td> - <td><ol> - {transaction.info.products.map(p => - <li>{p.description}</li> - )}</ol></td> - </tr> + <div> + {transaction.info.products && transaction.info.products.length > 0 && <div> + {transaction.info.products.map(p => <div style="display: flex; flex-direction: row; border: 1px solid gray; border-radius: 0.5em; margin: 0.5em 0px; justify-content: left; padding: 0.5em;"> + <a href="#" onClick={showLargePic}> + <img class="pure-img" style="display:inline-block" src={p.image ? p.image : emptyImg} width="32" height="32" /> + </a> + <div style="display: block; margin-left: 1em;"> + {p.quantity && p.quantity > 0 && <div style="font-size: small; color: gray;">x {p.quantity} {p.unit}</div>} + <div style={{ textOverflow: 'ellipsis', overflow: 'hidden', width: 'calc(20rem - 32px - 32px - 8px - 1em)', whiteSpace: 'nowrap' }}>{p.description}</div> + </div> + </div>)} + </div> } - <tr> - <td>Amount deduce</td> - <td>{transaction.amountRaw}</td> - </tr> - <tr> - <td>Amount received</td> - <td>{transaction.amountEffective}</td> - </tr> - <tr> - <td>Exchange fee</td> - <td>{Amounts.stringify( - Amounts.sub( - Amounts.parseOrThrow(transaction.amountRaw), - Amounts.parseOrThrow(transaction.amountEffective), - ).amount - )}</td> - </tr> - <tr> - <td>When</td> - <td>{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td> - </tr> - </table> + </div> + </section> <Footer /> </div> diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx index 6cc781aa7..543b68727 100644 --- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx +++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx @@ -88,7 +88,7 @@ function Application() { return ( <div> <Match>{({ path }: any) => <WalletNavBar current={path} />}</Match > - <div style={{ margin: "1em", width: 400 }}> + <div style={{ margin: "1em", width: 400, height: 'calc(20rem - 34px)' }}> <Router> <Route path={Pages.balance} component={BalancePage} /> <Route path={Pages.settings} component={SettingsPage} /> diff --git a/packages/taler-wallet-webextension/static/img/empty.png b/packages/taler-wallet-webextension/static/img/empty.png Binary files differnew file mode 100644 index 000000000..5120d3138 --- /dev/null +++ b/packages/taler-wallet-webextension/static/img/empty.png diff --git a/packages/taler-wallet-webextension/static/style/popup.css b/packages/taler-wallet-webextension/static/style/popup.css index a234f2a2c..504400471 100644 --- a/packages/taler-wallet-webextension/static/style/popup.css +++ b/packages/taler-wallet-webextension/static/style/popup.css @@ -5,11 +5,11 @@ */ body { - min-height: 20em; + /* min-height: 20em; */ /* width: 30em; */ margin: 0; padding: 0; - max-height: 800px; + /* max-height: 800px; */ overflow: hidden; background-color: #f8faf7; font-family: Arial, Helvetica, sans-serif; @@ -259,4 +259,33 @@ table.detailsTable { table.detailsTable.pending { color: gray; +} + +.overflow { + text-align: justify; + position: relative; + max-height: calc(1.2em * 2); + overflow: hidden; + padding-right: 1rem; /* space for ellipsis */ +} +.overflow::before { + position: absolute; + content: "..."; +/* inset-block-end: 0; + inset-inline-end: 0; */ + bottom: 0; + right: 0; +} +.overflow::after { + content: ""; + position: absolute; +/* inset-inline-end: 0; */ + right: 0; + width: 1rem; + height: 1rem; + background: white; +} + +button.danger { + background-color: 'red'; }
\ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14219b2f7..ca93291bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -211,6 +211,7 @@ importers: '@gnu-taler/taler-util': workspace:* '@gnu-taler/taler-wallet-core': workspace:* '@rollup/plugin-commonjs': ^17.0.0 + '@rollup/plugin-image': ^2.0.6 '@rollup/plugin-json': ^4.1.0 '@rollup/plugin-node-resolve': ^11.1.0 '@rollup/plugin-replace': ^2.3.4 @@ -253,6 +254,7 @@ importers: '@babel/plugin-transform-react-jsx-source': 7.12.13_@babel+core@7.14.0 '@babel/preset-typescript': 7.13.0_@babel+core@7.14.0 '@rollup/plugin-commonjs': 17.0.0_rollup@2.37.1 + '@rollup/plugin-image': 2.0.6_rollup@2.37.1 '@rollup/plugin-json': 4.1.0_rollup@2.37.1 '@rollup/plugin-node-resolve': 11.1.0_rollup@2.37.1 '@rollup/plugin-replace': 2.3.4_rollup@2.37.1 @@ -3280,6 +3282,17 @@ packages: rollup: 2.37.1 dev: true + /@rollup/plugin-image/2.0.6_rollup@2.37.1: + resolution: {integrity: sha512-bB+spXogbPiFjhBS7i8ajUOgOnVwWK3bnJ6VroxKey/q8/EPRkoSh+4O1qPCw97qMIDspF4TlzXVBhZ7nojIPw==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.37.1 + mini-svg-data-uri: 1.3.3 + rollup: 2.37.1 + dev: true + /@rollup/plugin-json/4.1.0_rollup@2.37.1: resolution: {integrity: sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==} peerDependencies: @@ -12933,6 +12946,11 @@ packages: webpack-sources: 1.4.3 dev: true + /mini-svg-data-uri/1.3.3: + resolution: {integrity: sha512-+fA2oRcR1dJI/7ITmeQJDrYWks0wodlOz0pAEhKYJ2IVc1z0AnwJUsKY2fzFmPAM3Jo9J0rBx8JAA9QQSJ5PuA==} + hasBin: true + dev: true + /minimalistic-assert/1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} dev: true |