wallet transaction detail view
This commit is contained in:
parent
7f3ce304cb
commit
956fc35a20
@ -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',
|
||||
|
@ -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",
|
||||
|
@ -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(),
|
||||
];
|
||||
|
||||
|
||||
|
27
packages/taler-wallet-webextension/src/custom.d.ts
vendored
Normal file
27
packages/taler-wallet-webextension/src/custom.d.ts
vendored
Normal file
@ -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;
|
||||
}
|
256
packages/taler-wallet-webextension/src/popup/History.stories.tsx
Normal file
256
packages/taler-wallet-webextension/src/popup/History.stories.tsx
Normal file
@ -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,
|
||||
// });
|
@ -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) => (
|
||||
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>
|
||||
);
|
||||
|
@ -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: '',
|
||||
@ -136,6 +136,14 @@ 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 },
|
||||
});
|
||||
@ -145,10 +153,44 @@ export const PaymentWithProducts = createExample(TestedComponent,{
|
||||
...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,
|
||||
|
@ -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) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
|
||||
<section>
|
||||
<h1>Withdrawal <Pending /></h1>
|
||||
<p>
|
||||
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(
|
||||
const fee = 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>
|
||||
return (
|
||||
<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>
|
||||
</span>
|
||||
<h3>Withdraw <Pending /></h3>
|
||||
<h1>{transaction.amountEffective} <Fee value={fee} /></h1>
|
||||
</section>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (transaction.type === TransactionType.Payment) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
|
||||
<section>
|
||||
<h1>Payment ({transaction.proposalId.substring(0, 10)}...) <Pending /></h1>
|
||||
<p>
|
||||
To <b>{transaction.info.merchant.name}</b>
|
||||
</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>
|
||||
const showLargePic = () => {
|
||||
|
||||
}
|
||||
<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(
|
||||
|
||||
if (transaction.type === TransactionType.Payment) {
|
||||
const fee = 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>
|
||||
|
||||
return (
|
||||
<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>
|
||||
<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>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
<Footer />
|
||||
</div>
|
||||
@ -166,36 +140,19 @@ export function TransactionView({ transaction, onDelete, onBack }: WalletTransac
|
||||
}
|
||||
|
||||
if (transaction.type === TransactionType.Deposit) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
|
||||
<section>
|
||||
<h1>Deposit ({transaction.depositGroupId}) <Pending /></h1>
|
||||
<p>
|
||||
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),
|
||||
const fee = 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>
|
||||
return (
|
||||
<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>
|
||||
</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) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
|
||||
<section>
|
||||
<h1>Tip <Pending /></h1>
|
||||
<p>
|
||||
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(
|
||||
const fee = 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>
|
||||
return (
|
||||
<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>
|
||||
</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) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '20rem' }} >
|
||||
<section>
|
||||
<h1>Refund ({TRANSACTION_FROM_REFUND.exec(transaction.refundedTransactionId)![1]}...) <Pending /></h1>
|
||||
<p>
|
||||
From <b>{transaction.info.merchant.name}</b>
|
||||
</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>
|
||||
}
|
||||
<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(
|
||||
const fee = 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>
|
||||
return (
|
||||
<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>
|
||||
<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>
|
||||
}
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<Footer />
|
||||
</div>
|
||||
|
@ -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} />
|
||||
|
BIN
packages/taler-wallet-webextension/static/img/empty.png
Normal file
BIN
packages/taler-wallet-webextension/static/img/empty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
@ -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;
|
||||
@ -260,3 +260,32 @@ 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';
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user