pretty
This commit is contained in:
parent
860f10e6f0
commit
6ddb2de842
@ -23,11 +23,9 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { CompletedView, LoadingUriView, ReadyView } from "./views.js";
|
import { CompletedView, LoadingUriView, ReadyView } from "./views.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerDepositUri: string | undefined,
|
talerDepositUri: string | undefined;
|
||||||
amountStr: AmountString | undefined,
|
amountStr: AmountString | undefined;
|
||||||
cancel: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +36,6 @@ export type State =
|
|||||||
| State.Completed;
|
| State.Completed;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -63,10 +60,14 @@ export namespace State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
"loading": Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
completed: CompletedView,
|
completed: CompletedView,
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DepositPage = compose("Deposit", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
export const DepositPage = compose(
|
||||||
|
"Deposit",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { Amounts, CreateDepositGroupResponse } from "@gnu-taler/taler-util";
|
import { Amounts, CreateDepositGroupResponse } from "@gnu-taler/taler-util";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
@ -41,7 +40,7 @@ export function useComponentState(
|
|||||||
return { deposit, uri: talerDepositUri, amount };
|
return { deposit, uri: talerDepositUri, amount };
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!info) return { status: "loading", error: undefined }
|
if (!info) return { status: "loading", error: undefined };
|
||||||
if (info.hasError) {
|
if (info.hasError) {
|
||||||
return {
|
return {
|
||||||
status: "loading-uri",
|
status: "loading-uri",
|
||||||
@ -74,4 +73,4 @@ export function useComponentState(
|
|||||||
effective: deposit.effectiveDepositAmount,
|
effective: deposit.effectiveDepositAmount,
|
||||||
cancel,
|
cancel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,7 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { Amounts, PrepareDepositResponse } from "@gnu-taler/taler-util";
|
||||||
Amounts, PrepareDepositResponse
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { mountHook } from "../../test-utils.js";
|
import { mountHook } from "../../test-utils.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
@ -30,11 +28,20 @@ describe("Deposit CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerDepositUri: undefined, amountStr: undefined, cancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareRefund: async () => ({}),
|
{
|
||||||
applyRefund: async () => ({}),
|
talerDepositUri: undefined,
|
||||||
onUpdateNotification: async () => ({}),
|
amountStr: undefined,
|
||||||
} as any),
|
cancel: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prepareRefund: async () => ({}),
|
||||||
|
applyRefund: async () => ({}),
|
||||||
|
onUpdateNotification: async () => ({}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -61,14 +68,23 @@ describe("Deposit CTA states", () => {
|
|||||||
it("should be ready after loading", async () => {
|
it("should be ready after loading", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerDepositUri: "payto://refund/asdasdas", amountStr: "EUR:1", cancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareDeposit: async () =>
|
{
|
||||||
({
|
talerDepositUri: "payto://refund/asdasdas",
|
||||||
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
|
amountStr: "EUR:1",
|
||||||
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
|
cancel: async () => {
|
||||||
} as PrepareDepositResponse as any),
|
null;
|
||||||
createDepositGroup: async () => ({}),
|
},
|
||||||
} as any),
|
},
|
||||||
|
{
|
||||||
|
prepareDeposit: async () =>
|
||||||
|
({
|
||||||
|
effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"),
|
||||||
|
totalDepositCost: Amounts.parseOrThrow("EUR:1.2"),
|
||||||
|
} as PrepareDepositResponse as any),
|
||||||
|
createDepositGroup: async () => ({}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,6 @@ export type State =
|
|||||||
| State.Ready;
|
| State.Ready;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -59,8 +58,8 @@ export namespace State {
|
|||||||
status: "ready";
|
status: "ready";
|
||||||
create: ButtonHandler;
|
create: ButtonHandler;
|
||||||
subject: TextFieldHandler;
|
subject: TextFieldHandler;
|
||||||
toBeReceived: AmountJson,
|
toBeReceived: AmountJson;
|
||||||
chosenAmount: AmountJson,
|
chosenAmount: AmountJson;
|
||||||
exchangeUrl: string;
|
exchangeUrl: string;
|
||||||
invalid: boolean;
|
invalid: boolean;
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -71,10 +70,12 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
"created": CreatedView,
|
created: CreatedView,
|
||||||
"ready": ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const InvoiceCreatePage = compose(
|
||||||
export const InvoiceCreatePage = compose("InvoiceCreatePage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
"InvoiceCreatePage",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -25,21 +25,22 @@ export function useComponentState(
|
|||||||
{ amount: amountStr, onClose }: Props,
|
{ amount: amountStr, onClose }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const amount = Amounts.parseOrThrow(amountStr)
|
const amount = Amounts.parseOrThrow(amountStr);
|
||||||
|
|
||||||
const [subject, setSubject] = useState("");
|
const [subject, setSubject] = useState("");
|
||||||
const [talerUri, setTalerUri] = useState("")
|
const [talerUri, setTalerUri] = useState("");
|
||||||
|
|
||||||
const hook = useAsyncAsHook(api.listExchanges);
|
const hook = useAsyncAsHook(api.listExchanges);
|
||||||
const [exchangeIdx, setExchangeIdx] = useState("0")
|
const [exchangeIdx, setExchangeIdx] = useState("0");
|
||||||
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
const [operationError, setOperationError] = useState<
|
||||||
|
TalerErrorDetail | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
if (!hook) {
|
if (!hook) {
|
||||||
return {
|
return {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if (hook.hasError) {
|
if (hook.hasError) {
|
||||||
return {
|
return {
|
||||||
@ -54,62 +55,65 @@ export function useComponentState(
|
|||||||
talerUri,
|
talerUri,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
cancel: {
|
cancel: {
|
||||||
onClick: onClose
|
onClick: onClose,
|
||||||
},
|
},
|
||||||
copyToClipboard: {
|
copyToClipboard: {
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
navigator.clipboard.writeText(talerUri);
|
navigator.clipboard.writeText(talerUri);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const exchanges = hook.response.exchanges.filter(e => e.currency === amount.currency);
|
const exchanges = hook.response.exchanges.filter(
|
||||||
const exchangeMap = exchanges.reduce((prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }), {} as Record<string, string>)
|
(e) => e.currency === amount.currency,
|
||||||
|
);
|
||||||
|
const exchangeMap = exchanges.reduce(
|
||||||
|
(prev, cur, idx) => ({ ...prev, [String(idx)]: cur.exchangeBaseUrl }),
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
const selected = exchanges[Number(exchangeIdx)];
|
const selected = exchanges[Number(exchangeIdx)];
|
||||||
|
|
||||||
async function accept(): Promise<string> {
|
async function accept(): Promise<string> {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const resp = await api.initiatePeerPullPayment({
|
const resp = await api.initiatePeerPullPayment({
|
||||||
amount: Amounts.stringify(amount),
|
amount: Amounts.stringify(amount),
|
||||||
exchangeBaseUrl: selected.exchangeBaseUrl,
|
exchangeBaseUrl: selected.exchangeBaseUrl,
|
||||||
partialContractTerms: {
|
partialContractTerms: {
|
||||||
summary: subject
|
summary: subject,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
return resp.talerUri
|
return resp.talerUri;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail)
|
setOperationError(e.errorDetail);
|
||||||
}
|
}
|
||||||
console.error(e)
|
console.error(e);
|
||||||
throw Error("error trying to accept")
|
throw Error("error trying to accept");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
subject: {
|
subject: {
|
||||||
error: !subject ? "cant be empty" : undefined,
|
error: !subject ? "cant be empty" : undefined,
|
||||||
value: subject,
|
value: subject,
|
||||||
onInput: async (e) => setSubject(e)
|
onInput: async (e) => setSubject(e),
|
||||||
},
|
},
|
||||||
invalid: !subject || Amounts.isZero(amount),
|
invalid: !subject || Amounts.isZero(amount),
|
||||||
exchangeUrl: selected.exchangeBaseUrl,
|
exchangeUrl: selected.exchangeBaseUrl,
|
||||||
create: {
|
create: {
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const uri = await accept();
|
const uri = await accept();
|
||||||
setTalerUri(uri)
|
setTalerUri(uri);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
onClick: onClose
|
onClick: onClose,
|
||||||
},
|
},
|
||||||
chosenAmount: amount,
|
chosenAmount: amount,
|
||||||
toBeReceived: amount,
|
toBeReceived: amount,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
operationError
|
operationError,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,7 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
describe("test description", () => {
|
describe("test description", () => {
|
||||||
|
|
||||||
it("should assert", () => {
|
it("should assert", () => {
|
||||||
|
expect([]).deep.equals([]);
|
||||||
expect([]).deep.equals([])
|
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
@ -14,7 +14,12 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AbsoluteTime, AmountJson, PreparePayResult, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
AbsoluteTime,
|
||||||
|
AmountJson,
|
||||||
|
PreparePayResult,
|
||||||
|
TalerErrorDetail,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
import { ButtonHandler } from "../../mui/handlers.js";
|
import { ButtonHandler } from "../../mui/handlers.js";
|
||||||
@ -37,7 +42,6 @@ export type State =
|
|||||||
| State.Ready;
|
| State.Ready;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -52,20 +56,20 @@ export namespace State {
|
|||||||
error: undefined;
|
error: undefined;
|
||||||
uri: string;
|
uri: string;
|
||||||
cancel: ButtonHandler;
|
cancel: ButtonHandler;
|
||||||
amount: AmountJson,
|
amount: AmountJson;
|
||||||
goToWalletManualWithdraw: (currency: string) => Promise<void>;
|
goToWalletManualWithdraw: (currency: string) => Promise<void>;
|
||||||
summary: string | undefined,
|
summary: string | undefined;
|
||||||
expiration: AbsoluteTime | undefined,
|
expiration: AbsoluteTime | undefined;
|
||||||
operationError?: TalerErrorDetail;
|
operationError?: TalerErrorDetail;
|
||||||
payStatus: PreparePayResult;
|
payStatus: PreparePayResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NoBalanceForCurrency extends BaseInfo {
|
export interface NoBalanceForCurrency extends BaseInfo {
|
||||||
status: "no-balance-for-currency"
|
status: "no-balance-for-currency";
|
||||||
balance: undefined;
|
balance: undefined;
|
||||||
}
|
}
|
||||||
export interface NoEnoughBalance extends BaseInfo {
|
export interface NoEnoughBalance extends BaseInfo {
|
||||||
status: "no-enough-balance"
|
status: "no-enough-balance";
|
||||||
balance: AmountJson;
|
balance: AmountJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,9 +86,11 @@ const viewMapping: StateViewMap<State> = {
|
|||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
"no-balance-for-currency": ReadyView,
|
"no-balance-for-currency": ReadyView,
|
||||||
"no-enough-balance": ReadyView,
|
"no-enough-balance": ReadyView,
|
||||||
"ready": ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const InvoicePayPage = compose(
|
||||||
export const InvoicePayPage = compose("InvoicePayPage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
"InvoicePayPage",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -14,7 +14,15 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AbsoluteTime, Amounts, NotificationType, PreparePayResult, PreparePayResultType, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
AbsoluteTime,
|
||||||
|
Amounts,
|
||||||
|
NotificationType,
|
||||||
|
PreparePayResult,
|
||||||
|
PreparePayResultType,
|
||||||
|
TalerErrorDetail,
|
||||||
|
TalerProtocolTimestamp,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
@ -27,11 +35,11 @@ export function useComponentState(
|
|||||||
): State {
|
): State {
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
const p2p = await api.checkPeerPullPayment({
|
const p2p = await api.checkPeerPullPayment({
|
||||||
talerUri: talerPayPullUri
|
talerUri: talerPayPullUri,
|
||||||
})
|
});
|
||||||
const balance = await api.getBalance();
|
const balance = await api.getBalance();
|
||||||
return { p2p, balance }
|
return { p2p, balance };
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.onUpdateNotification([NotificationType.CoinWithdrawn], () => {
|
api.onUpdateNotification([NotificationType.CoinWithdrawn], () => {
|
||||||
@ -39,13 +47,15 @@ export function useComponentState(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
const [operationError, setOperationError] = useState<
|
||||||
|
TalerErrorDetail | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
if (!hook) {
|
if (!hook) {
|
||||||
return {
|
return {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if (hook.hasError) {
|
if (hook.hasError) {
|
||||||
return {
|
return {
|
||||||
@ -56,13 +66,17 @@ export function useComponentState(
|
|||||||
|
|
||||||
// const { payStatus } = hook.response.p2p;
|
// const { payStatus } = hook.response.p2p;
|
||||||
|
|
||||||
const { amount: purseAmount, contractTerms, peerPullPaymentIncomingId } = hook.response.p2p
|
const {
|
||||||
|
amount: purseAmount,
|
||||||
|
contractTerms,
|
||||||
|
peerPullPaymentIncomingId,
|
||||||
|
} = hook.response.p2p;
|
||||||
|
|
||||||
|
const amountStr: string = contractTerms?.amount;
|
||||||
const amountStr: string = contractTerms?.amount
|
const amount = Amounts.parseOrThrow(amountStr);
|
||||||
const amount = Amounts.parseOrThrow(amountStr)
|
const summary: string | undefined = contractTerms?.summary;
|
||||||
const summary: string | undefined = contractTerms?.summary
|
const expiration: TalerProtocolTimestamp | undefined =
|
||||||
const expiration: TalerProtocolTimestamp | undefined = contractTerms?.purse_expiration
|
contractTerms?.purse_expiration;
|
||||||
|
|
||||||
const foundBalance = hook.response.balance.balances.find(
|
const foundBalance = hook.response.balance.balances.find(
|
||||||
(b) => Amounts.parseOrThrow(b.available).currency === amount.currency,
|
(b) => Amounts.parseOrThrow(b.available).currency === amount.currency,
|
||||||
@ -71,35 +85,32 @@ export function useComponentState(
|
|||||||
const paymentPossible: PreparePayResult = {
|
const paymentPossible: PreparePayResult = {
|
||||||
status: PreparePayResultType.PaymentPossible,
|
status: PreparePayResultType.PaymentPossible,
|
||||||
proposalId: "fakeID",
|
proposalId: "fakeID",
|
||||||
contractTerms: {
|
contractTerms: {} as any,
|
||||||
} as any,
|
|
||||||
contractTermsHash: "asd",
|
contractTermsHash: "asd",
|
||||||
amountRaw: hook.response.p2p.amount,
|
amountRaw: hook.response.p2p.amount,
|
||||||
amountEffective: hook.response.p2p.amount,
|
amountEffective: hook.response.p2p.amount,
|
||||||
noncePriv: "",
|
noncePriv: "",
|
||||||
} as PreparePayResult
|
} as PreparePayResult;
|
||||||
|
|
||||||
const insufficientBalance: PreparePayResult = {
|
const insufficientBalance: PreparePayResult = {
|
||||||
status: PreparePayResultType.InsufficientBalance,
|
status: PreparePayResultType.InsufficientBalance,
|
||||||
proposalId: "fakeID",
|
proposalId: "fakeID",
|
||||||
contractTerms: {
|
contractTerms: {} as any,
|
||||||
} as any,
|
|
||||||
amountRaw: hook.response.p2p.amount,
|
amountRaw: hook.response.p2p.amount,
|
||||||
noncePriv: "",
|
noncePriv: "",
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const baseResult = {
|
const baseResult = {
|
||||||
uri: talerPayPullUri,
|
uri: talerPayPullUri,
|
||||||
cancel: {
|
cancel: {
|
||||||
onClick: onClose
|
onClick: onClose,
|
||||||
},
|
},
|
||||||
amount,
|
amount,
|
||||||
goToWalletManualWithdraw,
|
goToWalletManualWithdraw,
|
||||||
summary,
|
summary,
|
||||||
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
|
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
|
||||||
operationError,
|
operationError,
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!foundBalance) {
|
if (!foundBalance) {
|
||||||
return {
|
return {
|
||||||
@ -108,20 +119,21 @@ export function useComponentState(
|
|||||||
balance: undefined,
|
balance: undefined,
|
||||||
...baseResult,
|
...baseResult,
|
||||||
payStatus: insufficientBalance,
|
payStatus: insufficientBalance,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundAmount = Amounts.parseOrThrow(foundBalance.available);
|
const foundAmount = Amounts.parseOrThrow(foundBalance.available);
|
||||||
|
|
||||||
//FIXME: should use pay result type since it check for coins exceptions
|
//FIXME: should use pay result type since it check for coins exceptions
|
||||||
if (Amounts.cmp(foundAmount, amount) < 0) { //payStatus.status === PreparePayResultType.InsufficientBalance) {
|
if (Amounts.cmp(foundAmount, amount) < 0) {
|
||||||
|
//payStatus.status === PreparePayResultType.InsufficientBalance) {
|
||||||
return {
|
return {
|
||||||
status: 'no-enough-balance',
|
status: "no-enough-balance",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
balance: foundAmount,
|
balance: foundAmount,
|
||||||
...baseResult,
|
...baseResult,
|
||||||
payStatus: insufficientBalance,
|
payStatus: insufficientBalance,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
|
// if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
|
||||||
@ -135,19 +147,18 @@ export function useComponentState(
|
|||||||
async function accept(): Promise<void> {
|
async function accept(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const resp = await api.acceptPeerPullPayment({
|
const resp = await api.acceptPeerPullPayment({
|
||||||
peerPullPaymentIncomingId
|
peerPullPaymentIncomingId,
|
||||||
})
|
});
|
||||||
await onClose()
|
await onClose();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail)
|
setOperationError(e.errorDetail);
|
||||||
}
|
}
|
||||||
console.error(e)
|
console.error(e);
|
||||||
throw Error("error trying to accept")
|
throw Error("error trying to accept");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
@ -155,7 +166,7 @@ export function useComponentState(
|
|||||||
payStatus: paymentPossible,
|
payStatus: paymentPossible,
|
||||||
balance: foundAmount,
|
balance: foundAmount,
|
||||||
accept: {
|
accept: {
|
||||||
onClick: accept
|
onClick: accept,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,7 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
describe("test description", () => {
|
describe("test description", () => {
|
||||||
|
|
||||||
it("should assert", () => {
|
it("should assert", () => {
|
||||||
|
expect([]).deep.equals([]);
|
||||||
expect([]).deep.equals([])
|
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
@ -14,7 +14,14 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountJson, ConfirmPayResult, PreparePayResult, PreparePayResultAlreadyConfirmed, PreparePayResultInsufficientBalance, PreparePayResultPaymentPossible } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
AmountJson,
|
||||||
|
ConfirmPayResult,
|
||||||
|
PreparePayResult,
|
||||||
|
PreparePayResultAlreadyConfirmed,
|
||||||
|
PreparePayResultInsufficientBalance,
|
||||||
|
PreparePayResultPaymentPossible,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
@ -24,8 +31,6 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { LoadingUriView, BaseView } from "./views.js";
|
import { LoadingUriView, BaseView } from "./views.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerPayUri?: string;
|
talerPayUri?: string;
|
||||||
goToWalletManualWithdraw: (amount?: string) => Promise<void>;
|
goToWalletManualWithdraw: (amount?: string) => Promise<void>;
|
||||||
@ -42,7 +47,6 @@ export type State =
|
|||||||
| State.Confirmed;
|
| State.Confirmed;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -60,12 +64,12 @@ export namespace State {
|
|||||||
cancel: () => Promise<void>;
|
cancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
export interface NoBalanceForCurrency extends BaseInfo {
|
export interface NoBalanceForCurrency extends BaseInfo {
|
||||||
status: "no-balance-for-currency"
|
status: "no-balance-for-currency";
|
||||||
payStatus: PreparePayResult;
|
payStatus: PreparePayResult;
|
||||||
balance: undefined;
|
balance: undefined;
|
||||||
}
|
}
|
||||||
export interface NoEnoughBalance extends BaseInfo {
|
export interface NoEnoughBalance extends BaseInfo {
|
||||||
status: "no-enough-balance"
|
status: "no-enough-balance";
|
||||||
payStatus: PreparePayResult;
|
payStatus: PreparePayResult;
|
||||||
balance: AmountJson;
|
balance: AmountJson;
|
||||||
}
|
}
|
||||||
@ -101,4 +105,8 @@ const viewMapping: StateViewMap<State> = {
|
|||||||
ready: BaseView,
|
ready: BaseView,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PaymentPage = compose("Payment", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
export const PaymentPage = compose(
|
||||||
|
"Payment",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -14,8 +14,15 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
import { AmountJson, Amounts, ConfirmPayResult, ConfirmPayResultType, NotificationType, PreparePayResultType, TalerErrorCode } from "@gnu-taler/taler-util";
|
AmountJson,
|
||||||
|
Amounts,
|
||||||
|
ConfirmPayResult,
|
||||||
|
ConfirmPayResultType,
|
||||||
|
NotificationType,
|
||||||
|
PreparePayResultType,
|
||||||
|
TalerErrorCode,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
@ -82,8 +89,9 @@ export function useComponentState(
|
|||||||
uri: hook.response.uri,
|
uri: hook.response.uri,
|
||||||
amount,
|
amount,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
cancel, goToWalletManualWithdraw
|
cancel,
|
||||||
}
|
goToWalletManualWithdraw,
|
||||||
|
};
|
||||||
|
|
||||||
if (!foundBalance) {
|
if (!foundBalance) {
|
||||||
return {
|
return {
|
||||||
@ -91,7 +99,7 @@ export function useComponentState(
|
|||||||
balance: undefined,
|
balance: undefined,
|
||||||
payStatus,
|
payStatus,
|
||||||
...baseResult,
|
...baseResult,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundAmount = Amounts.parseOrThrow(foundBalance.available);
|
const foundAmount = Amounts.parseOrThrow(foundBalance.available);
|
||||||
@ -109,11 +117,11 @@ export function useComponentState(
|
|||||||
|
|
||||||
if (payStatus.status === PreparePayResultType.InsufficientBalance) {
|
if (payStatus.status === PreparePayResultType.InsufficientBalance) {
|
||||||
return {
|
return {
|
||||||
status: 'no-enough-balance',
|
status: "no-enough-balance",
|
||||||
balance: foundAmount,
|
balance: foundAmount,
|
||||||
payStatus,
|
payStatus,
|
||||||
...baseResult,
|
...baseResult,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
|
if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
|
||||||
@ -125,7 +133,6 @@ export function useComponentState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function doPayment(): Promise<void> {
|
async function doPayment(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (payStatus.status !== "payment-possible") {
|
if (payStatus.status !== "payment-possible") {
|
||||||
@ -169,8 +176,6 @@ export function useComponentState(
|
|||||||
payHandler,
|
payHandler,
|
||||||
payStatus,
|
payStatus,
|
||||||
...baseResult,
|
...baseResult,
|
||||||
balance: foundAmount
|
balance: foundAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,9 +70,16 @@ describe("Payment CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: undefined, cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState(
|
||||||
onUpdateNotification: nullFunction,
|
{
|
||||||
} as Partial<typeof wxApi> as any),
|
talerPayUri: undefined,
|
||||||
|
cancel: nullFunction,
|
||||||
|
goToWalletManualWithdraw: nullFunction,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onUpdateNotification: nullFunction,
|
||||||
|
} as Partial<typeof wxApi> as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -98,18 +105,25 @@ describe("Payment CTA states", () => {
|
|||||||
it("should response with no balance", async () => {
|
it("should response with no balance", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState(
|
||||||
onUpdateNotification: nullFunction,
|
{
|
||||||
preparePay: async () =>
|
talerPayUri: "taller://pay",
|
||||||
({
|
cancel: nullFunction,
|
||||||
amountRaw: "USD:10",
|
goToWalletManualWithdraw: nullFunction,
|
||||||
status: PreparePayResultType.InsufficientBalance,
|
},
|
||||||
} as Partial<PreparePayResult>),
|
{
|
||||||
getBalance: async () =>
|
onUpdateNotification: nullFunction,
|
||||||
({
|
preparePay: async () =>
|
||||||
balances: [],
|
({
|
||||||
} as Partial<BalancesResponse>),
|
amountRaw: "USD:10",
|
||||||
} as Partial<typeof wxApi> as any),
|
status: PreparePayResultType.InsufficientBalance,
|
||||||
|
} as Partial<PreparePayResult>),
|
||||||
|
getBalance: async () =>
|
||||||
|
({
|
||||||
|
balances: [],
|
||||||
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -133,22 +147,29 @@ describe("Payment CTA states", () => {
|
|||||||
it("should not be able to pay if there is no enough balance", async () => {
|
it("should not be able to pay if there is no enough balance", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState(
|
||||||
onUpdateNotification: nullFunction,
|
{
|
||||||
preparePay: async () =>
|
talerPayUri: "taller://pay",
|
||||||
({
|
cancel: nullFunction,
|
||||||
amountRaw: "USD:10",
|
goToWalletManualWithdraw: nullFunction,
|
||||||
status: PreparePayResultType.InsufficientBalance,
|
},
|
||||||
} as Partial<PreparePayResult>),
|
{
|
||||||
getBalance: async () =>
|
onUpdateNotification: nullFunction,
|
||||||
({
|
preparePay: async () =>
|
||||||
balances: [
|
({
|
||||||
{
|
amountRaw: "USD:10",
|
||||||
available: "USD:5",
|
status: PreparePayResultType.InsufficientBalance,
|
||||||
},
|
} as Partial<PreparePayResult>),
|
||||||
],
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
} as Partial<typeof wxApi> as any),
|
balances: [
|
||||||
|
{
|
||||||
|
available: "USD:5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -172,23 +193,30 @@ describe("Payment CTA states", () => {
|
|||||||
it("should be able to pay (without fee)", async () => {
|
it("should be able to pay (without fee)", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState(
|
||||||
onUpdateNotification: nullFunction,
|
{
|
||||||
preparePay: async () =>
|
talerPayUri: "taller://pay",
|
||||||
({
|
cancel: nullFunction,
|
||||||
amountRaw: "USD:10",
|
goToWalletManualWithdraw: nullFunction,
|
||||||
amountEffective: "USD:10",
|
},
|
||||||
status: PreparePayResultType.PaymentPossible,
|
{
|
||||||
} as Partial<PreparePayResult>),
|
onUpdateNotification: nullFunction,
|
||||||
getBalance: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
amountRaw: "USD:10",
|
||||||
{
|
amountEffective: "USD:10",
|
||||||
available: "USD:15",
|
status: PreparePayResultType.PaymentPossible,
|
||||||
},
|
} as Partial<PreparePayResult>),
|
||||||
],
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
} as Partial<typeof wxApi> as any),
|
balances: [
|
||||||
|
{
|
||||||
|
available: "USD:15",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -214,23 +242,30 @@ describe("Payment CTA states", () => {
|
|||||||
it("should be able to pay (with fee)", async () => {
|
it("should be able to pay (with fee)", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState(
|
||||||
onUpdateNotification: nullFunction,
|
{
|
||||||
preparePay: async () =>
|
talerPayUri: "taller://pay",
|
||||||
({
|
cancel: nullFunction,
|
||||||
amountRaw: "USD:9",
|
goToWalletManualWithdraw: nullFunction,
|
||||||
amountEffective: "USD:10",
|
},
|
||||||
status: PreparePayResultType.PaymentPossible,
|
{
|
||||||
} as Partial<PreparePayResult>),
|
onUpdateNotification: nullFunction,
|
||||||
getBalance: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
amountRaw: "USD:9",
|
||||||
{
|
amountEffective: "USD:10",
|
||||||
available: "USD:15",
|
status: PreparePayResultType.PaymentPossible,
|
||||||
},
|
} as Partial<PreparePayResult>),
|
||||||
],
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
} as Partial<typeof wxApi> as any),
|
balances: [
|
||||||
|
{
|
||||||
|
available: "USD:15",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -256,28 +291,35 @@ describe("Payment CTA states", () => {
|
|||||||
it("should get confirmation done after pay successfully", async () => {
|
it("should get confirmation done after pay successfully", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState(
|
||||||
onUpdateNotification: nullFunction,
|
{
|
||||||
preparePay: async () =>
|
talerPayUri: "taller://pay",
|
||||||
({
|
cancel: nullFunction,
|
||||||
amountRaw: "USD:9",
|
goToWalletManualWithdraw: nullFunction,
|
||||||
amountEffective: "USD:10",
|
},
|
||||||
status: PreparePayResultType.PaymentPossible,
|
{
|
||||||
} as Partial<PreparePayResult>),
|
onUpdateNotification: nullFunction,
|
||||||
getBalance: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
amountRaw: "USD:9",
|
||||||
{
|
amountEffective: "USD:10",
|
||||||
available: "USD:15",
|
status: PreparePayResultType.PaymentPossible,
|
||||||
},
|
} as Partial<PreparePayResult>),
|
||||||
],
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
confirmPay: async () =>
|
balances: [
|
||||||
({
|
{
|
||||||
type: ConfirmPayResultType.Done,
|
available: "USD:15",
|
||||||
contractTerms: {},
|
},
|
||||||
} as Partial<ConfirmPayResult>),
|
],
|
||||||
} as Partial<typeof wxApi> as any),
|
} as Partial<BalancesResponse>),
|
||||||
|
confirmPay: async () =>
|
||||||
|
({
|
||||||
|
type: ConfirmPayResultType.Done,
|
||||||
|
contractTerms: {},
|
||||||
|
} as Partial<ConfirmPayResult>),
|
||||||
|
} as Partial<typeof wxApi> as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -317,28 +359,35 @@ describe("Payment CTA states", () => {
|
|||||||
it("should not stay in ready state after pay with error", async () => {
|
it("should not stay in ready state after pay with error", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState(
|
||||||
onUpdateNotification: nullFunction,
|
{
|
||||||
preparePay: async () =>
|
talerPayUri: "taller://pay",
|
||||||
({
|
cancel: nullFunction,
|
||||||
amountRaw: "USD:9",
|
goToWalletManualWithdraw: nullFunction,
|
||||||
amountEffective: "USD:10",
|
},
|
||||||
status: PreparePayResultType.PaymentPossible,
|
{
|
||||||
} as Partial<PreparePayResult>),
|
onUpdateNotification: nullFunction,
|
||||||
getBalance: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
amountRaw: "USD:9",
|
||||||
{
|
amountEffective: "USD:10",
|
||||||
available: "USD:15",
|
status: PreparePayResultType.PaymentPossible,
|
||||||
},
|
} as Partial<PreparePayResult>),
|
||||||
],
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
confirmPay: async () =>
|
balances: [
|
||||||
({
|
{
|
||||||
type: ConfirmPayResultType.Pending,
|
available: "USD:15",
|
||||||
lastError: { code: 1 },
|
},
|
||||||
} as Partial<ConfirmPayResult>),
|
],
|
||||||
} as Partial<typeof wxApi> as any),
|
} as Partial<BalancesResponse>),
|
||||||
|
confirmPay: async () =>
|
||||||
|
({
|
||||||
|
type: ConfirmPayResultType.Pending,
|
||||||
|
lastError: { code: 1 },
|
||||||
|
} as Partial<ConfirmPayResult>),
|
||||||
|
} as Partial<typeof wxApi> as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -393,23 +442,30 @@ describe("Payment CTA states", () => {
|
|||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction }, {
|
useComponentState(
|
||||||
onUpdateNotification: subscriptions.saveSubscription,
|
{
|
||||||
preparePay: async () =>
|
talerPayUri: "taller://pay",
|
||||||
({
|
cancel: nullFunction,
|
||||||
amountRaw: "USD:9",
|
goToWalletManualWithdraw: nullFunction,
|
||||||
amountEffective: "USD:10",
|
},
|
||||||
status: PreparePayResultType.PaymentPossible,
|
{
|
||||||
} as Partial<PreparePayResult>),
|
onUpdateNotification: subscriptions.saveSubscription,
|
||||||
getBalance: async () =>
|
preparePay: async () =>
|
||||||
({
|
({
|
||||||
balances: [
|
amountRaw: "USD:9",
|
||||||
{
|
amountEffective: "USD:10",
|
||||||
available: Amounts.stringify(availableBalance),
|
status: PreparePayResultType.PaymentPossible,
|
||||||
},
|
} as Partial<PreparePayResult>),
|
||||||
],
|
getBalance: async () =>
|
||||||
} as Partial<BalancesResponse>),
|
({
|
||||||
} as Partial<typeof wxApi> as any),
|
balances: [
|
||||||
|
{
|
||||||
|
available: Amounts.stringify(availableBalance),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as Partial<BalancesResponse>),
|
||||||
|
} as Partial<typeof wxApi> as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -21,9 +21,13 @@ import { ButtonHandler } from "../../mui/handlers.js";
|
|||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { CompletedView, IgnoredView, InProgressView, LoadingUriView, ReadyView } from "./views.js";
|
import {
|
||||||
|
CompletedView,
|
||||||
|
IgnoredView,
|
||||||
|
InProgressView,
|
||||||
|
LoadingUriView,
|
||||||
|
ReadyView,
|
||||||
|
} from "./views.js";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerRefundUri?: string;
|
talerRefundUri?: string;
|
||||||
@ -39,7 +43,6 @@ export type State =
|
|||||||
| State.Completed;
|
| State.Completed;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -75,13 +78,11 @@ export namespace State {
|
|||||||
export interface InProgress extends BaseInfo {
|
export interface InProgress extends BaseInfo {
|
||||||
status: "in-progress";
|
status: "in-progress";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
|
|
||||||
}
|
}
|
||||||
export interface Completed extends BaseInfo {
|
export interface Completed extends BaseInfo {
|
||||||
status: "completed";
|
status: "completed";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
@ -93,4 +94,8 @@ const viewMapping: StateViewMap<State> = {
|
|||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RefundPage = compose("Refund", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
export const RefundPage = compose(
|
||||||
|
"Refund",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { Amounts, NotificationType } from "@gnu-taler/taler-util";
|
import { Amounts, NotificationType } from "@gnu-taler/taler-util";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
@ -40,7 +39,7 @@ export function useComponentState(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!info) {
|
if (!info) {
|
||||||
return { status: "loading", error: undefined }
|
return { status: "loading", error: undefined };
|
||||||
}
|
}
|
||||||
if (info.hasError) {
|
if (info.hasError) {
|
||||||
return {
|
return {
|
||||||
@ -67,7 +66,7 @@ export function useComponentState(
|
|||||||
products: info.response.refund.info.products,
|
products: info.response.refund.info.products,
|
||||||
awaitingAmount: Amounts.parseOrThrow(refund.awaiting),
|
awaitingAmount: Amounts.parseOrThrow(refund.awaiting),
|
||||||
error: undefined,
|
error: undefined,
|
||||||
}
|
};
|
||||||
|
|
||||||
if (ignored) {
|
if (ignored) {
|
||||||
return {
|
return {
|
||||||
|
@ -21,8 +21,9 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AmountJson,
|
AmountJson,
|
||||||
Amounts, NotificationType,
|
Amounts,
|
||||||
PrepareRefundResult
|
NotificationType,
|
||||||
|
PrepareRefundResult,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { mountHook } from "../../test-utils.js";
|
import { mountHook } from "../../test-utils.js";
|
||||||
@ -33,11 +34,19 @@ describe("Refund CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerRefundUri: undefined, cancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareRefund: async () => ({}),
|
{
|
||||||
applyRefund: async () => ({}),
|
talerRefundUri: undefined,
|
||||||
onUpdateNotification: async () => ({}),
|
cancel: async () => {
|
||||||
} as any),
|
null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prepareRefund: async () => ({}),
|
||||||
|
applyRefund: async () => ({}),
|
||||||
|
onUpdateNotification: async () => ({}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -64,27 +73,35 @@ describe("Refund CTA states", () => {
|
|||||||
it("should be ready after loading", async () => {
|
it("should be ready after loading", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerRefundUri: "taler://refund/asdasdas", cancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareRefund: async () =>
|
{
|
||||||
({
|
talerRefundUri: "taler://refund/asdasdas",
|
||||||
effectivePaid: "EUR:2",
|
cancel: async () => {
|
||||||
awaiting: "EUR:2",
|
null;
|
||||||
gone: "EUR:0",
|
|
||||||
granted: "EUR:0",
|
|
||||||
pending: false,
|
|
||||||
proposalId: "1",
|
|
||||||
info: {
|
|
||||||
contractTermsHash: "123",
|
|
||||||
merchant: {
|
|
||||||
name: "the merchant name",
|
|
||||||
},
|
|
||||||
orderId: "orderId1",
|
|
||||||
summary: "the summary",
|
|
||||||
},
|
},
|
||||||
} as PrepareRefundResult as any),
|
},
|
||||||
applyRefund: async () => ({}),
|
{
|
||||||
onUpdateNotification: async () => ({}),
|
prepareRefund: async () =>
|
||||||
} as any),
|
({
|
||||||
|
effectivePaid: "EUR:2",
|
||||||
|
awaiting: "EUR:2",
|
||||||
|
gone: "EUR:0",
|
||||||
|
granted: "EUR:0",
|
||||||
|
pending: false,
|
||||||
|
proposalId: "1",
|
||||||
|
info: {
|
||||||
|
contractTermsHash: "123",
|
||||||
|
merchant: {
|
||||||
|
name: "the merchant name",
|
||||||
|
},
|
||||||
|
orderId: "orderId1",
|
||||||
|
summary: "the summary",
|
||||||
|
},
|
||||||
|
} as PrepareRefundResult as any),
|
||||||
|
applyRefund: async () => ({}),
|
||||||
|
onUpdateNotification: async () => ({}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -113,27 +130,35 @@ describe("Refund CTA states", () => {
|
|||||||
it("should be ignored after clicking the ignore button", async () => {
|
it("should be ignored after clicking the ignore button", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerRefundUri: "taler://refund/asdasdas", cancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareRefund: async () =>
|
{
|
||||||
({
|
talerRefundUri: "taler://refund/asdasdas",
|
||||||
effectivePaid: "EUR:2",
|
cancel: async () => {
|
||||||
awaiting: "EUR:2",
|
null;
|
||||||
gone: "EUR:0",
|
|
||||||
granted: "EUR:0",
|
|
||||||
pending: false,
|
|
||||||
proposalId: "1",
|
|
||||||
info: {
|
|
||||||
contractTermsHash: "123",
|
|
||||||
merchant: {
|
|
||||||
name: "the merchant name",
|
|
||||||
},
|
|
||||||
orderId: "orderId1",
|
|
||||||
summary: "the summary",
|
|
||||||
},
|
},
|
||||||
} as PrepareRefundResult as any),
|
},
|
||||||
applyRefund: async () => ({}),
|
{
|
||||||
onUpdateNotification: async () => ({}),
|
prepareRefund: async () =>
|
||||||
} as any),
|
({
|
||||||
|
effectivePaid: "EUR:2",
|
||||||
|
awaiting: "EUR:2",
|
||||||
|
gone: "EUR:0",
|
||||||
|
granted: "EUR:0",
|
||||||
|
pending: false,
|
||||||
|
proposalId: "1",
|
||||||
|
info: {
|
||||||
|
contractTermsHash: "123",
|
||||||
|
merchant: {
|
||||||
|
name: "the merchant name",
|
||||||
|
},
|
||||||
|
orderId: "orderId1",
|
||||||
|
summary: "the summary",
|
||||||
|
},
|
||||||
|
} as PrepareRefundResult as any),
|
||||||
|
applyRefund: async () => ({}),
|
||||||
|
onUpdateNotification: async () => ({}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -189,27 +214,35 @@ describe("Refund CTA states", () => {
|
|||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerRefundUri: "taler://refund/asdasdas", cancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareRefund: async () =>
|
{
|
||||||
({
|
talerRefundUri: "taler://refund/asdasdas",
|
||||||
awaiting: Amounts.stringify(awaiting),
|
cancel: async () => {
|
||||||
effectivePaid: "EUR:2",
|
null;
|
||||||
gone: "EUR:0",
|
|
||||||
granted: Amounts.stringify(granted),
|
|
||||||
pending,
|
|
||||||
proposalId: "1",
|
|
||||||
info: {
|
|
||||||
contractTermsHash: "123",
|
|
||||||
merchant: {
|
|
||||||
name: "the merchant name",
|
|
||||||
},
|
|
||||||
orderId: "orderId1",
|
|
||||||
summary: "the summary",
|
|
||||||
},
|
},
|
||||||
} as PrepareRefundResult as any),
|
},
|
||||||
applyRefund: async () => ({}),
|
{
|
||||||
onUpdateNotification: subscriptions.saveSubscription,
|
prepareRefund: async () =>
|
||||||
} as any),
|
({
|
||||||
|
awaiting: Amounts.stringify(awaiting),
|
||||||
|
effectivePaid: "EUR:2",
|
||||||
|
gone: "EUR:0",
|
||||||
|
granted: Amounts.stringify(granted),
|
||||||
|
pending,
|
||||||
|
proposalId: "1",
|
||||||
|
info: {
|
||||||
|
contractTermsHash: "123",
|
||||||
|
merchant: {
|
||||||
|
name: "the merchant name",
|
||||||
|
},
|
||||||
|
orderId: "orderId1",
|
||||||
|
summary: "the summary",
|
||||||
|
},
|
||||||
|
} as PrepareRefundResult as any),
|
||||||
|
applyRefund: async () => ({}),
|
||||||
|
onUpdateNotification: subscriptions.saveSubscription,
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -20,13 +20,14 @@ import { HookError } from "../../hooks/useAsyncAsHook.js";
|
|||||||
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import {
|
import { Props as TermsOfServiceSectionProps } from "../TermsOfServiceSection.js";
|
||||||
Props as TermsOfServiceSectionProps
|
|
||||||
} from "../TermsOfServiceSection.js";
|
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { AcceptedView, IgnoredView, LoadingUriView, ReadyView } from "./views.js";
|
import {
|
||||||
|
AcceptedView,
|
||||||
|
IgnoredView,
|
||||||
|
LoadingUriView,
|
||||||
|
ReadyView,
|
||||||
|
} from "./views.js";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerTipUri?: string;
|
talerTipUri?: string;
|
||||||
@ -42,7 +43,6 @@ export type State =
|
|||||||
| State.Ignored;
|
| State.Ignored;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -77,9 +77,13 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
"accepted": AcceptedView,
|
accepted: AcceptedView,
|
||||||
"ignored": IgnoredView,
|
ignored: IgnoredView,
|
||||||
"ready": ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TipPage = compose("Tip", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
export const TipPage = compose(
|
||||||
|
"Tip",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { Amounts } from "@gnu-taler/taler-util";
|
import { Amounts } from "@gnu-taler/taler-util";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
@ -37,7 +36,7 @@ export function useComponentState(
|
|||||||
return {
|
return {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if (tipInfo.hasError) {
|
if (tipInfo.hasError) {
|
||||||
return {
|
return {
|
||||||
@ -59,9 +58,9 @@ export function useComponentState(
|
|||||||
amount: Amounts.parseOrThrow(tip.tipAmountEffective),
|
amount: Amounts.parseOrThrow(tip.tipAmountEffective),
|
||||||
error: undefined,
|
error: undefined,
|
||||||
cancel: {
|
cancel: {
|
||||||
onClick: onCancel
|
onClick: onCancel,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
if (tipIgnored) {
|
if (tipIgnored) {
|
||||||
return {
|
return {
|
||||||
@ -85,4 +84,3 @@ export function useComponentState(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,7 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { Amounts, PrepareTipResult } from "@gnu-taler/taler-util";
|
||||||
Amounts, PrepareTipResult
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { mountHook } from "../../test-utils.js";
|
import { mountHook } from "../../test-utils.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
@ -30,10 +28,18 @@ describe("Tip CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: undefined, onCancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareTip: async () => ({}),
|
{
|
||||||
acceptTip: async () => ({}),
|
talerTipUri: undefined,
|
||||||
} as any),
|
onCancel: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prepareTip: async () => ({}),
|
||||||
|
acceptTip: async () => ({}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -62,19 +68,27 @@ describe("Tip CTA states", () => {
|
|||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: "taler://tip/asd", onCancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareTip: async () =>
|
{
|
||||||
({
|
talerTipUri: "taler://tip/asd",
|
||||||
accepted: tipAccepted,
|
onCancel: async () => {
|
||||||
exchangeBaseUrl: "exchange url",
|
null;
|
||||||
merchantBaseUrl: "merchant url",
|
},
|
||||||
tipAmountEffective: "EUR:1",
|
|
||||||
walletTipId: "tip_id",
|
|
||||||
} as PrepareTipResult as any),
|
|
||||||
acceptTip: async () => {
|
|
||||||
tipAccepted = true;
|
|
||||||
},
|
},
|
||||||
} as any),
|
{
|
||||||
|
prepareTip: async () =>
|
||||||
|
({
|
||||||
|
accepted: tipAccepted,
|
||||||
|
exchangeBaseUrl: "exchange url",
|
||||||
|
merchantBaseUrl: "merchant url",
|
||||||
|
tipAmountEffective: "EUR:1",
|
||||||
|
walletTipId: "tip_id",
|
||||||
|
} as PrepareTipResult as any),
|
||||||
|
acceptTip: async () => {
|
||||||
|
tipAccepted = true;
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -114,16 +128,24 @@ describe("Tip CTA states", () => {
|
|||||||
it("should be ignored after clicking the ignore button", async () => {
|
it("should be ignored after clicking the ignore button", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: "taler://tip/asd", onCancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareTip: async () =>
|
{
|
||||||
({
|
talerTipUri: "taler://tip/asd",
|
||||||
exchangeBaseUrl: "exchange url",
|
onCancel: async () => {
|
||||||
merchantBaseUrl: "merchant url",
|
null;
|
||||||
tipAmountEffective: "EUR:1",
|
},
|
||||||
walletTipId: "tip_id",
|
},
|
||||||
} as PrepareTipResult as any),
|
{
|
||||||
acceptTip: async () => ({}),
|
prepareTip: async () =>
|
||||||
} as any),
|
({
|
||||||
|
exchangeBaseUrl: "exchange url",
|
||||||
|
merchantBaseUrl: "merchant url",
|
||||||
|
tipAmountEffective: "EUR:1",
|
||||||
|
walletTipId: "tip_id",
|
||||||
|
} as PrepareTipResult as any),
|
||||||
|
acceptTip: async () => ({}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -160,17 +182,25 @@ describe("Tip CTA states", () => {
|
|||||||
it("should render accepted if the tip has been used previously", async () => {
|
it("should render accepted if the tip has been used previously", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: "taler://tip/asd", onCancel: async () => { null } }, {
|
useComponentState(
|
||||||
prepareTip: async () =>
|
{
|
||||||
({
|
talerTipUri: "taler://tip/asd",
|
||||||
accepted: true,
|
onCancel: async () => {
|
||||||
exchangeBaseUrl: "exchange url",
|
null;
|
||||||
merchantBaseUrl: "merchant url",
|
},
|
||||||
tipAmountEffective: "EUR:1",
|
},
|
||||||
walletTipId: "tip_id",
|
{
|
||||||
} as PrepareTipResult as any),
|
prepareTip: async () =>
|
||||||
acceptTip: async () => ({}),
|
({
|
||||||
} as any),
|
accepted: true,
|
||||||
|
exchangeBaseUrl: "exchange url",
|
||||||
|
merchantBaseUrl: "merchant url",
|
||||||
|
tipAmountEffective: "EUR:1",
|
||||||
|
walletTipId: "tip_id",
|
||||||
|
} as PrepareTipResult as any),
|
||||||
|
acceptTip: async () => ({}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,6 @@ export type State =
|
|||||||
| State.Ready;
|
| State.Ready;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -59,9 +58,9 @@ export namespace State {
|
|||||||
status: "ready";
|
status: "ready";
|
||||||
invalid: boolean;
|
invalid: boolean;
|
||||||
create: ButtonHandler;
|
create: ButtonHandler;
|
||||||
toBeReceived: AmountJson,
|
toBeReceived: AmountJson;
|
||||||
chosenAmount: AmountJson,
|
chosenAmount: AmountJson;
|
||||||
subject: TextFieldHandler,
|
subject: TextFieldHandler;
|
||||||
error: undefined;
|
error: undefined;
|
||||||
operationError?: TalerErrorDetail;
|
operationError?: TalerErrorDetail;
|
||||||
}
|
}
|
||||||
@ -70,10 +69,12 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
"created": CreatedView,
|
created: CreatedView,
|
||||||
"ready": ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TransferCreatePage = compose(
|
||||||
export const TransferCreatePage = compose("TransferCreatePage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
"TransferCreatePage",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -24,11 +24,13 @@ export function useComponentState(
|
|||||||
{ amount: amountStr, onClose }: Props,
|
{ amount: amountStr, onClose }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const amount = Amounts.parseOrThrow(amountStr)
|
const amount = Amounts.parseOrThrow(amountStr);
|
||||||
|
|
||||||
const [subject, setSubject] = useState("");
|
const [subject, setSubject] = useState("");
|
||||||
const [talerUri, setTalerUri] = useState("")
|
const [talerUri, setTalerUri] = useState("");
|
||||||
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
const [operationError, setOperationError] = useState<
|
||||||
|
TalerErrorDetail | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
if (talerUri) {
|
if (talerUri) {
|
||||||
return {
|
return {
|
||||||
@ -41,28 +43,26 @@ export function useComponentState(
|
|||||||
copyToClipboard: {
|
copyToClipboard: {
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
navigator.clipboard.writeText(talerUri);
|
navigator.clipboard.writeText(talerUri);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function accept(): Promise<string> {
|
async function accept(): Promise<string> {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const resp = await api.initiatePeerPushPayment({
|
const resp = await api.initiatePeerPushPayment({
|
||||||
amount: Amounts.stringify(amount),
|
amount: Amounts.stringify(amount),
|
||||||
partialContractTerms: {
|
partialContractTerms: {
|
||||||
summary: subject
|
summary: subject,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
return resp.talerUri
|
return resp.talerUri;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail)
|
setOperationError(e.errorDetail);
|
||||||
}
|
}
|
||||||
console.error(e)
|
console.error(e);
|
||||||
throw Error("error trying to accept")
|
throw Error("error trying to accept");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -74,17 +74,17 @@ export function useComponentState(
|
|||||||
subject: {
|
subject: {
|
||||||
error: !subject ? "cant be empty" : undefined,
|
error: !subject ? "cant be empty" : undefined,
|
||||||
value: subject,
|
value: subject,
|
||||||
onInput: async (e) => setSubject(e)
|
onInput: async (e) => setSubject(e),
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const uri = await accept();
|
const uri = await accept();
|
||||||
setTalerUri(uri)
|
setTalerUri(uri);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
chosenAmount: amount,
|
chosenAmount: amount,
|
||||||
toBeReceived: amount,
|
toBeReceived: amount,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
operationError
|
operationError,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,7 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
describe("test description", () => {
|
describe("test description", () => {
|
||||||
|
|
||||||
it("should assert", () => {
|
it("should assert", () => {
|
||||||
|
expect([]).deep.equals([]);
|
||||||
expect([]).deep.equals([])
|
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
@ -14,7 +14,11 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AbsoluteTime, AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
AbsoluteTime,
|
||||||
|
AmountJson,
|
||||||
|
TalerErrorDetail,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
import { ButtonHandler } from "../../mui/handlers.js";
|
import { ButtonHandler } from "../../mui/handlers.js";
|
||||||
@ -28,13 +32,9 @@ export interface Props {
|
|||||||
onClose: () => Promise<void>;
|
onClose: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State = State.Loading | State.LoadingUriError | State.Ready;
|
||||||
| State.Loading
|
|
||||||
| State.LoadingUriError
|
|
||||||
| State.Ready;
|
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -51,7 +51,7 @@ export namespace State {
|
|||||||
}
|
}
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
amount: AmountJson,
|
amount: AmountJson;
|
||||||
summary: string | undefined;
|
summary: string | undefined;
|
||||||
expiration: AbsoluteTime | undefined;
|
expiration: AbsoluteTime | undefined;
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -63,9 +63,11 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
"ready": ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TransferPickupPage = compose(
|
||||||
export const TransferPickupPage = compose("TransferPickupPage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
"TransferPickupPage",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -14,7 +14,12 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AbsoluteTime, Amounts, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
AbsoluteTime,
|
||||||
|
Amounts,
|
||||||
|
TalerErrorDetail,
|
||||||
|
TalerProtocolTimestamp,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
@ -28,15 +33,17 @@ export function useComponentState(
|
|||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
return await api.checkPeerPushPayment({
|
return await api.checkPeerPushPayment({
|
||||||
talerUri: talerPayPushUri,
|
talerUri: talerPayPushUri,
|
||||||
})
|
});
|
||||||
}, [])
|
}, []);
|
||||||
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
const [operationError, setOperationError] = useState<
|
||||||
|
TalerErrorDetail | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
if (!hook) {
|
if (!hook) {
|
||||||
return {
|
return {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if (hook.hasError) {
|
if (hook.hasError) {
|
||||||
return {
|
return {
|
||||||
@ -45,24 +52,29 @@ export function useComponentState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { amount: purseAmount, contractTerms, peerPushPaymentIncomingId } = hook.response
|
const {
|
||||||
|
amount: purseAmount,
|
||||||
|
contractTerms,
|
||||||
|
peerPushPaymentIncomingId,
|
||||||
|
} = hook.response;
|
||||||
|
|
||||||
const amount: string = contractTerms?.amount
|
const amount: string = contractTerms?.amount;
|
||||||
const summary: string | undefined = contractTerms?.summary
|
const summary: string | undefined = contractTerms?.summary;
|
||||||
const expiration: TalerProtocolTimestamp | undefined = contractTerms?.purse_expiration
|
const expiration: TalerProtocolTimestamp | undefined =
|
||||||
|
contractTerms?.purse_expiration;
|
||||||
|
|
||||||
async function accept(): Promise<void> {
|
async function accept(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const resp = await api.acceptPeerPushPayment({
|
const resp = await api.acceptPeerPushPayment({
|
||||||
peerPushPaymentIncomingId
|
peerPushPaymentIncomingId,
|
||||||
})
|
});
|
||||||
await onClose()
|
await onClose();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail)
|
setOperationError(e.errorDetail);
|
||||||
}
|
}
|
||||||
console.error(e)
|
console.error(e);
|
||||||
throw Error("error trying to accept")
|
throw Error("error trying to accept");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -70,13 +82,13 @@ export function useComponentState(
|
|||||||
amount: Amounts.parseOrThrow(amount),
|
amount: Amounts.parseOrThrow(amount),
|
||||||
error: undefined,
|
error: undefined,
|
||||||
accept: {
|
accept: {
|
||||||
onClick: accept
|
onClick: accept,
|
||||||
},
|
},
|
||||||
summary,
|
summary,
|
||||||
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
|
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
|
||||||
cancel: {
|
cancel: {
|
||||||
onClick: onClose
|
onClick: onClose,
|
||||||
},
|
},
|
||||||
operationError
|
operationError,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,7 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
describe("test description", () => {
|
describe("test description", () => {
|
||||||
|
|
||||||
it("should assert", () => {
|
it("should assert", () => {
|
||||||
|
expect([]).deep.equals([]);
|
||||||
expect([]).deep.equals([])
|
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
@ -20,12 +20,18 @@ import { HookError } from "../../hooks/useAsyncAsHook.js";
|
|||||||
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
|
import { Props as TermsOfServiceSectionProps } from "../TermsOfServiceSection.js";
|
||||||
import {
|
import {
|
||||||
Props as TermsOfServiceSectionProps
|
useComponentStateFromParams,
|
||||||
} from "../TermsOfServiceSection.js";
|
useComponentStateFromURI,
|
||||||
import { useComponentStateFromParams, useComponentStateFromURI } from "./state.js";
|
} from "./state.js";
|
||||||
import { CompletedView, LoadingExchangeView, LoadingInfoView, LoadingUriView, SuccessView } from "./views.js";
|
import {
|
||||||
|
CompletedView,
|
||||||
|
LoadingExchangeView,
|
||||||
|
LoadingInfoView,
|
||||||
|
LoadingUriView,
|
||||||
|
SuccessView,
|
||||||
|
} from "./views.js";
|
||||||
|
|
||||||
export interface PropsFromURI {
|
export interface PropsFromURI {
|
||||||
talerWithdrawUri: string | undefined;
|
talerWithdrawUri: string | undefined;
|
||||||
@ -46,7 +52,6 @@ export type State =
|
|||||||
| State.Completed;
|
| State.Completed;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -99,5 +104,13 @@ const viewMapping: StateViewMap<State> = {
|
|||||||
success: SuccessView,
|
success: SuccessView,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithdrawPageFromURI = compose("WithdrawPageFromURI", (p: PropsFromURI) => useComponentStateFromURI(p, wxApi), viewMapping)
|
export const WithdrawPageFromURI = compose(
|
||||||
export const WithdrawPageFromParams = compose("WithdrawPageFromParams", (p: PropsFromParams) => useComponentStateFromParams(p, wxApi), viewMapping)
|
"WithdrawPageFromURI",
|
||||||
|
(p: PropsFromURI) => useComponentStateFromURI(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
export const WithdrawPageFromParams = compose(
|
||||||
|
"WithdrawPageFromParams",
|
||||||
|
(p: PropsFromParams) => useComponentStateFromParams(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { Amounts, parsePaytoUri } from "@gnu-taler/taler-util";
|
import { Amounts, parsePaytoUri } from "@gnu-taler/taler-util";
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
@ -27,7 +26,6 @@ export function useComponentStateFromParams(
|
|||||||
{ amount, cancel }: PropsFromParams,
|
{ amount, cancel }: PropsFromParams,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
|
|
||||||
const [ageRestricted, setAgeRestricted] = useState(0);
|
const [ageRestricted, setAgeRestricted] = useState(0);
|
||||||
|
|
||||||
const exchangeHook = useAsyncAsHook(api.listExchanges);
|
const exchangeHook = useAsyncAsHook(api.listExchanges);
|
||||||
@ -40,14 +38,20 @@ export function useComponentStateFromParams(
|
|||||||
const chosenAmount = Amounts.parseOrThrow(amount);
|
const chosenAmount = Amounts.parseOrThrow(amount);
|
||||||
|
|
||||||
// get the first exchange with the currency as the default one
|
// get the first exchange with the currency as the default one
|
||||||
const exchange = exchangeHookDep ? exchangeHookDep.exchanges.find(e => e.currency === chosenAmount.currency) : undefined
|
const exchange = exchangeHookDep
|
||||||
|
? exchangeHookDep.exchanges.find(
|
||||||
|
(e) => e.currency === chosenAmount.currency,
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
/**
|
/**
|
||||||
* For the exchange selected, bring the status of the terms of service
|
* For the exchange selected, bring the status of the terms of service
|
||||||
*/
|
*/
|
||||||
const terms = useAsyncAsHook(async () => {
|
const terms = useAsyncAsHook(async () => {
|
||||||
if (!exchange) return undefined
|
if (!exchange) return undefined;
|
||||||
|
|
||||||
const exchangeTos = await api.getExchangeTos(exchange.exchangeBaseUrl, ["text/xml"]);
|
const exchangeTos = await api.getExchangeTos(exchange.exchangeBaseUrl, [
|
||||||
|
"text/xml",
|
||||||
|
]);
|
||||||
|
|
||||||
const state = buildTermsOfServiceState(exchangeTos);
|
const state = buildTermsOfServiceState(exchangeTos);
|
||||||
|
|
||||||
@ -59,7 +63,7 @@ export function useComponentStateFromParams(
|
|||||||
* about the withdrawal
|
* about the withdrawal
|
||||||
*/
|
*/
|
||||||
const amountHook = useAsyncAsHook(async () => {
|
const amountHook = useAsyncAsHook(async () => {
|
||||||
if (!exchange) return undefined
|
if (!exchange) return undefined;
|
||||||
|
|
||||||
const info = await api.getExchangeWithdrawalInfo({
|
const info = await api.getExchangeWithdrawalInfo({
|
||||||
exchangeBaseUrl: exchange.exchangeBaseUrl,
|
exchangeBaseUrl: exchange.exchangeBaseUrl,
|
||||||
@ -71,9 +75,12 @@ export function useComponentStateFromParams(
|
|||||||
const withdrawAmount = {
|
const withdrawAmount = {
|
||||||
raw: Amounts.parseOrThrow(info.withdrawalAmountRaw),
|
raw: Amounts.parseOrThrow(info.withdrawalAmountRaw),
|
||||||
effective: Amounts.parseOrThrow(info.withdrawalAmountEffective),
|
effective: Amounts.parseOrThrow(info.withdrawalAmountEffective),
|
||||||
}
|
};
|
||||||
|
|
||||||
return { amount: withdrawAmount, ageRestrictionOptions: info.ageRestrictionOptions };
|
return {
|
||||||
|
amount: withdrawAmount,
|
||||||
|
ageRestrictionOptions: info.ageRestrictionOptions,
|
||||||
|
};
|
||||||
}, [exchangeHookDep]);
|
}, [exchangeHookDep]);
|
||||||
|
|
||||||
const [reviewing, setReviewing] = useState<boolean>(false);
|
const [reviewing, setReviewing] = useState<boolean>(false);
|
||||||
@ -85,7 +92,7 @@ export function useComponentStateFromParams(
|
|||||||
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
||||||
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
|
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
|
||||||
|
|
||||||
if (!exchangeHook) return { status: "loading", error: undefined }
|
if (!exchangeHook) return { status: "loading", error: undefined };
|
||||||
if (exchangeHook.hasError) {
|
if (exchangeHook.hasError) {
|
||||||
return {
|
return {
|
||||||
status: "loading-uri",
|
status: "loading-uri",
|
||||||
@ -125,7 +132,7 @@ export function useComponentStateFromParams(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!amountHook) {
|
if (!amountHook) {
|
||||||
return { status: "loading", error: undefined }
|
return { status: "loading", error: undefined };
|
||||||
}
|
}
|
||||||
if (amountHook.hasError) {
|
if (amountHook.hasError) {
|
||||||
return {
|
return {
|
||||||
@ -149,8 +156,8 @@ export function useComponentStateFromParams(
|
|||||||
const { state: termsState } = (!terms
|
const { state: termsState } = (!terms
|
||||||
? undefined
|
? undefined
|
||||||
: terms.hasError
|
: terms.hasError
|
||||||
? undefined
|
? undefined
|
||||||
: terms.response) || { state: undefined };
|
: terms.response) || { state: undefined };
|
||||||
|
|
||||||
async function onAccept(accepted: boolean): Promise<void> {
|
async function onAccept(accepted: boolean): Promise<void> {
|
||||||
if (!termsState || !exchange) return;
|
if (!termsState || !exchange) return;
|
||||||
@ -173,21 +180,25 @@ export function useComponentStateFromParams(
|
|||||||
termsState !== undefined &&
|
termsState !== undefined &&
|
||||||
(termsState.status === "changed" || termsState.status === "new");
|
(termsState.status === "changed" || termsState.status === "new");
|
||||||
|
|
||||||
const ageRestrictionOptions = amountHook.response.
|
const ageRestrictionOptions =
|
||||||
ageRestrictionOptions?.
|
amountHook.response.ageRestrictionOptions?.reduce(
|
||||||
reduce((p, c) => ({ ...p, [c]: `under ${c}` }), {} as Record<string, string>)
|
(p, c) => ({ ...p, [c]: `under ${c}` }),
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
|
||||||
const ageRestrictionEnabled = ageRestrictionOptions !== undefined
|
const ageRestrictionEnabled = ageRestrictionOptions !== undefined;
|
||||||
if (ageRestrictionEnabled) {
|
if (ageRestrictionEnabled) {
|
||||||
ageRestrictionOptions["0"] = "Not restricted";
|
ageRestrictionOptions["0"] = "Not restricted";
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: calculate based on exchange info
|
//TODO: calculate based on exchange info
|
||||||
const ageRestriction = ageRestrictionEnabled ? {
|
const ageRestriction = ageRestrictionEnabled
|
||||||
list: ageRestrictionOptions,
|
? {
|
||||||
value: String(ageRestricted),
|
list: ageRestrictionOptions,
|
||||||
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
|
value: String(ageRestricted),
|
||||||
} : undefined;
|
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
@ -207,12 +218,12 @@ export function useComponentStateFromParams(
|
|||||||
tosProps: !termsState
|
tosProps: !termsState
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
onAccept,
|
onAccept,
|
||||||
onReview: setReviewing,
|
onReview: setReviewing,
|
||||||
reviewed: reviewed,
|
reviewed: reviewed,
|
||||||
reviewing: reviewing,
|
reviewing: reviewing,
|
||||||
terms: termsState,
|
terms: termsState,
|
||||||
},
|
},
|
||||||
mustAcceptFirst,
|
mustAcceptFirst,
|
||||||
cancel,
|
cancel,
|
||||||
};
|
};
|
||||||
@ -233,7 +244,7 @@ export function useComponentStateFromURI(
|
|||||||
const uriInfo = await api.getWithdrawalDetailsForUri({
|
const uriInfo = await api.getWithdrawalDetailsForUri({
|
||||||
talerWithdrawUri,
|
talerWithdrawUri,
|
||||||
});
|
});
|
||||||
const { amount, defaultExchangeBaseUrl } = uriInfo
|
const { amount, defaultExchangeBaseUrl } = uriInfo;
|
||||||
return { amount, thisExchange: defaultExchangeBaseUrl };
|
return { amount, thisExchange: defaultExchangeBaseUrl };
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -245,14 +256,15 @@ export function useComponentStateFromURI(
|
|||||||
? undefined
|
? undefined
|
||||||
: uriInfoHook.response;
|
: uriInfoHook.response;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For the exchange selected, bring the status of the terms of service
|
* For the exchange selected, bring the status of the terms of service
|
||||||
*/
|
*/
|
||||||
const terms = useAsyncAsHook(async () => {
|
const terms = useAsyncAsHook(async () => {
|
||||||
if (!uriHookDep?.thisExchange) return false;
|
if (!uriHookDep?.thisExchange) return false;
|
||||||
|
|
||||||
const exchangeTos = await api.getExchangeTos(uriHookDep.thisExchange, ["text/xml"]);
|
const exchangeTos = await api.getExchangeTos(uriHookDep.thisExchange, [
|
||||||
|
"text/xml",
|
||||||
|
]);
|
||||||
|
|
||||||
const state = buildTermsOfServiceState(exchangeTos);
|
const state = buildTermsOfServiceState(exchangeTos);
|
||||||
|
|
||||||
@ -276,9 +288,12 @@ export function useComponentStateFromURI(
|
|||||||
const withdrawAmount = {
|
const withdrawAmount = {
|
||||||
raw: Amounts.parseOrThrow(info.withdrawalAmountRaw),
|
raw: Amounts.parseOrThrow(info.withdrawalAmountRaw),
|
||||||
effective: Amounts.parseOrThrow(info.withdrawalAmountEffective),
|
effective: Amounts.parseOrThrow(info.withdrawalAmountEffective),
|
||||||
}
|
};
|
||||||
|
|
||||||
return { amount: withdrawAmount, ageRestrictionOptions: info.ageRestrictionOptions };
|
return {
|
||||||
|
amount: withdrawAmount,
|
||||||
|
ageRestrictionOptions: info.ageRestrictionOptions,
|
||||||
|
};
|
||||||
}, [uriHookDep]);
|
}, [uriHookDep]);
|
||||||
|
|
||||||
const [reviewing, setReviewing] = useState<boolean>(false);
|
const [reviewing, setReviewing] = useState<boolean>(false);
|
||||||
@ -290,7 +305,7 @@ export function useComponentStateFromURI(
|
|||||||
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false);
|
||||||
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
|
const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false);
|
||||||
|
|
||||||
if (!uriInfoHook) return { status: "loading", error: undefined }
|
if (!uriInfoHook) return { status: "loading", error: undefined };
|
||||||
if (uriInfoHook.hasError) {
|
if (uriInfoHook.hasError) {
|
||||||
return {
|
return {
|
||||||
status: "loading-uri",
|
status: "loading-uri",
|
||||||
@ -298,7 +313,7 @@ export function useComponentStateFromURI(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { amount, thisExchange } = uriInfoHook.response
|
const { amount, thisExchange } = uriInfoHook.response;
|
||||||
|
|
||||||
const chosenAmount = Amounts.parseOrThrow(amount);
|
const chosenAmount = Amounts.parseOrThrow(amount);
|
||||||
|
|
||||||
@ -339,7 +354,7 @@ export function useComponentStateFromURI(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!amountHook) {
|
if (!amountHook) {
|
||||||
return { status: "loading", error: undefined }
|
return { status: "loading", error: undefined };
|
||||||
}
|
}
|
||||||
if (amountHook.hasError) {
|
if (amountHook.hasError) {
|
||||||
return {
|
return {
|
||||||
@ -363,8 +378,8 @@ export function useComponentStateFromURI(
|
|||||||
const { state: termsState } = (!terms
|
const { state: termsState } = (!terms
|
||||||
? undefined
|
? undefined
|
||||||
: terms.hasError
|
: terms.hasError
|
||||||
? undefined
|
? undefined
|
||||||
: terms.response) || { state: undefined };
|
: terms.response) || { state: undefined };
|
||||||
|
|
||||||
async function onAccept(accepted: boolean): Promise<void> {
|
async function onAccept(accepted: boolean): Promise<void> {
|
||||||
if (!termsState || !thisExchange) return;
|
if (!termsState || !thisExchange) return;
|
||||||
@ -387,21 +402,25 @@ export function useComponentStateFromURI(
|
|||||||
termsState !== undefined &&
|
termsState !== undefined &&
|
||||||
(termsState.status === "changed" || termsState.status === "new");
|
(termsState.status === "changed" || termsState.status === "new");
|
||||||
|
|
||||||
const ageRestrictionOptions = amountHook.response.
|
const ageRestrictionOptions =
|
||||||
ageRestrictionOptions?.
|
amountHook.response.ageRestrictionOptions?.reduce(
|
||||||
reduce((p, c) => ({ ...p, [c]: `under ${c}` }), {} as Record<string, string>)
|
(p, c) => ({ ...p, [c]: `under ${c}` }),
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
|
||||||
const ageRestrictionEnabled = ageRestrictionOptions !== undefined
|
const ageRestrictionEnabled = ageRestrictionOptions !== undefined;
|
||||||
if (ageRestrictionEnabled) {
|
if (ageRestrictionEnabled) {
|
||||||
ageRestrictionOptions["0"] = "Not restricted";
|
ageRestrictionOptions["0"] = "Not restricted";
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: calculate based on exchange info
|
//TODO: calculate based on exchange info
|
||||||
const ageRestriction = ageRestrictionEnabled ? {
|
const ageRestriction = ageRestrictionEnabled
|
||||||
list: ageRestrictionOptions,
|
? {
|
||||||
value: String(ageRestricted),
|
list: ageRestrictionOptions,
|
||||||
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
|
value: String(ageRestricted),
|
||||||
} : undefined;
|
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
@ -422,14 +441,13 @@ export function useComponentStateFromURI(
|
|||||||
tosProps: !termsState
|
tosProps: !termsState
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
onAccept,
|
onAccept,
|
||||||
onReview: setReviewing,
|
onReview: setReviewing,
|
||||||
reviewed: reviewed,
|
reviewed: reviewed,
|
||||||
reviewing: reviewing,
|
reviewing: reviewing,
|
||||||
terms: termsState,
|
terms: termsState,
|
||||||
},
|
},
|
||||||
mustAcceptFirst,
|
mustAcceptFirst,
|
||||||
cancel,
|
cancel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,13 +62,21 @@ describe("Withdraw CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentStateFromURI({ talerWithdrawUri: undefined, cancel: async () => { null } }, {
|
useComponentStateFromURI(
|
||||||
listExchanges: async () => ({ exchanges }),
|
{
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
talerWithdrawUri: undefined,
|
||||||
amount: "ARS:2",
|
cancel: async () => {
|
||||||
possibleExchanges: exchanges,
|
null;
|
||||||
}),
|
},
|
||||||
} as any),
|
},
|
||||||
|
{
|
||||||
|
listExchanges: async () => ({ exchanges }),
|
||||||
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
|
amount: "ARS:2",
|
||||||
|
possibleExchanges: exchanges,
|
||||||
|
}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -94,13 +102,21 @@ describe("Withdraw CTA states", () => {
|
|||||||
it("should tell the user that there is not known exchange", async () => {
|
it("should tell the user that there is not known exchange", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentStateFromURI({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, {
|
useComponentStateFromURI(
|
||||||
listExchanges: async () => ({ exchanges }),
|
{
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
talerWithdrawUri: "taler-withdraw://",
|
||||||
amount: "EUR:2",
|
cancel: async () => {
|
||||||
possibleExchanges: [],
|
null;
|
||||||
}),
|
},
|
||||||
} as any),
|
},
|
||||||
|
{
|
||||||
|
listExchanges: async () => ({ exchanges }),
|
||||||
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
|
amount: "EUR:2",
|
||||||
|
possibleExchanges: [],
|
||||||
|
}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -128,26 +144,34 @@ describe("Withdraw CTA states", () => {
|
|||||||
it("should be able to withdraw if tos are ok", async () => {
|
it("should be able to withdraw if tos are ok", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentStateFromURI({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, {
|
useComponentStateFromURI(
|
||||||
listExchanges: async () => ({ exchanges }),
|
{
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
talerWithdrawUri: "taler-withdraw://",
|
||||||
amount: "ARS:2",
|
cancel: async () => {
|
||||||
possibleExchanges: exchanges,
|
null;
|
||||||
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl
|
},
|
||||||
}),
|
},
|
||||||
getExchangeWithdrawalInfo:
|
{
|
||||||
async (): Promise<ExchangeWithdrawDetails> =>
|
listExchanges: async () => ({ exchanges }),
|
||||||
({
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
withdrawalAmountRaw: "ARS:2",
|
amount: "ARS:2",
|
||||||
withdrawalAmountEffective: "ARS:2",
|
possibleExchanges: exchanges,
|
||||||
} as any),
|
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl,
|
||||||
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
}),
|
||||||
contentType: "text",
|
getExchangeWithdrawalInfo:
|
||||||
content: "just accept",
|
async (): Promise<ExchangeWithdrawDetails> =>
|
||||||
acceptedEtag: "v1",
|
({
|
||||||
currentEtag: "v1",
|
withdrawalAmountRaw: "ARS:2",
|
||||||
}),
|
withdrawalAmountEffective: "ARS:2",
|
||||||
} as any),
|
} as any),
|
||||||
|
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
||||||
|
contentType: "text",
|
||||||
|
content: "just accept",
|
||||||
|
acceptedEtag: "v1",
|
||||||
|
currentEtag: "v1",
|
||||||
|
}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -194,27 +218,35 @@ describe("Withdraw CTA states", () => {
|
|||||||
it("should be accept the tos before withdraw", async () => {
|
it("should be accept the tos before withdraw", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentStateFromURI({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, {
|
useComponentStateFromURI(
|
||||||
listExchanges: async () => ({ exchanges }),
|
{
|
||||||
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
talerWithdrawUri: "taler-withdraw://",
|
||||||
amount: "ARS:2",
|
cancel: async () => {
|
||||||
possibleExchanges: exchanges,
|
null;
|
||||||
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl
|
},
|
||||||
}),
|
},
|
||||||
getExchangeWithdrawalInfo:
|
{
|
||||||
async (): Promise<ExchangeWithdrawDetails> =>
|
listExchanges: async () => ({ exchanges }),
|
||||||
({
|
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
|
||||||
withdrawalAmountRaw: "ARS:2",
|
amount: "ARS:2",
|
||||||
withdrawalAmountEffective: "ARS:2",
|
possibleExchanges: exchanges,
|
||||||
} as any),
|
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl,
|
||||||
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
}),
|
||||||
contentType: "text",
|
getExchangeWithdrawalInfo:
|
||||||
content: "just accept",
|
async (): Promise<ExchangeWithdrawDetails> =>
|
||||||
acceptedEtag: "v1",
|
({
|
||||||
currentEtag: "v2",
|
withdrawalAmountRaw: "ARS:2",
|
||||||
}),
|
withdrawalAmountEffective: "ARS:2",
|
||||||
setExchangeTosAccepted: async () => ({}),
|
} as any),
|
||||||
} as any),
|
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
|
||||||
|
contentType: "text",
|
||||||
|
content: "just accept",
|
||||||
|
acceptedEtag: "v1",
|
||||||
|
currentEtag: "v2",
|
||||||
|
}),
|
||||||
|
setExchangeTosAccepted: async () => ({}),
|
||||||
|
} as any,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -55,7 +55,9 @@ async function handleClipboardPerm(
|
|||||||
// as the result of an input event ...
|
// as the result of an input event ...
|
||||||
let granted: boolean;
|
let granted: boolean;
|
||||||
try {
|
try {
|
||||||
granted = await platform.getPermissionsApi().requestClipboardPermissions();
|
granted = await platform
|
||||||
|
.getPermissionsApi()
|
||||||
|
.requestClipboardPermissions();
|
||||||
} catch (lastError) {
|
} catch (lastError) {
|
||||||
onChange(false);
|
onChange(false);
|
||||||
throw lastError;
|
throw lastError;
|
||||||
|
@ -20,7 +20,6 @@ import { h, VNode } from "preact";
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
describe("useTalerActionURL hook", () => {
|
describe("useTalerActionURL hook", () => {
|
||||||
|
|
||||||
it("should be set url to undefined when dismiss", async () => {
|
it("should be set url to undefined when dismiss", async () => {
|
||||||
const ctx = ({ children }: { children: any }): VNode => {
|
const ctx = ({ children }: { children: any }): VNode => {
|
||||||
return h(IoCProviderForTesting, {
|
return h(IoCProviderForTesting, {
|
||||||
@ -46,7 +45,7 @@ describe("useTalerActionURL hook", () => {
|
|||||||
const [url, setDismissed] = getLastResultOrThrow();
|
const [url, setDismissed] = getLastResultOrThrow();
|
||||||
expect(url).deep.equals({
|
expect(url).deep.equals({
|
||||||
location: "clipboard",
|
location: "clipboard",
|
||||||
uri: "qwe"
|
uri: "qwe",
|
||||||
});
|
});
|
||||||
setDismissed(true);
|
setDismissed(true);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import { useIocContext } from "../context/iocContext.js";
|
|||||||
|
|
||||||
export interface UriLocation {
|
export interface UriLocation {
|
||||||
uri: string;
|
uri: string;
|
||||||
location: "clipboard" | "activeTab"
|
location: "clipboard" | "activeTab";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTalerActionURL(): [
|
export function useTalerActionURL(): [
|
||||||
@ -37,7 +37,7 @@ export function useTalerActionURL(): [
|
|||||||
if (clipUri) {
|
if (clipUri) {
|
||||||
setTalerActionUrl({
|
setTalerActionUrl({
|
||||||
location: "clipboard",
|
location: "clipboard",
|
||||||
uri: clipUri
|
uri: clipUri,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ export function useTalerActionURL(): [
|
|||||||
if (tabUri) {
|
if (tabUri) {
|
||||||
setTalerActionUrl({
|
setTalerActionUrl({
|
||||||
location: "activeTab",
|
location: "activeTab",
|
||||||
uri: tabUri
|
uri: tabUri,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -119,8 +119,9 @@ function handleContainer(containerInfo: Container, props: ManagedModalProps) {
|
|||||||
el: container,
|
el: container,
|
||||||
});
|
});
|
||||||
// Use computed style, here to get the real padding to add our scrollbar width.
|
// Use computed style, here to get the real padding to add our scrollbar width.
|
||||||
container.style.paddingRight = `${getPaddingRight(container) + scrollbarSize
|
container.style.paddingRight = `${
|
||||||
}px`;
|
getPaddingRight(container) + scrollbarSize
|
||||||
|
}px`;
|
||||||
|
|
||||||
// .mui-fixed is a global helper.
|
// .mui-fixed is a global helper.
|
||||||
const fixedElements =
|
const fixedElements =
|
||||||
@ -131,8 +132,9 @@ function handleContainer(containerInfo: Container, props: ManagedModalProps) {
|
|||||||
property: "padding-right",
|
property: "padding-right",
|
||||||
el: element,
|
el: element,
|
||||||
});
|
});
|
||||||
element.style.paddingRight = `${getPaddingRight(element) + scrollbarSize
|
element.style.paddingRight = `${
|
||||||
}px`;
|
getPaddingRight(element) + scrollbarSize
|
||||||
|
}px`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +144,7 @@ function handleContainer(containerInfo: Container, props: ManagedModalProps) {
|
|||||||
const containerWindow = ownerWindow(container);
|
const containerWindow = ownerWindow(container);
|
||||||
const scrollContainer =
|
const scrollContainer =
|
||||||
parent?.nodeName === "HTML" &&
|
parent?.nodeName === "HTML" &&
|
||||||
containerWindow.getComputedStyle(parent).overflowY === "scroll"
|
containerWindow.getComputedStyle(parent).overflowY === "scroll"
|
||||||
? parent
|
? parent
|
||||||
: container;
|
: container;
|
||||||
|
|
||||||
|
@ -176,13 +176,13 @@ export interface PlatformAPI {
|
|||||||
findTalerUriInClipboard(): Promise<string | undefined>;
|
findTalerUriInClipboard(): Promise<string | undefined>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used from the frontend to send commands to the wallet
|
* Used from the frontend to send commands to the wallet
|
||||||
*
|
*
|
||||||
* @param operation
|
* @param operation
|
||||||
* @param payload
|
* @param payload
|
||||||
*
|
*
|
||||||
* @return response from the backend
|
* @return response from the backend
|
||||||
*/
|
*/
|
||||||
sendMessageToWalletBackground(
|
sendMessageToWalletBackground(
|
||||||
operation: string,
|
operation: string,
|
||||||
payload: any,
|
payload: any,
|
||||||
|
@ -109,7 +109,7 @@ export async function requestClipboardPermissions(): Promise<boolean> {
|
|||||||
rej(le);
|
rej(le);
|
||||||
}
|
}
|
||||||
res(resp);
|
res(resp);
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,13 +130,13 @@ type HeaderListenerFunc = (
|
|||||||
) => void;
|
) => void;
|
||||||
let currentHeaderListener: HeaderListenerFunc | undefined = undefined;
|
let currentHeaderListener: HeaderListenerFunc | undefined = undefined;
|
||||||
|
|
||||||
type TabListenerFunc = (
|
type TabListenerFunc = (tabId: number, info: chrome.tabs.TabChangeInfo) => void;
|
||||||
tabId: number, info: chrome.tabs.TabChangeInfo,
|
|
||||||
) => void;
|
|
||||||
let currentTabListener: TabListenerFunc | undefined = undefined;
|
let currentTabListener: TabListenerFunc | undefined = undefined;
|
||||||
|
|
||||||
export function containsTalerHeaderListener(): boolean {
|
export function containsTalerHeaderListener(): boolean {
|
||||||
return currentHeaderListener !== undefined || currentTabListener !== undefined;
|
return (
|
||||||
|
currentHeaderListener !== undefined || currentTabListener !== undefined
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeHostPermissions(): Promise<boolean> {
|
export async function removeHostPermissions(): Promise<boolean> {
|
||||||
@ -147,9 +147,11 @@ export async function removeHostPermissions(): Promise<boolean> {
|
|||||||
) {
|
) {
|
||||||
chrome.webRequest.onHeadersReceived.removeListener(currentHeaderListener);
|
chrome.webRequest.onHeadersReceived.removeListener(currentHeaderListener);
|
||||||
}
|
}
|
||||||
if (currentTabListener &&
|
if (
|
||||||
chrome?.tabs?.onUpdated?.hasListener(currentTabListener)) {
|
currentTabListener &&
|
||||||
chrome.tabs.onUpdated.removeListener(currentTabListener)
|
chrome?.tabs?.onUpdated?.hasListener(currentTabListener)
|
||||||
|
) {
|
||||||
|
chrome.tabs.onUpdated.removeListener(currentTabListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentHeaderListener = undefined;
|
currentHeaderListener = undefined;
|
||||||
@ -413,20 +415,25 @@ function registerTalerHeaderListener(
|
|||||||
.map((h) => h.value)
|
.map((h) => h.value)
|
||||||
.filter((value): value is string => !!value);
|
.filter((value): value is string => !!value);
|
||||||
if (values.length > 0) {
|
if (values.length > 0) {
|
||||||
logger.info(`Found a Taler URI in a response header for the request ${details.url} from tab ${details.tabId}`)
|
logger.info(
|
||||||
|
`Found a Taler URI in a response header for the request ${details.url} from tab ${details.tabId}`,
|
||||||
|
);
|
||||||
callback(details.tabId, values[0]);
|
callback(details.tabId, values[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tabListener(tabId: number, info: chrome.tabs.TabChangeInfo): Promise<void> {
|
async function tabListener(
|
||||||
|
tabId: number,
|
||||||
|
info: chrome.tabs.TabChangeInfo,
|
||||||
|
): Promise<void> {
|
||||||
if (tabId < 0) return;
|
if (tabId < 0) return;
|
||||||
if (info.status !== "complete") return;
|
if (info.status !== "complete") return;
|
||||||
const uri = await findTalerUriInTab(tabId);
|
const uri = await findTalerUriInTab(tabId);
|
||||||
if (!uri) return;
|
if (!uri) return;
|
||||||
logger.info(`Found a Taler URI in the tab ${tabId}`)
|
logger.info(`Found a Taler URI in the tab ${tabId}`);
|
||||||
callback(tabId, uri)
|
callback(tabId, uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevHeaderListener = currentHeaderListener;
|
const prevHeaderListener = currentHeaderListener;
|
||||||
@ -442,14 +449,18 @@ function registerTalerHeaderListener(
|
|||||||
) {
|
) {
|
||||||
chrome.webRequest.onHeadersReceived.removeListener(prevHeaderListener);
|
chrome.webRequest.onHeadersReceived.removeListener(prevHeaderListener);
|
||||||
}
|
}
|
||||||
if (prevTabListener && chrome?.tabs?.onUpdated?.hasListener(prevTabListener)) {
|
if (
|
||||||
chrome.tabs.onUpdated.removeListener(prevTabListener)
|
prevTabListener &&
|
||||||
|
chrome?.tabs?.onUpdated?.hasListener(prevTabListener)
|
||||||
|
) {
|
||||||
|
chrome.tabs.onUpdated.removeListener(prevTabListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
//if the result was positive, add the headerListener
|
//if the result was positive, add the headerListener
|
||||||
if (result) {
|
if (result) {
|
||||||
const headersEvent: chrome.webRequest.WebResponseHeadersEvent | undefined =
|
const headersEvent:
|
||||||
chrome?.webRequest?.onHeadersReceived;
|
| chrome.webRequest.WebResponseHeadersEvent
|
||||||
|
| undefined = chrome?.webRequest?.onHeadersReceived;
|
||||||
if (headersEvent) {
|
if (headersEvent) {
|
||||||
headersEvent.addListener(headerListener, { urls: ["<all_urls>"] }, [
|
headersEvent.addListener(headerListener, { urls: ["<all_urls>"] }, [
|
||||||
"responseHeaders",
|
"responseHeaders",
|
||||||
@ -472,7 +483,6 @@ function registerTalerHeaderListener(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const alertIcons = {
|
const alertIcons = {
|
||||||
@ -515,26 +525,26 @@ function setAlertedIcon(): void {
|
|||||||
|
|
||||||
interface OffscreenCanvasRenderingContext2D
|
interface OffscreenCanvasRenderingContext2D
|
||||||
extends CanvasState,
|
extends CanvasState,
|
||||||
CanvasTransform,
|
CanvasTransform,
|
||||||
CanvasCompositing,
|
CanvasCompositing,
|
||||||
CanvasImageSmoothing,
|
CanvasImageSmoothing,
|
||||||
CanvasFillStrokeStyles,
|
CanvasFillStrokeStyles,
|
||||||
CanvasShadowStyles,
|
CanvasShadowStyles,
|
||||||
CanvasFilters,
|
CanvasFilters,
|
||||||
CanvasRect,
|
CanvasRect,
|
||||||
CanvasDrawPath,
|
CanvasDrawPath,
|
||||||
CanvasUserInterface,
|
CanvasUserInterface,
|
||||||
CanvasText,
|
CanvasText,
|
||||||
CanvasDrawImage,
|
CanvasDrawImage,
|
||||||
CanvasImageData,
|
CanvasImageData,
|
||||||
CanvasPathDrawingStyles,
|
CanvasPathDrawingStyles,
|
||||||
CanvasTextDrawingStyles,
|
CanvasTextDrawingStyles,
|
||||||
CanvasPath {
|
CanvasPath {
|
||||||
readonly canvas: OffscreenCanvas;
|
readonly canvas: OffscreenCanvas;
|
||||||
}
|
}
|
||||||
declare const OffscreenCanvasRenderingContext2D: {
|
declare const OffscreenCanvasRenderingContext2D: {
|
||||||
prototype: OffscreenCanvasRenderingContext2D;
|
prototype: OffscreenCanvasRenderingContext2D;
|
||||||
new(): OffscreenCanvasRenderingContext2D;
|
new (): OffscreenCanvasRenderingContext2D;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface OffscreenCanvas extends EventTarget {
|
interface OffscreenCanvas extends EventTarget {
|
||||||
@ -547,7 +557,7 @@ interface OffscreenCanvas extends EventTarget {
|
|||||||
}
|
}
|
||||||
declare const OffscreenCanvas: {
|
declare const OffscreenCanvas: {
|
||||||
prototype: OffscreenCanvas;
|
prototype: OffscreenCanvas;
|
||||||
new(width: number, height: number): OffscreenCanvas;
|
new (width: number, height: number): OffscreenCanvas;
|
||||||
};
|
};
|
||||||
|
|
||||||
function createCanvas(size: number): OffscreenCanvas {
|
function createCanvas(size: number): OffscreenCanvas {
|
||||||
@ -727,20 +737,23 @@ async function findTalerUriInTab(tabId: number): Promise<string | undefined> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function timeout(ms: number): Promise<void> {
|
async function timeout(ms: number): Promise<void> {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
async function findTalerUriInClipboard(): Promise<string | undefined> {
|
async function findTalerUriInClipboard(): Promise<string | undefined> {
|
||||||
try {
|
try {
|
||||||
//It looks like clipboard promise does not return, so we need a timeout
|
//It looks like clipboard promise does not return, so we need a timeout
|
||||||
const textInClipboard = await Promise.any([
|
const textInClipboard = await Promise.any([
|
||||||
timeout(100),
|
timeout(100),
|
||||||
window.navigator.clipboard.readText()
|
window.navigator.clipboard.readText(),
|
||||||
])
|
]);
|
||||||
if (!textInClipboard) return;
|
if (!textInClipboard) return;
|
||||||
return textInClipboard.startsWith("taler://") || textInClipboard.startsWith("taler+http://") ? textInClipboard : undefined
|
return textInClipboard.startsWith("taler://") ||
|
||||||
|
textInClipboard.startsWith("taler+http://")
|
||||||
|
? textInClipboard
|
||||||
|
: undefined;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("could not read clipboard", e)
|
logger.error("could not read clipboard", e);
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,8 @@ function getJsonIfOk(r: Response): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Try another server: (${r.status}) ${r.statusText || "internal server error"
|
`Try another server: (${r.status}) ${
|
||||||
|
r.statusText || "internal server error"
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -102,10 +103,10 @@ export function buildTermsOfServiceStatus(
|
|||||||
return !content
|
return !content
|
||||||
? "notfound"
|
? "notfound"
|
||||||
: !acceptedVersion
|
: !acceptedVersion
|
||||||
? "new"
|
? "new"
|
||||||
: acceptedVersion !== currentVersion
|
: acceptedVersion !== currentVersion
|
||||||
? "changed"
|
? "changed"
|
||||||
: "accepted";
|
: "accepted";
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTermsOfServiceContent(
|
function parseTermsOfServiceContent(
|
||||||
|
@ -62,9 +62,9 @@ describe("DepositPage states", () => {
|
|||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [{ available: `${currency}:0` }],
|
balances: [{ available: `${currency}:0` }],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
listKnownBankAccounts: async () => ({ accounts: {} }),
|
listKnownBankAccounts: async () => ({ accounts: {} }),
|
||||||
} as Partial<typeof wxApi> as any),
|
} as Partial<typeof wxApi> as any),
|
||||||
);
|
);
|
||||||
@ -89,9 +89,9 @@ describe("DepositPage states", () => {
|
|||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [{ available: `${currency}:1` }],
|
balances: [{ available: `${currency}:1` }],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
listKnownBankAccounts: async () => ({ accounts: {} }),
|
listKnownBankAccounts: async () => ({ accounts: {} }),
|
||||||
} as Partial<typeof wxApi> as any),
|
} as Partial<typeof wxApi> as any),
|
||||||
);
|
);
|
||||||
@ -111,19 +111,21 @@ describe("DepositPage states", () => {
|
|||||||
await assertNoPendingUpdate();
|
await assertNoPendingUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
const ibanPayto_str = "payto://iban/ES8877998399652238"
|
const ibanPayto_str = "payto://iban/ES8877998399652238";
|
||||||
const ibanPayto = { ibanPayto_str: parsePaytoUri(ibanPayto_str)! };
|
const ibanPayto = { ibanPayto_str: parsePaytoUri(ibanPayto_str)! };
|
||||||
const talerBankPayto_str = "payto://x-taler-bank/ES8877998399652238"
|
const talerBankPayto_str = "payto://x-taler-bank/ES8877998399652238";
|
||||||
const talerBankPayto = { talerBankPayto_str: parsePaytoUri(talerBankPayto_str)! };
|
const talerBankPayto = {
|
||||||
|
talerBankPayto_str: parsePaytoUri(talerBankPayto_str)!,
|
||||||
|
};
|
||||||
|
|
||||||
it("should have status 'ready' but unable to deposit ", async () => {
|
it("should have status 'ready' but unable to deposit ", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [{ available: `${currency}:1` }],
|
balances: [{ available: `${currency}:1` }],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
||||||
} as Partial<typeof wxApi> as any),
|
} as Partial<typeof wxApi> as any),
|
||||||
);
|
);
|
||||||
@ -153,9 +155,9 @@ describe("DepositPage states", () => {
|
|||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [{ available: `${currency}:1` }],
|
balances: [{ available: `${currency}:1` }],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
||||||
getFeeForDeposit: withoutFee,
|
getFeeForDeposit: withoutFee,
|
||||||
} as Partial<typeof wxApi> as any),
|
} as Partial<typeof wxApi> as any),
|
||||||
@ -202,9 +204,9 @@ describe("DepositPage states", () => {
|
|||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [{ available: `${currency}:1` }],
|
balances: [{ available: `${currency}:1` }],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
||||||
getFeeForDeposit: withSomeFee,
|
getFeeForDeposit: withSomeFee,
|
||||||
} as Partial<typeof wxApi> as any),
|
} as Partial<typeof wxApi> as any),
|
||||||
@ -252,9 +254,9 @@ describe("DepositPage states", () => {
|
|||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [{ available: `${currency}:1` }],
|
balances: [{ available: `${currency}:1` }],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
listKnownBankAccounts: async () => ({
|
listKnownBankAccounts: async () => ({
|
||||||
accounts: { ...ibanPayto, ...talerBankPayto },
|
accounts: { ...ibanPayto, ...talerBankPayto },
|
||||||
}),
|
}),
|
||||||
@ -338,9 +340,9 @@ describe("DepositPage states", () => {
|
|||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState(currency, nullFunction, nullFunction, {
|
useComponentState(currency, nullFunction, nullFunction, {
|
||||||
getBalance: async () =>
|
getBalance: async () =>
|
||||||
({
|
({
|
||||||
balances: [{ available: `${currency}:15` }],
|
balances: [{ available: `${currency}:15` }],
|
||||||
} as Partial<BalancesResponse>),
|
} as Partial<BalancesResponse>),
|
||||||
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
||||||
getFeeForDeposit: withSomeFee,
|
getFeeForDeposit: withSomeFee,
|
||||||
} as Partial<typeof wxApi> as any),
|
} as Partial<typeof wxApi> as any),
|
||||||
|
@ -25,13 +25,9 @@ export interface Props {
|
|||||||
p: string;
|
p: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State = State.Loading | State.LoadingUriError | State.Ready;
|
||||||
| State.Loading
|
|
||||||
| State.LoadingUriError
|
|
||||||
| State.Ready;
|
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -54,10 +50,11 @@ export namespace State {
|
|||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
"ready": ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ComponentName = compose(
|
||||||
export const ComponentName = compose("ComponentName", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
"ComponentName",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -17,12 +17,9 @@
|
|||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState({ p }: Props, api: typeof wxApi): State {
|
||||||
{ p }: Props,
|
|
||||||
api: typeof wxApi,
|
|
||||||
): State {
|
|
||||||
return {
|
return {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,7 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
describe("test description", () => {
|
describe("test description", () => {
|
||||||
|
|
||||||
it("should assert", () => {
|
it("should assert", () => {
|
||||||
|
expect([]).deep.equals([]);
|
||||||
expect([]).deep.equals([])
|
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
@ -14,16 +14,25 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FeeDescription, FeeDescriptionPair, AbsoluteTime, ExchangeFullDetails, OperationMap } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
FeeDescription,
|
||||||
|
FeeDescriptionPair,
|
||||||
|
AbsoluteTime,
|
||||||
|
ExchangeFullDetails,
|
||||||
|
OperationMap,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { ComparingView, LoadingUriView, NoExchangesView, ReadyView } from "./views.js";
|
import {
|
||||||
|
ComparingView,
|
||||||
|
LoadingUriView,
|
||||||
|
NoExchangesView,
|
||||||
|
ReadyView,
|
||||||
|
} from "./views.js";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
currency?: string;
|
currency?: string;
|
||||||
@ -39,7 +48,6 @@ export type State =
|
|||||||
| State.NoExchanges;
|
| State.NoExchanges;
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
|
|
||||||
export interface Loading {
|
export interface Loading {
|
||||||
status: "loading";
|
status: "loading";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
@ -75,13 +83,16 @@ export namespace State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-uri": LoadingUriView,
|
"loading-uri": LoadingUriView,
|
||||||
"comparing": ComparingView,
|
comparing: ComparingView,
|
||||||
"no-exchanges": NoExchangesView,
|
"no-exchanges": NoExchangesView,
|
||||||
"ready": ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExchangeSelectionPage = compose("ExchangeSelectionPage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
export const ExchangeSelectionPage = compose(
|
||||||
|
"ExchangeSelectionPage",
|
||||||
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
|
viewMapping,
|
||||||
|
);
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { FeeDescription, OperationMap } from "@gnu-taler/taler-util";
|
import { FeeDescription, OperationMap } from "@gnu-taler/taler-util";
|
||||||
import { createDenominationPairTimeline } from "@gnu-taler/taler-wallet-core";
|
import { createDenominationPairTimeline } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
@ -26,26 +25,32 @@ export function useComponentState(
|
|||||||
{ onCancel, onSelection, currency }: Props,
|
{ onCancel, onSelection, currency }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const initialValue = 0
|
const initialValue = 0;
|
||||||
const [value, setValue] = useState(String(initialValue));
|
const [value, setValue] = useState(String(initialValue));
|
||||||
|
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
const { exchanges } = await api.listExchanges()
|
const { exchanges } = await api.listExchanges();
|
||||||
|
|
||||||
const selectedIdx = parseInt(value, 10)
|
const selectedIdx = parseInt(value, 10);
|
||||||
const selectedExchange = exchanges.length == 0 ? undefined : exchanges[selectedIdx]
|
const selectedExchange =
|
||||||
const selected = !selectedExchange ? undefined : await api.getExchangeDetailedInfo(selectedExchange.exchangeBaseUrl)
|
exchanges.length == 0 ? undefined : exchanges[selectedIdx];
|
||||||
|
const selected = !selectedExchange
|
||||||
|
? undefined
|
||||||
|
: await api.getExchangeDetailedInfo(selectedExchange.exchangeBaseUrl);
|
||||||
|
|
||||||
const initialExchange = selectedIdx === initialValue ? undefined : exchanges[initialValue]
|
const initialExchange =
|
||||||
const original = !initialExchange ? undefined : await api.getExchangeDetailedInfo(initialExchange.exchangeBaseUrl)
|
selectedIdx === initialValue ? undefined : exchanges[initialValue];
|
||||||
return { exchanges, selected, original }
|
const original = !initialExchange
|
||||||
|
? undefined
|
||||||
|
: await api.getExchangeDetailedInfo(initialExchange.exchangeBaseUrl);
|
||||||
|
return { exchanges, selected, original };
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hook) {
|
if (!hook) {
|
||||||
return {
|
return {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if (hook.hasError) {
|
if (hook.hasError) {
|
||||||
return {
|
return {
|
||||||
@ -60,11 +65,14 @@ export function useComponentState(
|
|||||||
//!selected <=> exchanges.length === 0
|
//!selected <=> exchanges.length === 0
|
||||||
return {
|
return {
|
||||||
status: "no-exchanges",
|
status: "no-exchanges",
|
||||||
error: undefined
|
error: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const exchangeMap = exchanges.reduce((prev, cur, idx) => ({ ...prev, [cur.exchangeBaseUrl]: String(idx) }), {} as Record<string, string>)
|
const exchangeMap = exchanges.reduce(
|
||||||
|
(prev, cur, idx) => ({ ...prev, [cur.exchangeBaseUrl]: String(idx) }),
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
|
||||||
if (!original) {
|
if (!original) {
|
||||||
// !original <=> selected == original
|
// !original <=> selected == original
|
||||||
@ -74,24 +82,36 @@ export function useComponentState(
|
|||||||
list: exchangeMap,
|
list: exchangeMap,
|
||||||
value: value,
|
value: value,
|
||||||
onChange: async (v) => {
|
onChange: async (v) => {
|
||||||
setValue(v)
|
setValue(v);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
error: undefined,
|
error: undefined,
|
||||||
onClose: {
|
onClose: {
|
||||||
onClick: onCancel
|
onClick: onCancel,
|
||||||
},
|
},
|
||||||
selected,
|
selected,
|
||||||
timeline: selected.feesDescription
|
timeline: selected.feesDescription,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const pairTimeline: OperationMap<FeeDescription[]> = {
|
const pairTimeline: OperationMap<FeeDescription[]> = {
|
||||||
deposit: createDenominationPairTimeline(selected.feesDescription.deposit, original.feesDescription.deposit),
|
deposit: createDenominationPairTimeline(
|
||||||
refresh: createDenominationPairTimeline(selected.feesDescription.refresh, original.feesDescription.refresh),
|
selected.feesDescription.deposit,
|
||||||
refund: createDenominationPairTimeline(selected.feesDescription.refund, original.feesDescription.refund),
|
original.feesDescription.deposit,
|
||||||
withdraw: createDenominationPairTimeline(selected.feesDescription.withdraw, original.feesDescription.withdraw),
|
),
|
||||||
}
|
refresh: createDenominationPairTimeline(
|
||||||
|
selected.feesDescription.refresh,
|
||||||
|
original.feesDescription.refresh,
|
||||||
|
),
|
||||||
|
refund: createDenominationPairTimeline(
|
||||||
|
selected.feesDescription.refund,
|
||||||
|
original.feesDescription.refund,
|
||||||
|
),
|
||||||
|
withdraw: createDenominationPairTimeline(
|
||||||
|
selected.feesDescription.withdraw,
|
||||||
|
original.feesDescription.withdraw,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "comparing",
|
status: "comparing",
|
||||||
@ -99,23 +119,21 @@ export function useComponentState(
|
|||||||
list: exchangeMap,
|
list: exchangeMap,
|
||||||
value: value,
|
value: value,
|
||||||
onChange: async (v) => {
|
onChange: async (v) => {
|
||||||
setValue(v)
|
setValue(v);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
error: undefined,
|
error: undefined,
|
||||||
onReset: {
|
onReset: {
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
setValue(String(initialValue))
|
setValue(String(initialValue));
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
onSelect: {
|
onSelect: {
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
onSelection(selected.exchangeBaseUrl)
|
onSelection(selected.exchangeBaseUrl);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
selected,
|
selected,
|
||||||
pairTimeline,
|
pairTimeline,
|
||||||
}
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,8 +19,5 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { AbsoluteTime, Amounts, DenominationInfo } from "@gnu-taler/taler-util";
|
||||||
AbsoluteTime,
|
|
||||||
Amounts, DenominationInfo
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
@ -261,9 +261,11 @@ export function listExchanges(): Promise<ExchangesListResponse> {
|
|||||||
return callBackend("listExchanges", {});
|
return callBackend("listExchanges", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExchangeDetailedInfo(exchangeBaseUrl: string): Promise<ExchangeFullDetails> {
|
export function getExchangeDetailedInfo(
|
||||||
|
exchangeBaseUrl: string,
|
||||||
|
): Promise<ExchangeFullDetails> {
|
||||||
return callBackend("getExchangeDetailedInfo", {
|
return callBackend("getExchangeDetailedInfo", {
|
||||||
exchangeBaseUrl
|
exchangeBaseUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,6 +540,6 @@ export function acceptPeerPullPayment(
|
|||||||
|
|
||||||
export function getTransactionById(tid: string): Promise<Transaction> {
|
export function getTransactionById(tid: string): Promise<Transaction> {
|
||||||
return callBackend("getTransactionById", {
|
return callBackend("getTransactionById", {
|
||||||
transactionId: tid
|
transactionId: tid,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
@ -344,7 +344,6 @@ export async function wxMain(): Promise<void> {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// On platforms that support it, also listen to external
|
// On platforms that support it, also listen to external
|
||||||
// modification of permissions.
|
// modification of permissions.
|
||||||
platform.getPermissionsApi().addPermissionsListener((perm, lastError) => {
|
platform.getPermissionsApi().addPermissionsListener((perm, lastError) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user