4a781bd0dd
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
181 lines
4.5 KiB
TypeScript
181 lines
4.5 KiB
TypeScript
/*
|
|
This file is part of GNU Taler
|
|
(C) 2022 Taler Systems S.A.
|
|
|
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
|
terms of the GNU General Public License as published by the Free Software
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
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/>
|
|
*/
|
|
|
|
/**
|
|
*
|
|
* @author Sebastian Javier Marchano (sebasjm)
|
|
*/
|
|
|
|
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 InfoAlert {
|
|
message: TranslatedString;
|
|
description: TranslatedString | VNode;
|
|
type: "info" | "warning" | "success";
|
|
}
|
|
|
|
export type Alert = InfoAlert | ErrorAlert;
|
|
|
|
export interface ErrorAlert {
|
|
message: TranslatedString;
|
|
description: TranslatedString | VNode;
|
|
type: "error";
|
|
context: object;
|
|
cause: any;
|
|
}
|
|
|
|
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;
|
|
},
|
|
removeAlert: () => {
|
|
null;
|
|
},
|
|
};
|
|
|
|
const Context = createContext<Type>(initial);
|
|
|
|
type AlertWithDate = Alert & { since: Date };
|
|
|
|
type Props = Partial<Type> & {
|
|
children: ComponentChildren;
|
|
};
|
|
|
|
export const AlertProvider = ({ children }: Props): VNode => {
|
|
const timeout = 3000;
|
|
|
|
const [alerts, setAlerts] = useState<AlertWithDate[]>([]);
|
|
|
|
const pushAlert = (n: Alert): void => {
|
|
const entry = { ...n, since: new Date() };
|
|
setAlerts((ns) => [...ns, entry]);
|
|
if (n.type !== "error") {
|
|
setTimeout(() => {
|
|
setAlerts((ns) => ns.filter((x) => x.since !== entry.since));
|
|
}, timeout);
|
|
}
|
|
};
|
|
|
|
const removeAlert = (alert: Alert): void => {
|
|
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, pushAlertOnError },
|
|
children,
|
|
});
|
|
};
|
|
|
|
export const useAlertContext = (): Type => useContext(Context);
|
|
|
|
export function alertFromError(
|
|
message: TranslatedString,
|
|
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: TranslatedString;
|
|
let cause: any;
|
|
|
|
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 {
|
|
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,
|
|
context,
|
|
};
|
|
}
|