diff options
author | Sebastian <sebasjm@gmail.com> | 2022-08-08 14:09:28 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-08-08 14:09:36 -0300 |
commit | 7a600514c6d43bbaeba6b962533415e59fc46057 (patch) | |
tree | d96c02537cda29f1637787a8fb8e659a37ea8c1f /packages/taler-wallet-webextension/src/components | |
parent | 4409d8384b77401489c2a92d3de20f79959ae34a (diff) | |
download | wallet-core-7a600514c6d43bbaeba6b962533415e59fc46057.tar.xz |
fixing #6096
merchant details and contract terms details factored out, to be used by other components
tests and stories updated
payment completed != confirmed (confirmed if paid by someone else)
Diffstat (limited to 'packages/taler-wallet-webextension/src/components')
5 files changed, 605 insertions, 2 deletions
diff --git a/packages/taler-wallet-webextension/src/components/Modal.tsx b/packages/taler-wallet-webextension/src/components/Modal.tsx new file mode 100644 index 000000000..3fea063d3 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/Modal.tsx @@ -0,0 +1,91 @@ +/* + 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 { styled } from "@linaria/react"; +import { ComponentChildren, h, VNode } from "preact"; +import { ButtonHandler } from "../mui/handlers.js"; +import closeIcon from "../svg/close_24px.svg"; +import { Link, LinkPrimary, LinkWarning } from "./styled/index.js"; + +interface Props { + children: ComponentChildren; + onClose: ButtonHandler; + title: string; +} + +const FullSize = styled.div` + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + z-index: 10; +`; + +const Header = styled.div` + display: flex; + justify-content: space-between; + height: 5%; + vertical-align: center; + align-items: center; +`; + +const Body = styled.div` + height: 95%; +`; + +export function Modal({ title, children, onClose }: Props): VNode { + return ( + <FullSize onClick={onClose?.onClick}> + <div + onClick={(e) => e.stopPropagation()} + style={{ + background: "white", + width: 600, + height: "80%", + margin: "auto", + borderRadius: 8, + padding: 8, + // overflow: "scroll", + }} + > + <Header> + <div> + <h2>{title}</h2> + </div> + <Link onClick={onClose?.onClick}> + <div + style={{ + height: 24, + width: 24, + marginLeft: 4, + marginRight: 4, + // fill: "white", + }} + dangerouslySetInnerHTML={{ __html: closeIcon }} + /> + </Link> + </Header> + <hr /> + + <Body onClick={(e: any) => e.stopPropagation()}>{children}</Body> + </div> + </FullSize> + ); +} diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx new file mode 100644 index 000000000..6f71b9d2e --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx @@ -0,0 +1,116 @@ +/* + 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { WalletContractData } from "@gnu-taler/taler-wallet-core"; +import { createExample } from "../test-utils.js"; +import { + ErrorView, + HiddenView, + LoadingView, + ShowView, +} from "./ShowFullContractTermPopup.js"; + +export default { + title: "component/ShowFullContractTermPopup", +}; + +const cd: WalletContractData = { + amount: { + currency: "ARS", + fraction: 0, + value: 2, + }, + contractTermsHash: + "92X0KSJPZ8XS2XECCGFWTCGW8XMFCXTT2S6WHZDP6H9Y3TSKMTHY94WXEWDERTNN5XWCYGW4VN5CF2D4846HXTW7P06J4CZMHCWKC9G", + fulfillmentUrl: "", + merchantBaseUrl: "https://merchant-backend.taler.ar/", + merchantPub: "JZYHJ13M91GMSQMT75J8Q6ZN0QP8XF8CRHR7K5MMWYE8JQB6AAPG", + merchantSig: + "0YA1WETV15R6K8QKS79QA3QMT16010F42Q49VSKYQ71HVQKAG0A4ZJCA4YTKHE9EA5SP156TJSKZEJJJ87305N6PS80PC48RNKYZE08", + orderId: "2022.220-0281XKKB8W7YE", + summary: "w", + maxWireFee: { + currency: "ARS", + fraction: 0, + value: 1, + }, + payDeadline: { + t_s: 1660002673, + }, + refundDeadline: { + t_s: 1660002673, + }, + wireFeeAmortization: 1, + allowedAuditors: [ + { + auditorBaseUrl: "https://auditor.taler.ar/", + auditorPub: "0000000000000000000000000000000000000000000000000000", + }, + ], + allowedExchanges: [ + { + exchangeBaseUrl: "https://exchange.taler.ar/", + exchangePub: "1C2EYE90PYDNVRTQ25A3PA0KW5W4WPAJNNQHVHV49PT6W5CERFV0", + }, + ], + timestamp: { + t_s: 1659972710, + }, + wireMethod: "x-taler-bank", + wireInfoHash: + "QDT28374ZHYJ59WQFZ3TW1D5WKJVDYHQT86VHED3TNMB15ANJSKXDYPPNX01348KDYCX6T4WXA5A8FJJ8YWNEB1JW726C1JPKHM89DR", + maxDepositFee: { + currency: "ARS", + fraction: 0, + value: 1, + }, + merchant: { + name: "Default", + address: { + country: "ar", + }, + jurisdiction: { + country: "ar", + }, + }, + products: [], + autoRefund: undefined, + summaryI18n: undefined, + deliveryDate: undefined, + deliveryLocation: undefined, +}; + +export const ShowingSimpleOrder = createExample(ShowView, { + contractTerms: cd, +}); +export const Error = createExample(ErrorView, { + proposalId: "asd", + error: { + hasError: true, + message: "message", + operational: false, + // details: { + // code: 123, + // }, + }, +}); +export const Loading = createExample(LoadingView, {}); +export const Hidden = createExample(HiddenView, {}); diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx new file mode 100644 index 000000000..b7d8376bd --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx @@ -0,0 +1,385 @@ +/* + 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 { AbsoluteTime, Duration, Location } from "@gnu-taler/taler-util"; +import { WalletContractData } from "@gnu-taler/taler-wallet-core"; +import { styled } from "@linaria/react"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { Loading } from "../components/Loading.js"; +import { LoadingError } from "../components/LoadingError.js"; +import { Modal } from "../components/Modal.js"; +import { Time } from "../components/Time.js"; +import { useTranslationContext } from "../context/translation.js"; +import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; +import { ButtonHandler } from "../mui/handlers.js"; +import { compose, StateViewMap } from "../utils/index.js"; +import * as wxApi from "../wxApi.js"; +import { Amount } from "./Amount.js"; +import { Link, LinkPrimary } from "./styled/index.js"; + +const ContractTermsTable = styled.table` + width: 100%; + border-spacing: 0px; + & > tr > td { + padding: 5px; + } + & > tr > td:nth-child(2n) { + text-align: right; + } + & > tr:nth-child(2n) { + background: #ebebeb; + } +`; + +function locationAsText(l: Location | undefined): VNode { + if (!l) return <span />; + const lines = [ + ...(l.address_lines || []).map((e) => [e]), + [l.town_location, l.town, l.street], + [l.building_name, l.building_number], + [l.country, l.country_subdivision], + [l.district, l.post_code], + ]; + //remove all missing value + //then remove all empty lines + const curated = lines + .map((l) => l.filter((v) => !!v)) + .filter((l) => l.length > 0); + return ( + <span> + {curated.map((c, i) => ( + <div key={i}>{c.join(",")}</div> + ))} + </span> + ); +} + +type State = States.Loading | States.Error | States.Hidden | States.Show; + +namespace States { + export interface Loading { + status: "loading"; + hideHandler: ButtonHandler; + } + export interface Error { + status: "error"; + proposalId: string; + error: HookError; + hideHandler: ButtonHandler; + } + export interface Hidden { + status: "hidden"; + showHandler: ButtonHandler; + } + export interface Show { + status: "show"; + hideHandler: ButtonHandler; + contractTerms: WalletContractData; + } +} + +interface Props { + proposalId: string; +} + +function useComponentState({ proposalId }: Props, api: typeof wxApi): State { + const [show, setShow] = useState(false); + const hook = useAsyncAsHook(async () => { + if (!show) return undefined; + return await api.getContractTermsDetails(proposalId); + }, [show]); + + const hideHandler = { + onClick: async () => setShow(false), + }; + const showHandler = { + onClick: async () => setShow(true), + }; + if (!show) { + return { + status: "hidden", + showHandler, + }; + } + if (!hook) return { status: "loading", hideHandler }; + if (hook.hasError) + return { status: "error", proposalId, error: hook, hideHandler }; + if (!hook.response) return { status: "loading", hideHandler }; + return { + status: "show", + contractTerms: hook.response, + hideHandler, + }; +} + +const viewMapping: StateViewMap<State> = { + loading: LoadingView, + error: ErrorView, + show: ShowView, + hidden: HiddenView, +}; + +export const ShowFullContractTermPopup = compose( + "ShowFullContractTermPopup", + (p: Props) => useComponentState(p, wxApi), + viewMapping, +); + +export function LoadingView({ hideHandler }: States.Loading): VNode { + return ( + <Modal title="Full detail" onClose={hideHandler}> + <Loading /> + </Modal> + ); +} + +export function ErrorView({ + hideHandler, + error, + proposalId, +}: States.Error): VNode { + const { i18n } = useTranslationContext(); + return ( + <Modal title="Full detail" onClose={hideHandler}> + <LoadingError + title={ + <i18n.Translate> + Could not load purchase proposal details + </i18n.Translate> + } + error={error} + /> + </Modal> + ); +} + +export function HiddenView({ showHandler }: States.Hidden): VNode { + return <Link onClick={showHandler?.onClick}>Show full details</Link>; +} + +export function ShowView({ contractTerms, hideHandler }: States.Show): VNode { + const createdAt = AbsoluteTime.fromTimestamp(contractTerms.timestamp); + + return ( + <Modal title="Full detail" onClose={hideHandler}> + <div style={{ overflowY: "auto", height: "95%", padding: 5 }}> + <ContractTermsTable> + <tr> + <td>Order Id</td> + <td>{contractTerms.orderId}</td> + </tr> + <tr> + <td>Summary</td> + <td>{contractTerms.summary}</td> + </tr> + <tr> + <td>Amount</td> + <td> + <Amount value={contractTerms.amount} /> + </td> + </tr> + <tr> + <td>Merchant name</td> + <td>{contractTerms.merchant.name}</td> + </tr> + <tr> + <td>Merchant jurisdiction</td> + <td>{locationAsText(contractTerms.merchant.jurisdiction)}</td> + </tr> + <tr> + <td>Merchant address</td> + <td>{locationAsText(contractTerms.merchant.address)}</td> + </tr> + <tr> + <td>Merchant logo</td> + <td> + <div> + <img + src={contractTerms.merchant.logo} + style={{ width: 64, height: 64, margin: 4 }} + /> + </div> + </td> + </tr> + <tr> + <td>Merchant website</td> + <td>{contractTerms.merchant.website}</td> + </tr> + <tr> + <td>Merchant email</td> + <td>{contractTerms.merchant.email}</td> + </tr> + <tr> + <td>Merchant public key</td> + <td> + <span title={contractTerms.merchantPub}> + {contractTerms.merchantPub.substring(0, 6)}... + </span> + </td> + </tr> + <tr> + <td>Delivery date</td> + <td> + {contractTerms.deliveryDate && ( + <Time + timestamp={AbsoluteTime.fromTimestamp( + contractTerms.deliveryDate, + )} + format="dd MMMM yyyy, HH:mm" + /> + )} + </td> + </tr> + <tr> + <td>Delivery location</td> + <td>{locationAsText(contractTerms.deliveryLocation)}</td> + </tr> + <tr> + <td>Products</td> + <td> + {!contractTerms.products || contractTerms.products.length === 0 + ? "none" + : contractTerms.products + .map((p) => `${p.description} x ${p.quantity}`) + .join(", ")} + </td> + </tr> + <tr> + <td>Created at</td> + <td> + {contractTerms.timestamp && ( + <Time + timestamp={AbsoluteTime.fromTimestamp( + contractTerms.timestamp, + )} + format="dd MMMM yyyy, HH:mm" + /> + )} + </td> + </tr> + <tr> + <td>Refund deadline</td> + <td> + { + <Time + timestamp={AbsoluteTime.fromTimestamp( + contractTerms.refundDeadline, + )} + format="dd MMMM yyyy, HH:mm" + /> + } + </td> + </tr> + <tr> + <td>Auto refund</td> + <td> + { + <Time + timestamp={AbsoluteTime.addDuration( + createdAt, + !contractTerms.autoRefund + ? Duration.getZero() + : Duration.fromTalerProtocolDuration( + contractTerms.autoRefund, + ), + )} + format="dd MMMM yyyy, HH:mm" + /> + } + </td> + </tr> + <tr> + <td>Pay deadline</td> + <td> + { + <Time + timestamp={AbsoluteTime.fromTimestamp( + contractTerms.payDeadline, + )} + format="dd MMMM yyyy, HH:mm" + /> + } + </td> + </tr> + <tr> + <td>Fulfillment URL</td> + <td>{contractTerms.fulfillmentUrl}</td> + </tr> + <tr> + <td>Fulfillment message</td> + <td>{contractTerms.fulfillmentMessage}</td> + </tr> + {/* <tr> + <td>Public reorder URL</td> + <td>{contractTerms.public_reorder_url}</td> + </tr> */} + <tr> + <td>Max deposit fee</td> + <td> + <Amount value={contractTerms.maxDepositFee} /> + </td> + </tr> + <tr> + <td>Max fee</td> + <td> + <Amount value={contractTerms.maxWireFee} /> + </td> + </tr> + <tr> + <td>Minimum age</td> + <td>{contractTerms.minimumAge}</td> + </tr> + {/* <tr> + <td>Extra</td> + <td> + <pre>{contractTerms.}</pre> + </td> + </tr> */} + <tr> + <td>Wire fee amortization</td> + <td>{contractTerms.wireFeeAmortization}</td> + </tr> + <tr> + <td>Auditors</td> + <td> + {(contractTerms.allowedAuditors || []).map((e) => ( + <Fragment key={e.auditorPub}> + <a href={e.auditorBaseUrl} title={e.auditorPub}> + {e.auditorPub.substring(0, 6)}... + </a> + + </Fragment> + ))} + </td> + </tr> + <tr> + <td>Exchanges</td> + <td> + {(contractTerms.allowedExchanges || []).map((e) => ( + <Fragment key={e.exchangePub}> + <a href={e.exchangeBaseUrl} title={e.exchangePub}> + {e.exchangePub.substring(0, 6)}... + </a> + + </Fragment> + ))} + </td> + </tr> + </ContractTermsTable> + </div> + </Modal> + ); +} diff --git a/packages/taler-wallet-webextension/src/components/index.stories.tsx b/packages/taler-wallet-webextension/src/components/index.stories.tsx index 053b27f79..901347e4f 100644 --- a/packages/taler-wallet-webextension/src/components/index.stories.tsx +++ b/packages/taler-wallet-webextension/src/components/index.stories.tsx @@ -19,8 +19,9 @@ * @author Sebastian Javier Marchano (sebasjm) */ - import * as a1 from "./Banner.stories.js"; +import * as a1 from "./Banner.stories.js"; import * as a2 from "./PendingTransactions.stories.js"; import * as a3 from "./Amount.stories.js"; +import * as a4 from "./ShowFullContractTermPopup.stories.js"; -export default [a1, a2, a3]; +export default [a1, a2, a3, a4]; diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index 928562fb6..ff4a5b4d5 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -40,8 +40,18 @@ export const WalletAction = styled.div` & h1:first-child { margin-top: 0; } + & > * { + width: 600px; + } section { margin-bottom: 2em; + table td { + padding: 5px 5px; + } + table tr { + border-bottom: 1px solid black; + border-top: 1px solid black; + } button { margin-right: 8px; margin-left: 8px; |