some fixes
-fix fulfillment messages -fix product list pricing and image on payment -filter exchange by currency on withdrawal -error message on operation error on withdrawal -add taler url on balance page (just for dev) -add no balance help -better text when doing manual withdraw for the firt time -removed balance from wallet (just history) -removed pending page
This commit is contained in:
parent
2b2b8c1608
commit
606be7577b
@ -44,8 +44,6 @@ export enum Pages {
|
|||||||
backup_provider_detail = "/backup/provider/:pid",
|
backup_provider_detail = "/backup/provider/:pid",
|
||||||
backup_provider_add = "/backup/provider/add",
|
backup_provider_add = "/backup/provider/add",
|
||||||
|
|
||||||
pending = "/pending",
|
|
||||||
|
|
||||||
settings = "/settings",
|
settings = "/settings",
|
||||||
settings_exchange_add = "/settings/exchange/add",
|
settings_exchange_add = "/settings/exchange/add",
|
||||||
|
|
||||||
|
@ -123,15 +123,20 @@ export const TicketWithAProductList = createExample(TestedComponent, {
|
|||||||
amount: "USD:10",
|
amount: "USD:10",
|
||||||
products: [
|
products: [
|
||||||
{
|
{
|
||||||
description: "beer",
|
description: "ten beers",
|
||||||
price: "USD:1",
|
price: "USD:1",
|
||||||
quantity: 10,
|
quantity: 10,
|
||||||
image: beer,
|
image: beer,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "brown beer",
|
description: "beer without image",
|
||||||
price: "USD:2",
|
price: "USD:1",
|
||||||
quantity: 10,
|
quantity: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "one brown beer",
|
||||||
|
price: "USD:2",
|
||||||
|
quantity: 1,
|
||||||
image: beer,
|
image: beer,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -48,6 +48,7 @@ import { Part } from "../components/Part";
|
|||||||
import { QR } from "../components/QR";
|
import { QR } from "../components/QR";
|
||||||
import {
|
import {
|
||||||
ButtonSuccess,
|
ButtonSuccess,
|
||||||
|
LightText,
|
||||||
LinkSuccess,
|
LinkSuccess,
|
||||||
SmallLightText,
|
SmallLightText,
|
||||||
SuccessBox,
|
SuccessBox,
|
||||||
@ -313,7 +314,9 @@ export function PaymentRequestView({
|
|||||||
<h3>Payment complete</h3>
|
<h3>Payment complete</h3>
|
||||||
<p>
|
<p>
|
||||||
{!payResult.contractTerms.fulfillment_message
|
{!payResult.contractTerms.fulfillment_message
|
||||||
? "You will now be sent back to the merchant you came from."
|
? payResult.contractTerms.fulfillment_url
|
||||||
|
? `You are going to be redirected to ${payResult.contractTerms.fulfillment_url}`
|
||||||
|
: "You can close this page."
|
||||||
: payResult.contractTerms.fulfillment_message}
|
: payResult.contractTerms.fulfillment_message}
|
||||||
</p>
|
</p>
|
||||||
</SuccessBox>
|
</SuccessBox>
|
||||||
@ -373,19 +376,59 @@ function ProductList({ products }: { products: Product[] }): VNode {
|
|||||||
List of products
|
List of products
|
||||||
</SmallLightText>
|
</SmallLightText>
|
||||||
<dl>
|
<dl>
|
||||||
{products.map((p, i) => (
|
{products.map((p, i) => {
|
||||||
<div key={i} style={{ display: "flex", textAlign: "left" }}>
|
if (p.price) {
|
||||||
<div>
|
const pPrice = Amounts.parseOrThrow(p.price);
|
||||||
<img src={p.image} style={{ width: 32, height: 32 }} />
|
return (
|
||||||
|
<div key={i} style={{ display: "flex", textAlign: "left" }}>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src={p.image ? p.image : undefined}
|
||||||
|
style={{ width: 32, height: 32 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>
|
||||||
|
{p.quantity ?? 1} x {p.description}{" "}
|
||||||
|
<span style={{ color: "gray" }}>
|
||||||
|
{Amounts.stringify(pPrice)}
|
||||||
|
</span>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<b>
|
||||||
|
{Amounts.stringify(
|
||||||
|
Amounts.mult(pPrice, p.quantity ?? 1).amount,
|
||||||
|
)}
|
||||||
|
</b>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div key={i} style={{ display: "flex", textAlign: "left" }}>
|
||||||
|
<div>
|
||||||
|
<img src={p.image} style={{ width: 32, height: 32 }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>
|
||||||
|
{p.quantity ?? 1} x {p.description}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
Total{` `}
|
||||||
|
{p.price
|
||||||
|
? `${Amounts.stringifyValue(
|
||||||
|
Amounts.mult(
|
||||||
|
Amounts.parseOrThrow(p.price),
|
||||||
|
p.quantity ?? 1,
|
||||||
|
).amount,
|
||||||
|
)} ${p}`
|
||||||
|
: "free"}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
);
|
||||||
<dt>{p.description}</dt>
|
})}
|
||||||
<dd>
|
|
||||||
{p.price} x {p.quantity} {p.unit ? `(${p.unit})` : ``}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</dl>
|
</dl>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
@ -28,10 +28,12 @@ import {
|
|||||||
i18n,
|
i18n,
|
||||||
WithdrawUriInfoResponse,
|
WithdrawUriInfoResponse,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { OperationFailedError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { Loading } from "../components/Loading";
|
import { Loading } from "../components/Loading";
|
||||||
import { LoadingError } from "../components/LoadingError";
|
import { LoadingError } from "../components/LoadingError";
|
||||||
|
import { ErrorTalerOperation } from "../components/ErrorTalerOperation";
|
||||||
import { LogoHeader } from "../components/LogoHeader";
|
import { LogoHeader } from "../components/LogoHeader";
|
||||||
import { Part } from "../components/Part";
|
import { Part } from "../components/Part";
|
||||||
import { SelectList } from "../components/SelectList";
|
import { SelectList } from "../components/SelectList";
|
||||||
@ -64,7 +66,6 @@ export interface ViewProps {
|
|||||||
onAccept: (b: boolean) => void;
|
onAccept: (b: boolean) => void;
|
||||||
reviewing: boolean;
|
reviewing: boolean;
|
||||||
reviewed: boolean;
|
reviewed: boolean;
|
||||||
confirmed: boolean;
|
|
||||||
terms: TermsState;
|
terms: TermsState;
|
||||||
knownExchanges: ExchangeListItem[];
|
knownExchanges: ExchangeListItem[];
|
||||||
}
|
}
|
||||||
@ -81,8 +82,12 @@ export function View({
|
|||||||
onReview,
|
onReview,
|
||||||
onAccept,
|
onAccept,
|
||||||
reviewed,
|
reviewed,
|
||||||
confirmed,
|
|
||||||
}: ViewProps): VNode {
|
}: ViewProps): VNode {
|
||||||
|
const [withdrawError, setWithdrawError] = useState<
|
||||||
|
OperationFailedError | undefined
|
||||||
|
>(undefined);
|
||||||
|
const [confirmDisabled, setConfirmDisabled] = useState<boolean>(false);
|
||||||
|
|
||||||
const needsReview = terms.status === "changed" || terms.status === "new";
|
const needsReview = terms.status === "changed" || terms.status === "new";
|
||||||
|
|
||||||
const [switchingExchange, setSwitchingExchange] = useState(false);
|
const [switchingExchange, setSwitchingExchange] = useState(false);
|
||||||
@ -90,15 +95,37 @@ export function View({
|
|||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const exchanges = knownExchanges.reduce(
|
const exchanges = knownExchanges
|
||||||
(prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }),
|
.filter((e) => e.currency === amount.currency)
|
||||||
{},
|
.reduce(
|
||||||
);
|
(prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
async function doWithdrawAndCheckError() {
|
||||||
|
try {
|
||||||
|
setConfirmDisabled(true);
|
||||||
|
await onWithdraw();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof OperationFailedError) {
|
||||||
|
setWithdrawError(e);
|
||||||
|
}
|
||||||
|
setConfirmDisabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WalletAction>
|
<WalletAction>
|
||||||
<LogoHeader />
|
<LogoHeader />
|
||||||
<h2>{i18n.str`Digital cash withdrawal`}</h2>
|
<h2>{i18n.str`Digital cash withdrawal`}</h2>
|
||||||
|
|
||||||
|
{withdrawError && (
|
||||||
|
<ErrorTalerOperation
|
||||||
|
title="Could not finish the withdrawal operation"
|
||||||
|
error={withdrawError.operationError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Part
|
<Part
|
||||||
title="Total to withdraw"
|
title="Total to withdraw"
|
||||||
@ -168,8 +195,8 @@ export function View({
|
|||||||
{(terms.status === "accepted" || (needsReview && reviewed)) && (
|
{(terms.status === "accepted" || (needsReview && reviewed)) && (
|
||||||
<ButtonSuccess
|
<ButtonSuccess
|
||||||
upperCased
|
upperCased
|
||||||
disabled={!exchangeBaseUrl || confirmed}
|
disabled={!exchangeBaseUrl || confirmDisabled}
|
||||||
onClick={onWithdraw}
|
onClick={doWithdrawAndCheckError}
|
||||||
>
|
>
|
||||||
{i18n.str`Confirm withdrawal`}
|
{i18n.str`Confirm withdrawal`}
|
||||||
</ButtonSuccess>
|
</ButtonSuccess>
|
||||||
@ -178,7 +205,7 @@ export function View({
|
|||||||
<ButtonWarning
|
<ButtonWarning
|
||||||
upperCased
|
upperCased
|
||||||
disabled={!exchangeBaseUrl}
|
disabled={!exchangeBaseUrl}
|
||||||
onClick={onWithdraw}
|
onClick={doWithdrawAndCheckError}
|
||||||
>
|
>
|
||||||
{i18n.str`Withdraw anyway`}
|
{i18n.str`Withdraw anyway`}
|
||||||
</ButtonWarning>
|
</ButtonWarning>
|
||||||
@ -204,7 +231,6 @@ export function WithdrawPageWithParsedURI({
|
|||||||
|
|
||||||
const [reviewing, setReviewing] = useState<boolean>(false);
|
const [reviewing, setReviewing] = useState<boolean>(false);
|
||||||
const [reviewed, setReviewed] = useState<boolean>(false);
|
const [reviewed, setReviewed] = useState<boolean>(false);
|
||||||
const [confirmed, setConfirmed] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const knownExchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
|
const knownExchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
|
||||||
|
|
||||||
@ -267,16 +293,11 @@ export function WithdrawPageWithParsedURI({
|
|||||||
|
|
||||||
const onWithdraw = async (): Promise<void> => {
|
const onWithdraw = async (): Promise<void> => {
|
||||||
if (!exchange) return;
|
if (!exchange) return;
|
||||||
setConfirmed(true);
|
|
||||||
console.log("accepting exchange", exchange);
|
console.log("accepting exchange", exchange);
|
||||||
try {
|
const res = await wxApi.acceptWithdrawal(uri, exchange);
|
||||||
const res = await wxApi.acceptWithdrawal(uri, exchange);
|
console.log("accept withdrawal response", res);
|
||||||
console.log("accept withdrawal response", res);
|
if (res.confirmTransferUrl) {
|
||||||
if (res.confirmTransferUrl) {
|
document.location.href = res.confirmTransferUrl;
|
||||||
document.location.href = res.confirmTransferUrl;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setConfirmed(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -289,7 +310,6 @@ export function WithdrawPageWithParsedURI({
|
|||||||
terms={detailsHook.response.tos}
|
terms={detailsHook.response.tos}
|
||||||
onSwitchExchange={setCustomExchange}
|
onSwitchExchange={setCustomExchange}
|
||||||
knownExchanges={knownExchanges}
|
knownExchanges={knownExchanges}
|
||||||
confirmed={confirmed}
|
|
||||||
reviewed={reviewed}
|
reviewed={reviewed}
|
||||||
onAccept={onAccept}
|
onAccept={onAccept}
|
||||||
reviewing={reviewing}
|
reviewing={reviewing}
|
||||||
|
@ -14,8 +14,9 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Amounts, Balance, i18n } from "@gnu-taler/taler-util";
|
import { Amounts, Balance } from "@gnu-taler/taler-util";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
import { BalanceTable } from "../components/BalanceTable";
|
import { BalanceTable } from "../components/BalanceTable";
|
||||||
import { JustInDevMode } from "../components/JustInDevMode";
|
import { JustInDevMode } from "../components/JustInDevMode";
|
||||||
import { Loading } from "../components/Loading";
|
import { Loading } from "../components/Loading";
|
||||||
@ -23,8 +24,9 @@ import { LoadingError } from "../components/LoadingError";
|
|||||||
import { MultiActionButton } from "../components/MultiActionButton";
|
import { MultiActionButton } from "../components/MultiActionButton";
|
||||||
import { ButtonBoxPrimary, ButtonPrimary } from "../components/styled";
|
import { ButtonBoxPrimary, ButtonPrimary } from "../components/styled";
|
||||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||||
import { PageLink } from "../renderHtml";
|
import { AddNewActionView } from "../wallet/AddNewActionView";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
import { NoBalanceHelp } from "./NoBalanceHelp";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
goToWalletDeposit: (currency: string) => void;
|
goToWalletDeposit: (currency: string) => void;
|
||||||
@ -36,6 +38,7 @@ export function BalancePage({
|
|||||||
goToWalletDeposit,
|
goToWalletDeposit,
|
||||||
goToWalletHistory,
|
goToWalletHistory,
|
||||||
}: Props): VNode {
|
}: Props): VNode {
|
||||||
|
const [addingAction, setAddingAction] = useState(false);
|
||||||
const state = useAsyncAsHook(wxApi.getBalance);
|
const state = useAsyncAsHook(wxApi.getBalance);
|
||||||
const balances = !state || state.hasError ? [] : state.response.balances;
|
const balances = !state || state.hasError ? [] : state.response.balances;
|
||||||
|
|
||||||
@ -47,18 +50,24 @@ export function BalancePage({
|
|||||||
return <LoadingError title="Could not load balance page" error={state} />;
|
return <LoadingError title="Could not load balance page" error={state} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (addingAction) {
|
||||||
|
return <AddNewActionView onCancel={() => setAddingAction(false)} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BalanceView
|
<BalanceView
|
||||||
balances={balances}
|
balances={balances}
|
||||||
goToWalletManualWithdraw={goToWalletManualWithdraw}
|
goToWalletManualWithdraw={goToWalletManualWithdraw}
|
||||||
goToWalletDeposit={goToWalletDeposit}
|
goToWalletDeposit={goToWalletDeposit}
|
||||||
goToWalletHistory={goToWalletHistory}
|
goToWalletHistory={goToWalletHistory}
|
||||||
|
goToAddAction={() => setAddingAction(true)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export interface BalanceViewProps {
|
export interface BalanceViewProps {
|
||||||
balances: Balance[];
|
balances: Balance[];
|
||||||
goToWalletManualWithdraw: () => void;
|
goToWalletManualWithdraw: () => void;
|
||||||
|
goToAddAction: () => void;
|
||||||
goToWalletDeposit: (currency: string) => void;
|
goToWalletDeposit: (currency: string) => void;
|
||||||
goToWalletHistory: (currency: string) => void;
|
goToWalletHistory: (currency: string) => void;
|
||||||
}
|
}
|
||||||
@ -68,6 +77,7 @@ export function BalanceView({
|
|||||||
goToWalletManualWithdraw,
|
goToWalletManualWithdraw,
|
||||||
goToWalletDeposit,
|
goToWalletDeposit,
|
||||||
goToWalletHistory,
|
goToWalletHistory,
|
||||||
|
goToAddAction,
|
||||||
}: BalanceViewProps): VNode {
|
}: BalanceViewProps): VNode {
|
||||||
const currencyWithNonZeroAmount = balances
|
const currencyWithNonZeroAmount = balances
|
||||||
.filter((b) => !Amounts.isZero(b.available))
|
.filter((b) => !Amounts.isZero(b.available))
|
||||||
@ -75,21 +85,7 @@ export function BalanceView({
|
|||||||
|
|
||||||
if (balances.length === 0) {
|
if (balances.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<NoBalanceHelp goToWalletManualWithdraw={goToWalletManualWithdraw} />
|
||||||
<p>
|
|
||||||
<i18n.Translate>
|
|
||||||
You have no balance to show.
|
|
||||||
<a href="https://demo.taler.net/" style={{ display: "block" }}>
|
|
||||||
Learn how to top up your wallet balance »
|
|
||||||
</a>
|
|
||||||
</i18n.Translate>
|
|
||||||
</p>
|
|
||||||
<footer style={{ justifyContent: "space-between" }}>
|
|
||||||
<ButtonPrimary onClick={goToWalletManualWithdraw}>
|
|
||||||
Withdraw
|
|
||||||
</ButtonPrimary>
|
|
||||||
</footer>
|
|
||||||
</Fragment>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +109,7 @@ export function BalanceView({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<JustInDevMode>
|
<JustInDevMode>
|
||||||
<ButtonBoxPrimary onClick={() => null}>enter uri</ButtonBoxPrimary>
|
<ButtonBoxPrimary onClick={goToAddAction}>enter uri</ButtonBoxPrimary>
|
||||||
</JustInDevMode>
|
</JustInDevMode>
|
||||||
</footer>
|
</footer>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
import { i18n } from "@gnu-taler/taler-util";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { ButtonBoxWarning, WarningBox } from "../components/styled";
|
||||||
|
|
||||||
|
export function NoBalanceHelp({
|
||||||
|
goToWalletManualWithdraw,
|
||||||
|
}: {
|
||||||
|
goToWalletManualWithdraw: () => void;
|
||||||
|
}): VNode {
|
||||||
|
return (
|
||||||
|
<WarningBox>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
<i18n.Translate>You have no balance to show.</i18n.Translate>
|
||||||
|
</b>
|
||||||
|
<br />
|
||||||
|
<i18n.Translate>
|
||||||
|
To withdraw money you can start from your bank site or click the
|
||||||
|
"withdraw" button to use a known exchange.
|
||||||
|
</i18n.Translate>
|
||||||
|
</p>
|
||||||
|
<ButtonBoxWarning onClick={() => goToWalletManualWithdraw()}>
|
||||||
|
Withdraw
|
||||||
|
</ButtonBoxWarning>
|
||||||
|
</WarningBox>
|
||||||
|
);
|
||||||
|
}
|
@ -37,7 +37,6 @@ import { DeveloperPage } from "./popup/DeveloperPage";
|
|||||||
import { TalerActionFound } from "./popup/TalerActionFound";
|
import { TalerActionFound } from "./popup/TalerActionFound";
|
||||||
import { BackupPage } from "./wallet/BackupPage";
|
import { BackupPage } from "./wallet/BackupPage";
|
||||||
import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
|
import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
|
||||||
import { Pending } from "./wallet/PendingPage";
|
|
||||||
import { ProviderAddPage } from "./wallet/ProviderAddPage";
|
import { ProviderAddPage } from "./wallet/ProviderAddPage";
|
||||||
import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
|
import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
|
||||||
|
|
||||||
@ -126,8 +125,6 @@ function Application(): VNode {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path={Pages.pending} component={Pending} />
|
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={Pages.backup}
|
path={Pages.backup}
|
||||||
component={BackupPage}
|
component={BackupPage}
|
||||||
|
@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of GNU Taler
|
|
||||||
(C) 2021 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 { createExample } from "../test-utils";
|
|
||||||
import { BalanceView as TestedComponent } from "./BalancePage";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "wallet/balance",
|
|
||||||
component: TestedComponent,
|
|
||||||
argTypes: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EmptyBalance = createExample(TestedComponent, {
|
|
||||||
balances: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SomeCoins = createExample(TestedComponent, {
|
|
||||||
balances: [
|
|
||||||
{
|
|
||||||
available: "USD:10.5",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "USD:0",
|
|
||||||
pendingOutgoing: "USD:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
|
|
||||||
balances: [
|
|
||||||
{
|
|
||||||
available: "EUR:1",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "USD:0",
|
|
||||||
pendingOutgoing: "USD:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "TESTKUDOS:2000",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "USD:0",
|
|
||||||
pendingOutgoing: "USD:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "JPY:4",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "EUR:15",
|
|
||||||
pendingOutgoing: "EUR:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const NoCoinsInTreeCurrencies = createExample(TestedComponent, {
|
|
||||||
balances: [
|
|
||||||
{
|
|
||||||
available: "EUR:3",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "USD:0",
|
|
||||||
pendingOutgoing: "USD:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "USD:2",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "USD:0",
|
|
||||||
pendingOutgoing: "USD:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "ARS:1",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "EUR:15",
|
|
||||||
pendingOutgoing: "EUR:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SomeCoinsInFiveCurrencies = createExample(TestedComponent, {
|
|
||||||
balances: [
|
|
||||||
{
|
|
||||||
available: "USD:0",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "USD:0",
|
|
||||||
pendingOutgoing: "USD:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "ARS:13451",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "USD:0",
|
|
||||||
pendingOutgoing: "USD:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "EUR:202.02",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "EUR:0",
|
|
||||||
pendingOutgoing: "EUR:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "JPY:0",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "EUR:0",
|
|
||||||
pendingOutgoing: "EUR:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "JPY:51223233",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "EUR:0",
|
|
||||||
pendingOutgoing: "EUR:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "DEMOKUDOS:6",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "USD:0",
|
|
||||||
pendingOutgoing: "USD:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
available: "TESTKUDOS:6",
|
|
||||||
hasPendingTransactions: false,
|
|
||||||
pendingIncoming: "USD:5",
|
|
||||||
pendingOutgoing: "USD:0",
|
|
||||||
requiresUserInput: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of TALER
|
|
||||||
(C) 2016 GNUnet e.V.
|
|
||||||
|
|
||||||
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 <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Amounts, Balance, i18n } from "@gnu-taler/taler-util";
|
|
||||||
import { Fragment, h, VNode } from "preact";
|
|
||||||
import { BalanceTable } from "../components/BalanceTable";
|
|
||||||
import { Loading } from "../components/Loading";
|
|
||||||
import { LoadingError } from "../components/LoadingError";
|
|
||||||
import { MultiActionButton } from "../components/MultiActionButton";
|
|
||||||
import { ButtonPrimary, Centered } from "../components/styled";
|
|
||||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
|
||||||
import { PageLink } from "../renderHtml";
|
|
||||||
import * as wxApi from "../wxApi";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
goToWalletDeposit: (currency: string) => void;
|
|
||||||
goToWalletHistory: (currency: string) => void;
|
|
||||||
goToWalletManualWithdraw: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BalancePage({
|
|
||||||
goToWalletManualWithdraw,
|
|
||||||
goToWalletDeposit,
|
|
||||||
goToWalletHistory,
|
|
||||||
}: Props): VNode {
|
|
||||||
const state = useAsyncAsHook(wxApi.getBalance);
|
|
||||||
|
|
||||||
const balances = !state || state.hasError ? [] : state.response.balances;
|
|
||||||
|
|
||||||
if (!state) {
|
|
||||||
return <Loading />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.hasError) {
|
|
||||||
return <LoadingError title="Could not load balance page" error={state} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BalanceView
|
|
||||||
balances={balances}
|
|
||||||
goToWalletManualWithdraw={goToWalletManualWithdraw}
|
|
||||||
goToWalletDeposit={goToWalletDeposit}
|
|
||||||
goToWalletHistory={goToWalletHistory}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BalanceViewProps {
|
|
||||||
balances: Balance[];
|
|
||||||
goToWalletManualWithdraw: () => void;
|
|
||||||
goToWalletDeposit: (currency: string) => void;
|
|
||||||
goToWalletHistory: (currency: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BalanceView({
|
|
||||||
balances,
|
|
||||||
goToWalletManualWithdraw,
|
|
||||||
goToWalletDeposit,
|
|
||||||
goToWalletHistory,
|
|
||||||
}: BalanceViewProps): VNode {
|
|
||||||
const currencyWithNonZeroAmount = balances
|
|
||||||
.filter((b) => !Amounts.isZero(b.available))
|
|
||||||
.map((b) => b.available.split(":")[0]);
|
|
||||||
|
|
||||||
if (balances.length === 0) {
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<p>
|
|
||||||
<Centered style={{ marginTop: 100 }}>
|
|
||||||
<i18n.Translate>
|
|
||||||
You have no balance to show. Need some{" "}
|
|
||||||
<PageLink pageName="/welcome">help</PageLink> getting started?
|
|
||||||
</i18n.Translate>
|
|
||||||
</Centered>
|
|
||||||
</p>
|
|
||||||
<footer style={{ justifyContent: "space-between" }}>
|
|
||||||
<div />
|
|
||||||
<ButtonPrimary onClick={goToWalletManualWithdraw}>
|
|
||||||
Withdraw
|
|
||||||
</ButtonPrimary>
|
|
||||||
</footer>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<section>
|
|
||||||
<BalanceTable
|
|
||||||
balances={balances}
|
|
||||||
goToWalletHistory={goToWalletHistory}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
<footer style={{ justifyContent: "space-between" }}>
|
|
||||||
<ButtonPrimary onClick={goToWalletManualWithdraw}>
|
|
||||||
Withdraw
|
|
||||||
</ButtonPrimary>
|
|
||||||
{currencyWithNonZeroAmount.length > 0 && (
|
|
||||||
<MultiActionButton
|
|
||||||
label={(s) => `Deposit ${s}`}
|
|
||||||
actions={currencyWithNonZeroAmount}
|
|
||||||
onClick={(c) => goToWalletDeposit(c)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</footer>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
@ -98,12 +98,20 @@ export function CreateManualWithdraw({
|
|||||||
|
|
||||||
if (!initialExchange) {
|
if (!initialExchange) {
|
||||||
return (
|
return (
|
||||||
<Centered style={{ marginTop: 100 }}>
|
<section>
|
||||||
<BoldLight>No exchange configured</BoldLight>
|
<h2>Manual Withdrawal</h2>
|
||||||
<ButtonSuccess onClick={onAddExchange}>
|
<LightText>
|
||||||
<i18n.Translate>Add exchange</i18n.Translate>
|
Choose a exchange from where the coins will be withdrawn. The exchange
|
||||||
</ButtonSuccess>
|
will send the coins to this wallet after receiving a wire transfer
|
||||||
</Centered>
|
with the correct subject.
|
||||||
|
</LightText>
|
||||||
|
<Centered style={{ marginTop: 100 }}>
|
||||||
|
<BoldLight>No exchange configured</BoldLight>
|
||||||
|
<ButtonSuccess onClick={onAddExchange}>
|
||||||
|
<i18n.Translate>Add exchange</i18n.Translate>
|
||||||
|
</ButtonSuccess>
|
||||||
|
</Centered>
|
||||||
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,13 @@ import {
|
|||||||
import { Fragment, h } from "preact";
|
import { Fragment, h } from "preact";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { ErrorMessage } from "../components/ErrorMessage";
|
import { ErrorMessage } from "../components/ErrorMessage";
|
||||||
import { Button, ButtonPrimary, Input, WarningBox } from "../components/styled";
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonPrimary,
|
||||||
|
Input,
|
||||||
|
LightText,
|
||||||
|
WarningBox,
|
||||||
|
} from "../components/styled";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
initialValue?: string;
|
initialValue?: string;
|
||||||
@ -89,6 +95,14 @@ export function ExchangeSetUrlPage({
|
|||||||
) : (
|
) : (
|
||||||
<h2>Add exchange for {expectedCurrency}</h2>
|
<h2>Add exchange for {expectedCurrency}</h2>
|
||||||
)}
|
)}
|
||||||
|
{!result && (
|
||||||
|
<LightText>Enter the URL of an exchange you trust.</LightText>
|
||||||
|
)}
|
||||||
|
{result && (
|
||||||
|
<LightText>
|
||||||
|
An exchange has been found! Review the information and click next
|
||||||
|
</LightText>
|
||||||
|
)}
|
||||||
{result && expectedCurrency && expectedCurrency !== result.currency && (
|
{result && expectedCurrency && expectedCurrency !== result.currency && (
|
||||||
<WarningBox>
|
<WarningBox>
|
||||||
This exchange doesn't match the expected currency{" "}
|
This exchange doesn't match the expected currency{" "}
|
||||||
|
@ -35,7 +35,7 @@ import { HistoryView as TestedComponent } from "./History";
|
|||||||
import { createExample } from "../test-utils";
|
import { createExample } from "../test-utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "wallet/balance/history",
|
title: "wallet/balance",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ import {
|
|||||||
import { Time } from "../components/Time";
|
import { Time } from "../components/Time";
|
||||||
import { TransactionItem } from "../components/TransactionItem";
|
import { TransactionItem } from "../components/TransactionItem";
|
||||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||||
|
import { NoBalanceHelp } from "../popup/NoBalanceHelp";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -130,14 +131,7 @@ export function HistoryView({
|
|||||||
|
|
||||||
if (balances.length === 0 || !selectedCurrency) {
|
if (balances.length === 0 || !selectedCurrency) {
|
||||||
return (
|
return (
|
||||||
<WarningBox>
|
<NoBalanceHelp goToWalletManualWithdraw={goToWalletManualWithdraw} />
|
||||||
<p>
|
|
||||||
You have <b>no balance</b>. Withdraw some funds into your wallet
|
|
||||||
</p>
|
|
||||||
<ButtonBoxWarning onClick={() => goToWalletManualWithdraw()}>
|
|
||||||
Withdraw
|
|
||||||
</ButtonBoxWarning>
|
|
||||||
</WarningBox>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of GNU Taler
|
|
||||||
(C) 2021 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 { createExample } from "../test-utils";
|
|
||||||
import { queryToSlashKeys } from "../utils/index";
|
|
||||||
import { Pending as TestedComponent } from "./PendingPage";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "wallet/pending",
|
|
||||||
component: TestedComponent,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InitialState = createExample(TestedComponent, {
|
|
||||||
onVerify: queryToSlashKeys,
|
|
||||||
});
|
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of TALER
|
|
||||||
(C) 2016 GNUnet e.V.
|
|
||||||
|
|
||||||
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 <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { h, VNode } from "preact";
|
|
||||||
import { useState } from "preact/hooks";
|
|
||||||
import { ButtonPrimary } from "../components/styled";
|
|
||||||
import { AddNewActionView } from "./AddNewActionView";
|
|
||||||
|
|
||||||
export function Pending(): VNode {
|
|
||||||
const [addingAction, setAddingAction] = useState(false);
|
|
||||||
|
|
||||||
if (addingAction) {
|
|
||||||
return <AddNewActionView onCancel={() => setAddingAction(false)} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<div />
|
|
||||||
<ButtonPrimary onClick={() => setAddingAction(true)}>+</ButtonPrimary>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as a1 from "./Backup.stories";
|
import * as a1 from "./Backup.stories";
|
||||||
import * as a2 from "./Balance.stories";
|
|
||||||
import * as a3 from "./CreateManualWithdraw.stories";
|
import * as a3 from "./CreateManualWithdraw.stories";
|
||||||
import * as a4 from "./DepositPage.stories";
|
import * as a4 from "./DepositPage.stories";
|
||||||
import * as a5 from "./ExchangeAddConfirm.stories";
|
import * as a5 from "./ExchangeAddConfirm.stories";
|
||||||
@ -35,20 +34,4 @@ import * as a13 from "./Transaction.stories";
|
|||||||
import * as a14 from "./Welcome.stories";
|
import * as a14 from "./Welcome.stories";
|
||||||
import * as a15 from "./AddNewActionView.stories";
|
import * as a15 from "./AddNewActionView.stories";
|
||||||
|
|
||||||
export default [
|
export default [a1, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15];
|
||||||
a1,
|
|
||||||
a2,
|
|
||||||
a3,
|
|
||||||
a4,
|
|
||||||
a5,
|
|
||||||
a6,
|
|
||||||
a7,
|
|
||||||
a8,
|
|
||||||
a9,
|
|
||||||
a10,
|
|
||||||
a11,
|
|
||||||
a12,
|
|
||||||
a13,
|
|
||||||
a14,
|
|
||||||
a15,
|
|
||||||
];
|
|
||||||
|
@ -47,7 +47,6 @@ import { DepositPage } from "./wallet/DepositPage";
|
|||||||
import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
|
import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
|
||||||
import { HistoryPage } from "./wallet/History";
|
import { HistoryPage } from "./wallet/History";
|
||||||
import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage";
|
import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage";
|
||||||
import { Pending } from "./wallet/PendingPage";
|
|
||||||
import { ProviderAddPage } from "./wallet/ProviderAddPage";
|
import { ProviderAddPage } from "./wallet/ProviderAddPage";
|
||||||
import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
|
import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
|
||||||
import { SettingsPage } from "./wallet/Settings";
|
import { SettingsPage } from "./wallet/Settings";
|
||||||
@ -85,6 +84,14 @@ function Application(): VNode {
|
|||||||
function clearNotification(): void {
|
function clearNotification(): void {
|
||||||
setGlobalNotification(undefined);
|
setGlobalNotification(undefined);
|
||||||
}
|
}
|
||||||
|
function clearNotificationWhenMovingOut(): void {
|
||||||
|
// const movingOutFromNotification =
|
||||||
|
// globalNotification && e.url !== globalNotification.to;
|
||||||
|
if (globalNotification) {
|
||||||
|
//&& movingOutFromNotification) {
|
||||||
|
setGlobalNotification(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<DevContextProvider>
|
<DevContextProvider>
|
||||||
@ -112,13 +119,7 @@ function Application(): VNode {
|
|||||||
)}
|
)}
|
||||||
<Router
|
<Router
|
||||||
history={hash_history}
|
history={hash_history}
|
||||||
onChange={() => {
|
onChange={clearNotificationWhenMovingOut}
|
||||||
// const movingOutFromNotification =
|
|
||||||
// globalNotification && e.url !== globalNotification.to;
|
|
||||||
if (globalNotification) {
|
|
||||||
setGlobalNotification(undefined);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Route path={Pages.welcome} component={WelcomePage} />
|
<Route path={Pages.welcome} component={WelcomePage} />
|
||||||
|
|
||||||
@ -175,7 +176,6 @@ function Application(): VNode {
|
|||||||
{/**
|
{/**
|
||||||
* PENDING
|
* PENDING
|
||||||
*/}
|
*/}
|
||||||
<Route path={Pages.pending} component={Pending} />
|
|
||||||
<Route path={Pages.settings} component={SettingsPage} />
|
<Route path={Pages.settings} component={SettingsPage} />
|
||||||
|
|
||||||
{/**
|
{/**
|
||||||
|
Loading…
Reference in New Issue
Block a user