diff options
author | Sebastian <sebasjm@gmail.com> | 2021-08-24 15:16:11 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-08-24 15:16:33 -0300 |
commit | e22bdd52f7dc878738d1b1306a15ae0f573c30a2 (patch) | |
tree | fa436f991ea5bf88ff973007d300aea39a5efac6 | |
parent | 0bc235c64b6936aa092a2df40e0c4909e4ac05d5 (diff) |
transaction details new ui
7 files changed, 135 insertions, 57 deletions
diff --git a/packages/taler-util/src/amounts.ts b/packages/taler-util/src/amounts.ts index e472de503..f0434be0e 100644 --- a/packages/taler-util/src/amounts.ts +++ b/packages/taler-util/src/amounts.ts @@ -402,6 +402,12 @@ export class Amounts { */ static stringify(a: AmountLike): string { a = Amounts.jsonifyAmount(a); + const s = this.stringifyValue(a) + + return `${a.currency}:${s}`; + } + + static stringifyValue(a: AmountJson): string { const av = a.value + Math.floor(a.fraction / amountFractionalBase); const af = a.fraction % amountFractionalBase; let s = av.toString(); @@ -417,7 +423,6 @@ export class Amounts { n = (n * 10) % amountFractionalBase; } } - - return `${a.currency}:${s}`; + return s } } diff --git a/packages/taler-wallet-webextension/.storybook/preview.js b/packages/taler-wallet-webextension/.storybook/preview.js index 920e6b1ca..488663469 100644 --- a/packages/taler-wallet-webextension/.storybook/preview.js +++ b/packages/taler-wallet-webextension/.storybook/preview.js @@ -158,8 +158,6 @@ export const decorators = [ </style> <LogoHeader /> <NavBar path={path} devMode={path === '/dev'} /> - <link key="1" rel="stylesheet" type="text/css" href="/static/style/pure.css" /> - <link key="2" rel="stylesheet" type="text/css" href="/static/style/wallet.css" /> <Story /> </div> } diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index 66595d84c..8f795ce83 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -85,6 +85,7 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>` flex-direction: row; justify-content: space-between; display: flex; + background-color: #f7f7f7; & button { margin-right: 8px; margin-left: 8px; @@ -199,6 +200,33 @@ export const Button = styled.button` } `; +export const FontIcon = styled.div` + font-family: monospace; + font-size: x-large; + text-align: center; + font-weight: bold; + /* vertical-align: text-top; */ +` +export const ButtonBox = styled(Button)` + padding: .5em; + width: 2em; + height: 2em; + + & > ${FontIcon} { + width: 1em; + height: 1em; + display: inline; + line-height: 0px; + } + background-color: transparent; + + border: 1px solid; + border-radius: 4px; + border-color: black; + color: black; +` + + const ButtonVariant = styled(Button)` color: white; border-radius: 4px; @@ -208,18 +236,35 @@ const ButtonVariant = styled(Button)` export const ButtonPrimary = styled(ButtonVariant)` background-color: rgb(66, 184, 221); ` +export const ButtonBoxPrimary = styled(ButtonBox)` + color: rgb(66, 184, 221); + border-color: rgb(66, 184, 221); +` export const ButtonSuccess = styled(ButtonVariant)` background-color: rgb(28, 184, 65); ` +export const ButtonBoxSuccess = styled(ButtonBox)` + color: rgb(28, 184, 65); + border-color: rgb(28, 184, 65); +` export const ButtonWarning = styled(ButtonVariant)` background-color: rgb(223, 117, 20); ` +export const ButtonBoxWarning = styled(ButtonBox)` + color: rgb(223, 117, 20); + border-color: rgb(223, 117, 20); +` export const ButtonDestructive = styled(ButtonVariant)` background-color: rgb(202, 60, 60); ` +export const ButtonBoxDestructive = styled(ButtonBox)` + color: rgb(202, 60, 60); + border-color: rgb(202, 60, 60); +` + export const BoldLight = styled.div` color: gray; @@ -336,6 +381,7 @@ export const CenteredTextBold = styled(CenteredText)` font-weight: bold; color: ${((props: any): any => String(props.color) as any) as any}; ` + export const Input = styled.div<{ invalid?: boolean }>` & label { display: block; @@ -359,7 +405,7 @@ export const ErrorBox = styled.div` /* margin: 0.5em; */ padding-left: 1em; padding-right: 1em; - width: 100%; + /* width: 100%; */ color: #721c24; background: #f8d7da; diff --git a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx index bfc32a8f3..83d94ac0f 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx @@ -58,9 +58,7 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged return ( <PopupBox> - {info.backupProblem || info.lastError ? <header> - <Error info={info} /> - </header> : undefined } + <Error info={info} /> <header> <h3>{info.name} <SmallTextLight>{info.syncProviderBaseUrl}</SmallTextLight></h3> <PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid' : 'Unpaid'}</PaymentStatus> diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx index fc361f625..871e30b71 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx @@ -58,9 +58,7 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged return ( <WalletBox> - {info.backupProblem || info.lastError ? <header> - <Error info={info} /> - </header> : undefined } + <Error info={info} /> <header> <h3>{info.name} <SmallTextLight>{info.syncProviderBaseUrl}</SmallTextLight></h3> <PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid' : 'Unpaid'}</PaymentStatus> diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx index 0f7ea457d..535509cef 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx @@ -40,8 +40,8 @@ export default { }; const commonTransaction = { - amountRaw: 'USD:10', - amountEffective: 'USD:9', + amountRaw: 'KUDOS:11', + amountEffective: 'KUDOS:9.2', pending: false, timestamp: { t_ms: new Date().getTime() @@ -62,7 +62,7 @@ const exampleData = { } as TransactionWithdrawal, payment: { ...commonTransaction, - amountEffective: 'USD:11', + amountEffective: 'KUDOS:11', type: TransactionType.Payment, info: { contractTermsHash: 'ASDZXCASD', @@ -147,7 +147,7 @@ export const PaymentError = createExample(TestedComponent, { export const PaymentWithoutFee = createExample(TestedComponent, { transaction: { ...exampleData.payment, - amountRaw: 'USD:11', + amountRaw: 'KUDOS:11', } }); diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index d00abc16a..ad00b3d1b 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson, Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util"; +import { AmountJson, AmountLike, Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util"; import { format } from "date-fns"; import { Fragment, JSX, VNode, h } from "preact"; import { route } from 'preact-router'; @@ -22,7 +22,7 @@ import { useEffect, useState } from "preact/hooks"; import * as wxApi from "../wxApi"; import { Pages } from "../NavigationBar"; import emptyImg from "../../static/img/empty.png" -import { Button, ButtonDestructive, ButtonPrimary, ListOfProducts, PopupBox, Row, RowBorderGray, SmallTextLight, WalletBox } from "../components/styled"; +import { Button, ButtonBox, ButtonBoxDestructive, ButtonDestructive, ButtonPrimary, ExtraLargeText, FontIcon, LargeText, ListOfProducts, PopupBox, Row, RowBorderGray, SmallTextLight, WalletBox } from "../components/styled"; import { ErrorMessage } from "../components/ErrorMessage"; export function TransactionPage({ tid }: { tid: string; }): JSX.Element { @@ -73,43 +73,54 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall return null } - function Fee({ value }: { value: AmountJson }) { - if (Amounts.isZero(value)) return null - return <span style="font-size: 16px;font-weight: normal;color: gray;">(fee {Amounts.stringify(value)})</span> - } - - function TransactionTemplate({ upperRight, children }: { upperRight: VNode, children: VNode[] }) { + function TransactionTemplate({ children }: { children: VNode[] }) { return <WalletBox> - <header> - <SmallTextLight> - {transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')} - </SmallTextLight> - <SmallTextLight> - {upperRight} - </SmallTextLight> - </header> <section> <ErrorMessage title={transaction?.error?.hint} /> - {children} + <div style={{ textAlign: 'center' }}> + {children} + </div> </section> <footer> - <Button onClick={onBack}><i18n.Translate> < back</i18n.Translate></Button> + <ButtonBox onClick={onBack}><i18n.Translate> <FontIcon>←</FontIcon> </i18n.Translate></ButtonBox> <div> {transaction?.error ? <ButtonPrimary onClick={onRetry}><i18n.Translate>retry</i18n.Translate></ButtonPrimary> : null} - <ButtonDestructive onClick={onDelete}><i18n.Translate>delete</i18n.Translate></ButtonDestructive> + <ButtonBoxDestructive onClick={onDelete}><i18n.Translate>🗑</i18n.Translate></ButtonBoxDestructive> </div> </footer> </WalletBox> } + type Kind = 'positive' | 'negative' | 'neutral'; + function Part({ text, title, kind, big }: { title: string, text: AmountLike, kind: Kind, big?: boolean }) { + const Text = big ? ExtraLargeText : LargeText; + return <div style={{ margin: '1em' }}> + <SmallTextLight style={{ margin: '.5em' }}>{title}</SmallTextLight> + <Text style={{ color: kind == 'positive' ? 'green' : (kind == 'negative' ? 'red' : 'black') }}> + {text} + </Text> + </div> + } + + function amountToString(text: AmountLike) { + const aj = Amounts.jsonifyAmount(text) + const amount = Amounts.stringifyValue(aj) + return `${amount} ${aj.currency}` + } + if (transaction.type === TransactionType.Withdrawal) { const fee = Amounts.sub( Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), ).amount - return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.exchangeBaseUrl}</b></Fragment>}> - <h3>Withdraw <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + return <TransactionTemplate> + <h2>Withdrawal <Status /></h2> + <div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> + <br /> + <Part title="Total withdrawn" text={amountToString(transaction.amountEffective)} kind='positive' /> + <Part title="Chosen amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> + <Part title="Exchange fee" text={amountToString(fee)} kind='negative' /> + <Part title="Exchange" text={new URL(transaction.exchangeBaseUrl).hostname} kind='neutral' /> </TransactionTemplate> } @@ -123,13 +134,17 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), ).amount - return <TransactionTemplate upperRight={<Fragment>To <b>{transaction.info.merchant.name}</b></Fragment>}> - <h3>Payment <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> - <span style="font-size:small; color:gray">#{transaction.info.orderId}</span> - <p> - {transaction.info.summary} - </p> + return <TransactionTemplate> + <h2>Payment <Status /></h2> + <div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> + <br /> + <Part big title="Total paid" text={amountToString(transaction.amountEffective)} kind='negative' /> + <Part big title="Purchase amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> + <Part big title="Fee" text={amountToString(fee)} kind='negative' /> + <Part title="Merchant" text={transaction.info.merchant.name} kind='neutral' /> + <Part title="Purchase" text={transaction.info.summary} kind='neutral' /> + <Part title="Receipt" text={`#${transaction.info.orderId}`} kind='neutral' /> + <div> {transaction.info.products && transaction.info.products.length > 0 && <ListOfProducts> @@ -153,9 +168,13 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), ).amount - return <TransactionTemplate upperRight={<Fragment>To <b>{transaction.targetPaytoUri}</b></Fragment>}> - <h3>Deposit <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + return <TransactionTemplate> + <h2>Deposit <Status /></h2> + <div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> + <br /> + <Part big title="Total deposit" text={amountToString(transaction.amountEffective)} kind='negative' /> + <Part big title="Purchase amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> + <Part big title="Fee" text={amountToString(fee)} kind='negative' /> </TransactionTemplate> } @@ -164,9 +183,13 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), ).amount - return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.exchangeBaseUrl}</b></Fragment>}> - <h3>Refresh <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + return <TransactionTemplate> + <h2>Refresh <Status /></h2> + <div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> + <br /> + <Part big title="Total refresh" text={amountToString(transaction.amountEffective)} kind='negative' /> + <Part big title="Refresh amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> + <Part big title="Fee" text={amountToString(fee)} kind='negative' /> </TransactionTemplate> } @@ -175,9 +198,13 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), ).amount - return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.merchantBaseUrl}</b></Fragment>}> - <h3>Tip <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + return <TransactionTemplate> + <h2>Tip <Status /></h2> + <div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> + <br /> + <Part big title="Total tip" text={amountToString(transaction.amountEffective)} kind='positive' /> + <Part big title="Received amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> + <Part big title="Fee" text={amountToString(fee)} kind='negative' /> </TransactionTemplate> } @@ -186,11 +213,17 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), ).amount - return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.info.merchant.name}</b></Fragment>}> - <h3>Refund <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + return <TransactionTemplate> + <h2>Refund <Status /></h2> + <div>{transaction.timestamp.t_ms === 'never' ? 'never': format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> + <br /> + <Part big title="Total refund" text={amountToString(transaction.amountEffective)} kind='positive' /> + <Part big title="Refund amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> + <Part big title="Fee" text={amountToString(fee)} kind='negative' /> + <Part title="Merchant" text={transaction.info.merchant.name} kind='neutral' /> + <Part title="Purchase" text={transaction.info.summary} kind='neutral' /> + <Part title="Receipt" text={`#${transaction.info.orderId}`} kind='neutral' /> - <span style="font-size:small; color:gray">#{transaction.info.orderId}</span> <p> {transaction.info.summary} </p> |