aboutsummaryrefslogtreecommitdiff
path: root/packages/web-util/src
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-03-05 10:02:32 -0300
committerSebastian <sebasjm@gmail.com>2024-03-05 10:02:32 -0300
commita6b6c6abf3f2c5cc9b20a6204078416ea5fba510 (patch)
tree28eddeffc37c70b1746ba0b3208e6019e6ed5c69 /packages/web-util/src
parent63aedafd841f3a3d7d3b7974d4e5b8fbd02afd3d (diff)
downloadwallet-core-a6b6c6abf3f2c5cc9b20a6204078416ea5fba510.tar.xz
fix #8573
Diffstat (limited to 'packages/web-util/src')
-rw-r--r--packages/web-util/src/components/Attention.tsx62
-rw-r--r--packages/web-util/src/components/Button.tsx3
-rw-r--r--packages/web-util/src/components/GlobalNotificationBanner.tsx11
-rw-r--r--packages/web-util/src/hooks/useNotifications.ts47
4 files changed, 78 insertions, 45 deletions
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 <div class={`group attention-${type} mt-2 shadow-lg`}>
- <div class="rounded-md group-[.attention-info]:bg-blue-50 group-[.attention-warning]:bg-yellow-50 group-[.attention-danger]:bg-red-50 group-[.attention-success]:bg-green-50 p-4 shadow">
+ <style>{`
+ .progress {
+ animation: notificationTimeoutBar 3s ease-in-out;
+ animation-fill-mode:both;
+ }
+
+ @keyframes notificationTimeoutBar {
+ 0% { width: 0; }
+ 100% { width: 100%; }
+ }
+ `}</style>
+
+ <div data-timed={timeout.d_ms !== "forever"} class="rounded-md data-[timed=true]:rounded-b-none group-[.attention-info]:bg-blue-50 group-[.attention-low]:bg-gray-100 group-[.attention-warning]:bg-yellow-50 group-[.attention-danger]:bg-red-50 group-[.attention-success]:bg-green-50 p-4 shadow">
<div class="flex">
<div >
- <svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 group-[.attention-info]:text-blue-400 group-[.attention-warning]:text-yellow-400 group-[.attention-danger]:text-red-400 group-[.attention-success]:text-green-400">
- {(() => {
- switch (type) {
- case "info":
- return <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" />
- case "warning":
- return <path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
- case "danger":
- return <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
- case "success":
- return <path fill-rule="evenodd" d="M7.493 18.75c-.425 0-.82-.236-.975-.632A7.48 7.48 0 016 15.375c0-1.75.599-3.358 1.602-4.634.151-.192.373-.309.6-.397.473-.183.89-.514 1.212-.924a9.042 9.042 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75 2.25 2.25 0 012.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H14.23c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23h-.777zM2.331 10.977a11.969 11.969 0 00-.831 4.398 12 12 0 00.52 3.507c.26.85 1.084 1.368 1.973 1.368H4.9c.445 0 .72-.498.523-.898a8.963 8.963 0 01-.924-3.977c0-1.708.476-3.305 1.302-4.666.245-.403-.028-.959-.5-.959H4.25c-.832 0-1.612.453-1.918 1.227z" />
- default:
- assertUnreachable(type)
- }
- })()}
- </svg>
+ {type === "low" ? undefined :
+ <svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 group-[.attention-info]:text-blue-400 group-[.attention-warning]:text-yellow-400 group-[.attention-danger]:text-red-400 group-[.attention-success]:text-green-400">
+ {(() => {
+ switch (type) {
+ case "info":
+ return <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" />
+ case "warning":
+ return <path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
+ case "danger":
+ return <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
+ case "success":
+ return <path fill-rule="evenodd" d="M7.493 18.75c-.425 0-.82-.236-.975-.632A7.48 7.48 0 016 15.375c0-1.75.599-3.358 1.602-4.634.151-.192.373-.309.6-.397.473-.183.89-.514 1.212-.924a9.042 9.042 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75 2.25 2.25 0 012.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H14.23c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23h-.777zM2.331 10.977a11.969 11.969 0 00-.831 4.398 12 12 0 00.52 3.507c.26.85 1.084 1.368 1.973 1.368H4.9c.445 0 .72-.498.523-.898a8.963 8.963 0 01-.924-3.977c0-1.708.476-3.305 1.302-4.666.245-.403-.028-.959-.5-.959H4.25c-.832 0-1.612.453-1.918 1.227z" />
+ default:
+ assertUnreachable(type)
+ }
+ })()}
+ </svg>
+ }
</div>
<div class="ml-3 w-full">
- <h3 class="text-sm group-hover:text-white font-bold group-[.attention-info]:text-blue-800 group-[.attention-success]:text-green-800 group-[.attention-warning]:text-yellow-800 group-[.attention-danger]:text-red-800">
+ <h3 class="text-sm font-bold group-[.attention-info]:text-blue-800 group-[.attention-success]:text-green-800 group-[.attention-warning]:text-yellow-800 group-[.attention-danger]:text-red-800">
{title}
</h3>
<div class="mt-2 text-sm group-[.attention-info]:text-blue-700 group-[.attention-warning]:text-yellow-700 group-[.attention-danger]:text-red-700 group-[.attention-success]:text-green-700">
@@ -53,6 +68,11 @@ export function Attention({ type = "info", title, children, onClose }: Props): V
}
</div>
</div>
+ {timeout.d_ms === "forever" ? undefined :
+ <div class="meter group-[.attention-info]:bg-blue-50 group-[.attention-low]:bg-gray-100 group-[.attention-warning]:bg-yellow-50 group-[.attention-danger]:bg-red-50 group-[.attention-success]:bg-green-50 h-1 relative overflow-hidden -mt-1">
+ <span class="w-full h-full block"><span class="h-full block progress group-[.attention-info]:bg-blue-600 group-[.attention-low]:bg-gray-600 group-[.attention-warning]:bg-yellow-600 group-[.attention-danger]:bg-red-600 group-[.attention-success]:bg-green-600"></span></span>
+ </div>
+ }
</div>
}
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<T extends OperationResult<A, B>, A, B>({
handler.onClick().then((resp) => {
if (resp) {
if (resp.type === "ok") {
- // @ts-expect-error this is an operationOk
- const result: OperationOk<any> = resp.body
+ const result: OperationOk<any> = 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 <Fragment />
- return <div class="fixed z-20 w-full p-4"> {
+ return <Fragment> {
notifs.map(n => {
switch (n.message.type) {
case "error":
return <Attention type="danger" title={n.message.title} onClose={() => {
n.remove()
- }}>
+ }} timeout={GLOBAL_NOTIFICATION_TIMEOUT}>
{n.message.description &&
<div class="mt-2 text-sm text-red-700">
{n.message.description}
</div>
}
- {/* <MaybeShowDebugInfo info={n.message.debug} /> */}
</Attention>
case "info":
return <Attention type="success" title={n.message.title} onClose={() => {
n.remove();
- }} />
+ }} timeout={GLOBAL_NOTIFICATION_TIMEOUT} />
}
})}
- </div>
+ </Fragment>
}
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<Map<string, NotificationMessage>>();
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<string, NotificationMessage> =
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<NotificationMessage>();
const notif = !value ? undefined : {
@@ -154,12 +169,12 @@ export function useLocalNotification(): [Notification | undefined, (n: Notificat
return [notif, setter, errorHandling]
}
-type HandlerMaker = <T extends OperationResult<A, B>,A,B>(
+type HandlerMaker = <T extends OperationResult<A, B>, A, B>(
onClick: () => Promise<T | undefined>,
- onOperationSuccess: ((result:T extends OperationOk<any> ? T :never) => void) | ((result:T extends OperationOk<any> ? T :never) => TranslatedString | undefined),
+ onOperationSuccess: ((result: T extends OperationOk<any> ? T : never) => void) | ((result: T extends OperationOk<any> ? T : never) => TranslatedString | undefined),
onOperationFail: (d: T extends OperationFail<any> ? T : never) => TranslatedString,
onOperationComplete?: () => void,
-) => ButtonHandler<T,A,B>;
+) => ButtonHandler<T, A, B>;
export function useLocalNotificationHandler(): [Notification | undefined, HandlerMaker, (n: NotificationMessage) => void] {
const [value, setter] = useState<NotificationMessage>();
@@ -169,20 +184,20 @@ export function useLocalNotificationHandler(): [Notification | undefined, Handle
setter(undefined);
},
}
-
- function makeHandler<T extends OperationResult<A, B>,A,B>(
+
+ function makeHandler<T extends OperationResult<A, B>, A, B>(
onClick: () => Promise<T | undefined>,
- onOperationSuccess: ((result:T extends OperationOk<any> ? T :never) => void) | ((result:T extends OperationOk<any> ? T :never) => TranslatedString | undefined),
+ onOperationSuccess: ((result: T extends OperationOk<any> ? T : never) => void) | ((result: T extends OperationOk<any> ? T : never) => TranslatedString | undefined),
onOperationFail: (d: T extends OperationFail<any> ? T : never) => TranslatedString,
- onOperationComplete?: () => void,
- ): ButtonHandler<T,A,B> {
+ onOperationComplete?: () => void,
+ ): ButtonHandler<T, A, B> {
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: {