diff options
author | Sebastian <sebasjm@gmail.com> | 2023-11-05 18:04:22 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-11-05 18:04:22 -0300 |
commit | b58d53dd93bd8e97aecc28fae788c5c7051fd73d (patch) | |
tree | 7a402fafa4ae19a64f10eeb3042147f5f4733081 /packages/web-util | |
parent | 31cf3187e447e2c4ec8a473362c5bacc07a874f1 (diff) | |
download | wallet-core-b58d53dd93bd8e97aecc28fae788c5c7051fd73d.tar.xz |
sharing components in web-util
Diffstat (limited to 'packages/web-util')
-rw-r--r-- | packages/web-util/src/assets/lang.svg | 48 | ||||
-rw-r--r-- | packages/web-util/src/components/Attention.tsx | 58 | ||||
-rw-r--r-- | packages/web-util/src/components/CopyButton.tsx | 46 | ||||
-rw-r--r-- | packages/web-util/src/components/ErrorLoading.tsx | 119 | ||||
-rw-r--r-- | packages/web-util/src/components/LangSelector.tsx | 111 | ||||
-rw-r--r-- | packages/web-util/src/components/Loading.tsx | 45 | ||||
-rw-r--r-- | packages/web-util/src/components/ShowInputErrorLabel.tsx | 29 | ||||
-rw-r--r-- | packages/web-util/src/components/ShowLocalNotification.tsx | 43 | ||||
-rw-r--r-- | packages/web-util/src/components/index.ts | 7 | ||||
-rw-r--r-- | packages/web-util/src/declaration.d.ts | 35 |
10 files changed, 541 insertions, 0 deletions
diff --git a/packages/web-util/src/assets/lang.svg b/packages/web-util/src/assets/lang.svg new file mode 100644 index 000000000..dd72ce65e --- /dev/null +++ b/packages/web-util/src/assets/lang.svg @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 2411.2 2794" style="enable-background:new 0 0 2411.2 2794;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:#FFFFFF;} + .st1{fill-rule:evenodd;clip-rule:evenodd;} + .st2{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;} +</style> +<g id="Layer_2"> +</g> +<g id="Layer_x5F_1_x5F_1"> + <g> + <polygon points="1204.6,359.2 271.8,30 271.8,2060.1 1204.6,1758.3 "/> + <polygon class="st0" points="1182.2,358.1 2150.6,29 2150.6,2059 1182.2,1757.3 "/> + <polygon class="st0" points="30,2415.4 1182.2,2031.4 1182.2,357.9 30,742 "/> + <polygon points="1707.2,2440.7 1870.5,2709.4 1956.6,2459.8 "/> + <g> + <path d="M421.7,934.8c-6.1-6,8,49.1,27.6,68.9c34.8,35.1,61.9,39.6,76.4,40.2c32,1.3,71.5-8,94.9-17.8 + c22.7-9.7,62.4-30,77.5-59.6c3.2-6.3,11.9-17,6.4-43.2c-4.2-20.2-17-27.3-32.7-26.2c-15.7,1.1-63.2,13.7-86.1,20.8 + c-23,7-70.3,21.4-90.9,25.8C474.3,948.2,429,941.7,421.7,934.8z"/> + <path d="M1003.1,1593.7c-9.1-3.3-196.9-81.1-223.6-93.9c-21.8-10.5-75.2-33.1-100.4-43.3c70.8-109.2,115.5-191.6,121.5-204.1 + c11-23,86-169.6,87.7-178.7c1.7-9.1,3.8-42.9,2.2-51c-1.7-8.2-29.1,7.6-66.4,20.2c-37.4,12.6-108.4,58.8-135.8,64.6 + c-27.5,5.7-115.5,39.1-160.5,54c-45,14.9-130.2,40.9-165.2,50.4c-35.1,9.5-65.7,10.2-85.3,16.2c0,0,2.6,27.5,7.8,35.7 + c5.2,8.2,23.7,28.4,45.3,34.1c21.6,5.7,57.3,3.4,73.6-0.3c16.3-3.8,44.4-17.5,48.2-23.6c3.8-6.1-2-24.9,4.5-30.6 + c6.5-5.6,92.2-25.7,124.6-35.4c32.4-10,156.3-52.6,173.1-50.5c-5.3,17.7-105,215.1-137.1,274c-32.1,58.9-218.6,318-258.3,363.6 + c-30.1,34.7-103.2,123.5-128.5,143.6c6.4,1.8,51.6-2.1,59.9-7.2c51.3-31.6,136.9-138.1,164.4-170.5 + c81.9-96,153.8-196.8,210.8-283.4h0.1c11.1,4.6,100.9,77.8,124.4,94c23.4,16.2,115.9,67.8,136,76.4c20,8.7,97.1,44.2,100.3,32.2 + C1029.4,1668,1012.2,1597.1,1003.1,1593.7z"/> + </g> + <path class="st1" d="M569,2572c18,11,35,20,54,29c38,19,81,39,122,54c56,21,112,38,168,51c31,7,65,13,98,18c3,0,92,11,110,11h90 + c35-3,68-5,103-10c28-4,59-9,89-16c22-5,45-10,67-17c21-6,45-14,68-22c15-5,31-12,47-18c13-6,29-13,44-19c18-8,39-19,59-29 + c16-8,34-18,51-28c13-7,43-30,59-30c18,0,30,16,30,30c0,29-39,38-57,51c-19,13-42,23-62,34c-40,21-81,39-120,54 + c-51,19-107,37-157,49c-19,4-38,9-57,12c-10,2-114,18-143,18h-132c-35-3-72-7-107-12c-31-5-64-11-95-18c-24-5-50-12-73-19 + c-40-11-79-25-117-40c-69-26-141-60-209-105c-12-8-13-16-13-25c0-15,11-29,29-29C531,2546,563,2569,569,2572z"/> + <path class="st1" d="M1151,2009L61,2372V764l1090-363V2009z M1212,354v1680c-1,5-3,10-7,15c-2,3-6,7-9,8c-25,10-1151,388-1166,388 + c-12,0-23-8-29-21c0-1-1-2-1-4V739c2-5,3-12,7-16c8-11,22-13,31-16c17-6,1126-378,1142-378C1190,329,1212,336,1212,354z"/> + <path class="st1" d="M2120,2017l-907-282V380l907-308V2017z M2181,32v2023c-1,23-17,33-32,33c-13,0-107-32-123-37 + c-126-39-253-78-378-117c-28-9-57-18-84-27c-24-7-50-15-74-23c-107-33-216-66-323-102c-4-1-14-15-14-18V351c2-5,4-11,9-15 + c8-9,351-123,486-168c36-13,487-168,501-168C2167,0,2181,13,2181,32z"/> + <polygon points="2411.2,2440.7 1199.5,2054.5 1204.6,373.2 2411.2,757.2 "/> + <g> + <path class="st2" d="M1800.3,1124.6L1681.4,1412l218.6,66.3L1800.3,1124.6z M1729,853.2l156.1,47.3l284.4,1025l-160.3-48.7 + l-57.6-210.4L1620.2,1566l-71.3,171.4l-160.4-48.7L1729,853.2z"/> + </g> + </g> +</g> +</svg> diff --git a/packages/web-util/src/components/Attention.tsx b/packages/web-util/src/components/Attention.tsx new file mode 100644 index 000000000..b85230a1b --- /dev/null +++ b/packages/web-util/src/components/Attention.tsx @@ -0,0 +1,58 @@ +import { TranslatedString, assertUnreachable } from "@gnu-taler/taler-util"; +import { ComponentChildren, Fragment, VNode, h } from "preact"; + +interface Props { + type?: "info" | "success" | "warning" | "danger", + onClose?: () => void, + title: TranslatedString, + children?: ComponentChildren, +} +export function Attention({ type = "info", title, children, onClose }: 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"> + <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> + </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"> + {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"> + {children} + </div> + </div> + {onClose && + <div> + <button type="button" class="font-semibold items-center rounded bg-transparent px-2 py-1 text-xs text-gray-900 hover:bg-gray-50" + onClick={(e) => { + e.preventDefault(); + onClose(); + }} + > + <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" /> + </svg> + </button> + </div> + } + </div> + </div> + + </div> +} diff --git a/packages/web-util/src/components/CopyButton.tsx b/packages/web-util/src/components/CopyButton.tsx new file mode 100644 index 000000000..0096da365 --- /dev/null +++ b/packages/web-util/src/components/CopyButton.tsx @@ -0,0 +1,46 @@ +import { h, VNode } from "preact"; +import { useEffect, useState } from "preact/hooks"; + +export function CopyIcon(): VNode { + return ( + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> + <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" /> + </svg> + ) +}; + +export function CopiedIcon(): VNode { + return ( + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> + <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /> + </svg> + ) +}; + +export function CopyButton({ class: clazz, getContent }: { class: string, getContent: () => string }): VNode { + const [copied, setCopied] = useState(false); + function copyText(): void { + navigator.clipboard.writeText(getContent() || ""); + setCopied(true); + } + useEffect(() => { + if (copied) { + setTimeout(() => { + setCopied(false); + }, 1000); + } + }, [copied]); + + if (!copied) { + return ( + <button class={clazz} onClick={copyText} > + <CopyIcon /> + </button> + ); + } + return ( + <button class={clazz} disabled> + <CopiedIcon /> + </button> + ); +} diff --git a/packages/web-util/src/components/ErrorLoading.tsx b/packages/web-util/src/components/ErrorLoading.tsx new file mode 100644 index 000000000..02f2a3282 --- /dev/null +++ b/packages/web-util/src/components/ErrorLoading.tsx @@ -0,0 +1,119 @@ +/* +/* + 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/> + */ + +import { TalerError, TalerErrorCode, assertUnreachable } from "@gnu-taler/taler-util"; +import { Fragment, VNode, h } from "preact"; +import { Attention } from "./Attention.js"; +import { useTranslationContext } from "../index.browser.js"; + +export function ErrorLoading({ error, showDetail }: { error: TalerError, showDetail?: boolean }): VNode { + const { i18n } = useTranslationContext() + switch (error.errorDetail.code) { + ////////////////// + // Every error that can be produce in a Http Request + ////////////////// + case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: { + if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_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.WALLET_HTTP_REQUEST_THROTTLED: { + if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED)) { + const { requestMethod, requestUrl, throttleStats } = error.errorDetail + return <Attention type="danger" title={i18n.str`A lot of request were made to the same server and this action was throttled`}> + {error.message} + {showDetail && + <pre class="whitespace-break-spaces "> + {JSON.stringify({ requestMethod, requestUrl, throttleStats }, undefined, 2)} + </pre> + } + </Attention> + } + assertUnreachable(1 as never) + } + case TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE: { + if (error.hasErrorCode(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE)) { + const { requestMethod, requestUrl, httpStatusCode, validationError } = error.errorDetail + return <Attention type="danger" title={i18n.str`The response of the request is malformed.`}> + {error.message} + {showDetail && + <pre class="whitespace-break-spaces "> + {JSON.stringify({ requestMethod, requestUrl, httpStatusCode, validationError }, undefined, 2)} + </pre> + } + </Attention> + } + assertUnreachable(1 as never) + } + case TalerErrorCode.WALLET_NETWORK_ERROR: { + if (error.hasErrorCode(TalerErrorCode.WALLET_NETWORK_ERROR)) { + const { requestMethod, requestUrl } = error.errorDetail + return <Attention type="danger" title={i18n.str`Could not complete the request due to a network problem.`}> + {error.message} + {showDetail && + <pre class="whitespace-break-spaces "> + {JSON.stringify({ requestMethod, requestUrl }, undefined, 2)} + </pre> + } + </Attention> + } + assertUnreachable(1 as never) + } + case TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR: { + if (error.hasErrorCode(TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR)) { + const { requestMethod, requestUrl, httpStatusCode, errorResponse } = error.errorDetail + return <Attention type="danger" title={i18n.str`Unexpected request error`}> + {error.message} + {showDetail && + <pre class="whitespace-break-spaces "> + {JSON.stringify({ requestMethod, requestUrl, httpStatusCode, errorResponse }, undefined, 2)} + </pre> + } + </Attention> + } + assertUnreachable(1 as never) + } + ////////////////// + // Every other error + ////////////////// + // case TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR: { + // return <Attention type="danger" title={i18n.str``}> + // </Attention> + // } + ////////////////// + // Default message for unhandled case + ////////////////// + default: return <Attention type="danger" title={i18n.str`Unexpected error`}> + {error.message} + {showDetail && + <pre class="whitespace-break-spaces "> + {JSON.stringify(error.errorDetail, undefined, 2)} + </pre> + } + </Attention> + } +} + diff --git a/packages/web-util/src/components/LangSelector.tsx b/packages/web-util/src/components/LangSelector.tsx new file mode 100644 index 000000000..a8d910129 --- /dev/null +++ b/packages/web-util/src/components/LangSelector.tsx @@ -0,0 +1,111 @@ +/* + 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 { Fragment, h, VNode } from "preact"; +import { useEffect, useState } from "preact/hooks"; +// import { strings as messages } from "../i18n/strings.js"; +import langIcon from "../assets/lang.svg"; +import { useTranslationContext } from "../index.browser.js"; + +type LangsNames = { + [P: string]: string; +}; + +const names: LangsNames = { + es: "Español [es]", + en: "English [en]", + fr: "Français [fr]", + de: "Deutsch [de]", + sv: "Svenska [sv]", + it: "Italiano [it]", +}; + +function getLangName(s: keyof LangsNames | string): string { + if (names[s]) return names[s]; + return String(s); +} + +export function LangSelector({ supportedLangs }: { supportedLangs: string[] }): VNode { + const [updatingLang, setUpdatingLang] = useState(false); + const { lang, changeLanguage } = useTranslationContext(); + const [hidden, setHidden] = useState(true); + + useEffect(() => { + function bodyKeyPress(event: KeyboardEvent) { + if (event.code === "Escape") setHidden(true); + } + function bodyOnClick(event: Event) { + setHidden(true); + } + document.body.addEventListener("click", bodyOnClick); + document.body.addEventListener("keydown", bodyKeyPress as any); + return () => { + document.body.removeEventListener("keydown", bodyKeyPress as any); + document.body.removeEventListener("click", bodyOnClick); + }; + }, []); + return ( + <div> + <div class="relative mt-2"> + <button type="button" class="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" + onClick={() => { + setHidden((h) => !h); + }}> + <span class="flex items-center"> + <img alt="language" class="h-5 w-5 flex-shrink-0 rounded-full" src={langIcon} /> + <span class="ml-3 block truncate">{getLangName(lang)}</span> + </span> + <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> + <svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd" /> + </svg> + </span> + </button> + + {!hidden && + <ul class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" tabIndex={-1} role="listbox" aria-labelledby="listbox-label" aria-activedescendant="listbox-option-3"> + {supportedLangs + .filter((l) => l !== lang) + .map((lang) => ( + <li class="text-gray-900 hover:bg-indigo-600 hover:text-white cursor-pointer relative select-none py-2 pl-3 pr-9" role="option" + onClick={() => { + changeLanguage(lang); + setUpdatingLang(false); + setHidden(true) + }} + > + <span class="font-normal block truncate">{getLangName(lang)}</span> + + <span class="text-indigo-600 absolute inset-y-0 right-0 flex items-center pr-4"> + {/* <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" /> + </svg> */} + </span> + </li> + ))} + + </ul> + } + + </div> + </div> + ); +} diff --git a/packages/web-util/src/components/Loading.tsx b/packages/web-util/src/components/Loading.tsx new file mode 100644 index 000000000..b567e9056 --- /dev/null +++ b/packages/web-util/src/components/Loading.tsx @@ -0,0 +1,45 @@ +/* + 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/> + */ + +import { h, VNode } from "preact"; + +export function Loading(): VNode { + return ( + <div + class="columns is-centered is-vcentered" + style={{ + width: "100%", + height: "200px", + display: "flex", + margin: "auto", + justifyContent: "center", + }} + > + <Spinner /> + </div> + ); +} + +export function Spinner(): VNode { + return ( + <div class="lds-ring" style={{margin:"auto"}}> + <div /> + <div /> + <div /> + <div /> + </div> + ); +} diff --git a/packages/web-util/src/components/ShowInputErrorLabel.tsx b/packages/web-util/src/components/ShowInputErrorLabel.tsx new file mode 100644 index 000000000..c5840cad9 --- /dev/null +++ b/packages/web-util/src/components/ShowInputErrorLabel.tsx @@ -0,0 +1,29 @@ +/* + 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/> + */ + +import { Fragment, h, VNode } from "preact"; + +export function ShowInputErrorLabel({ + isDirty, + message, +}: { + message: string | undefined; + isDirty: boolean; +}): VNode { + if (message && isDirty) + return <div class="text-base" style={{ color: "red" }}>{message}</div>; + return <div class="text-base" style={{ }}> </div>; +} diff --git a/packages/web-util/src/components/ShowLocalNotification.tsx b/packages/web-util/src/components/ShowLocalNotification.tsx new file mode 100644 index 000000000..cb947e536 --- /dev/null +++ b/packages/web-util/src/components/ShowLocalNotification.tsx @@ -0,0 +1,43 @@ +import { h, Fragment, VNode } from "preact"; +import { Attention } from "./Attention.js"; +import { Notification } from "../index.browser.js"; +// import { useSettings } from "../hooks/settings.js"; + +export function ShowLocalNotification({ notification }: { notification?: Notification }): VNode { + if (!notification) return <Fragment /> + switch (notification.message.type) { + case "error": + 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.message.description && + <div class="mt-2 text-sm text-red-700"> + {notification.message.description} + </div> + } + {/* <MaybeShowDebugInfo info={notification.message.debug} /> */} + </Attention> + </div> + </div> + case "info": + 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(); + }} /></div></div> + } +} + + +// function MaybeShowDebugInfo({ info }: { info: any }): VNode { +// const [settings] = useSettings() +// if (settings.showDebugInfo) { +// return <pre class="whitespace-break-spaces "> +// {info} +// </pre> +// } +// return <Fragment /> +// } + diff --git a/packages/web-util/src/components/index.ts b/packages/web-util/src/components/index.ts index 9441e971d..8344d4a7a 100644 --- a/packages/web-util/src/components/index.ts +++ b/packages/web-util/src/components/index.ts @@ -1 +1,8 @@ export * as utils from "./utils.js"; +export * from "./Attention.js"; +export * from "./CopyButton.js"; +export * from "./ErrorLoading.js"; +export * from "./LangSelector.js"; +export * from "./Loading.js"; +export * from "./ShowInputErrorLabel.js"; +export * from "./ShowLocalNotification.js"; diff --git a/packages/web-util/src/declaration.d.ts b/packages/web-util/src/declaration.d.ts new file mode 100644 index 000000000..c8ba3d576 --- /dev/null +++ b/packages/web-util/src/declaration.d.ts @@ -0,0 +1,35 @@ +/* + 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/> + */ + +declare module "*.css" { + const mapping: Record<string, string>; + export default mapping; +} +declare module "*.svg" { + const content: any; + export default content; +} +declare module "*.jpeg" { + const content: any; + export default content; +} +declare module "*.png" { + const content: any; + export default content; +} + +declare const __VERSION__: string; +declare const __GIT_HASH__: string; |