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");
return currencyFormatter.format(Number(s));
const currencyFormatter = new Intl.NumberFormat("en-US", {
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 };
}
@ -199,6 +199,27 @@ getExchangeDetails.makeContext = (db: DbAccess<typeof WalletStoresV1>) =>
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(
ws: InternalWalletState,
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.
* Optionally link the reserve entry to the new or existing
@ -479,7 +530,7 @@ async function updateExchangeFromUrlImpl(
logger.info("updating exchange /keys info");
const timeout = getExchangeRequestTimeout(r);
const timeout = getExchangeRequestTimeout();
const keysInfo = await downloadKeysInfo(baseUrl, ws.http, timeout);
@ -507,33 +558,10 @@ async function updateExchangeFromUrlImpl(
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");

View File

@ -89,9 +89,12 @@ import {
} from "./operations/deposits.js";
import {
acceptExchangeTermsOfService,
downloadTosFromAcceptedFormat,
getExchangeDetails,
getExchangeRequestTimeout,
getExchangeTrust,
updateExchangeFromUrl
updateExchangeFromUrl,
updateExchangeTermsOfService
} from "./operations/exchanges.js";
import { getMerchantInfo } from "./operations/merchants.js";
import {
@ -441,7 +444,6 @@ async function getExchangeTos(
ws,
exchangeBaseUrl,
acceptedFormat,
true,
);
const content = exchangeDetails.termsOfServiceText;
const currentEtag = exchangeDetails.termsOfServiceLastEtag;
@ -453,12 +455,34 @@ async function getExchangeTos(
) {
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 {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
currentEtag,
content,
contentType,
currentEtag: tosDownload.tosEtag,
content: tosDownload.tosText,
contentType: tosDownload.tosContentType,
};
}
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_add = "/backup/provider/add",
last_activity = "/last-activity",
pending = "/pending",
settings = "/settings",
settings_exchange_add = "/settings/exchange/add",
@ -84,10 +84,7 @@ export function NavBar({
<PopupNavigation devMode={devMode}>
<div>
<Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
<Tab
target="/last-activity"
current={path}
>{i18n.str`Last Activity`}</Tab>
<Tab target="/pending" current={path}>{i18n.str`Pending`}</Tab>
<Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab>
<Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab>
{devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>}

View File

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

View File

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

View File

@ -39,7 +39,7 @@ import { SettingsPage } from "./popup/Settings";
import { TalerActionFound } from "./popup/TalerActionFound";
import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
import { IoCProviderForRuntime } from "./context/iocContext";
import { LastActivityPage } from "./wallet/LastActivityPage";
import { Pending } from "./wallet/PendingPage";
import { Match } from "preact-router/match";
function main(): void {
@ -125,7 +125,7 @@ function Application(): VNode {
}}
/>
<Route path={Pages.last_activity} component={LastActivityPage} />
<Route path={Pages.pending} component={Pending} />
<Route
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,
ButtonBoxWarning,
ButtonPrimary,
CenteredBoldText,
CenteredText,
DateSeparator,
NiceSelect,
WarningBox,
@ -126,8 +128,6 @@ export function HistoryView({
}, {} as { [x: string]: Transaction[] });
const datesWithTransaction = Object.keys(byDate);
const multiCurrency = balances.length > 1;
if (balances.length === 0 || !selectedCurrency) {
return (
<WarningBox>
@ -143,52 +143,73 @@ export function HistoryView({
return (
<Fragment>
<section>
<p
<div
style={{
display: "flex",
justifyContent: "space-between",
flexWrap: "wrap",
alignItems: "center",
justifyContent: "space-between",
}}
>
{currencies.length === 1 ? (
<div style={{ fontSize: "large" }}>{selectedCurrency}</div>
) : (
<NiceSelect>
<select
value={currencyIndex}
onChange={(e) => {
setCurrencyIndex(Number(e.currentTarget.value));
<div
style={{
width: "fit-content",
display: "flex",
}}
>
{currencies.length === 1 ? (
<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) => {
return (
<option value={index} key={currency}>
{currency}
</option>
);
})}
</select>
</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)}
{Amounts.stringifyValue(currencyAmount)}
</CenteredBoldText>
)}
</div>
<div>
<ButtonPrimary
style={{ marginLeft: 0, marginTop: 8 }}
onClick={() => goToWalletManualWithdraw(selectedCurrency)}
>
Deposit
</ButtonBoxPrimary>
)}
Withdraw
</ButtonPrimary>
{currencyAmount && Amounts.isNonZero(currencyAmount) && (
<ButtonBoxPrimary
style={{ marginLeft: 0, marginTop: 8 }}
onClick={() => goToWalletDeposit(selectedCurrency)}
>
Deposit
</ButtonBoxPrimary>
)}
</div>
</div>
</section>
{datesWithTransaction.length === 0 ? (
@ -205,11 +226,7 @@ export function HistoryView({
/>
</DateSeparator>
{byDate[d].map((tx, i) => (
<TransactionItem
key={i}
tx={tx}
multiCurrency={multiCurrency}
/>
<TransactionItem key={i} tx={tx} />
))}
</Fragment>
);

View File

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

View File

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

View File

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