From a6b6c6abf3f2c5cc9b20a6204078416ea5fba510 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 5 Mar 2024 10:02:32 -0300 Subject: fix #8573 --- packages/web-util/src/components/Attention.tsx | 62 ++++++++++++++-------- packages/web-util/src/components/Button.tsx | 3 +- .../src/components/GlobalNotificationBanner.tsx | 11 ++-- packages/web-util/src/hooks/useNotifications.ts | 47 ++++++++++------ 4 files changed, 78 insertions(+), 45 deletions(-) (limited to 'packages/web-util/src') diff --git a/packages/web-util/src/components/Attention.tsx b/packages/web-util/src/components/Attention.tsx index b85230a1b..50378e85a 100644 --- a/packages/web-util/src/components/Attention.tsx +++ b/packages/web-util/src/components/Attention.tsx @@ -1,36 +1,51 @@ -import { TranslatedString, assertUnreachable } from "@gnu-taler/taler-util"; +import { Duration, TranslatedString, assertUnreachable } from "@gnu-taler/taler-util"; import { ComponentChildren, Fragment, VNode, h } from "preact"; interface Props { - type?: "info" | "success" | "warning" | "danger", + type?: "info" | "success" | "warning" | "danger" | "low", onClose?: () => void, title: TranslatedString, children?: ComponentChildren, + timeout?: Duration, } -export function Attention({ type = "info", title, children, onClose }: Props): VNode { +export function Attention({ type = "info", title, children, onClose, timeout = Duration.getForever() }: Props): VNode { return
-
+ + +
- - {(() => { - switch (type) { - case "info": - return - case "warning": - return - case "danger": - return - case "success": - return - default: - assertUnreachable(type) - } - })()} - + {type === "low" ? undefined : + + {(() => { + switch (type) { + case "info": + return + case "warning": + return + case "danger": + return + case "success": + return + default: + assertUnreachable(type) + } + })()} + + }
-

+

{title}

