diff options
author | Sebastian <sebasjm@gmail.com> | 2023-01-04 11:24:58 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-01-04 11:38:00 -0300 |
commit | 24cac493dded00ef40e0e30a0d2263e4f35c3e29 (patch) | |
tree | 1bbd1fb4f9149af349358491c3720750d031d255 /packages/taler-wallet-webextension/src/components | |
parent | 7d02e4212346b7b7b88197259a7e74554e1b10a3 (diff) | |
download | wallet-core-24cac493dded00ef40e0e30a0d2263e4f35c3e29.tar.xz |
fix #7522
Diffstat (limited to 'packages/taler-wallet-webextension/src/components')
6 files changed, 359 insertions, 103 deletions
diff --git a/packages/taler-wallet-webextension/src/components/Banner.stories.tsx b/packages/taler-wallet-webextension/src/components/Banner.stories.tsx index 39012480b..60b100478 100644 --- a/packages/taler-wallet-webextension/src/components/Banner.stories.tsx +++ b/packages/taler-wallet-webextension/src/components/Banner.stories.tsx @@ -65,23 +65,25 @@ export const BasicExample = (): VNode => ( </a> </p> <Banner - elements={[ - { - icon: <SignalWifiOffIcon color="gray" />, - description: ( - <Typography> - You have lost connection to the internet. This app is offline. - </Typography> - ), - }, - ]} + // elements={[ + // { + // icon: <SignalWifiOffIcon color="gray" />, + // description: ( + // <Typography> + // You have lost connection to the internet. This app is offline. + // </Typography> + // ), + // }, + // ]} confirm={{ label: "turn on wifi", action: async () => { return; }, }} - /> + > + <div /> + </Banner> </Wrapper> </Fragment> ); @@ -92,31 +94,33 @@ export const PendingOperation = (): VNode => ( <Banner title="PENDING TRANSACTIONS" style={{ backgroundColor: "lightcyan", padding: 8 }} - elements={[ - { - icon: ( - <Avatar - style={{ - border: "solid blue 1px", - color: "blue", - boxSizing: "border-box", - }} - > - P - </Avatar> - ), - description: ( - <Fragment> - <Typography inline bold> - EUR 37.95 - </Typography> - - <Typography inline>- 5 feb 2022</Typography> - </Fragment> - ), - }, - ]} - /> + // elements={[ + // { + // icon: ( + // <Avatar + // style={{ + // border: "solid blue 1px", + // color: "blue", + // boxSizing: "border-box", + // }} + // > + // P + // </Avatar> + // ), + // description: ( + // <Fragment> + // <Typography inline bold> + // EUR 37.95 + // </Typography> + // + // <Typography inline>- 5 feb 2022</Typography> + // </Fragment> + // ), + // }, + // ]} + > + asd + </Banner> </Wrapper> </Fragment> ); diff --git a/packages/taler-wallet-webextension/src/components/Banner.tsx b/packages/taler-wallet-webextension/src/components/Banner.tsx index f95647d42..a91fd384f 100644 --- a/packages/taler-wallet-webextension/src/components/Banner.tsx +++ b/packages/taler-wallet-webextension/src/components/Banner.tsx @@ -13,21 +13,20 @@ 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/> */ -import { h, Fragment, VNode, JSX } from "preact"; -import { Divider } from "../mui/Divider.js"; +import { ComponentChildren, Fragment, h, JSX, VNode } from "preact"; import { Button } from "../mui/Button.js"; -import { Typography } from "../mui/Typography.js"; -import { Avatar } from "../mui/Avatar.js"; +import { Divider } from "../mui/Divider.js"; import { Grid } from "../mui/Grid.js"; import { Paper } from "../mui/Paper.js"; interface Props extends JSX.HTMLAttributes<HTMLDivElement> { titleHead?: VNode; - elements: { - icon?: VNode; - description: VNode; - action?: () => void; - }[]; + children: ComponentChildren; + // elements: { + // icon?: VNode; + // description: VNode; + // action?: () => void; + // }[]; confirm?: { label: string; action: () => Promise<void>; @@ -36,8 +35,9 @@ interface Props extends JSX.HTMLAttributes<HTMLDivElement> { export function Banner({ titleHead, - elements, + children, confirm, + href, ...rest }: Props): VNode { return ( @@ -49,25 +49,7 @@ export function Banner({ </Grid> )} <Grid container columns={1}> - {elements.map((e, i) => ( - <Grid - container - item - xs={1} - key={i} - wrap="nowrap" - spacing={1} - alignItems="center" - onClick={e.action} - > - {e.icon && ( - <Grid item xs={"auto"}> - <Avatar>{e.icon}</Avatar> - </Grid> - )} - <Grid item>{e.description}</Grid> - </Grid> - ))} + {children} </Grid> {confirm && ( <Grid container justifyContent="flex-end" spacing={8}> diff --git a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx new file mode 100644 index 000000000..def1e16eb --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx @@ -0,0 +1,153 @@ +/* + This file is part of GNU Taler + (C) 2022 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/> + */ + +import { + AmountJson, + Amounts, + PreparePayResult, + PreparePayResultType, +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { Amount } from "./Amount.js"; +import { Part } from "./Part.js"; +import { QR } from "./QR.js"; +import { LinkSuccess, WarningBox } from "./styled/index.js"; +import { useTranslationContext } from "../context/translation.js"; +import { Button } from "../mui/Button.js"; +import { ButtonHandler } from "../mui/handlers.js"; +import { assertUnreachable } from "../utils/index.js"; + +interface Props { + payStatus: PreparePayResult; + payHandler: ButtonHandler | undefined; + balance: AmountJson | undefined; + uri: string; + amount: AmountJson; + goToWalletManualWithdraw: (currency: string) => Promise<void>; +} + +export function PaymentButtons({ + payStatus, + uri, + payHandler, + balance, + amount, + goToWalletManualWithdraw, +}: Props): VNode { + const { i18n } = useTranslationContext(); + if (payStatus.status === PreparePayResultType.PaymentPossible) { + const privateUri = `${uri}&n=${payStatus.noncePriv}`; + + return ( + <Fragment> + <section> + <Button + variant="contained" + color="success" + onClick={payHandler?.onClick} + > + <i18n.Translate> + Pay + {<Amount value={amount} />} + </i18n.Translate> + </Button> + </section> + <PayWithMobile uri={privateUri} /> + </Fragment> + ); + } + + if (payStatus.status === PreparePayResultType.InsufficientBalance) { + let BalanceMessage = ""; + if (!balance) { + BalanceMessage = i18n.str`You have no balance for this currency. Withdraw digital cash first.`; + } else { + const balanceShouldBeEnough = Amounts.cmp(balance, amount) !== -1; + if (balanceShouldBeEnough) { + BalanceMessage = i18n.str`Could not find enough coins to pay. Even if you have enough ${balance.currency} some restriction may apply.`; + } else { + BalanceMessage = i18n.str`Your current balance is not enough.`; + } + } + const uriPrivate = `${uri}&n=${payStatus.noncePriv}`; + + return ( + <Fragment> + <section> + <WarningBox>{BalanceMessage}</WarningBox> + </section> + <section> + <Button + variant="contained" + color="success" + onClick={() => goToWalletManualWithdraw(Amounts.stringify(amount))} + > + <i18n.Translate>Get digital cash</i18n.Translate> + </Button> + </section> + <PayWithMobile uri={uriPrivate} /> + </Fragment> + ); + } + if (payStatus.status === PreparePayResultType.AlreadyConfirmed) { + return ( + <Fragment> + <section> + {payStatus.paid && payStatus.contractTerms.fulfillment_message && ( + <Part + title={<i18n.Translate>Merchant message</i18n.Translate>} + text={payStatus.contractTerms.fulfillment_message} + kind="neutral" + /> + )} + </section> + {!payStatus.paid && <PayWithMobile uri={uri} />} + </Fragment> + ); + } + + assertUnreachable(payStatus); +} + +function PayWithMobile({ uri }: { uri: string }): VNode { + const { i18n } = useTranslationContext(); + + const [showQR, setShowQR] = useState<boolean>(false); + + return ( + <section> + <LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}> + {!showQR ? ( + <i18n.Translate>Pay with a mobile phone</i18n.Translate> + ) : ( + <i18n.Translate>Hide QR</i18n.Translate> + )} + </LinkSuccess> + {showQR && ( + <div> + <QR text={uri} /> + <i18n.Translate> + Scan the QR code or + <a href={uri}> + <i18n.Translate>click here</i18n.Translate> + </a> + </i18n.Translate> + </div> + )} + </section> + ); +} diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx index 85b43fb4e..e41ff2836 100644 --- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx +++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx @@ -26,6 +26,7 @@ import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Avatar } from "../mui/Avatar.js"; +import { Grid } from "../mui/Grid.js"; import { Typography } from "../mui/Typography.js"; import Banner from "./Banner.js"; import { Time } from "./Time.js"; @@ -34,6 +35,11 @@ interface Props extends JSX.HTMLAttributes { goToTransaction: (id: string) => Promise<void>; } +/** + * this cache will save the tx from the previous render + */ +const cache = { tx: [] as Transaction[] }; + export function PendingTransactions({ goToTransaction }: Props): VNode { const api = useBackendContext(); const state = useAsyncAsHook(() => @@ -49,12 +55,13 @@ export function PendingTransactions({ goToTransaction }: Props): VNode { const transactions = !state || state.hasError - ? [] + ? cache.tx : state.response.transactions.filter((t) => t.pending); - if (!state || state.hasError || !transactions.length) { + if (!transactions.length) { return <Fragment />; } + cache.tx = transactions; return ( <PendingTransactionsView goToTransaction={goToTransaction} @@ -72,46 +79,67 @@ export function PendingTransactionsView({ }): VNode { const { i18n } = useTranslationContext(); return ( - <Banner - titleHead={<i18n.Translate>PENDING OPERATIONS</i18n.Translate>} + <div style={{ backgroundColor: "lightcyan", - maxHeight: 150, - padding: 8, - flexGrow: 1, - maxWidth: 500, - overflowY: transactions.length > 3 ? "scroll" : "hidden", + display: "flex", + justifyContent: "center", }} - elements={transactions.map((t) => { - const amount = Amounts.parseOrThrow(t.amountEffective); - return { - icon: ( - <Avatar - style={{ - border: "solid blue 1px", - color: "blue", - boxSizing: "border-box", + > + <Banner + titleHead={<i18n.Translate>PENDING OPERATIONS</i18n.Translate>} + style={{ + backgroundColor: "lightcyan", + maxHeight: 150, + padding: 8, + flexGrow: 1, + maxWidth: 500, + overflowY: transactions.length > 3 ? "scroll" : "hidden", + }} + > + {transactions.map((t, i) => { + const amount = Amounts.parseOrThrow(t.amountEffective); + return ( + <Grid + container + item + xs={1} + key={i} + wrap="nowrap" + role="button" + spacing={1} + alignItems="center" + onClick={() => { + goToTransaction(t.transactionId); }} > - {t.type.substring(0, 1)} - </Avatar> - ), - action: () => goToTransaction(t.transactionId), - description: ( - <Fragment> - <Typography inline bold> - {amount.currency} {Amounts.stringifyValue(amount)} - </Typography> - - - <Time - timestamp={AbsoluteTime.fromTimestamp(t.timestamp)} - format="dd MMMM yyyy" - /> - </Fragment> - ), - }; - })} - /> + <Grid item xs={"auto"}> + <Avatar + style={{ + border: "solid blue 1px", + color: "blue", + boxSizing: "border-box", + }} + > + {t.type.substring(0, 1)} + </Avatar> + </Grid> + + <Grid item> + <Typography inline bold> + {amount.currency} {Amounts.stringifyValue(amount)} + </Typography> + - + <Time + timestamp={AbsoluteTime.fromTimestamp(t.timestamp)} + format="dd MMMM yyyy" + /> + </Grid> + </Grid> + ); + })} + </Banner> + </div> ); } diff --git a/packages/taler-wallet-webextension/src/components/ProductList.tsx b/packages/taler-wallet-webextension/src/components/ProductList.tsx new file mode 100644 index 000000000..a78733179 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/ProductList.tsx @@ -0,0 +1,89 @@ +/* + This file is part of GNU Taler + (C) 2022 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/> + */ + +import { Amounts, Product } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { SmallLightText } from "./styled/index.js"; +import { useTranslationContext } from "../context/translation.js"; + +export function ProductList({ products }: { products: Product[] }): VNode { + const { i18n } = useTranslationContext(); + return ( + <Fragment> + <SmallLightText style={{ margin: ".5em" }}> + <i18n.Translate>List of products</i18n.Translate> + </SmallLightText> + <dl> + {products.map((p, i) => { + if (p.price) { + const pPrice = Amounts.parseOrThrow(p.price); + return ( + <div key={i} style={{ display: "flex", textAlign: "left" }}> + <div> + <img + src={p.image ? p.image : undefined} + style={{ width: 32, height: 32 }} + /> + </div> + <div> + <dt> + {p.quantity ?? 1} x {p.description}{" "} + <span style={{ color: "gray" }}> + {Amounts.stringify(pPrice)} + </span> + </dt> + <dd> + <b> + {Amounts.stringify( + Amounts.mult(pPrice, p.quantity ?? 1).amount, + )} + </b> + </dd> + </div> + </div> + ); + } + return ( + <div key={i} style={{ display: "flex", textAlign: "left" }}> + <div> + <img src={p.image} style={{ width: 32, height: 32 }} /> + </div> + <div> + <dt> + {p.quantity ?? 1} x {p.description} + </dt> + <dd> + <i18n.Translate>Total</i18n.Translate> + {` `} + {p.price ? ( + `${Amounts.stringifyValue( + Amounts.mult( + Amounts.parseOrThrow(p.price), + p.quantity ?? 1, + ).amount, + )} ${p}` + ) : ( + <i18n.Translate>free</i18n.Translate> + )} + </dd> + </div> + </div> + ); + })} + </dl> + </Fragment> + ); +} diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index 7a3c27c73..8e98f75eb 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -159,7 +159,7 @@ export const Middle = styled.div` height: 100%; `; -export const PopupBox = styled.div<{ noPadding?: boolean; devMode?: boolean }>` +export const PopupBox = styled.div<{ noPadding?: boolean }>` height: 290px; width: 500px; overflow-y: visible; |