fix #7153: more error handling

if handler do not trap error then fail at compile time,
all safe handlers push alert on error
errors are typed so they render good information
This commit is contained in:
Sebastian 2023-01-09 20:20:09 -03:00
parent 8a70edb2f8
commit 4a781bd0dd
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
91 changed files with 1003 additions and 1008 deletions

View File

@ -20,12 +20,10 @@
*/
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { useTranslationContext } from "../context/translation.js";
import { Grid } from "../mui/Grid.js";
import { AmountFieldHandler, TextFieldHandler } from "../mui/handlers.js";
import { AmountFieldHandler, nullFunction, withSafe } from "../mui/handlers.js";
import { AmountField } from "./AmountField.js";
export default {
@ -39,9 +37,9 @@ function RenderAmount(): VNode {
const handler: AmountFieldHandler = {
value: value ?? Amounts.zeroOfCurrency("USD"),
onInput: async (e) => {
onInput: withSafe(async (e) => {
setValue(e);
},
}, nullFunction),
error,
};
const { i18n } = useTranslationContext();

View File

@ -18,7 +18,6 @@ import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { useTranslationContext } from "../../../web-util/src/index.browser.js";
import {
ErrorAlert,
Alert as AlertNotification,
useAlertContext,
} from "../context/alert.js";
@ -37,41 +36,78 @@ function AlertContext({
context: undefined | object;
}): VNode {
const [more, setMore] = useState(false);
const [wrap, setWrap] = useState(false);
const { i18n } = useTranslationContext();
if (!more) {
return (
<div style={{ display: "flex", justifyContent: "right" }}>
<a onClick={() => setMore(true)}>
<a
onClick={() => setMore(true)}
style={{ cursor: "pointer", textDecoration: "underline" }}
>
<i18n.Translate>more info</i18n.Translate>
</a>
</div>
);
}
return (
<pre style={{ overflow: "overlay" }}>
{JSON.stringify(
const errorInfo = JSON.stringify(
context === undefined ? { cause } : { context, cause },
undefined,
2,
)}
);
return (
<Fragment>
<div style={{ display: "flex", justifyContent: "right" }}>
<a
onClick={() => setWrap(!wrap)}
style={{ cursor: "pointer", textDecoration: "underline" }}
>
<i18n.Translate>wrap text</i18n.Translate>
</a>
&nbsp;&nbsp;
<a
onClick={() => navigator.clipboard.writeText(errorInfo)}
style={{ cursor: "pointer", textDecoration: "underline" }}
>
<i18n.Translate>copy content</i18n.Translate>
</a>
&nbsp;&nbsp;
<a
onClick={() => setMore(false)}
style={{ cursor: "pointer", textDecoration: "underline" }}
>
<i18n.Translate>less info</i18n.Translate>
</a>
</div>
<pre
style={
wrap
? {
whiteSpace: "pre-wrap",
overflowWrap: "anywhere",
}
: {
overflow: "overlay",
}
}
>
{errorInfo}
</pre>
</Fragment>
);
}
export function ErrorAlertView({
error: alert,
error,
onClose,
}: {
error: ErrorAlert;
error: AlertNotification;
onClose?: () => Promise<void>;
}): VNode {
return (
<Alert title={alert.message} severity={alert.type} onClose={onClose}>
<div style={{ display: "flex", flexDirection: "column" }}>
<div>{alert.description}</div>
<AlertContext context={alert.context} cause={alert.cause} />
</div>
</Alert>
<Wrapper>
<AlertView alert={error} onClose={onClose} />
</Wrapper>
);
}
@ -86,6 +122,9 @@ export function AlertView({
<Alert title={alert.message} severity={alert.type} onClose={onClose}>
<div style={{ display: "flex", flexDirection: "column" }}>
<div>{alert.description}</div>
{alert.type === "error" ? (
<AlertContext context={alert.context} cause={alert.cause} />
) : undefined}
</div>
</Alert>
);
@ -104,5 +143,5 @@ export function CurrentAlerts(): VNode {
}
function Wrapper({ children }: { children: ComponentChildren }): VNode {
return <div style={{ margin: "2em" }}>{children}</div>;
return <div style={{ margin: "1em" }}>{children}</div>;
}

View File

@ -24,7 +24,7 @@ import {
Transaction,
TransactionType,
} from "@gnu-taler/taler-util";
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { PendingTransactionsView as TestedComponent } from "./PendingTransactions.js";
export default {
@ -32,7 +32,7 @@ export default {
component: TestedComponent,
};
export const OnePendingTransaction = createExample(TestedComponent, {
export const OnePendingTransaction = tests.createExample(TestedComponent, {
transactions: [
{
amountEffective: "USD:10",
@ -42,7 +42,7 @@ export const OnePendingTransaction = createExample(TestedComponent, {
],
});
export const ThreePendingTransactions = createExample(TestedComponent, {
export const ThreePendingTransactions = tests.createExample(TestedComponent, {
transactions: [
{
amountEffective: "USD:10",
@ -62,7 +62,7 @@ export const ThreePendingTransactions = createExample(TestedComponent, {
],
});
export const TenPendingTransactions = createExample(TestedComponent, {
export const TenPendingTransactions = tests.createExample(TestedComponent, {
transactions: [
{
amountEffective: "USD:10",

View File

@ -19,13 +19,13 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { QR } from "./QR.js";
export default {
title: "qr",
};
export const Restore = createExample(QR, {
export const Restore = tests.createExample(QR, {
text: "taler://restore/6J0RZTJC6AV21WXK87BTE67WTHE9P2QSHF2BZXTP7PDZY2ARYBPG@sync1.demo.taler.net,sync2.demo.taler.net,sync1.demo.taler.net,sync3.demo.taler.net",
});

View File

@ -20,7 +20,7 @@
*/
import { WalletContractData } from "@gnu-taler/taler-wallet-core";
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import {
ErrorView,
HiddenView,
@ -86,10 +86,10 @@ const cd: WalletContractData = {
deliveryLocation: undefined,
};
export const ShowingSimpleOrder = createExample(ShowView, {
export const ShowingSimpleOrder = tests.createExample(ShowView, {
contractTerms: cd,
});
export const Error = createExample(ErrorView, {
export const Error = tests.createExample(ErrorView, {
proposalId: "asd",
error: {
hasError: true,
@ -103,5 +103,5 @@ export const Error = createExample(ErrorView, {
// },
},
});
export const Loading = createExample(LoadingView, {});
export const Hidden = createExample(HiddenView, {});
export const Loading = tests.createExample(LoadingView, {});
export const Hidden = tests.createExample(HiddenView, {});

View File

@ -24,14 +24,14 @@ import { useState } from "preact/hooks";
import { Loading } from "../components/Loading.js";
import { Modal } from "../components/Modal.js";
import { Time } from "../components/Time.js";
import { alertFromError } from "../context/alert.js";
import { alertFromError, useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js";
import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../mui/handlers.js";
import { compose, StateViewMap } from "../utils/index.js";
import { Amount } from "./Amount.js";
import { AlertView } from "./CurrentAlerts.js";
import { ErrorAlertView } from "./CurrentAlerts.js";
import { Link } from "./styled/index.js";
const ContractTermsTable = styled.table`
@ -102,6 +102,7 @@ interface Props {
function useComponentState({ proposalId }: Props): State {
const api = useBackendContext();
const [show, setShow] = useState(false);
const { pushAlertOnError } = useAlertContext();
const hook = useAsyncAsHook(async () => {
if (!show) return undefined;
return await api.wallet.call(WalletApiOperation.GetContractTermsDetails, {
@ -110,10 +111,10 @@ function useComponentState({ proposalId }: Props): State {
}, [show]);
const hideHandler = {
onClick: async () => setShow(false),
onClick: pushAlertOnError(async () => setShow(false)),
};
const showHandler = {
onClick: async () => setShow(true),
onClick: pushAlertOnError(async () => setShow(true)),
};
if (!show) {
return {
@ -161,8 +162,8 @@ export function ErrorView({
const { i18n } = useTranslationContext();
return (
<Modal title="Full detail" onClose={hideHandler}>
<AlertView
alert={alertFromError(
<ErrorAlertView
error={alertFromError(
i18n.str`Could not load purchase proposal details`,
error,
{ proposalId },

View File

@ -28,7 +28,7 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
const readOnly = !onChange;
const [showContent, setShowContent] = useState<boolean>(readOnly);
const { i18n } = useTranslationContext();
const { pushAlert } = useAlertContext();
const { pushAlertOnError } = useAlertContext();
/**
* For the exchange selected, bring the status of the terms of service
@ -67,7 +67,6 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
async function onUpdate(accepted: boolean): Promise<void> {
if (!state) return;
try {
if (accepted) {
await api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
exchangeBaseUrl: exchangeUrl,
@ -82,9 +81,6 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
}
// setAccepted(accepted);
if (!readOnly) onChange(accepted); //external update
} catch (e) {
pushAlert(alertFromError(i18n.str`Could not accept terms of service`, e));
}
}
const accepted = state.status === "accepted";
@ -94,20 +90,20 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
showingTermsOfService: {
value: showContent,
button: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setShowContent(!showContent);
},
}),
},
},
terms: state,
termsAccepted: {
value: accepted,
button: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
const newValue = !accepted; //toggle
onUpdate(newValue);
await onUpdate(newValue);
setShowContent(false);
},
}),
},
},
};

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
// import { ReadyView } from "./views.js";
export default {
title: "TermsOfService",
};
// export const Ready = createExample(ReadyView, {});
// export const Ready = tests.createExample(ReadyView, {});

View File

@ -19,19 +19,26 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { TranslatedString } from "@gnu-taler/taler-util";
import { TalerErrorDetail, TranslatedString } from "@gnu-taler/taler-util";
import { ComponentChildren, createContext, h, VNode } from "preact";
import { useContext, useState } from "preact/hooks";
import { HookError } from "../hooks/useAsyncAsHook.js";
import { SafeHandler, withSafe } from "../mui/handlers.js";
import { BackgroundError } from "../wxApi.js";
export type AlertType = "info" | "warning" | "error" | "success";
export interface Alert {
export interface InfoAlert {
message: TranslatedString;
description: TranslatedString | VNode;
type: AlertType;
type: "info" | "warning" | "success";
}
export interface ErrorAlert extends Alert {
export type Alert = InfoAlert | ErrorAlert;
export interface ErrorAlert {
message: TranslatedString;
description: TranslatedString | VNode;
type: "error";
context: object;
cause: any;
@ -41,10 +48,14 @@ type Type = {
alerts: Alert[];
pushAlert: (n: Alert) => void;
removeAlert: (n: Alert) => void;
pushAlertOnError: <T>(h: (p: T) => Promise<void>) => SafeHandler<T>;
};
const initial: Type = {
alerts: [],
pushAlertOnError: () => {
throw Error("alert context not initialized");
},
pushAlert: () => {
null;
},
@ -80,8 +91,17 @@ export const AlertProvider = ({ children }: Props): VNode => {
setAlerts((ns: AlertWithDate[]) => ns.filter((n) => n !== alert));
};
function pushAlertOnError<T>(
handler: (p: T) => Promise<void>,
): SafeHandler<T> {
return withSafe(handler, (e) => {
const a = alertFromError(e.message as TranslatedString, e);
pushAlert(a);
});
}
return h(Context.Provider, {
value: { alerts, pushAlert, removeAlert },
value: { alerts, pushAlert, removeAlert, pushAlertOnError },
children,
});
};
@ -90,29 +110,71 @@ export const useAlertContext = (): Type => useContext(Context);
export function alertFromError(
message: TranslatedString,
error: unknown,
error: HookError,
...context: any[]
): ErrorAlert;
export function alertFromError(
message: TranslatedString,
error: Error,
...context: any[]
): ErrorAlert;
export function alertFromError(
message: TranslatedString,
error: TalerErrorDetail,
...context: any[]
): ErrorAlert;
export function alertFromError(
message: TranslatedString,
error: HookError | TalerErrorDetail | Error,
...context: any[]
): ErrorAlert {
let description = "" as TranslatedString;
let description: TranslatedString;
let cause: any;
const isObject = typeof error === "object" &&
error !== null;
const hasMessage =
isObject &&
"message" in error &&
typeof error.message === "string";
if (hasMessage) {
if (typeof error === "object" && error !== null) {
if ("code" in error) {
//TalerErrorDetail
description = (error.hint ??
`Error code: ${error.code}`) as TranslatedString;
cause = {
details: error,
};
} else if ("hasError" in error) {
//HookError
description = error.message as TranslatedString;
if (error.type === "taler") {
cause = {
details: error.details,
};
}
} else {
description = `Unknown error: ${String(error)}` as TranslatedString;
if (error instanceof BackgroundError) {
description = (error.errorDetail.hint ??
`Error code: ${error.errorDetail.code}`) as TranslatedString;
cause = {
details: error.errorDetail,
stack: error.stack,
};
} else {
description = error.message as TranslatedString;
cause = {
stack: error.stack,
};
}
}
} else {
description = "" as TranslatedString;
cause = error;
}
return {
type: "error",
message,
description,
cause: error,
cause,
context,
};
}

View File

@ -22,16 +22,15 @@
import { createContext, h, VNode } from "preact";
import { useContext } from "preact/hooks";
import { useWalletDevMode } from "../hooks/useWalletDevMode.js";
import { ToggleHandler } from "../mui/handlers.js";
interface Type {
devMode: boolean;
devModeToggle: ToggleHandler;
toggle: () => Promise<void>;
}
const Context = createContext<Type>({
devMode: false,
devModeToggle: {
button: {},
toggle: async () => {
null;
},
});
@ -47,9 +46,8 @@ export const DevContextProviderForTesting = ({
return h(Context.Provider, {
value: {
devMode: !!value,
devModeToggle: {
value,
button: {},
toggle: async () => {
null;
},
},
children,
@ -58,7 +56,10 @@ export const DevContextProviderForTesting = ({
export const DevContextProvider = ({ children }: { children: any }): VNode => {
const devModeToggle = useWalletDevMode();
const value: Type = { devMode: !!devModeToggle.value, devModeToggle };
const value: Type = {
devMode: !!devModeToggle.value,
toggle: devModeToggle.toggle,
};
//support for function as children, useful for getting the value right away
children =
children.length === 1 && typeof children === "function"

View File

@ -16,7 +16,7 @@
import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -29,6 +29,7 @@ export function useComponentState({
onSuccess,
}: Props): State {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const info = useAsyncAsHook(async () => {
if (!talerDepositUri) throw Error("ERROR_NO-URI-FOR-DEPOSIT");
if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT");
@ -66,7 +67,7 @@ export function useComponentState({
status: "ready",
error: undefined,
confirm: {
onClick: doDeposit,
onClick: pushAlertOnError(doDeposit),
},
fee: Amounts.sub(deposit.totalDepositCost, deposit.effectiveDepositAmount)
.amount,

View File

@ -20,14 +20,14 @@
*/
import { Amounts } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js";
export default {
title: "deposit",
};
export const Ready = createExample(ReadyView, {
export const Ready = tests.createExample(ReadyView, {
status: "ready",
confirm: {},
cost: Amounts.parseOrThrow("EUR:1.2"),

View File

@ -55,7 +55,7 @@ describe("Deposit CTA states", () => {
if (!error) expect.fail();
// if (!error.hasError) expect.fail();
// if (error.operational) expect.fail();
expect(error.cause?.message).eq("ERROR_NO-URI-FOR-DEPOSIT");
expect(error.description).eq("ERROR_NO-URI-FOR-DEPOSIT");
},
],
TestingContext,

View File

@ -15,15 +15,11 @@
*/
/* eslint-disable react-hooks/rules-of-hooks */
import {
Amounts,
TalerErrorDetail,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util";
import { Amounts, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { isFuture, parse } from "date-fns";
import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -71,6 +67,7 @@ export function useComponentState({
return () => {
const [subject, setSubject] = useState<string | undefined>();
const [timestamp, setTimestamp] = useState<string | undefined>();
const { pushAlertOnError } = useAlertContext();
const selectedExchange = useSelectedExchange({
currency: amount.currency,
@ -144,7 +141,7 @@ export function useComponentState({
async function accept(): Promise<void> {
if (!subject || !purse_expiration) return;
try {
const resp = await api.wallet.call(
WalletApiOperation.InitiatePeerPullPayment,
{
@ -158,13 +155,6 @@ export function useComponentState({
);
onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
// setOperationError(e.errorDetail);
}
console.error(e);
throw Error("error trying to accept");
}
}
const unableToCreate =
!subject || Amounts.isZero(amount) || !purse_expiration;
@ -179,22 +169,22 @@ export function useComponentState({
? "Can't be empty"
: undefined,
value: subject ?? "",
onInput: async (e) => setSubject(e),
onInput: pushAlertOnError(async (e) => setSubject(e)),
},
expiration: {
error: timestampError,
value: timestamp === undefined ? "" : timestamp,
onInput: async (e) => {
onInput: pushAlertOnError(async (e) => {
setTimestamp(e);
},
}),
},
doSelectExchange: selectedExchange.doSelect,
exchangeUrl: exchange.exchangeBaseUrl,
create: {
onClick: unableToCreate ? undefined : accept,
onClick: unableToCreate ? undefined : pushAlertOnError(accept),
},
cancel: {
onClick: onClose,
onClick: pushAlertOnError(onClose),
},
requestAmount,
toBeReceived,

View File

@ -19,14 +19,15 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { nullFunction } from "../../mui/handlers.js";
import { ReadyView } from "./views.js";
export default {
title: "invoice create",
};
export const Ready = createExample(ReadyView, {
export const Ready = tests.createExample(ReadyView, {
requestAmount: {
currency: "ARS",
value: 1,
@ -45,9 +46,7 @@ export const Ready = createExample(ReadyView, {
exchangeUrl: "https://exchange.taler.ar",
subject: {
value: "some subject",
onInput: async () => {
null;
},
onInput: nullFunction,
},
create: {},
});

View File

@ -61,7 +61,6 @@ export namespace State {
goToWalletManualWithdraw: (currency: string) => Promise<void>;
summary: string | undefined;
expiration: AbsoluteTime | undefined;
operationError?: TalerErrorDetail;
payStatus: PreparePayResult;
}

View File

@ -25,10 +25,11 @@ import {
} from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { withSafe } from "../../mui/handlers.js";
import { Props, State } from "./index.js";
export function useComponentState({
@ -39,6 +40,7 @@ export function useComponentState({
}: Props): State {
const api = useBackendContext();
const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const hook = useAsyncAsHook(async () => {
const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, {
talerUri: talerPayPullUri,
@ -54,10 +56,6 @@ export function useComponentState({
),
);
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>(undefined);
if (!hook) {
return {
status: "loading",
@ -109,18 +107,17 @@ export function useComponentState({
contractTerms: {} as any,
amountRaw: hook.response.p2p.amount,
noncePriv: "",
};
} as any; //FIXME: check this interface with new values
const baseResult = {
uri: talerPayPullUri,
cancel: {
onClick: onClose,
onClick: pushAlertOnError(onClose),
},
amount,
goToWalletManualWithdraw,
summary,
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
operationError,
};
if (!foundBalance) {
@ -148,7 +145,6 @@ export function useComponentState({
}
async function accept(): Promise<void> {
try {
const resp = await api.wallet.call(
WalletApiOperation.AcceptPeerPullPayment,
{
@ -156,13 +152,6 @@ export function useComponentState({
},
);
onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);
}
console.error(e);
throw Error("error trying to accept");
}
}
return {
@ -172,7 +161,7 @@ export function useComponentState({
payStatus: paymentPossible,
balance: foundAmount,
accept: {
onClick: accept,
onClick: pushAlertOnError(accept),
},
};
}

View File

@ -20,14 +20,14 @@
*/
import { PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js";
export default {
title: "invoice payment",
};
export const Ready = createExample(ReadyView, {
export const Ready = tests.createExample(ReadyView, {
amount: {
currency: "ARS",
value: 1,

View File

@ -16,11 +16,10 @@
import { Fragment, h, VNode } from "preact";
import { Amount } from "../../components/Amount.js";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js";
import { PaymentButtons } from "../../components/PaymentButtons.js";
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
import { SubTitle, WalletAction } from "../../components/styled/index.js";
import { Time } from "../../components/Time.js";
import { useTranslationContext } from "../../context/translation.js";
import { State } from "./index.js";
@ -29,29 +28,14 @@ export function ReadyView(
state: State.Ready | State.NoBalanceForCurrency | State.NoEnoughBalance,
): VNode {
const { i18n } = useTranslationContext();
const {
operationError,
summary,
amount,
expiration,
uri,
status,
balance,
payStatus,
cancel,
} = state;
const { summary, amount, expiration, uri, status, balance, payStatus } =
state;
return (
<WalletAction>
<LogoHeader />
<SubTitle>
<i18n.Translate>Digital invoice</i18n.Translate>
</SubTitle>
{operationError && (
<ErrorTalerOperation
title={i18n.str`Could not finish the payment operation`}
error={operationError}
/>
)}
<section style={{ textAlign: "left" }}>
<Part title={i18n.str`Subject`} text={<div>{summary}</div>} />
<Part title={i18n.str`Amount`} text={<Amount value={amount} />} />

View File

@ -19,11 +19,10 @@ import {
ConfirmPayResultType,
NotificationType,
PreparePayResultType,
TalerErrorCode,
} from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect } from "preact/hooks";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -36,7 +35,7 @@ export function useComponentState({
goToWalletManualWithdraw,
onSuccess,
}: Props): State {
const [payErrMsg, setPayErrMsg] = useState<TalerError | undefined>(undefined);
const { pushAlertOnError } = useAlertContext();
const api = useBackendContext();
const { i18n } = useTranslationContext();
@ -142,23 +141,27 @@ export function useComponentState({
}
async function doPayment(): Promise<void> {
try {
if (payStatus.status !== "payment-possible") {
throw TalerError.fromUncheckedDetail({
code: TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR,
hint: `payment is not possible: ${payStatus.status}`,
});
}
// if (payStatus.status !== "payment-possible") {
// throw TalerError.fromUncheckedDetail({
// code: TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR,
// when: new Date().toISOString(),
// hint: `payment is not possible: ${payStatus.status}`,
// });
// }
const res = await api.wallet.call(WalletApiOperation.ConfirmPay, {
proposalId: payStatus.proposalId,
});
// handle confirm pay
if (res.type !== ConfirmPayResultType.Done) {
throw TalerError.fromUncheckedDetail({
code: TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR,
hint: `could not confirm payment`,
payResult: res,
});
// throw new BackgroundError("Could not confirm payment", res.lastError)
// // throw TalerError.fromUncheckedDetail({
// // code: TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR,
// // when: new Date().toISOString(),
// // hint: `could not confirm payment`,
// // payResult: res,
// // });
onSuccess(res.transactionId);
return;
}
const fu = res.contractTerms.fulfillment_url;
if (fu) {
@ -169,16 +172,10 @@ export function useComponentState({
}
}
onSuccess(res.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setPayErrMsg(e);
}
}
}
const payHandler: ButtonHandler = {
onClick: payErrMsg ? undefined : doPayment,
error: payErrMsg,
onClick: pushAlertOnError(doPayment),
};
// (payStatus.status === PreparePayResultType.PaymentPossible)

View File

@ -24,10 +24,11 @@ import {
MerchantContractTerms as ContractTerms,
PreparePayResultType,
} from "@gnu-taler/taler-util";
import merchantIcon from "../../../static-dev/merchant-icon.jpeg";
import { createExample } from "../../test-utils.js";
import { BaseView } from "./views.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import beer from "../../../static-dev/beer.png";
import merchantIcon from "../../../static-dev/merchant-icon.jpeg";
import { nullFunction } from "../../mui/handlers.js";
import { BaseView } from "./views.js";
export default {
title: "payment",
@ -35,7 +36,7 @@ export default {
argTypes: {},
};
export const NoBalance = createExample(BaseView, {
export const NoBalance = tests.createExample(BaseView, {
status: "no-balance-for-currency",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),
@ -44,6 +45,7 @@ export const NoBalance = createExample(BaseView, {
uri: "",
payStatus: {
status: PreparePayResultType.InsufficientBalance,
balanceDetails: {} as any,
talerUri: "taler://pay/..",
noncePriv: "",
proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
@ -61,7 +63,7 @@ export const NoBalance = createExample(BaseView, {
},
});
export const NoEnoughBalance = createExample(BaseView, {
export const NoEnoughBalance = tests.createExample(BaseView, {
status: "no-enough-balance",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),
@ -74,6 +76,7 @@ export const NoEnoughBalance = createExample(BaseView, {
uri: "",
payStatus: {
status: PreparePayResultType.InsufficientBalance,
balanceDetails: {} as any,
talerUri: "taler://pay/..",
noncePriv: "",
proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
@ -91,7 +94,7 @@ export const NoEnoughBalance = createExample(BaseView, {
},
});
export const EnoughBalanceButRestricted = createExample(BaseView, {
export const EnoughBalanceButRestricted = tests.createExample(BaseView, {
status: "no-enough-balance",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),
@ -104,6 +107,7 @@ export const EnoughBalanceButRestricted = createExample(BaseView, {
uri: "",
payStatus: {
status: PreparePayResultType.InsufficientBalance,
balanceDetails: {} as any,
talerUri: "taler://pay/..",
noncePriv: "",
proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
@ -121,7 +125,7 @@ export const EnoughBalanceButRestricted = createExample(BaseView, {
},
});
export const PaymentPossible = createExample(BaseView, {
export const PaymentPossible = tests.createExample(BaseView, {
status: "ready",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),
@ -131,9 +135,7 @@ export const PaymentPossible = createExample(BaseView, {
value: 11,
},
payHandler: {
onClick: async () => {
null;
},
onClick: nullFunction,
},
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
@ -162,7 +164,7 @@ export const PaymentPossible = createExample(BaseView, {
},
});
export const PaymentPossibleWithFee = createExample(BaseView, {
export const PaymentPossibleWithFee = tests.createExample(BaseView, {
status: "ready",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),
@ -172,9 +174,7 @@ export const PaymentPossibleWithFee = createExample(BaseView, {
value: 11,
},
payHandler: {
onClick: async () => {
null;
},
onClick: nullFunction,
},
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
@ -200,7 +200,7 @@ export const PaymentPossibleWithFee = createExample(BaseView, {
},
});
export const TicketWithAProductList = createExample(BaseView, {
export const TicketWithAProductList = tests.createExample(BaseView, {
status: "ready",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),
@ -210,9 +210,7 @@ export const TicketWithAProductList = createExample(BaseView, {
value: 11,
},
payHandler: {
onClick: async () => {
null;
},
onClick: nullFunction,
},
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
@ -257,7 +255,7 @@ export const TicketWithAProductList = createExample(BaseView, {
},
});
export const TicketWithShipping = createExample(BaseView, {
export const TicketWithShipping = tests.createExample(BaseView, {
status: "ready",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),
@ -267,9 +265,7 @@ export const TicketWithShipping = createExample(BaseView, {
value: 11,
},
payHandler: {
onClick: async () => {
null;
},
onClick: nullFunction,
},
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
@ -309,7 +305,7 @@ export const TicketWithShipping = createExample(BaseView, {
},
});
export const AlreadyConfirmedByOther = createExample(BaseView, {
export const AlreadyConfirmedByOther = tests.createExample(BaseView, {
status: "confirmed",
error: undefined,
amount: Amounts.parseOrThrow("USD:10"),

View File

@ -31,7 +31,8 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js";
import { mountHook, nullFunction } from "../../test-utils.js";
import { ErrorAlert, useAlertContext } from "../../context/alert.js";
import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js";
@ -385,8 +386,12 @@ describe("Payment CTA states", () => {
} as ConfirmPayResult);
const hookBehavior = await tests.hookBehaveLikeThis(
useComponentState,
props,
() => {
const state = useComponentState(props);
// const { alerts } = useAlertContext();
return { ...state, alerts: {} };
},
{},
[
({ status, error }) => {
expect(status).equals("loading");
@ -400,22 +405,21 @@ describe("Payment CTA states", () => {
if (state.payHandler.onClick === undefined) expect.fail();
state.payHandler.onClick();
},
(state) => {
if (state.status !== "ready") expect.fail();
expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
expect(state.payHandler.onClick).undefined;
if (state.payHandler.error === undefined) expect.fail();
//FIXME: error message here is bad
expect(state.payHandler.error.errorDetail.hint).eq(
"could not confirm payment",
);
expect(state.payHandler.error.errorDetail.payResult).deep.equal({
type: ConfirmPayResultType.Pending,
lastError: { code: 1 },
});
},
// (state) => {
// if (state.status !== "ready") expect.fail();
// expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
// expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// // FIXME: check that the error is pushed to the alertContext
// // expect(state.alerts.length).eq(1);
// // const alert = state.alerts[0]
// // if (alert.type !== "error") expect.fail();
// // expect(alert.cause.errorDetail.payResult).deep.equal({
// // type: ConfirmPayResultType.Pending,
// // lastError: { code: 1 },
// // });
// },
],
TestingContext,
);

View File

@ -16,7 +16,7 @@
import { parseRecoveryUri } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Alert } from "../../context/alert.js";
import { useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { Props, State } from "./index.js";
@ -27,6 +27,7 @@ export function useComponentState({
onSuccess,
}: Props): State {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const { i18n } = useTranslationContext();
if (!talerRecoveryUri) {
return {
@ -67,10 +68,10 @@ export function useComponentState({
status: "ready",
accept: {
onClick: recoverBackup,
onClick: pushAlertOnError(recoverBackup),
},
cancel: {
onClick: onCancel,
onClick: pushAlertOnError(onCancel),
},
error: undefined,
};

View File

@ -20,7 +20,7 @@
*/
import { Amounts } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js";
export default {

View File

@ -17,7 +17,7 @@
import { Amounts, NotificationType } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -31,6 +31,7 @@ export function useComponentState({
const api = useBackendContext();
const { i18n } = useTranslationContext();
const [ignored, setIgnored] = useState(false);
const { pushAlertOnError } = useAlertContext();
const info = useAsyncAsHook(async () => {
if (!talerRefundUri) throw Error("ERROR_NO-URI-FOR-REFUND");
@ -108,10 +109,10 @@ export function useComponentState({
...baseInfo,
orderId: info.response.refund.info.orderId,
accept: {
onClick: doAccept,
onClick: pushAlertOnError(doAccept),
},
ignore: {
onClick: doIgnore,
onClick: pushAlertOnError(doIgnore),
},
cancel,
};

View File

@ -21,13 +21,13 @@
import { Amounts } from "@gnu-taler/taler-util";
import beer from "../../../static-dev/beer.png";
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { IgnoredView, InProgressView, ReadyView } from "./views.js";
export default {
title: "refund",
};
export const InProgress = createExample(InProgressView, {
export const InProgress = tests.createExample(InProgressView, {
status: "in-progress",
error: undefined,
amount: Amounts.parseOrThrow("USD:1"),
@ -37,7 +37,7 @@ export const InProgress = createExample(InProgressView, {
products: undefined,
});
export const Ready = createExample(ReadyView, {
export const Ready = tests.createExample(ReadyView, {
status: "ready",
error: undefined,
accept: {},
@ -51,7 +51,7 @@ export const Ready = createExample(ReadyView, {
orderId: "abcdef",
});
export const WithAProductList = createExample(ReadyView, {
export const WithAProductList = tests.createExample(ReadyView, {
status: "ready",
error: undefined,
accept: {},
@ -75,7 +75,7 @@ export const WithAProductList = createExample(ReadyView, {
orderId: "abcdef",
});
export const Ignored = createExample(IgnoredView, {
export const Ignored = tests.createExample(IgnoredView, {
status: "ignored",
error: undefined,
merchantName: "the merchant",

View File

@ -27,11 +27,8 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js";
import {
createWalletApiMock,
mountHook,
nullFunction,
} from "../../test-utils.js";
import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js";
describe("Refund CTA states", () => {
@ -57,7 +54,7 @@ describe("Refund CTA states", () => {
if (!error) expect.fail();
// if (!error.hasError) expect.fail();
// if (error.operational) expect.fail();
expect(error.cause?.message).eq("ERROR_NO-URI-FOR-REFUND");
expect(error.description).eq("ERROR_NO-URI-FOR-REFUND");
},
],
TestingContext,

View File

@ -16,7 +16,7 @@
import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -29,6 +29,7 @@ export function useComponentState({
}: Props): State {
const api = useBackendContext();
const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const tipInfo = useAsyncAsHook(async () => {
if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP");
const tip = await api.wallet.call(WalletApiOperation.PrepareTip, {
@ -77,7 +78,7 @@ export function useComponentState({
amount: Amounts.parseOrThrow(tip.tipAmountEffective),
error: undefined,
cancel: {
onClick: onCancel,
onClick: pushAlertOnError(onCancel),
},
};
@ -92,7 +93,7 @@ export function useComponentState({
status: "ready",
...baseInfo,
accept: {
onClick: doAccept,
onClick: pushAlertOnError(doAccept),
},
};
}

View File

@ -20,14 +20,14 @@
*/
import { Amounts } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { AcceptedView, ReadyView } from "./views.js";
export default {
title: "tip",
};
export const Accepted = createExample(AcceptedView, {
export const Accepted = tests.createExample(AcceptedView, {
status: "accepted",
error: undefined,
amount: Amounts.parseOrThrow("EUR:1"),
@ -35,7 +35,7 @@ export const Accepted = createExample(AcceptedView, {
merchantBaseUrl: "",
});
export const Ready = createExample(ReadyView, {
export const Ready = tests.createExample(ReadyView, {
status: "ready",
error: undefined,
amount: Amounts.parseOrThrow("EUR:1"),

View File

@ -23,7 +23,8 @@ import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js";
import { createWalletApiMock, nullFunction } from "../../test-utils.js";
import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { Props } from "./index.js";
import { useComponentState } from "./state.js";
@ -48,7 +49,7 @@ describe("Tip CTA states", () => {
({ status, error }) => {
expect(status).equals("error");
if (!error) expect.fail();
expect(error.cause?.message).eq("ERROR_NO-URI-FOR-TIP");
expect(error.description).eq("ERROR_NO-URI-FOR-TIP");
},
],
TestingContext,

View File

@ -54,7 +54,6 @@ export namespace State {
subject: TextFieldHandler;
expiration: TextFieldHandler;
error: undefined;
operationError?: TalerErrorDetail;
}
}

View File

@ -22,7 +22,7 @@ import {
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { isFuture, parse } from "date-fns";
import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -34,16 +34,13 @@ export function useComponentState({
onSuccess,
}: Props): State {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const amount = Amounts.parseOrThrow(amountStr);
const { i18n } = useTranslationContext();
const [subject, setSubject] = useState<string | undefined>();
const [timestamp, setTimestamp] = useState<string | undefined>();
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>(undefined);
const hook = useAsyncAsHook(async () => {
const resp = await api.wallet.call(
WalletApiOperation.PreparePeerPushPayment,
@ -104,7 +101,6 @@ export function useComponentState({
async function accept(): Promise<void> {
if (!subject || !purse_expiration) return;
try {
const resp = await api.wallet.call(
WalletApiOperation.InitiatePeerPushPayment,
{
@ -116,13 +112,6 @@ export function useComponentState({
},
);
onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);
}
console.error(e);
throw Error("error trying to accept");
}
}
const unableToCreate =
@ -131,7 +120,7 @@ export function useComponentState({
return {
status: "ready",
cancel: {
onClick: onClose,
onClick: pushAlertOnError(onClose),
},
subject: {
error:
@ -141,21 +130,20 @@ export function useComponentState({
? "Can't be empty"
: undefined,
value: subject ?? "",
onInput: async (e) => setSubject(e),
onInput: pushAlertOnError(async (e) => setSubject(e)),
},
expiration: {
error: timestampError,
value: timestamp === undefined ? "" : timestamp,
onInput: async (e) => {
onInput: pushAlertOnError(async (e) => {
setTimestamp(e);
},
}),
},
create: {
onClick: unableToCreate ? undefined : accept,
onClick: unableToCreate ? undefined : pushAlertOnError(accept),
},
debitAmount,
toBeReceived,
error: undefined,
operationError,
};
}

View File

@ -19,14 +19,15 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { nullFunction } from "../../mui/handlers.js";
import { ReadyView } from "./views.js";
export default {
title: "transfer create",
};
export const Ready = createExample(ReadyView, {
export const Ready = tests.createExample(ReadyView, {
debitAmount: {
currency: "ARS",
value: 1,
@ -44,8 +45,6 @@ export const Ready = createExample(ReadyView, {
},
subject: {
value: "the subject",
onInput: async () => {
null;
},
onInput: nullFunction,
},
});

View File

@ -32,8 +32,6 @@ export function ReadyView({
toBeReceived,
debitAmount,
create,
operationError,
cancel,
}: State.Ready): VNode {
const { i18n } = useTranslationContext();
@ -65,12 +63,6 @@ export function ReadyView({
<SubTitle>
<i18n.Translate>Digital cash transfer</i18n.Translate>
</SubTitle>
{operationError && (
<ErrorTalerOperation
title={i18n.str`Could not finish the transfer creation`}
error={operationError}
/>
)}
<section style={{ textAlign: "left" }}>
<p>
<TextField

View File

@ -57,7 +57,6 @@ export namespace State {
expiration: AbsoluteTime | undefined;
error: undefined;
accept: ButtonHandler;
operationError?: TalerErrorDetail;
}
}

View File

@ -22,7 +22,7 @@ import {
} from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -34,15 +34,13 @@ export function useComponentState({
onSuccess,
}: Props): State {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(async () => {
return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, {
talerUri: talerPayPushUri,
});
}, []);
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>(undefined);
if (!hook) {
return {
@ -74,7 +72,6 @@ export function useComponentState({
contractTerms?.purse_expiration;
async function accept(): Promise<void> {
try {
const resp = await api.wallet.call(
WalletApiOperation.AcceptPeerPushPayment,
{
@ -82,26 +79,18 @@ export function useComponentState({
},
);
onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);
}
console.error(e);
throw Error("error trying to accept");
}
}
return {
status: "ready",
amount: Amounts.parseOrThrow(amount),
error: undefined,
accept: {
onClick: accept,
onClick: pushAlertOnError(accept),
},
summary,
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
cancel: {
onClick: onClose,
onClick: pushAlertOnError(onClose),
},
operationError,
};
}

View File

@ -19,14 +19,14 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js";
export default {
title: "transfer pickup",
};
export const Ready = createExample(ReadyView, {
export const Ready = tests.createExample(ReadyView, {
amount: {
currency: "ARS",
value: 1,

View File

@ -30,8 +30,6 @@ export function ReadyView({
summary,
expiration,
amount,
cancel,
operationError,
}: State.Ready): VNode {
const { i18n } = useTranslationContext();
return (
@ -40,12 +38,6 @@ export function ReadyView({
<SubTitle>
<i18n.Translate>Digital cash transfer</i18n.Translate>
</SubTitle>
{operationError && (
<ErrorTalerOperation
title={i18n.str`Could not finish the pickup operation`}
error={operationError}
/>
)}
<section style={{ textAlign: "left" }}>
<Part title={i18n.str`Subject`} text={<div>{summary}</div>} />
<Part title={i18n.str`Amount`} text={<Amount value={amount} />} />

View File

@ -23,7 +23,7 @@ import {
} from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -205,6 +205,7 @@ function exchangeSelectionState(
return () => {
const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const [ageRestricted, setAgeRestricted] = useState(0);
const currentExchange = selectedExchange.selected;
const tosNeedToBeAccepted =
@ -299,7 +300,9 @@ function exchangeSelectionState(
? {
list: ageRestrictionOptions,
value: String(ageRestricted),
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)),
onChange: pushAlertOnError(async (v: string) =>
setAgeRestricted(parseInt(v, 10)),
),
}
: undefined;
@ -317,7 +320,7 @@ function exchangeSelectionState(
onClick:
doingWithdraw || tosNeedToBeAccepted
? undefined
: doWithdrawAndCheckError,
: pushAlertOnError(doWithdrawAndCheckError),
error: withdrawError,
},
onTosUpdate,

View File

@ -20,7 +20,8 @@
*/
import { ExchangeListItem } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { nullFunction } from "../../mui/handlers.js";
// import { TermsState } from "../../utils/index.js";
import { SuccessView } from "./views.js";
@ -28,28 +29,6 @@ export default {
title: "withdraw",
};
const exchangeList = {
"exchange.demo.taler.net": "http://exchange.demo.taler.net (USD)",
"exchange.test.taler.net": "http://exchange.test.taler.net (KUDOS)",
};
const nullHandler = {
onClick: async (): Promise<void> => {
null;
},
};
// const normalTosState = {
// terms: {
// status: "accepted",
// version: "",
// } as TermsState,
// onAccept: () => null,
// onReview: () => null,
// reviewed: false,
// reviewing: false,
// };
const ageRestrictionOptions: Record<string, string> = "6:12:18"
.split(":")
.reduce((p, c) => ({ ...p, [c]: `under ${c}` }), {});
@ -61,7 +40,7 @@ const ageRestrictionSelectField = {
value: "0",
};
export const TermsOfServiceNotYetLoaded = createExample(SuccessView, {
export const TermsOfServiceNotYetLoaded = tests.createExample(SuccessView, {
error: undefined,
status: "success",
chosenAmount: {
@ -69,7 +48,7 @@ export const TermsOfServiceNotYetLoaded = createExample(SuccessView, {
value: 2,
fraction: 10000000,
},
doWithdrawal: nullHandler,
doWithdrawal: { onClick: nullFunction },
currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {},
@ -87,7 +66,7 @@ export const TermsOfServiceNotYetLoaded = createExample(SuccessView, {
},
});
export const WithSomeFee = createExample(SuccessView, {
export const WithSomeFee = tests.createExample(SuccessView, {
error: undefined,
status: "success",
chosenAmount: {
@ -95,7 +74,7 @@ export const WithSomeFee = createExample(SuccessView, {
value: 2,
fraction: 10000000,
},
doWithdrawal: nullHandler,
doWithdrawal: { onClick: nullFunction },
currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {},
@ -113,7 +92,7 @@ export const WithSomeFee = createExample(SuccessView, {
doSelectExchange: {},
});
export const WithoutFee = createExample(SuccessView, {
export const WithoutFee = tests.createExample(SuccessView, {
error: undefined,
status: "success",
chosenAmount: {
@ -121,7 +100,7 @@ export const WithoutFee = createExample(SuccessView, {
value: 2,
fraction: 0,
},
doWithdrawal: nullHandler,
doWithdrawal: { onClick: nullFunction },
currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {},
@ -139,7 +118,7 @@ export const WithoutFee = createExample(SuccessView, {
},
});
export const EditExchangeUntouched = createExample(SuccessView, {
export const EditExchangeUntouched = tests.createExample(SuccessView, {
error: undefined,
status: "success",
chosenAmount: {
@ -147,7 +126,7 @@ export const EditExchangeUntouched = createExample(SuccessView, {
value: 2,
fraction: 10000000,
},
doWithdrawal: nullHandler,
doWithdrawal: { onClick: nullFunction },
currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {},
@ -165,7 +144,7 @@ export const EditExchangeUntouched = createExample(SuccessView, {
},
});
export const EditExchangeModified = createExample(SuccessView, {
export const EditExchangeModified = tests.createExample(SuccessView, {
error: undefined,
status: "success",
chosenAmount: {
@ -173,7 +152,7 @@ export const EditExchangeModified = createExample(SuccessView, {
value: 2,
fraction: 10000000,
},
doWithdrawal: nullHandler,
doWithdrawal: { onClick: nullFunction },
currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {},
@ -191,7 +170,7 @@ export const EditExchangeModified = createExample(SuccessView, {
},
});
export const WithAgeRestriction = createExample(SuccessView, {
export const WithAgeRestriction = tests.createExample(SuccessView, {
error: undefined,
status: "success",
ageRestriction: ageRestrictionSelectField,
@ -201,7 +180,7 @@ export const WithAgeRestriction = createExample(SuccessView, {
fraction: 10000000,
},
doSelectExchange: {},
doWithdrawal: nullHandler,
doWithdrawal: { onClick: nullFunction },
currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {},

View File

@ -28,7 +28,6 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js";
import { mountHook } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentStateFromURI } from "./state.js";
@ -88,7 +87,7 @@ describe("Withdraw CTA states", () => {
if (!error) expect.fail();
// if (!error.hasError) expect.fail();
// if (error.operational) expect.fail();
expect(error.cause?.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL");
expect(error.description).eq("ERROR_NO-URI-FOR-WITHDRAWAL");
},
],
TestingContext,

View File

@ -18,7 +18,6 @@ import { ExchangeTosStatus } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { Amount } from "../../components/Amount.js";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { Part } from "../../components/Part.js";
import { QR } from "../../components/QR.js";
import { SelectList } from "../../components/SelectList.js";
@ -36,13 +35,6 @@ export function SuccessView(state: State.Success): VNode {
state.currentExchange.tosStatus === ExchangeTosStatus.Accepted;
return (
<Fragment>
{state.doWithdrawal.error && (
<ErrorTalerOperation
title={i18n.str`Could not finish the withdrawal operation`}
error={state.doWithdrawal.error.errorDetail}
/>
)}
<section style={{ textAlign: "left" }}>
<Part
title={

View File

@ -16,7 +16,7 @@
import { TalerErrorDetail } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useMemo, useState } from "preact/hooks";
import { WalletError } from "../wxApi.js";
import { BackgroundError } from "../wxApi.js";
export interface HookOk<T> {
hasError: false;
@ -74,12 +74,12 @@ export function useAsyncAsHook<T>(
message: e.message,
details: e.errorDetail,
});
} else if (e instanceof WalletError) {
} else if (e instanceof BackgroundError) {
setHookResponse({
hasError: true,
type: "taler",
message: e.message,
details: e.errorDetail.errorDetail,
details: e.errorDetail,
});
} else if (e instanceof Error) {
setHookResponse({

View File

@ -14,23 +14,41 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { ToggleHandler } from "../mui/handlers.js";
import { platform } from "../platform/foreground.js";
export function useAutoOpenPermissions(): ToggleHandler {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const [enabled, setEnabled] = useState(false);
const [error, setError] = useState<TalerError | undefined>();
const toggle = async (): Promise<void> => {
return handleAutoOpenPerm(enabled, setEnabled, api.background).catch(
(e) => {
setError(TalerError.fromException(e));
},
);
};
async function handleAutoOpenPerm(): Promise<void> {
if (!enabled) {
// We set permissions here, since apparently FF wants this to be done
// as the result of an input event ...
let granted: boolean;
try {
granted = await platform.getPermissionsApi().requestHostPermissions();
} catch (lastError) {
setEnabled(false);
throw lastError;
}
const res = await api.background.call("toggleHeaderListener", granted);
setEnabled(res.newValue);
} else {
try {
await api.background
.call("toggleHeaderListener", false)
.then((r) => setEnabled(r.newValue));
} catch (e) {
console.log(e);
}
}
return;
}
useEffect(() => {
async function getValue(): Promise<void> {
@ -42,40 +60,11 @@ export function useAutoOpenPermissions(): ToggleHandler {
}
getValue();
}, []);
return {
value: enabled,
button: {
onClick: toggle,
error,
onClick: pushAlertOnError(handleAutoOpenPerm),
},
};
}
async function handleAutoOpenPerm(
isEnabled: boolean,
onChange: (value: boolean) => void,
background: ReturnType<typeof useBackendContext>["background"],
): Promise<void> {
if (!isEnabled) {
// We set permissions here, since apparently FF wants this to be done
// as the result of an input event ...
let granted: boolean;
try {
granted = await platform.getPermissionsApi().requestHostPermissions();
} catch (lastError) {
onChange(false);
throw lastError;
}
const res = await background.call("toggleHeaderListener", granted);
onChange(res.newValue);
} else {
try {
await background
.call("toggleHeaderListener", false)
.then((r) => onChange(r.newValue));
} catch (e) {
console.log(e);
}
}
return;
}

View File

@ -14,24 +14,42 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { ToggleHandler } from "../mui/handlers.js";
import { platform } from "../platform/foreground.js";
export function useClipboardPermissions(): ToggleHandler {
const [enabled, setEnabled] = useState(false);
const [error, setError] = useState<TalerError | undefined>();
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const toggle = async (): Promise<void> => {
return handleClipboardPerm(enabled, setEnabled, api.background).catch(
(e) => {
setError(TalerError.fromException(e));
},
);
};
async function handleClipboardPerm(): Promise<void> {
if (!enabled) {
// We set permissions here, since apparently FF wants this to be done
// as the result of an input event ...
let granted: boolean;
try {
granted = await platform
.getPermissionsApi()
.requestClipboardPermissions();
} catch (lastError) {
setEnabled(false);
throw lastError;
}
setEnabled(granted);
} else {
try {
await api.background
.call("toggleHeaderListener", false)
.then((r) => setEnabled(r.newValue));
} catch (e) {
console.log(e);
}
}
return;
}
useEffect(() => {
async function getValue(): Promise<void> {
@ -47,38 +65,7 @@ export function useClipboardPermissions(): ToggleHandler {
return {
value: enabled,
button: {
onClick: toggle,
error,
onClick: pushAlertOnError(handleClipboardPerm),
},
};
}
async function handleClipboardPerm(
isEnabled: boolean,
onChange: (value: boolean) => void,
background: ReturnType<typeof useBackendContext>["background"],
): Promise<void> {
if (!isEnabled) {
// We set permissions here, since apparently FF wants this to be done
// as the result of an input event ...
let granted: boolean;
try {
granted = await platform
.getPermissionsApi()
.requestClipboardPermissions();
} catch (lastError) {
onChange(false);
throw lastError;
}
onChange(granted);
} else {
try {
await background
.call("toggleHeaderListener", false)
.then((r) => onChange(r.newValue));
} catch (e) {
console.log(e);
}
}
return;
}

View File

@ -16,6 +16,7 @@
import { ExchangeListItem } from "@gnu-taler/taler-util";
import { useState } from "preact/hooks";
import { useAlertContext } from "../context/alert.js";
import { ButtonHandler } from "../mui/handlers.js";
type State = State.Ready | State.NoExchange | State.Selecting;
@ -59,6 +60,7 @@ export function useSelectedExchange({
const [selectedExchange, setSelectedExchange] = useState<string | undefined>(
undefined,
);
const { pushAlertOnError } = useAlertContext();
if (!list.length) {
return {
@ -105,7 +107,7 @@ export function useSelectedExchange({
return {
status: "ready",
doSelect: {
onClick: async () => setIsSelecting(true),
onClick: pushAlertOnError(async () => setIsSelecting(true)),
},
selected: found,
};
@ -118,7 +120,7 @@ export function useSelectedExchange({
return {
status: "ready",
doSelect: {
onClick: async () => setIsSelecting(true),
onClick: pushAlertOnError(async () => setIsSelecting(true)),
},
selected: found,
};
@ -127,7 +129,7 @@ export function useSelectedExchange({
return {
status: "ready",
doSelect: {
onClick: async () => setIsSelecting(true),
onClick: pushAlertOnError(async () => setIsSelecting(true)),
},
selected: listCurrency[0],
};

View File

@ -13,11 +13,11 @@
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { useTalerActionURL } from "./useTalerActionURL.js";
import { mountHook } from "../test-utils.js";
import { IoCProviderForTesting } from "../context/iocContext.js";
import { h, VNode } from "preact";
import { expect } from "chai";
import { h, VNode } from "preact";
import { IoCProviderForTesting } from "../context/iocContext.js";
import { useTalerActionURL } from "./useTalerActionURL.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
describe("useTalerActionURL hook", () => {
it("should be set url to undefined when dismiss", async () => {
@ -31,32 +31,28 @@ describe("useTalerActionURL hook", () => {
});
};
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
mountHook(useTalerActionURL, ctx);
{
const [url] = pullLastResultOrThrow();
const hookBehavior = await tests.hookBehaveLikeThis(
useTalerActionURL,
{},
[
([url]) => {
expect(url).undefined;
}
expect(await waitForStateUpdate()).true;
{
const [url, setDismissed] = pullLastResultOrThrow();
},
([url, setDismissed]) => {
expect(url).deep.equals({
location: "clipboard",
uri: "qwe",
});
setDismissed(true);
}
expect(await waitForStateUpdate()).true;
{
const [url] = pullLastResultOrThrow();
},
([url]) => {
if (url !== undefined) throw Error("invalid");
expect(url).undefined;
}
await assertNoPendingUpdate();
},
],
ctx,
);
expect(hookBehavior).deep.equal({ result: "ok" });
});
});

View File

@ -14,21 +14,28 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { useState, useEffect } from "preact/hooks";
import { ToggleHandler } from "../mui/handlers.js";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { useBackendContext } from "../context/backend.js";
export function useWalletDevMode(): ToggleHandler {
const [enabled, setEnabled] = useState<undefined | boolean>(undefined);
const [error, setError] = useState<TalerError | undefined>();
const api = useBackendContext();
type Result = {
value: boolean | undefined;
toggle: () => Promise<void>;
};
const toggle = async (): Promise<void> => {
return handleOpen(enabled, setEnabled, api).catch((e) => {
setError(TalerError.fromException(e));
export function useWalletDevMode(): Result {
const [enabled, setEnabled] = useState<undefined | boolean>(undefined);
const api = useBackendContext();
// const { pushAlertOnError } = useAlertContext();
async function handleOpen(): Promise<void> {
const nextValue = !enabled;
await api.wallet.call(WalletApiOperation.SetDevMode, {
devModeEnabled: nextValue,
});
};
setEnabled(nextValue);
return;
}
useEffect(() => {
async function getValue(): Promise<void> {
@ -37,24 +44,9 @@ export function useWalletDevMode(): ToggleHandler {
}
getValue();
}, []);
return {
value: enabled,
button: {
onClick: enabled === undefined ? undefined : toggle,
error,
},
toggle: handleOpen,
};
}
async function handleOpen(
currentValue: undefined | boolean,
onChange: (value: boolean) => void,
api: ReturnType<typeof useBackendContext>,
): Promise<void> {
const nextValue = !currentValue;
await api.wallet.call(WalletApiOperation.SetDevMode, {
devModeEnabled: nextValue,
});
onChange(nextValue);
return;
}

View File

@ -14,23 +14,51 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { AmountJson } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core";
export interface TextFieldHandler {
onInput?: (value: string) => Promise<void>;
onInput?: SafeHandler<string>;
value: string;
error?: string;
}
export interface AmountFieldHandler {
onInput?: (value: AmountJson) => Promise<void>;
onInput?: SafeHandler<AmountJson>;
value: AmountJson;
error?: string;
}
declare const __safe_handler: unique symbol;
export type SafeHandler<T> = {
<Req extends T>(req: Req): Promise<void>;
(): Promise<void>;
[__safe_handler]: true;
};
export function withSafe<T>(
handler: (p: T) => Promise<void>,
onError: (e: Error) => void,
): SafeHandler<T> {
const sh = async function (p: T): Promise<void> {
try {
await handler(p);
} catch (e) {
if (e instanceof Error) {
onError(e);
} else {
onError(new Error(String(e)));
}
}
};
return sh as SafeHandler<T>;
}
export const nullFunction = async function (): Promise<void> {
//do nothing
} as SafeHandler<void>;
export interface ButtonHandler {
onClick?: () => Promise<void>;
error?: TalerError;
onClick?: SafeHandler<void>;
// error?: TalerError;
}
export interface ToggleHandler {
@ -39,7 +67,7 @@ export interface ToggleHandler {
}
export interface SelectFieldHandler {
onChange?: (value: string) => Promise<void>;
onChange?: SafeHandler<string>;
error?: string;
value: string;
isDirty?: boolean;

View File

@ -23,10 +23,10 @@
import { createHashHistory } from "history";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import Router, { route, Route } from "preact-router";
import { Match } from "preact-router/match";
import { useEffect, useState } from "preact/hooks";
import PendingTransactions from "../components/PendingTransactions.js";
import { PopupBox } from "../components/styled/index.js";
import { AlertProvider } from "../context/alert.js";
import { DevContextProvider } from "../context/devContext.js";
import { IoCProviderForRuntime } from "../context/iocContext.js";
import {
@ -34,7 +34,7 @@ import {
useTranslationContext,
} from "../context/translation.js";
import { useTalerActionURL } from "../hooks/useTalerActionURL.js";
import { PopupNavBarOptions, Pages, PopupNavBar } from "../NavigationBar.js";
import { Pages, PopupNavBar, PopupNavBarOptions } from "../NavigationBar.js";
import { platform } from "../platform/foreground.js";
import { BackupPage } from "../wallet/BackupPage.js";
import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js";
@ -219,7 +219,9 @@ function PopupTemplate({
<PendingTransactions goToTransaction={goToTransaction} />
) : undefined}
<PopupNavBar path={path} />
<PopupBox>{children}</PopupBox>
<PopupBox>
<AlertProvider>{children}</AlertProvider>
</PopupBox>
</Fragment>
);
}

View File

@ -19,19 +19,19 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { BalanceView as TestedComponent } from "./BalancePage.js";
export default {
title: "balance",
};
export const EmptyBalance = createExample(TestedComponent, {
export const EmptyBalance = tests.createExample(TestedComponent, {
balances: [],
goToWalletManualWithdraw: {},
});
export const SomeCoins = createExample(TestedComponent, {
export const SomeCoins = tests.createExample(TestedComponent, {
balances: [
{
available: "USD:10.5",
@ -45,7 +45,7 @@ export const SomeCoins = createExample(TestedComponent, {
goToWalletManualWithdraw: {},
});
export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
export const SomeCoinsInTreeCurrencies = tests.createExample(TestedComponent, {
balances: [
{
available: "EUR:1",
@ -73,7 +73,7 @@ export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
addAction: {},
});
export const NoCoinsInTreeCurrencies = createExample(TestedComponent, {
export const NoCoinsInTreeCurrencies = tests.createExample(TestedComponent, {
balances: [
{
available: "EUR:3",
@ -101,7 +101,7 @@ export const NoCoinsInTreeCurrencies = createExample(TestedComponent, {
addAction: {},
});
export const SomeCoinsInFiveCurrencies = createExample(TestedComponent, {
export const SomeCoinsInFiveCurrencies = tests.createExample(TestedComponent, {
balances: [
{
available: "USD:0",

View File

@ -22,7 +22,11 @@ import { BalanceTable } from "../components/BalanceTable.js";
import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js";
import { MultiActionButton } from "../components/MultiActionButton.js";
import { alertFromError, ErrorAlert } from "../context/alert.js";
import {
alertFromError,
ErrorAlert,
useAlertContext,
} from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
@ -75,6 +79,7 @@ function useComponentState({
}: Props): State {
const api = useBackendContext();
const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const [addingAction, setAddingAction] = useState(false);
const state = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.GetBalances, {}),
@ -104,7 +109,7 @@ function useComponentState({
status: "action",
error: undefined,
cancel: {
onClick: async () => setAddingAction(false),
onClick: pushAlertOnError(async () => setAddingAction(false)),
},
};
}
@ -113,10 +118,10 @@ function useComponentState({
error: undefined,
balances: state.response.balances,
addAction: {
onClick: async () => setAddingAction(true),
onClick: pushAlertOnError(async () => setAddingAction(true)),
},
goToWalletManualWithdraw: {
onClick: goToWalletManualWithdraw,
onClick: pushAlertOnError(goToWalletManualWithdraw),
},
goToWalletDeposit,
goToWalletHistory,

View File

@ -19,33 +19,33 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { TalerActionFound as TestedComponent } from "./TalerActionFound.js";
export default {
title: "TalerActionFound",
};
export const PayAction = createExample(TestedComponent, {
export const PayAction = tests.createExample(TestedComponent, {
url: "taler://pay/something",
});
export const WithdrawalAction = createExample(TestedComponent, {
export const WithdrawalAction = tests.createExample(TestedComponent, {
url: "taler://withdraw/something",
});
export const TipAction = createExample(TestedComponent, {
export const TipAction = tests.createExample(TestedComponent, {
url: "taler://tip/something",
});
export const NotifyAction = createExample(TestedComponent, {
export const NotifyAction = tests.createExample(TestedComponent, {
url: "taler://notify-reserve/something",
});
export const RefundAction = createExample(TestedComponent, {
export const RefundAction = tests.createExample(TestedComponent, {
url: "taler://refund/something",
});
export const InvalidAction = createExample(TestedComponent, {
export const InvalidAction = tests.createExample(TestedComponent, {
url: "taler://something/asd",
});

View File

@ -20,15 +20,17 @@
*/
import { setupI18n } from "@gnu-taler/taler-util";
import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser";
import { setupPlatform } from "./platform/foreground.js";
import chromeAPI from "./platform/chrome.js";
import { renderNodeOrBrowser } from "./test-utils.js";
import { setupPlatform } from "./platform/foreground.js";
import * as components from "./components/index.stories.js";
import * as cta from "./cta/index.stories.js";
import * as mui from "./mui/index.stories.js";
import * as popup from "./popup/index.stories.js";
import * as wallet from "./wallet/index.stories.js";
import { renderNodeOrBrowser } from "./test-utils.js";
import { h, VNode } from "preact";
import { AlertProvider } from "./context/alert.js";
setupI18n("en", { en: {} });
setupPlatform(chromeAPI);
@ -41,10 +43,15 @@ describe("All the examples:", () => {
describe(`Component ${component.name}:`, () => {
component.examples.forEach((example) => {
it(`should render example: ${example.name}`, () => {
renderNodeOrBrowser(
example.render.component,
example.render.props,
);
function C(): VNode {
const B = h(example.render.component, example.render.props);
//FIXME:
//some components push the alter in the UI function
//that's not correct, should be moved into the sate function
// until then, we ran the tests with the alert provider
return h(AlertProvider, { children: B }, B);
}
renderNodeOrBrowser(C, {});
});
});
});

View File

@ -31,8 +31,10 @@ import {
VNode,
} from "preact";
import { render as renderToString } from "preact-render-to-string";
import { AlertProvider } from "./context/alert.js";
import { BackendProvider } from "./context/backend.js";
import { TranslationProvider } from "./context/translation.js";
import { nullFunction } from "./mui/handlers.js";
import { BackgroundApiClient, wxApi } from "./wxApi.js";
// When doing tests we want the requestAnimationFrame to be as fast as possible.
@ -218,7 +220,7 @@ export function mountHook<T extends object>(
};
}
export const nullFunction: any = () => null;
// export const nullFunction: any = () => null;
interface MockHandler {
addWalletCallResponse<Op extends WalletCoreOpKeys>(
@ -365,6 +367,7 @@ export function createWalletApiMock(): {
children: ComponentChildren;
}): VNode {
let children = _cs;
children = create(AlertProvider, { children }, children);
children = create(TranslationProvider, { children }, children);
return create(
BackendProvider,

View File

@ -53,7 +53,7 @@ export namespace State {
export interface ConfirmProvider {
status: "confirm-provider";
error: undefined | TalerErrorDetail;
error: undefined;
url: string;
provider: SyncTermsOfServiceResponse;
tos: ToggleHandler;

View File

@ -14,16 +14,13 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import {
canonicalizeBaseUrl,
Codec,
TalerErrorDetail,
} from "@gnu-taler/taler-util";
import { canonicalizeBaseUrl, Codec } from "@gnu-taler/taler-util";
import {
codecForSyncTermsOfServiceResponse,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { assertUnreachable } from "../../utils/index.js";
import { Props, State } from "./index.js";
@ -152,17 +149,15 @@ export function useComponentState({
const [url, setHost] = useState<string | undefined>();
const [name, setName] = useState<string | undefined>();
const [tos, setTos] = useState(false);
const { pushAlertOnError } = useAlertContext();
const urlState = useUrlState(
url,
"config",
codecForSyncTermsOfServiceResponse(),
);
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>();
const [showConfirm, setShowConfirm] = useState(false);
async function addBackupProvider() {
async function addBackupProvider(): Promise<void> {
if (!url || !name) return;
const resp = await api.wallet.call(WalletApiOperation.AddBackupProvider, {
@ -178,8 +173,6 @@ export function useComponentState({
} else {
return onComplete(url);
}
case "error":
return setOperationError(resp.error);
case "ok":
return onComplete(url);
default:
@ -190,18 +183,18 @@ export function useComponentState({
if (showConfirm && urlState && urlState.status === "ok") {
return {
status: "confirm-provider",
error: operationError,
error: undefined,
onAccept: {
onClick: !tos ? undefined : addBackupProvider,
onClick: !tos ? undefined : pushAlertOnError(addBackupProvider),
},
onCancel: {
onClick: onBack,
onClick: pushAlertOnError(onBack),
},
provider: urlState.result,
tos: {
value: tos,
button: {
onClick: async () => setTos(!tos),
onClick: pushAlertOnError(async () => setTos(!tos)),
},
},
url: url ?? "",
@ -213,25 +206,25 @@ export function useComponentState({
error: undefined,
name: {
value: name || "",
onInput: async (e) => setName(e),
onInput: pushAlertOnError(async (e) => setName(e)),
error:
name === undefined ? undefined : !name ? "Can't be empty" : undefined,
},
onCancel: {
onClick: onBack,
onClick: pushAlertOnError(onBack),
},
onConfirm: {
onClick:
!urlState || urlState.status !== "ok" || !name
? undefined
: async () => {
: pushAlertOnError(async () => {
setShowConfirm(true);
},
}),
},
urlOk: urlState?.status === "ok",
url: {
value: url || "",
onInput: async (e) => setHost(e),
onInput: pushAlertOnError(async (e) => setHost(e)),
error: errorString(urlState),
},
};

View File

@ -19,14 +19,14 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ConfirmProviderView, SelectProviderView } from "./views.js";
export default {
title: "add backup provider",
};
export const DemoService = createExample(ConfirmProviderView, {
export const DemoService = tests.createExample(ConfirmProviderView, {
url: "https://sync.demo.taler.net/",
provider: {
annual_fee: "KUDOS:0.1",
@ -40,7 +40,7 @@ export const DemoService = createExample(ConfirmProviderView, {
onCancel: {},
});
export const FreeService = createExample(ConfirmProviderView, {
export const FreeService = tests.createExample(ConfirmProviderView, {
url: "https://sync.taler:9667/",
provider: {
annual_fee: "ARS:0",
@ -54,14 +54,14 @@ export const FreeService = createExample(ConfirmProviderView, {
onCancel: {},
});
export const Initial = createExample(SelectProviderView, {
export const Initial = tests.createExample(SelectProviderView, {
url: { value: "" },
name: { value: "" },
onCancel: {},
onConfirm: {},
});
export const WithValue = createExample(SelectProviderView, {
export const WithValue = tests.createExample(SelectProviderView, {
url: {
value: "sync.demo.taler.net",
},
@ -72,7 +72,7 @@ export const WithValue = createExample(SelectProviderView, {
onConfirm: {},
});
export const WithConnectionError = createExample(SelectProviderView, {
export const WithConnectionError = tests.createExample(SelectProviderView, {
url: {
value: "sync.demo.taler.net",
error: "Network error",
@ -84,7 +84,7 @@ export const WithConnectionError = createExample(SelectProviderView, {
onConfirm: {},
});
export const WithClientError = createExample(SelectProviderView, {
export const WithClientError = tests.createExample(SelectProviderView, {
url: {
value: "sync.demo.taler.net",
error: "URL may not be right: (404) Not Found",
@ -96,7 +96,7 @@ export const WithClientError = createExample(SelectProviderView, {
onConfirm: {},
});
export const WithServerError = createExample(SelectProviderView, {
export const WithServerError = tests.createExample(SelectProviderView, {
url: {
value: "sync.demo.taler.net",
error: "Try another server: (500) Internal Server Error",

View File

@ -21,7 +21,8 @@
import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js";
import { createWalletApiMock, nullFunction } from "../../test-utils.js";
import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { Props } from "./index.js";
import { useComponentState } from "./state.js";

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { AddNewActionView as TestedComponent } from "./AddNewActionView.js";
export default {
@ -30,4 +30,4 @@ export default {
},
};
export const Initial = createExample(TestedComponent, {});
export const Initial = tests.createExample(TestedComponent, {});

View File

@ -25,14 +25,14 @@ import {
BackupView as TestedComponent,
ShowRecoveryInfo,
} from "./BackupPage.js";
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
export default {
title: "backup",
};
export const LotOfProviders = createExample(TestedComponent, {
export const LotOfProviders = tests.createExample(TestedComponent, {
providers: [
{
active: true,
@ -164,7 +164,7 @@ export const LotOfProviders = createExample(TestedComponent, {
],
});
export const OneProvider = createExample(TestedComponent, {
export const OneProvider = tests.createExample(TestedComponent, {
providers: [
{
active: true,
@ -190,10 +190,10 @@ export const OneProvider = createExample(TestedComponent, {
],
});
export const Empty = createExample(TestedComponent, {
export const Empty = tests.createExample(TestedComponent, {
providers: [],
});
export const Recovery = createExample(ShowRecoveryInfo, {
export const Recovery = tests.createExample(ShowRecoveryInfo, {
info: "taler://recovery/ASLDKJASLKDJASD",
});

View File

@ -29,7 +29,7 @@ import {
} from "date-fns";
import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { AlertView } from "../components/CurrentAlerts.js";
import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js";
import { QR } from "../components/QR.js";
import {
@ -118,8 +118,8 @@ export function BackupPage({ onAddProvider }: Props): VNode {
}
if (status.hasError) {
return (
<AlertView
alert={alertFromError(
<ErrorAlertView
error={alertFromError(
i18n.str`Could not load backup providers`,
status,
)}

View File

@ -25,7 +25,7 @@ import {
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -39,6 +39,7 @@ export function useComponentState({
}: Props): State {
const api = useBackendContext();
const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr);
const currency = parsed !== undefined ? parsed.currency : currencyStr;
@ -130,9 +131,9 @@ export function useComponentState({
error: undefined,
currency,
onAddAccount: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setAddingAccount(true);
},
}),
},
};
}
@ -221,27 +222,27 @@ export function useComponentState({
currency,
amount: {
value: amount,
onInput: updateAmount,
onInput: pushAlertOnError(updateAmount),
error: amountError,
},
onAddAccount: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setAddingAccount(true);
},
}),
},
account: {
list: accountMap,
value: stringifyPaytoUri(currentAccount),
onChange: updateAccountFromList,
onChange: pushAlertOnError(updateAccountFromList),
},
currentAccount,
cancelHandler: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
onCancel(currency);
},
}),
},
depositHandler: {
onClick: unableToDeposit ? undefined : doSend,
onClick: unableToDeposit ? undefined : pushAlertOnError(doSend),
},
totalFee,
totalToDeposit,
@ -263,7 +264,7 @@ async function getFeeForAmount(
});
}
export function labelForAccountType(id: string) {
export function labelForAccountType(id: string): string {
switch (id) {
case "":
return "Choose one";

View File

@ -19,26 +19,21 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { Amounts, DepositGroupFees } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js";
import { labelForAccountType } from "./state.js";
import { Amounts } from "@gnu-taler/taler-util";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { nullFunction } from "../../mui/handlers.js";
import { ReadyView } from "./views.js";
export default {
title: "deposit",
};
// const ac = parsePaytoUri("payto://iban/ES8877998399652238")!;
// const accountMap = createLabelsForBankAccount([ac]);
export const WithNoAccountForIBAN = createExample(ReadyView, {
export const WithNoAccountForIBAN = tests.createExample(ReadyView, {
status: "ready",
account: {
list: {},
value: "",
onChange: async () => {
null;
},
onChange: nullFunction,
},
currentAccount: {
isKnown: true,
@ -49,31 +44,25 @@ export const WithNoAccountForIBAN = createExample(ReadyView, {
},
currency: "USD",
amount: {
onInput: async () => {
null;
},
onInput: nullFunction,
value: Amounts.parseOrThrow("USD:10"),
},
onAddAccount: {},
cancelHandler: {},
depositHandler: {
onClick: async () => {
return;
},
onClick: nullFunction,
},
totalFee: Amounts.zeroOfCurrency("USD"),
totalToDeposit: Amounts.parseOrThrow("USD:10"),
// onCalculateFee: alwaysReturnFeeToOne,
});
export const WithIBANAccountTypeSelected = createExample(ReadyView, {
export const WithIBANAccountTypeSelected = tests.createExample(ReadyView, {
status: "ready",
account: {
list: { asdlkajsdlk: "asdlkajsdlk", qwerqwer: "qwerqwer" },
value: "asdlkajsdlk",
onChange: async () => {
null;
},
onChange: nullFunction,
},
currentAccount: {
isKnown: true,
@ -84,31 +73,25 @@ export const WithIBANAccountTypeSelected = createExample(ReadyView, {
},
currency: "USD",
amount: {
onInput: async () => {
null;
},
onInput: nullFunction,
value: Amounts.parseOrThrow("USD:10"),
},
onAddAccount: {},
cancelHandler: {},
depositHandler: {
onClick: async () => {
return;
},
onClick: nullFunction,
},
totalFee: Amounts.zeroOfCurrency("USD"),
totalToDeposit: Amounts.parseOrThrow("USD:10"),
// onCalculateFee: alwaysReturnFeeToOne,
});
export const NewBitcoinAccountTypeSelected = createExample(ReadyView, {
export const NewBitcoinAccountTypeSelected = tests.createExample(ReadyView, {
status: "ready",
account: {
list: {},
value: "asdlkajsdlk",
onChange: async () => {
null;
},
onChange: nullFunction,
},
currentAccount: {
isKnown: true,
@ -120,16 +103,12 @@ export const NewBitcoinAccountTypeSelected = createExample(ReadyView, {
onAddAccount: {},
currency: "USD",
amount: {
onInput: async () => {
null;
},
onInput: nullFunction,
value: Amounts.parseOrThrow("USD:10"),
},
cancelHandler: {},
depositHandler: {
onClick: async () => {
return;
},
onClick: nullFunction,
},
totalFee: Amounts.zeroOfCurrency("USD"),
totalToDeposit: Amounts.parseOrThrow("USD:10"),

View File

@ -28,7 +28,8 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js";
import { createWalletApiMock, nullFunction } from "../../test-utils.js";
import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js";

View File

@ -17,7 +17,7 @@
import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -26,6 +26,7 @@ import { Contact, Props, State } from "./index.js";
export function useComponentState(props: Props): RecursiveState<State> {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const parsedInitialAmount = !props.amount
? undefined
: Amounts.parse(props.amount);
@ -108,26 +109,26 @@ export function useComponentState(props: Props): RecursiveState<State> {
error: undefined,
previous,
selectCurrency: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setAmount(undefined);
},
}),
},
goToBank: {
onClick: invalid
? undefined
: async () => {
: pushAlertOnError(async () => {
props.goToWalletBankDeposit(currencyAndAmount);
},
}),
},
goToWallet: {
onClick: invalid
? undefined
: async () => {
: pushAlertOnError(async () => {
props.goToWalletWalletSend(currencyAndAmount);
},
}),
},
amountHandler: {
onInput: async (s) => setAmount(s),
onInput: pushAlertOnError(async (s) => setAmount(s)),
value: amount,
},
type: props.type,
@ -138,26 +139,26 @@ export function useComponentState(props: Props): RecursiveState<State> {
error: undefined,
previous,
selectCurrency: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setAmount(undefined);
},
}),
},
goToBank: {
onClick: invalid
? undefined
: async () => {
: pushAlertOnError(async () => {
props.goToWalletManualWithdraw(currencyAndAmount);
},
}),
},
goToWallet: {
onClick: invalid
? undefined
: async () => {
: pushAlertOnError(async () => {
props.goToWalletWalletInvoice(currencyAndAmount);
},
}),
},
amountHandler: {
onInput: async (s) => setAmount(s),
onInput: pushAlertOnError(async (s) => setAmount(s)),
value: amount,
},
type: props.type,

View File

@ -19,14 +19,14 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView, SelectCurrencyView } from "./views.js";
export default {
title: "destination",
};
export const GetCash = createExample(ReadyView, {
export const GetCash = tests.createExample(ReadyView, {
amountHandler: {
value: {
currency: "EUR",
@ -40,7 +40,7 @@ export const GetCash = createExample(ReadyView, {
selectCurrency: {},
type: "get",
});
export const SendCash = createExample(ReadyView, {
export const SendCash = tests.createExample(ReadyView, {
amountHandler: {
value: {
currency: "EUR",
@ -55,7 +55,7 @@ export const SendCash = createExample(ReadyView, {
type: "send",
});
export const SelectCurrency = createExample(SelectCurrencyView, {
export const SelectCurrency = tests.createExample(SelectCurrencyView, {
currencies: {
"": "Select a currency",
USD: "USD",

View File

@ -28,7 +28,8 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js";
import { createWalletApiMock, nullFunction } from "../../test-utils.js";
import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js";
const exchangeArs: ExchangeListItem = {

View File

@ -20,7 +20,7 @@
*/
import { PendingTaskType } from "@gnu-taler/taler-wallet-core";
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { View as TestedComponent } from "./DeveloperPage.js";
export default {
@ -31,7 +31,7 @@ export default {
},
};
export const AllOff = createExample(TestedComponent, {
export const AllOff = tests.createExample(TestedComponent, {
onDownloadDatabase: async () => "this is the content of the database",
operations: [
{

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js";
export default {
title: "example",
};
export const Ready = createExample(ReadyView, {});
export const Ready = tests.createExample(ReadyView, {});

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ExchangeAddConfirmPage as TestedComponent } from "./ExchangeAddConfirm.js";
export default {
@ -32,14 +32,14 @@ export default {
},
};
export const TermsNotFound = createExample(TestedComponent, {
export const TermsNotFound = tests.createExample(TestedComponent, {
url: "https://exchange.demo.taler.net/",
});
export const NewTerms = createExample(TestedComponent, {
export const NewTerms = tests.createExample(TestedComponent, {
url: "https://exchange.demo.taler.net/",
});
export const TermsChanged = createExample(TestedComponent, {
export const TermsChanged = tests.createExample(TestedComponent, {
url: "https://exchange.demo.taler.net/",
});

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { queryToSlashKeys } from "../utils/index.js";
import { ExchangeSetUrlPage as TestedComponent } from "./ExchangeSetUrl.js";
@ -27,17 +27,17 @@ export default {
title: "exchange add set url",
};
export const ExpectedUSD = createExample(TestedComponent, {
export const ExpectedUSD = tests.createExample(TestedComponent, {
expectedCurrency: "USD",
onVerify: queryToSlashKeys,
});
export const ExpectedKUDOS = createExample(TestedComponent, {
export const ExpectedKUDOS = tests.createExample(TestedComponent, {
expectedCurrency: "KUDOS",
onVerify: queryToSlashKeys,
});
export const InitialState = createExample(TestedComponent, {
export const InitialState = tests.createExample(TestedComponent, {
onVerify: queryToSlashKeys,
});
@ -55,7 +55,7 @@ const knownExchanges = [
},
];
export const WithDemoAsKnownExchange = createExample(TestedComponent, {
export const WithDemoAsKnownExchange = tests.createExample(TestedComponent, {
onVerify: async (url) => {
const found =
knownExchanges.findIndex((e) => e.exchangeBaseUrl === url) !== -1;

View File

@ -20,7 +20,7 @@ import {
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -33,6 +33,7 @@ export function useComponentState({
currentExchange,
}: Props): State {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const { i18n } = useTranslationContext();
const initialValue = exchanges.findIndex(
(e) => e.exchangeBaseUrl === currentExchange,
@ -115,7 +116,7 @@ export function useComponentState({
status: "showing-privacy",
error: undefined,
onClose: {
onClick: async () => setShowingPrivacy(undefined),
onClick: pushAlertOnError(async () => setShowingPrivacy(undefined)),
},
exchangeUrl: showingPrivacy,
};
@ -125,7 +126,7 @@ export function useComponentState({
status: "showing-tos",
error: undefined,
onClose: {
onClick: async () => setShowingTos(undefined),
onClick: pushAlertOnError(async () => setShowingTos(undefined)),
},
exchangeUrl: showingTos,
};
@ -138,24 +139,24 @@ export function useComponentState({
exchanges: {
list: exchangeMap,
value: value,
onChange: async (v) => {
onChange: pushAlertOnError(async (v) => {
setValue(v);
},
}),
},
error: undefined,
onClose: {
onClick: onCancel,
onClick: pushAlertOnError(onCancel),
},
selected,
onShowPrivacy: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setShowingPrivacy(selected.exchangeBaseUrl);
},
}),
},
onShowTerms: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setShowingTos(selected.exchangeBaseUrl);
},
}),
},
};
}
@ -215,30 +216,30 @@ export function useComponentState({
exchanges: {
list: exchangeMap,
value: value,
onChange: async (v) => {
onChange: pushAlertOnError(async (v) => {
setValue(v);
},
}),
},
error: undefined,
onReset: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setValue(String(initialValue));
},
}),
},
onSelect: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
onSelection(selected.exchangeBaseUrl);
},
}),
},
onShowPrivacy: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setShowingPrivacy(selected.exchangeBaseUrl);
},
}),
},
onShowTerms: {
onClick: async () => {
onClick: pushAlertOnError(async () => {
setShowingTos(selected.exchangeBaseUrl);
},
}),
},
selected,
coinOperationTimeline,

View File

@ -19,14 +19,14 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ComparingView, ReadyView } from "./views.js";
export default {
title: "select exchange",
};
export const Bitcoin1 = createExample(ReadyView, {
export const Bitcoin1 = tests.createExample(ReadyView, {
exchanges: {
list: { "0": "https://exchange.taler.ar" },
value: "0",
@ -43,7 +43,7 @@ export const Bitcoin1 = createExample(ReadyView, {
onShowTerms: {},
onClose: {},
});
export const Bitcoin2 = createExample(ReadyView, {
export const Bitcoin2 = tests.createExample(ReadyView, {
exchanges: {
list: {
"https://exchange.taler.ar": "https://exchange.taler.ar",
@ -64,7 +64,7 @@ export const Bitcoin2 = createExample(ReadyView, {
onClose: {},
});
export const Kudos1 = createExample(ReadyView, {
export const Kudos1 = tests.createExample(ReadyView, {
exchanges: {
list: {
"https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar",
@ -83,7 +83,7 @@ export const Kudos1 = createExample(ReadyView, {
onShowTerms: {},
onClose: {},
});
export const Kudos2 = createExample(ReadyView, {
export const Kudos2 = tests.createExample(ReadyView, {
exchanges: {
list: {
"https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar",
@ -103,7 +103,7 @@ export const Kudos2 = createExample(ReadyView, {
onShowTerms: {},
onClose: {},
});
export const ComparingBitcoin = createExample(ComparingView, {
export const ComparingBitcoin = tests.createExample(ComparingView, {
exchanges: {
list: { "http://exchange": "http://exchange" },
value: "http://exchange",
@ -131,7 +131,7 @@ export const ComparingBitcoin = createExample(ComparingView, {
missingWireTYpe: [],
wireFeeTimeline: {},
});
export const ComparingKudos = createExample(ComparingView, {
export const ComparingKudos = tests.createExample(ComparingView, {
exchanges: {
list: { "http://exchange": "http://exchange" },
value: "http://exchange",

View File

@ -37,7 +37,7 @@ import {
WithdrawalType,
} from "@gnu-taler/taler-util";
import { HistoryView as TestedComponent } from "./History.js";
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
export default {
title: "balance",
@ -160,12 +160,14 @@ const exampleData = {
} as TransactionPeerPullDebit,
};
export const NoBalance = createExample(TestedComponent, {
export const NoBalance = tests.createExample(TestedComponent, {
transactions: [],
balances: [],
});
export const SomeBalanceWithNoTransactions = createExample(TestedComponent, {
export const SomeBalanceWithNoTransactions = tests.createExample(
TestedComponent,
{
transactions: [],
balances: [
{
@ -176,9 +178,10 @@ export const SomeBalanceWithNoTransactions = createExample(TestedComponent, {
requiresUserInput: false,
},
],
});
},
);
export const OneSimpleTransaction = createExample(TestedComponent, {
export const OneSimpleTransaction = tests.createExample(TestedComponent, {
transactions: [exampleData.withdraw],
balances: [
{
@ -191,7 +194,9 @@ export const OneSimpleTransaction = createExample(TestedComponent, {
],
});
export const TwoTransactionsAndZeroBalance = createExample(TestedComponent, {
export const TwoTransactionsAndZeroBalance = tests.createExample(
TestedComponent,
{
transactions: [exampleData.withdraw, exampleData.deposit],
balances: [
{
@ -202,9 +207,10 @@ export const TwoTransactionsAndZeroBalance = createExample(TestedComponent, {
requiresUserInput: false,
},
],
});
},
);
export const OneTransactionPending = createExample(TestedComponent, {
export const OneTransactionPending = tests.createExample(TestedComponent, {
transactions: [
{
...exampleData.withdraw,
@ -222,7 +228,7 @@ export const OneTransactionPending = createExample(TestedComponent, {
],
});
export const SomeTransactions = createExample(TestedComponent, {
export const SomeTransactions = tests.createExample(TestedComponent, {
transactions: [
exampleData.withdraw,
exampleData.payment,
@ -251,7 +257,7 @@ export const SomeTransactions = createExample(TestedComponent, {
],
});
export const SomeTransactionsWithTwoCurrencies = createExample(
export const SomeTransactionsWithTwoCurrencies = tests.createExample(
TestedComponent,
{
transactions: [
@ -283,7 +289,7 @@ export const SomeTransactionsWithTwoCurrencies = createExample(
},
);
export const FiveOfficialCurrencies = createExample(TestedComponent, {
export const FiveOfficialCurrencies = tests.createExample(TestedComponent, {
transactions: [exampleData.withdraw],
balances: [
{
@ -324,7 +330,7 @@ export const FiveOfficialCurrencies = createExample(TestedComponent, {
],
});
export const FiveOfficialCurrenciesWithHighValue = createExample(
export const FiveOfficialCurrenciesWithHighValue = tests.createExample(
TestedComponent,
{
transactions: [exampleData.withdraw],
@ -368,7 +374,7 @@ export const FiveOfficialCurrenciesWithHighValue = createExample(
},
);
export const PeerToPeer = createExample(TestedComponent, {
export const PeerToPeer = tests.createExample(TestedComponent, {
transactions: [
exampleData.pull_credit,
exampleData.pull_debit,

View File

@ -23,7 +23,7 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { AlertView } from "../components/CurrentAlerts.js";
import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js";
import {
CenteredBoldText,
@ -33,7 +33,7 @@ import {
} from "../components/styled/index.js";
import { Time } from "../components/Time.js";
import { TransactionItem } from "../components/TransactionItem.js";
import { alertFromError } from "../context/alert.js";
import { alertFromError, useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
@ -72,8 +72,8 @@ export function HistoryPage({
if (state.hasError) {
return (
<AlertView
alert={alertFromError(
<ErrorAlertView
error={alertFromError(
i18n.str`Could not load the list of transactions`,
state,
)}
@ -112,6 +112,7 @@ export function HistoryView({
}): VNode {
const { i18n } = useTranslationContext();
const currencies = balances.map((b) => b.available.split(":")[0]);
const { pushAlertOnError } = useAlertContext();
const defaultCurrencyIndex = currencies.findIndex(
(c) => c === defaultCurrency,
@ -145,7 +146,7 @@ export function HistoryView({
return (
<NoBalanceHelp
goToWalletManualWithdraw={{
onClick: goToWalletManualWithdraw,
onClick: pushAlertOnError(goToWalletManualWithdraw),
}}
/>
);

View File

@ -21,7 +21,7 @@ import {
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -33,6 +33,7 @@ export function useComponentState({
onCancel,
}: Props): State {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }),
@ -109,30 +110,30 @@ export function useComponentState({
accountType: {
list: accountType,
value: type,
onChange: async (v) => {
onChange: pushAlertOnError(async (v) => {
setType(v);
},
}),
},
alias: {
value: alias,
onInput: async (v) => {
onInput: pushAlertOnError(async (v) => {
setAlias(v);
},
}),
},
uri: {
value: payto,
error: paytoUriError,
onInput: async (v) => {
onInput: pushAlertOnError(async (v) => {
setPayto(v);
},
}),
},
accountByType,
deleteAccount,
onAccountAdded: {
onClick: unableToAdd ? undefined : addAccount,
onClick: unableToAdd ? undefined : pushAlertOnError(addAccount),
},
onCancel: {
onClick: async () => onCancel(),
onClick: pushAlertOnError(async () => onCancel()),
},
};
}

View File

@ -19,18 +19,15 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { nullFunction } from "../../mui/handlers.js";
import { ReadyView } from "./views.js";
export default {
title: "manage account",
};
const nullFunction = async () => {
null;
};
export const JustTwoBitcoinAccounts = createExample(ReadyView, {
export const JustTwoBitcoinAccounts = tests.createExample(ReadyView, {
status: "ready",
currency: "ARS",
accountType: {
@ -84,7 +81,7 @@ export const JustTwoBitcoinAccounts = createExample(ReadyView, {
onCancel: {},
});
export const WithAllTypeOfAccounts = createExample(ReadyView, {
export const WithAllTypeOfAccounts = tests.createExample(ReadyView, {
status: "ready",
currency: "ARS",
accountType: {
@ -165,7 +162,7 @@ export const WithAllTypeOfAccounts = createExample(ReadyView, {
onCancel: {},
});
export const AddingIbanAccount = createExample(ReadyView, {
export const AddingIbanAccount = tests.createExample(ReadyView, {
status: "ready",
currency: "ARS",
accountType: {

View File

@ -20,14 +20,14 @@
*/
import { AbsoluteTime, AttentionType } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js";
export default {
title: "notifications",
};
export const Ready = createExample(ReadyView, {
export const Ready = tests.createExample(ReadyView, {
list: [
{
when: AbsoluteTime.now(),

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage.js";
export default {
@ -32,7 +32,7 @@ export default {
},
};
export const DemoService = createExample(TestedComponent, {
export const DemoService = tests.createExample(TestedComponent, {
url: "https://sync.demo.taler.net/",
provider: {
annual_fee: "KUDOS:0.1",
@ -41,7 +41,7 @@ export const DemoService = createExample(TestedComponent, {
},
});
export const FreeService = createExample(TestedComponent, {
export const FreeService = tests.createExample(TestedComponent, {
url: "https://sync.taler:9667/",
provider: {
annual_fee: "ARS:0",

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { SetUrlView as TestedComponent } from "./ProviderAddPage.js";
export default {
@ -32,20 +32,20 @@ export default {
},
};
export const Initial = createExample(TestedComponent, {});
export const Initial = tests.createExample(TestedComponent, {});
export const WithValue = createExample(TestedComponent, {
export const WithValue = tests.createExample(TestedComponent, {
initialValue: "sync.demo.taler.net",
});
export const WithConnectionError = createExample(TestedComponent, {
export const WithConnectionError = tests.createExample(TestedComponent, {
withError: "Network error",
});
export const WithClientError = createExample(TestedComponent, {
export const WithClientError = tests.createExample(TestedComponent, {
withError: "URL may not be right: (404) Not Found",
});
export const WithServerError = createExample(TestedComponent, {
export const WithServerError = tests.createExample(TestedComponent, {
withError: "Try another server: (500) Internal Server Error",
});

View File

@ -21,7 +21,7 @@
import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ProviderView as TestedComponent } from "./ProviderDetailPage.js";
export default {
@ -34,7 +34,7 @@ export default {
},
};
export const Active = createExample(TestedComponent, {
export const Active = tests.createExample(TestedComponent, {
info: {
active: true,
name: "sync.demo",
@ -58,7 +58,7 @@ export const Active = createExample(TestedComponent, {
},
});
export const ActiveErrorSync = createExample(TestedComponent, {
export const ActiveErrorSync = tests.createExample(TestedComponent, {
info: {
active: true,
name: "sync.demo",
@ -79,6 +79,7 @@ export const ActiveErrorSync = createExample(TestedComponent, {
lastError: {
code: 2002,
details: "details",
when: new Date().toISOString(),
hint: "error hint from the server",
message: "message",
},
@ -90,7 +91,9 @@ export const ActiveErrorSync = createExample(TestedComponent, {
},
});
export const ActiveBackupProblemUnreadable = createExample(TestedComponent, {
export const ActiveBackupProblemUnreadable = tests.createExample(
TestedComponent,
{
info: {
active: true,
name: "sync.demo",
@ -115,9 +118,10 @@ export const ActiveBackupProblemUnreadable = createExample(TestedComponent, {
supportedProtocolVersion: "0.0",
},
},
});
},
);
export const ActiveBackupProblemDevice = createExample(TestedComponent, {
export const ActiveBackupProblemDevice = tests.createExample(TestedComponent, {
info: {
active: true,
name: "sync.demo",
@ -149,7 +153,7 @@ export const ActiveBackupProblemDevice = createExample(TestedComponent, {
},
});
export const InactiveUnpaid = createExample(TestedComponent, {
export const InactiveUnpaid = tests.createExample(TestedComponent, {
info: {
active: false,
name: "sync.demo",
@ -166,7 +170,9 @@ export const InactiveUnpaid = createExample(TestedComponent, {
},
});
export const InactiveInsufficientBalance = createExample(TestedComponent, {
export const InactiveInsufficientBalance = tests.createExample(
TestedComponent,
{
info: {
active: false,
name: "sync.demo",
@ -182,9 +188,10 @@ export const InactiveInsufficientBalance = createExample(TestedComponent, {
supportedProtocolVersion: "0.0",
},
},
});
},
);
export const InactivePending = createExample(TestedComponent, {
export const InactivePending = tests.createExample(TestedComponent, {
info: {
active: false,
name: "sync.demo",
@ -202,7 +209,7 @@ export const InactivePending = createExample(TestedComponent, {
},
});
export const ActiveTermsChanged = createExample(TestedComponent, {
export const ActiveTermsChanged = tests.createExample(TestedComponent, {
info: {
active: true,
name: "sync.demo",

View File

@ -23,7 +23,7 @@ import {
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact";
import { AlertView } from "../components/CurrentAlerts.js";
import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { ErrorMessage } from "../components/ErrorMessage.js";
import { Loading } from "../components/Loading.js";
import { PaymentStatus, SmallLightText } from "../components/styled/index.js";
@ -66,8 +66,8 @@ export function ProviderDetailPage({
}
if (state.hasError) {
return (
<AlertView
alert={alertFromError(
<ErrorAlertView
error={alertFromError(
i18n.str`There was an error loading the provider detail for &quot;${providerURL}&quot;`,
state,
)}

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { QrReaderPage } from "./QrReader.js";
export default {
title: "qr reader",
};
export const Reading = createExample(QrReaderPage, {});
export const Reading = tests.createExample(QrReaderPage, {});

View File

@ -20,7 +20,7 @@
*/
import { parsePaytoUri } from "@gnu-taler/taler-util";
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReserveCreated as TestedComponent } from "./ReserveCreated.js";
export default {
@ -29,7 +29,7 @@ export default {
argTypes: {},
};
export const TalerBank = createExample(TestedComponent, {
export const TalerBank = tests.createExample(TestedComponent, {
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
paytoURI: parsePaytoUri(
"payto://x-taler-bank/bank.taler:5882/exchangeminator?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
@ -42,7 +42,7 @@ export const TalerBank = createExample(TestedComponent, {
exchangeBaseUrl: "https://exchange.demo.taler.net",
});
export const IBAN = createExample(TestedComponent, {
export const IBAN = tests.createExample(TestedComponent, {
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
paytoURI: parsePaytoUri(
"payto://iban/ES8877998399652238?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
@ -55,7 +55,7 @@ export const IBAN = createExample(TestedComponent, {
exchangeBaseUrl: "https://exchange.demo.taler.net",
});
export const WithReceiverName = createExample(TestedComponent, {
export const WithReceiverName = tests.createExample(TestedComponent, {
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
paytoURI: parsePaytoUri(
"payto://iban/ES8877998399652238?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG&receiver=Sebastian",
@ -68,7 +68,7 @@ export const WithReceiverName = createExample(TestedComponent, {
exchangeBaseUrl: "https://exchange.demo.taler.net",
});
export const Bitcoin = createExample(TestedComponent, {
export const Bitcoin = tests.createExample(TestedComponent, {
reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
paytoURI: parsePaytoUri(
"payto://bitcoin/bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
@ -81,7 +81,7 @@ export const Bitcoin = createExample(TestedComponent, {
exchangeBaseUrl: "https://exchange.demo.taler.net",
});
export const BitcoinRegTest = createExample(TestedComponent, {
export const BitcoinRegTest = tests.createExample(TestedComponent, {
reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
paytoURI: parsePaytoUri(
"payto://bitcoin/bcrt1q6ps8qs6v8tkqrnru4xqqqa6rfwcx5ufpdfqht4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
@ -93,7 +93,7 @@ export const BitcoinRegTest = createExample(TestedComponent, {
},
exchangeBaseUrl: "https://exchange.demo.taler.net",
});
export const BitcoinTest = createExample(TestedComponent, {
export const BitcoinTest = tests.createExample(TestedComponent, {
reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
paytoURI: parsePaytoUri(
"payto://bitcoin/tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { SettingsView as TestedComponent } from "./Settings.js";
export default {
@ -45,7 +45,7 @@ const version = {
},
};
export const AllOff = createExample(TestedComponent, {
export const AllOff = tests.createExample(TestedComponent, {
deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} },
@ -54,7 +54,7 @@ export const AllOff = createExample(TestedComponent, {
...version,
});
export const OneChecked = createExample(TestedComponent, {
export const OneChecked = tests.createExample(TestedComponent, {
deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} },
@ -63,7 +63,7 @@ export const OneChecked = createExample(TestedComponent, {
...version,
});
export const WithOneExchange = createExample(TestedComponent, {
export const WithOneExchange = tests.createExample(TestedComponent, {
deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} },
@ -85,7 +85,9 @@ export const WithOneExchange = createExample(TestedComponent, {
...version,
});
export const WithExchangeInDifferentState = createExample(TestedComponent, {
export const WithExchangeInDifferentState = tests.createExample(
TestedComponent,
{
deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} },
@ -101,7 +103,9 @@ export const WithExchangeInDifferentState = createExample(TestedComponent, {
content: "content of tos",
contentType: "text/plain",
},
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"],
paytoUris: [
"payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator",
],
},
{
currency: "USD",
@ -112,7 +116,9 @@ export const WithExchangeInDifferentState = createExample(TestedComponent, {
content: "content of tos",
contentType: "text/plain",
},
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"],
paytoUris: [
"payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator",
],
} as any, //TODO: complete with auditors, wireInfo and denominations
{
currency: "USD",
@ -122,8 +128,11 @@ export const WithExchangeInDifferentState = createExample(TestedComponent, {
content: "content of tos",
contentType: "text/plain",
},
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"],
paytoUris: [
"payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator",
],
},
],
...version,
});
},
);

View File

@ -22,7 +22,6 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact";
import { Checkbox } from "../components/Checkbox.js";
import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js";
import { JustInDevMode } from "../components/JustInDevMode.js";
import { Part } from "../components/Part.js";
import { SelectList } from "../components/SelectList.js";
@ -34,6 +33,7 @@ import {
SuccessText,
WarningText,
} from "../components/styled/index.js";
import { useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { useDevContext } from "../context/devContext.js";
import { useTranslationContext } from "../context/translation.js";
@ -50,8 +50,9 @@ const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
export function SettingsPage(): VNode {
const autoOpenToggle = useAutoOpenPermissions();
const clipboardToggle = useClipboardPermissions();
const { devModeToggle } = useDevContext();
const { devMode, toggle } = useDevContext();
const { name, update } = useBackupDeviceName();
const { pushAlertOnError } = useAlertContext();
const webex = platform.getWalletWebExVersion();
const api = useBackendContext();
@ -72,7 +73,12 @@ export function SettingsPage(): VNode {
setDeviceName={update}
autoOpenToggle={autoOpenToggle}
clipboardToggle={clipboardToggle}
devModeToggle={devModeToggle}
devModeToggle={{
value: devMode,
button: {
onClick: pushAlertOnError(toggle),
},
}}
webexVersion={{
version: webex.version,
hash: GIT_HASH,
@ -109,18 +115,6 @@ export function SettingsView({
return (
<Fragment>
<section>
{autoOpenToggle.button.error && (
<ErrorTalerOperation
title={i18n.str`Could not toggle auto-open`}
error={autoOpenToggle.button.error.errorDetail}
/>
)}
{/* {clipboardToggle.button.error && (
<ErrorTalerOperation
title={i18n.str`Could not toggle clipboard`}
error={clipboardToggle.button.error.errorDetail}
/>
)} */}
<SubTitle>
<i18n.Translate>Navigator</i18n.Translate>
</SubTitle>

View File

@ -39,11 +39,13 @@ import {
WithdrawalType,
} from "@gnu-taler/taler-util";
import { DevContextProviderForTesting } from "../context/devContext.js";
import {
createExample,
createExampleWithCustomContext as createExampleInCustomContext,
} from "../test-utils.js";
// import {
// createExample,
// createExampleWithCustomContext as createExampleInCustomContext,
// } from "../test-utils.js";
import { TransactionView as TestedComponent } from "./Transaction.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import beer from "../../static-dev/beer.png";
export default {
title: "transaction details",
@ -214,24 +216,28 @@ const transactionError = {
hint: "The payment is too late, the offer has expired.",
},
},
when: new Date().toISOString(),
hint: "Error: WALLET_UNEXPECTED_REQUEST_ERROR",
message: "Unexpected error code in response",
};
export const Withdraw = createExample(TestedComponent, {
export const Withdraw = tests.createExample(TestedComponent, {
transaction: exampleData.withdraw,
});
export const WithdrawFiveMinutesAgo = createExample(TestedComponent, () => ({
export const WithdrawFiveMinutesAgo = tests.createExample(
TestedComponent,
() => ({
transaction: {
...exampleData.withdraw,
timestamp: TalerProtocolTimestamp.fromSeconds(
new Date().getTime() / 1000 - 60 * 5,
),
},
}));
}),
);
export const WithdrawFiveMinutesAgoAndPending = createExample(
export const WithdrawFiveMinutesAgoAndPending = tests.createExample(
TestedComponent,
() => ({
transaction: {
@ -244,26 +250,28 @@ export const WithdrawFiveMinutesAgoAndPending = createExample(
}),
);
export const WithdrawError = createExample(TestedComponent, {
export const WithdrawError = tests.createExample(TestedComponent, {
transaction: {
...exampleData.withdraw,
error: transactionError,
},
});
export const WithdrawErrorInDevMode = createExampleInCustomContext(
TestedComponent,
{
transaction: {
...exampleData.withdraw,
error: transactionError,
},
},
DevContextProviderForTesting,
{ value: true },
);
// export const WithdrawErrorInDevMode = tests.createExampleInCustomContext(
// TestedComponent,
// {
// transaction: {
// ...exampleData.withdraw,
// error: transactionError,
// },
// },
// DevContextProviderForTesting,
// { value: true },
// );
export const WithdrawPendingManual = createExample(TestedComponent, () => ({
export const WithdrawPendingManual = tests.createExample(
TestedComponent,
() => ({
transaction: {
...exampleData.withdraw,
withdrawalDetails: {
@ -273,9 +281,10 @@ export const WithdrawPendingManual = createExample(TestedComponent, () => ({
} as WithdrawalDetails,
pending: true,
},
}));
}),
);
export const WithdrawPendingTalerBankUnconfirmed = createExample(
export const WithdrawPendingTalerBankUnconfirmed = tests.createExample(
TestedComponent,
{
transaction: {
@ -291,7 +300,7 @@ export const WithdrawPendingTalerBankUnconfirmed = createExample(
},
);
export const WithdrawPendingTalerBankConfirmed = createExample(
export const WithdrawPendingTalerBankConfirmed = tests.createExample(
TestedComponent,
{
transaction: {
@ -306,18 +315,18 @@ export const WithdrawPendingTalerBankConfirmed = createExample(
},
);
export const Payment = createExample(TestedComponent, {
export const Payment = tests.createExample(TestedComponent, {
transaction: exampleData.payment,
});
export const PaymentError = createExample(TestedComponent, {
export const PaymentError = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
error: transactionError,
},
});
export const PaymentWithRefund = createExample(TestedComponent, {
export const PaymentWithRefund = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
amountRaw: "KUDOS:12",
@ -334,7 +343,7 @@ export const PaymentWithRefund = createExample(TestedComponent, {
},
});
export const PaymentWithDeliveryDate = createExample(TestedComponent, {
export const PaymentWithDeliveryDate = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
amountRaw: "KUDOS:12",
@ -347,7 +356,7 @@ export const PaymentWithDeliveryDate = createExample(TestedComponent, {
},
});
export const PaymentWithDeliveryAddr = createExample(TestedComponent, {
export const PaymentWithDeliveryAddr = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
amountRaw: "KUDOS:12",
@ -363,7 +372,7 @@ export const PaymentWithDeliveryAddr = createExample(TestedComponent, {
},
});
export const PaymentWithDeliveryFull = createExample(TestedComponent, {
export const PaymentWithDeliveryFull = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
amountRaw: "KUDOS:12",
@ -382,7 +391,7 @@ export const PaymentWithDeliveryFull = createExample(TestedComponent, {
},
});
export const PaymentWithRefundPending = createExample(TestedComponent, {
export const PaymentWithRefundPending = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
amountRaw: "KUDOS:12",
@ -392,7 +401,7 @@ export const PaymentWithRefundPending = createExample(TestedComponent, {
},
});
export const PaymentWithFeeAndRefund = createExample(TestedComponent, {
export const PaymentWithFeeAndRefund = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
amountRaw: "KUDOS:11",
@ -401,7 +410,7 @@ export const PaymentWithFeeAndRefund = createExample(TestedComponent, {
},
});
export const PaymentWithFeeAndRefundFee = createExample(TestedComponent, {
export const PaymentWithFeeAndRefundFee = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
amountRaw: "KUDOS:11",
@ -410,20 +419,18 @@ export const PaymentWithFeeAndRefundFee = createExample(TestedComponent, {
},
});
export const PaymentWithoutFee = createExample(TestedComponent, {
export const PaymentWithoutFee = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
amountRaw: "KUDOS:12",
},
});
export const PaymentPending = createExample(TestedComponent, {
export const PaymentPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.payment, pending: true },
});
import beer from "../../static-dev/beer.png";
export const PaymentWithProducts = createExample(TestedComponent, {
export const PaymentWithProducts = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
info: {
@ -460,7 +467,7 @@ export const PaymentWithProducts = createExample(TestedComponent, {
} as TransactionPayment,
});
export const PaymentWithLongSummary = createExample(TestedComponent, {
export const PaymentWithLongSummary = tests.createExample(TestedComponent, {
transaction: {
...exampleData.payment,
info: {
@ -484,16 +491,16 @@ export const PaymentWithLongSummary = createExample(TestedComponent, {
} as TransactionPayment,
});
export const Deposit = createExample(TestedComponent, {
export const Deposit = tests.createExample(TestedComponent, {
transaction: exampleData.deposit,
});
export const DepositTalerBank = createExample(TestedComponent, {
export const DepositTalerBank = tests.createExample(TestedComponent, {
transaction: {
...exampleData.deposit,
targetPaytoUri: "payto://x-taler-bank/bank.demo.taler.net/Exchange",
},
});
export const DepositBitcoin = createExample(TestedComponent, {
export const DepositBitcoin = tests.createExample(TestedComponent, {
transaction: {
...exampleData.deposit,
amountRaw: "BITCOINBTC:0.0000011",
@ -502,88 +509,88 @@ export const DepositBitcoin = createExample(TestedComponent, {
"payto://bitcoin/bcrt1q6ps8qs6v8tkqrnru4xqqqa6rfwcx5ufpdfqht4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
},
});
export const DepositIBAN = createExample(TestedComponent, {
export const DepositIBAN = tests.createExample(TestedComponent, {
transaction: {
...exampleData.deposit,
targetPaytoUri: "payto://iban/ES8877998399652238",
},
});
export const DepositError = createExample(TestedComponent, {
export const DepositError = tests.createExample(TestedComponent, {
transaction: {
...exampleData.deposit,
error: transactionError,
},
});
export const DepositPending = createExample(TestedComponent, {
export const DepositPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.deposit, pending: true },
});
export const Refresh = createExample(TestedComponent, {
export const Refresh = tests.createExample(TestedComponent, {
transaction: exampleData.refresh,
});
export const RefreshError = createExample(TestedComponent, {
export const RefreshError = tests.createExample(TestedComponent, {
transaction: {
...exampleData.refresh,
error: transactionError,
},
});
export const Tip = createExample(TestedComponent, {
export const Tip = tests.createExample(TestedComponent, {
transaction: exampleData.tip,
});
export const TipError = createExample(TestedComponent, {
export const TipError = tests.createExample(TestedComponent, {
transaction: {
...exampleData.tip,
error: transactionError,
},
});
export const TipPending = createExample(TestedComponent, {
export const TipPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.tip, pending: true },
});
export const Refund = createExample(TestedComponent, {
export const Refund = tests.createExample(TestedComponent, {
transaction: exampleData.refund,
});
export const RefundError = createExample(TestedComponent, {
export const RefundError = tests.createExample(TestedComponent, {
transaction: {
...exampleData.refund,
error: transactionError,
},
});
export const RefundPending = createExample(TestedComponent, {
export const RefundPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.refund, pending: true },
});
export const InvoiceCreditComplete = createExample(TestedComponent, {
export const InvoiceCreditComplete = tests.createExample(TestedComponent, {
transaction: { ...exampleData.pull_credit },
});
export const InvoiceCreditIncomplete = createExample(TestedComponent, {
export const InvoiceCreditIncomplete = tests.createExample(TestedComponent, {
transaction: {
...exampleData.pull_credit,
pending: true,
},
});
export const InvoiceDebit = createExample(TestedComponent, {
export const InvoiceDebit = tests.createExample(TestedComponent, {
transaction: { ...exampleData.pull_debit },
});
export const TransferCredit = createExample(TestedComponent, {
export const TransferCredit = tests.createExample(TestedComponent, {
transaction: { ...exampleData.push_credit },
});
export const TransferDebitComplete = createExample(TestedComponent, {
export const TransferDebitComplete = tests.createExample(TestedComponent, {
transaction: { ...exampleData.push_debit },
});
export const TransferDebitIncomplete = createExample(TestedComponent, {
export const TransferDebitIncomplete = tests.createExample(TestedComponent, {
transaction: {
...exampleData.push_debit,
pending: true,

View File

@ -44,7 +44,7 @@ import emptyImg from "../../static/img/empty.png";
import { Amount } from "../components/Amount.js";
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js";
import { CopyButton } from "../components/CopyButton.js";
import { AlertView, ErrorAlertView } from "../components/CurrentAlerts.js";
import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js";
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
import { QR } from "../components/QR.js";
@ -99,8 +99,8 @@ export function TransactionPage({
if (state.hasError) {
return (
<AlertView
alert={alertFromError(
<ErrorAlertView
error={alertFromError(
i18n.str`Could not load transaction information`,
state,
)}

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createExample } from "../test-utils.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { View as TestedComponent } from "./Welcome.js";
export default {
@ -27,7 +27,7 @@ export default {
component: TestedComponent,
};
export const Normal = createExample(TestedComponent, {
export const Normal = tests.createExample(TestedComponent, {
permissionToggle: { value: true, button: {} },
diagnostics: {
errors: [],
@ -38,11 +38,11 @@ export const Normal = createExample(TestedComponent, {
},
});
export const TimedoutDiagnostics = createExample(TestedComponent, {
export const TimedoutDiagnostics = tests.createExample(TestedComponent, {
timedOut: true,
permissionToggle: { value: true, button: {} },
});
export const RunningDiagnostics = createExample(TestedComponent, {
export const RunningDiagnostics = tests.createExample(TestedComponent, {
permissionToggle: { value: true, button: {} },
});

View File

@ -93,22 +93,12 @@ export interface BackgroundApiClient {
): Promise<BackgroundOperations[Op]["response"]>;
}
export class WalletError extends Error {
public errorDetail: TalerError;
constructor(op: string, e: TalerError) {
super(`Wallet operation "${op}" failed: ${e.message}`);
this.errorDetail = e;
// Object.setPrototypeOf(this, WalletError.prototype);
}
}
export class BackgroundError extends Error {
public errorDetail: TalerErrorDetail;
constructor(op: string, e: TalerErrorDetail) {
super(`Background operation "${op}" failed: ${e.message}`);
constructor(title: string, e: TalerErrorDetail) {
super(title);
this.errorDetail = e;
// Object.setPrototypeOf(this, BackgroundError.prototype);
}
}
@ -135,13 +125,17 @@ class BackgroundApiClientImpl implements BackgroundApiClient {
if (error instanceof Error) {
throw new BackgroundError(operation, {
code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR,
when: new Date().toISOString(),
});
}
throw error;
}
logger.info("got response", response);
if (response.type === "error") {
throw new BackgroundError(operation, response.error);
throw new BackgroundError(
`Background operation "${operation}" failed`,
response.error,
);
}
return response.result as any;
}
@ -169,8 +163,10 @@ class WalletApiClientImpl implements WalletCoreApiClient {
}
logger.info("got response", response);
if (response.type === "error") {
const error = TalerError.fromUncheckedDetail(response.error);
throw new WalletError(operation, error);
throw new BackgroundError(
`Wallet operation "${operation}" failed`,
response.error,
);
}
return response.result as any;
}