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 /packages/taler-wallet-webextension/src | |
| parent | a2ff528c66255e8872fc85aadca471335c46ac2b (diff) | |
refactored transaction details
Diffstat (limited to 'packages/taler-wallet-webextension/src')
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>    }  | 
