diff options
author | Sebastian <sebasjm@gmail.com> | 2024-03-11 14:57:03 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-03-11 14:57:49 -0300 |
commit | 6e02a3852590f39cdd414a1caf89506bcd9dc83a (patch) | |
tree | 3fddf6ae92eaf3abed6a702531f2d3536e023449 /packages/web-util/src/components | |
parent | 37f46f4d6b821d163c3e4db5c374b1120212ac74 (diff) | |
download | wallet-core-6e02a3852590f39cdd414a1caf89506bcd9dc83a.tar.xz |
obs and cancel request, plus lint
Diffstat (limited to 'packages/web-util/src/components')
-rw-r--r-- | packages/web-util/src/components/Button.tsx | 36 | ||||
-rw-r--r-- | packages/web-util/src/components/CopyButton.tsx | 5 | ||||
-rw-r--r-- | packages/web-util/src/components/ErrorLoading.tsx | 28 | ||||
-rw-r--r-- | packages/web-util/src/components/Header.tsx | 39 | ||||
-rw-r--r-- | packages/web-util/src/components/NotificationBanner.tsx | 4 | ||||
-rw-r--r-- | packages/web-util/src/components/ToastBanner.tsx | 59 |
6 files changed, 133 insertions, 38 deletions
diff --git a/packages/web-util/src/components/Button.tsx b/packages/web-util/src/components/Button.tsx index 26b778eec..ea0ea2f38 100644 --- a/packages/web-util/src/components/Button.tsx +++ b/packages/web-util/src/components/Button.tsx @@ -1,8 +1,24 @@ -import { OperationFail, OperationOk, OperationResult, TalerError, TranslatedString } from "@gnu-taler/taler-util"; +/* + This file is part of GNU Taler + (C) 2022-2024 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/> + */ + +import { AbsoluteTime, OperationFail, OperationOk, OperationResult, TalerError, TranslatedString } from "@gnu-taler/taler-util"; // import { NotificationMessage, notifyInfo } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { HTMLAttributes, useEffect, useState, useTransition } from "preact/compat"; -import { NotificationMessage, buildRequestErrorMessage, notifyInfo, useTranslationContext } from "../index.browser.js"; +import { NotificationMessage, buildUnifiedRequestErrorMessage, notifyInfo, useTranslationContext } from "../index.browser.js"; // import { useBankCoreApiContext } from "../context/config.js"; // function errorMap<T extends OperationFail<unknown>>(resp: T, map: (d: T["case"]) => TranslatedString): void { @@ -10,7 +26,7 @@ import { NotificationMessage, buildRequestErrorMessage, notifyInfo, useTranslati export interface ButtonHandler<T extends OperationResult<A, B>, A, B> { onClick: () => Promise<T | undefined>, onNotification: (n: NotificationMessage) => void; - 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; } @@ -33,10 +49,10 @@ export function Button<T extends OperationResult<A, B>, A, B>({ handler, children, disabled, - onClick:clickEvent, + onClick: clickEvent, ...rest }: Props<T, A, B>): VNode { - const {i18n} = useTranslationContext(); + const { i18n } = useTranslationContext(); const [running, setRunning] = useState(false) return <button {...rest} disabled={disabled || running} onClick={(e) => { e.preventDefault(); @@ -62,6 +78,7 @@ export function Button<T extends OperationResult<A, B>, A, B>({ type: "error", description: error.detail.hint as TranslatedString, debug: error.detail, + when: AbsoluteTime.now(), }) } } @@ -71,17 +88,18 @@ export function Button<T extends OperationResult<A, B>, A, B>({ setRunning(false) }).catch(error => { console.error(error) - + if (error instanceof TalerError) { - handler.onNotification(buildRequestErrorMessage(i18n, error)) + handler.onNotification(buildUnifiedRequestErrorMessage(i18n, error)) } else { const description = (error instanceof Error ? error.message : String(error)) as TranslatedString - + handler.onNotification({ title: i18n.str`Operation failed`, type: "error", description, + when: AbsoluteTime.now(), }) } @@ -95,7 +113,7 @@ export function Button<T extends OperationResult<A, B>, A, B>({ </button> } -function Wait():VNode { +function Wait(): VNode { return <Fragment> <style>{` #l1 { width: 120px; diff --git a/packages/web-util/src/components/CopyButton.tsx b/packages/web-util/src/components/CopyButton.tsx index e76447291..fd7f8b3b4 100644 --- a/packages/web-util/src/components/CopyButton.tsx +++ b/packages/web-util/src/components/CopyButton.tsx @@ -38,7 +38,10 @@ export function CopyButton({ class: clazz, getContent }: { class: string, getCon if (!copied) { return ( - <button class={clazz} onClick={copyText} > + <button class={clazz} onClick={e => { + e.preventDefault() + copyText() + }} > <CopyIcon /> </button> ); diff --git a/packages/web-util/src/components/ErrorLoading.tsx b/packages/web-util/src/components/ErrorLoading.tsx index 02f2a3282..7089266b9 100644 --- a/packages/web-util/src/components/ErrorLoading.tsx +++ b/packages/web-util/src/components/ErrorLoading.tsx @@ -26,6 +26,34 @@ export function ErrorLoading({ error, showDetail }: { error: TalerError, showDet ////////////////// // Every error that can be produce in a Http Request ////////////////// + case TalerErrorCode.GENERIC_TIMEOUT: { + if (error.hasErrorCode(TalerErrorCode.GENERIC_TIMEOUT)) { + const { requestMethod, requestUrl, timeoutMs } = error.errorDetail + return <Attention type="danger" title={i18n.str`The request reached a timeout, check your connection.`}> + {error.message} + {showDetail && + <pre class="whitespace-break-spaces "> + {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)} + </pre> + } + </Attention> + } + assertUnreachable(1 as never) + } + case TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR: { + if (error.hasErrorCode(TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR)) { + const { requestMethod, requestUrl, timeoutMs } = error.errorDetail + return <Attention type="danger" title={i18n.str`The request was cancelled.`}> + {error.message} + {showDetail && + <pre class="whitespace-break-spaces "> + {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)} + </pre> + } + </Attention> + } + assertUnreachable(1 as never) + } case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: { if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT)) { const { requestMethod, requestUrl, timeoutMs } = error.errorDetail diff --git a/packages/web-util/src/components/Header.tsx b/packages/web-util/src/components/Header.tsx index fc7716320..29f4a4949 100644 --- a/packages/web-util/src/components/Header.tsx +++ b/packages/web-util/src/components/Header.tsx @@ -1,12 +1,24 @@ import { useState } from "preact/hooks"; -import { LangSelector, useTranslationContext } from "../index.browser.js"; +import { LangSelector, useNotifications, useTranslationContext } from "../index.browser.js"; import { ComponentChildren, Fragment, VNode, h } from "preact"; import logo from "../assets/logo-2021.svg"; -export function Header({ title, profileURL, iconLinkURL, sites, onLogout, children }: - { title: string, iconLinkURL: string, profileURL?: string, children?: ComponentChildren, onLogout: (() => void) | undefined, sites: Array<Array<string>>, supportedLangs: string[] }): VNode { +interface Props { + title: string; + iconLinkURL: string; + profileURL?: string; + notificationURL?: string; + children?: ComponentChildren; + onLogout: (() => void) | undefined; + sites: Array<Array<string>>; + supportedLangs: string[] +} + +export function Header({ title, profileURL, notificationURL, iconLinkURL, sites, onLogout, children }: Props): VNode { const { i18n } = useTranslationContext(); const [open, setOpen] = useState(false) + const ns = useNotifications(); + return <Fragment> <header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400"> <div class="flex flex-row h-16 items-center "> @@ -35,6 +47,22 @@ export function Header({ title, profileURL, iconLinkURL, sites, onLogout, childr </div> </div> <div class="flex justify-end"> + {!notificationURL ? undefined : + <a href={notificationURL} name="notifications" class="relative inline-flex items-center justify-center rounded-md bg-indigo-600 p-1 mr-2 text-indigo-200 hover:bg-indigo-500 hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600" aria-controls="mobile-menu" aria-expanded="false"> + <span class="absolute -inset-0.5"></span> + <span class="sr-only"><i18n.Translate>Show notifications</i18n.Translate></span> + {ns.length > 0 ? + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-10 h-10"> + <path d="M5.85 3.5a.75.75 0 0 0-1.117-1 9.719 9.719 0 0 0-2.348 4.876.75.75 0 0 0 1.479.248A8.219 8.219 0 0 1 5.85 3.5ZM19.267 2.5a.75.75 0 1 0-1.118 1 8.22 8.22 0 0 1 1.987 4.124.75.75 0 0 0 1.48-.248A9.72 9.72 0 0 0 19.266 2.5Z" /> + <path fill-rule="evenodd" d="M12 2.25A6.75 6.75 0 0 0 5.25 9v.75a8.217 8.217 0 0 1-2.119 5.52.75.75 0 0 0 .298 1.206c1.544.57 3.16.99 4.831 1.243a3.75 3.75 0 1 0 7.48 0 24.583 24.583 0 0 0 4.83-1.244.75.75 0 0 0 .298-1.205 8.217 8.217 0 0 1-2.118-5.52V9A6.75 6.75 0 0 0 12 2.25ZM9.75 18c0-.034 0-.067.002-.1a25.05 25.05 0 0 0 4.496 0l.002.1a2.25 2.25 0 1 1-4.5 0Z" clip-rule="evenodd" /> + </svg> + : + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-10 h-10"> + <path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" /> + </svg> + } + </a> + } {!profileURL ? undefined : <a href={profileURL} name="profile" class="relative inline-flex items-center justify-center rounded-md bg-indigo-600 p-1 mr-2 text-indigo-200 hover:bg-indigo-500 hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600" aria-controls="mobile-menu" aria-expanded="false"> <span class="absolute -inset-0.5"></span> @@ -58,7 +86,8 @@ export function Header({ title, profileURL, iconLinkURL, sites, onLogout, childr </div> </header> - {open && + { + open && <div class="relative z-10" name="sidebar overlay" aria-labelledby="slide-over-title" role="dialog" aria-modal="true" onClick={() => { setOpen(false) @@ -150,5 +179,5 @@ export function Header({ title, profileURL, iconLinkURL, sites, onLogout, childr </div> </div> } - </Fragment> + </Fragment > } diff --git a/packages/web-util/src/components/NotificationBanner.tsx b/packages/web-util/src/components/NotificationBanner.tsx index 62733ab3c..31d5a5d01 100644 --- a/packages/web-util/src/components/NotificationBanner.tsx +++ b/packages/web-util/src/components/NotificationBanner.tsx @@ -9,7 +9,7 @@ export function LocalNotificationBanner({ notification, showDebug }: { notificat return <div class="relative"> <div class="fixed top-0 left-0 right-0 z-20 w-full p-4"> <Attention type="danger" title={notification.message.title} onClose={() => { - notification.remove() + notification.acknowledge() }}> {notification.message.description && <div class="mt-2 text-sm text-red-700"> @@ -26,7 +26,7 @@ export function LocalNotificationBanner({ notification, showDebug }: { notificat return <div class="relative"> <div class="fixed top-0 left-0 right-0 z-20 w-full p-4"> <Attention type="success" title={notification.message.title} onClose={() => { - notification.remove(); + notification.acknowledge(); }} /></div></div> } } diff --git a/packages/web-util/src/components/ToastBanner.tsx b/packages/web-util/src/components/ToastBanner.tsx index 2424b17ff..ece26285f 100644 --- a/packages/web-util/src/components/ToastBanner.tsx +++ b/packages/web-util/src/components/ToastBanner.tsx @@ -1,5 +1,20 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 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/> + */ import { Fragment, VNode, h } from "preact" -import { Attention, GLOBAL_NOTIFICATION_TIMEOUT as GLOBAL_TOAST_TIMEOUT, useNotifications } from "../index.browser.js" +import { Attention, GLOBAL_NOTIFICATION_TIMEOUT as GLOBAL_TOAST_TIMEOUT, Notification, useNotifications } from "../index.browser.js" /** * Toasts should be considered when displaying these types of information to the user: @@ -21,24 +36,26 @@ import { Attention, GLOBAL_NOTIFICATION_TIMEOUT as GLOBAL_TOAST_TIMEOUT, useNoti export function ToastBanner(): VNode { const notifs = useNotifications() if (notifs.length === 0) return <Fragment /> - return <Fragment> { - notifs.map(n => { - switch (n.message.type) { - case "error": - return <Attention type="danger" title={n.message.title} onClose={() => { - n.remove() - }} timeout={GLOBAL_TOAST_TIMEOUT}> - {n.message.description && - <div class="mt-2 text-sm text-red-700"> - {n.message.description} - </div> - } - </Attention> - case "info": - return <Attention type="success" title={n.message.title} onClose={() => { - n.remove(); - }} timeout={GLOBAL_TOAST_TIMEOUT} /> - } - })} - </Fragment> + const show = notifs.filter(e => !e.message.ack && !e.message.timeout) + if (show.length === 0) return <Fragment /> + return <AttentionByType msg={show[0]} /> +} + +function AttentionByType({ msg }: { msg: Notification }) { + switch (msg.message.type) { + case "error": + return <Attention type="danger" title={msg.message.title} onClose={() => { + msg.acknowledge() + }} timeout={GLOBAL_TOAST_TIMEOUT}> + {msg.message.description && + <div class="mt-2 text-sm text-red-700"> + {msg.message.description} + </div> + } + </Attention> + case "info": + return <Attention type="success" title={msg.message.title} onClose={() => { + msg.acknowledge(); + }} timeout={GLOBAL_TOAST_TIMEOUT} /> + } } |