wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx

932 lines
26 KiB
TypeScript
Raw Normal View History

2022-08-26 06:08:51 +02:00
/*
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 { FeeDescription, FeeDescriptionPair } from "@gnu-taler/taler-util";
2022-08-26 06:08:51 +02:00
import { styled } from "@linaria/react";
2022-09-12 15:57:13 +02:00
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
2022-08-26 06:08:51 +02:00
import { Amount } from "../../components/Amount.js";
2022-11-04 19:38:58 +01:00
import { ErrorMessage } from "../../components/ErrorMessage.js";
2022-09-12 15:57:13 +02:00
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";
2022-08-26 06:08:51 +02:00
import { Time } from "../../components/Time.js";
2022-09-12 15:57:13 +02:00
import { useTranslationContext } from "../../context/translation.js";
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
2022-08-26 06:08:51 +02:00
import { Button } from "../../mui/Button.js";
2022-09-12 15:57:13 +02:00
import arrowDown from "../../svg/chevron-down.svg";
import { State } from "./index.js";
2022-08-26 06:08:51 +02:00
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;
}
`;
2022-08-26 06:08:51 +02:00
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%;
2022-08-26 06:08:51 +02:00
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 {
2022-08-26 06:08:51 +02:00
const { i18n } = useTranslationContext();
return (
<LoadingError
title={<i18n.Translate>Could not load exchange fees</i18n.Translate>}
2022-08-26 06:08:51 +02:00
error={error}
/>
);
}
export function PrivacyContentView({
exchangeUrl,
onClose,
}: State.ShowingPrivacy): VNode {
const { i18n } = useTranslationContext();
return (
<div>
<Button variant="outlined" onClick={onClose.onClick}>
<i18n.Translate>Close</i18n.Translate>
</Button>
<div>show privacy terms for {exchangeUrl}</div>
</div>
);
}
export function TosContentView({
exchangeUrl,
onClose,
}: State.ShowingTos): VNode {
const { i18n } = useTranslationContext();
return (
<div>
<Button variant="outlined" onClick={onClose.onClick}>
<i18n.Translate>Close</i18n.Translate>
</Button>
<TermsOfService exchangeUrl={exchangeUrl} />
</div>
);
}
export function NoExchangesView({
currency,
}: SelectExchangeState.NoExchange): VNode {
2022-08-26 06:08:51 +02:00
const { i18n } = useTranslationContext();
if (!currency) {
return (
2022-11-04 19:38:58 +01:00
<ErrorMessage
title={<i18n.Translate>Could not find any exchange</i18n.Translate>}
/>
);
}
2022-09-13 16:07:39 +02:00
return (
2022-11-04 19:38:58 +01:00
<ErrorMessage
title={
<i18n.Translate>
Could not find any exchange for the currency {currency}
</i18n.Translate>
}
2022-11-04 19:38:58 +01:00
/>
2022-09-13 16:07:39 +02:00
);
2022-08-26 06:08:51 +02:00
}
export function ComparingView({
exchanges,
selected,
onReset,
onSelect,
2022-12-20 19:23:33 +01:00
coinOperationTimeline,
globalFeeTimeline,
wireFeeTimeline,
missingWireTYpe,
newWireType,
onShowPrivacy,
onShowTerms,
2022-08-26 06:08:51 +02:00
}: State.Comparing): VNode {
const { i18n } = useTranslationContext();
return (
<Container>
<h2>
<i18n.Translate>Service fee description</i18n.Translate>
</h2>
<section>
<div
style={{
display: "flex",
flexWrap: "wrap",
alignItems: "center",
justifyContent: "space-between",
}}
>
<p>
<Input>
<SelectList
label={
<i18n.Translate>
Select {selected.currency} exchange
</i18n.Translate>
}
list={exchanges.list}
name="lang"
value={exchanges.value}
onChange={exchanges.onChange}
/>
</Input>
</p>
<ButtonGroup>
<Button variant="outlined" onClick={onReset.onClick}>
2022-09-13 16:07:39 +02:00
<i18n.Translate>Reset</i18n.Translate>
2022-08-26 06:08:51 +02:00
</Button>
<Button variant="contained" onClick={onSelect.onClick}>
2022-09-13 16:07:39 +02:00
<i18n.Translate>Use this exchange</i18n.Translate>
2022-08-26 06:08:51 +02:00
</Button>
</ButtonGroup>
</div>
</section>
<section>
<dl>
2022-09-13 16:07:39 +02:00
<dt>
<i18n.Translate>Auditors</i18n.Translate>
</dt>
2022-08-26 06:08:51 +02:00
{selected.auditors.length === 0 ? (
2022-09-13 16:07:39 +02:00
<dd style={{ color: "red" }}>
<i18n.Translate>Doesn&apos;t have auditors</i18n.Translate>
</dd>
2022-08-26 06:08:51 +02:00
) : (
selected.auditors.map((a) => {
<dd>{a.auditor_url}</dd>;
})
)}
</dl>
<table>
<tr>
2022-09-13 16:07:39 +02:00
<td>
<i18n.Translate>currency</i18n.Translate>
</td>
2022-08-26 06:08:51 +02:00
<td>{selected.currency}</td>
</tr>
</table>
</section>
<section>
2022-09-13 16:07:39 +02:00
<h2>
2022-12-20 19:23:33 +01:00
<i18n.Translate>Coin operations</i18n.Translate>
2022-09-13 16:07:39 +02:00
</h2>
2022-12-20 19:23:33 +01:00
<p>
<i18n.Translate>
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.
</i18n.Translate>
</p>
2022-09-13 16:07:39 +02:00
<p>
<i18n.Translate>Deposits</i18n.Translate>
</p>
2022-08-26 06:08:51 +02:00
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
2022-09-13 16:07:39 +02:00
<th>
<i18n.Translate>Denomination</i18n.Translate>
</th>
<th class="fee">
2022-11-18 15:29:24 +01:00
<i18n.Translate>Current</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Selected</i18n.Translate>
2022-09-13 16:07:39 +02:00
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
2022-08-26 06:08:51 +02:00
</tr>
</thead>
<tbody>
2022-11-18 15:29:24 +01:00
<RenderFeePairByValue
2022-12-20 19:23:33 +01:00
list={coinOperationTimeline.deposit}
2022-11-18 15:29:24 +01:00
sorting={(a, b) => Number(a) - Number(b)}
/>
2022-08-26 06:08:51 +02:00
</tbody>
</FeeDescriptionTable>
2022-09-13 16:07:39 +02:00
<p>
<i18n.Translate>Withdrawals</i18n.Translate>
</p>
2022-08-26 06:08:51 +02:00
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
2022-09-13 16:07:39 +02:00
<th>
<i18n.Translate>Denomination</i18n.Translate>
</th>
<th class="fee">
2022-12-20 19:23:33 +01:00
<i18n.Translate>Current</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Selected</i18n.Translate>
2022-09-13 16:07:39 +02:00
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
2022-08-26 06:08:51 +02:00
</tr>
</thead>
<tbody>
2022-11-18 15:29:24 +01:00
<RenderFeePairByValue
2022-12-20 19:23:33 +01:00
list={coinOperationTimeline.withdraw}
2022-11-18 15:29:24 +01:00
sorting={(a, b) => Number(a) - Number(b)}
/>
2022-08-26 06:08:51 +02:00
</tbody>
</FeeDescriptionTable>
2022-09-13 16:07:39 +02:00
<p>
<i18n.Translate>Refunds</i18n.Translate>
</p>
2022-08-26 06:08:51 +02:00
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
2022-09-13 16:07:39 +02:00
<th>
<i18n.Translate>Denomination</i18n.Translate>
</th>
<th class="fee">
2022-12-20 19:23:33 +01:00
<i18n.Translate>Current</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Selected</i18n.Translate>
2022-09-13 16:07:39 +02:00
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
2022-08-26 06:08:51 +02:00
</tr>
</thead>
<tbody>
2022-11-18 15:29:24 +01:00
<RenderFeePairByValue
2022-12-20 19:23:33 +01:00
list={coinOperationTimeline.refund}
2022-11-18 15:29:24 +01:00
sorting={(a, b) => Number(a) - Number(b)}
/>
2022-08-26 06:08:51 +02:00
</tbody>
</FeeDescriptionTable>{" "}
2022-09-13 16:07:39 +02:00
<p>
<i18n.Translate>Refresh</i18n.Translate>
</p>
2022-08-26 06:08:51 +02:00
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
2022-09-13 16:07:39 +02:00
<th>
<i18n.Translate>Denomination</i18n.Translate>
</th>
<th class="fee">
2022-12-20 19:23:33 +01:00
<i18n.Translate>Current</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Selected</i18n.Translate>
2022-09-13 16:07:39 +02:00
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
2022-08-26 06:08:51 +02:00
</tr>
</thead>
<tbody>
2022-11-18 15:29:24 +01:00
<RenderFeePairByValue
2022-12-20 19:23:33 +01:00
list={coinOperationTimeline.refresh}
2022-11-18 15:29:24 +01:00
sorting={(a, b) => Number(a) - Number(b)}
/>
2022-08-26 06:08:51 +02:00
</tbody>
</FeeDescriptionTable>{" "}
</section>
2022-12-20 19:23:33 +01:00
<section>
<h2>
<i18n.Translate>Transfer operations</i18n.Translate>
</h2>
<p>
<i18n.Translate>
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.
</i18n.Translate>
</p>
{missingWireTYpe.map((type) => {
return (
<p key={type}>
Wire <b>{type}</b> is not supported for this exchange.
</p>
);
})}
{newWireType.map((type) => {
return (
<Fragment key={type}>
<p>
Wire <b>{type}</b> is not supported for the previous exchange.
</p>
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
<th>
<i18n.Translate>Operation</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Fee</i18n.Translate>
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
</tr>
</thead>
<tbody>
<RenderFeeDescriptionByValue
list={selected.transferFees[type]}
/>
</tbody>
</FeeDescriptionTable>
</Fragment>
);
})}
{Object.entries(wireFeeTimeline).map(([type, fees], idx) => {
return (
<Fragment key={idx}>
<p>{type}</p>
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
<th>
<i18n.Translate>Operation</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Current</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Selected</i18n.Translate>
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
</tr>
</thead>
<tbody>
<RenderFeePairByValue
list={fees}
sorting={(a, b) => a.localeCompare(b)}
/>
</tbody>
</FeeDescriptionTable>
</Fragment>
);
})}
</section>
<section>
<h2>
<i18n.Translate>Wallet operations</i18n.Translate>
</h2>
<p>
<i18n.Translate>
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.
</i18n.Translate>
</p>
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
<th>
<i18n.Translate>Feature</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Current</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Selected</i18n.Translate>
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
</tr>
</thead>
<tbody>
<RenderFeePairByValue
list={globalFeeTimeline}
sorting={(a, b) => a.localeCompare(b)}
/>
</tbody>
</FeeDescriptionTable>
</section>
<section>
<ButtonGroupFooter>
<Button onClick={onShowPrivacy.onClick} variant="outlined">
Privacy policy
</Button>
<Button onClick={onShowTerms.onClick} variant="outlined">
Terms of service
</Button>
</ButtonGroupFooter>
</section>
2022-08-26 06:08:51 +02:00
<section>
<ButtonGroupFooter>
<Button onClick={onShowPrivacy.onClick} variant="outlined">
Privacy policy
</Button>
<Button onClick={onShowTerms.onClick} variant="outlined">
Terms of service
</Button>
</ButtonGroupFooter>
2022-08-26 06:08:51 +02:00
</section>
</Container>
);
}
export function ReadyView({
exchanges,
selected,
onClose,
onShowPrivacy,
onShowTerms,
2022-08-26 06:08:51 +02:00
}: State.Ready): VNode {
const { i18n } = useTranslationContext();
return (
<Container>
<h2>
<i18n.Translate>Service fee description</i18n.Translate>
</h2>
<p>
All fee indicated below are in the same and only currency the exchange
works.
</p>
2022-08-26 06:08:51 +02:00
<section>
<div
style={{
display: "flex",
flexWrap: "wrap",
alignItems: "center",
justifyContent: "space-between",
}}
>
{Object.keys(exchanges.list).length === 1 ? (
<Fragment>
<p>Exchange: {selected.exchangeBaseUrl}</p>
</Fragment>
) : (
<p>
<Input>
<SelectList
label={
<i18n.Translate>
Select {selected.currency} exchange
</i18n.Translate>
}
list={exchanges.list}
name="lang"
value={exchanges.value}
onChange={exchanges.onChange}
/>
</Input>
</p>
)}
2022-08-26 06:08:51 +02:00
<Button variant="outlined" onClick={onClose.onClick}>
2022-09-13 16:07:39 +02:00
<i18n.Translate>Close</i18n.Translate>
2022-08-26 06:08:51 +02:00
</Button>
</div>
</section>
<section>
<dl>
<dt>Auditors</dt>
{selected.auditors.length === 0 ? (
2022-09-13 16:07:39 +02:00
<dd style={{ color: "red" }}>
<i18n.Translate>Doesn&apos;t have auditors</i18n.Translate>
</dd>
2022-08-26 06:08:51 +02:00
) : (
selected.auditors.map((a) => {
<dd>{a.auditor_url}</dd>;
})
)}
</dl>
<table>
<tr>
2022-09-13 16:07:39 +02:00
<td>
<i18n.Translate>Currency</i18n.Translate>
</td>
<td>
<b>{selected.currency}</b>
2022-09-13 16:07:39 +02:00
</td>
2022-08-26 06:08:51 +02:00
</tr>
</table>
</section>
<section>
2022-09-13 16:07:39 +02:00
<h2>
<i18n.Translate>Coin operations</i18n.Translate>
2022-09-13 16:07:39 +02:00
</h2>
<p>
<i18n.Translate>
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.
</i18n.Translate>
</p>
2022-09-13 16:07:39 +02:00
<p>
<i18n.Translate>Deposits</i18n.Translate>
</p>
2022-08-26 06:08:51 +02:00
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
2022-09-13 16:07:39 +02:00
<th>
<i18n.Translate>Denomination</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Fee</i18n.Translate>
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
2022-08-26 06:08:51 +02:00
</tr>
</thead>
<tbody>
<RenderFeeDescriptionByValue
list={selected.denomFees.deposit}
sorting={(a, b) => Number(a) - Number(b)}
/>
2022-08-26 06:08:51 +02:00
</tbody>
</FeeDescriptionTable>
2022-09-13 16:07:39 +02:00
<p>
<i18n.Translate>Withdrawals</i18n.Translate>
</p>
2022-08-26 06:08:51 +02:00
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
2022-09-13 16:07:39 +02:00
<th>
<i18n.Translate>Denomination</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Fee</i18n.Translate>
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
2022-08-26 06:08:51 +02:00
</tr>
</thead>
<tbody>
<RenderFeeDescriptionByValue
list={selected.denomFees.withdraw}
sorting={(a, b) => Number(a) - Number(b)}
/>
2022-08-26 06:08:51 +02:00
</tbody>
</FeeDescriptionTable>
2022-09-13 16:07:39 +02:00
<p>
<i18n.Translate>Refunds</i18n.Translate>
</p>
2022-08-26 06:08:51 +02:00
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
2022-09-13 16:07:39 +02:00
<th>
<i18n.Translate>Denomination</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Fee</i18n.Translate>
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
2022-08-26 06:08:51 +02:00
</tr>
</thead>
<tbody>
<RenderFeeDescriptionByValue
list={selected.denomFees.refund}
sorting={(a, b) => Number(a) - Number(b)}
/>
2022-08-26 06:08:51 +02:00
</tbody>
</FeeDescriptionTable>{" "}
2022-09-13 16:07:39 +02:00
<p>
<i18n.Translate>Refresh</i18n.Translate>
</p>
2022-08-26 06:08:51 +02:00
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
2022-09-13 16:07:39 +02:00
<th>
<i18n.Translate>Denomination</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Fee</i18n.Translate>
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
2022-08-26 06:08:51 +02:00
</tr>
</thead>
<tbody>
<RenderFeeDescriptionByValue
list={selected.denomFees.refresh}
sorting={(a, b) => Number(a) - Number(b)}
/>
2022-08-26 06:08:51 +02:00
</tbody>
</FeeDescriptionTable>
2022-08-26 06:08:51 +02:00
</section>
<section>
<h2>
<i18n.Translate>Transfer operations</i18n.Translate>
</h2>
<p>
<i18n.Translate>
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.
</i18n.Translate>
</p>
{Object.entries(selected.transferFees).map(([type, fees], idx) => {
return (
<Fragment key={idx}>
<p>{type}</p>
<FeeDescriptionTable>
<thead>
<tr>
<th>&nbsp;</th>
<th>
<i18n.Translate>Operation</i18n.Translate>
</th>
<th class="fee">
<i18n.Translate>Fee</i18n.Translate>
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
</tr>
</thead>
<tbody>
<RenderFeeDescriptionByValue list={fees} />
</tbody>
</FeeDescriptionTable>
</Fragment>
);
})}
</section>
<section>
<h2>
<i18n.Translate>Wallet operations</i18n.Translate>
</h2>
<p>
<i18n.Translate>
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.
</i18n.Translate>
</p>
<FeeDescriptionTable>
2022-08-26 06:08:51 +02:00
<thead>
<tr>
<th>&nbsp;</th>
<th>
<i18n.Translate>Feature</i18n.Translate>
</th>
<th class="fee">
2022-09-13 16:07:39 +02:00
<i18n.Translate>Fee</i18n.Translate>
</th>
<th>
<i18n.Translate>Until</i18n.Translate>
</th>
2022-08-26 06:08:51 +02:00
</tr>
</thead>
<tbody>
<RenderFeeDescriptionByValue list={selected.globalFees} />
2022-08-26 06:08:51 +02:00
</tbody>
</FeeDescriptionTable>
2022-08-26 06:08:51 +02:00
</section>
<section>
<ButtonGroupFooter>
<Button onClick={onShowPrivacy.onClick} variant="outlined">
Privacy policy
</Button>
<Button onClick={onShowTerms.onClick} variant="outlined">
Terms of service
</Button>
</ButtonGroupFooter>
2022-08-26 06:08:51 +02:00
</section>
</Container>
);
}
function FeeDescriptionRowsGroup({
infos,
}: {
infos: FeeDescription[];
}): VNode {
const [expanded, setExpand] = useState(false);
const hasMoreInfo = infos.length > 1;
return (
<Fragment>
{infos.map((info, idx) => {
const main = idx === 0;
return (
<tr
key={idx}
class="value"
data-hasMore={hasMoreInfo}
2022-08-26 06:08:51 +02:00
data-main={main}
data-hidden={!main && !expanded}
onClick={() => setExpand((p) => !p)}
>
<td class="icon">
{hasMoreInfo && main ? (
<SvgIcon
title="Select this contact"
dangerouslySetInnerHTML={{ __html: arrowDown }}
color="currentColor"
transform={expanded ? "" : "rotate(-90deg)"}
/>
) : undefined}
</td>
<td class="value">{main ? info.group : ""}</td>
2022-08-26 06:08:51 +02:00
{info.fee ? (
<td class="fee">{<Amount value={info.fee} hideCurrency />}</td>
) : undefined}
<td class="expiration">
<Time timestamp={info.until} format="dd-MMM-yyyy" />
</td>
</tr>
);
})}
</Fragment>
);
}
function FeePairRowsGroup({ infos }: { infos: FeeDescriptionPair[] }): VNode {
const [expanded, setExpand] = useState(false);
const hasMoreInfo = infos.length > 1;
return (
<Fragment>
{infos.map((info, idx) => {
const main = idx === 0;
return (
<tr
key={idx}
class="value"
data-hasMore={hasMoreInfo}
2022-08-26 06:08:51 +02:00
data-main={main}
data-hidden={!main && !expanded}
onClick={() => setExpand((p) => !p)}
>
<td class="icon">
{hasMoreInfo && main ? (
<SvgIcon
2022-11-18 15:29:24 +01:00
title="Expand"
2022-08-26 06:08:51 +02:00
dangerouslySetInnerHTML={{ __html: arrowDown }}
color="currentColor"
transform={expanded ? "" : "rotate(-90deg)"}
/>
) : undefined}
</td>
<td class="value">{main ? info.group : ""}</td>
2022-08-26 06:08:51 +02:00
{info.left ? (
<td class="fee">{<Amount value={info.left} hideCurrency />}</td>
) : (
<td class="fee"> --- </td>
)}
{info.right ? (
<td class="fee">{<Amount value={info.right} hideCurrency />}</td>
) : (
<td class="fee"> --- </td>
)}
<td class="expiration">
2022-11-18 15:29:24 +01:00
<Time timestamp={info.until} format="dd-MMM-yyyy HH:mm:ss" />
2022-08-26 06:08:51 +02:00
</td>
</tr>
);
})}
</Fragment>
);
}
/**
* Group by value and then render using FeePairRowsGroup
* @param param0
* @returns
*/
2022-11-18 15:29:24 +01:00
function RenderFeePairByValue({
list,
sorting,
}: {
list: FeeDescriptionPair[];
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<string, FeeDescriptionPair[]>);
const p = Object.keys(grouped)
.sort(sorting)
.map((i, idx) => <FeePairRowsGroup key={idx} infos={grouped[i]} />);
return <Fragment>{p}</Fragment>;
2022-08-26 06:08:51 +02:00
}
/**
*
* Group by value and then render using FeeDescriptionRowsGroup
* @param param0
* @returns
*/
function RenderFeeDescriptionByValue({
list,
sorting,
2022-08-26 06:08:51 +02:00
}: {
list: FeeDescription[];
sorting?: (a: string, b: string) => number;
2022-08-26 06:08:51 +02:00
}): VNode {
const grouped = list.reduce((prev, cur) => {
if (!prev[cur.group]) {
prev[cur.group] = [];
}
prev[cur.group].push(cur);
return prev;
}, {} as Record<string, FeeDescription[]>);
const p = Object.keys(grouped)
.sort(sorting)
.map((i, idx) => <FeeDescriptionRowsGroup key={idx} infos={grouped[i]} />);
return <Fragment>{p}</Fragment>;
2022-08-26 06:08:51 +02:00
}