summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension')
-rw-r--r--packages/taler-wallet-webextension/src/components/styled/index.tsx7
-rw-r--r--packages/taler-wallet-webextension/src/cta/Deposit/index.ts2
-rw-r--r--packages/taler-wallet-webextension/src/cta/Deposit/state.ts3
-rw-r--r--packages/taler-wallet-webextension/src/cta/Deposit/test.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Deposit/views.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/cta/Payment/index.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Payment/state.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Payment/test.ts16
-rw-r--r--packages/taler-wallet-webextension/src/cta/Payment/views.tsx6
-rw-r--r--packages/taler-wallet-webextension/src/cta/Refund/index.ts2
-rw-r--r--packages/taler-wallet-webextension/src/cta/Refund/state.ts3
-rw-r--r--packages/taler-wallet-webextension/src/cta/Refund/test.ts8
-rw-r--r--packages/taler-wallet-webextension/src/cta/Refund/views.tsx17
-rw-r--r--packages/taler-wallet-webextension/src/cta/Tip/index.ts3
-rw-r--r--packages/taler-wallet-webextension/src/cta/Tip/state.ts10
-rw-r--r--packages/taler-wallet-webextension/src/cta/Tip/stories.tsx1
-rw-r--r--packages/taler-wallet-webextension/src/cta/Tip/test.ts24
-rw-r--r--packages/taler-wallet-webextension/src/cta/Tip/views.tsx15
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/index.ts12
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/state.ts161
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx82
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/test.ts54
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx127
-rw-r--r--packages/taler-wallet-webextension/src/stories.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/svg/edit_24px.svg1
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Application.tsx81
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.tsx111
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 &nbsp; {<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 &nbsp;
+ Send &nbsp;
{<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 &nbsp; <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 &nbsp; {<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 &nbsp; <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,