last activity -> pending; fix downloadTos

This commit is contained in:
Sebastian 2022-01-24 14:12:12 -03:00
parent ec059d42d7
commit 882d6b3710
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
12 changed files with 228 additions and 135 deletions

View File

@ -431,7 +431,9 @@ export class Amounts {
} }
} }
const currencyFormatter = new Intl.NumberFormat("en-US"); const currencyFormatter = new Intl.NumberFormat("en-US", {
return currencyFormatter.format(Number(s)); minimumFractionDigits: minFractional,
});
return currencyFormatter.format(s as any);
} }
} }

View File

@ -138,7 +138,7 @@ async function handleExchangeUpdateError(
} }
} }
function getExchangeRequestTimeout(e: ExchangeRecord): Duration { export function getExchangeRequestTimeout(): Duration {
return { d_ms: 5000 }; return { d_ms: 5000 };
} }
@ -199,6 +199,27 @@ getExchangeDetails.makeContext = (db: DbAccess<typeof WalletStoresV1>) =>
exchangeDetails: x.exchangeDetails, exchangeDetails: x.exchangeDetails,
})); }));
export async function updateExchangeTermsOfService(
ws: InternalWalletState,
exchangeBaseUrl: string,
tos: ExchangeTosDownloadResult,
): Promise<void> {
await ws.db
.mktx((x) => ({
exchanges: x.exchanges,
exchangeDetails: x.exchangeDetails,
}))
.runReadWrite(async (tx) => {
const d = await getExchangeDetails(tx, exchangeBaseUrl);
if (d) {
d.termsOfServiceText = tos.tosText;
d.termsOfServiceContentType = tos.tosContentType;
d.termsOfServiceLastEtag = tos.tosEtag;
await tx.exchangeDetails.put(d);
}
});
}
export async function acceptExchangeTermsOfService( export async function acceptExchangeTermsOfService(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseUrl: string, exchangeBaseUrl: string,
@ -434,6 +455,36 @@ async function downloadKeysInfo(
}; };
} }
export async function downloadTosFromAcceptedFormat(
ws: InternalWalletState,
baseUrl: string,
timeout: Duration,
acceptedFormat?: string[]): Promise<ExchangeTosDownloadResult> {
let tosFound: ExchangeTosDownloadResult | undefined;
//Remove this when exchange supports multiple content-type in accept header
if (acceptedFormat)
for (const format of acceptedFormat) {
const resp = await downloadExchangeWithTermsOfService(
baseUrl,
ws.http,
timeout,
format,
);
if (resp.tosContentType === format) {
tosFound = resp;
break;
}
}
if (tosFound !== undefined) return tosFound
// If none of the specified format was found try text/plain
return await downloadExchangeWithTermsOfService(
baseUrl,
ws.http,
timeout,
"text/plain",
);
}
/** /**
* Update or add exchange DB entry by fetching the /keys and /wire information. * Update or add exchange DB entry by fetching the /keys and /wire information.
* Optionally link the reserve entry to the new or existing * Optionally link the reserve entry to the new or existing
@ -479,7 +530,7 @@ async function updateExchangeFromUrlImpl(
logger.info("updating exchange /keys info"); logger.info("updating exchange /keys info");
const timeout = getExchangeRequestTimeout(r); const timeout = getExchangeRequestTimeout();
const keysInfo = await downloadKeysInfo(baseUrl, ws.http, timeout); const keysInfo = await downloadKeysInfo(baseUrl, ws.http, timeout);
@ -507,33 +558,10 @@ async function updateExchangeFromUrlImpl(
logger.info("finished validating exchange /wire info"); logger.info("finished validating exchange /wire info");
let tosFound: ExchangeTosDownloadResult | undefined;
//Remove this when exchange supports multiple content-type in accept header
if (acceptedFormat)
for (const format of acceptedFormat) {
const resp = await downloadExchangeWithTermsOfService(
baseUrl,
ws.http,
timeout,
format,
);
if (resp.tosContentType === format) {
tosFound = resp;
break;
}
}
// If none of the specified format was found try text/plain
const tosDownload =
tosFound !== undefined
? tosFound
: await downloadExchangeWithTermsOfService(
baseUrl,
ws.http,
timeout,
"text/plain",
);
let recoupGroupId: string | undefined = undefined; const tosDownload = await downloadTosFromAcceptedFormat(ws, baseUrl, timeout, acceptedFormat)
let recoupGroupId: string | undefined;
logger.trace("updating exchange info in database"); logger.trace("updating exchange info in database");

View File

@ -89,9 +89,12 @@ import {
} from "./operations/deposits.js"; } from "./operations/deposits.js";
import { import {
acceptExchangeTermsOfService, acceptExchangeTermsOfService,
downloadTosFromAcceptedFormat,
getExchangeDetails, getExchangeDetails,
getExchangeRequestTimeout,
getExchangeTrust, getExchangeTrust,
updateExchangeFromUrl updateExchangeFromUrl,
updateExchangeTermsOfService
} from "./operations/exchanges.js"; } from "./operations/exchanges.js";
import { getMerchantInfo } from "./operations/merchants.js"; import { getMerchantInfo } from "./operations/merchants.js";
import { import {
@ -441,7 +444,6 @@ async function getExchangeTos(
ws, ws,
exchangeBaseUrl, exchangeBaseUrl,
acceptedFormat, acceptedFormat,
true,
); );
const content = exchangeDetails.termsOfServiceText; const content = exchangeDetails.termsOfServiceText;
const currentEtag = exchangeDetails.termsOfServiceLastEtag; const currentEtag = exchangeDetails.termsOfServiceLastEtag;
@ -453,12 +455,34 @@ async function getExchangeTos(
) { ) {
throw Error("exchange is in invalid state"); throw Error("exchange is in invalid state");
} }
if (acceptedFormat && acceptedFormat.findIndex(f => f === contentType) !== -1) {
return {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
currentEtag,
content,
contentType,
};
}
const tosDownload = await downloadTosFromAcceptedFormat(ws, exchangeBaseUrl, getExchangeRequestTimeout(), acceptedFormat);
if (tosDownload.tosContentType === contentType) {
return {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
currentEtag,
content,
contentType,
};
}
await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload)
return { return {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag, acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
currentEtag, currentEtag: tosDownload.tosEtag,
content, content: tosDownload.tosText,
contentType, contentType: tosDownload.tosContentType,
}; };
} }
async function listKnownBankAccounts( async function listKnownBankAccounts(
@ -1245,3 +1269,4 @@ class InternalWalletStateImpl implements InternalWalletState {
} }
} }
} }

View File

@ -43,7 +43,7 @@ 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",
last_activity = "/last-activity", pending = "/pending",
settings = "/settings", settings = "/settings",
settings_exchange_add = "/settings/exchange/add", settings_exchange_add = "/settings/exchange/add",
@ -84,10 +84,7 @@ export function NavBar({
<PopupNavigation devMode={devMode}> <PopupNavigation devMode={devMode}>
<div> <div>
<Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab> <Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
<Tab <Tab target="/pending" current={path}>{i18n.str`Pending`}</Tab>
target="/last-activity"
current={path}
>{i18n.str`Last Activity`}</Tab>
<Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab> <Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab>
<Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab> <Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab>
{devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>} {devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>}

View File

@ -15,6 +15,8 @@
*/ */
import { import {
AmountJson,
Amounts,
AmountString, AmountString,
Timestamp, Timestamp,
Transaction, Transaction,
@ -37,10 +39,7 @@ import {
} from "./styled"; } from "./styled";
import { Time } from "./Time"; import { Time } from "./Time";
export function TransactionItem(props: { export function TransactionItem(props: { tx: Transaction }): VNode {
tx: Transaction;
multiCurrency: boolean;
}): VNode {
const tx = props.tx; const tx = props.tx;
switch (tx.type) { switch (tx.type) {
case TransactionType.Withdrawal: case TransactionType.Withdrawal:
@ -53,7 +52,6 @@ export function TransactionItem(props: {
timestamp={tx.timestamp} timestamp={tx.timestamp}
iconPath={imageBank} iconPath={imageBank}
pending={tx.pending} pending={tx.pending}
multiCurrency={props.multiCurrency}
/> />
); );
case TransactionType.Payment: case TransactionType.Payment:
@ -67,7 +65,6 @@ export function TransactionItem(props: {
timestamp={tx.timestamp} timestamp={tx.timestamp}
iconPath={imageShoppingCart} iconPath={imageShoppingCart}
pending={tx.pending} pending={tx.pending}
multiCurrency={props.multiCurrency}
/> />
); );
case TransactionType.Refund: case TransactionType.Refund:
@ -80,7 +77,6 @@ export function TransactionItem(props: {
timestamp={tx.timestamp} timestamp={tx.timestamp}
iconPath={imageRefund} iconPath={imageRefund}
pending={tx.pending} pending={tx.pending}
multiCurrency={props.multiCurrency}
/> />
); );
case TransactionType.Tip: case TransactionType.Tip:
@ -93,7 +89,6 @@ export function TransactionItem(props: {
timestamp={tx.timestamp} timestamp={tx.timestamp}
iconPath={imageHandHeart} iconPath={imageHandHeart}
pending={tx.pending} pending={tx.pending}
multiCurrency={props.multiCurrency}
/> />
); );
case TransactionType.Refresh: case TransactionType.Refresh:
@ -106,7 +101,6 @@ export function TransactionItem(props: {
timestamp={tx.timestamp} timestamp={tx.timestamp}
iconPath={imageRefresh} iconPath={imageRefresh}
pending={tx.pending} pending={tx.pending}
multiCurrency={props.multiCurrency}
/> />
); );
case TransactionType.Deposit: case TransactionType.Deposit:
@ -119,7 +113,6 @@ export function TransactionItem(props: {
timestamp={tx.timestamp} timestamp={tx.timestamp}
iconPath={imageRefresh} iconPath={imageRefresh}
pending={tx.pending} pending={tx.pending}
multiCurrency={props.multiCurrency}
/> />
); );
} }
@ -144,13 +137,12 @@ function TransactionLayout(props: TransactionLayoutProps): VNode {
</LightText> </LightText>
)} )}
<SmallLightText style={{ marginTop: 5 }}> <SmallLightText style={{ marginTop: 5 }}>
<Time timestamp={props.timestamp} format="dd MMM, hh:mm" /> <Time timestamp={props.timestamp} format="hh:mm" />
</SmallLightText> </SmallLightText>
</Column> </Column>
<TransactionAmount <TransactionAmount
pending={props.pending} pending={props.pending}
amount={props.amount} amount={Amounts.parseOrThrow(props.amount)}
multiCurrency={props.multiCurrency}
debitCreditIndicator={props.debitCreditIndicator} debitCreditIndicator={props.debitCreditIndicator}
/> />
</HistoryRow> </HistoryRow>
@ -166,18 +158,15 @@ interface TransactionLayoutProps {
id: string; id: string;
iconPath: string; iconPath: string;
pending: boolean; pending: boolean;
multiCurrency: boolean;
} }
interface TransactionAmountProps { interface TransactionAmountProps {
debitCreditIndicator: "debit" | "credit" | "unknown"; debitCreditIndicator: "debit" | "credit" | "unknown";
amount: AmountString | "unknown"; amount: AmountJson;
pending: boolean; pending: boolean;
multiCurrency: boolean;
} }
function TransactionAmount(props: TransactionAmountProps): VNode { function TransactionAmount(props: TransactionAmountProps): VNode {
const [currency, amount] = props.amount.split(":");
let sign: string; let sign: string;
switch (props.debitCreditIndicator) { switch (props.debitCreditIndicator) {
case "credit": case "credit":
@ -204,9 +193,8 @@ function TransactionAmount(props: TransactionAmountProps): VNode {
> >
<ExtraLargeText> <ExtraLargeText>
{sign} {sign}
{amount} {Amounts.stringifyValue(props.amount)}
</ExtraLargeText> </ExtraLargeText>
{props.multiCurrency && <div>{currency}</div>}
{props.pending && <div>PENDING</div>} {props.pending && <div>PENDING</div>}
</Column> </Column>
); );

View File

@ -77,7 +77,7 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
& > * { & > * {
width: 400px; width: 500px;
} }
& > section { & > section {
padding: ${({ noPadding }) => (noPadding ? "0px" : "8px")}; padding: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
@ -142,7 +142,7 @@ export const Middle = styled.div`
export const PopupBox = styled.div<{ noPadding?: boolean; devMode: boolean }>` export const PopupBox = styled.div<{ noPadding?: boolean; devMode: boolean }>`
height: 290px; height: 290px;
width: ${({ devMode }) => (!devMode ? "400px" : "500px")}; width: 500px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@ -783,7 +783,7 @@ export const PopupNavigation = styled.div<{ devMode?: boolean }>`
display: flex; display: flex;
& > div { & > div {
width: ${({ devMode }) => (!devMode ? "400px" : "500px")}; width: 500px;
} }
& > div > a { & > div > a {
@ -815,15 +815,16 @@ export const NiceSelect = styled.div`
box-shadow: none; box-shadow: none;
background-image: ${image}; background-image: ${image};
background-position: right 8px center; background-position: right 4px center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 1.5em 1.5em; background-size: 32px 32px;
background-color: white; background-color: white;
border-radius: 0.25rem; border-radius: 0.25rem;
font-size: 1em; font-size: 1em;
padding: 0.5em 3em 0.5em 1em; padding: 8px 32px 8px 8px;
/* 0.5em 3em 0.5em 1em; */
cursor: pointer; cursor: pointer;
} }

View File

@ -39,7 +39,7 @@ import { SettingsPage } from "./popup/Settings";
import { TalerActionFound } from "./popup/TalerActionFound"; import { TalerActionFound } from "./popup/TalerActionFound";
import { ExchangeAddPage } from "./wallet/ExchangeAddPage"; import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
import { IoCProviderForRuntime } from "./context/iocContext"; import { IoCProviderForRuntime } from "./context/iocContext";
import { LastActivityPage } from "./wallet/LastActivityPage"; import { Pending } from "./wallet/PendingPage";
import { Match } from "preact-router/match"; import { Match } from "preact-router/match";
function main(): void { function main(): void {
@ -125,7 +125,7 @@ function Application(): VNode {
}} }}
/> />
<Route path={Pages.last_activity} component={LastActivityPage} /> <Route path={Pages.pending} component={Pending} />
<Route <Route
path={Pages.balance_transaction} path={Pages.balance_transaction}

View File

@ -277,3 +277,47 @@ export const FiveOfficialCurrencies = createExample(TestedComponent, {
}, },
], ],
}); });
export const FiveOfficialCurrenciesWithHighValue = createExample(
TestedComponent,
{
transactions: [exampleData.withdraw],
balances: [
{
available: "USD:881001321230000",
pendingIncoming: "USD:0",
pendingOutgoing: "USD:0",
hasPendingTransactions: false,
requiresUserInput: false,
},
{
available: "EUR:10",
pendingIncoming: "TESTKUDOS:0",
pendingOutgoing: "TESTKUDOS:0",
hasPendingTransactions: false,
requiresUserInput: false,
},
{
available: "COL:443000123123000.5123123",
pendingIncoming: "TESTKUDOS:0",
pendingOutgoing: "TESTKUDOS:0",
hasPendingTransactions: false,
requiresUserInput: false,
},
{
available: "JPY:1564450000000.6123123",
pendingIncoming: "TESTKUDOS:0",
pendingOutgoing: "TESTKUDOS:0",
hasPendingTransactions: false,
requiresUserInput: false,
},
{
available: "GBP:736001231231200.23123",
pendingIncoming: "TESTKUDOS:0",
pendingOutgoing: "TESTKUDOS:0",
hasPendingTransactions: false,
requiresUserInput: false,
},
],
},
);

View File

@ -28,6 +28,8 @@ import {
ButtonBoxPrimary, ButtonBoxPrimary,
ButtonBoxWarning, ButtonBoxWarning,
ButtonPrimary, ButtonPrimary,
CenteredBoldText,
CenteredText,
DateSeparator, DateSeparator,
NiceSelect, NiceSelect,
WarningBox, WarningBox,
@ -126,8 +128,6 @@ export function HistoryView({
}, {} as { [x: string]: Transaction[] }); }, {} as { [x: string]: Transaction[] });
const datesWithTransaction = Object.keys(byDate); const datesWithTransaction = Object.keys(byDate);
const multiCurrency = balances.length > 1;
if (balances.length === 0 || !selectedCurrency) { if (balances.length === 0 || !selectedCurrency) {
return ( return (
<WarningBox> <WarningBox>
@ -143,52 +143,73 @@ export function HistoryView({
return ( return (
<Fragment> <Fragment>
<section> <section>
<p <div
style={{ style={{
display: "flex", display: "flex",
justifyContent: "space-between", flexWrap: "wrap",
alignItems: "center", alignItems: "center",
justifyContent: "space-between",
}} }}
> >
{currencies.length === 1 ? ( <div
<div style={{ fontSize: "large" }}>{selectedCurrency}</div> style={{
) : ( width: "fit-content",
<NiceSelect> display: "flex",
<select }}
value={currencyIndex} >
onChange={(e) => { {currencies.length === 1 ? (
setCurrencyIndex(Number(e.currentTarget.value)); <CenteredText style={{ fontSize: "x-large", margin: 8 }}>
{selectedCurrency}
</CenteredText>
) : (
<NiceSelect>
<select
style={{
fontSize: "x-large",
}}
value={currencyIndex}
onChange={(e) => {
setCurrencyIndex(Number(e.currentTarget.value));
}}
>
{currencies.map((currency, index) => {
return (
<option value={index} key={currency}>
{currency}
</option>
);
})}
</select>
</NiceSelect>
)}
{currencyAmount && (
<CenteredBoldText
style={{
display: "inline-block",
fontSize: "x-large",
margin: 8,
}} }}
> >
{currencies.map((currency, index) => { {Amounts.stringifyValue(currencyAmount)}
return ( </CenteredBoldText>
<option value={index} key={currency}> )}
{currency} </div>
</option> <div>
); <ButtonPrimary
})} style={{ marginLeft: 0, marginTop: 8 }}
</select> onClick={() => goToWalletManualWithdraw(selectedCurrency)}
</NiceSelect>
)}
{currencyAmount && (
<h2 style={{ margin: 0 }}>
{Amounts.stringifyValue(currencyAmount)}
</h2>
)}
</p>
<div style={{ marginLeft: "auto", width: "fit-content" }}>
<ButtonPrimary
onClick={() => goToWalletManualWithdraw(selectedCurrency)}
>
Withdraw
</ButtonPrimary>
{currencyAmount && Amounts.isNonZero(currencyAmount) && (
<ButtonBoxPrimary
onClick={() => goToWalletDeposit(selectedCurrency)}
> >
Deposit Withdraw
</ButtonBoxPrimary> </ButtonPrimary>
)} {currencyAmount && Amounts.isNonZero(currencyAmount) && (
<ButtonBoxPrimary
style={{ marginLeft: 0, marginTop: 8 }}
onClick={() => goToWalletDeposit(selectedCurrency)}
>
Deposit
</ButtonBoxPrimary>
)}
</div>
</div> </div>
</section> </section>
{datesWithTransaction.length === 0 ? ( {datesWithTransaction.length === 0 ? (
@ -205,11 +226,7 @@ export function HistoryView({
/> />
</DateSeparator> </DateSeparator>
{byDate[d].map((tx, i) => ( {byDate[d].map((tx, i) => (
<TransactionItem <TransactionItem key={i} tx={tx} />
key={i}
tx={tx}
multiCurrency={multiCurrency}
/>
))} ))}
</Fragment> </Fragment>
); );

View File

@ -21,10 +21,10 @@
import { createExample } from "../test-utils"; import { createExample } from "../test-utils";
import { queryToSlashKeys } from "../utils/index"; import { queryToSlashKeys } from "../utils/index";
import { LastActivityPage as TestedComponent } from "./LastActivityPage"; import { Pending as TestedComponent } from "./PendingPage";
export default { export default {
title: "wallet/last activity", title: "wallet/pending",
component: TestedComponent, component: TestedComponent,
}; };

View File

@ -19,7 +19,7 @@ import { useState } from "preact/hooks";
import { ButtonPrimary } from "../components/styled"; import { ButtonPrimary } from "../components/styled";
import { AddNewActionView } from "./AddNewActionView"; import { AddNewActionView } from "./AddNewActionView";
export function LastActivityPage(): VNode { export function Pending(): VNode {
const [addingAction, setAddingAction] = useState(false); const [addingAction, setAddingAction] = useState(false);
if (addingAction) { if (addingAction) {

View File

@ -38,12 +38,11 @@ import { strings } from "./i18n/strings";
import { NavBar, Pages } from "./NavigationBar"; import { NavBar, Pages } from "./NavigationBar";
import { DeveloperPage } from "./popup/DeveloperPage"; import { DeveloperPage } from "./popup/DeveloperPage";
import { BackupPage } from "./wallet/BackupPage"; import { BackupPage } from "./wallet/BackupPage";
import { BalancePage } from "./wallet/BalancePage";
import { DepositPage } from "./wallet/DepositPage"; 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 { LastActivityPage } from "./wallet/LastActivityPage";
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";
@ -124,19 +123,10 @@ function Application(): VNode {
<Route <Route
path={Pages.balance} path={Pages.balance}
component={BalancePage} component={Redirect}
goToWalletManualWithdraw={() => to={Pages.balance_history.replace(":currency", "")}
route(
Pages.balance_manual_withdraw.replace(":currency?", ""),
)
}
goToWalletDeposit={(currency: string) =>
route(Pages.balance_deposit.replace(":currency", currency))
}
goToWalletHistory={(currency: string) =>
route(Pages.balance_history.replace(":currency", currency))
}
/> />
<Route <Route
path={Pages.balance_history} path={Pages.balance_history}
component={HistoryPage} component={HistoryPage}
@ -173,12 +163,9 @@ function Application(): VNode {
}} }}
/> />
{/** {/**
* LAST ACTIVITY * PENDING
*/} */}
<Route <Route path={Pages.pending} component={Pending} />
path={Pages.last_activity}
component={LastActivityPage}
/>
<Route path={Pages.settings} component={SettingsPage} /> <Route path={Pages.settings} component={SettingsPage} />
{/** {/**
@ -246,7 +233,11 @@ function Application(): VNode {
{/** {/**
* NOT FOUND * NOT FOUND
*/} */}
<Route default component={Redirect} to={Pages.balance} /> <Route
default
component={Redirect}
to={Pages.balance_history.replace(":currency", "")}
/>
</Router> </Router>
</WalletBox> </WalletBox>
</IoCProviderForRuntime> </IoCProviderForRuntime>