@@ -53,6 +68,11 @@ export function Attention({ type = "info", title, children, onClose }: Props): V }
+ {timeout.d_ms === "forever" ? undefined : +
+ +
+ }
} diff --git a/packages/web-util/src/components/Button.tsx b/packages/web-util/src/components/Button.tsx index 758efafcf..26b778eec 100644 --- a/packages/web-util/src/components/Button.tsx +++ b/packages/web-util/src/components/Button.tsx @@ -45,8 +45,7 @@ export function Button, A, B>({ handler.onClick().then((resp) => { if (resp) { if (resp.type === "ok") { - // @ts-expect-error this is an operationOk - const result: OperationOk = resp.body + const result: OperationOk = resp // @ts-expect-error this is an operationOk const msg = handler.onOperationSuccess(result) if (msg) { diff --git a/packages/web-util/src/components/GlobalNotificationBanner.tsx b/packages/web-util/src/components/GlobalNotificationBanner.tsx index c8049acc3..b0a06f7e1 100644 --- a/packages/web-util/src/components/GlobalNotificationBanner.tsx +++ b/packages/web-util/src/components/GlobalNotificationBanner.tsx @@ -1,28 +1,27 @@ import { Fragment, VNode, h } from "preact" -import { Attention, useNotifications } from "../index.browser.js" +import { Attention, GLOBAL_NOTIFICATION_TIMEOUT, useNotifications } from "../index.browser.js" export function GlobalNotificationsBanner(): VNode { const notifs = useNotifications() if (notifs.length === 0) return - return
{ + return { notifs.map(n => { switch (n.message.type) { case "error": return { n.remove() - }}> + }} timeout={GLOBAL_NOTIFICATION_TIMEOUT}> {n.message.description &&
{n.message.description}
} - {/* */}
case "info": return { n.remove(); - }} /> + }} timeout={GLOBAL_NOTIFICATION_TIMEOUT} /> } })} -
+
} diff --git a/packages/web-util/src/hooks/useNotifications.ts b/packages/web-util/src/hooks/useNotifications.ts index 33e0cdf53..9f955f92d 100644 --- a/packages/web-util/src/hooks/useNotifications.ts +++ b/packages/web-util/src/hooks/useNotifications.ts @@ -1,4 +1,4 @@ -import { OperationFail, OperationOk, OperationResult, TalerError, TalerErrorCode, TranslatedString } from "@gnu-taler/taler-util"; +import { Duration, OperationFail, OperationOk, OperationResult, TalerError, TalerErrorCode, TranslatedString } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; import { ButtonHandler } from "../components/Button.js"; import { InternationalizationAPI, memoryMap, useTranslationContext } from "../index.browser.js"; @@ -19,10 +19,28 @@ export interface InfoNotification { const storage = memoryMap>(); const NOTIFICATION_KEY = "notification"; +export const GLOBAL_NOTIFICATION_TIMEOUT: Duration = { d_ms: 3 * 1000 } + +function removeFromStorage(n: NotificationMessage) { + const h = hash(n) + const mem = storage.get(NOTIFICATION_KEY) ?? new Map(); + const newState = new Map(mem); + newState.delete(h); + storage.set(NOTIFICATION_KEY, newState); +} + + export function notify(notif: NotificationMessage): void { const currentState: Map = storage.get(NOTIFICATION_KEY) ?? new Map(); const newState = currentState.set(hash(notif), notif); + + if (GLOBAL_NOTIFICATION_TIMEOUT.d_ms !== "forever") { + setTimeout(() => { + removeFromStorage(notif) + }, GLOBAL_NOTIFICATION_TIMEOUT.d_ms); + } + storage.set(NOTIFICATION_KEY, newState); } export function notifyError( @@ -73,10 +91,7 @@ export function useNotifications(): Notification[] { return { message, remove: () => { - const mem = storage.get(NOTIFICATION_KEY) ?? new Map(); - const newState = new Map(mem); - newState.delete(hash(message)); - storage.set(NOTIFICATION_KEY, newState); + removeFromStorage(message) }, }; }); @@ -124,7 +139,7 @@ export type ErrorNotificationHandler = (cb: (notify: typeof errorMap) => Promise * @returns */ export function useLocalNotification(): [Notification | undefined, (n: NotificationMessage) => void, ErrorNotificationHandler] { - const {i18n} = useTranslationContext(); + const { i18n } = useTranslationContext(); const [value, setter] = useState(); const notif = !value ? undefined : { @@ -154,12 +169,12 @@ export function useLocalNotification(): [Notification | undefined, (n: Notificat return [notif, setter, errorHandling] } -type HandlerMaker = ,A,B>( +type HandlerMaker = , A, B>( onClick: () => Promise, - onOperationSuccess: ((result:T extends OperationOk ? T :never) => void) | ((result:T extends OperationOk ? T :never) => TranslatedString | undefined), + onOperationSuccess: ((result: T extends OperationOk ? T : never) => void) | ((result: T extends OperationOk ? T : never) => TranslatedString | undefined), onOperationFail: (d: T extends OperationFail ? T : never) => TranslatedString, onOperationComplete?: () => void, -) => ButtonHandler; +) => ButtonHandler; export function useLocalNotificationHandler(): [Notification | undefined, HandlerMaker, (n: NotificationMessage) => void] { const [value, setter] = useState(); @@ -169,20 +184,20 @@ export function useLocalNotificationHandler(): [Notification | undefined, Handle setter(undefined); }, } - - function makeHandler,A,B>( + + function makeHandler, A, B>( onClick: () => Promise, - onOperationSuccess: ((result:T extends OperationOk ? T :never) => void) | ((result:T extends OperationOk ? T :never) => TranslatedString | undefined), + onOperationSuccess: ((result: T extends OperationOk ? T : never) => void) | ((result: T extends OperationOk ? T : never) => TranslatedString | undefined), onOperationFail: (d: T extends OperationFail ? T : never) => TranslatedString, - onOperationComplete?: () => void, - ): ButtonHandler { + onOperationComplete?: () => void, + ): ButtonHandler { return { onClick, onNotification: setter, onOperationFail, onOperationSuccess, onOperationComplete } } - + return [notif, makeHandler, setter] } -export function buildRequestErrorMessage( i18n: InternationalizationAPI, cause: TalerError): ErrorNotification { +export function buildRequestErrorMessage(i18n: InternationalizationAPI, cause: TalerError): ErrorNotification { let result: ErrorNotification; switch (cause.errorDetail.code) { case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: { -- cgit v1.2.3