/* 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 */ import { FeeDescription, FeeDescriptionPair } from "@gnu-taler/taler-util"; import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Amount } from "../../components/Amount.js"; import { ErrorMessage } from "../../components/ErrorMessage.js"; import { LoadingError } from "../../components/LoadingError.js"; import { SelectList } from "../../components/SelectList.js"; import { Input, SvgIcon } from "../../components/styled/index.js"; import { TermsOfService } from "../../components/TermsOfService/index.js"; import { Time } from "../../components/Time.js"; import { useTranslationContext } from "../../context/translation.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { Button } from "../../mui/Button.js"; import arrowDown from "../../svg/chevron-down.svg"; import { State } from "./index.js"; const ButtonGroup = styled.div` & > button { margin-left: 8px; margin-right: 8px; } `; const ButtonGroupFooter = styled.div` & { display: flex; justify-content: space-between; } & > button { margin-left: 8px; margin-right: 8px; } `; const FeeDescriptionTable = styled.table` & { margin-bottom: 20px; width: 100%; border-collapse: collapse; } td { padding: 8px; } td.fee { text-align: center; } th.fee { text-align: center; } td.value { text-align: right; width: 15%; white-space: nowrap; } td.icon { width: 24px; } td.icon > div { width: 24px; height: 24px; margin: 0px; } td.expiration { text-align: center; } tr[data-main="true"] { background-color: #add8e662; } tr[data-main="true"] > td.value, tr[data-main="true"] > td.expiration, tr[data-main="true"] > td.fee { border-bottom: lightgray solid 1px; } tr[data-hidden="true"] { display: none; } tbody > tr.value[data-hasMore="true"], tbody > tr.value[data-hasMore="true"] > td { cursor: pointer; } th { position: sticky; top: 0; background-color: white; } `; const Container = styled.div` display: flex; flex-direction: column; & > * { margin-bottom: 20px; } `; export function ErrorLoadingView({ error }: State.LoadingUriError): VNode { const { i18n } = useTranslationContext(); return ( Could not load exchange fees} error={error} /> ); } export function PrivacyContentView({ exchangeUrl, onClose, }: State.ShowingPrivacy): VNode { const { i18n } = useTranslationContext(); return (
show privacy terms for {exchangeUrl}
); } export function TosContentView({ exchangeUrl, onClose, }: State.ShowingTos): VNode { const { i18n } = useTranslationContext(); return (
); } export function NoExchangesView({ currency, }: SelectExchangeState.NoExchange): VNode { const { i18n } = useTranslationContext(); if (!currency) { return ( Could not find any exchange} /> ); } return ( Could not find any exchange for the currency {currency} } /> ); } export function ComparingView({ exchanges, selected, onReset, onSelect, pairTimeline, onShowPrivacy, onShowTerms, }: State.Comparing): VNode { const { i18n } = useTranslationContext(); return (

Service fee description

Select {selected.currency} exchange } list={exchanges.list} name="lang" value={exchanges.value} onChange={exchanges.onChange} />

Auditors
{selected.auditors.length === 0 ? (
Doesn't have auditors
) : ( selected.auditors.map((a) => {
{a.auditor_url}
; }) )}
currency {selected.currency}

Operations

Deposits

  Denomination Fee Until

Withdrawals

  Denomination Fee Until

Refunds

  Denomination Fee Until {" "}

Refresh

  Denomination Fee Until {" "}
); } export function ReadyView({ exchanges, selected, onClose, onShowPrivacy, onShowTerms, }: State.Ready): VNode { const { i18n } = useTranslationContext(); return (

Service fee description

All fee indicated below are in the same and only currency the exchange works.

{Object.keys(exchanges.list).length === 1 ? (

Exchange: {selected.exchangeBaseUrl}

) : (

Select {selected.currency} exchange } list={exchanges.list} name="lang" value={exchanges.value} onChange={exchanges.onChange} />

)}
Auditors
{selected.auditors.length === 0 ? (
Doesn't have auditors
) : ( selected.auditors.map((a) => {
{a.auditor_url}
; }) )}
Currency {selected.currency}

Coin operations

Every operation in this section may be different by denomination value and is valid for a period of time. The exchange will charge the indicated amount every time a coin is used in such operation.

Deposits

  Denomination Fee Until Number(a) - Number(b)} />

Withdrawals

  Denomination Fee Until Number(a) - Number(b)} />

Refunds

  Denomination Fee Until Number(a) - Number(b)} /> {" "}

Refresh

  Denomination Fee Until Number(a) - Number(b)} />

Transfer operations

Every operation in this section may be different by transfer type and is valid for a period of time. The exchange will charge the indicated amount every time a transfer is made.

{Object.entries(selected.transferFees).map(([type, fees], idx) => { return (

{type}

  Operation Fee Until
); })}

Wallet operations

Every operation in this section may be different by transfer type and is valid for a period of time. The exchange will charge the indicated amount every time a transfer is made.

  Feature Fee Until
); } function FeeDescriptionRowsGroup({ infos, }: { infos: FeeDescription[]; }): VNode { const [expanded, setExpand] = useState(false); const hasMoreInfo = infos.length > 1; return ( {infos.map((info, idx) => { const main = idx === 0; return ( setExpand((p) => !p)} > {hasMoreInfo && main ? ( ) : undefined} {main ? info.group : ""} {info.fee ? ( {} ) : undefined} ); } function FeePairRowsGroup({ infos }: { infos: FeeDescriptionPair[] }): VNode { const [expanded, setExpand] = useState(false); const hasMoreInfo = infos.length > 1; return ( {infos.map((info, idx) => { const main = idx === 0; return ( setExpand((p) => !p)} > {hasMoreInfo && main ? ( ) : undefined} {main ? info.group : ""} {info.left ? ( {} ) : ( --- )} {info.right ? ( {} ) : ( --- )} ); } /** * Group by value and then render using FeePairRowsGroup * @param param0 * @returns */ function RenderFeePairByValue({ list }: { list: FeeDescriptionPair[] }): VNode { return ( { list.reduce( (prev, info, idx) => { const next = idx >= list.length - 1 ? undefined : list[idx + 1]; const nextIsMoreInfo = next !== undefined && next.group === info.group; prev.rows.push(info); if (nextIsMoreInfo) { return prev; } // prev.rows = []; prev.views.push(); return prev; }, { rows: [], views: [] } as { rows: FeeDescriptionPair[]; views: h.JSX.Element[]; }, ).views } ); } /** * * Group by value and then render using FeeDescriptionRowsGroup * @param param0 * @returns */ function RenderFeeDescriptionByValue({ list, sorting, }: { list: FeeDescription[]; sorting?: (a: string, b: string) => number; }): VNode { const grouped = list.reduce((prev, cur) => { if (!prev[cur.group]) { prev[cur.group] = []; } prev[cur.group].push(cur); return prev; }, {} as Record); const p = Object.keys(grouped) .sort(sorting) .map((i, idx) => ); return {p}; }