diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/ExchangeSelection')
4 files changed, 604 insertions, 170 deletions
| diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts index 4b28904fb..9603b3d2c 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts @@ -15,15 +15,15 @@   */  import { -  FeeDescription, -  FeeDescriptionPair, -  AbsoluteTime, +  DenomOperationMap,    ExchangeFullDetails, -  OperationMap, -  ExchangeListItem, +  ExchangeListItem, FeeDescriptionPair  } from "@gnu-taler/taler-util";  import { Loading } from "../../components/Loading.js";  import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { +  State as SelectExchangeState +} from "../../hooks/useSelectedExchange.js";  import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";  import { compose, StateViewMap } from "../../utils/index.js";  import * as wxApi from "../../wxApi.js"; @@ -32,7 +32,7 @@ import {    ComparingView,    ErrorLoadingView,    NoExchangesView, -  ReadyView, +  ReadyView  } from "./views.js";  export interface Props { @@ -41,9 +41,6 @@ export interface Props {    onCancel: () => Promise<void>;    onSelection: (exchange: string) => Promise<void>;  } -import { -  State as SelectExchangeState -} from "../../hooks/useSelectedExchange.js";  export type State =    | State.Loading @@ -71,13 +68,12 @@ export namespace State {    export interface Ready extends BaseInfo {      status: "ready"; -    timeline: OperationMap<FeeDescription[]>;      onClose: ButtonHandler;    }    export interface Comparing extends BaseInfo {      status: "comparing"; -    pairTimeline: OperationMap<FeeDescriptionPair[]>; +    pairTimeline: DenomOperationMap<FeeDescriptionPair[]>;      onReset: ButtonHandler;      onSelect: ButtonHandler;    } diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts index 0279f6514..954e52239 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts @@ -14,8 +14,8 @@   GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>   */ -import { FeeDescription, OperationMap } from "@gnu-taler/taler-util"; -import { createDenominationPairTimeline } from "@gnu-taler/taler-wallet-core"; +import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util"; +import { createPairTimeline } from "@gnu-taler/taler-wallet-core";  import { useState } from "preact/hooks";  import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";  import * as wxApi from "../../wxApi.js"; @@ -94,27 +94,26 @@ export function useComponentState(          onClick: onCancel,        },        selected, -      timeline: selected.feesDescription,      };    } -  const pairTimeline: OperationMap<FeeDescription[]> = { -    deposit: createDenominationPairTimeline( -      selected.feesDescription.deposit, -      original.feesDescription.deposit, +  const pairTimeline: DenomOperationMap<FeeDescription[]> = { +    deposit: createPairTimeline( +      selected.denomFees.deposit, +      original.denomFees.deposit,      ), -    refresh: createDenominationPairTimeline( -      selected.feesDescription.refresh, -      original.feesDescription.refresh, +    refresh: createPairTimeline( +      selected.denomFees.refresh, +      original.denomFees.refresh,      ), -    refund: createDenominationPairTimeline( -      selected.feesDescription.refund, -      original.feesDescription.refund, -    ), -    withdraw: createDenominationPairTimeline( -      selected.feesDescription.withdraw, -      original.feesDescription.withdraw, +    refund: createPairTimeline( +      selected.denomFees.refund, +      original.denomFees.refund,      ), +    withdraw: createPairTimeline( +      selected.denomFees.withdraw, +      original.denomFees.withdraw, +    )    };    return { diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/stories.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/stories.tsx index 43a147e28..38b63e615 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/stories.tsx @@ -28,71 +28,72 @@ export default {  export const Bitcoin1 = createExample(ReadyView, {    exchanges: { -    list: { "http://exchange": "http://exchange" }, -    value: "http://exchange", +    list: { "0": "https://exchange.taler.ar" }, +    value: "0",    },    selected: {      currency: "BITCOINBTC",      auditors: [], +    exchangeBaseUrl: "https://exchange.taler.ar", +    denomFees: timelineExample(), +    transferFees: {}, +    globalFees: [],    } as any,    onClose: {}, -  timeline: { -    deposit: [], -    refresh: [], -    refund: [], -    withdraw: [], -  },  });  export const Bitcoin2 = createExample(ReadyView, {    exchanges: { -    list: { "http://exchange": "http://exchange" }, -    value: "http://exchange", +    list: { +      "https://exchange.taler.ar": "https://exchange.taler.ar", +      "https://exchange-btc.taler.ar": "https://exchange-btc.taler.ar", +    }, +    value: "https://exchange.taler.ar",    },    selected: {      currency: "BITCOINBTC",      auditors: [], +    exchangeBaseUrl: "https://exchange.taler.ar", +    denomFees: timelineExample(), +    transferFees: {}, +    globalFees: [],    } as any,    onClose: {}, -  timeline: { -    deposit: [], -    refresh: [], -    refund: [], -    withdraw: [], -  },  }); +  export const Kudos1 = createExample(ReadyView, {    exchanges: { -    list: { "http://exchange": "http://exchange" }, -    value: "http://exchange", +    list: { +      "https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar", +    }, +    value: "https://exchange-kudos.taler.ar",    },    selected: {      currency: "BITCOINBTC",      auditors: [], +    exchangeBaseUrl: "https://exchange.taler.ar", +    denomFees: timelineExample(), +    transferFees: {}, +    globalFees: [],    } as any,    onClose: {}, -  timeline: { -    deposit: [], -    refresh: [], -    refund: [], -    withdraw: [], -  },  });  export const Kudos2 = createExample(ReadyView, {    exchanges: { -    list: { "http://exchange": "http://exchange" }, -    value: "http://exchange", +    list: { +      "https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar", +      "https://exchange-kudos2.taler.ar": "https://exchange-kudos2.taler.ar", +    }, +    value: "https://exchange-kudos.taler.ar",    },    selected: {      currency: "BITCOINBTC",      auditors: [], +    exchangeBaseUrl: "https://exchange.taler.ar", +    denomFees: timelineExample(), +    transferFees: {}, +    globalFees: [],    } as any,    onClose: {}, -  timeline: { -    deposit: [], -    refresh: [], -    refund: [], -    withdraw: [], -  },  });  export const ComparingBitcoin = createExample(ComparingView, {    exchanges: { @@ -102,6 +103,9 @@ export const ComparingBitcoin = createExample(ComparingView, {    selected: {      currency: "BITCOINBTC",      auditors: [], +    exchangeBaseUrl: "https://exchange.taler.ar", +    transferFees: {}, +    globalFees: [],    } as any,    onReset: {},    onSelect: {}, @@ -121,6 +125,9 @@ export const ComparingKudos = createExample(ComparingView, {    selected: {      currency: "KUDOS",      auditors: [], +    exchangeBaseUrl: "https://exchange.taler.ar", +    transferFees: {}, +    globalFees: [],    } as any,    onReset: {},    onSelect: {}, @@ -132,3 +139,400 @@ export const ComparingKudos = createExample(ComparingView, {      withdraw: [],    },  }); + +function timelineExample() { +  return { +    deposit: [ +      { +        group: "0.1", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1916386904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "1", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1916386904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "10", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1916386904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "1000", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1916386904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "2", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1916386904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "5", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1916386904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +    ], +    refresh: [ +      { +        group: "0.1", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "1", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "10", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "1000", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "2", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "5", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +    ], +    refund: [ +      { +        group: "0.1", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "1", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "10", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "1000", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "2", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "5", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +    ], +    withdraw: [ +      { +        group: "0.1", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "1", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "10", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "1000", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "2", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +      { +        group: "5", +        from: { +          t_ms: 1664098904000, +        }, +        until: { +          t_ms: 1758706904000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +    ], +    wad: [ +      { +        group: "iban", +        from: { +          t_ms: 1640995200000, +        }, +        until: { +          t_ms: 1798761600000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +    ], +    wire: [ +      { +        group: "iban", +        from: { +          t_ms: 1640995200000, +        }, +        until: { +          t_ms: 1798761600000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +    ], +    closing: [ +      { +        group: "iban", +        from: { +          t_ms: 1640995200000, +        }, +        until: { +          t_ms: 1798761600000, +        }, +        fee: { +          currency: "KUDOS", +          fraction: 1000000, +          value: 0, +        }, +      }, +    ], +  }; +} diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx index 47554bfcd..6b753e215 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx @@ -31,9 +31,7 @@ import { useTranslationContext } from "../../context/translation.js";  import { Button } from "../../mui/Button.js";  import arrowDown from "../../svg/chevron-down.svg";  import { State } from "./index.js"; -import { -  State as SelectExchangeState -} from "../../hooks/useSelectedExchange.js"; +import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";  const ButtonGroup = styled.div`    & > button { @@ -59,7 +57,7 @@ const FeeDescriptionTable = styled.table`    }    td.value {      text-align: right; -    width: 1%; +    width: 15%;      white-space: nowrap;    }    td.icon { @@ -109,26 +107,28 @@ export function ErrorLoadingView({ error }: State.LoadingUriError): VNode {    return (      <LoadingError -      title={<i18n.Translate>Could not load tip status</i18n.Translate>} +      title={<i18n.Translate>Could not load exchange fees</i18n.Translate>}        error={error}      />    );  } - - -export function NoExchangesView({currency}: SelectExchangeState.NoExchange): VNode { +export function NoExchangesView({ +  currency, +}: SelectExchangeState.NoExchange): VNode {    const { i18n } = useTranslationContext();    if (!currency) {      return (        <div>          <i18n.Translate>could not find any exchange</i18n.Translate>        </div> -    );   +    );    }    return (      <div> -      <i18n.Translate>could not find any exchange for the currency {currency}</i18n.Translate> +      <i18n.Translate> +        could not find any exchange for the currency {currency} +      </i18n.Translate>      </div>    );  } @@ -356,7 +356,6 @@ export function ReadyView({    exchanges,    selected,    onClose, -  timeline,  }: State.Ready): VNode {    const { i18n } = useTranslationContext(); @@ -365,7 +364,10 @@ export function ReadyView({        <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>        <section>          <div            style={{ @@ -375,21 +377,27 @@ export function ReadyView({              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> +          {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> +          )}            <Button variant="outlined" onClick={onClose.onClick}>              <i18n.Translate>Close</i18n.Translate>            </Button> @@ -411,17 +419,26 @@ export function ReadyView({          <table>            <tr>              <td> -              <i18n.Translate>currency</i18n.Translate> +              <i18n.Translate>Currency</i18n.Translate> +            </td> +            <td> +              <b>{selected.currency}</b>              </td> -            <td>{selected.currency}</td>            </tr>          </table>        </section>        <section>          <h2> -          <i18n.Translate>Operations</i18n.Translate> +          <i18n.Translate>Coin operations</i18n.Translate>          </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> +        <p>            <i18n.Translate>Deposits</i18n.Translate>          </p>          <FeeDescriptionTable> @@ -440,7 +457,10 @@ export function ReadyView({              </tr>            </thead>            <tbody> -            <RenderFeeDescriptionByValue first={timeline.deposit} /> +            <RenderFeeDescriptionByValue +              list={selected.denomFees.deposit} +              sorting={(a, b) => Number(a) - Number(b)} +            />            </tbody>          </FeeDescriptionTable>          <p> @@ -462,7 +482,10 @@ export function ReadyView({              </tr>            </thead>            <tbody> -            <RenderFeeDescriptionByValue first={timeline.withdraw} /> +            <RenderFeeDescriptionByValue +              list={selected.denomFees.withdraw} +              sorting={(a, b) => Number(a) - Number(b)} +            />            </tbody>          </FeeDescriptionTable>          <p> @@ -484,7 +507,10 @@ export function ReadyView({              </tr>            </thead>            <tbody> -            <RenderFeeDescriptionByValue first={timeline.refund} /> +            <RenderFeeDescriptionByValue +              list={selected.denomFees.refund} +              sorting={(a, b) => Number(a) - Number(b)} +            />            </tbody>          </FeeDescriptionTable>{" "}          <p> @@ -506,53 +532,81 @@ export function ReadyView({              </tr>            </thead>            <tbody> -            <RenderFeeDescriptionByValue first={timeline.refresh} /> +            <RenderFeeDescriptionByValue +              list={selected.denomFees.refresh} +              sorting={(a, b) => Number(a) - Number(b)} +            />            </tbody> -        </FeeDescriptionTable>{" "} +        </FeeDescriptionTable>        </section>        <section> -        <table> +        <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> </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>            <thead>              <tr> -              <td> -                <i18n.Translate>Wallet operations</i18n.Translate> -              </td> -              <td> +              <th> </th> +              <th> +                <i18n.Translate>Feature</i18n.Translate> +              </th> +              <th class="fee">                  <i18n.Translate>Fee</i18n.Translate> -              </td> +              </th> +              <th> +                <i18n.Translate>Until</i18n.Translate> +              </th>              </tr>            </thead>            <tbody> -            <tr> -              <td>history(i) </td> -              <td>0.1</td> -            </tr> -            <tr> -              <td>kyc (i) </td> -              <td>0.1</td> -            </tr> -            <tr> -              <td>account (i) </td> -              <td>0.1</td> -            </tr> -            <tr> -              <td>purse (i) </td> -              <td>0.1</td> -            </tr> -            <tr> -              <td>wire SEPA (i) </td> -              <td>0.1</td> -            </tr> -            <tr> -              <td>closing SEPA(i) </td> -              <td>0.1</td> -            </tr> -            <tr> -              <td>wad SEPA (i) </td> -              <td>0.1</td> -            </tr> +            <RenderFeeDescriptionByValue list={selected.globalFees} />            </tbody> -        </table> +        </FeeDescriptionTable>        </section>        <section>          <ButtonGroup> @@ -579,7 +633,7 @@ function FeeDescriptionRowsGroup({            <tr              key={idx}              class="value" -            data-hasMore={!hasMoreInfo} +            data-hasMore={hasMoreInfo}              data-main={main}              data-hidden={!main && !expanded}              onClick={() => setExpand((p) => !p)} @@ -594,9 +648,7 @@ function FeeDescriptionRowsGroup({                  />                ) : undefined}              </td> -            <td class="value"> -              {main ? <Amount value={info.value} hideCurrency /> : ""} -            </td> +            <td class="value">{main ? info.group : ""}</td>              {info.fee ? (                <td class="fee">{<Amount value={info.fee} hideCurrency />}</td>              ) : undefined} @@ -621,7 +673,7 @@ function FeePairRowsGroup({ infos }: { infos: FeeDescriptionPair[] }): VNode {            <tr              key={idx}              class="value" -            data-hasMore={!hasMoreInfo} +            data-hasMore={hasMoreInfo}              data-main={main}              data-hidden={!main && !expanded}              onClick={() => setExpand((p) => !p)} @@ -636,9 +688,7 @@ function FeePairRowsGroup({ infos }: { infos: FeeDescriptionPair[] }): VNode {                  />                ) : undefined}              </td> -            <td class="value"> -              {main ? <Amount value={info.value} hideCurrency /> : ""} -            </td> +            <td class="value">{main ? info.group : ""}</td>              {info.left ? (                <td class="fee">{<Amount value={info.left} hideCurrency />}</td>              ) : ( @@ -673,7 +723,7 @@ function RenderFeePairByValue({ list }: { list: FeeDescriptionPair[] }): VNode {              const next = idx >= list.length - 1 ? undefined : list[idx + 1];              const nextIsMoreInfo = -              next !== undefined && Amounts.cmp(next.value, info.value) === 0; +              next !== undefined && next.group === info.group;              prev.rows.push(info); @@ -681,7 +731,7 @@ function RenderFeePairByValue({ list }: { list: FeeDescriptionPair[] }): VNode {                return prev;              } -            prev.rows = []; +            // prev.rows = [];              prev.views.push(<FeePairRowsGroup infos={prev.rows} />);              return prev;            }, @@ -701,36 +751,21 @@ function RenderFeePairByValue({ list }: { list: FeeDescriptionPair[] }): VNode {   * @returns   */  function RenderFeeDescriptionByValue({ -  first, +  list, +  sorting,  }: { -  first: FeeDescription[]; +  list: FeeDescription[]; +  sorting?: (a: string, b: string) => number;  }): VNode { -  return ( -    <Fragment> -      { -        first.reduce( -          (prev, info, idx) => { -            const next = idx >= first.length - 1 ? undefined : first[idx + 1]; - -            const nextIsMoreInfo = -              next !== undefined && Amounts.cmp(next.value, info.value) === 0; - -            prev.rows.push(info); - -            if (nextIsMoreInfo) { -              return prev; -            } - -            prev.rows = []; -            prev.views.push(<FeeDescriptionRowsGroup infos={prev.rows} />); -            return prev; -          }, -          { rows: [], views: [] } as { -            rows: FeeDescription[]; -            views: h.JSX.Element[]; -          }, -        ).views -      } -    </Fragment> -  ); +  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>;  } | 
