diff options
author | Sebastian <sebasjm@gmail.com> | 2022-08-10 11:50:46 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-08-10 11:50:46 -0300 |
commit | dce055d0d3fe2037d4c3018baa360b9082e37194 (patch) | |
tree | fd8daa463459b4daa78fe41bb5262d302d03ff7a | |
parent | 7a600514c6d43bbaeba6b962533415e59fc46057 (diff) |
withdraw call to action
27 files changed, 344 insertions, 418 deletions
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index ff4a5b4d5..2430be7c1 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -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 { fill: ${({ color }) => color}; } @@ -846,6 +850,7 @@ export const SvgIcon = styled.div<{ color: string }>` margin-left: auto; margin-right: 8px; padding: 4px; + cursor: ${({ onClick }) => (onClick ? "pointer" : "inherit")}; `; export const Icon = styled.div` diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts index c2d700617..796fad97c 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts @@ -28,6 +28,7 @@ import { CompletedView, LoadingUriView, ReadyView } from "./views.js"; export interface Props { talerDepositUri: string | undefined, amountStr: AmountString | undefined, + cancel: () => Promise<void>; } export type State = @@ -53,6 +54,7 @@ export namespace State { cost: AmountJson; effective: AmountJson; confirm: ButtonHandler; + cancel: () => Promise<void>; } export interface Completed { status: "completed"; diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts index 8876a2971..3939c7ba2 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts @@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( - { talerDepositUri, amountStr }: Props, + { talerDepositUri, amountStr, cancel }: Props, api: typeof wxApi, ): State { const [result, setResult] = useState<CreateDepositGroupResponse | undefined>( @@ -72,5 +72,6 @@ export function useComponentState( .amount, cost: deposit.totalDepositCost, effective: deposit.effectiveDepositAmount, + cancel, }; }
\ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts index 6e7aaf237..4e7f5cfb4 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts @@ -30,7 +30,7 @@ describe("Deposit CTA states", () => { it("should tell the user that the URI is missing", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerDepositUri: undefined, amountStr: undefined }, { + useComponentState({ talerDepositUri: undefined, amountStr: undefined, cancel: async () => { null } }, { prepareRefund: async () => ({}), applyRefund: async () => ({}), onUpdateNotification: async () => ({}), @@ -61,7 +61,7 @@ describe("Deposit CTA states", () => { it("should be ready after loading", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerDepositUri: "payto://refund/asdasdas", amountStr: "EUR:1" }, { + useComponentState({ talerDepositUri: "payto://refund/asdasdas", amountStr: "EUR:1", cancel: async () => { null } }, { prepareDeposit: async () => ({ effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"), diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx b/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx index ba1ca58d6..9045e5bfa 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx @@ -100,7 +100,7 @@ export function ReadyView(state: State.Ready): VNode { onClick={state.confirm.onClick} > <i18n.Translate> - Deposit {<Amount value={state.effective} />} + Send {<Amount value={state.cost} />} </i18n.Translate> </Button> </section> diff --git a/packages/taler-wallet-webextension/src/cta/Payment/index.ts b/packages/taler-wallet-webextension/src/cta/Payment/index.ts index 5c0f6f0d6..2eb41eb17 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/index.ts @@ -28,7 +28,7 @@ import { LoadingUriView, BaseView } from "./views.js"; export interface Props { talerPayUri?: string; goToWalletManualWithdraw: (currency?: string) => Promise<void>; - goBack: () => Promise<void>; + cancel: () => Promise<void>; } export type State = @@ -56,7 +56,7 @@ export namespace State { uri: string; error: undefined; goToWalletManualWithdraw: (currency?: string) => Promise<void>; - goBack: () => Promise<void>; + cancel: () => Promise<void>; } export interface NoBalanceForCurrency extends BaseInfo { status: "no-balance-for-currency" diff --git a/packages/taler-wallet-webextension/src/cta/Payment/state.ts b/packages/taler-wallet-webextension/src/cta/Payment/state.ts index f75cef06f..842bb7ed6 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/state.ts @@ -24,7 +24,7 @@ import * as wxApi from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( - { talerPayUri, goBack, goToWalletManualWithdraw }: Props, + { talerPayUri, cancel, goToWalletManualWithdraw }: Props, api: typeof wxApi, ): State { const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>( @@ -82,7 +82,7 @@ export function useComponentState( uri: hook.response.uri, amount, error: undefined, - goBack, goToWalletManualWithdraw + cancel, goToWalletManualWithdraw } if (!foundBalance) { diff --git a/packages/taler-wallet-webextension/src/cta/Payment/test.ts b/packages/taler-wallet-webextension/src/cta/Payment/test.ts index afd881a72..2052927b1 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/test.ts @@ -70,7 +70,7 @@ describe("Payment CTA states", () => { it("should tell the user that the URI is missing", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerPayUri: undefined, goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, { + useComponentState({ talerPayUri: undefined, cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, { onUpdateNotification: nullFunction, } as Partial<typeof wxApi> as any), ); @@ -98,7 +98,7 @@ describe("Payment CTA states", () => { it("should response with no balance", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, { + useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, { onUpdateNotification: nullFunction, preparePay: async () => ({ @@ -133,7 +133,7 @@ describe("Payment CTA states", () => { it("should not be able to pay if there is no enough balance", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, { + useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, { onUpdateNotification: nullFunction, preparePay: async () => ({ @@ -172,7 +172,7 @@ describe("Payment CTA states", () => { it("should be able to pay (without fee)", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, { + useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, { onUpdateNotification: nullFunction, preparePay: async () => ({ @@ -214,7 +214,7 @@ describe("Payment CTA states", () => { it("should be able to pay (with fee)", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, { + useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, { onUpdateNotification: nullFunction, preparePay: async () => ({ @@ -256,7 +256,7 @@ describe("Payment CTA states", () => { it("should get confirmation done after pay successfully", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, { + useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, { onUpdateNotification: nullFunction, preparePay: async () => ({ @@ -317,7 +317,7 @@ describe("Payment CTA states", () => { it("should not stay in ready state after pay with error", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, { + useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, { onUpdateNotification: nullFunction, preparePay: async () => ({ @@ -393,7 +393,7 @@ describe("Payment CTA states", () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerPayUri: "taller://pay", goBack: nullFunction, goToWalletManualWithdraw: nullFunction }, { + useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, { onUpdateNotification: subscriptions.saveSubscription, preparePay: async () => ({ diff --git a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx index 4c2ddc0f2..d18dc7065 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx @@ -74,7 +74,7 @@ export function BaseView(state: SupportedStates): VNode { ? Amounts.parseOrThrow(state.payStatus.amountEffective) : state.amount, }; - const totalFees = Amounts.sub(price.effective, price.raw).amount; + // const totalFees = Amounts.sub(price.effective, price.raw).amount; return ( <WalletAction> @@ -168,7 +168,7 @@ export function BaseView(state: SupportedStates): VNode { goToWalletManualWithdraw={state.goToWalletManualWithdraw} /> <section> - <Link upperCased onClick={state.goBack}> + <Link upperCased onClick={state.cancel}> <i18n.Translate>Cancel</i18n.Translate> </Link> </section> @@ -358,7 +358,7 @@ function ButtonsSection({ onClick={state.payHandler.onClick} > <i18n.Translate> - Pay + Send {<Amount value={state.payStatus.amountEffective} />} </i18n.Translate> </Button> diff --git a/packages/taler-wallet-webextension/src/cta/Refund/index.ts b/packages/taler-wallet-webextension/src/cta/Refund/index.ts index b122559a9..d1f808f00 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/index.ts @@ -27,6 +27,7 @@ import { CompletedView, IgnoredView, InProgressView, LoadingUriView, ReadyView } export interface Props { talerRefundUri?: string; + cancel: () => Promise<void>; } export type State = @@ -64,6 +65,7 @@ export namespace State { accept: ButtonHandler; ignore: ButtonHandler; orderId: string; + cancel: () => Promise<void>; } export interface Ignored extends BaseInfo { diff --git a/packages/taler-wallet-webextension/src/cta/Refund/state.ts b/packages/taler-wallet-webextension/src/cta/Refund/state.ts index f8ce71a13..e520be8e5 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/state.ts @@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( - { talerRefundUri }: Props, + { talerRefundUri, cancel }: Props, api: typeof wxApi, ): State { const [ignored, setIgnored] = useState(false); @@ -100,5 +100,6 @@ export function useComponentState( ignore: { onClick: doIgnore, }, + cancel, }; } diff --git a/packages/taler-wallet-webextension/src/cta/Refund/test.ts b/packages/taler-wallet-webextension/src/cta/Refund/test.ts index 04c83b8f1..e238078db 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/test.ts @@ -33,7 +33,7 @@ describe("Refund CTA states", () => { it("should tell the user that the URI is missing", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerRefundUri: undefined }, { + useComponentState({ talerRefundUri: undefined, cancel: async () => { null } }, { prepareRefund: async () => ({}), applyRefund: async () => ({}), onUpdateNotification: async () => ({}), @@ -64,7 +64,7 @@ describe("Refund CTA states", () => { it("should be ready after loading", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerRefundUri: "taler://refund/asdasdas" }, { + useComponentState({ talerRefundUri: "taler://refund/asdasdas", cancel: async () => { null } }, { prepareRefund: async () => ({ effectivePaid: "EUR:2", @@ -113,7 +113,7 @@ describe("Refund CTA states", () => { it("should be ignored after clicking the ignore button", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerRefundUri: "taler://refund/asdasdas" }, { + useComponentState({ talerRefundUri: "taler://refund/asdasdas", cancel: async () => { null } }, { prepareRefund: async () => ({ effectivePaid: "EUR:2", @@ -189,7 +189,7 @@ describe("Refund CTA states", () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerRefundUri: "taler://refund/asdasdas" }, { + useComponentState({ talerRefundUri: "taler://refund/asdasdas", cancel: async () => { null } }, { prepareRefund: async () => ({ awaiting: Amounts.stringify(awaiting), diff --git a/packages/taler-wallet-webextension/src/cta/Refund/views.tsx b/packages/taler-wallet-webextension/src/cta/Refund/views.tsx index e0c7bb553..3a8148e99 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Refund/views.tsx @@ -20,7 +20,7 @@ import { Amount } from "../../components/Amount.js"; import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.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 { Button } from "../../mui/Button.js"; import { ProductList } from "../Payment/views.js"; @@ -163,10 +163,21 @@ export function ReadyView(state: State.Ready): VNode { </section> ) : undefined} <section> - <Button variant="contained" onClick={state.accept.onClick}> - <i18n.Translate>Confirm refund</i18n.Translate> + <Button + variant="contained" + color="success" + onClick={state.accept.onClick} + > + <i18n.Translate> + Receive <Amount value={state.amount} /> + </i18n.Translate> </Button> </section> + <section> + <Link upperCased onClick={state.cancel}> + <i18n.Translate>Cancel</i18n.Translate> + </Link> + </section> </WalletAction> ); } diff --git a/packages/taler-wallet-webextension/src/cta/Tip/index.ts b/packages/taler-wallet-webextension/src/cta/Tip/index.ts index 24a7b1cff..b174d5160 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/index.ts @@ -30,6 +30,7 @@ import { AcceptedView, IgnoredView, LoadingUriView, ReadyView } from "./views.js export interface Props { talerTipUri?: string; + cancel: () => Promise<void>; } export type State = @@ -69,7 +70,7 @@ export namespace State { export interface Ready extends BaseInfo { status: "ready"; accept: ButtonHandler; - ignore: ButtonHandler; + cancel: () => Promise<void>; } } diff --git a/packages/taler-wallet-webextension/src/cta/Tip/state.ts b/packages/taler-wallet-webextension/src/cta/Tip/state.ts index e5511074e..f18911f65 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/state.ts @@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( - { talerTipUri }: Props, + { talerTipUri, cancel }: Props, api: typeof wxApi, ): State { const [tipIgnored, setTipIgnored] = useState(false); @@ -53,10 +53,6 @@ export function useComponentState( tipInfo.retry(); }; - const doIgnore = async (): Promise<void> => { - setTipIgnored(true); - }; - const baseInfo = { merchantBaseUrl: tip.merchantBaseUrl, exchangeBaseUrl: tip.exchangeBaseUrl, @@ -84,9 +80,7 @@ export function useComponentState( accept: { onClick: doAccept, }, - ignore: { - onClick: doIgnore, - }, + cancel, }; } diff --git a/packages/taler-wallet-webextension/src/cta/Tip/stories.tsx b/packages/taler-wallet-webextension/src/cta/Tip/stories.tsx index 8c72a8812..efb9424d1 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Tip/stories.tsx @@ -42,5 +42,4 @@ export const Ready = createExample(ReadyView, { merchantBaseUrl: "http://merchant.url/", exchangeBaseUrl: "http://exchange.url/", accept: {}, - ignore: {}, }); diff --git a/packages/taler-wallet-webextension/src/cta/Tip/test.ts b/packages/taler-wallet-webextension/src/cta/Tip/test.ts index 1c7d363f4..363652e47 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/test.ts @@ -30,7 +30,7 @@ describe("Tip CTA states", () => { it("should tell the user that the URI is missing", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerTipUri: undefined }, { + useComponentState({ talerTipUri: undefined, cancel: async () => { null } }, { prepareTip: async () => ({}), acceptTip: async () => ({}), } as any), @@ -62,7 +62,7 @@ describe("Tip CTA states", () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerTipUri: "taler://tip/asd" }, { + useComponentState({ talerTipUri: "taler://tip/asd", cancel: async () => { null } }, { prepareTip: async () => ({ accepted: tipAccepted, @@ -114,7 +114,7 @@ describe("Tip CTA states", () => { it("should be ignored after clicking the ignore button", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerTipUri: "taler://tip/asd" }, { + useComponentState({ talerTipUri: "taler://tip/asd", cancel: async () => { null } }, { prepareTip: async () => ({ exchangeBaseUrl: "exchange url", @@ -142,25 +142,25 @@ describe("Tip CTA states", () => { expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.merchantBaseUrl).eq("merchant 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(); - { - const state = getLastResultOrThrow(); + // await waitNextUpdate(); + // { + // const state = getLastResultOrThrow(); - if (state.status !== "ignored") expect.fail(); - if (state.error) expect.fail(); - } + // if (state.status !== "ignored") expect.fail(); + // if (state.error) expect.fail(); + // } await assertNoPendingUpdate(); }); it("should render accepted if the tip has been used previously", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerTipUri: "taler://tip/asd" }, { + useComponentState({ talerTipUri: "taler://tip/asd", cancel: async () => { null } }, { prepareTip: async () => ({ accepted: true, diff --git a/packages/taler-wallet-webextension/src/cta/Tip/views.tsx b/packages/taler-wallet-webextension/src/cta/Tip/views.tsx index 442d41d28..2df55f8fd 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Tip/views.tsx @@ -19,7 +19,7 @@ import { Amount } from "../../components/Amount.js"; import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.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 { Button } from "../../mui/Button.js"; import { State } from "./index.js"; @@ -69,7 +69,6 @@ export function ReadyView(state: State.Ready): VNode { title={<i18n.Translate>Amount</i18n.Translate>} text={<Amount value={state.amount} />} kind="positive" - big /> <Part title={<i18n.Translate>Merchant URL</i18n.Translate>} @@ -88,12 +87,16 @@ export function ReadyView(state: State.Ready): VNode { color="success" onClick={state.accept.onClick} > - <i18n.Translate>Accept tip</i18n.Translate> - </Button> - <Button onClick={state.ignore.onClick}> - <i18n.Translate>Ignore</i18n.Translate> + <i18n.Translate> + Receive {<Amount value={state.amount} />} + </i18n.Translate> </Button> </section> + <section> + <Link upperCased onClick={state.cancel}> + <i18n.Translate>Cancel</i18n.Translate> + </Link> + </section> </WalletAction> ); } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index 1bf38721c..b12e8df3b 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -29,6 +29,7 @@ import { CompletedView, LoadingExchangeView, LoadingInfoView, LoadingUriView, Su export interface Props { talerWithdrawUri: string | undefined; + cancel: () => Promise<void>; } export type State = @@ -67,13 +68,8 @@ export namespace State { status: "success"; error: undefined; - exchange: SelectFieldHandler; + exchangeUrl: string; - editExchange: ButtonHandler; - cancelEditExchange: ButtonHandler; - confirmEditExchange: ButtonHandler; - - showExchangeSelection: boolean; chosenAmount: AmountJson; withdrawalFee: AmountJson; toBeReceived: AmountJson; @@ -82,7 +78,9 @@ export namespace State { tosProps?: TermsOfServiceSectionProps; mustAcceptFirst: boolean; - ageRestriction: SelectFieldHandler; + ageRestriction?: SelectFieldHandler; + + cancel: () => Promise<void>; }; } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index 2e63c0f47..849dd5cca 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -17,20 +17,16 @@ import { Amounts } from "@gnu-taler/taler-util"; 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 { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { buildTermsOfServiceState } from "../../utils/index.js"; import * as wxApi from "../../wxApi.js"; -import { State, Props } from "./index.js"; +import { Props, State } from "./index.js"; export function useComponentState( - { talerWithdrawUri }: Props, + { talerWithdrawUri, cancel }: Props, api: typeof wxApi, ): State { - const [customExchange, setCustomExchange] = useState<string | undefined>( - undefined, - ); const [ageRestricted, setAgeRestricted] = useState(0); /** @@ -42,9 +38,8 @@ export function useComponentState( const uriInfo = await api.getWithdrawalDetailsForUri({ talerWithdrawUri, }); - const { exchanges: knownExchanges } = await api.listExchanges(); - - return { uriInfo, knownExchanges }; + const { amount, defaultExchangeBaseUrl } = uriInfo + return { amount, thisExchange: defaultExchangeBaseUrl }; }); /** @@ -55,65 +50,55 @@ export function useComponentState( ? undefined : uriInfoHook.response; - const { amount, thisExchange, thisCurrencyExchanges } = useMemo(() => { - if (!uriHookDep) - return { - amount: undefined, - thisExchange: undefined, - thisCurrencyExchanges: [], - }; + // const { amount, thisExchange } = useMemo(() => { + // if (!uriHookDep) + // return { + // amount: undefined, + // thisExchange: undefined, + // thisCurrencyExchanges: [], + // }; - const { uriInfo, knownExchanges } = uriHookDep; + // const { uriInfo } = uriHookDep; - const amount = uriInfo ? Amounts.parseOrThrow(uriInfo.amount) : undefined; - const thisCurrencyExchanges = - !amount || !knownExchanges - ? [] - : knownExchanges.filter((ex) => ex.currency === amount.currency); + // const amount = uriHookDep ? Amounts.parseOrThrow(uriHookDep.amount) : undefined; + // const thisExchange = uriHookDep?.thisExchange; - const thisExchange: string | undefined = - customExchange ?? - uriInfo?.defaultExchangeBaseUrl ?? - (thisCurrencyExchanges && thisCurrencyExchanges[0] - ? thisCurrencyExchanges[0].exchangeBaseUrl - : undefined); - - return { amount, thisExchange, thisCurrencyExchanges }; - }, [uriHookDep, customExchange]); + // return { amount, thisExchange }; + // }, [uriHookDep]); /** * For the exchange selected, bring the status of the terms of service */ 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); return { state }; - }, [thisExchange]); + }, [uriHookDep]); /** * With the exchange and amount, ask the wallet the information * about the withdrawal */ - const info = useAsyncAsHook(async () => { - if (!thisExchange || !amount) return false; + const amountHook = useAsyncAsHook(async () => { + if (!uriHookDep?.thisExchange) return false; const info = await api.getExchangeWithdrawalInfo({ - exchangeBaseUrl: thisExchange, - amount, + exchangeBaseUrl: uriHookDep?.thisExchange, + amount: Amounts.parseOrThrow(uriHookDep.amount), tosAcceptedFormat: ["text/xml"], }); - const withdrawalFee = Amounts.sub( - Amounts.parseOrThrow(info.withdrawalAmountRaw), - Amounts.parseOrThrow(info.withdrawalAmountEffective), - ).amount; + const withdrawAmount = { + raw: Amounts.parseOrThrow(info.withdrawalAmountRaw), + effective: Amounts.parseOrThrow(info.withdrawalAmountEffective), + } - return { info, withdrawalFee }; - }, [thisExchange, amount]); + return { amount: withdrawAmount }; + }, [uriHookDep]); const [reviewing, setReviewing] = useState<boolean>(false); const [reviewed, setReviewed] = useState<boolean>(false); @@ -124,9 +109,6 @@ export function useComponentState( const [doingWithdraw, setDoingWithdraw] = 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.hasError) { return { @@ -135,7 +117,10 @@ export function useComponentState( }; } - if (!thisExchange || !amount) { + const { amount, thisExchange } = uriInfoHook.response + const chosenAmount = Amounts.parseOrThrow(amount); + + if (!thisExchange) { return { status: "loading-exchange", error: { @@ -146,15 +131,17 @@ export function useComponentState( }; } - const selectedExchange = thisExchange; + // const selectedExchange = thisExchange; async function doWithdrawAndCheckError(): Promise<void> { + if (!thisExchange) return; + try { setDoingWithdraw(true); if (!talerWithdrawUri) return; const res = await api.acceptWithdrawal( talerWithdrawUri, - selectedExchange, + thisExchange, !ageRestricted ? undefined : ageRestricted, ); if (res.confirmTransferUrl) { @@ -169,54 +156,27 @@ export function useComponentState( setDoingWithdraw(false); } - const exchanges = thisCurrencyExchanges.reduce( - (prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }), - {}, - ); - - if (!info) { + if (!amountHook) { return { status: "loading", error: undefined } } - if (info.hasError) { + if (amountHook.hasError) { return { status: "loading-info", - error: info, + error: amountHook, }; } - if (!info.response) { + if (!amountHook.response) { return { status: "loading", error: undefined }; } if (withdrawCompleted) { return { status: "completed", error: undefined }; } - const exchangeHandler: SelectFieldHandler = { - onChange: async (e) => setNextExchange(e), - value: nextExchange ?? thisExchange, - list: exchanges, - isDirty: nextExchange !== undefined, - }; - - 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 withdrawalFee = Amounts.sub( + amountHook.response.amount.raw, + amountHook.response.amount.effective, + ).amount; + const toBeReceived = amountHook.response.amount.effective; const { state: termsState } = (!terms ? undefined @@ -225,11 +185,11 @@ export function useComponentState( : terms.response) || { state: undefined }; async function onAccept(accepted: boolean): Promise<void> { - if (!termsState) return; + if (!termsState || !thisExchange) return; try { await api.setExchangeTosAccepted( - selectedExchange, + thisExchange, accepted ? termsState.version : undefined, ); setReviewed(accepted); @@ -253,22 +213,22 @@ export function useComponentState( 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 { status: "success", error: undefined, - exchange: exchangeHandler, - editExchange, - cancelEditExchange, - confirmEditExchange, - showExchangeSelection, + exchangeUrl: thisExchange, toBeReceived, withdrawalFee, - chosenAmount: amount, - ageRestriction: { - list: ageRestrictionOptions, - value: String(ageRestricted), - onChange: async (v) => setAgeRestricted(parseInt(v, 10)), - }, + chosenAmount, + ageRestriction, doWithdrawal: { onClick: doingWithdraw || (mustAcceptFirst && !reviewed) @@ -286,6 +246,7 @@ export function useComponentState( terms: termsState, }, mustAcceptFirst, + cancel, }; } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx index 3ecccd1b2..9c4faaf4a 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx @@ -63,24 +63,13 @@ const ageRestrictionSelectField = { export const TermsOfServiceNotYetLoaded = createExample(SuccessView, { error: 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, + exchangeUrl: "https://exchange.demo.taler.net", mustAcceptFirst: false, withdrawalFee: { currency: "USD", @@ -97,24 +86,13 @@ export const TermsOfServiceNotYetLoaded = createExample(SuccessView, { export const WithSomeFee = createExample(SuccessView, { error: 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, + exchangeUrl: "https://exchange.demo.taler.net", mustAcceptFirst: false, withdrawalFee: { currency: "USD", @@ -132,24 +110,13 @@ export const WithSomeFee = createExample(SuccessView, { export const WithoutFee = createExample(SuccessView, { error: undefined, status: "success", - cancelEditExchange: nullHandler, - confirmEditExchange: nullHandler, - ageRestriction: ageRestrictionSelectField, chosenAmount: { currency: "USD", value: 2, - fraction: 10000000, + fraction: 0, }, doWithdrawal: nullHandler, - editExchange: nullHandler, - exchange: { - list: exchangeList, - value: "exchange.demo.taler.net", - onChange: async () => { - null; - }, - }, - showExchangeSelection: false, + exchangeUrl: "https://exchange.demo.taler.net", mustAcceptFirst: false, withdrawalFee: { currency: "USD", @@ -167,24 +134,13 @@ export const WithoutFee = createExample(SuccessView, { export const EditExchangeUntouched = createExample(SuccessView, { error: 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: true, + exchangeUrl: "https://exchange.demo.taler.net", mustAcceptFirst: false, withdrawalFee: { currency: "USD", @@ -202,25 +158,13 @@ export const EditExchangeUntouched = createExample(SuccessView, { export const EditExchangeModified = createExample(SuccessView, { error: undefined, status: "success", - cancelEditExchange: nullHandler, - confirmEditExchange: nullHandler, - ageRestriction: ageRestrictionSelectField, chosenAmount: { currency: "USD", value: 2, fraction: 10000000, }, doWithdrawal: nullHandler, - editExchange: nullHandler, - exchange: { - list: exchangeList, - isDirty: true, - value: "exchange.test.taler.net", - onChange: async () => { - null; - }, - }, - showExchangeSelection: true, + exchangeUrl: "https://exchange.demo.taler.net", mustAcceptFirst: false, withdrawalFee: { currency: "USD", @@ -240,11 +184,9 @@ export const CompletedWithoutBankURL = createExample(CompletedView, { error: undefined, }); -export const WithAgeRestrictionSelected = createExample(SuccessView, { +export const WithAgeRestriction = createExample(SuccessView, { error: undefined, status: "success", - cancelEditExchange: nullHandler, - confirmEditExchange: nullHandler, ageRestriction: ageRestrictionSelectField, chosenAmount: { currency: "USD", @@ -252,15 +194,7 @@ export const WithAgeRestrictionSelected = createExample(SuccessView, { fraction: 10000000, }, doWithdrawal: nullHandler, - editExchange: nullHandler, - exchange: { - list: exchangeList, - value: "exchange.demo.taler.net", - onChange: async () => { - null; - }, - }, - showExchangeSelection: false, + exchangeUrl: "https://exchange.demo.taler.net", mustAcceptFirst: false, withdrawalFee: { currency: "USD", diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts index f335f46a8..5917be092 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts @@ -44,7 +44,7 @@ describe("Withdraw CTA states", () => { it("should tell the user that the URI is missing", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerWithdrawUri: undefined }, { + useComponentState({ talerWithdrawUri: undefined, cancel: async () => { null } }, { listExchanges: async () => ({ exchanges }), getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ amount: "ARS:2", @@ -76,7 +76,7 @@ describe("Withdraw CTA states", () => { it("should tell the user that there is not known exchange", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerWithdrawUri: "taler-withdraw://" }, { + useComponentState({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, { listExchanges: async () => ({ exchanges }), getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ amount: "EUR:2", @@ -110,17 +110,18 @@ describe("Withdraw CTA states", () => { it("should be able to withdraw if tos are ok", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerWithdrawUri: "taler-withdraw://" }, { + useComponentState({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, { listExchanges: async () => ({ exchanges }), getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ amount: "ARS:2", possibleExchanges: exchanges, + defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl }), getExchangeWithdrawalInfo: async (): Promise<ExchangeWithdrawDetails> => ({ - withdrawalAmountRaw: "ARS:5", - withdrawalAmountEffective: "ARS:5", + withdrawalAmountRaw: "ARS:2", + withdrawalAmountEffective: "ARS:2", } as any), getExchangeTos: async (): Promise<GetExchangeTosResult> => ({ contentType: "text", @@ -154,12 +155,12 @@ describe("Withdraw CTA states", () => { expect(state.status).equals("success"); if (state.status !== "success") return; - expect(state.exchange.isDirty).false; - expect(state.exchange.value).equal("http://exchange.demo.taler.net"); - expect(state.exchange.list).deep.equal({ - "http://exchange.demo.taler.net": "http://exchange.demo.taler.net", - }); - expect(state.showExchangeSelection).false; + // expect(state.exchange.isDirty).false; + // expect(state.exchange.value).equal("http://exchange.demo.taler.net"); + // expect(state.exchange.list).deep.equal({ + // "http://exchange.demo.taler.net": "http://exchange.demo.taler.net", + // }); + // expect(state.showExchangeSelection).false; expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); 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 () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerWithdrawUri: "taler-withdraw://" }, { + useComponentState({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, { listExchanges: async () => ({ exchanges }), getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ amount: "ARS:2", possibleExchanges: exchanges, + defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl }), getExchangeWithdrawalInfo: async (): Promise<ExchangeWithdrawDetails> => ({ - withdrawalAmountRaw: "ARS:5", - withdrawalAmountEffective: "ARS:5", + withdrawalAmountRaw: "ARS:2", + withdrawalAmountEffective: "ARS:2", } as any), getExchangeTos: async (): Promise<GetExchangeTosResult> => ({ contentType: "text", @@ -220,12 +222,12 @@ describe("Withdraw CTA states", () => { expect(state.status).equals("success"); if (state.status !== "success") return; - expect(state.exchange.isDirty).false; - expect(state.exchange.value).equal("http://exchange.demo.taler.net"); - expect(state.exchange.list).deep.equal({ - "http://exchange.demo.taler.net": "http://exchange.demo.taler.net", - }); - expect(state.showExchangeSelection).false; + // expect(state.exchange.isDirty).false; + // expect(state.exchange.value).equal("http://exchange.demo.taler.net"); + // expect(state.exchange.list).deep.equal({ + // "http://exchange.demo.taler.net": "http://exchange.demo.taler.net", + // }); + // expect(state.showExchangeSelection).false; expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); @@ -245,12 +247,12 @@ describe("Withdraw CTA states", () => { expect(state.status).equals("success"); if (state.status !== "success") return; - expect(state.exchange.isDirty).false; - expect(state.exchange.value).equal("http://exchange.demo.taler.net"); - expect(state.exchange.list).deep.equal({ - "http://exchange.demo.taler.net": "http://exchange.demo.taler.net", - }); - expect(state.showExchangeSelection).false; + // expect(state.exchange.isDirty).false; + // expect(state.exchange.value).equal("http://exchange.demo.taler.net"); + // expect(state.exchange.list).deep.equal({ + // "http://exchange.demo.taler.net": "http://exchange.demo.taler.net", + // }); + // expect(state.showExchangeSelection).false; expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx index 578e5e61f..6ca8f888f 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx @@ -15,25 +15,26 @@ */ 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 { Loading } from "../../components/Loading.js"; import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.js"; import { Part } from "../../components/Part.js"; import { SelectList } from "../../components/SelectList.js"; import { Input, - LinkSuccess, + Link, SubTitle, SuccessBox, + SvgIcon, WalletAction, } from "../../components/styled/index.js"; -import { Amounts } from "@gnu-taler/taler-util"; -import { TermsOfServiceSection } from "../TermsOfServiceSection.js"; +import { useTranslationContext } from "../../context/translation.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 { const { i18n } = useTranslationContext(); @@ -115,77 +116,50 @@ export function SuccessView(state: State.Success): VNode { /> )} - <section> - <Part - title={<i18n.Translate>Total to withdraw</i18n.Translate>} - text={<Amount value={state.toBeReceived} />} - kind="positive" - /> - {Amounts.isNonZero(state.withdrawalFee) && ( - <Fragment> - <Part - title={<i18n.Translate>Chosen amount</i18n.Translate>} - text={<Amount value={state.chosenAmount} />} - kind="neutral" - /> - <Part - title={<i18n.Translate>Exchange fee</i18n.Translate>} - text={<Amount value={state.withdrawalFee} />} - kind="negative" - /> - </Fragment> - )} + <section style={{ textAlign: "left" }}> <Part - title={<i18n.Translate>Exchange</i18n.Translate>} - text={state.exchange.value} + title={ + <div + style={{ + display: "flex", + }} + > + <i18n.Translate>Exchange</i18n.Translate> + <SvgIcon + title="Edit" + dangerouslySetInnerHTML={{ __html: editIcon }} + color="black" + onClick={() => console.log("ok")} + /> + </div> + } + text={<ExchangeDetails exchange={state.exchangeUrl} />} kind="neutral" big /> - {state.showExchangeSelection ? ( - <Fragment> - <div> - <SelectList - label={<i18n.Translate>Known exchanges</i18n.Translate>} - list={state.exchange.list} - value={state.exchange.value} - name="switchingExchange" - onChange={state.exchange.onChange} - /> - </div> - <LinkSuccess - upperCased - style={{ fontSize: "small" }} - onClick={state.confirmEditExchange.onClick} - > - {state.exchange.isDirty ? ( - <i18n.Translate>Confirm exchange selection</i18n.Translate> - ) : ( - <i18n.Translate>Cancel exchange selection</i18n.Translate> - )} - </LinkSuccess> - </Fragment> - ) : ( - <LinkSuccess - style={{ fontSize: "small" }} - upperCased - onClick={state.editExchange.onClick} - > - <i18n.Translate>Edit exchange</i18n.Translate> - </LinkSuccess> + <Part + title={<i18n.Translate>Details</i18n.Translate>} + text={ + <WithdrawDetails + amount={{ + effective: state.toBeReceived, + raw: state.chosenAmount, + }} + /> + } + /> + {state.ageRestriction && ( + <Input> + <SelectList + label={<i18n.Translate>Age restriction</i18n.Translate>} + list={state.ageRestriction.list} + name="age" + value={state.ageRestriction.value} + onChange={state.ageRestriction.onChange} + /> + </Input> )} </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> @@ -197,7 +171,9 @@ export function SuccessView(state: State.Success): VNode { disabled={!state.doWithdrawal.onClick} onClick={state.doWithdrawal.onClick} > - <i18n.Translate>Confirm withdrawal</i18n.Translate> + <i18n.Translate> + Receive <Amount value={state.toBeReceived} /> + </i18n.Translate> </Button> )} {state.tosProps.terms.status === "notfound" && ( @@ -216,6 +192,11 @@ export function SuccessView(state: State.Success): VNode { <i18n.Translate>Loading terms of service...</i18n.Translate> </section> )} + <section> + <Link upperCased onClick={state.cancel}> + <i18n.Translate>Cancel</i18n.Translate> + </Link> + </section> </WalletAction> ); } diff --git a/packages/taler-wallet-webextension/src/stories.tsx b/packages/taler-wallet-webextension/src/stories.tsx index e1e2191a1..4a090e52e 100644 --- a/packages/taler-wallet-webextension/src/stories.tsx +++ b/packages/taler-wallet-webextension/src/stories.tsx @@ -206,7 +206,7 @@ function ExampleList({ {list.map((k) => ( <li key={k.name}> <dl> - <dt>{k.name}</dt> + <dt>{k.name.substring(k.name.indexOf("/") + 1)}</dt> {k.examples.map((r) => { const e = encodeURIComponent; const eId = `${e(r.group)}-${e(r.component)}-${e(r.name)}`; diff --git a/packages/taler-wallet-webextension/src/svg/edit_24px.svg b/packages/taler-wallet-webextension/src/svg/edit_24px.svg new file mode 100644 index 000000000..a4b3c9f6b --- /dev/null +++ b/packages/taler-wallet-webextension/src/svg/edit_24px.svg @@ -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>
\ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index 603163cee..f6cef7e90 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -76,33 +76,34 @@ export function Application(): VNode { <IoCProviderForRuntime> {/* <Match/> won't work in the first render if <Router /> is not called first */} {/* https://github.com/preactjs/preact-router/issues/415 */} - <Router history={hash_history} /> - <Match> - {({ path }: { path: string }) => { - if (path && path.startsWith("/cta")) return; - return ( - <Fragment> - <LogoHeader /> - <WalletNavBar path={path} /> - {shouldShowPendingOperations(path) && ( - <div - style={{ - backgroundColor: "lightcyan", - display: "flex", - justifyContent: "center", - }} - > - <PendingTransactions - goToTransaction={(tid: string) => - redirectTo(Pages.balanceTransaction({ tid })) - } - /> - </div> - )} - </Fragment> - ); - }} - </Match> + <Router history={hash_history}> + <Match default> + {({ path }: { path: string }) => { + if (path && path.startsWith("/cta")) return; + return ( + <Fragment> + <LogoHeader /> + <WalletNavBar path={path} /> + {shouldShowPendingOperations(path) && ( + <div + style={{ + backgroundColor: "lightcyan", + display: "flex", + justifyContent: "center", + }} + > + <PendingTransactions + goToTransaction={(tid: string) => + redirectTo(Pages.balanceTransaction({ tid })) + } + /> + </div> + )} + </Fragment> + ); + }} + </Match> + </Router> <WalletBox> {globalNotification && ( <SuccessBox onClick={clearNotification}> @@ -206,12 +207,28 @@ export function Application(): VNode { goToWalletManualWithdraw={(currency?: string) => 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 diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index e643fef18..ff3b70b65 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -32,7 +32,6 @@ import { TransactionRefund, TransactionTip, TransactionType, - TransactionWithdrawal, WithdrawalType, } from "@gnu-taler/taler-util"; import { styled } from "@linaria/react"; @@ -308,7 +307,14 @@ export function TransactionView({ )} <Part title={<i18n.Translate>Details</i18n.Translate>} - text={<WithdrawDetails transaction={transaction} />} + text={ + <WithdrawDetails + amount={{ + effective: Amounts.parseOrThrow(transaction.amountEffective), + raw: Amounts.parseOrThrow(transaction.amountRaw), + }} + /> + } /> </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 { effective: 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({ price, 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({ timestamp, total, |