add age restriction option to withdraw cta

This commit is contained in:
Sebastian 2022-05-04 16:25:53 -03:00
parent 4491118494
commit 7a2fe8018f
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
11 changed files with 167 additions and 25 deletions

View File

@ -17,8 +17,8 @@
import { h, VNode } from "preact";
interface Props {
enabled: boolean;
onToggle: () => void;
enabled?: boolean;
onToggle?: () => void;
label: VNode;
name: string;
description?: VNode;

View File

@ -20,7 +20,7 @@ import { NiceSelect } from "./styled/index.js";
interface Props {
value?: string;
onChange: (s: string) => void;
onChange?: (s: string) => void;
label: VNode;
list: {
[label: string]: string;
@ -28,6 +28,7 @@ interface Props {
name: string;
description?: string;
canBeNull?: boolean;
maxWidth?: boolean;
}
export function SelectList({
@ -36,6 +37,7 @@ export function SelectList({
list,
onChange,
label,
maxWidth,
description,
canBeNull,
}: Props): VNode {
@ -53,8 +55,9 @@ export function SelectList({
<select
name={name}
value={value}
style={maxWidth ? { width: "100%" } : undefined}
onChange={(e) => {
onChange(e.currentTarget.value);
if (onChange) onChange(e.currentTarget.value);
}}
>
{value === undefined ||

View File

@ -101,6 +101,42 @@ export const NoEnoughBalance = createExample(TestedComponent, {
goToWalletManualWithdraw: () => null,
});
export const EnoughBalanceButRestricted = createExample(TestedComponent, {
state: {
status: "ready",
hook: undefined,
amount: Amounts.parseOrThrow("USD:10"),
balance: {
currency: "USD",
fraction: 40000000,
value: 19,
},
payHandler: {
onClick: async () => {
null;
},
},
totalFees: Amounts.parseOrThrow("USD:0"),
payResult: undefined,
uri: "",
payStatus: {
status: PreparePayResultType.InsufficientBalance,
noncePriv: "",
proposalId: "proposal1234",
contractTerms: {
merchant: {
name: "someone",
},
summary: "some beers",
amount: "USD:10",
} as Partial<ContractTerms> as any,
amountRaw: "USD:10",
},
},
goBack: () => null,
goToWalletManualWithdraw: () => null,
});
export const PaymentPossible = createExample(TestedComponent, {
state: {
status: "ready",

View File

@ -542,23 +542,22 @@ function ButtonsSection({
);
}
if (payStatus.status === PreparePayResultType.InsufficientBalance) {
let BalanceMessage = "";
if (!state.balance) {
BalanceMessage = i18n.str`You have no balance for this currency. Withdraw digital cash first.`;
} else {
const balanceShouldBeEnough =
Amounts.cmp(state.balance, state.amount) !== -1;
if (balanceShouldBeEnough) {
BalanceMessage = i18n.str`Could not find enough coins to pay this order. Even if you have enough ${state.balance.currency} some restriction may apply.`;
} else {
BalanceMessage = i18n.str`Your current balance is not enough for this order.`;
}
}
return (
<Fragment>
<section>
{state.balance ? (
<WarningBox>
<i18n.Translate>
Your balance of {<Amount value={state.balance} />} is not
enough to pay for this purchase
</i18n.Translate>
</WarningBox>
) : (
<WarningBox>
<i18n.Translate>
Your balance is not enough to pay for this purchase.
</i18n.Translate>
</WarningBox>
)}
<WarningBox>{BalanceMessage}</WarningBox>
</section>
<section>
<ButtonSuccess

View File

@ -50,12 +50,24 @@ const normalTosState = {
reviewing: false,
};
const ageRestrictionOptions: Record<string, string> = "6:12:18"
.split(":")
.reduce((p, c) => ({ ...p, [c]: `under ${c}` }), {});
ageRestrictionOptions["0"] = "Not restricted";
const ageRestrictionSelectField = {
list: ageRestrictionOptions,
value: "0",
};
export const TermsOfServiceNotYetLoaded = createExample(TestedComponent, {
state: {
hook: undefined,
status: "success",
cancelEditExchange: nullHandler,
confirmEditExchange: nullHandler,
ageRestriction: ageRestrictionSelectField,
chosenAmount: {
currency: "USD",
value: 2,
@ -91,6 +103,7 @@ export const WithSomeFee = createExample(TestedComponent, {
status: "success",
cancelEditExchange: nullHandler,
confirmEditExchange: nullHandler,
ageRestriction: ageRestrictionSelectField,
chosenAmount: {
currency: "USD",
value: 2,
@ -127,6 +140,7 @@ export const WithoutFee = createExample(TestedComponent, {
status: "success",
cancelEditExchange: nullHandler,
confirmEditExchange: nullHandler,
ageRestriction: ageRestrictionSelectField,
chosenAmount: {
currency: "USD",
value: 2,
@ -163,6 +177,7 @@ export const EditExchangeUntouched = createExample(TestedComponent, {
status: "success",
cancelEditExchange: nullHandler,
confirmEditExchange: nullHandler,
ageRestriction: ageRestrictionSelectField,
chosenAmount: {
currency: "USD",
value: 2,
@ -199,6 +214,7 @@ export const EditExchangeModified = createExample(TestedComponent, {
status: "success",
cancelEditExchange: nullHandler,
confirmEditExchange: nullHandler,
ageRestriction: ageRestrictionSelectField,
chosenAmount: {
currency: "USD",
value: 2,
@ -236,3 +252,40 @@ export const CompletedWithoutBankURL = createExample(TestedComponent, {
hook: undefined,
},
});
export const WithAgeRestrictionSelected = createExample(TestedComponent, {
state: {
hook: undefined,
status: "success",
cancelEditExchange: nullHandler,
confirmEditExchange: nullHandler,
ageRestriction: ageRestrictionSelectField,
chosenAmount: {
currency: "USD",
value: 2,
fraction: 10000000,
},
doWithdrawal: nullHandler,
editExchange: nullHandler,
exchange: {
list: exchangeList,
value: "exchange.demo.taler.net",
onChange: async () => {
null;
},
},
showExchangeSelection: false,
mustAcceptFirst: false,
withdrawalFee: {
currency: "USD",
fraction: 0,
value: 0,
},
toBeReceived: {
currency: "USD",
fraction: 0,
value: 2,
},
tosProps: normalTosState,
},
});

View File

@ -35,6 +35,7 @@ import { SelectList } from "../components/SelectList.js";
import {
ButtonSuccess,
ButtonWarning,
Input,
LinkSuccess,
SubTitle,
SuccessBox,
@ -43,12 +44,18 @@ import {
import { useTranslationContext } from "../context/translation.js";
import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { buildTermsOfServiceState } from "../utils/index.js";
import { ButtonHandler, SelectFieldHandler } from "../mui/handlers.js";
import {
ButtonHandler,
SelectFieldHandler,
ToggleHandler,
} from "../mui/handlers.js";
import * as wxApi from "../wxApi.js";
import {
Props as TermsOfServiceSectionProps,
TermsOfServiceSection,
} from "./TermsOfServiceSection.js";
import { startOfWeekYear } from "date-fns/esm";
import { Checkbox } from "../components/Checkbox.js";
interface Props {
talerWithdrawUri?: string;
@ -97,6 +104,8 @@ type Success = {
doWithdrawal: ButtonHandler;
tosProps?: TermsOfServiceSectionProps;
mustAcceptFirst: boolean;
ageRestriction: SelectFieldHandler;
};
export function useComponentState(
@ -106,6 +115,7 @@ export function useComponentState(
const [customExchange, setCustomExchange] = useState<string | undefined>(
undefined,
);
const [ageRestricted, setAgeRestricted] = useState(0);
/**
* Ask the wallet about the withdraw URI
@ -228,6 +238,7 @@ export function useComponentState(
const res = await api.acceptWithdrawal(
talerWithdrawUri,
selectedExchange,
!ageRestricted ? undefined : ageRestricted,
);
if (res.confirmTransferUrl) {
document.location.href = res.confirmTransferUrl;
@ -320,6 +331,14 @@ export function useComponentState(
termsState !== undefined &&
(termsState.status === "changed" || termsState.status === "new");
const ageRestrictionOptions: Record<string, string> | undefined = "6:12:18"
.split(":")
.reduce((p, c) => ({ ...p, [c]: `under ${c}` }), {});
if (ageRestrictionOptions) {
ageRestrictionOptions["0"] = "Not restricted";
}
return {
status: "success",
hook: undefined,
@ -331,6 +350,11 @@ export function useComponentState(
toBeReceived,
withdrawalFee,
chosenAmount: amount,
ageRestriction: {
list: ageRestrictionOptions,
value: String(ageRestricted),
onChange: async (v) => setAgeRestricted(parseInt(v, 10)),
},
doWithdrawal: {
onClick:
doingWithdraw || (mustAcceptFirst && !reviewed)
@ -486,6 +510,18 @@ export function View({ state }: { state: State }): VNode {
</LinkSuccess>
)}
</section>
<section>
<Input>
<SelectList
label={<i18n.Translate>Age restriction</i18n.Translate>}
list={state.ageRestriction.list}
name="age"
maxWidth
value={state.ageRestriction.value}
onChange={state.ageRestriction.onChange}
/>
</Input>
</section>
{state.tosProps && <TermsOfServiceSection {...state.tosProps} />}
{state.tosProps ? (
<section>

View File

@ -17,7 +17,7 @@ export interface ToggleHandler {
}
export interface SelectFieldHandler {
onChange: (value: string) => Promise<void>;
onChange?: (value: string) => Promise<void>;
error?: string;
value: string;
isDirty?: boolean;

View File

@ -87,7 +87,7 @@ describe("CreateManualWithdraw states", () => {
const { exchange, currency } = getLastResultOrThrow()
expect(exchange.value).equal("url2")
if (currency.onChange === undefined) expect.fail();
currency.onChange("USD")
}
@ -111,6 +111,7 @@ describe("CreateManualWithdraw states", () => {
expect(exchange.value).equal("url2")
expect(currency.value).equal("ARS")
if (exchange.onChange === undefined) expect.fail();
exchange.onChange("url1")
}
@ -205,6 +206,7 @@ async function defaultTestForInputSelect(awaiter: () => Promise<void>, getField:
throw new Error('no enough values')
}
nextValue = keys[nextIdx]
if (field.onChange === undefined) expect.fail();
field.onChange(nextValue)
}

View File

@ -258,6 +258,7 @@ describe("DepositPage states", () => {
expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`))
if (r.account.onChange === undefined) expect.fail();
r.account.onChange("1")
}
@ -290,6 +291,7 @@ describe("DepositPage states", () => {
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`))
expect(r.depositHandler.onClick).undefined;
if (r.account.onChange === undefined) expect.fail();
r.account.onChange("0")
}

View File

@ -81,6 +81,7 @@ export function DeveloperPage(): VNode {
type CoinsInfo = CoinDumpJson["coins"];
type CalculatedCoinfInfo = {
ageKeysCount: number | undefined;
denom_value: number;
remain_value: number;
status: string;
@ -132,11 +133,13 @@ export function View({
const money_by_exchange = coins.reduce(
(prev, cur) => {
const denom = Amounts.parseOrThrow(cur.denom_value);
console.log(cur);
if (!prev[cur.exchange_base_url]) {
prev[cur.exchange_base_url] = [];
currencies[cur.exchange_base_url] = denom.currency;
}
prev[cur.exchange_base_url].push({
ageKeysCount: cur.ageCommitmentProof?.proof.privateKeys.length,
denom_value: parseFloat(Amounts.stringifyValue(denom)),
remain_value: parseFloat(
Amounts.stringifyValue(Amounts.parseOrThrow(cur.remaining_value)),
@ -305,7 +308,7 @@ function ShowAllCoins({
<p>
<b>{ex}</b>: {total} {currencies[ex]}
</p>
<p>
<p onClick={() => setCollapsedUnspent(true)}>
<b>
<i18n.Translate>usable coins</i18n.Translate>
</b>
@ -313,7 +316,7 @@ function ShowAllCoins({
{collapsedUnspent ? (
<div onClick={() => setCollapsedUnspent(false)}>click to show</div>
) : (
<table onClick={() => setCollapsedUnspent(true)}>
<table>
<tr>
<td>
<i18n.Translate>id</i18n.Translate>
@ -330,6 +333,9 @@ function ShowAllCoins({
<td>
<i18n.Translate>from refresh?</i18n.Translate>
</td>
<td>
<i18n.Translate>age key count</i18n.Translate>
</td>
</tr>
{coins.usable.map((c, idx) => {
return (
@ -339,12 +345,13 @@ function ShowAllCoins({
<td>{c.remain_value}</td>
<td>{c.status}</td>
<td>{c.from_refresh ? "true" : "false"}</td>
<td>{String(c.ageKeysCount)}</td>
</tr>
);
})}
</table>
)}
<p>
<p onClick={() => setCollapsedSpent(true)}>
<i18n.Translate>spent coins</i18n.Translate>
</p>
{collapsedSpent ? (
@ -352,7 +359,7 @@ function ShowAllCoins({
<i18n.Translate>click to show</i18n.Translate>
</div>
) : (
<table onClick={() => setCollapsedSpent(true)}>
<table>
<tr>
<td>
<i18n.Translate>id</i18n.Translate>

View File

@ -324,10 +324,12 @@ export function preparePay(talerPayUri: string): Promise<PreparePayResult> {
export function acceptWithdrawal(
talerWithdrawUri: string,
selectedExchange: string,
restrictAge?: number,
): Promise<AcceptWithdrawalResponse> {
return callBackend("acceptBankIntegratedWithdrawal", {
talerWithdrawUri,
exchangeBaseUrl: selectedExchange,
restrictAge
});
}
@ -340,10 +342,12 @@ export function acceptWithdrawal(
export function acceptManualWithdrawal(
exchangeBaseUrl: string,
amount: string,
restrictAge?: number,
): Promise<AcceptManualWithdrawalResult> {
return callBackend("acceptManualWithdrawal", {
amount,
exchangeBaseUrl,
restrictAge
});
}