/* 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 "../amounts"; import * as Amounts from "../amounts"; import { DenominationRecord, } from "../dbTypes"; import { ReserveCreationInfo, } from "../walletTypes"; import { ImplicitStateComponent } from "./components"; import * as moment from "moment"; import * as i18n from "../i18n"; import * as React from "react"; /** * Render amount as HTML, which non-breaking space between * decimal value and currency. */ export function renderAmount(amount: AmountJson | string) { 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 AmountDisplay = ({amount}: {amount: AmountJson | string}) => 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: number = 5) { 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 { constructor(props: CollapsibleProps) { super(props); this.state = { collapsed: props.initiallyCollapsed }; } render() { const doOpen = (e: any) => { this.setState({collapsed: false}); e.preventDefault(); }; const doClose = (e: any) => { this.setState({collapsed: true}); e.preventDefault(); }; if (this.state.collapsed) { return

{this.props.title}

; } return (

{this.props.title}

{this.props.children}
); } } function AuditorDetailsView(props: {rci: ReserveCreationInfo|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.auditors.length === 0) { return (

The exchange is not audited by any auditors.

); } return (
{rci.exchangeInfo.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: ReserveCreationInfo|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) { return ( {countByPub[denom.denomPub] + "x"} {renderAmount(denom.value)} {renderAmount(denom.feeWithdraw)} {renderAmount(denom.feeRefresh)} {renderAmount(denom.feeDeposit)} ); } function wireFee(s: string) { return [ Wire Method {s} Applies Until Wire Fee Closing Fee , {rci!.wireFees.feesForType[s].map((f) => ( {moment.unix(f.endStamp).format("llll")} {renderAmount(f.wireFee)} {renderAmount(f.closingFee)} ))} , ]; } 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): ${moment.unix(rci.earliestDepositExpiration).fromNow()}`}

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(wireFee)}
); } /** * Shows details about a withdraw request. */ export function WithdrawDetailView(props: {rci: ReserveCreationInfo | 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 class ExpanderText extends ImplicitStateComponent { private expanded = this.makeState(false); private textArea: any = undefined; componentDidUpdate() { if (this.expanded() && this.textArea) { this.textArea.focus(); this.textArea.scrollTop = 0; } } render(): JSX.Element { if (!this.expanded()) { return ( { this.expanded(true); }}> {(this.props.text.length <= 10) ? this.props.text : ( {this.props.text.substring(0, 10)} ... ) } ); } return ( ); } } export interface LoadingButtonProps { loading: boolean; } export function ProgressButton( props: & React.PropsWithChildren & React.DetailedHTMLProps< React.ButtonHTMLAttributes, HTMLButtonElement >, ) { return (