/* This file is part of TALER (C) 2016 INRIA 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. 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 TALER; see the file COPYING. If not, see */ /** * Helpers functions to render Taler-related data structures to HTML. * * @author Florian Dold */ /** * Imports. */ import { AmountJson } from "../util/amounts"; import * as Amounts from "../util/amounts"; import { DenominationRecord } from "../types/dbTypes"; import { ExchangeWithdrawDetails } from "../types/walletTypes"; import * as i18n from "./i18n"; import React from "react"; import { stringifyTimestamp } from "../util/time"; /** * Render amount as HTML, which non-breaking space between * decimal value and currency. */ export function renderAmount(amount: AmountJson | string): JSX.Element { let a; if (typeof amount === "string") { a = Amounts.parse(amount); } else { a = amount; } if (!a) { return (invalid amount); } const x = a.value + a.fraction / Amounts.fractionalBase; return ( {x} {a.currency} ); } export const AmountView = ({ amount, }: { amount: AmountJson | string; }): JSX.Element => renderAmount(amount); /** * Abbreviate a string to a given length, and show the full * string on hover as a tooltip. */ export function abbrev(s: string, n = 5): JSX.Element { let sAbbrev = s; if (s.length > n) { sAbbrev = s.slice(0, n) + ".."; } return ( {sAbbrev} ); } interface CollapsibleState { collapsed: boolean; } interface CollapsibleProps { initiallyCollapsed: boolean; title: string; } /** * Component that shows/hides its children when clicking * a heading. */ export class Collapsible extends React.Component< CollapsibleProps, CollapsibleState > { constructor(props: CollapsibleProps) { super(props); this.state = { collapsed: props.initiallyCollapsed }; } render(): JSX.Element { const doOpen = (e: any): void => { this.setState({ collapsed: false }); e.preventDefault(); }; const doClose = (e: any): void => { this.setState({ collapsed: true }); e.preventDefault(); }; if (this.state.collapsed) { return (

{" "} {this.props.title}

); } return (

{" "} {this.props.title}

{this.props.children}
); } } function WireFee(props: { s: string; rci: ExchangeWithdrawDetails; }): JSX.Element { return ( <> Wire Method {props.s} Applies Until Wire Fee Closing Fee {props.rci.wireFees.feesForType[props.s].map((f) => ( {stringifyTimestamp(f.endStamp)} {renderAmount(f.wireFee)} {renderAmount(f.closingFee)} ))} ); } function AuditorDetailsView(props: { rci: ExchangeWithdrawDetails | null; }): JSX.Element { const rci = props.rci; console.log("rci", rci); if (!rci) { return (

Details will be displayed when a valid exchange provider URL is entered.

); } if ((rci.exchangeInfo.details?.auditors ?? []).length === 0) { return

The exchange is not audited by any auditors.

; } return (
{(rci.exchangeInfo.details?.auditors ?? []).map((a) => (

Auditor {a.auditor_url}

Public key:

Trusted:{" "} {rci.trustedAuditorPubs.indexOf(a.auditor_pub) >= 0 ? "yes" : "no"}

Audits {a.denomination_keys.length} of {rci.numOfferedDenoms}{" "} denominations

))}
); } function FeeDetailsView(props: { rci: ExchangeWithdrawDetails | null; }): JSX.Element { const rci = props.rci; if (!rci) { return (

Details will be displayed when a valid exchange provider URL is entered.

); } const denoms = rci.selectedDenoms; const countByPub: { [s: string]: number } = {}; const uniq: DenominationRecord[] = []; denoms.forEach((x: DenominationRecord) => { let c = countByPub[x.denomPub] || 0; if (c === 0) { uniq.push(x); } c += 1; countByPub[x.denomPub] = c; }); function row(denom: DenominationRecord): JSX.Element { return ( {countByPub[denom.denomPub] + "x"} {renderAmount(denom.value)} {renderAmount(denom.feeWithdraw)} {renderAmount(denom.feeRefresh)} {renderAmount(denom.feeDeposit)} ); } const withdrawFee = renderAmount(rci.withdrawFee); const overhead = renderAmount(rci.overhead); return (

Overview

Public key:{" "}

{i18n.str`Withdrawal fees:`} {withdrawFee}

{i18n.str`Rounding loss:`} {overhead}

{i18n.str`Earliest expiration (for deposit): ${stringifyTimestamp( rci.earliestDepositExpiration, )}`}

Coin Fees

{uniq.map(row)}
{i18n.str`# Coins`} {i18n.str`Value`} {i18n.str`Withdraw Fee`} {i18n.str`Refresh Fee`} {i18n.str`Deposit Fee`}

Wire Fees

{Object.keys(rci.wireFees.feesForType).map((s) => ( ))}
); } /** * Shows details about a withdraw request. */ export function WithdrawDetailView(props: { rci: ExchangeWithdrawDetails | null; }): JSX.Element { const rci = props.rci; return (
); } interface ExpanderTextProps { text: string; } /** * Show a heading with a toggle to show/hide the expandable content. */ export function ExpanderText({ text }: ExpanderTextProps): JSX.Element { return {text}; } export interface LoadingButtonProps { loading: boolean; } export function ProgressButton( props: React.PropsWithChildren & React.DetailedHTMLProps< React.ButtonHTMLAttributes, HTMLButtonElement >, ): JSX.Element { return (