aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx')
-rw-r--r--packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx385
1 files changed, 385 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..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>
+ &nbsp;
+ </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>
+ &nbsp;
+ </Fragment>
+ ))}
+ </td>
+ </tr>
+ </ContractTermsTable>
+ </div>
+ </Modal>
+ );
+}