first iteration of exchange selection: added information in the exchangeDetails response from core
This commit is contained in:
parent
d1980c39fc
commit
4ca38113ab
@ -98,6 +98,7 @@ export const Pages = {
|
|||||||
receiveCash: pageDefinition<{ amount?: string }>("/destination/get/:amount?"),
|
receiveCash: pageDefinition<{ amount?: string }>("/destination/get/:amount?"),
|
||||||
dev: "/dev",
|
dev: "/dev",
|
||||||
|
|
||||||
|
exchanges: "/exchanges",
|
||||||
backup: "/backup",
|
backup: "/backup",
|
||||||
backupProviderDetail: pageDefinition<{ pid: string }>(
|
backupProviderDetail: pageDefinition<{ pid: string }>(
|
||||||
"/backup/provider/:pid",
|
"/backup/provider/:pid",
|
||||||
|
@ -37,6 +37,18 @@ const exchanges: ExchangeListItem[] = [
|
|||||||
tos: {
|
tos: {
|
||||||
acceptedVersion: "",
|
acceptedVersion: "",
|
||||||
},
|
},
|
||||||
|
auditors: [
|
||||||
|
{
|
||||||
|
auditor_pub: "pubpubpubpubpub",
|
||||||
|
auditor_url: "https://audotor.taler.net",
|
||||||
|
denomination_keys: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
denominations: [{} as any],
|
||||||
|
wireInfo: {
|
||||||
|
accounts: [],
|
||||||
|
feesForType: {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ import {
|
|||||||
DestinationSelectionSendCash,
|
DestinationSelectionSendCash,
|
||||||
} from "./DestinationSelection.js";
|
} from "./DestinationSelection.js";
|
||||||
import { Amounts } from "@gnu-taler/taler-util";
|
import { Amounts } from "@gnu-taler/taler-util";
|
||||||
|
import { ExchangeSelection } from "./ExchangeSelection.js";
|
||||||
|
|
||||||
export function Application(): VNode {
|
export function Application(): VNode {
|
||||||
const [globalNotification, setGlobalNotification] = useState<
|
const [globalNotification, setGlobalNotification] = useState<
|
||||||
@ -141,6 +142,7 @@ export function Application(): VNode {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route path={Pages.exchanges} component={ExchangeSelection} />
|
||||||
<Route
|
<Route
|
||||||
path={Pages.sendCash.pattern}
|
path={Pages.sendCash.pattern}
|
||||||
component={DestinationSelectionSendCash}
|
component={DestinationSelectionSendCash}
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
|
||||||
|
import { createExample } from "../test-utils.js";
|
||||||
|
import { ExchangeSelectionView } from "./ExchangeSelection.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "wallet/select exchange",
|
||||||
|
};
|
||||||
|
|
||||||
|
const exchangeList = [
|
||||||
|
{
|
||||||
|
currency: "KUDOS",
|
||||||
|
exchangeBaseUrl: "https://exchange.demo.taler.net",
|
||||||
|
paytoUris: [],
|
||||||
|
tos: {},
|
||||||
|
auditors: [
|
||||||
|
{
|
||||||
|
auditor_pub: "pubpubpubpubpub",
|
||||||
|
auditor_url: "https://audotor.taler.net",
|
||||||
|
denomination_keys: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
denominations: [
|
||||||
|
{
|
||||||
|
stampStart: TalerProtocolTimestamp.never(),
|
||||||
|
stampExpireWithdraw: TalerProtocolTimestamp.never(),
|
||||||
|
stampExpireLegal: TalerProtocolTimestamp.never(),
|
||||||
|
stampExpireDeposit: TalerProtocolTimestamp.never(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
wireInfo: {
|
||||||
|
accounts: [],
|
||||||
|
feesForType: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency: "ARS",
|
||||||
|
exchangeBaseUrl: "https://exchange.taler.ar",
|
||||||
|
paytoUris: [],
|
||||||
|
tos: {},
|
||||||
|
auditors: [
|
||||||
|
{
|
||||||
|
auditor_pub: "pubpubpubpubpub",
|
||||||
|
auditor_url: "https://audotor.taler.net",
|
||||||
|
denomination_keys: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
denominations: [
|
||||||
|
{
|
||||||
|
stampStart: TalerProtocolTimestamp.never(),
|
||||||
|
stampExpireWithdraw: TalerProtocolTimestamp.never(),
|
||||||
|
stampExpireLegal: TalerProtocolTimestamp.never(),
|
||||||
|
stampExpireDeposit: TalerProtocolTimestamp.never(),
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
wireInfo: {
|
||||||
|
accounts: [],
|
||||||
|
feesForType: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Listing = createExample(ExchangeSelectionView, {
|
||||||
|
exchanges: exchangeList,
|
||||||
|
});
|
@ -0,0 +1,282 @@
|
|||||||
|
/*
|
||||||
|
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 {
|
||||||
|
AbsoluteTime,
|
||||||
|
ExchangeListItem,
|
||||||
|
TalerProtocolTimestamp,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { styled } from "@linaria/react";
|
||||||
|
import { Fragment, h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { Loading } from "../components/Loading.js";
|
||||||
|
import { LoadingError } from "../components/LoadingError.js";
|
||||||
|
import { SelectList } from "../components/SelectList.js";
|
||||||
|
import { Input, LinkPrimary } from "../components/styled/index.js";
|
||||||
|
import { Time } from "../components/Time.js";
|
||||||
|
import { useTranslationContext } from "../context/translation.js";
|
||||||
|
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
|
||||||
|
import { Button } from "../mui/Button.js";
|
||||||
|
import * as wxApi from "../wxApi.js";
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
& > * {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initialValue?: number;
|
||||||
|
exchanges: ExchangeListItem[];
|
||||||
|
onSelected: (exchange: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ButtonGroup = styled.div`
|
||||||
|
& > button {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function ExchangeSelection(): VNode {
|
||||||
|
const hook = useAsyncAsHook(wxApi.listExchanges);
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
if (!hook) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
if (hook.hasError) {
|
||||||
|
return (
|
||||||
|
<LoadingError
|
||||||
|
error={hook}
|
||||||
|
title={<i18n.Translate>Could not load list of exchange</i18n.Translate>}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ExchangeSelectionView
|
||||||
|
exchanges={hook.response.exchanges}
|
||||||
|
onSelected={(exchange) => alert(`ok, selected: ${exchange}`)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ExchangeSelectionView({
|
||||||
|
initialValue,
|
||||||
|
exchanges,
|
||||||
|
onSelected,
|
||||||
|
}: Props): VNode {
|
||||||
|
const list: Record<string, string> = {};
|
||||||
|
exchanges.forEach((e, i) => (list[String(i)] = e.exchangeBaseUrl));
|
||||||
|
|
||||||
|
const [value, setValue] = useState(String(initialValue || 0));
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
if (!exchanges.length) {
|
||||||
|
return <div>no exchanges for listing, please add one</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = exchanges[Number(value)];
|
||||||
|
|
||||||
|
const hasChange = value !== current.exchangeBaseUrl;
|
||||||
|
|
||||||
|
function nearestTimestamp(
|
||||||
|
first: TalerProtocolTimestamp,
|
||||||
|
second: TalerProtocolTimestamp,
|
||||||
|
): TalerProtocolTimestamp {
|
||||||
|
const f = AbsoluteTime.fromTimestamp(first);
|
||||||
|
const s = AbsoluteTime.fromTimestamp(second);
|
||||||
|
const a = AbsoluteTime.min(f, s);
|
||||||
|
return AbsoluteTime.toTimestamp(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextFeeUpdate = TalerProtocolTimestamp.never();
|
||||||
|
|
||||||
|
nextFeeUpdate = Object.values(current.wireInfo.feesForType).reduce(
|
||||||
|
(prev, cur) => {
|
||||||
|
return cur.reduce((p, c) => nearestTimestamp(p, c.endStamp), prev);
|
||||||
|
},
|
||||||
|
nextFeeUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
|
nextFeeUpdate = current.denominations.reduce((prev, cur) => {
|
||||||
|
return [
|
||||||
|
cur.stampExpireWithdraw,
|
||||||
|
cur.stampExpireLegal,
|
||||||
|
cur.stampExpireDeposit,
|
||||||
|
].reduce(nearestTimestamp, prev);
|
||||||
|
}, nextFeeUpdate);
|
||||||
|
|
||||||
|
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>Known exchanges</i18n.Translate>}
|
||||||
|
list={list}
|
||||||
|
name="lang"
|
||||||
|
value={value}
|
||||||
|
onChange={(v) => setValue(v)}
|
||||||
|
/>
|
||||||
|
</Input>
|
||||||
|
</p>
|
||||||
|
{hasChange ? (
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={async () => {
|
||||||
|
setValue(current.exchangeBaseUrl);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={async () => {
|
||||||
|
onSelected(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Use this exchange
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={async () => {
|
||||||
|
null;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<dl>
|
||||||
|
<dt>Auditors</dt>
|
||||||
|
{current.auditors.map((a) => {
|
||||||
|
<dd>{a.auditor_url}</dd>;
|
||||||
|
})}
|
||||||
|
</dl>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>currency</td>
|
||||||
|
<td>{current.currency}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>next fee update</td>
|
||||||
|
<td>
|
||||||
|
{
|
||||||
|
<Time
|
||||||
|
timestamp={AbsoluteTime.fromTimestamp(nextFeeUpdate)}
|
||||||
|
format="dd MMMM yyyy, HH:mm"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Denomination operations</td>
|
||||||
|
<td>Current fee</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colSpan={2}>deposit (i)</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>* 10</td>
|
||||||
|
<td>0.1</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>* 5</td>
|
||||||
|
<td>0.05</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>* 1</td>
|
||||||
|
<td>0.01</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Wallet operations</td>
|
||||||
|
<td>Current fee</td>
|
||||||
|
</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>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<ButtonGroup>
|
||||||
|
<LinkPrimary>Privacy policy</LinkPrimary>
|
||||||
|
<LinkPrimary>Terms of service</LinkPrimary>
|
||||||
|
</ButtonGroup>
|
||||||
|
</section>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -57,7 +57,7 @@ export const WithOneExchange = createExample(TestedComponent, {
|
|||||||
contentType: "text/plain",
|
contentType: "text/plain",
|
||||||
},
|
},
|
||||||
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"],
|
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"],
|
||||||
},
|
} as any, //TODO: complete with auditors, wireInfo and denominations
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ export const WithExchangeInDifferentState = createExample(TestedComponent, {
|
|||||||
contentType: "text/plain",
|
contentType: "text/plain",
|
||||||
},
|
},
|
||||||
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"],
|
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"],
|
||||||
},
|
} as any, //TODO: complete with auditors, wireInfo and denominations
|
||||||
{
|
{
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
exchangeBaseUrl: "http://exchange3.taler",
|
exchangeBaseUrl: "http://exchange3.taler",
|
||||||
|
@ -36,6 +36,7 @@ import * as a15 from "./AddNewActionView.stories.js";
|
|||||||
import * as a16 from "./DeveloperPage.stories.js";
|
import * as a16 from "./DeveloperPage.stories.js";
|
||||||
import * as a17 from "./QrReader.stories.js";
|
import * as a17 from "./QrReader.stories.js";
|
||||||
import * as a18 from "./DestinationSelection.stories.js";
|
import * as a18 from "./DestinationSelection.stories.js";
|
||||||
|
import * as a19 from "./ExchangeSelection.stories.js";
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
a1,
|
a1,
|
||||||
@ -55,4 +56,5 @@ export default [
|
|||||||
a16,
|
a16,
|
||||||
a17,
|
a17,
|
||||||
a18,
|
a18,
|
||||||
|
a19,
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user