diff options
author | Sebastian <sebasjm@gmail.com> | 2021-07-13 15:33:28 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-07-13 15:33:28 -0300 |
commit | 99163fe80d66a947d309842ad9565c6cc0bd2f78 (patch) | |
tree | 4c0191f23e311f5d48ab580f9a3083d98e60b152 | |
parent | a2ff528c66255e8872fc85aadca471335c46ac2b (diff) |
refactored transaction details
5 files changed, 161 insertions, 194 deletions
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index 434e3350a..7d8118392 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -4,7 +4,7 @@ import type * as Linaria from '@linaria/core'; import { styled } from '@linaria/react'; -export const PaymentStatus = styled.span<{color:string}>` +export const PaymentStatus = styled.span<{ color: string }>` padding: 5px; border-radius: 5px; color: white; @@ -136,20 +136,53 @@ export const Centered = styled.div` ` export const Row = styled.div` display: flex; - border: 1px solid gray; - border-radius: 0.5em; margin: 0.5em 0; justify-content: space-between; padding: 0.5em; ` +export const Column = styled.div` + display: flex; + flex-direction: column; + margin: 0em 1em; + justify-content: space-between; +` + +export const RowBorderGray = styled(Row)` + border: 1px solid gray; + border-radius: 0.5em; +` + +export const HistoryRow = styled(RowBorderGray)` + & > ${Column}:last-of-type { + margin-left: auto; + align-self: center; + } +` + +export const ListOfProducts = styled.div` + & > div > a > img { + max-width: 100%; + display: inline-block; + + width: 32px; + height: 32px; + } + & > div > div { + margin-right: auto; + margin-left: 1em; + } +` + export const LightText = styled.div` color: gray; ` export const SmallText = styled.div` font-size: small; - margin-top: 0.5em; +` +export const ExtraLargeText = styled.div` + font-size: x-large; ` export const SmallTextLight = styled(SmallText)` diff --git a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx index c2067ad21..9428922d5 100644 --- a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx @@ -21,7 +21,7 @@ import { differenceInMonths, formatDuration, intervalToDuration } from "date-fns import { FunctionalComponent, Fragment, JSX, VNode, AnyComponent } from "preact"; import { BoldLight, ButtonPrimary, ButtonSuccess, Centered, - CenteredText, CenteredTextBold, PopupBox, Row, + CenteredText, CenteredTextBold, PopupBox, RowBorderGray, SmallText, SmallTextLight } from "../components/styled"; import { useBackupStatus } from "../hooks/useBackupStatus"; @@ -94,12 +94,12 @@ function BackupLayout(props: TransactionLayoutProps): JSX.Element { return ( - <Row> + <RowBorderGray> <div style={{ color: !props.active ? "grey" : undefined }}> <a href={Pages.provider_detail.replace(':pid', encodeURIComponent(props.id))}><span>{props.title}</span></a> - {dateStr && <SmallText>Last synced: {dateStr}</SmallText>} - {!dateStr && <SmallTextLight>Not synced</SmallTextLight>} + {dateStr && <SmallText style={{marginTop: 5}}>Last synced: {dateStr}</SmallText>} + {!dateStr && <SmallTextLight style={{marginTop: 5}}>Not synced</SmallTextLight>} </div> <div> {props.status?.type === 'paid' ? @@ -107,7 +107,7 @@ function BackupLayout(props: TransactionLayoutProps): JSX.Element { <div>{props.status.type}</div> } </div> - </Row> + </RowBorderGray> ); } diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx b/packages/taler-wallet-webextension/src/popup/History.tsx index e76e656c1..57fc10c26 100644 --- a/packages/taler-wallet-webextension/src/popup/History.tsx +++ b/packages/taler-wallet-webextension/src/popup/History.tsx @@ -42,11 +42,13 @@ export function HistoryPage(props: any): JSX.Element { } export function HistoryView({ list }: { list: Transaction[] }) { - return <div style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' }}> - {list.map((tx, i) => ( - <TransactionItem key={i} tx={tx} /> - ))} - </div> + return <PopupBox> + <section> + {list.map((tx, i) => ( + <TransactionItem key={i} tx={tx} /> + ))} + </section> + </PopupBox> } import imageBank from '../../static/img/ri-bank-line.svg'; @@ -54,6 +56,7 @@ 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'; +import { Column, ExtraLargeText, HistoryRow, PopupBox, Row, RowBorderGray, SmallTextLight } from "../components/styled"; function TransactionItem(props: { tx: Transaction }): JSX.Element { const tx = props.tx; @@ -146,37 +149,25 @@ function TransactionLayout(props: TransactionLayoutProps): JSX.Element { timeStyle: "short", } as any); return ( - <div - style={{ - display: "flex", - flexDirection: "row", - border: "1px solid gray", - borderRadius: "0.5em", - margin: "0.5em 0", - justifyContent: "space-between", - padding: "0.5em", - }} - > + <HistoryRow> <img src={props.iconPath} /> - <div - style={{ display: "flex", flexDirection: "column", marginLeft: "1em" }} - > - <div style={{ fontSize: "small", color: "gray" }}>{dateStr}</div> - <div style={{ fontVariant: "small-caps", fontSize: "x-large" }}> + <Column> + <SmallTextLight>{dateStr}</SmallTextLight> + <ExtraLargeText> <a href={Pages.transaction.replace(':tid', props.id)}><span>{props.title}</span></a> {props.pending ? ( <span style={{ color: "darkblue" }}> (Pending)</span> ) : null} - </div> + </ExtraLargeText> <div>{props.subtitle}</div> - </div> + </Column> <TransactionAmount pending={props.pending} amount={props.amount} debitCreditIndicator={props.debitCreditIndicator} /> - </div> + </HistoryRow> ); } @@ -210,24 +201,14 @@ function TransactionAmount(props: TransactionAmountProps): JSX.Element { case "unknown": sign = ""; } - const style: JSX.AllCSSProperties = { - marginLeft: "auto", - display: "flex", - flexDirection: "column", - alignItems: "center", - alignSelf: "center" - }; - if (props.pending) { - style.color = "gray"; - } return ( - <div style={{ ...style }}> - <div style={{ fontSize: "x-large" }}> + <Column style={{ color: props.pending ? "gray" : undefined }}> + <ExtraLargeText> {sign} {amount} - </div> + </ExtraLargeText> <div>{currency}</div> - </div> + </Column> ); } diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx index 00108aac6..3c0bed6c7 100644 --- a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx @@ -123,8 +123,6 @@ function createExample<Props>(Component: FunctionalComponent<Props>, props: Part return r } -export const NotYetLoaded = createExample(TestedComponent, {}); - export const Withdraw = createExample(TestedComponent, { transaction: exampleData.withdraw }); diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.tsx b/packages/taler-wallet-webextension/src/popup/Transaction.tsx index c5274da58..fd7389c04 100644 --- a/packages/taler-wallet-webextension/src/popup/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/popup/Transaction.tsx @@ -16,12 +16,14 @@ import { AmountJson, Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util"; import { format } from "date-fns"; -import { Fragment, JSX } from "preact"; +import { Fragment, JSX, VNode } 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" +import { Button, ButtonDestructive, ButtonPrimary, ListOfProducts, PopupBox, Row, RowBorderGray, SmallTextLight } from "../components/styled"; +import { ErrorMessage } from "../components/ErrorMessage"; export function TransactionPage({ tid }: { tid: string; }): JSX.Element { const [transaction, setTransaction] = useState< @@ -41,6 +43,9 @@ export function TransactionPage({ tid }: { tid: string; }): JSX.Element { fetchData(); }, []); + if (!transaction) { + return <div><i18n.Translate>Loading ...</i18n.Translate></div>; + } return <TransactionView transaction={transaction} onDelete={() => wxApi.deleteTransaction(tid).then(_ => history.go(-1))} @@ -49,65 +54,63 @@ export function TransactionPage({ tid }: { tid: string; }): JSX.Element { } export interface WalletTransactionProps { - transaction?: Transaction, + transaction: Transaction, onDelete: () => void, onRetry: () => void, onBack: () => void, } -export function TransactionView({ transaction, onDelete, onRetry, onBack }: WalletTransactionProps) { - if (!transaction) { - return <div><i18n.Translate>Loading ...</i18n.Translate></div>; - } - - function Footer() { - return <footer style={{ marginTop: 'auto', display: 'flex', flexShrink: 0 }}> - <button class="pure-button" onClick={onBack}><i18n.Translate>back</i18n.Translate></button> - <div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}> - {transaction?.error ? <button class="pure-button button-secondary" style={{marginRight: 5}} onClick={onRetry}><i18n.Translate>retry</i18n.Translate></button> : null } - <button class="pure-button button-destructive" onClick={onDelete}><i18n.Translate>delete</i18n.Translate></button> - </div> - </footer> - } +export function TransactionView({ transaction, onDelete, onRetry, onBack }: WalletTransactionProps) { function Status() { - if (transaction?.error) { + if (transaction.error) { return <span style={{ fontWeight: 'normal', fontSize: 16, color: 'red' }}>(failed)</span> } - if (!transaction?.pending) return null - return <span style={{ fontWeight: 'normal', fontSize: 16, color: 'gray' }}>(pending...)</span> + if (transaction.pending) { + return <span style={{ fontWeight: 'normal', fontSize: 16, color: 'gray' }}>(pending...)</span> + } + return null } - function Error() { - if (!transaction?.error) return null - return <div class="errorbox" > - <p>{transaction.error.hint}</p> - </div> + 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> } - const Fee = ({ value }: { value: AmountJson }) => Amounts.isNonZero(value) ? - <span style="font-size: 16px;font-weight: normal;color: gray;">(fee {Amounts.stringify(value)})</span> : null + function TransactionTemplate({ upperRight, children }: { upperRight: VNode, children: VNode[] }) { + return <PopupBox> + <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} + </section> + <footer> + <Button onClick={onBack}><i18n.Translate> < back</i18n.Translate></Button> + <div> + {transaction?.error ? <ButtonPrimary onClick={onRetry}><i18n.Translate>retry</i18n.Translate></ButtonPrimary> : null} + <ButtonDestructive onClick={onDelete}><i18n.Translate>delete</i18n.Translate></ButtonDestructive> + </div> + </footer> + </PopupBox> + } 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' }} > - <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(320px - 34px - 34px - 16px)', overflow: 'auto' }}> - <span style="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> - <Error /> - <h3>Withdraw <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> - </section> - <Footer /> - </div> - ); + return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.exchangeBaseUrl}</b></Fragment>}> + <h3>Withdraw <Status /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + </TransactionTemplate> } const showLargePic = () => { @@ -120,38 +123,29 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), ).amount - return ( - <div style={{ display: 'flex', flexDirection: 'column' }} > - <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(320px - 34px - 34px - 16px)', 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> - <Error /> - <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> - <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 /> + 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> + <div> + {transaction.info.products && transaction.info.products.length > 0 && + <ListOfProducts> + {transaction.info.products.map(p => <RowBorderGray> + <a href="#" onClick={showLargePic}> + <img src={p.image ? p.image : emptyImg} /> + </a> + <div> + {p.quantity && p.quantity > 0 && <SmallTextLight>x {p.quantity} {p.unit}</SmallTextLight>} + <div>{p.description}</div> + </div> + </RowBorderGray>)} + </ListOfProducts> + } </div> - ); + </TransactionTemplate> } if (transaction.type === TransactionType.Deposit) { @@ -159,20 +153,10 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), ).amount - return ( - <div style={{ display: 'flex', flexDirection: 'column' }} > - <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(320px - 34px - 34px - 16px)', 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> - <Error /> - <h3>Deposit <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> - </section> - <Footer /> - </div> - ); + return <TransactionTemplate upperRight={<Fragment>To <b>{transaction.targetPaytoUri}</b></Fragment>}> + <h3>Deposit <Status /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + </TransactionTemplate> } if (transaction.type === TransactionType.Refresh) { @@ -180,20 +164,10 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), ).amount - return ( - <div style={{ display: 'flex', flexDirection: 'column' }} > - <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(320px - 34px - 34px - 16px)', 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> - <Error /> - <h3>Refresh <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> - </section> - <Footer /> - </div> - ); + return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.exchangeBaseUrl}</b></Fragment>}> + <h3>Refresh <Status /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + </TransactionTemplate> } if (transaction.type === TransactionType.Tip) { @@ -201,20 +175,10 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), ).amount - return ( - <div style={{ display: 'flex', flexDirection: 'column' }} > - <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(320px - 34px - 34px - 16px)', 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> - <Error /> - <h3>Tip <Status /></h3> - <h1>{transaction.amountEffective} <Fee value={fee} /></h1> - </section> - <Footer /> - </div> - ); + return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.merchantBaseUrl}</b></Fragment>}> + <h3>Tip <Status /></h3> + <h1>{transaction.amountEffective} <Fee value={fee} /></h1> + </TransactionTemplate> } if (transaction.type === TransactionType.Refund) { @@ -222,39 +186,30 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), ).amount - return ( - <div style={{ display: 'flex', flexDirection: 'column' }} > - <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(320px - 34px - 34px - 16px)', 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> - <Error /> - <h3>Refund <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> - <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 /> + return <TransactionTemplate upperRight={<Fragment>From <b>{transaction.info.merchant.name}</b></Fragment>}> + <h3>Refund <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> + <div> + {transaction.info.products && transaction.info.products.length > 0 && + <ListOfProducts> + {transaction.info.products.map(p => <RowBorderGray> + <a href="#" onClick={showLargePic}> + <img src={p.image ? p.image : emptyImg} /> + </a> + <div> + {p.quantity && p.quantity > 0 && <SmallTextLight>x {p.quantity} {p.unit}</SmallTextLight>} + <div>{p.description}</div> + </div> + </RowBorderGray>)} + </ListOfProducts> + } </div> - ); + </TransactionTemplate> } |