withdraw call to action
This commit is contained in:
parent
7a600514c6
commit
dce055d0d3
@ -837,7 +837,11 @@ export const NavigationHeader = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SvgIcon = styled.div<{ color: string }>`
|
export const SvgIcon = styled.div<{
|
||||||
|
title: string;
|
||||||
|
color: string;
|
||||||
|
onClick?: any;
|
||||||
|
}>`
|
||||||
& > svg {
|
& > svg {
|
||||||
fill: ${({ color }) => color};
|
fill: ${({ color }) => color};
|
||||||
}
|
}
|
||||||
@ -846,6 +850,7 @@ export const SvgIcon = styled.div<{ color: string }>`
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
cursor: ${({ onClick }) => (onClick ? "pointer" : "inherit")};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Icon = styled.div`
|
export const Icon = styled.div`
|
||||||
|
@ -28,6 +28,7 @@ import { CompletedView, LoadingUriView, ReadyView } from "./views.js";
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
talerDepositUri: string | undefined,
|
talerDepositUri: string | undefined,
|
||||||
amountStr: AmountString | undefined,
|
amountStr: AmountString | undefined,
|
||||||
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -53,6 +54,7 @@ export namespace State {
|
|||||||
cost: AmountJson;
|
cost: AmountJson;
|
||||||
effective: AmountJson;
|
effective: AmountJson;
|
||||||
confirm: ButtonHandler;
|
confirm: ButtonHandler;
|
||||||
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
export interface Completed {
|
export interface Completed {
|
||||||
status: "completed";
|
status: "completed";
|
||||||
|
@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerDepositUri, amountStr }: Props,
|
{ talerDepositUri, amountStr, cancel }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [result, setResult] = useState<CreateDepositGroupResponse | undefined>(
|
const [result, setResult] = useState<CreateDepositGroupResponse | undefined>(
|
||||||
@ -72,5 +72,6 @@ export function useComponentState(
|
|||||||
.amount,
|
.amount,
|
||||||
cost: deposit.totalDepositCost,
|
cost: deposit.totalDepositCost,
|
||||||
effective: deposit.effectiveDepositAmount,
|
effective: deposit.effectiveDepositAmount,
|
||||||
|
cancel,
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -30,7 +30,7 @@ describe("Deposit CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerDepositUri: undefined, amountStr: undefined }, {
|
useComponentState({ talerDepositUri: undefined, amountStr: undefined, cancel: async () => { null } }, {
|
||||||
prepareRefund: async () => ({}),
|
prepareRefund: async () => ({}),
|
||||||
applyRefund: async () => ({}),
|
applyRefund: async () => ({}),
|
||||||
onUpdateNotification: async () => ({}),
|
onUpdateNotification: async () => ({}),
|
||||||
@ -61,7 +61,7 @@ describe("Deposit CTA states", () => {
|
|||||||
it("should be ready after loading", async () => {
|
it("should be ready after loading", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerDepositUri: "payto://refund/asdasdas", amountStr: "EUR:1" }, {
|
useComponentState({ talerDepositUri: "payto://refund/asdasdas", amountStr: "EUR:1", cancel: async () => { null } }, {
|
||||||
prepareDeposit: async () =>
|
prepareDeposit: async () =>
|
||||||
({
|
({
|
||||||
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
|
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
|
||||||
|
@ -100,7 +100,7 @@ export function ReadyView(state: State.Ready): VNode {
|
|||||||
onClick={state.confirm.onClick}
|
onClick={state.confirm.onClick}
|
||||||
>
|
>
|
||||||
<i18n.Translate>
|
<i18n.Translate>
|
||||||
Deposit {<Amount value={state.effective} />}
|
Send {<Amount value={state.cost} />}
|
||||||
</i18n.Translate>
|
</i18n.Translate>
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
|
@ -28,7 +28,7 @@ import { LoadingUriView, BaseView } from "./views.js";
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
talerPayUri?: string;
|
talerPayUri?: string;
|
||||||
goToWalletManualWithdraw: (currency?: string) => Promise<void>;
|
goToWalletManualWithdraw: (currency?: string) => Promise<void>;
|
||||||
goBack: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -56,7 +56,7 @@ export namespace State {
|
|||||||
uri: string;
|
uri: string;
|
||||||
error: undefined;
|
error: undefined;
|
||||||
goToWalletManualWithdraw: (currency?: string) => Promise<void>;
|
goToWalletManualWithdraw: (currency?: string) => Promise<void>;
|
||||||
goBack: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
export interface NoBalanceForCurrency extends BaseInfo {
|
export interface NoBalanceForCurrency extends BaseInfo {
|
||||||
status: "no-balance-for-currency"
|
status: "no-balance-for-currency"
|
||||||
|
@ -24,7 +24,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerPayUri, goBack, goToWalletManualWithdraw }: Props,
|
{ talerPayUri, cancel, goToWalletManualWithdraw }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(
|
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(
|
||||||
@ -82,7 +82,7 @@ export function useComponentState(
|
|||||||
uri: hook.response.uri,
|
uri: hook.response.uri,
|
||||||
amount,
|
amount,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
goBack, goToWalletManualWithdraw
|
cancel, goToWalletManualWithdraw
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundBalance) {
|
if (!foundBalance) {
|
||||||
|
@ -70,7 +70,7 @@ describe("Payment CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: undefined, goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState({ talerPayUri: undefined, cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
} as Partial<typeof wxApi> as any),
|
} as Partial<typeof wxApi> as any),
|
||||||
);
|
);
|
||||||
@ -98,7 +98,7 @@ describe("Payment CTA states", () => {
|
|||||||
it("should response with no balance", async () => {
|
it("should response with no balance", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
@ -133,7 +133,7 @@ describe("Payment CTA states", () => {
|
|||||||
it("should not be able to pay if there is no enough balance", async () => {
|
it("should not be able to pay if there is no enough balance", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
@ -172,7 +172,7 @@ describe("Payment CTA states", () => {
|
|||||||
it("should be able to pay (without fee)", async () => {
|
it("should be able to pay (without fee)", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
@ -214,7 +214,7 @@ describe("Payment CTA states", () => {
|
|||||||
it("should be able to pay (with fee)", async () => {
|
it("should be able to pay (with fee)", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
@ -256,7 +256,7 @@ describe("Payment CTA states", () => {
|
|||||||
it("should get confirmation done after pay successfully", async () => {
|
it("should get confirmation done after pay successfully", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
@ -317,7 +317,7 @@ describe("Payment CTA states", () => {
|
|||||||
it("should not stay in ready state after pay with error", async () => {
|
it("should not stay in ready state after pay with error", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
||||||
onUpdateNotification: nullFunction,
|
onUpdateNotification: nullFunction,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
@ -393,7 +393,7 @@ describe("Payment CTA states", () => {
|
|||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
||||||
onUpdateNotification: subscriptions.saveSubscription,
|
onUpdateNotification: subscriptions.saveSubscription,
|
||||||
preparePay: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
|
@ -74,7 +74,7 @@ export function BaseView(state: SupportedStates): VNode {
|
|||||||
? Amounts.parseOrThrow(state.payStatus.amountEffective)
|
? Amounts.parseOrThrow(state.payStatus.amountEffective)
|
||||||
: state.amount,
|
: state.amount,
|
||||||
};
|
};
|
||||||
const totalFees = Amounts.sub(price.effective, price.raw).amount;
|
// const totalFees = Amounts.sub(price.effective, price.raw).amount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WalletAction>
|
<WalletAction>
|
||||||
@ -168,7 +168,7 @@ export function BaseView(state: SupportedStates): VNode {
|
|||||||
goToWalletManualWithdraw={state.goToWalletManualWithdraw}
|
goToWalletManualWithdraw={state.goToWalletManualWithdraw}
|
||||||
/>
|
/>
|
||||||
<section>
|
<section>
|
||||||
<Link upperCased onClick={state.goBack}>
|
<Link upperCased onClick={state.cancel}>
|
||||||
<i18n.Translate>Cancel</i18n.Translate>
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
@ -358,7 +358,7 @@ function ButtonsSection({
|
|||||||
onClick={state.payHandler.onClick}
|
onClick={state.payHandler.onClick}
|
||||||
>
|
>
|
||||||
<i18n.Translate>
|
<i18n.Translate>
|
||||||
Pay
|
Send
|
||||||
{<Amount value={state.payStatus.amountEffective} />}
|
{<Amount value={state.payStatus.amountEffective} />}
|
||||||
</i18n.Translate>
|
</i18n.Translate>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -27,6 +27,7 @@ import { CompletedView, IgnoredView, InProgressView, LoadingUriView, ReadyView }
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerRefundUri?: string;
|
talerRefundUri?: string;
|
||||||
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -64,6 +65,7 @@ export namespace State {
|
|||||||
accept: ButtonHandler;
|
accept: ButtonHandler;
|
||||||
ignore: ButtonHandler;
|
ignore: ButtonHandler;
|
||||||
orderId: string;
|
orderId: string;
|
||||||
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Ignored extends BaseInfo {
|
export interface Ignored extends BaseInfo {
|
||||||
|
@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerRefundUri }: Props,
|
{ talerRefundUri, cancel }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [ignored, setIgnored] = useState(false);
|
const [ignored, setIgnored] = useState(false);
|
||||||
@ -100,5 +100,6 @@ export function useComponentState(
|
|||||||
ignore: {
|
ignore: {
|
||||||
onClick: doIgnore,
|
onClick: doIgnore,
|
||||||
},
|
},
|
||||||
|
cancel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ describe("Refund CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerRefundUri: undefined }, {
|
useComponentState({ talerRefundUri: undefined, cancel: async () => { null } }, {
|
||||||
prepareRefund: async () => ({}),
|
prepareRefund: async () => ({}),
|
||||||
applyRefund: async () => ({}),
|
applyRefund: async () => ({}),
|
||||||
onUpdateNotification: async () => ({}),
|
onUpdateNotification: async () => ({}),
|
||||||
@ -64,7 +64,7 @@ describe("Refund CTA states", () => {
|
|||||||
it("should be ready after loading", async () => {
|
it("should be ready after loading", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerRefundUri: "taler://refund/asdasdas" }, {
|
useComponentState({ talerRefundUri: "taler://refund/asdasdas", cancel: async () => { null } }, {
|
||||||
prepareRefund: async () =>
|
prepareRefund: async () =>
|
||||||
({
|
({
|
||||||
effectivePaid: "EUR:2",
|
effectivePaid: "EUR:2",
|
||||||
@ -113,7 +113,7 @@ describe("Refund CTA states", () => {
|
|||||||
it("should be ignored after clicking the ignore button", async () => {
|
it("should be ignored after clicking the ignore button", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerRefundUri: "taler://refund/asdasdas" }, {
|
useComponentState({ talerRefundUri: "taler://refund/asdasdas", cancel: async () => { null } }, {
|
||||||
prepareRefund: async () =>
|
prepareRefund: async () =>
|
||||||
({
|
({
|
||||||
effectivePaid: "EUR:2",
|
effectivePaid: "EUR:2",
|
||||||
@ -189,7 +189,7 @@ describe("Refund CTA states", () => {
|
|||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerRefundUri: "taler://refund/asdasdas" }, {
|
useComponentState({ talerRefundUri: "taler://refund/asdasdas", cancel: async () => { null } }, {
|
||||||
prepareRefund: async () =>
|
prepareRefund: async () =>
|
||||||
({
|
({
|
||||||
awaiting: Amounts.stringify(awaiting),
|
awaiting: Amounts.stringify(awaiting),
|
||||||
|
@ -20,7 +20,7 @@ import { Amount } from "../../components/Amount.js";
|
|||||||
import { LoadingError } from "../../components/LoadingError.js";
|
import { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||||
import { Part } from "../../components/Part.js";
|
import { Part } from "../../components/Part.js";
|
||||||
import { SubTitle, WalletAction } from "../../components/styled/index.js";
|
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
import { ProductList } from "../Payment/views.js";
|
import { ProductList } from "../Payment/views.js";
|
||||||
@ -163,10 +163,21 @@ export function ReadyView(state: State.Ready): VNode {
|
|||||||
</section>
|
</section>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
<section>
|
<section>
|
||||||
<Button variant="contained" onClick={state.accept.onClick}>
|
<Button
|
||||||
<i18n.Translate>Confirm refund</i18n.Translate>
|
variant="contained"
|
||||||
|
color="success"
|
||||||
|
onClick={state.accept.onClick}
|
||||||
|
>
|
||||||
|
<i18n.Translate>
|
||||||
|
Receive <Amount value={state.amount} />
|
||||||
|
</i18n.Translate>
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<Link upperCased onClick={state.cancel}>
|
||||||
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
</WalletAction>
|
</WalletAction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import { AcceptedView, IgnoredView, LoadingUriView, ReadyView } from "./views.js
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerTipUri?: string;
|
talerTipUri?: string;
|
||||||
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -69,7 +70,7 @@ export namespace State {
|
|||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
accept: ButtonHandler;
|
accept: ButtonHandler;
|
||||||
ignore: ButtonHandler;
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerTipUri }: Props,
|
{ talerTipUri, cancel }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [tipIgnored, setTipIgnored] = useState(false);
|
const [tipIgnored, setTipIgnored] = useState(false);
|
||||||
@ -53,10 +53,6 @@ export function useComponentState(
|
|||||||
tipInfo.retry();
|
tipInfo.retry();
|
||||||
};
|
};
|
||||||
|
|
||||||
const doIgnore = async (): Promise<void> => {
|
|
||||||
setTipIgnored(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const baseInfo = {
|
const baseInfo = {
|
||||||
merchantBaseUrl: tip.merchantBaseUrl,
|
merchantBaseUrl: tip.merchantBaseUrl,
|
||||||
exchangeBaseUrl: tip.exchangeBaseUrl,
|
exchangeBaseUrl: tip.exchangeBaseUrl,
|
||||||
@ -84,9 +80,7 @@ export function useComponentState(
|
|||||||
accept: {
|
accept: {
|
||||||
onClick: doAccept,
|
onClick: doAccept,
|
||||||
},
|
},
|
||||||
ignore: {
|
cancel,
|
||||||
onClick: doIgnore,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,5 +42,4 @@ export const Ready = createExample(ReadyView, {
|
|||||||
merchantBaseUrl: "http://merchant.url/",
|
merchantBaseUrl: "http://merchant.url/",
|
||||||
exchangeBaseUrl: "http://exchange.url/",
|
exchangeBaseUrl: "http://exchange.url/",
|
||||||
accept: {},
|
accept: {},
|
||||||
ignore: {},
|
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@ describe("Tip CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: undefined }, {
|
useComponentState({ talerTipUri: undefined, cancel: async () => { null } }, {
|
||||||
prepareTip: async () => ({}),
|
prepareTip: async () => ({}),
|
||||||
acceptTip: async () => ({}),
|
acceptTip: async () => ({}),
|
||||||
} as any),
|
} as any),
|
||||||
@ -62,7 +62,7 @@ describe("Tip CTA states", () => {
|
|||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: "taler://tip/asd" }, {
|
useComponentState({ talerTipUri: "taler://tip/asd", cancel: async () => { null } }, {
|
||||||
prepareTip: async () =>
|
prepareTip: async () =>
|
||||||
({
|
({
|
||||||
accepted: tipAccepted,
|
accepted: tipAccepted,
|
||||||
@ -114,7 +114,7 @@ describe("Tip CTA states", () => {
|
|||||||
it("should be ignored after clicking the ignore button", async () => {
|
it("should be ignored after clicking the ignore button", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: "taler://tip/asd" }, {
|
useComponentState({ talerTipUri: "taler://tip/asd", cancel: async () => { null } }, {
|
||||||
prepareTip: async () =>
|
prepareTip: async () =>
|
||||||
({
|
({
|
||||||
exchangeBaseUrl: "exchange url",
|
exchangeBaseUrl: "exchange url",
|
||||||
@ -142,25 +142,25 @@ describe("Tip CTA states", () => {
|
|||||||
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1"));
|
||||||
expect(state.merchantBaseUrl).eq("merchant url");
|
expect(state.merchantBaseUrl).eq("merchant url");
|
||||||
expect(state.exchangeBaseUrl).eq("exchange url");
|
expect(state.exchangeBaseUrl).eq("exchange url");
|
||||||
if (state.ignore.onClick === undefined) expect.fail();
|
// if (state.ignore.onClick === undefined) expect.fail();
|
||||||
|
|
||||||
state.ignore.onClick();
|
// state.ignore.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitNextUpdate();
|
// await waitNextUpdate();
|
||||||
{
|
// {
|
||||||
const state = getLastResultOrThrow();
|
// const state = getLastResultOrThrow();
|
||||||
|
|
||||||
if (state.status !== "ignored") expect.fail();
|
// if (state.status !== "ignored") expect.fail();
|
||||||
if (state.error) expect.fail();
|
// if (state.error) expect.fail();
|
||||||
}
|
// }
|
||||||
await assertNoPendingUpdate();
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render accepted if the tip has been used previously", async () => {
|
it("should render accepted if the tip has been used previously", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: "taler://tip/asd" }, {
|
useComponentState({ talerTipUri: "taler://tip/asd", cancel: async () => { null } }, {
|
||||||
prepareTip: async () =>
|
prepareTip: async () =>
|
||||||
({
|
({
|
||||||
accepted: true,
|
accepted: true,
|
||||||
|
@ -19,7 +19,7 @@ import { Amount } from "../../components/Amount.js";
|
|||||||
import { LoadingError } from "../../components/LoadingError.js";
|
import { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||||
import { Part } from "../../components/Part.js";
|
import { Part } from "../../components/Part.js";
|
||||||
import { SubTitle, WalletAction } from "../../components/styled/index.js";
|
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
@ -69,7 +69,6 @@ export function ReadyView(state: State.Ready): VNode {
|
|||||||
title={<i18n.Translate>Amount</i18n.Translate>}
|
title={<i18n.Translate>Amount</i18n.Translate>}
|
||||||
text={<Amount value={state.amount} />}
|
text={<Amount value={state.amount} />}
|
||||||
kind="positive"
|
kind="positive"
|
||||||
big
|
|
||||||
/>
|
/>
|
||||||
<Part
|
<Part
|
||||||
title={<i18n.Translate>Merchant URL</i18n.Translate>}
|
title={<i18n.Translate>Merchant URL</i18n.Translate>}
|
||||||
@ -88,12 +87,16 @@ export function ReadyView(state: State.Ready): VNode {
|
|||||||
color="success"
|
color="success"
|
||||||
onClick={state.accept.onClick}
|
onClick={state.accept.onClick}
|
||||||
>
|
>
|
||||||
<i18n.Translate>Accept tip</i18n.Translate>
|
<i18n.Translate>
|
||||||
</Button>
|
Receive {<Amount value={state.amount} />}
|
||||||
<Button onClick={state.ignore.onClick}>
|
</i18n.Translate>
|
||||||
<i18n.Translate>Ignore</i18n.Translate>
|
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<Link upperCased onClick={state.cancel}>
|
||||||
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
</WalletAction>
|
</WalletAction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import { CompletedView, LoadingExchangeView, LoadingInfoView, LoadingUriView, Su
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerWithdrawUri: string | undefined;
|
talerWithdrawUri: string | undefined;
|
||||||
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -67,13 +68,8 @@ export namespace State {
|
|||||||
status: "success";
|
status: "success";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
|
|
||||||
exchange: SelectFieldHandler;
|
exchangeUrl: string;
|
||||||
|
|
||||||
editExchange: ButtonHandler;
|
|
||||||
cancelEditExchange: ButtonHandler;
|
|
||||||
confirmEditExchange: ButtonHandler;
|
|
||||||
|
|
||||||
showExchangeSelection: boolean;
|
|
||||||
chosenAmount: AmountJson;
|
chosenAmount: AmountJson;
|
||||||
withdrawalFee: AmountJson;
|
withdrawalFee: AmountJson;
|
||||||
toBeReceived: AmountJson;
|
toBeReceived: AmountJson;
|
||||||
@ -82,7 +78,9 @@ export namespace State {
|
|||||||
tosProps?: TermsOfServiceSectionProps;
|
tosProps?: TermsOfServiceSectionProps;
|
||||||
mustAcceptFirst: boolean;
|
mustAcceptFirst: boolean;
|
||||||
|
|
||||||
ageRestriction: SelectFieldHandler;
|
ageRestriction?: SelectFieldHandler;
|
||||||
|
|
||||||
|
cancel: () => Promise<void>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,20 +17,16 @@
|
|||||||
|
|
||||||
import { Amounts } from "@gnu-taler/taler-util";
|
import { Amounts } from "@gnu-taler/taler-util";
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useMemo, useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
|
||||||
import { buildTermsOfServiceState } from "../../utils/index.js";
|
import { buildTermsOfServiceState } from "../../utils/index.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { State, Props } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerWithdrawUri }: Props,
|
{ talerWithdrawUri, cancel }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [customExchange, setCustomExchange] = useState<string | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
const [ageRestricted, setAgeRestricted] = useState(0);
|
const [ageRestricted, setAgeRestricted] = useState(0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,9 +38,8 @@ export function useComponentState(
|
|||||||
const uriInfo = await api.getWithdrawalDetailsForUri({
|
const uriInfo = await api.getWithdrawalDetailsForUri({
|
||||||
talerWithdrawUri,
|
talerWithdrawUri,
|
||||||
});
|
});
|
||||||
const { exchanges: knownExchanges } = await api.listExchanges();
|
const { amount, defaultExchangeBaseUrl } = uriInfo
|
||||||
|
return { amount, thisExchange: defaultExchangeBaseUrl };
|
||||||
return { uriInfo, knownExchanges };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,65 +50,55 @@ export function useComponentState(
|
|||||||
? undefined
|
? undefined
|
||||||
: uriInfoHook.response;
|
: uriInfoHook.response;
|
||||||
|
|
||||||
const { amount, thisExchange, thisCurrencyExchanges } = useMemo(() => {
|
// const { amount, thisExchange } = useMemo(() => {
|
||||||
if (!uriHookDep)
|
// if (!uriHookDep)
|
||||||
return {
|
// return {
|
||||||
amount: undefined,
|
// amount: undefined,
|
||||||
thisExchange: undefined,
|
// thisExchange: undefined,
|
||||||
thisCurrencyExchanges: [],
|
// thisCurrencyExchanges: [],
|
||||||
};
|
// };
|
||||||
|
|
||||||
const { uriInfo, knownExchanges } = uriHookDep;
|
// const { uriInfo } = uriHookDep;
|
||||||
|
|
||||||
const amount = uriInfo ? Amounts.parseOrThrow(uriInfo.amount) : undefined;
|
// const amount = uriHookDep ? Amounts.parseOrThrow(uriHookDep.amount) : undefined;
|
||||||
const thisCurrencyExchanges =
|
// const thisExchange = uriHookDep?.thisExchange;
|
||||||
!amount || !knownExchanges
|
|
||||||
? []
|
|
||||||
: knownExchanges.filter((ex) => ex.currency === amount.currency);
|
|
||||||
|
|
||||||
const thisExchange: string | undefined =
|
// return { amount, thisExchange };
|
||||||
customExchange ??
|
// }, [uriHookDep]);
|
||||||
uriInfo?.defaultExchangeBaseUrl ??
|
|
||||||
(thisCurrencyExchanges && thisCurrencyExchanges[0]
|
|
||||||
? thisCurrencyExchanges[0].exchangeBaseUrl
|
|
||||||
: undefined);
|
|
||||||
|
|
||||||
return { amount, thisExchange, thisCurrencyExchanges };
|
|
||||||
}, [uriHookDep, customExchange]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For the exchange selected, bring the status of the terms of service
|
* For the exchange selected, bring the status of the terms of service
|
||||||
*/
|
*/
|
||||||
const terms = useAsyncAsHook(async () => {
|
const terms = useAsyncAsHook(async () => {
|
||||||
if (!thisExchange) return false;
|
if (!uriHookDep?.thisExchange) return false;
|
||||||
|
|
||||||
const exchangeTos = await api.getExchangeTos(thisExchange, ["text/xml"]);
|
const exchangeTos = await api.getExchangeTos(uriHookDep.thisExchange, ["text/xml"]);
|
||||||
|
|
||||||
const state = buildTermsOfServiceState(exchangeTos);
|
const state = buildTermsOfServiceState(exchangeTos);
|
||||||
|
|
||||||
return { state };
|
return { state };
|
||||||
}, [thisExchange]);
|
}, [uriHookDep]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* With the exchange and amount, ask the wallet the information
|
* With the exchange and amount, ask the wallet the information
|
||||||
* about the withdrawal
|
* about the withdrawal
|
||||||
*/
|
*/
|
||||||
const info = useAsyncAsHook(async () => {
|
const amountHook = useAsyncAsHook(async () => {
|
||||||
if (!thisExchange || !amount) return false;
|
if (!uriHookDep?.thisExchange) return false;
|
||||||
|
|
||||||
const info = await api.getExchangeWithdrawalInfo({
|
const info = await api.getExchangeWithdrawalInfo({
|
||||||
exchangeBaseUrl: thisExchange,
|
exchangeBaseUrl: uriHookDep?.thisExchange,
|
||||||
amount,
|
amount: Amounts.parseOrThrow(uriHookDep.amount),
|
||||||
tosAcceptedFormat: ["text/xml"],
|
tosAcceptedFormat: ["text/xml"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const withdrawalFee = Amounts.sub(
|
const withdrawAmount = {
|
||||||
Amounts.parseOrThrow(info.withdrawalAmountRaw),
|
raw: Amounts.parseOrThrow(info.withdrawalAmountRaw),
|
||||||
Amounts.parseOrThrow(info.withdrawalAmountEffective),
|
effective: Amounts.parseOrThrow(info.withdrawalAmountEffective),
|
||||||
).amount;
|
}
|
||||||
|
|
||||||
return { info, withdrawalFee };
|
return { amount: withdrawAmount };
|
||||||
}, [thisExchange, amount]);
|
}, [uriHookDep]);
|
||||||
|
|
||||||
const [reviewing, setReviewing] = useState<boolean>(false);
|
const [reviewing, setReviewing] = useState<boolean>(false);
|
||||||
const [reviewed, setReviewed] = useState<boolean>(false);
|
const [reviewed, setReviewed] = useState<boolean>(false);
|
||||||
@ -124,9 +109,6 @@ export function useComponentState(
|
|||||||
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
||||||
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
|
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
|
||||||
|
|
||||||
const [showExchangeSelection, setShowExchangeSelection] = useState(false);
|
|
||||||
const [nextExchange, setNextExchange] = useState<string | undefined>();
|
|
||||||
|
|
||||||
if (!uriInfoHook) return { status: "loading", error: undefined }
|
if (!uriInfoHook) return { status: "loading", error: undefined }
|
||||||
if (uriInfoHook.hasError) {
|
if (uriInfoHook.hasError) {
|
||||||
return {
|
return {
|
||||||
@ -135,7 +117,10 @@ export function useComponentState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!thisExchange || !amount) {
|
const { amount, thisExchange } = uriInfoHook.response
|
||||||
|
const chosenAmount = Amounts.parseOrThrow(amount);
|
||||||
|
|
||||||
|
if (!thisExchange) {
|
||||||
return {
|
return {
|
||||||
status: "loading-exchange",
|
status: "loading-exchange",
|
||||||
error: {
|
error: {
|
||||||
@ -146,15 +131,17 @@ export function useComponentState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedExchange = thisExchange;
|
// const selectedExchange = thisExchange;
|
||||||
|
|
||||||
async function doWithdrawAndCheckError(): Promise<void> {
|
async function doWithdrawAndCheckError(): Promise<void> {
|
||||||
|
if (!thisExchange) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setDoingWithdraw(true);
|
setDoingWithdraw(true);
|
||||||
if (!talerWithdrawUri) return;
|
if (!talerWithdrawUri) return;
|
||||||
const res = await api.acceptWithdrawal(
|
const res = await api.acceptWithdrawal(
|
||||||
talerWithdrawUri,
|
talerWithdrawUri,
|
||||||
selectedExchange,
|
thisExchange,
|
||||||
!ageRestricted ? undefined : ageRestricted,
|
!ageRestricted ? undefined : ageRestricted,
|
||||||
);
|
);
|
||||||
if (res.confirmTransferUrl) {
|
if (res.confirmTransferUrl) {
|
||||||
@ -169,54 +156,27 @@ export function useComponentState(
|
|||||||
setDoingWithdraw(false);
|
setDoingWithdraw(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const exchanges = thisCurrencyExchanges.reduce(
|
if (!amountHook) {
|
||||||
(prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!info) {
|
|
||||||
return { status: "loading", error: undefined }
|
return { status: "loading", error: undefined }
|
||||||
}
|
}
|
||||||
if (info.hasError) {
|
if (amountHook.hasError) {
|
||||||
return {
|
return {
|
||||||
status: "loading-info",
|
status: "loading-info",
|
||||||
error: info,
|
error: amountHook,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!info.response) {
|
if (!amountHook.response) {
|
||||||
return { status: "loading", error: undefined };
|
return { status: "loading", error: undefined };
|
||||||
}
|
}
|
||||||
if (withdrawCompleted) {
|
if (withdrawCompleted) {
|
||||||
return { status: "completed", error: undefined };
|
return { status: "completed", error: undefined };
|
||||||
}
|
}
|
||||||
|
|
||||||
const exchangeHandler: SelectFieldHandler = {
|
const withdrawalFee = Amounts.sub(
|
||||||
onChange: async (e) => setNextExchange(e),
|
amountHook.response.amount.raw,
|
||||||
value: nextExchange ?? thisExchange,
|
amountHook.response.amount.effective,
|
||||||
list: exchanges,
|
).amount;
|
||||||
isDirty: nextExchange !== undefined,
|
const toBeReceived = amountHook.response.amount.effective;
|
||||||
};
|
|
||||||
|
|
||||||
const editExchange: ButtonHandler = {
|
|
||||||
onClick: async () => {
|
|
||||||
setShowExchangeSelection(true);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const cancelEditExchange: ButtonHandler = {
|
|
||||||
onClick: async () => {
|
|
||||||
setShowExchangeSelection(false);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const confirmEditExchange: ButtonHandler = {
|
|
||||||
onClick: async () => {
|
|
||||||
setCustomExchange(exchangeHandler.value);
|
|
||||||
setShowExchangeSelection(false);
|
|
||||||
setNextExchange(undefined);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const { withdrawalFee } = info.response;
|
|
||||||
const toBeReceived = Amounts.sub(amount, withdrawalFee).amount;
|
|
||||||
|
|
||||||
const { state: termsState } = (!terms
|
const { state: termsState } = (!terms
|
||||||
? undefined
|
? undefined
|
||||||
@ -225,11 +185,11 @@ export function useComponentState(
|
|||||||
: terms.response) || { state: undefined };
|
: terms.response) || { state: undefined };
|
||||||
|
|
||||||
async function onAccept(accepted: boolean): Promise<void> {
|
async function onAccept(accepted: boolean): Promise<void> {
|
||||||
if (!termsState) return;
|
if (!termsState || !thisExchange) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.setExchangeTosAccepted(
|
await api.setExchangeTosAccepted(
|
||||||
selectedExchange,
|
thisExchange,
|
||||||
accepted ? termsState.version : undefined,
|
accepted ? termsState.version : undefined,
|
||||||
);
|
);
|
||||||
setReviewed(accepted);
|
setReviewed(accepted);
|
||||||
@ -253,22 +213,22 @@ export function useComponentState(
|
|||||||
ageRestrictionOptions["0"] = "Not restricted";
|
ageRestrictionOptions["0"] = "Not restricted";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: calculate based on exchange info
|
||||||
|
const ageRestrictionEnabled = false;
|
||||||
|
const ageRestriction = ageRestrictionEnabled ? {
|
||||||
|
list: ageRestrictionOptions,
|
||||||
|
value: String(ageRestricted),
|
||||||
|
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
exchange: exchangeHandler,
|
exchangeUrl: thisExchange,
|
||||||
editExchange,
|
|
||||||
cancelEditExchange,
|
|
||||||
confirmEditExchange,
|
|
||||||
showExchangeSelection,
|
|
||||||
toBeReceived,
|
toBeReceived,
|
||||||
withdrawalFee,
|
withdrawalFee,
|
||||||
chosenAmount: amount,
|
chosenAmount,
|
||||||
ageRestriction: {
|
ageRestriction,
|
||||||
list: ageRestrictionOptions,
|
|
||||||
value: String(ageRestricted),
|
|
||||||
onChange: async (v) => setAgeRestricted(parseInt(v, 10)),
|
|
||||||
},
|
|
||||||
doWithdrawal: {
|
doWithdrawal: {
|
||||||
onClick:
|
onClick:
|
||||||
doingWithdraw || (mustAcceptFirst && !reviewed)
|
doingWithdraw || (mustAcceptFirst && !reviewed)
|
||||||
@ -286,6 +246,7 @@ export function useComponentState(
|
|||||||
terms: termsState,
|
terms: termsState,
|
||||||
},
|
},
|
||||||
mustAcceptFirst,
|
mustAcceptFirst,
|
||||||
|
cancel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,24 +63,13 @@ const ageRestrictionSelectField = {
|
|||||||
export const TermsOfServiceNotYetLoaded = createExample(SuccessView, {
|
export const TermsOfServiceNotYetLoaded = createExample(SuccessView, {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
status: "success",
|
status: "success",
|
||||||
cancelEditExchange: nullHandler,
|
|
||||||
confirmEditExchange: nullHandler,
|
|
||||||
ageRestriction: ageRestrictionSelectField,
|
|
||||||
chosenAmount: {
|
chosenAmount: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
value: 2,
|
value: 2,
|
||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
doWithdrawal: nullHandler,
|
doWithdrawal: nullHandler,
|
||||||
editExchange: nullHandler,
|
exchangeUrl: "https://exchange.demo.taler.net",
|
||||||
exchange: {
|
|
||||||
list: exchangeList,
|
|
||||||
value: "exchange.demo.taler.net",
|
|
||||||
onChange: async () => {
|
|
||||||
null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
showExchangeSelection: false,
|
|
||||||
mustAcceptFirst: false,
|
mustAcceptFirst: false,
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
@ -97,24 +86,13 @@ export const TermsOfServiceNotYetLoaded = createExample(SuccessView, {
|
|||||||
export const WithSomeFee = createExample(SuccessView, {
|
export const WithSomeFee = createExample(SuccessView, {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
status: "success",
|
status: "success",
|
||||||
cancelEditExchange: nullHandler,
|
|
||||||
confirmEditExchange: nullHandler,
|
|
||||||
ageRestriction: ageRestrictionSelectField,
|
|
||||||
chosenAmount: {
|
chosenAmount: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
value: 2,
|
value: 2,
|
||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
doWithdrawal: nullHandler,
|
doWithdrawal: nullHandler,
|
||||||
editExchange: nullHandler,
|
exchangeUrl: "https://exchange.demo.taler.net",
|
||||||
exchange: {
|
|
||||||
list: exchangeList,
|
|
||||||
value: "exchange.demo.taler.net",
|
|
||||||
onChange: async () => {
|
|
||||||
null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
showExchangeSelection: false,
|
|
||||||
mustAcceptFirst: false,
|
mustAcceptFirst: false,
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
@ -132,24 +110,13 @@ export const WithSomeFee = createExample(SuccessView, {
|
|||||||
export const WithoutFee = createExample(SuccessView, {
|
export const WithoutFee = createExample(SuccessView, {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
status: "success",
|
status: "success",
|
||||||
cancelEditExchange: nullHandler,
|
|
||||||
confirmEditExchange: nullHandler,
|
|
||||||
ageRestriction: ageRestrictionSelectField,
|
|
||||||
chosenAmount: {
|
chosenAmount: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
value: 2,
|
value: 2,
|
||||||
fraction: 10000000,
|
fraction: 0,
|
||||||
},
|
},
|
||||||
doWithdrawal: nullHandler,
|
doWithdrawal: nullHandler,
|
||||||
editExchange: nullHandler,
|
exchangeUrl: "https://exchange.demo.taler.net",
|
||||||
exchange: {
|
|
||||||
list: exchangeList,
|
|
||||||
value: "exchange.demo.taler.net",
|
|
||||||
onChange: async () => {
|
|
||||||
null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
showExchangeSelection: false,
|
|
||||||
mustAcceptFirst: false,
|
mustAcceptFirst: false,
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
@ -167,24 +134,13 @@ export const WithoutFee = createExample(SuccessView, {
|
|||||||
export const EditExchangeUntouched = createExample(SuccessView, {
|
export const EditExchangeUntouched = createExample(SuccessView, {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
status: "success",
|
status: "success",
|
||||||
cancelEditExchange: nullHandler,
|
|
||||||
confirmEditExchange: nullHandler,
|
|
||||||
ageRestriction: ageRestrictionSelectField,
|
|
||||||
chosenAmount: {
|
chosenAmount: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
value: 2,
|
value: 2,
|
||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
doWithdrawal: nullHandler,
|
doWithdrawal: nullHandler,
|
||||||
editExchange: nullHandler,
|
exchangeUrl: "https://exchange.demo.taler.net",
|
||||||
exchange: {
|
|
||||||
list: exchangeList,
|
|
||||||
value: "exchange.demo.taler.net",
|
|
||||||
onChange: async () => {
|
|
||||||
null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
showExchangeSelection: true,
|
|
||||||
mustAcceptFirst: false,
|
mustAcceptFirst: false,
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
@ -202,25 +158,13 @@ export const EditExchangeUntouched = createExample(SuccessView, {
|
|||||||
export const EditExchangeModified = createExample(SuccessView, {
|
export const EditExchangeModified = createExample(SuccessView, {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
status: "success",
|
status: "success",
|
||||||
cancelEditExchange: nullHandler,
|
|
||||||
confirmEditExchange: nullHandler,
|
|
||||||
ageRestriction: ageRestrictionSelectField,
|
|
||||||
chosenAmount: {
|
chosenAmount: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
value: 2,
|
value: 2,
|
||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
doWithdrawal: nullHandler,
|
doWithdrawal: nullHandler,
|
||||||
editExchange: nullHandler,
|
exchangeUrl: "https://exchange.demo.taler.net",
|
||||||
exchange: {
|
|
||||||
list: exchangeList,
|
|
||||||
isDirty: true,
|
|
||||||
value: "exchange.test.taler.net",
|
|
||||||
onChange: async () => {
|
|
||||||
null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
showExchangeSelection: true,
|
|
||||||
mustAcceptFirst: false,
|
mustAcceptFirst: false,
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
@ -240,11 +184,9 @@ export const CompletedWithoutBankURL = createExample(CompletedView, {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithAgeRestrictionSelected = createExample(SuccessView, {
|
export const WithAgeRestriction = createExample(SuccessView, {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
status: "success",
|
status: "success",
|
||||||
cancelEditExchange: nullHandler,
|
|
||||||
confirmEditExchange: nullHandler,
|
|
||||||
ageRestriction: ageRestrictionSelectField,
|
ageRestriction: ageRestrictionSelectField,
|
||||||
chosenAmount: {
|
chosenAmount: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
@ -252,15 +194,7 @@ export const WithAgeRestrictionSelected = createExample(SuccessView, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
doWithdrawal: nullHandler,
|
doWithdrawal: nullHandler,
|
||||||
editExchange: nullHandler,
|
exchangeUrl: "https://exchange.demo.taler.net",
|
||||||
exchange: {
|
|
||||||
list: exchangeList,
|
|
||||||
value: "exchange.demo.taler.net",
|
|
||||||
onChange: async () => {
|
|
||||||
null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
showExchangeSelection: false,
|
|
||||||
mustAcceptFirst: false,
|
mustAcceptFirst: false,
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
|
@ -44,7 +44,7 @@ describe("Withdraw CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerWithdrawUri: undefined }, {
|
useComponentState({ talerWithdrawUri: undefined, cancel: async () => { null } }, {
|
||||||
listExchanges: async () => ({ exchanges }),
|
listExchanges: async () => ({ exchanges }),
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
amount: "ARS:2",
|
amount: "ARS:2",
|
||||||
@ -76,7 +76,7 @@ describe("Withdraw CTA states", () => {
|
|||||||
it("should tell the user that there is not known exchange", async () => {
|
it("should tell the user that there is not known exchange", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerWithdrawUri: "taler-withdraw://" }, {
|
useComponentState({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, {
|
||||||
listExchanges: async () => ({ exchanges }),
|
listExchanges: async () => ({ exchanges }),
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
amount: "EUR:2",
|
amount: "EUR:2",
|
||||||
@ -110,17 +110,18 @@ describe("Withdraw CTA states", () => {
|
|||||||
it("should be able to withdraw if tos are ok", async () => {
|
it("should be able to withdraw if tos are ok", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerWithdrawUri: "taler-withdraw://" }, {
|
useComponentState({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, {
|
||||||
listExchanges: async () => ({ exchanges }),
|
listExchanges: async () => ({ exchanges }),
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
amount: "ARS:2",
|
amount: "ARS:2",
|
||||||
possibleExchanges: exchanges,
|
possibleExchanges: exchanges,
|
||||||
|
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl
|
||||||
}),
|
}),
|
||||||
getExchangeWithdrawalInfo:
|
getExchangeWithdrawalInfo:
|
||||||
async (): Promise<ExchangeWithdrawDetails> =>
|
async (): Promise<ExchangeWithdrawDetails> =>
|
||||||
({
|
({
|
||||||
withdrawalAmountRaw: "ARS:5",
|
withdrawalAmountRaw: "ARS:2",
|
||||||
withdrawalAmountEffective: "ARS:5",
|
withdrawalAmountEffective: "ARS:2",
|
||||||
} as any),
|
} as any),
|
||||||
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
||||||
contentType: "text",
|
contentType: "text",
|
||||||
@ -154,12 +155,12 @@ describe("Withdraw CTA states", () => {
|
|||||||
expect(state.status).equals("success");
|
expect(state.status).equals("success");
|
||||||
if (state.status !== "success") return;
|
if (state.status !== "success") return;
|
||||||
|
|
||||||
expect(state.exchange.isDirty).false;
|
// expect(state.exchange.isDirty).false;
|
||||||
expect(state.exchange.value).equal("http://exchange.demo.taler.net");
|
// expect(state.exchange.value).equal("http://exchange.demo.taler.net");
|
||||||
expect(state.exchange.list).deep.equal({
|
// expect(state.exchange.list).deep.equal({
|
||||||
"http://exchange.demo.taler.net": "http://exchange.demo.taler.net",
|
// "http://exchange.demo.taler.net": "http://exchange.demo.taler.net",
|
||||||
});
|
// });
|
||||||
expect(state.showExchangeSelection).false;
|
// expect(state.showExchangeSelection).false;
|
||||||
|
|
||||||
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
||||||
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"));
|
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"));
|
||||||
@ -175,17 +176,18 @@ describe("Withdraw CTA states", () => {
|
|||||||
it("should be accept the tos before withdraw", async () => {
|
it("should be accept the tos before withdraw", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerWithdrawUri: "taler-withdraw://" }, {
|
useComponentState({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, {
|
||||||
listExchanges: async () => ({ exchanges }),
|
listExchanges: async () => ({ exchanges }),
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
amount: "ARS:2",
|
amount: "ARS:2",
|
||||||
possibleExchanges: exchanges,
|
possibleExchanges: exchanges,
|
||||||
|
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl
|
||||||
}),
|
}),
|
||||||
getExchangeWithdrawalInfo:
|
getExchangeWithdrawalInfo:
|
||||||
async (): Promise<ExchangeWithdrawDetails> =>
|
async (): Promise<ExchangeWithdrawDetails> =>
|
||||||
({
|
({
|
||||||
withdrawalAmountRaw: "ARS:5",
|
withdrawalAmountRaw: "ARS:2",
|
||||||
withdrawalAmountEffective: "ARS:5",
|
withdrawalAmountEffective: "ARS:2",
|
||||||
} as any),
|
} as any),
|
||||||
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
||||||
contentType: "text",
|
contentType: "text",
|
||||||
@ -220,12 +222,12 @@ describe("Withdraw CTA states", () => {
|
|||||||
expect(state.status).equals("success");
|
expect(state.status).equals("success");
|
||||||
if (state.status !== "success") return;
|
if (state.status !== "success") return;
|
||||||
|
|
||||||
expect(state.exchange.isDirty).false;
|
// expect(state.exchange.isDirty).false;
|
||||||
expect(state.exchange.value).equal("http://exchange.demo.taler.net");
|
// expect(state.exchange.value).equal("http://exchange.demo.taler.net");
|
||||||
expect(state.exchange.list).deep.equal({
|
// expect(state.exchange.list).deep.equal({
|
||||||
"http://exchange.demo.taler.net": "http://exchange.demo.taler.net",
|
// "http://exchange.demo.taler.net": "http://exchange.demo.taler.net",
|
||||||
});
|
// });
|
||||||
expect(state.showExchangeSelection).false;
|
// expect(state.showExchangeSelection).false;
|
||||||
|
|
||||||
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
||||||
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"));
|
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"));
|
||||||
@ -245,12 +247,12 @@ describe("Withdraw CTA states", () => {
|
|||||||
expect(state.status).equals("success");
|
expect(state.status).equals("success");
|
||||||
if (state.status !== "success") return;
|
if (state.status !== "success") return;
|
||||||
|
|
||||||
expect(state.exchange.isDirty).false;
|
// expect(state.exchange.isDirty).false;
|
||||||
expect(state.exchange.value).equal("http://exchange.demo.taler.net");
|
// expect(state.exchange.value).equal("http://exchange.demo.taler.net");
|
||||||
expect(state.exchange.list).deep.equal({
|
// expect(state.exchange.list).deep.equal({
|
||||||
"http://exchange.demo.taler.net": "http://exchange.demo.taler.net",
|
// "http://exchange.demo.taler.net": "http://exchange.demo.taler.net",
|
||||||
});
|
// });
|
||||||
expect(state.showExchangeSelection).false;
|
// expect(state.showExchangeSelection).false;
|
||||||
|
|
||||||
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2"));
|
||||||
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"));
|
expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0"));
|
||||||
|
@ -15,25 +15,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { State } from "./index.js";
|
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
|
||||||
import { Amount } from "../../components/Amount.js";
|
|
||||||
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
||||||
import { Loading } from "../../components/Loading.js";
|
|
||||||
import { LoadingError } from "../../components/LoadingError.js";
|
import { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||||
import { Part } from "../../components/Part.js";
|
import { Part } from "../../components/Part.js";
|
||||||
import { SelectList } from "../../components/SelectList.js";
|
import { SelectList } from "../../components/SelectList.js";
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
LinkSuccess,
|
Link,
|
||||||
SubTitle,
|
SubTitle,
|
||||||
SuccessBox,
|
SuccessBox,
|
||||||
|
SvgIcon,
|
||||||
WalletAction,
|
WalletAction,
|
||||||
} from "../../components/styled/index.js";
|
} from "../../components/styled/index.js";
|
||||||
import { Amounts } from "@gnu-taler/taler-util";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { TermsOfServiceSection } from "../TermsOfServiceSection.js";
|
|
||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
|
import { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js";
|
||||||
|
import { TermsOfServiceSection } from "../TermsOfServiceSection.js";
|
||||||
|
import { State } from "./index.js";
|
||||||
|
import editIcon from "../../svg/edit_24px.svg";
|
||||||
|
import { Amount } from "../../components/Amount.js";
|
||||||
|
|
||||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
@ -115,77 +116,50 @@ export function SuccessView(state: State.Success): VNode {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<section>
|
<section style={{ textAlign: "left" }}>
|
||||||
<Part
|
<Part
|
||||||
title={<i18n.Translate>Total to withdraw</i18n.Translate>}
|
title={
|
||||||
text={<Amount value={state.toBeReceived} />}
|
<div
|
||||||
kind="positive"
|
style={{
|
||||||
/>
|
display: "flex",
|
||||||
{Amounts.isNonZero(state.withdrawalFee) && (
|
}}
|
||||||
<Fragment>
|
>
|
||||||
<Part
|
<i18n.Translate>Exchange</i18n.Translate>
|
||||||
title={<i18n.Translate>Chosen amount</i18n.Translate>}
|
<SvgIcon
|
||||||
text={<Amount value={state.chosenAmount} />}
|
title="Edit"
|
||||||
kind="neutral"
|
dangerouslySetInnerHTML={{ __html: editIcon }}
|
||||||
/>
|
color="black"
|
||||||
<Part
|
onClick={() => console.log("ok")}
|
||||||
title={<i18n.Translate>Exchange fee</i18n.Translate>}
|
/>
|
||||||
text={<Amount value={state.withdrawalFee} />}
|
</div>
|
||||||
kind="negative"
|
}
|
||||||
/>
|
text={<ExchangeDetails exchange={state.exchangeUrl} />}
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
<Part
|
|
||||||
title={<i18n.Translate>Exchange</i18n.Translate>}
|
|
||||||
text={state.exchange.value}
|
|
||||||
kind="neutral"
|
kind="neutral"
|
||||||
big
|
big
|
||||||
/>
|
/>
|
||||||
{state.showExchangeSelection ? (
|
<Part
|
||||||
<Fragment>
|
title={<i18n.Translate>Details</i18n.Translate>}
|
||||||
<div>
|
text={
|
||||||
<SelectList
|
<WithdrawDetails
|
||||||
label={<i18n.Translate>Known exchanges</i18n.Translate>}
|
amount={{
|
||||||
list={state.exchange.list}
|
effective: state.toBeReceived,
|
||||||
value={state.exchange.value}
|
raw: state.chosenAmount,
|
||||||
name="switchingExchange"
|
}}
|
||||||
onChange={state.exchange.onChange}
|
/>
|
||||||
/>
|
}
|
||||||
</div>
|
/>
|
||||||
<LinkSuccess
|
{state.ageRestriction && (
|
||||||
upperCased
|
<Input>
|
||||||
style={{ fontSize: "small" }}
|
<SelectList
|
||||||
onClick={state.confirmEditExchange.onClick}
|
label={<i18n.Translate>Age restriction</i18n.Translate>}
|
||||||
>
|
list={state.ageRestriction.list}
|
||||||
{state.exchange.isDirty ? (
|
name="age"
|
||||||
<i18n.Translate>Confirm exchange selection</i18n.Translate>
|
value={state.ageRestriction.value}
|
||||||
) : (
|
onChange={state.ageRestriction.onChange}
|
||||||
<i18n.Translate>Cancel exchange selection</i18n.Translate>
|
/>
|
||||||
)}
|
</Input>
|
||||||
</LinkSuccess>
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<LinkSuccess
|
|
||||||
style={{ fontSize: "small" }}
|
|
||||||
upperCased
|
|
||||||
onClick={state.editExchange.onClick}
|
|
||||||
>
|
|
||||||
<i18n.Translate>Edit exchange</i18n.Translate>
|
|
||||||
</LinkSuccess>
|
|
||||||
)}
|
)}
|
||||||
</section>
|
</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 && <TermsOfServiceSection {...state.tosProps} />}
|
||||||
{state.tosProps ? (
|
{state.tosProps ? (
|
||||||
<section>
|
<section>
|
||||||
@ -197,7 +171,9 @@ export function SuccessView(state: State.Success): VNode {
|
|||||||
disabled={!state.doWithdrawal.onClick}
|
disabled={!state.doWithdrawal.onClick}
|
||||||
onClick={state.doWithdrawal.onClick}
|
onClick={state.doWithdrawal.onClick}
|
||||||
>
|
>
|
||||||
<i18n.Translate>Confirm withdrawal</i18n.Translate>
|
<i18n.Translate>
|
||||||
|
Receive <Amount value={state.toBeReceived} />
|
||||||
|
</i18n.Translate>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{state.tosProps.terms.status === "notfound" && (
|
{state.tosProps.terms.status === "notfound" && (
|
||||||
@ -216,6 +192,11 @@ export function SuccessView(state: State.Success): VNode {
|
|||||||
<i18n.Translate>Loading terms of service...</i18n.Translate>
|
<i18n.Translate>Loading terms of service...</i18n.Translate>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
<section>
|
||||||
|
<Link upperCased onClick={state.cancel}>
|
||||||
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
</WalletAction>
|
</WalletAction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ function ExampleList({
|
|||||||
{list.map((k) => (
|
{list.map((k) => (
|
||||||
<li key={k.name}>
|
<li key={k.name}>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>{k.name}</dt>
|
<dt>{k.name.substring(k.name.indexOf("/") + 1)}</dt>
|
||||||
{k.examples.map((r) => {
|
{k.examples.map((r) => {
|
||||||
const e = encodeURIComponent;
|
const e = encodeURIComponent;
|
||||||
const eId = `${e(r.group)}-${e(r.component)}-${e(r.name)}`;
|
const eId = `${e(r.group)}-${e(r.component)}-${e(r.name)}`;
|
||||||
|
1
packages/taler-wallet-webextension/src/svg/edit_24px.svg
Normal file
1
packages/taler-wallet-webextension/src/svg/edit_24px.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
|
After Width: | Height: | Size: 287 B |
@ -76,33 +76,34 @@ export function Application(): VNode {
|
|||||||
<IoCProviderForRuntime>
|
<IoCProviderForRuntime>
|
||||||
{/* <Match/> won't work in the first render if <Router /> is not called first */}
|
{/* <Match/> won't work in the first render if <Router /> is not called first */}
|
||||||
{/* https://github.com/preactjs/preact-router/issues/415 */}
|
{/* https://github.com/preactjs/preact-router/issues/415 */}
|
||||||
<Router history={hash_history} />
|
<Router history={hash_history}>
|
||||||
<Match>
|
<Match default>
|
||||||
{({ path }: { path: string }) => {
|
{({ path }: { path: string }) => {
|
||||||
if (path && path.startsWith("/cta")) return;
|
if (path && path.startsWith("/cta")) return;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<LogoHeader />
|
<LogoHeader />
|
||||||
<WalletNavBar path={path} />
|
<WalletNavBar path={path} />
|
||||||
{shouldShowPendingOperations(path) && (
|
{shouldShowPendingOperations(path) && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "lightcyan",
|
backgroundColor: "lightcyan",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PendingTransactions
|
<PendingTransactions
|
||||||
goToTransaction={(tid: string) =>
|
goToTransaction={(tid: string) =>
|
||||||
redirectTo(Pages.balanceTransaction({ tid }))
|
redirectTo(Pages.balanceTransaction({ tid }))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Match>
|
</Match>
|
||||||
|
</Router>
|
||||||
<WalletBox>
|
<WalletBox>
|
||||||
{globalNotification && (
|
{globalNotification && (
|
||||||
<SuccessBox onClick={clearNotification}>
|
<SuccessBox onClick={clearNotification}>
|
||||||
@ -206,12 +207,28 @@ export function Application(): VNode {
|
|||||||
goToWalletManualWithdraw={(currency?: string) =>
|
goToWalletManualWithdraw={(currency?: string) =>
|
||||||
redirectTo(Pages.balanceManualWithdraw({ currency }))
|
redirectTo(Pages.balanceManualWithdraw({ currency }))
|
||||||
}
|
}
|
||||||
goBack={() => redirectTo(Pages.balance)}
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={Pages.ctaRefund}
|
||||||
|
component={RefundPage}
|
||||||
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={Pages.ctaTips}
|
||||||
|
component={TipPage}
|
||||||
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={Pages.ctaWithdraw}
|
||||||
|
component={WithdrawPage}
|
||||||
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={Pages.ctaDeposit}
|
||||||
|
component={DepositPageCTA}
|
||||||
|
cancel={() => redirectTo(Pages.balance)}
|
||||||
/>
|
/>
|
||||||
<Route path={Pages.ctaRefund} component={RefundPage} />
|
|
||||||
<Route path={Pages.ctaTips} component={TipPage} />
|
|
||||||
<Route path={Pages.ctaWithdraw} component={WithdrawPage} />
|
|
||||||
<Route path={Pages.ctaDeposit} component={DepositPageCTA} />
|
|
||||||
|
|
||||||
{/**
|
{/**
|
||||||
* NOT FOUND
|
* NOT FOUND
|
||||||
|
@ -32,7 +32,6 @@ import {
|
|||||||
TransactionRefund,
|
TransactionRefund,
|
||||||
TransactionTip,
|
TransactionTip,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
TransactionWithdrawal,
|
|
||||||
WithdrawalType,
|
WithdrawalType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { styled } from "@linaria/react";
|
import { styled } from "@linaria/react";
|
||||||
@ -308,7 +307,14 @@ export function TransactionView({
|
|||||||
)}
|
)}
|
||||||
<Part
|
<Part
|
||||||
title={<i18n.Translate>Details</i18n.Translate>}
|
title={<i18n.Translate>Details</i18n.Translate>}
|
||||||
text={<WithdrawDetails transaction={transaction} />}
|
text={
|
||||||
|
<WithdrawDetails
|
||||||
|
amount={{
|
||||||
|
effective: Amounts.parseOrThrow(transaction.amountEffective),
|
||||||
|
raw: Amounts.parseOrThrow(transaction.amountRaw),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</TransactionTemplate>
|
</TransactionTemplate>
|
||||||
);
|
);
|
||||||
@ -713,10 +719,64 @@ function DeliveryDetails({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ExchangeDetails({ exchange }: { exchange: string }): VNode {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p style={{ marginTop: 0 }}>
|
||||||
|
<a rel="noreferrer" target="_blank" href={exchange}>
|
||||||
|
{exchange}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export interface AmountWithFee {
|
export interface AmountWithFee {
|
||||||
effective: AmountJson;
|
effective: AmountJson;
|
||||||
raw: AmountJson;
|
raw: AmountJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function WithdrawDetails({ amount }: { amount: AmountWithFee }): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
const fee = Amounts.sub(amount.raw, amount.effective).amount;
|
||||||
|
|
||||||
|
const maxFrac = [amount.raw, amount.effective, fee]
|
||||||
|
.map((a) => Amounts.maxFractionalDigits(a))
|
||||||
|
.reduce((c, p) => Math.max(c, p), 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PurchaseDetailsTable>
|
||||||
|
<tr>
|
||||||
|
<td>Withdraw</td>
|
||||||
|
<td>
|
||||||
|
<Amount value={amount.raw} maxFracSize={maxFrac} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{Amounts.isNonZero(fee) && (
|
||||||
|
<tr>
|
||||||
|
<td>Transaction fees</td>
|
||||||
|
<td>
|
||||||
|
<Amount value={fee} negative maxFracSize={maxFrac} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
<tr>
|
||||||
|
<td colSpan={2}>
|
||||||
|
<hr />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total</td>
|
||||||
|
<td>
|
||||||
|
<Amount value={amount.effective} maxFracSize={maxFrac} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</PurchaseDetailsTable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function PurchaseDetails({
|
export function PurchaseDetails({
|
||||||
price,
|
price,
|
||||||
refund,
|
refund,
|
||||||
@ -1020,53 +1080,6 @@ function TipDetails({ transaction }: { transaction: TransactionTip }): VNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WithdrawDetails({
|
|
||||||
transaction,
|
|
||||||
}: {
|
|
||||||
transaction: TransactionWithdrawal;
|
|
||||||
}): VNode {
|
|
||||||
const { i18n } = useTranslationContext();
|
|
||||||
|
|
||||||
const r = Amounts.parseOrThrow(transaction.amountRaw);
|
|
||||||
const e = Amounts.parseOrThrow(transaction.amountEffective);
|
|
||||||
const fee = Amounts.sub(r, e).amount;
|
|
||||||
|
|
||||||
const maxFrac = [r, e, fee]
|
|
||||||
.map((a) => Amounts.maxFractionalDigits(a))
|
|
||||||
.reduce((c, p) => Math.max(c, p), 0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PurchaseDetailsTable>
|
|
||||||
<tr>
|
|
||||||
<td>Withdraw</td>
|
|
||||||
<td>
|
|
||||||
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{Amounts.isNonZero(fee) && (
|
|
||||||
<tr>
|
|
||||||
<td>Transaction fees</td>
|
|
||||||
<td>
|
|
||||||
<Amount value={fee} negative maxFracSize={maxFrac} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
<tr>
|
|
||||||
<td colSpan={2}>
|
|
||||||
<hr />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Total</td>
|
|
||||||
<td>
|
|
||||||
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</PurchaseDetailsTable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Header({
|
function Header({
|
||||||
timestamp,
|
timestamp,
|
||||||
total,
|
total,
|
||||||
|
Loading…
Reference in New Issue
Block a user