From 956fc35a20466ac5ae0889c08b05176207c0a54d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 20 Jun 2021 20:37:35 -0300 Subject: wallet transaction detail view --- .../.storybook/preview.js | 5 +- packages/taler-wallet-webextension/package.json | 1 + .../taler-wallet-webextension/rollup.config.js | 2 + packages/taler-wallet-webextension/src/custom.d.ts | 27 ++ .../src/popup/History.stories.tsx | 256 +++++++++++++++++ .../src/popup/History.tsx | 34 ++- .../src/popup/Transaction.stories.tsx | 76 +++-- .../src/popup/Transaction.tsx | 308 ++++++++------------- .../src/popupEntryPoint.tsx | 2 +- .../taler-wallet-webextension/static/img/empty.png | Bin 0 -> 2785 bytes .../static/style/popup.css | 33 ++- pnpm-lock.yaml | 18 ++ 12 files changed, 531 insertions(+), 231 deletions(-) create mode 100644 packages/taler-wallet-webextension/src/custom.d.ts create mode 100644 packages/taler-wallet-webextension/src/popup/History.stories.tsx create mode 100644 packages/taler-wallet-webextension/static/img/empty.png 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 + */ +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 + */ + +/** +* +* @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) =>
+ + + +
+ +
+
+ ], +}; + +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(Component: FunctionalComponent, props: Partial) { + const r = (args: any) => + 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
Loading ...
; } - const txs = [...transactions.transactions].reverse(); + return ; +} - return ( -
- {txs.map((tx, i) => ( - - ))} -
- ); +export function HistoryView({ list }: { list: Transaction[] }) { + return
+ {list.map((tx, i) => ( + + ))} +
} +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} > ); @@ -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} > ); @@ -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} > ); @@ -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} > ); @@ -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} > ); @@ -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} > ); 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 { -
+
@@ -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(Component: FunctionalComponent, 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 */ -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