diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx')
-rw-r--r-- | packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx | 413 |
1 files changed, 413 insertions, 0 deletions
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..e655def39 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx @@ -0,0 +1,413 @@ +/* + 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, + TransactionIdStr, + WalletContractData, +} from "@gnu-taler/taler-util"; +import { WalletApiOperation } 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 { Modal } from "../components/Modal.js"; +import { Time } from "../components/Time.js"; +import { alertFromError, useAlertContext } from "../context/alert.js"; +import { useBackendContext } from "../context/backend.js"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; +import { ButtonHandler } from "../mui/handlers.js"; +import { compose, StateViewMap } from "../utils/index.js"; +import { Amount } from "./Amount.js"; +import { ErrorAlertView } from "./CurrentAlerts.js"; +import { Link } 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; + overflow-wrap: anywhere; + } + & > 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; + +export namespace States { + export interface Loading { + status: "loading"; + hideHandler: ButtonHandler; + } + export interface Error { + status: "error"; + transactionId: string; + error: HookError; + hideHandler: ButtonHandler; + } + export interface Hidden { + status: "hidden"; + showHandler: ButtonHandler; + } + export interface Show { + status: "show"; + hideHandler: ButtonHandler; + contractTerms: WalletContractData; + } +} + +interface Props { + transactionId: TransactionIdStr; +} + +function useComponentState({ transactionId }: Props): State { + const api = useBackendContext(); + const [show, setShow] = useState(false); + const { pushAlertOnError } = useAlertContext(); + const hook = useAsyncAsHook(async () => { + if (!show) return undefined; + return await api.wallet.call(WalletApiOperation.GetContractTermsDetails, { + transactionId, + }); + }, [show]); + + const hideHandler = { + onClick: pushAlertOnError(async () => setShow(false)), + }; + const showHandler = { + onClick: pushAlertOnError(async () => setShow(true)), + }; + if (!show) { + return { + status: "hidden", + showHandler, + }; + } + if (!hook) return { status: "loading", hideHandler }; + if (hook.hasError) + return { status: "error", transactionId, 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), + viewMapping, +); + +export function LoadingView({ hideHandler }: States.Loading): VNode { + return ( + <Modal title="Full detail" onClose={hideHandler}> + <Loading /> + </Modal> + ); +} + +export function ErrorView({ + hideHandler, + error, + transactionId, +}: States.Error): VNode { + const { i18n } = useTranslationContext(); + return ( + <Modal title="Full detail" onClose={hideHandler}> + <ErrorAlertView + error={alertFromError( + i18n, + i18n.str`Could not load purchase proposal details`, + error, + { transactionId }, + )} + /> + </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.fromProtocolTimestamp(contractTerms.timestamp); + const { i18n } = useTranslationContext(); + + return ( + <Modal title="Full detail" onClose={hideHandler}> + <div style={{ overflowY: "auto", height: "95%", padding: 5 }}> + <ContractTermsTable> + <tr> + <td> + <i18n.Translate>Order Id</i18n.Translate> + </td> + <td>{contractTerms.orderId}</td> + </tr> + <tr> + <td> + <i18n.Translate>Summary</i18n.Translate> + </td> + <td>{contractTerms.summary}</td> + </tr> + <tr> + <td> + <i18n.Translate>Amount</i18n.Translate> + </td> + <td> + <Amount value={contractTerms.amount} /> + </td> + </tr> + <tr> + <td> + <i18n.Translate>Merchant name</i18n.Translate> + </td> + <td>{contractTerms.merchant.name}</td> + </tr> + <tr> + <td> + <i18n.Translate>Merchant jurisdiction</i18n.Translate> + </td> + <td>{locationAsText(contractTerms.merchant.jurisdiction)}</td> + </tr> + <tr> + <td> + <i18n.Translate>Merchant address</i18n.Translate> + </td> + <td>{locationAsText(contractTerms.merchant.address)}</td> + </tr> + <tr> + <td> + <i18n.Translate>Merchant logo</i18n.Translate> + </td> + <td> + <div> + <img + src={contractTerms.merchant.logo} + style={{ width: 64, height: 64, margin: 4 }} + /> + </div> + </td> + </tr> + <tr> + <td> + <i18n.Translate>Merchant website</i18n.Translate> + </td> + <td>{contractTerms.merchant.website}</td> + </tr> + <tr> + <td> + <i18n.Translate>Merchant email</i18n.Translate> + </td> + <td>{contractTerms.merchant.email}</td> + </tr> + <tr> + <td> + <i18n.Translate>Merchant public key</i18n.Translate> + </td> + <td> + <span title={contractTerms.merchantPub}> + {contractTerms.merchantPub.substring(0, 6)}... + </span> + </td> + </tr> + {/* <tr> + <td> + <i18n.Translate>Delivery date</i18n.Translate> + </td> + <td> + {contractTerms.deliveryDate && ( + <Time + timestamp={AbsoluteTime.fromProtocolTimestamp( + contractTerms.deliveryDate, + )} + format="dd MMMM yyyy, HH:mm" + /> + )} + </td> + </tr> + <tr> + <td> + <i18n.Translate>Delivery location</i18n.Translate> + </td> + <td>{locationAsText(contractTerms.deliveryLocation)}</td> + </tr> + <tr> + <td> + <i18n.Translate>Products</i18n.Translate> + </td> + <td> + {!contractTerms.products || contractTerms.products.length === 0 + ? "none" + : contractTerms.products + .map((p) => `${p.description} x ${p.quantity}`) + .join(", ")} + </td> + </tr> */} + <tr> + <td> + <i18n.Translate>Created at</i18n.Translate> + </td> + <td> + {contractTerms.timestamp && ( + <Time + timestamp={AbsoluteTime.fromProtocolTimestamp( + contractTerms.timestamp, + )} + format="dd MMMM yyyy, HH:mm" + /> + )} + </td> + </tr> + <tr> + <td> + <i18n.Translate>Refund deadline</i18n.Translate> + </td> + <td> + { + <Time + timestamp={AbsoluteTime.fromProtocolTimestamp( + contractTerms.refundDeadline, + )} + format="dd MMMM yyyy, HH:mm" + /> + } + </td> + </tr> + <tr> + <td> + <i18n.Translate>Auto refund</i18n.Translate> + </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> + <i18n.Translate>Pay deadline</i18n.Translate> + </td> + <td> + { + <Time + timestamp={AbsoluteTime.fromProtocolTimestamp( + contractTerms.payDeadline, + )} + format="dd MMMM yyyy, HH:mm" + /> + } + </td> + </tr> + <tr> + <td> + <i18n.Translate>Fulfillment URL</i18n.Translate> + </td> + <td>{contractTerms.fulfillmentUrl}</td> + </tr> + <tr> + <td> + <i18n.Translate>Fulfillment message</i18n.Translate> + </td> + <td>{contractTerms.fulfillmentMessage}</td> + </tr> + {/* <tr> + <td>Public reorder URL</td> + <td>{contractTerms.public_reorder_url}</td> + </tr> */} + <tr> + <td> + <i18n.Translate>Max deposit fee</i18n.Translate> + </td> + <td> + <Amount value={contractTerms.maxDepositFee} /> + </td> + </tr> + {/* <tr> + <td>Extra</td> + <td> + <pre>{contractTerms.}</pre> + </td> + </tr> */} + <tr> + <td> + <i18n.Translate>Exchanges</i18n.Translate> + </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> + ); +} |