add age restriction option to withdraw cta
This commit is contained in:
parent
4491118494
commit
7a2fe8018f
@ -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;
|
||||
|
@ -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 ||
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user