diff options
104 files changed, 1076 insertions, 1030 deletions
diff --git a/packages/taler-wallet-webextension/package.json b/packages/taler-wallet-webextension/package.json index 8a6c19406..87fb27a72 100644 --- a/packages/taler-wallet-webextension/package.json +++ b/packages/taler-wallet-webextension/package.json @@ -69,7 +69,7 @@ "preact-cli": "^3.3.5", "preact-render-to-string": "^5.1.19", "rimraf": "^3.0.2", - "typescript": "^4.8.4" + "typescript": "4.9.4" }, "nyc": { "include": [ diff --git a/packages/taler-wallet-webextension/src/components/AmountField.stories.tsx b/packages/taler-wallet-webextension/src/components/AmountField.stories.tsx index 9a1d96014..61c4a7661 100644 --- a/packages/taler-wallet-webextension/src/components/AmountField.stories.tsx +++ b/packages/taler-wallet-webextension/src/components/AmountField.stories.tsx @@ -49,7 +49,7 @@ function RenderAmount(): VNode { <Fragment> <AmountField required - label={<i18n.Translate>Amount</i18n.Translate>} + label={i18n.str`Amount`} highestDenom={2000000} lowestDenom={0.01} handler={handler} diff --git a/packages/taler-wallet-webextension/src/components/AmountField.tsx b/packages/taler-wallet-webextension/src/components/AmountField.tsx index 2e8942f0d..4936e0604 100644 --- a/packages/taler-wallet-webextension/src/components/AmountField.tsx +++ b/packages/taler-wallet-webextension/src/components/AmountField.tsx @@ -21,6 +21,7 @@ import { amountMaxValue, Amounts, Result, + TranslatedString, } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -37,7 +38,7 @@ export function AmountField({ highestDenom = 1, required, }: { - label: VNode; + label: TranslatedString; lowestDenom?: number; highestDenom?: number; required?: boolean; diff --git a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx index 6f4980aff..d233547a4 100644 --- a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx +++ b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx @@ -19,6 +19,7 @@ import { Amounts, PaytoUri, segwitMinAmount, + TranslatedString, } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; @@ -106,27 +107,18 @@ export function BankDetailsByPaytoType({ } const accountPart = !payto.isKnown ? ( - <Row - name={<i18n.Translate>Account</i18n.Translate>} - value={payto.targetPath} - /> + <Row name={i18n.str`Account`} value={payto.targetPath} /> ) : payto.targetType === "x-taler-bank" ? ( <Fragment> - <Row - name={<i18n.Translate>Bank host</i18n.Translate>} - value={payto.host} - /> - <Row - name={<i18n.Translate>Bank account</i18n.Translate>} - value={payto.account} - /> + <Row name={i18n.str`Bank host`} value={payto.host} /> + <Row name={i18n.str`Bank account`} value={payto.account} /> </Fragment> ) : payto.targetType === "iban" ? ( <Fragment> {payto.bic !== undefined ? ( - <Row name={<i18n.Translate>BIC</i18n.Translate>} value={payto.bic} /> + <Row name={i18n.str`BIC`} value={payto.bic} /> ) : undefined} - <Row name={<i18n.Translate>IBAN</i18n.Translate>} value={payto.iban} /> + <Row name={i18n.str`IBAN`} value={payto.iban} /> </Fragment> ) : undefined; @@ -146,19 +138,12 @@ export function BankDetailsByPaytoType({ <table> {accountPart} <Row - name={<i18n.Translate>Amount</i18n.Translate>} + name={i18n.str`Amount`} value={<Amount value={amount} hideCurrency />} /> - <Row - name={<i18n.Translate>Subject</i18n.Translate>} - value={subject} - literal - /> + <Row name={i18n.str`Subject`} value={subject} literal /> {receiver ? ( - <Row - name={<i18n.Translate>Receiver name</i18n.Translate>} - value={receiver} - /> + <Row name={i18n.str`Receiver name`} value={receiver} /> ) : undefined} </table> </div> @@ -200,7 +185,7 @@ function Row({ value, literal, }: { - name: VNode; + name: TranslatedString; value: string | VNode; literal?: boolean; }): VNode { diff --git a/packages/taler-wallet-webextension/src/components/Banner.tsx b/packages/taler-wallet-webextension/src/components/Banner.tsx index a91fd384f..40a4847b8 100644 --- a/packages/taler-wallet-webextension/src/components/Banner.tsx +++ b/packages/taler-wallet-webextension/src/components/Banner.tsx @@ -13,6 +13,7 @@ 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 { TranslatedString } from "@gnu-taler/taler-util"; import { ComponentChildren, Fragment, h, JSX, VNode } from "preact"; import { Button } from "../mui/Button.js"; import { Divider } from "../mui/Divider.js"; @@ -20,7 +21,7 @@ import { Grid } from "../mui/Grid.js"; import { Paper } from "../mui/Paper.js"; interface Props extends JSX.HTMLAttributes<HTMLDivElement> { - titleHead?: VNode; + titleHead?: VNode | TranslatedString; children: ComponentChildren; // elements: { // icon?: VNode; diff --git a/packages/taler-wallet-webextension/src/components/Checkbox.tsx b/packages/taler-wallet-webextension/src/components/Checkbox.tsx index b6fa8b663..70dfab597 100644 --- a/packages/taler-wallet-webextension/src/components/Checkbox.tsx +++ b/packages/taler-wallet-webextension/src/components/Checkbox.tsx @@ -14,14 +14,15 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { TranslatedString } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; interface Props { enabled?: boolean; onToggle?: () => Promise<void>; - label: VNode; + label: TranslatedString; name: string; - description?: VNode; + description?: VNode | TranslatedString; } export function Checkbox({ name, diff --git a/packages/taler-wallet-webextension/src/components/CurrentAlerts.tsx b/packages/taler-wallet-webextension/src/components/CurrentAlerts.tsx new file mode 100644 index 000000000..a56c82dee --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/CurrentAlerts.tsx @@ -0,0 +1,108 @@ +/* + 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 { ComponentChildren, Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { useTranslationContext } from "../../../web-util/src/index.browser.js"; +import { + ErrorAlert, + Alert as AlertNotification, + useAlertContext, +} from "../context/alert.js"; +import { Alert } from "../mui/Alert.js"; + +/** + * + * @author sebasjm + */ + +function AlertContext({ + context, + cause, +}: { + cause: unknown; + context: undefined | object; +}): VNode { + const [more, setMore] = useState(false); + const { i18n } = useTranslationContext(); + if (!more) { + return ( + <div style={{ display: "flex", justifyContent: "right" }}> + <a onClick={() => setMore(true)}> + <i18n.Translate>more info</i18n.Translate> + </a> + </div> + ); + } + return ( + <pre style={{ overflow: "overlay" }}> + {JSON.stringify( + context === undefined ? { cause } : { context, cause }, + undefined, + 2, + )} + </pre> + ); +} + +export function ErrorAlertView({ + error: alert, + onClose, +}: { + error: ErrorAlert; + onClose?: () => Promise<void>; +}): VNode { + return ( + <Alert title={alert.message} severity={alert.type} onClose={onClose}> + <div style={{ display: "flex", flexDirection: "column" }}> + <div>{alert.description}</div> + <AlertContext context={alert.context} cause={alert.cause} /> + </div> + </Alert> + ); +} + +export function AlertView({ + alert, + onClose, +}: { + alert: AlertNotification; + onClose?: () => Promise<void>; +}): VNode { + return ( + <Alert title={alert.message} severity={alert.type} onClose={onClose}> + <div style={{ display: "flex", flexDirection: "column" }}> + <div>{alert.description}</div> + </div> + </Alert> + ); +} + +export function CurrentAlerts(): VNode { + const { alerts, removeAlert } = useAlertContext(); + if (alerts.length === 0) return <Fragment />; + return ( + <Wrapper> + {alerts.map((n, i) => ( + <AlertView key={i} alert={n} onClose={async () => removeAlert(n)} /> + ))} + </Wrapper> + ); +} + +function Wrapper({ children }: { children: ComponentChildren }): VNode { + return <div style={{ margin: "2em" }}>{children}</div>; +} diff --git a/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx b/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx index ce8dc0ad1..11f526865 100644 --- a/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx +++ b/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx @@ -13,6 +13,7 @@ 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 { TranslatedString } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import arrowDown from "../svg/chevron-down.svg"; @@ -22,7 +23,7 @@ export function ErrorMessage({ title, description, }: { - title: VNode; + title: TranslatedString; description?: string | VNode; }): VNode | null { const [showErrorDetail, setShowErrorDetail] = useState(false); diff --git a/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx b/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx index a7223d2db..f8203f38a 100644 --- a/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx +++ b/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx @@ -13,7 +13,7 @@ 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 { TalerErrorDetail } from "@gnu-taler/taler-util"; +import { TalerErrorDetail, TranslatedString } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import arrowDown from "../svg/chevron-down.svg"; @@ -24,7 +24,7 @@ export function ErrorTalerOperation({ title, error, }: { - title?: VNode; + title?: TranslatedString; error?: TalerErrorDetail; }): VNode | null { const { devMode } = useDevContext(); diff --git a/packages/taler-wallet-webextension/src/components/Loading.tsx b/packages/taler-wallet-webextension/src/components/Loading.tsx index f2195b646..3a6daaaa6 100644 --- a/packages/taler-wallet-webextension/src/components/Loading.tsx +++ b/packages/taler-wallet-webextension/src/components/Loading.tsx @@ -13,30 +13,88 @@ 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 { css } from "@linaria/core"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { useTranslationContext } from "../context/translation.js"; +import ProgressIcon from "../svg/progress.svg"; import { CenteredText } from "./styled/index.js"; +const fadeIn = css` + & { + animation: fadein 3s; + } + @keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } + } +`; + export function Loading(): VNode { const { i18n } = useTranslationContext(); - const [tooLong, setTooLong] = useState(false); - useEffect(() => { - const id = setTimeout(() => { - setTooLong(true); - }, 500); - return () => { - clearTimeout(id); - }; - }); - if (tooLong) { - return ( - <section style={{ margin: "auto" }}> - <CenteredText> - <i18n.Translate>Loading</i18n.Translate>... - </CenteredText> - </section> - ); - } - return <Fragment />; + return ( + <section style={{ margin: "auto" }}> + <CenteredText class={fadeIn}> + <i18n.Translate>Loading</i18n.Translate>... + </CenteredText> + {/* <div class={ripple} style={{ "--size": "250px" }}> + <div></div> + <div></div> + </div> */} + <div class={fadeIn} dangerouslySetInnerHTML={{ __html: ProgressIcon }} /> + </section> + ); } + +const ripple = css` + & { + display: inline-block; + position: relative; + width: var(--size); + height: var(--size); + } + & div { + position: absolute; + border: 4px solid black; + opacity: 1; + border-radius: 50%; + animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; + } + & div:nth-child(2) { + animation-delay: -0.3s; + } + @keyframes lds-ripple { + 0% { + top: calc(var(--size) / 2); + left: calc(var(--size) / 2); + width: 0; + height: 0; + opacity: 0; + } + 14.9% { + top: calc(var(--size) / 2); + left: calc(var(--size) / 2); + width: 0; + height: 0; + opacity: 0; + } + 15% { + top: calc(var(--size) / 2); + left: calc(var(--size) / 2); + width: 0; + height: 0; + opacity: 1; + } + 100% { + top: 0px; + left: 0px; + width: var(--size); + height: var(--size); + opacity: 0; + } + } +`; diff --git a/packages/taler-wallet-webextension/src/components/LoadingError.tsx b/packages/taler-wallet-webextension/src/components/LoadingError.tsx deleted file mode 100644 index 2cd8bee3b..000000000 --- a/packages/taler-wallet-webextension/src/components/LoadingError.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - 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"; -import { HookError } from "../hooks/useAsyncAsHook.js"; -import { ErrorMessage } from "./ErrorMessage.js"; -import { ErrorTalerOperation } from "./ErrorTalerOperation.js"; - -export interface Props { - title: VNode; - error: HookError; -} -export function LoadingError({ title, error }: Props): VNode { - if (error.operational) { - return <ErrorTalerOperation title={title} error={error.details} />; - } - return <ErrorMessage title={title} description={error.message} />; -} diff --git a/packages/taler-wallet-webextension/src/components/MultiActionButton.tsx b/packages/taler-wallet-webextension/src/components/MultiActionButton.tsx index 673ff3dc2..d6a730a4f 100644 --- a/packages/taler-wallet-webextension/src/components/MultiActionButton.tsx +++ b/packages/taler-wallet-webextension/src/components/MultiActionButton.tsx @@ -13,7 +13,7 @@ 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 { getUnpackedSettings } from "http2"; +import { TranslatedString } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Button } from "../mui/Button.js"; @@ -21,7 +21,7 @@ import arrowDown from "../svg/chevron-down.svg"; import { ParagraphClickable } from "./styled/index.js"; export interface Props { - label: (s: string) => VNode; + label: (s: string) => TranslatedString; actions: string[]; onClick: (s: string) => Promise<void>; } diff --git a/packages/taler-wallet-webextension/src/components/Part.tsx b/packages/taler-wallet-webextension/src/components/Part.tsx index a488ca4dc..1449bcac6 100644 --- a/packages/taler-wallet-webextension/src/components/Part.tsx +++ b/packages/taler-wallet-webextension/src/components/Part.tsx @@ -14,7 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { PaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; +import { + PaytoUri, + stringifyPaytoUri, + TranslatedString, +} from "@gnu-taler/taler-util"; import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -27,8 +31,8 @@ import { export type Kind = "positive" | "negative" | "neutral"; interface Props { - title: VNode | string; - text: VNode | string; + title: VNode | TranslatedString; + text: VNode | TranslatedString; kind?: Kind; big?: boolean; showSign?: boolean; diff --git a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx index def1e16eb..30c2ef833 100644 --- a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx +++ b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx @@ -19,6 +19,7 @@ import { Amounts, PreparePayResult, PreparePayResultType, + TranslatedString, } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -109,8 +110,10 @@ export function PaymentButtons({ <section> {payStatus.paid && payStatus.contractTerms.fulfillment_message && ( <Part - title={<i18n.Translate>Merchant message</i18n.Translate>} - text={payStatus.contractTerms.fulfillment_message} + title={i18n.str`Merchant message`} + text={ + payStatus.contractTerms.fulfillment_message as TranslatedString + } kind="neutral" /> )} @@ -131,11 +134,7 @@ function PayWithMobile({ uri }: { uri: string }): VNode { return ( <section> <LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}> - {!showQR ? ( - <i18n.Translate>Pay with a mobile phone</i18n.Translate> - ) : ( - <i18n.Translate>Hide QR</i18n.Translate> - )} + {!showQR ? i18n.str`Pay with a mobile phone` : i18n.str`Hide QR`} </LinkSuccess> {showQR && ( <div> diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx index e41ff2836..2bba86dba 100644 --- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx +++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx @@ -87,7 +87,7 @@ export function PendingTransactionsView({ }} > <Banner - titleHead={<i18n.Translate>PENDING OPERATIONS</i18n.Translate>} + titleHead={i18n.str`PENDING OPERATIONS`} style={{ backgroundColor: "lightcyan", maxHeight: 150, diff --git a/packages/taler-wallet-webextension/src/components/SelectList.tsx b/packages/taler-wallet-webextension/src/components/SelectList.tsx index 3ceac752e..809698711 100644 --- a/packages/taler-wallet-webextension/src/components/SelectList.tsx +++ b/packages/taler-wallet-webextension/src/components/SelectList.tsx @@ -14,6 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { TranslatedString } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useTranslationContext } from "../context/translation.js"; import { NiceSelect } from "./styled/index.js"; @@ -21,7 +22,7 @@ import { NiceSelect } from "./styled/index.js"; interface Props { value?: string; onChange?: (s: string) => void; - label: VNode; + label: VNode | TranslatedString; list: { [label: string]: string; }; diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx index 841583113..8c94e6e60 100644 --- a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx +++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx @@ -94,7 +94,10 @@ export const Error = createExample(ErrorView, { error: { hasError: true, message: "message", - operational: false, + // details: { + // co + // }, + type: "error", // details: { // code: 123, // }, diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx index 47c10347c..9871611f2 100644 --- a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx +++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx @@ -22,15 +22,16 @@ import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../components/Loading.js"; -import { LoadingError } from "../components/LoadingError.js"; import { Modal } from "../components/Modal.js"; import { Time } from "../components/Time.js"; +import { alertFromError } from "../context/alert.js"; import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../mui/handlers.js"; import { compose, StateViewMap } from "../utils/index.js"; import { Amount } from "./Amount.js"; +import { AlertView } from "./CurrentAlerts.js"; import { Link } from "./styled/index.js"; const ContractTermsTable = styled.table` @@ -160,13 +161,12 @@ export function ErrorView({ const { i18n } = useTranslationContext(); return ( <Modal title="Full detail" onClose={hideHandler}> - <LoadingError - title={ - <i18n.Translate> - Could not load purchase proposal details - </i18n.Translate> - } - error={error} + <AlertView + alert={alertFromError( + i18n.str`Could not load purchase proposal details`, + error, + { proposalId }, + )} /> </Modal> ); diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts index d7716f208..a8c1558d8 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts @@ -15,14 +15,13 @@ */ import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ToggleHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; +import { ErrorAlertView } from "../CurrentAlerts.js"; import { useComponentState } from "./state.js"; import { TermsState } from "./utils.js"; import { - ErrorAcceptingView, - LoadingUriView, ShowButtonsAcceptedTosView, ShowButtonsNonAcceptedTosView, ShowTosContentView, @@ -35,8 +34,7 @@ export interface Props { export type State = | State.Loading - | State.LoadingUriError - | State.ErrorAccepting + | State.Error | State.ShowButtonsAccepted | State.ShowButtonsNotAccepted | State.ShowContent; @@ -47,14 +45,9 @@ export namespace State { error: undefined; } - export interface LoadingUriError { - status: "loading-error"; - error: HookError; - } - - export interface ErrorAccepting { - status: "error-accepting"; - error: HookError; + export interface Error { + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -79,11 +72,10 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-error": LoadingUriView, + error: ErrorAlertView, "show-content": ShowTosContentView, "show-buttons-accepted": ShowButtonsAcceptedTosView, "show-buttons-not-accepted": ShowButtonsNonAcceptedTosView, - "error-accepting": ErrorAcceptingView, }; export const TermsOfService = compose( diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts index 3b75965d3..c25c0ed13 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts @@ -16,7 +16,9 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { alertFromError, useAlertContext } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; import { buildTermsOfServiceState } from "./utils.js"; @@ -25,9 +27,8 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State { const api = useBackendContext(); const readOnly = !onChange; const [showContent, setShowContent] = useState<boolean>(readOnly); - const [errorAccepting, setErrorAccepting] = useState<Error | undefined>( - undefined, - ); + const { i18n } = useTranslationContext(); + const { pushAlert } = useAlertContext(); /** * For the exchange selected, bring the status of the terms of service @@ -54,22 +55,13 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State { } if (terms.hasError) { return { - status: "loading-error", - error: terms, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + terms, + ), }; } - - if (errorAccepting) { - return { - status: "error-accepting", - error: { - hasError: true, - operational: false, - message: errorAccepting.message, - }, - }; - } - const { state } = terms.response; async function onUpdate(accepted: boolean): Promise<void> { @@ -77,13 +69,13 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State { try { if (accepted) { - api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { + await api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { exchangeBaseUrl: exchangeUrl, etag: state.version, }); } else { // mark as not accepted - api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { + await api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { exchangeBaseUrl: exchangeUrl, etag: undefined, }); @@ -91,11 +83,7 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State { // setAccepted(accepted); if (!readOnly) onChange(accepted); //external update } catch (e) { - if (e instanceof Error) { - //FIXME: uncomment this and display error - // setErrorAccepting(e.message); - setErrorAccepting(e); - } + pushAlert(alertFromError(i18n.str`Could not accept terms of service`, e)); } } diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx b/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx index a7e03fd01..0b5a71b3e 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx @@ -14,49 +14,23 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { ExchangeTosStatus } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; -import { LoadingError } from "../../components/LoadingError.js"; -import { useTranslationContext } from "../../context/translation.js"; -import { TermsDocument, TermsState } from "./utils.js"; -import { State } from "./index.js"; import { CheckboxOutlined } from "../../components/CheckboxOutlined.js"; +import { ExchangeXmlTos } from "../../components/ExchangeToS.js"; import { LinkSuccess, TermsOfService, WarningBox, WarningText, } from "../../components/styled/index.js"; -import { ExchangeXmlTos } from "../../components/ExchangeToS.js"; -import { ToggleHandler } from "../../mui/handlers.js"; +import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; -import { ExchangeTosStatus } from "@gnu-taler/taler-util"; - -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - -export function ErrorAcceptingView({ error }: State.ErrorAccepting): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} +import { State } from "./index.js"; export function ShowButtonsAcceptedTosView({ termsAccepted, showingTermsOfService, - terms, }: State.ShowButtonsAccepted): VNode { const { i18n } = useTranslationContext(); const ableToReviewTermsOfService = diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index 8e98f75eb..e36502333 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -535,7 +535,7 @@ export const LinkDestructive = styled(Link)` `; export const LinkPrimary = styled(Link)` - color: rgb(66, 184, 221); + color: black; `; export const ButtonPrimary = styled(ButtonVariant)<{ small?: boolean }>` diff --git a/packages/taler-wallet-webextension/src/context/alert.ts b/packages/taler-wallet-webextension/src/context/alert.ts new file mode 100644 index 000000000..cc98ec1e0 --- /dev/null +++ b/packages/taler-wallet-webextension/src/context/alert.ts @@ -0,0 +1,118 @@ +/* + 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 { TranslatedString } from "@gnu-taler/taler-util"; +import { ComponentChildren, createContext, h, VNode } from "preact"; +import { useContext, useState } from "preact/hooks"; + +export type AlertType = "info" | "warning" | "error" | "success"; + +export interface Alert { + message: TranslatedString; + description: TranslatedString | VNode; + type: AlertType; +} + +export interface ErrorAlert extends Alert { + type: "error"; + context: object; + cause: any; +} + +type Type = { + alerts: Alert[]; + pushAlert: (n: Alert) => void; + removeAlert: (n: Alert) => void; +}; + +const initial: Type = { + alerts: [], + 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)); + }; + + return h(Context.Provider, { + value: { alerts, pushAlert, removeAlert }, + children, + }); +}; + +export const useAlertContext = (): Type => useContext(Context); + +export function alertFromError( + message: TranslatedString, + error: unknown, + ...context: any[] +): ErrorAlert { + let description = "" as TranslatedString; + + const isObject = typeof error === "object" && + error !== null; + const hasMessage = + isObject && + "message" in error && + typeof error.message === "string"; + + if (hasMessage) { + description = error.message as TranslatedString; + } else { + description = `Unknown error: ${String(error)}` as TranslatedString; + } + + return { + type: "error", + message, + description, + cause: error, + context, + }; +} diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts index 9ff3ddd1d..6b228188b 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts @@ -15,12 +15,13 @@ */ import { AmountJson, AmountString } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ReadyView } from "./views.js"; export interface Props { talerDepositUri: string | undefined; @@ -37,8 +38,8 @@ export namespace State { error: undefined; } export interface LoadingUriError { - status: "loading-uri"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface Ready { status: "ready"; @@ -57,7 +58,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-uri": LoadingUriView, + error: ErrorAlertView, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts index dba435611..4cee7cfd0 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts @@ -16,7 +16,9 @@ import { Amounts } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; @@ -38,12 +40,16 @@ export function useComponentState({ }); return { deposit, uri: talerDepositUri, amount }; }); + const { i18n } = useTranslationContext(); if (!info) return { status: "loading", error: undefined }; if (info.hasError) { return { - status: "loading-uri", - error: info, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + info, + ), }; } diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts index 6a896fb7f..031dcffaa 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts @@ -50,12 +50,12 @@ describe("Deposit CTA states", () => { expect(status).equals("loading"); }, ({ status, error }) => { - expect(status).equals("loading-uri"); + expect(status).equals("error"); if (!error) expect.fail(); - if (!error.hasError) expect.fail(); - if (error.operational) expect.fail(); - expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); + // if (!error.hasError) expect.fail(); + // if (error.operational) expect.fail(); + expect(error.cause?.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); }, ], TestingContext, diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx b/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx index 2ec305de5..7fa43f878 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx @@ -17,7 +17,6 @@ import { Amounts } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { Amount } from "../../components/Amount.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.js"; import { Part } from "../../components/Part.js"; import { SubTitle, WalletAction } from "../../components/styled/index.js"; @@ -30,17 +29,6 @@ import { State } from "./index.js"; * @author sebasjm */ -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load deposit status</i18n.Translate>} - error={error} - /> - ); -} - export function ReadyView(state: State.Ready): VNode { const { i18n } = useTranslationContext(); @@ -55,7 +43,7 @@ export function ReadyView(state: State.Ready): VNode { {Amounts.isNonZero(state.cost) && ( <Part big - title={<i18n.Translate>Cost</i18n.Translate>} + title={i18n.str`Cost`} text={<Amount value={state.cost} />} kind="negative" /> @@ -63,14 +51,14 @@ export function ReadyView(state: State.Ready): VNode { {Amounts.isNonZero(state.fee) && ( <Part big - title={<i18n.Translate>Fee</i18n.Translate>} + title={i18n.str`Fee`} text={<Amount value={state.fee} />} kind="negative" /> )} <Part big - title={<i18n.Translate>To be received</i18n.Translate>} + title={i18n.str`To be received`} text={<Amount value={state.effective} />} kind="positive" /> diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts index 0569e8e5f..f39ab6794 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts @@ -14,16 +14,17 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util"; +import { AmountJson } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ReadyView } from "./views.js"; export interface Props { amount: string; @@ -45,8 +46,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-uri"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -63,13 +64,12 @@ export namespace State { requestAmount: AmountJson; exchangeUrl: string; error: undefined; - operationError?: TalerErrorDetail; } } const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-uri": LoadingUriView, + error: ErrorAlertView, "no-exchange": NoExchangesView, "selecting-exchange": ExchangeSelectionPage, ready: ReadyView, diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts index 998270e53..46b1262b1 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts @@ -23,7 +23,9 @@ import { import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { isFuture, parse } from "date-fns"; import { useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; import { RecursiveState } from "../../utils/index.js"; @@ -40,6 +42,7 @@ export function useComponentState({ const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {}), ); + const { i18n } = useTranslationContext(); if (!hook) { return { @@ -49,10 +52,19 @@ export function useComponentState({ } if (hook.hasError) { return { - status: "loading-uri", - error: hook, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + hook, + ), }; } + // if (hook.hasError) { + // return { + // status: "loading-uri", + // error: hook, + // }; + // } const exchangeList = hook.response.exchanges; @@ -60,10 +72,6 @@ export function useComponentState({ const [subject, setSubject] = useState<string | undefined>(); const [timestamp, setTimestamp] = useState<string | undefined>(); - const [operationError, setOperationError] = useState< - TalerErrorDetail | undefined - >(undefined); - const selectedExchange = useSelectedExchange({ currency: amount.currency, defaultExchange: undefined, @@ -93,11 +101,19 @@ export function useComponentState({ error: undefined, }; } + if (hook.hasError) { return { - status: "loading-uri", - error: hook, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + hook, + ), }; + // return { + // status: "loading-uri", + // error: hook, + // }; } const { amountEffective, amountRaw } = hook.response; @@ -160,8 +176,8 @@ export function useComponentState({ subject === undefined ? undefined : !subject - ? "Can't be empty" - : undefined, + ? "Can't be empty" + : undefined, value: subject ?? "", onInput: async (e) => setSubject(e), }, @@ -183,7 +199,6 @@ export function useComponentState({ requestAmount, toBeReceived, error: undefined, - operationError, }; }; } diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx index 0ef5c697e..10e0e68d5 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx @@ -17,41 +17,24 @@ import { format } from "date-fns"; import { h, VNode } from "preact"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.js"; import { Part } from "../../components/Part.js"; -import { QR } from "../../components/QR.js"; import { - Link, SubTitle, SvgIcon, WalletAction, } from "../../components/styled/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; -import { Grid } from "../../mui/Grid.js"; import { TextField } from "../../mui/TextField.js"; import editIcon from "../../svg/edit_24px.svg"; import { ExchangeDetails, InvoiceDetails } from "../../wallet/Transaction.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - export function ReadyView({ exchangeUrl, subject, expiration, - cancel, - operationError, create, toBeReceived, requestAmount, @@ -59,7 +42,7 @@ export function ReadyView({ }: State.Ready): VNode { const { i18n } = useTranslationContext(); - async function oneDayExpiration() { + async function oneDayExpiration(): Promise<void> { if (expiration.onInput) { expiration.onInput( format(new Date().getTime() + 1000 * 60 * 60 * 24, "dd/MM/yyyy"), @@ -67,14 +50,14 @@ export function ReadyView({ } } - async function oneWeekExpiration() { + async function oneWeekExpiration(): Promise<void> { if (expiration.onInput) { expiration.onInput( format(new Date().getTime() + 1000 * 60 * 60 * 24 * 7, "dd/MM/yyyy"), ); } } - async function _20DaysExpiration() { + async function _20DaysExpiration(): Promise<void> { if (expiration.onInput) { expiration.onInput( format(new Date().getTime() + 1000 * 60 * 60 * 24 * 20, "dd/MM/yyyy"), @@ -87,16 +70,6 @@ export function ReadyView({ <SubTitle> <i18n.Translate>Digital invoice</i18n.Translate> </SubTitle> - {operationError && ( - <ErrorTalerOperation - title={ - <i18n.Translate> - Could not finish the invoice creation - </i18n.Translate> - } - error={operationError} - /> - )} <section style={{ textAlign: "left" }}> <Part title={ @@ -125,9 +98,7 @@ export function ReadyView({ label="Subject" variant="filled" error={subject.error} - helperText={ - <i18n.Translate>Short description of the invoice</i18n.Translate> - } + helperText={i18n.str`Short description of the invoice`} required fullWidth value={subject.value} @@ -171,7 +142,7 @@ export function ReadyView({ </p> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <InvoiceDetails amount={{ @@ -187,11 +158,6 @@ export function ReadyView({ <i18n.Translate>Create</i18n.Translate> </Button> </section> - <section> - <Link upperCased onClick={cancel.onClick}> - <i18n.Translate>Cancel</i18n.Translate> - </Link> - </section> </WalletAction> ); } diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts index f3de0885d..82b2c7af5 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts @@ -20,12 +20,13 @@ import { PreparePayResult, TalerErrorDetail, } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ReadyView } from "./views.js"; export interface Props { talerPayPullUri: string; @@ -48,8 +49,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-uri"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -83,7 +84,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-uri": LoadingUriView, + error: ErrorAlertView, "no-balance-for-currency": ReadyView, "no-enough-balance": ReadyView, ready: ReadyView, diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts index c0b97c106..9c4a3162e 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts @@ -25,7 +25,9 @@ import { } from "@gnu-taler/taler-util"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; @@ -36,6 +38,7 @@ export function useComponentState({ onSuccess, }: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const hook = useAsyncAsHook(async () => { const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, { talerUri: talerPayPullUri, @@ -63,10 +66,19 @@ export function useComponentState({ } if (hook.hasError) { return { - status: "loading-uri", - error: hook, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + hook, + ), }; } + // if (hook.hasError) { + // return { + // status: "loading-uri", + // error: hook, + // }; + // } const { contractTerms, peerPullPaymentIncomingId } = hook.response.p2p; diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx index a53fa881a..6a9ab3cf7 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx @@ -17,26 +17,14 @@ import { Fragment, h, VNode } from "preact"; import { Amount } from "../../components/Amount.js"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.js"; import { Part } from "../../components/Part.js"; +import { PaymentButtons } from "../../components/PaymentButtons.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; import { Time } from "../../components/Time.js"; import { useTranslationContext } from "../../context/translation.js"; -import { PaymentButtons } from "../../components/PaymentButtons"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - export function ReadyView( state: State.Ready | State.NoBalanceForCurrency | State.NoEnoughBalance, ): VNode { @@ -60,25 +48,15 @@ export function ReadyView( </SubTitle> {operationError && ( <ErrorTalerOperation - title={ - <i18n.Translate> - Could not finish the payment operation - </i18n.Translate> - } + title={i18n.str`Could not finish the payment operation`} error={operationError} /> )} <section style={{ textAlign: "left" }}> + <Part title={i18n.str`Subject`} text={<div>{summary}</div>} /> + <Part title={i18n.str`Amount`} text={<Amount value={amount} />} /> <Part - title={<i18n.Translate>Subject</i18n.Translate>} - text={<div>{summary}</div>} - /> - <Part - title={<i18n.Translate>Amount</i18n.Translate>} - text={<Amount value={amount} />} - /> - <Part - title={<i18n.Translate>Valid until</i18n.Translate>} + title={i18n.str`Valid until`} text={<Time timestamp={expiration} format="dd MMMM yyyy, HH:mm" />} kind="neutral" /> @@ -91,11 +69,6 @@ export function ReadyView( payHandler={status === "ready" ? state.accept : undefined} goToWalletManualWithdraw={state.goToWalletManualWithdraw} /> - <section> - <Link upperCased onClick={cancel.onClick}> - <i18n.Translate>Cancel</i18n.Translate> - </Link> - </section> </WalletAction> ); } diff --git a/packages/taler-wallet-webextension/src/cta/Payment/index.ts b/packages/taler-wallet-webextension/src/cta/Payment/index.ts index 2dc6b6741..e844c1706 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/index.ts @@ -21,12 +21,13 @@ import { PreparePayResultInsufficientBalance, PreparePayResultPaymentPossible, } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { BaseView, LoadingUriView } from "./views.js"; +import { BaseView } from "./views.js"; export interface Props { talerPayUri?: string; @@ -49,8 +50,8 @@ export namespace State { error: undefined; } export interface LoadingUriError { - status: "loading-uri"; - error: HookError; + status: "error"; + error: ErrorAlert; } interface BaseInfo { @@ -86,7 +87,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-uri": LoadingUriView, + error: ErrorAlertView, "no-balance-for-currency": BaseView, "no-enough-balance": BaseView, confirmed: BaseView, diff --git a/packages/taler-wallet-webextension/src/cta/Payment/state.ts b/packages/taler-wallet-webextension/src/cta/Payment/state.ts index d4adf4bcb..6d7ef6b20 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/state.ts @@ -23,7 +23,9 @@ import { } from "@gnu-taler/taler-util"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { Props, State } from "./index.js"; @@ -36,6 +38,7 @@ export function useComponentState({ }: Props): State { const [payErrMsg, setPayErrMsg] = useState<TalerError | undefined>(undefined); const api = useBackendContext(); + const { i18n } = useTranslationContext(); const hook = useAsyncAsHook(async () => { if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT"); @@ -80,10 +83,19 @@ export function useComponentState({ if (!hook) return { status: "loading", error: undefined }; if (hook.hasError) { return { - status: "loading-uri", - error: hook, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + hook, + ), }; } + // if (hook.hasError) { + // return { + // status: "loading-uri", + // error: hook, + // }; + // } const { payStatus } = hook.response; const amount = Amounts.parseOrThrow(payStatus.amountRaw); diff --git a/packages/taler-wallet-webextension/src/cta/Payment/test.ts b/packages/taler-wallet-webextension/src/cta/Payment/test.ts index 077930972..123e95a87 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/test.ts @@ -54,10 +54,10 @@ describe("Payment CTA states", () => { expect(error).undefined; }, ({ status, error }) => { - expect(status).equals("loading-uri"); + expect(status).equals("error"); if (error === undefined) expect.fail(); - expect(error.hasError).true; - expect(error.operational).false; + // expect(error.hasError).true; + // expect(error.operational).false; }, ], TestingContext, diff --git a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx index efc8bcfc4..244ac5886 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx @@ -19,28 +19,17 @@ import { Amounts, MerchantContractTerms as ContractTerms, PreparePayResultType, + TranslatedString, } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; -import { LoadingError } from "../../components/LoadingError.js"; import { Part } from "../../components/Part.js"; import { PaymentButtons } from "../../components/PaymentButtons.js"; -import { Link, SuccessBox, WarningBox } from "../../components/styled/index.js"; +import { SuccessBox, WarningBox } from "../../components/styled/index.js"; import { Time } from "../../components/Time.js"; import { useTranslationContext } from "../../context/translation.js"; import { MerchantDetails, PurchaseDetails } from "../../wallet/Transaction.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load pay status</i18n.Translate>} - error={error} - /> - ); -} - type SupportedStates = | State.Ready | State.Confirmed @@ -66,17 +55,17 @@ export function BaseView(state: SupportedStates): VNode { <section style={{ textAlign: "left" }}> <Part - title={<i18n.Translate>Purchase</i18n.Translate>} - text={contractTerms.summary} + title={i18n.str`Purchase`} + text={contractTerms.summary as TranslatedString} kind="neutral" /> <Part - title={<i18n.Translate>Merchant</i18n.Translate>} + title={i18n.str`Merchant`} text={<MerchantDetails merchant={contractTerms.merchant} />} kind="neutral" /> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <PurchaseDetails price={price} @@ -93,14 +82,14 @@ export function BaseView(state: SupportedStates): VNode { /> {contractTerms.order_id && ( <Part - title={<i18n.Translate>Receipt</i18n.Translate>} - text={`#${contractTerms.order_id}`} + title={i18n.str`Receipt`} + text={`#${contractTerms.order_id}` as TranslatedString} kind="neutral" /> )} {contractTerms.pay_deadline && ( <Part - title={<i18n.Translate>Valid until</i18n.Translate>} + title={i18n.str`Valid until`} text={ <Time timestamp={AbsoluteTime.fromTimestamp( @@ -121,11 +110,6 @@ export function BaseView(state: SupportedStates): VNode { payHandler={state.status === "ready" ? state.payHandler : undefined} goToWalletManualWithdraw={state.goToWalletManualWithdraw} /> - <section> - <Link upperCased onClick={state.cancel}> - <i18n.Translate>Cancel</i18n.Translate> - </Link> - </section> </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/index.ts b/packages/taler-wallet-webextension/src/cta/Recovery/index.ts index 4a6fc79c9..79056c15b 100644 --- a/packages/taler-wallet-webextension/src/cta/Recovery/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Recovery/index.ts @@ -14,12 +14,13 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ReadyView } from "./views.js"; export interface Props { talerRecoveryUri?: string; @@ -36,8 +37,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-uri"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -53,7 +54,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-uri": LoadingUriView, + error: ErrorAlertView, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts index 4fef2c862..078e53bf9 100644 --- a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts @@ -16,7 +16,9 @@ import { parseRecoveryUri } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { Alert } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { Props, State } from "./index.js"; export function useComponentState({ @@ -25,13 +27,16 @@ export function useComponentState({ onSuccess, }: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); if (!talerRecoveryUri) { return { - status: "loading-uri", + status: "error", error: { - operational: false, - hasError: true, - message: "Missing URI", + type: "error", + message: i18n.str`Missing URI`, + description: i18n.str``, + cause: new Error("something"), + context: {}, }, }; } @@ -39,11 +44,13 @@ export function useComponentState({ if (!info) { return { - status: "loading-uri", + status: "error", error: { - operational: false, - hasError: true, - message: "Could not be read", + type: "error", + message: i18n.str`Could not parse the recovery URI`, + description: i18n.str``, + cause: new Error("something"), + context: {}, }, }; } diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/views.tsx b/packages/taler-wallet-webextension/src/cta/Recovery/views.tsx index 371516932..858349ef3 100644 --- a/packages/taler-wallet-webextension/src/cta/Recovery/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Recovery/views.tsx @@ -15,28 +15,12 @@ */ import { Fragment, h, VNode } from "preact"; -import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.js"; import { SubTitle, WalletAction } from "../../components/styled/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={ - <i18n.Translate> - Could not load backup recovery information - </i18n.Translate> - } - error={error} - /> - ); -} - export function ReadyView({ accept, cancel }: State.Ready): VNode { const { i18n } = useTranslationContext(); return ( diff --git a/packages/taler-wallet-webextension/src/cta/Refund/index.ts b/packages/taler-wallet-webextension/src/cta/Refund/index.ts index f79a77680..e90f770ff 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/index.ts @@ -15,17 +15,13 @@ */ import { AmountJson, Product } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { - IgnoredView, - InProgressView, - LoadingUriView, - ReadyView, -} from "./views.js"; +import { IgnoredView, InProgressView, ReadyView } from "./views.js"; export interface Props { talerRefundUri?: string; @@ -47,8 +43,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-uri"; - error: HookError; + status: "error"; + error: ErrorAlert; } interface BaseInfo { @@ -81,7 +77,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-uri": LoadingUriView, + error: ErrorAlertView, "in-progress": InProgressView, ignored: IgnoredView, ready: ReadyView, diff --git a/packages/taler-wallet-webextension/src/cta/Refund/state.ts b/packages/taler-wallet-webextension/src/cta/Refund/state.ts index 9e3311b65..5a5073ba3 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/state.ts @@ -17,7 +17,9 @@ import { Amounts, NotificationType } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; @@ -27,6 +29,7 @@ export function useComponentState({ onSuccess, }: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const [ignored, setIgnored] = useState(false); const info = useAsyncAsHook(async () => { @@ -49,10 +52,19 @@ export function useComponentState({ } if (info.hasError) { return { - status: "loading-uri", - error: info, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + info, + ), }; } + // if (info.hasError) { + // return { + // status: "loading-uri", + // error: info, + // }; + // } const { refund, uri } = info.response; diff --git a/packages/taler-wallet-webextension/src/cta/Refund/test.ts b/packages/taler-wallet-webextension/src/cta/Refund/test.ts index 24d483a9a..8c4daa4d2 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/test.ts @@ -53,11 +53,11 @@ describe("Refund CTA states", () => { expect(error).undefined; }, ({ status, error }) => { - expect(status).equals("loading-uri"); + expect(status).equals("error"); if (!error) expect.fail(); - if (!error.hasError) expect.fail(); - if (error.operational) expect.fail(); - expect(error.message).eq("ERROR_NO-URI-FOR-REFUND"); + // if (!error.hasError) expect.fail(); + // if (error.operational) expect.fail(); + expect(error.cause?.message).eq("ERROR_NO-URI-FOR-REFUND"); }, ], TestingContext, diff --git a/packages/taler-wallet-webextension/src/cta/Refund/views.tsx b/packages/taler-wallet-webextension/src/cta/Refund/views.tsx index a55bc43dd..16e1c519c 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Refund/views.tsx @@ -17,26 +17,14 @@ import { Amounts } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { Amount } from "../../components/Amount.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.js"; import { Part } from "../../components/Part.js"; +import { ProductList } from "../../components/ProductList.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; -import { ProductList } from "../../components/ProductList.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load refund status</i18n.Translate>} - error={error} - /> - ); -} - export function IgnoredView(state: State.Ignored): VNode { const { i18n } = useTranslationContext(); @@ -73,13 +61,13 @@ export function InProgressView(state: State.InProgress): VNode { <section> <Part big - title={<i18n.Translate>Total to refund</i18n.Translate>} + title={i18n.str`Total to refund`} text={<Amount value={state.awaitingAmount} />} kind="negative" /> <Part big - title={<i18n.Translate>Refunded</i18n.Translate>} + title={i18n.str`Refunded`} text={<Amount value={state.amount} />} kind="negative" /> @@ -112,21 +100,21 @@ export function ReadyView(state: State.Ready): VNode { <section> <Part big - title={<i18n.Translate>Order amount</i18n.Translate>} + title={i18n.str`Order amount`} text={<Amount value={state.amount} />} kind="neutral" /> {Amounts.isNonZero(state.granted) && ( <Part big - title={<i18n.Translate>Already refunded</i18n.Translate>} + title={i18n.str`Already refunded`} text={<Amount value={state.granted} />} kind="neutral" /> )} <Part big - title={<i18n.Translate>Refund offered</i18n.Translate>} + title={i18n.str`Refund offered`} text={<Amount value={state.awaitingAmount} />} kind="positive" /> @@ -147,11 +135,6 @@ export function ReadyView(state: State.Ready): VNode { </i18n.Translate> </Button> </section> - <section> - <Link upperCased onClick={state.cancel}> - <i18n.Translate>Cancel</i18n.Translate> - </Link> - </section> </WalletAction> ); } diff --git a/packages/taler-wallet-webextension/src/cta/Tip/index.ts b/packages/taler-wallet-webextension/src/cta/Tip/index.ts index 62e0688be..5e56db7bc 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/index.ts @@ -15,17 +15,13 @@ */ import { AmountJson } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { - AcceptedView, - IgnoredView, - LoadingUriView, - ReadyView, -} from "./views.js"; +import { AcceptedView, IgnoredView, ReadyView } from "./views.js"; export interface Props { talerTipUri?: string; @@ -48,8 +44,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-uri"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -75,7 +71,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-uri": LoadingUriView, + error: ErrorAlertView, accepted: AcceptedView, ignored: IgnoredView, ready: ReadyView, diff --git a/packages/taler-wallet-webextension/src/cta/Tip/state.ts b/packages/taler-wallet-webextension/src/cta/Tip/state.ts index e83755119..29a9c4c71 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/state.ts @@ -16,7 +16,9 @@ import { Amounts } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; @@ -26,6 +28,7 @@ export function useComponentState({ onSuccess, }: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const tipInfo = useAsyncAsHook(async () => { if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP"); const tip = await api.wallet.call(WalletApiOperation.PrepareTip, { @@ -42,10 +45,19 @@ export function useComponentState({ } if (tipInfo.hasError) { return { - status: "loading-uri", - error: tipInfo, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + tipInfo, + ), }; } + // if (tipInfo.hasError) { + // return { + // status: "loading-uri", + // error: tipInfo, + // }; + // } const { tip } = tipInfo.response; diff --git a/packages/taler-wallet-webextension/src/cta/Tip/test.ts b/packages/taler-wallet-webextension/src/cta/Tip/test.ts index 5688d82a9..2cc95f424 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/test.ts @@ -23,8 +23,7 @@ import { Amounts } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { tests } from "../../../../web-util/src/index.browser.js"; -import { mountHook, nullFunction } from "../../test-utils.js"; -import { createWalletApiMock } from "../../test-utils.js"; +import { createWalletApiMock, nullFunction } from "../../test-utils.js"; import { Props } from "./index.js"; import { useComponentState } from "./state.js"; @@ -47,11 +46,9 @@ describe("Tip CTA states", () => { expect(error).undefined; }, ({ status, error }) => { - expect(status).equals("loading-uri"); + expect(status).equals("error"); if (!error) expect.fail(); - if (!error.hasError) expect.fail(); - if (error.operational) expect.fail(); - expect(error.message).eq("ERROR_NO-URI-FOR-TIP"); + expect(error.cause?.message).eq("ERROR_NO-URI-FOR-TIP"); }, ], TestingContext, diff --git a/packages/taler-wallet-webextension/src/cta/Tip/views.tsx b/packages/taler-wallet-webextension/src/cta/Tip/views.tsx index fbc93c5ab..000daf19e 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Tip/views.tsx @@ -14,9 +14,9 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { TranslatedString } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { Amount } from "../../components/Amount.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.js"; import { Part } from "../../components/Part.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; @@ -24,17 +24,6 @@ import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load tip status</i18n.Translate>} - error={error} - /> - ); -} - export function IgnoredView(state: State.Ignored): VNode { const { i18n } = useTranslationContext(); return ( @@ -66,18 +55,18 @@ export function ReadyView(state: State.Ready): VNode { <i18n.Translate>The merchant is offering you a tip</i18n.Translate> </p> <Part - title={<i18n.Translate>Amount</i18n.Translate>} + title={i18n.str`Amount`} text={<Amount value={state.amount} />} kind="positive" /> <Part - title={<i18n.Translate>Merchant URL</i18n.Translate>} - text={state.merchantBaseUrl} + title={i18n.str`Merchant URL`} + text={state.merchantBaseUrl as TranslatedString} kind="neutral" /> <Part - title={<i18n.Translate>Exchange</i18n.Translate>} - text={state.exchangeBaseUrl} + title={i18n.str`Exchange`} + text={state.exchangeBaseUrl as TranslatedString} kind="neutral" /> </section> @@ -92,11 +81,6 @@ export function ReadyView(state: State.Ready): VNode { </i18n.Translate> </Button> </section> - <section> - <Link upperCased onClick={state.cancel.onClick}> - <i18n.Translate>Cancel</i18n.Translate> - </Link> - </section> </WalletAction> ); } diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts index 0715bb60e..b191b4efa 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts @@ -15,12 +15,13 @@ */ import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ReadyView } from "./views.js"; export interface Props { amount: string; @@ -37,8 +38,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-uri"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -59,7 +60,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-uri": LoadingUriView, + error: ErrorAlertView, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts index c09a524c8..ecea53848 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts @@ -22,7 +22,9 @@ import { import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { isFuture, parse } from "date-fns"; import { useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; @@ -33,6 +35,7 @@ export function useComponentState({ }: Props): State { const api = useBackendContext(); const amount = Amounts.parseOrThrow(amountStr); + const { i18n } = useTranslationContext(); const [subject, setSubject] = useState<string | undefined>(); const [timestamp, setTimestamp] = useState<string | undefined>(); @@ -59,10 +62,19 @@ export function useComponentState({ } if (hook.hasError) { return { - status: "loading-uri", - error: hook, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + hook, + ), }; } + // if (hook.hasError) { + // return { + // status: "loading-uri", + // error: hook, + // }; + // } const { amountEffective, amountRaw } = hook.response; const debitAmount = Amounts.parseOrThrow(amountRaw); diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx index 0b034e3fb..cee61b3b8 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx @@ -17,10 +17,8 @@ import { format } from "date-fns"; import { h, VNode } from "preact"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.js"; import { Part } from "../../components/Part.js"; -import { QR } from "../../components/QR.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; @@ -28,17 +26,6 @@ import { TextField } from "../../mui/TextField.js"; import { TransferDetails } from "../../wallet/Transaction.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - export function ReadyView({ subject, expiration, @@ -80,11 +67,7 @@ export function ReadyView({ </SubTitle> {operationError && ( <ErrorTalerOperation - title={ - <i18n.Translate> - Could not finish the transfer creation - </i18n.Translate> - } + title={i18n.str`Could not finish the transfer creation`} error={operationError} /> )} @@ -93,9 +76,7 @@ export function ReadyView({ <TextField label="Subject" variant="filled" - helperText={ - <i18n.Translate>Short description of the transfer</i18n.Translate> - } + helperText={i18n.str`Short description of the transfer`} error={subject.error} required fullWidth @@ -138,7 +119,7 @@ export function ReadyView({ </p> </p> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <TransferDetails amount={{ @@ -154,13 +135,6 @@ export function ReadyView({ <i18n.Translate>Create</i18n.Translate> </Button> </section> - <section> - <section> - <Link upperCased onClick={cancel.onClick}> - <i18n.Translate>Cancel</i18n.Translate> - </Link> - </section> - </section> </WalletAction> ); } diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts index fe6fb2ada..7bb8785d7 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts @@ -19,12 +19,13 @@ import { AmountJson, TalerErrorDetail, } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ReadyView } from "./views.js"; export interface Props { talerPayPushUri: string; @@ -41,8 +42,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-uri"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -62,7 +63,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-uri": LoadingUriView, + error: ErrorAlertView, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts index 82c95b0c6..04fc0e0a7 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts @@ -22,7 +22,9 @@ import { } from "@gnu-taler/taler-util"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; @@ -32,6 +34,7 @@ export function useComponentState({ onSuccess, }: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const hook = useAsyncAsHook(async () => { return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, { talerUri: talerPayPushUri, @@ -49,10 +52,19 @@ export function useComponentState({ } if (hook.hasError) { return { - status: "loading-uri", - error: hook, + status: "error", + error: alertFromError( + i18n.str`Could not load the status of the term of service`, + hook, + ), }; } + // if (hook.hasError) { + // return { + // status: "loading-uri", + // error: hook, + // }; + // } const { contractTerms, peerPushPaymentIncomingId } = hook.response; diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/views.tsx b/packages/taler-wallet-webextension/src/cta/TransferPickup/views.tsx index c43b0ff52..d2402db3a 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/views.tsx @@ -17,7 +17,6 @@ import { h, VNode } from "preact"; import { Amount } from "../../components/Amount.js"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { LogoHeader } from "../../components/LogoHeader.js"; import { Part } from "../../components/Part.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; @@ -26,17 +25,6 @@ import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - export function ReadyView({ accept, summary, @@ -54,25 +42,15 @@ export function ReadyView({ </SubTitle> {operationError && ( <ErrorTalerOperation - title={ - <i18n.Translate> - Could not finish the pickup operation - </i18n.Translate> - } + title={i18n.str`Could not finish the pickup operation`} error={operationError} /> )} <section style={{ textAlign: "left" }}> + <Part title={i18n.str`Subject`} text={<div>{summary}</div>} /> + <Part title={i18n.str`Amount`} text={<Amount value={amount} />} /> <Part - title={<i18n.Translate>Subject</i18n.Translate>} - text={<div>{summary}</div>} - /> - <Part - title={<i18n.Translate>Amount</i18n.Translate>} - text={<Amount value={amount} />} - /> - <Part - title={<i18n.Translate>Valid until</i18n.Translate>} + title={i18n.str`Valid until`} text={<Time timestamp={expiration} format="dd MMMM yyyy, HH:mm" />} kind="neutral" /> @@ -84,11 +62,6 @@ export function ReadyView({ </i18n.Translate> </Button> </section> - <section> - <Link upperCased onClick={cancel.onClick}> - <i18n.Translate>Cancel</i18n.Translate> - </Link> - </section> </WalletAction> ); } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index 25d4e44e5..7dfc7c141 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -27,7 +27,9 @@ import { import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; -import { LoadingInfoView, LoadingUriView, SuccessView } from "./views.js"; +import { SuccessView } from "./views.js"; +import { ErrorAlert } from "../../context/alert.js"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; export interface PropsFromURI { talerWithdrawUri: string | undefined; @@ -44,7 +46,6 @@ export interface PropsFromParams { export type State = | State.Loading | State.LoadingUriError - | State.LoadingInfoError | SelectExchangeState.NoExchange | SelectExchangeState.Selecting | State.Success; @@ -55,12 +56,8 @@ export namespace State { error: undefined; } export interface LoadingUriError { - status: "uri-error"; - error: HookError; - } - export interface LoadingInfoError { - status: "amount-error"; - error: HookError; + status: "error"; + error: ErrorAlert; } export type Success = { @@ -86,8 +83,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "uri-error": LoadingUriView, - "amount-error": LoadingInfoView, + error: ErrorAlertView, "no-exchange": NoExchangesView, "selecting-exchange": ExchangeSelectionPage, success: SuccessView, diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index d1853442b..18c467aae 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -23,7 +23,9 @@ import { } from "@gnu-taler/taler-util"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; import { RecursiveState } from "../../utils/index.js"; @@ -35,6 +37,7 @@ export function useComponentStateFromParams({ onSuccess, }: PropsFromParams): RecursiveState<State> { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const uriInfoHook = useAsyncAsHook(async () => { const exchanges = await api.wallet.call( WalletApiOperation.ListExchanges, @@ -47,8 +50,11 @@ export function useComponentStateFromParams({ if (uriInfoHook.hasError) { return { - status: "uri-error", - error: uriInfoHook, + status: "error", + error: alertFromError( + i18n.str`Could not load the list of exchanges`, + uriInfoHook, + ), }; } @@ -95,6 +101,7 @@ export function useComponentStateFromURI({ onSuccess, }: PropsFromURI): RecursiveState<State> { const api = useBackendContext(); + const { i18n } = useTranslationContext(); /** * Ask the wallet about the withdraw URI */ @@ -123,8 +130,11 @@ export function useComponentStateFromURI({ if (uriInfoHook.hasError) { return { - status: "uri-error", - error: uriInfoHook, + status: "error", + error: alertFromError( + i18n.str`Could not load info from URI`, + uriInfoHook, + ), }; } @@ -194,6 +204,7 @@ function exchangeSelectionState( } return () => { + const { i18n } = useTranslationContext(); const [ageRestricted, setAgeRestricted] = useState(0); const currentExchange = selectedExchange.selected; const tosNeedToBeAccepted = @@ -255,8 +266,11 @@ function exchangeSelectionState( } if (amountHook.hasError) { return { - status: "amount-error", - error: amountHook, + status: "error", + error: alertFromError( + i18n.str`Could not load the withdrawal details`, + amountHook, + ), }; } if (!amountHook.response) { diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts index 3277ac18d..2caa50dca 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts @@ -84,11 +84,11 @@ describe("Withdraw CTA states", () => { expect(status).equals("loading"); }, ({ status, error }) => { - if (status != "uri-error") expect.fail(); + if (status != "error") expect.fail(); if (!error) expect.fail(); - if (!error.hasError) expect.fail(); - if (error.operational) expect.fail(); - expect(error.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL"); + // if (!error.hasError) expect.fail(); + // if (error.operational) expect.fail(); + expect(error.cause?.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL"); }, ], TestingContext, diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx index 9dbe24b7e..cf87b35bb 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx @@ -19,16 +19,10 @@ import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Amount } from "../../components/Amount.js"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { Part } from "../../components/Part.js"; import { QR } from "../../components/QR.js"; import { SelectList } from "../../components/SelectList.js"; -import { - Input, - Link, - LinkSuccess, - SvgIcon, -} from "../../components/styled/index.js"; +import { Input, LinkSuccess, SvgIcon } from "../../components/styled/index.js"; import { TermsOfService } from "../../components/TermsOfService/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; @@ -36,30 +30,6 @@ import editIcon from "../../svg/edit_24px.svg"; import { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={ - <i18n.Translate>Could not get the info from the URI</i18n.Translate> - } - error={error} - /> - ); -} - -export function LoadingInfoView({ error }: State.LoadingInfoError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not get info of withdrawal</i18n.Translate>} - error={error} - /> - ); -} - export function SuccessView(state: State.Success): VNode { const { i18n } = useTranslationContext(); const currentTosVersionIsAccepted = @@ -68,11 +38,7 @@ export function SuccessView(state: State.Success): VNode { <Fragment> {state.doWithdrawal.error && ( <ErrorTalerOperation - title={ - <i18n.Translate> - Could not finish the withdrawal operation - </i18n.Translate> - } + title={i18n.str`Could not finish the withdrawal operation`} error={state.doWithdrawal.error.errorDetail} /> )} @@ -103,7 +69,7 @@ export function SuccessView(state: State.Success): VNode { big /> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <WithdrawDetails amount={{ @@ -116,7 +82,7 @@ export function SuccessView(state: State.Success): VNode { {state.ageRestriction && ( <Input> <SelectList - label={<i18n.Translate>Age restriction</i18n.Translate>} + label={i18n.str`Age restriction`} list={state.ageRestriction.list} name="age" value={state.ageRestriction.value} @@ -148,11 +114,6 @@ export function SuccessView(state: State.Success): VNode { {state.talerWithdrawUri ? ( <WithdrawWithMobile talerWithdrawUri={state.talerWithdrawUri} /> ) : undefined} - <section> - <Link upperCased onClick={state.cancel}> - <i18n.Translate>Cancel</i18n.Translate> - </Link> - </section> </Fragment> ); } @@ -168,11 +129,7 @@ function WithdrawWithMobile({ return ( <section> <LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}> - {!showQR ? ( - <i18n.Translate>Withdraw to a mobile phone</i18n.Translate> - ) : ( - <i18n.Translate>Hide QR</i18n.Translate> - )} + {!showQR ? i18n.str`Withdraw to a mobile phone` : i18n.str`Hide QR`} </LinkSuccess> {showQR && ( <div> diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts index 1b2929317..978ea90e1 100644 --- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts +++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts @@ -16,6 +16,7 @@ import { TalerErrorDetail } from "@gnu-taler/taler-util"; import { TalerError } from "@gnu-taler/taler-wallet-core"; import { useEffect, useMemo, useState } from "preact/hooks"; +import { WalletError } from "../wxApi.js"; export interface HookOk<T> { hasError: false; @@ -26,13 +27,14 @@ export type HookError = HookGenericError | HookOperationalError; export interface HookGenericError { hasError: true; - operational: false; + type: "error"; message: string; } export interface HookOperationalError { hasError: true; - operational: true; + type: "taler"; + message: string; details: TalerErrorDetail; } @@ -68,13 +70,21 @@ export function useAsyncAsHook<T>( if (e instanceof TalerError) { setHookResponse({ hasError: true, - operational: true, + type: "taler", + message: e.message, details: e.errorDetail, }); + } else if (e instanceof WalletError) { + setHookResponse({ + hasError: true, + type: "taler", + message: e.message, + details: e.errorDetail.errorDetail, + }); } else if (e instanceof Error) { setHookResponse({ hasError: true, - operational: false, + type: "error", message: e.message, }); } diff --git a/packages/taler-wallet-webextension/src/hooks/useLang.ts b/packages/taler-wallet-webextension/src/hooks/useLang.ts index 269fe6239..b1aa40015 100644 --- a/packages/taler-wallet-webextension/src/hooks/useLang.ts +++ b/packages/taler-wallet-webextension/src/hooks/useLang.ts @@ -17,6 +17,7 @@ import { useNotNullLocalStorage } from "./useLocalStorage.js"; function getBrowserLang(): string | undefined { + if (typeof window === "undefined") return undefined; if (window.navigator.languages) return window.navigator.languages[0]; if (window.navigator.language) return window.navigator.language; return undefined; diff --git a/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts b/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts index 88b7655b6..387798c96 100644 --- a/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts +++ b/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts @@ -75,6 +75,9 @@ export function useNotNullLocalStorage( } }; - const isSaved = window.localStorage.getItem(key) !== null; + const isSaved = + typeof window === "undefined" + ? false + : window.localStorage.getItem(key) !== null; return [storedValue, setValue, isSaved]; } diff --git a/packages/taler-wallet-webextension/src/mui/Alert.stories.tsx b/packages/taler-wallet-webextension/src/mui/Alert.stories.tsx index 62f7a2993..b0c2a2730 100644 --- a/packages/taler-wallet-webextension/src/mui/Alert.stories.tsx +++ b/packages/taler-wallet-webextension/src/mui/Alert.stories.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { TranslatedString } from "@gnu-taler/taler-util"; import { css } from "@linaria/core"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { Alert } from "./Alert.jsx"; @@ -53,16 +54,16 @@ export const BasicExample = (): VNode => ( export const WithTitle = (): VNode => ( <Wrapper> - <Alert title="Warning" severity="warning"> + <Alert title={"Warning" as TranslatedString} severity="warning"> this is an warning </Alert> - <Alert title="Error" severity="error"> + <Alert title={"Error" as TranslatedString} severity="error"> this is an error </Alert> - <Alert title="Success" severity="success"> + <Alert title={"Success" as TranslatedString} severity="success"> this is an success </Alert> - <Alert title="Info" severity="info"> + <Alert title={"Info" as TranslatedString} severity="info"> this is an info </Alert> </Wrapper> @@ -74,16 +75,32 @@ const showSomething = async function (): Promise<void> { export const WithAction = (): VNode => ( <Wrapper> - <Alert title="Warning" severity="warning" onClose={showSomething}> + <Alert + title={"Warning" as TranslatedString} + severity="warning" + onClose={showSomething} + > this is an warning </Alert> - <Alert title="Error" severity="error" onClose={showSomething}> + <Alert + title={"Error" as TranslatedString} + severity="error" + onClose={showSomething} + > this is an error </Alert> - <Alert title="Success" severity="success" onClose={showSomething}> + <Alert + title={"Success" as TranslatedString} + severity="success" + onClose={showSomething} + > this is an success </Alert> - <Alert title="Info" severity="info" onClose={showSomething}> + <Alert + title={"Info" as TranslatedString} + severity="info" + onClose={showSomething} + > this is an info </Alert> </Wrapper> diff --git a/packages/taler-wallet-webextension/src/mui/Alert.tsx b/packages/taler-wallet-webextension/src/mui/Alert.tsx index 360c3c3cb..b00312a86 100644 --- a/packages/taler-wallet-webextension/src/mui/Alert.tsx +++ b/packages/taler-wallet-webextension/src/mui/Alert.tsx @@ -13,6 +13,7 @@ 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 { TranslatedString } from "@gnu-taler/taler-util"; import { css } from "@linaria/core"; import { ComponentChildren, h, VNode } from "preact"; // eslint-disable-next-line import/extensions @@ -61,7 +62,7 @@ const colorVariant = { }; interface Props { - title?: string; + title?: TranslatedString; variant?: "filled" | "outlined" | "standard"; role?: string; onClose?: () => Promise<void>; @@ -110,20 +111,20 @@ function Message({ title, children, }: { - title?: string; + title?: TranslatedString; children: ComponentChildren; }): VNode { return ( <div class={css` padding: 8px 0px; - width: 100%; + width: 90%; `} > {title && ( <Typography class={css` - font-weight: ${theme.typography.fontWeightMedium}; + font-weight: ${theme.typography.fontWeightBold}; `} gutterBottom > @@ -160,6 +161,7 @@ export function Alert({ "--color-main": theme.palette[severity].main, "--color-light": theme.palette[severity].light, // ...(style as any), + textAlign: "left", }} elevation={1} > diff --git a/packages/taler-wallet-webextension/src/mui/Paper.tsx b/packages/taler-wallet-webextension/src/mui/Paper.tsx index 3b5f24bc1..0c805e307 100644 --- a/packages/taler-wallet-webextension/src/mui/Paper.tsx +++ b/packages/taler-wallet-webextension/src/mui/Paper.tsx @@ -29,9 +29,6 @@ const borderVariant = { `, }; const baseStyle = css` - background-color: ${theme.palette.background.paper}; - color: ${theme.palette.text.primary}; - .theme-dark & { background-image: var(--gradient-white-elevation); } diff --git a/packages/taler-wallet-webextension/src/mui/colors/manipulation.ts b/packages/taler-wallet-webextension/src/mui/colors/manipulation.ts index 226d3c860..f9bf9eb2b 100644 --- a/packages/taler-wallet-webextension/src/mui/colors/manipulation.ts +++ b/packages/taler-wallet-webextension/src/mui/colors/manipulation.ts @@ -57,7 +57,7 @@ export function hexToRgb(color: string): string { let colors = color.match(re); if (colors && colors[0].length === 1) { - colors = colors.map((n) => n + n); + colors = colors.map((n) => n + n) as RegExpMatchArray; } return colors diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts index cd09f6438..40993477b 100644 --- a/packages/taler-wallet-webextension/src/platform/api.ts +++ b/packages/taler-wallet-webextension/src/platform/api.ts @@ -146,9 +146,9 @@ export interface BackgroundPlatformAPI { */ getPermissionsApi(): CrossBrowserPermissionsApi; /** - * Used by the wallet backend to send notification about new information - * @param message - */ + * Used by the wallet backend to send notification about new information + * @param message + */ sendMessageToAllChannels(message: MessageFromBackend): void; /** @@ -196,7 +196,6 @@ export interface ForegroundPlatformAPI { */ openWalletURIFromPopup(talerUri: string): void; - /** * Popup API * @@ -248,5 +247,4 @@ export interface ForegroundPlatformAPI { listenToWalletBackground( listener: (message: MessageFromBackend) => void, ): () => void; - } diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts index e5efdec4e..fc51a65fb 100644 --- a/packages/taler-wallet-webextension/src/platform/chrome.ts +++ b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -14,17 +14,17 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { - classifyTalerUri, Logger, - TalerUriType -} from "@gnu-taler/taler-util"; +import { classifyTalerUri, Logger, TalerUriType } from "@gnu-taler/taler-util"; import { WalletOperations } from "@gnu-taler/taler-wallet-core"; import { BackgroundOperations } from "../wxApi.js"; import { - BackgroundPlatformAPI, CrossBrowserPermissionsApi, ForegroundPlatformAPI, MessageFromBackend, + BackgroundPlatformAPI, + CrossBrowserPermissionsApi, + ForegroundPlatformAPI, + MessageFromBackend, MessageFromFrontend, MessageResponse, - Permissions + Permissions, } from "./api.js"; const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { @@ -306,13 +306,12 @@ function openWalletPageFromPopup(page: string): void { }); } - let nextMessageIndex = 0; /** * To be used by the foreground - * @param message - * @returns + * @param message + * @returns */ async function sendMessageToBackground< Op extends WalletOperations | BackgroundOperations, @@ -321,13 +320,13 @@ async function sendMessageToBackground< return new Promise<any>((resolve, reject) => { logger.trace("send operation to the wallet background", message); - let timedout = false + let timedout = false; setTimeout(() => { - timedout = true - reject("timedout") + timedout = true; + reject("timedout"); }, 2000); chrome.runtime.sendMessage(messageWithId, (backgroundResponse) => { - if (timedout) return false + if (timedout) return false; if (chrome.runtime.lastError) { reject(chrome.runtime.lastError.message); } else { @@ -358,7 +357,6 @@ function listenToWalletBackground(listener: (m: any) => void): () => void { const allPorts: chrome.runtime.Port[] = []; - function sendMessageToAllChannels(message: MessageFromBackend): void { for (const notif of allPorts) { // const message: MessageFromBackend = { type: msg.type }; @@ -578,26 +576,26 @@ function setAlertedIcon(): void { interface OffscreenCanvasRenderingContext2D extends CanvasState, - CanvasTransform, - CanvasCompositing, - CanvasImageSmoothing, - CanvasFillStrokeStyles, - CanvasShadowStyles, - CanvasFilters, - CanvasRect, - CanvasDrawPath, - CanvasUserInterface, - CanvasText, - CanvasDrawImage, - CanvasImageData, - CanvasPathDrawingStyles, - CanvasTextDrawingStyles, - CanvasPath { + CanvasTransform, + CanvasCompositing, + CanvasImageSmoothing, + CanvasFillStrokeStyles, + CanvasShadowStyles, + CanvasFilters, + CanvasRect, + CanvasDrawPath, + CanvasUserInterface, + CanvasText, + CanvasDrawImage, + CanvasImageData, + CanvasPathDrawingStyles, + CanvasTextDrawingStyles, + CanvasPath { readonly canvas: OffscreenCanvas; } declare const OffscreenCanvasRenderingContext2D: { prototype: OffscreenCanvasRenderingContext2D; - new(): OffscreenCanvasRenderingContext2D; + new (): OffscreenCanvasRenderingContext2D; }; interface OffscreenCanvas extends EventTarget { @@ -610,7 +608,7 @@ interface OffscreenCanvas extends EventTarget { } declare const OffscreenCanvas: { prototype: OffscreenCanvas; - new(width: number, height: number): OffscreenCanvas; + new (width: number, height: number): OffscreenCanvas; }; function createCanvas(size: number): OffscreenCanvas { diff --git a/packages/taler-wallet-webextension/src/platform/firefox.ts b/packages/taler-wallet-webextension/src/platform/firefox.ts index a36859a0b..7f6980be7 100644 --- a/packages/taler-wallet-webextension/src/platform/firefox.ts +++ b/packages/taler-wallet-webextension/src/platform/firefox.ts @@ -14,7 +14,12 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { BackgroundPlatformAPI, CrossBrowserPermissionsApi, ForegroundPlatformAPI, Permissions } from "./api.js"; +import { + BackgroundPlatformAPI, + CrossBrowserPermissionsApi, + ForegroundPlatformAPI, + Permissions, +} from "./api.js"; import chromePlatform, { containsHostPermissions as chromeHostContains, removeHostPermissions as chromeHostRemove, diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx index 8786b2ff7..96f0f6dd9 100644 --- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx @@ -19,12 +19,13 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { BalanceTable } from "../components/BalanceTable.js"; +import { ErrorAlertView } from "../components/CurrentAlerts.js"; import { Loading } from "../components/Loading.js"; -import { LoadingError } from "../components/LoadingError.js"; import { MultiActionButton } from "../components/MultiActionButton.js"; +import { alertFromError, ErrorAlert } from "../context/alert.js"; import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; -import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; +import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Button } from "../mui/Button.js"; import { ButtonHandler } from "../mui/handlers.js"; import { compose, StateViewMap } from "../utils/index.js"; @@ -47,7 +48,7 @@ export namespace State { export interface Error { status: "error"; - error: HookError; + error: ErrorAlert; } export interface Action { @@ -73,6 +74,7 @@ function useComponentState({ goToWalletManualWithdraw, }: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const [addingAction, setAddingAction] = useState(false); const state = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.GetBalances, {}), @@ -94,7 +96,7 @@ function useComponentState({ if (state.hasError) { return { status: "error", - error: state, + error: alertFromError(i18n.str`Could not load the balance`, state), }; } if (addingAction) { @@ -123,7 +125,7 @@ function useComponentState({ const viewMapping: StateViewMap<State> = { loading: Loading, - error: ErrorView, + error: ErrorAlertView, action: ActionView, balance: BalanceView, }; @@ -134,16 +136,6 @@ export const BalancePage = compose( viewMapping, ); -function ErrorView({ error }: State.Error): VNode { - const { i18n } = useTranslationContext(); - return ( - <LoadingError - title={<i18n.Translate>Could not load balance page</i18n.Translate>} - error={error} - /> - ); -} - function ActionView({ cancel }: State.Action): VNode { return <AddNewActionView onCancel={cancel.onClick!} />; } @@ -179,7 +171,7 @@ export function BalanceView(state: State.Balances): VNode { </Button> {currencyWithNonZeroAmount.length > 0 && ( <MultiActionButton - label={(s) => <i18n.Translate>Send {s}</i18n.Translate>} + label={(s) => i18n.str`Send ${s}`} actions={currencyWithNonZeroAmount} onClick={(c) => state.goToWalletDeposit(c)} /> diff --git a/packages/taler-wallet-webextension/src/popup/NoBalanceHelp.tsx b/packages/taler-wallet-webextension/src/popup/NoBalanceHelp.tsx index 7d2e15726..5eb31ba46 100644 --- a/packages/taler-wallet-webextension/src/popup/NoBalanceHelp.tsx +++ b/packages/taler-wallet-webextension/src/popup/NoBalanceHelp.tsx @@ -33,7 +33,7 @@ export function NoBalanceHelp({ const { i18n } = useTranslationContext(); return ( <Paper class={margin}> - <Alert title="Your wallet is empty." severity="info"> + <Alert title={i18n.str`Your wallet is empty.`} severity="info"> <Button fullWidth color="info" diff --git a/packages/taler-wallet-webextension/src/svg/progress.svg b/packages/taler-wallet-webextension/src/svg/progress.svg new file mode 100644 index 000000000..c7284a545 --- /dev/null +++ b/packages/taler-wallet-webextension/src/svg/progress.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin:auto;background:#fff;display:block;" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"> + <defs> + <clipPath id="progress-cp" x="0" y="0" width="100" height="100"> + <rect x="0" y="0" width="0" height="100"> + <animate attributeName="width" repeatCount="indefinite" dur="2s" values="0;100;100" keyTimes="0;0.5;1"></animate> + <animate attributeName="x" repeatCount="indefinite" dur="2s" values="0;0;100" keyTimes="0;0.5;1"></animate> + </rect> + </clipPath> + </defs> + <path fill="none" stroke="darkgrey" stroke-width="1.04" d="M10.000000000000004 44.019999999999996L89.99999999999999 44.019999999999996A5.98 5.98 0 0 1 95.97999999999999 50L95.97999999999999 50A5.98 5.98 0 0 1 89.99999999999999 55.980000000000004L10.000000000000004 55.980000000000004A5.98 5.98 0 0 1 4.020000000000003 50L4.020000000000003 50A5.98 5.98 0 0 1 10.000000000000004 44.019999999999996 Z"></path> + <path fill="#0042b2" clip-path="url(#progress-cp)" d="M10.000000000000004 45.54L90 45.54A4.460000000000001 4.460000000000001 0 0 1 94.46 50L94.46 50A4.460000000000001 4.460000000000001 0 0 1 90 54.46L10.000000000000004 54.46A4.460000000000001 4.460000000000001 0 0 1 5.540000000000003 50L5.540000000000003 50A4.460000000000001 4.460000000000001 0 0 1 10.000000000000004 45.54 Z"></path> +</svg>
\ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/test-utils.ts b/packages/taler-wallet-webextension/src/test-utils.ts index 379513782..7e7ddd88d 100644 --- a/packages/taler-wallet-webextension/src/test-utils.ts +++ b/packages/taler-wallet-webextension/src/test-utils.ts @@ -32,6 +32,7 @@ import { } from "preact"; import { render as renderToString } from "preact-render-to-string"; import { BackendProvider } from "./context/backend.js"; +import { TranslationProvider } from "./context/translation.js"; import { BackgroundApiClient, wxApi } from "./wxApi.js"; // When doing tests we want the requestAnimationFrame to be as fast as possible. @@ -359,10 +360,12 @@ export function createWalletApiMock(): { }; function TestingContext({ - children, + children: _cs, }: { children: ComponentChildren; }): VNode { + let children = _cs; + children = create(TranslationProvider, { children }, children); return create( BackendProvider, { diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts index 10fcd84ce..4ec4c0ffe 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts @@ -16,8 +16,9 @@ import { TalerErrorDetail } from "@gnu-taler/taler-util"; import { SyncTermsOfServiceResponse } from "@gnu-taler/taler-wallet-core"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler, TextFieldHandler, @@ -25,11 +26,7 @@ import { } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { - ConfirmProviderView, - LoadingUriView, - SelectProviderView, -} from "./views.js"; +import { ConfirmProviderView, SelectProviderView } from "./views.js"; export interface Props { onBack: () => Promise<void>; @@ -50,8 +47,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-error"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface ConfirmProvider { @@ -77,7 +74,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-error": LoadingUriView, + error: ErrorAlertView, "select-provider": SelectProviderView, "confirm-provider": ConfirmProviderView, }; diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx index b633a595f..c3afc0d33 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx @@ -17,12 +17,10 @@ import { Amounts } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { Checkbox } from "../../components/Checkbox.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { LightText, SmallLightText, SubTitle, - TermsOfService, Title, } from "../../components/styled/index.js"; import { useTranslationContext } from "../../context/translation.js"; @@ -30,17 +28,6 @@ import { Button } from "../../mui/Button.js"; import { TextField } from "../../mui/TextField.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - export function ConfirmProviderView({ url, provider, @@ -88,9 +75,8 @@ export function ConfirmProviderView({ of service </i18n.Translate> </p> - {/* replace with <TermsOfService /> */} <Checkbox - label={<i18n.Translate>Accept terms of service</i18n.Translate>} + label={i18n.str`Accept terms of service`} name="terms" onToggle={tos.button.onClick} enabled={tos.value} diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index 372db847c..46fe02225 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -25,13 +25,17 @@ import { createHashHistory } from "history"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import Router, { route, Route } from "preact-router"; import { useEffect } from "preact/hooks"; +import { CurrentAlerts } from "../components/CurrentAlerts.js"; import { LogoHeader } from "../components/LogoHeader.js"; import PendingTransactions from "../components/PendingTransactions.js"; import { + Link, + LinkPrimary, SubTitle, WalletAction, WalletBox, } from "../components/styled/index.js"; +import { AlertProvider } from "../context/alert.js"; import { DevContextProvider } from "../context/devContext.js"; import { IoCProviderForRuntime } from "../context/iocContext.js"; import { @@ -66,6 +70,7 @@ import { QrReaderPage } from "./QrReader.js"; import { SettingsPage } from "./Settings.js"; import { TransactionPage } from "./Transaction.js"; import { WelcomePage } from "./Welcome.js"; +import CloseIcon from "../svg/close_24px.svg"; export function Application(): VNode { const { i18n } = useTranslationContext(); @@ -495,15 +500,6 @@ function matchesRoute(url: string, route: string): boolean { return !result ? false : true; } -function shouldShowPendingOperations(url: string): boolean { - return [ - Pages.balanceHistory.pattern, - Pages.dev, - Pages.settings, - Pages.backup, - ].some((p) => matchesRoute(url, p)); -} - function CallToActionTemplate({ title, children, @@ -511,11 +507,35 @@ function CallToActionTemplate({ title: TranslatedString; children: ComponentChildren; }): VNode { + const { i18n } = useTranslationContext(); return ( <WalletAction> <LogoHeader /> + <section style={{ display: "flex", justifyContent: "right", margin: 0 }}> + <LinkPrimary href={Pages.balance}> + <div + style={{ + height: 24, + width: 24, + marginLeft: 4, + marginRight: 4, + border: "1px solid black", + borderRadius: 12, + }} + dangerouslySetInnerHTML={{ __html: CloseIcon }} + /> + </LinkPrimary> + </section> <SubTitle>{title}</SubTitle> - {children} + <AlertProvider> + <CurrentAlerts /> + {children} + </AlertProvider> + <section style={{ display: "flex", justifyContent: "right" }}> + <LinkPrimary href={Pages.balance}> + <i18n.Translate>Return to wallet</i18n.Translate> + </LinkPrimary> + </section> </WalletAction> ); } @@ -536,7 +556,10 @@ function WalletTemplate({ {goToTransaction ? ( <PendingTransactions goToTransaction={goToTransaction} /> ) : undefined} - <WalletBox>{children}</WalletBox> + <CurrentAlerts /> + <WalletBox> + <AlertProvider>{children}</AlertProvider> + </WalletBox> </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx index 6e987f965..48c9c9cb1 100644 --- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx @@ -29,8 +29,8 @@ import { } from "date-fns"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; +import { AlertView } from "../components/CurrentAlerts.js"; import { Loading } from "../components/Loading.js"; -import { LoadingError } from "../components/LoadingError.js"; import { QR } from "../components/QR.js"; import { BoldLight, @@ -42,6 +42,7 @@ import { SmallText, WarningBox, } from "../components/styled/index.js"; +import { alertFromError } from "../context/alert.js"; import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; @@ -117,9 +118,11 @@ export function BackupPage({ onAddProvider }: Props): VNode { } if (status.hasError) { return ( - <LoadingError - title={<i18n.Translate>Could not load backup providers</i18n.Translate>} - error={status} + <AlertView + alert={alertFromError( + i18n.str`Could not load backup providers`, + status, + )} /> ); } @@ -219,11 +222,9 @@ export function BackupView({ </div> <div> <Button variant="contained" onClick={onSyncAll}> - {providers.length > 1 ? ( - <i18n.Translate>Sync all backups</i18n.Translate> - ) : ( - <i18n.Translate>Sync now</i18n.Translate> - )} + {providers.length > 1 + ? i18n.str`Sync all backups` + : i18n.str`Sync now`} </Button> <Button variant="contained" color="success" onClick={onAddProvider}> <i18n.Translate>Add provider</i18n.Translate> diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts index 6ffbccc27..6de406400 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts @@ -15,8 +15,9 @@ */ import { AmountJson, PaytoUri } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { AmountFieldHandler, ButtonHandler, @@ -27,7 +28,6 @@ import { ManageAccountPage } from "../ManageAccount/index.js"; import { useComponentState } from "./state.js"; import { AmountOrCurrencyErrorView, - LoadingErrorView, NoAccountToDepositView, NoEnoughBalanceView, ReadyView, @@ -56,8 +56,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-error"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface AddingAccount { @@ -107,7 +107,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-error": LoadingErrorView, + error: ErrorAlertView, "amount-or-currency-error": AmountOrCurrencyErrorView, "no-enough-balance": NoEnoughBalanceView, "no-accounts": NoAccountToDepositView, diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts index 02e85a1c7..b597c77be 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts @@ -25,7 +25,9 @@ import { } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; @@ -36,6 +38,7 @@ export function useComponentState({ onSuccess, }: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr); const currency = parsed !== undefined ? parsed.currency : currencyStr; @@ -82,8 +85,8 @@ export function useComponentState({ } if (hook.hasError) { return { - status: "loading-error", - error: hook, + status: "error", + error: alertFromError(i18n.str`Could not load balance information`, hook), }; } const { accounts, balances } = hook.response; diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx index 6a28f31e1..0d827db43 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx @@ -18,31 +18,13 @@ import { Amounts, PaytoUri } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { AmountField } from "../../components/AmountField.js"; import { ErrorMessage } from "../../components/ErrorMessage.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { SelectList } from "../../components/SelectList.js"; -import { - ErrorText, - Input, - InputWithLabel, - SubTitle, - WarningBox, -} from "../../components/styled/index.js"; +import { Input, SubTitle, WarningBox } from "../../components/styled/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; import { Grid } from "../../mui/Grid.js"; import { State } from "./index.js"; -export function LoadingErrorView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load deposit balance</i18n.Translate>} - error={error} - /> - ); -} - export function AmountOrCurrencyErrorView( p: State.AmountOrCurrencyError, ): VNode { @@ -50,11 +32,7 @@ export function AmountOrCurrencyErrorView( return ( <ErrorMessage - title={ - <i18n.Translate> - A currency or an amount should be indicated - </i18n.Translate> - } + title={i18n.str`A currency or an amount should be indicated`} /> ); } @@ -66,11 +44,7 @@ export function NoEnoughBalanceView({ return ( <ErrorMessage - title={ - <i18n.Translate> - There is no enough balance to make a deposit for currency {currency} - </i18n.Translate> - } + title={i18n.str`There is no enough balance to make a deposit for currency ${currency}`} /> ); } @@ -150,7 +124,7 @@ export function ReadyView(state: State.Ready): VNode { > <Input> <SelectList - label={<i18n.Translate>Select account</i18n.Translate>} + label={i18n.str`Select account`} list={state.account.list} name="account" value={state.account.value} @@ -171,14 +145,11 @@ export function ReadyView(state: State.Ready): VNode { </p> <Grid container spacing={2} columns={1}> <Grid item xs={1}> - <AmountField - label={<i18n.Translate>Amount</i18n.Translate>} - handler={state.amount} - /> + <AmountField label={i18n.str`Amount`} handler={state.amount} /> </Grid> <Grid item xs={1}> <AmountField - label={<i18n.Translate>Deposit fee</i18n.Translate>} + label={i18n.str`Deposit fee`} handler={{ value: state.totalFee, }} @@ -186,7 +157,7 @@ export function ReadyView(state: State.Ready): VNode { </Grid> <Grid item xs={1}> <AmountField - label={<i18n.Translate>Total deposit</i18n.Translate>} + label={i18n.str`Total deposit`} handler={{ value: state.totalToDeposit, }} diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts index f1e766a18..bd6b32e78 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts @@ -14,12 +14,13 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { AmountFieldHandler, ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView, SelectCurrencyView } from "./views.js"; +import { ReadyView, SelectCurrencyView } from "./views.js"; export type Props = PropsGet | PropsSend; @@ -49,8 +50,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-error"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface SelectCurrency { @@ -80,7 +81,7 @@ export type Contact = { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-error": LoadingUriView, + error: ErrorAlertView, "select-currency": SelectCurrencyView, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts index dd711f406..1fe324c5a 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts @@ -17,7 +17,9 @@ import { Amounts } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { assertUnreachable, RecursiveState } from "../../utils/index.js"; import { Contact, Props, State } from "./index.js"; @@ -59,6 +61,8 @@ export function useComponentState(props: Props): RecursiveState<State> { if (!amount) { return () => { // eslint-disable-next-line react-hooks/rules-of-hooks + const { i18n } = useTranslationContext(); + // eslint-disable-next-line react-hooks/rules-of-hooks const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {}), ); @@ -71,8 +75,8 @@ export function useComponentState(props: Props): RecursiveState<State> { } if (hook.hasError) { return { - status: "loading-error", - error: hook, + status: "error", + error: alertFromError(i18n.str`Could not load exchanges`, hook), }; } const currencies: Record<string, string> = {}; diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx index a9a4b2e41..8a7a1fa97 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx @@ -16,7 +16,7 @@ import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; -import { LoadingError } from "../../components/LoadingError.js"; +import { AmountField } from "../../components/AmountField.js"; import { SelectList } from "../../components/SelectList.js"; import { Input, @@ -25,24 +25,14 @@ import { SvgIcon, } from "../../components/styled/index.js"; import { useTranslationContext } from "../../context/translation.js"; -import { Pages } from "../../NavigationBar.js"; -import { Contact, State } from "./index.js"; -import arrowIcon from "../../svg/chevron-down.svg"; -import { AmountField } from "../../components/AmountField.js"; +import { Button } from "../../mui/Button.js"; import { Grid } from "../../mui/Grid.js"; import { Paper } from "../../mui/Paper.js"; -import { Button } from "../../mui/Button.js"; +import { Pages } from "../../NavigationBar.js"; +import arrowIcon from "../../svg/chevron-down.svg"; +import bankIcon from "../../svg/ri-bank-line.svg"; import { assertUnreachable } from "../../utils/index.js"; - -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} +import { Contact, State } from "./index.js"; export function SelectCurrencyView({ currencies, @@ -61,7 +51,7 @@ export function SelectCurrencyView({ <p> <Input> <SelectList - label={<i18n.Translate>Known currencies</i18n.Translate>} + label={i18n.str`Known currencies`} list={currencies} name="lang" value={""} @@ -214,7 +204,7 @@ export function ReadyGetView({ </h1> <Grid container columns={2} justifyContent="space-between"> <AmountField - label={<i18n.Translate>Amount</i18n.Translate>} + label={i18n.str`Amount`} required handler={amountHandler} /> @@ -304,7 +294,7 @@ export function ReadySendView({ <div> <AmountField - label={<i18n.Translate>Amount</i18n.Translate>} + label={i18n.str`Amount`} required handler={amountHandler} /> @@ -377,7 +367,6 @@ export function ReadySendView({ </Container> ); } -import bankIcon from "../../svg/ri-bank-line.svg"; function RowExample({ info, diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts index 95badb218..afbaf1945 100644 --- a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts @@ -14,11 +14,12 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ReadyView } from "./views.js"; export interface Props { p: string; @@ -33,8 +34,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-error"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -48,7 +49,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-error": LoadingUriView, + error: ErrorAlertView, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/views.tsx b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/views.tsx index 5784a7db5..dc8a42b84 100644 --- a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/views.tsx @@ -15,21 +15,9 @@ */ import { h, VNode } from "preact"; -import { LoadingError } from "../../components/LoadingError.js"; import { useTranslationContext } from "../../context/translation.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - export function ReadyView({ error }: State.Ready): VNode { const { i18n } = useTranslationContext(); diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts index 10e44ce7d..299c236c4 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts @@ -20,7 +20,9 @@ import { ExchangeListItem, FeeDescriptionPair, } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; +import { ErrorAlert } from "../../context/alert.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; @@ -28,7 +30,6 @@ import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; import { ComparingView, - ErrorLoadingView, NoExchangesView, PrivacyContentView, ReadyView, @@ -58,8 +59,8 @@ export namespace State { } export interface LoadingUriError { - status: "error-loading"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -99,7 +100,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "error-loading": ErrorLoadingView, + error: ErrorAlertView, comparing: ComparingView, "no-exchange": NoExchangesView, "showing-tos": TosContentView, diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts index 3c10febd9..cfb32cbbb 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts @@ -20,7 +20,9 @@ import { WalletApiOperation, } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; @@ -31,6 +33,7 @@ export function useComponentState({ currentExchange, }: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const initialValue = exchanges.findIndex( (e) => e.exchangeBaseUrl === currentExchange, ); @@ -84,8 +87,11 @@ export function useComponentState({ } if (hook.hasError) { return { - status: "error-loading", - error: hook, + status: "error", + error: alertFromError( + i18n.str`Could not load exchange details info`, + hook, + ), }; } diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx index 26ff2c0d3..d01ce7ca0 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx @@ -20,7 +20,6 @@ import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Amount } from "../../components/Amount.js"; import { ErrorMessage } from "../../components/ErrorMessage.js"; -import { LoadingError } from "../../components/LoadingError.js"; import { SelectList } from "../../components/SelectList.js"; import { Input, SvgIcon } from "../../components/styled/index.js"; import { TermsOfService } from "../../components/TermsOfService/index.js"; @@ -110,17 +109,6 @@ const Container = styled.div` } `; -export function ErrorLoadingView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load exchange fees</i18n.Translate>} - error={error} - /> - ); -} - export function PrivacyContentView({ exchangeUrl, onClose, @@ -156,19 +144,11 @@ export function NoExchangesView({ }: SelectExchangeState.NoExchange): VNode { const { i18n } = useTranslationContext(); if (!currency) { - return ( - <ErrorMessage - title={<i18n.Translate>Could not find any exchange</i18n.Translate>} - /> - ); + return <ErrorMessage title={i18n.str`Could not find any exchange`} />; } return ( <ErrorMessage - title={ - <i18n.Translate> - Could not find any exchange for the currency {currency} - </i18n.Translate> - } + title={i18n.str`Could not find any exchange for the currency ${currency}`} /> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx index 8c31d8d95..404fc8bee 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx @@ -140,15 +140,13 @@ export function ExchangeSetUrlPage({ )} {error && ( <ErrorMessage - title={ - <i18n.Translate>Unable to verify this exchange</i18n.Translate> - } + title={i18n.str`Unable to verify this exchange`} description={error} /> )} {confirmationError && ( <ErrorMessage - title={<i18n.Translate>Unable to add this exchange</i18n.Translate>} + title={i18n.str`Unable to add this exchange`} description={confirmationError} /> )} diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index 50f634f52..143d3adbb 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -23,8 +23,8 @@ import { import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; +import { AlertView } from "../components/CurrentAlerts.js"; import { Loading } from "../components/Loading.js"; -import { LoadingError } from "../components/LoadingError.js"; import { CenteredBoldText, CenteredText, @@ -33,6 +33,7 @@ import { } from "../components/styled/index.js"; import { Time } from "../components/Time.js"; import { TransactionItem } from "../components/TransactionItem.js"; +import { alertFromError } from "../context/alert.js"; import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; @@ -71,13 +72,11 @@ export function HistoryPage({ if (state.hasError) { return ( - <LoadingError - title={ - <i18n.Translate> - Could not load the list of transactions - </i18n.Translate> - } - error={state} + <AlertView + alert={alertFromError( + i18n.str`Could not load the list of transactions`, + state, + )} /> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts index 8541821b7..3a00d48ce 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts @@ -15,8 +15,9 @@ */ import { KnownBankAccountsInfo } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler, SelectFieldHandler, @@ -24,7 +25,7 @@ import { } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ReadyView } from "./views.js"; export interface Props { currency: string; @@ -41,8 +42,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-error"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -68,7 +69,7 @@ export type AccountByType = { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-error": LoadingUriView, + error: ErrorAlertView, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts index 9690a5c79..176a8d100 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts @@ -21,7 +21,9 @@ import { } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { AccountByType, Props, State } from "./index.js"; @@ -31,6 +33,7 @@ export function useComponentState({ onCancel, }: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }), ); @@ -47,8 +50,8 @@ export function useComponentState({ } if (hook.hasError) { return { - status: "loading-error", - error: hook, + status: "error", + error: alertFromError(i18n.str`Could not load known bank accounts`, hook), }; } diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx b/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx index 3af0d5505..e5be8d17d 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx @@ -23,11 +23,10 @@ import { import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { LoadingError } from "../../components/LoadingError.js"; +import { ErrorMessage } from "../../components/ErrorMessage.js"; import { SelectList } from "../../components/SelectList.js"; import { Input, - LightText, SubTitle, SvgIcon, WarningText, @@ -37,10 +36,9 @@ import { Button } from "../../mui/Button.js"; import { TextFieldHandler } from "../../mui/handlers.js"; import { TextField } from "../../mui/TextField.js"; import checkIcon from "../../svg/check_24px.svg"; -import warningIcon from "../../svg/warning_24px.svg"; import deleteIcon from "../../svg/delete_24px.svg"; +import warningIcon from "../../svg/warning_24px.svg"; import { State } from "./index.js"; -import { ErrorMessage } from "../../components/ErrorMessage.js"; type AccountType = "bitcoin" | "x-taler-bank" | "iban"; type ComponentFormByAccountType = { @@ -80,17 +78,6 @@ const AccountTable = styled.table` } `; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load</i18n.Translate>} - error={error} - /> - ); -} - export function ReadyView({ currency, error, @@ -118,14 +105,14 @@ export function ReadyView({ {error && ( <ErrorMessage - title={<i18n.Translate>Unable add this account</i18n.Translate>} + title={i18n.str`Unable add this account`} description={error} /> )} <p> <Input> <SelectList - label={<i18n.Translate>Select account type</i18n.Translate>} + label={i18n.str`Select account type`} list={accountType.list} name="accountType" value={accountType.value} diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts index 4697ca549..22b3adb0f 100644 --- a/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts @@ -15,11 +15,12 @@ */ import { UserAttentionUnreadList } from "@gnu-taler/taler-util"; +import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ErrorAlert } from "../../context/alert.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, ReadyView } from "./views.js"; +import { ReadyView } from "./views.js"; export type Props = object; @@ -32,8 +33,8 @@ export namespace State { } export interface LoadingUriError { - status: "loading-error"; - error: HookError; + status: "error"; + error: ErrorAlert; } export interface BaseInfo { @@ -49,7 +50,7 @@ export namespace State { const viewMapping: StateViewMap<State> = { loading: Loading, - "loading-error": LoadingUriView, + error: ErrorAlertView, ready: ReadyView, }; diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts index 648e490ce..0e06a1e75 100644 --- a/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts @@ -15,12 +15,15 @@ */ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { alertFromError } from "../../context/alert.js"; import { useBackendContext } from "../../context/backend.js"; +import { useTranslationContext } from "../../context/translation.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { Props, State } from "./index.js"; export function useComponentState(p: Props): State { const api = useBackendContext(); + const { i18n } = useTranslationContext(); const hook = useAsyncAsHook(async () => { return await api.wallet.call( WalletApiOperation.GetUserAttentionRequests, @@ -34,10 +37,14 @@ export function useComponentState(p: Props): State { error: undefined, }; } + if (hook.hasError) { return { - status: "loading-error", - error: hook, + status: "error", + error: alertFromError( + i18n.str`Could not load user attention request`, + hook, + ), }; } diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/views.tsx b/packages/taler-wallet-webextension/src/wallet/Notifications/views.tsx index 9146d8837..8d0bb34c0 100644 --- a/packages/taler-wallet-webextension/src/wallet/Notifications/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Notifications/views.tsx @@ -20,7 +20,6 @@ import { AttentionType, } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; -import { LoadingError } from "../../components/LoadingError.js"; import { Column, DateSeparator, @@ -37,17 +36,6 @@ import { Pages } from "../../NavigationBar.js"; import { assertUnreachable } from "../../utils/index.js"; import { State } from "./index.js"; -export function LoadingUriView({ error }: State.LoadingUriError): VNode { - const { i18n } = useTranslationContext(); - - return ( - <LoadingError - title={<i18n.Translate>Could not load notifications</i18n.Translate>} - error={error} - /> - ); -} - const term = 1000 * 60 * 60 * 24; function normalizeToDay(x: number): number { return Math.round(x / term) * term; diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx index eb86c9a3f..286a2a88d 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx @@ -127,11 +127,7 @@ export function SetUrlView({ </Title> {error && ( <ErrorMessage - title={ - <i18n.Translate> - Could not get provider information - </i18n.Translate> - } + title={i18n.str`Could not get provider information`} description={error} /> )} @@ -223,7 +219,7 @@ export function ConfirmProviderView({ </SubTitle> <p> {Amounts.isZero(provider.annual_fee) ? ( - <i18n.Translate>free of charge</i18n.Translate> + i18n.str`free of charge` ) : ( <i18n.Translate> {provider.annual_fee} per year of service @@ -240,7 +236,7 @@ export function ConfirmProviderView({ </i18n.Translate> </p> <Checkbox - label={<i18n.Translate>Accept terms of service</i18n.Translate>} + label={i18n.str`Accept terms of service`} name="terms" onToggle={async () => setAccepted((old) => !old)} enabled={accepted} diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx index 46d54e871..9b72c0fae 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx @@ -23,11 +23,12 @@ import { WalletApiOperation, } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; +import { AlertView } from "../components/CurrentAlerts.js"; import { ErrorMessage } from "../components/ErrorMessage.js"; import { Loading } from "../components/Loading.js"; -import { LoadingError } from "../components/LoadingError.js"; import { PaymentStatus, SmallLightText } from "../components/styled/index.js"; import { Time } from "../components/Time.js"; +import { alertFromError } from "../context/alert.js"; import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; @@ -65,14 +66,11 @@ export function ProviderDetailPage({ } if (state.hasError) { return ( - <LoadingError - title={ - <i18n.Translate> - There was an error loading the provider detail for " - {providerURL}" - </i18n.Translate> - } - error={state} + <AlertView + alert={alertFromError( + i18n.str`There was an error loading the provider detail for "${providerURL}"`, + state, + )} /> ); } @@ -270,9 +268,7 @@ function Error({ info }: { info: ProviderInfo }): VNode { if (info.lastError) { return ( <ErrorMessage - title={ - <i18n.Translate>This provider has reported an error</i18n.Translate> - } + title={i18n.str`This provider has reported an error`} description={info.lastError.hint} /> ); @@ -282,32 +278,17 @@ function Error({ info }: { info: ProviderInfo }): VNode { case "backup-conflicting-device": return ( <ErrorMessage - title={ - <Fragment> - <i18n.Translate> - There is conflict with another backup from{" "} - <b>{info.backupProblem.otherDeviceId}</b> - </i18n.Translate> - </Fragment> - } + title={i18n.str`There is conflict with another backup from "${info.backupProblem.otherDeviceId}"`} /> ); case "backup-unreadable": - return ( - <ErrorMessage - title={<i18n.Translate>Backup is not readable</i18n.Translate>} - /> - ); + return <ErrorMessage title={i18n.str`Backup is not readable`} />; default: return ( <ErrorMessage - title={ - <Fragment> - <i18n.Translate> - Unknown backup problem: {JSON.stringify(info.backupProblem)} - </i18n.Translate> - </Fragment> - } + title={i18n.str`Unknown backup problem: ${JSON.stringify( + info.backupProblem, + )}`} /> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx index a259f7c9a..c366f014f 100644 --- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx @@ -42,8 +42,8 @@ export function ReserveCreated({ if (!paytoURI) { return ( <ErrorMessage - title={<i18n.Translate>Could not parse the payto URI</i18n.Translate>} - description={<i18n.Translate>Please check the uri</i18n.Translate>} + title={i18n.str`Could not parse the payto URI`} + description={i18n.str`Please check the uri`} /> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index 768a4ca6a..ed1bc838a 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -111,13 +111,13 @@ export function SettingsView({ <section> {autoOpenToggle.button.error && ( <ErrorTalerOperation - title={<i18n.Translate>Could not toggle auto-open</i18n.Translate>} + title={i18n.str`Could not toggle auto-open`} error={autoOpenToggle.button.error.errorDetail} /> )} {/* {clipboardToggle.button.error && ( <ErrorTalerOperation - title={<i18n.Translate>Could not toggle clipboard</i18n.Translate>} + title={i18n.str`Could not toggle clipboard`} error={clipboardToggle.button.error.errorDetail} /> )} */} @@ -125,11 +125,7 @@ export function SettingsView({ <i18n.Translate>Navigator</i18n.Translate> </SubTitle> <Checkbox - label={ - <i18n.Translate> - Automatically open wallet based on page content - </i18n.Translate> - } + label={i18n.str`Automatically open wallet based on page content`} name="autoOpen" description={ <i18n.Translate> @@ -142,9 +138,7 @@ export function SettingsView({ /> {/* <Checkbox label={ - <i18n.Translate> - Automatically check clipboard for Taler URI - </i18n.Translate> + i18n.str`Automatically check clipboard for Taler URI` } name="clipboard" description={ @@ -241,13 +235,9 @@ export function SettingsView({ <i18n.Translate>Troubleshooting</i18n.Translate> </SubTitle> <Checkbox - label={<i18n.Translate>Developer mode</i18n.Translate>} + label={i18n.str`Developer mode`} name="devMode" - description={ - <i18n.Translate> - More options and information useful for debugging - </i18n.Translate> - } + description={i18n.str`More options and information useful for debugging`} enabled={devModeToggle.value!} onToggle={devModeToggle.button.onClick!} /> @@ -271,7 +261,7 @@ export function SettingsView({ </SubTitle> {coreVersion && ( <Part - title={<i18n.Translate>Wallet Core</i18n.Translate>} + title={i18n.str`Wallet Core`} text={ <span> {coreVersion.version}{" "} @@ -281,7 +271,7 @@ export function SettingsView({ /> )} <Part - title={<i18n.Translate>Web Extension</i18n.Translate>} + title={i18n.str`Web Extension`} text={ <span> {webexVersion.version}{" "} @@ -292,15 +282,15 @@ export function SettingsView({ {coreVersion && ( <JustInDevMode> <Part - title={<i18n.Translate>Exchange compatibility</i18n.Translate>} + title={i18n.str`Exchange compatibility`} text={<span>{coreVersion.exchange}</span>} /> <Part - title={<i18n.Translate>Merchant compatibility</i18n.Translate>} + title={i18n.str`Merchant compatibility`} text={<span>{coreVersion.merchant}</span>} /> <Part - title={<i18n.Translate>Bank compatibility</i18n.Translate>} + title={i18n.str`Bank compatibility`} text={<span>{coreVersion.bank}</span>} /> </JustInDevMode> diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index b7eb4a947..542694490 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -32,6 +32,7 @@ import { TransactionRefund, TransactionTip, TransactionType, + TranslatedString, WithdrawalType, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -43,9 +44,8 @@ import emptyImg from "../../static/img/empty.png"; import { Amount } from "../components/Amount.js"; import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js"; import { CopyButton } from "../components/CopyButton.js"; -import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js"; +import { AlertView, ErrorAlertView } from "../components/CurrentAlerts.js"; import { Loading } from "../components/Loading.js"; -import { LoadingError } from "../components/LoadingError.js"; import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js"; import { QR } from "../components/QR.js"; import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js"; @@ -60,6 +60,7 @@ import { WarningBox, } from "../components/styled/index.js"; import { Time } from "../components/Time.js"; +import { alertFromError } from "../context/alert.js"; import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; @@ -98,13 +99,11 @@ export function TransactionPage({ if (state.hasError) { return ( - <LoadingError - title={ - <i18n.Translate> - Could not load the transaction information - </i18n.Translate> - } - error={state} + <AlertView + alert={alertFromError( + i18n.str`Could not load transaction information`, + state, + )} /> ); } @@ -199,14 +198,14 @@ export function TransactionView({ return ( <Fragment> <section style={{ padding: 8, textAlign: "center" }}> - <ErrorTalerOperation - title={ - <i18n.Translate> - There was an error trying to complete the transaction - </i18n.Translate> - } - error={transaction?.error} - /> + {transaction?.error ? ( + <ErrorAlertView + error={alertFromError( + i18n.str`There was an error trying to complete the transaction`, + transaction.error, + )} + /> + ) : undefined} {transaction.pending && ( <WarningBox> <i18n.Translate>This transaction is not completed</i18n.Translate> @@ -367,7 +366,7 @@ export function TransactionView({ </Fragment> )} <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <WithdrawDetails amount={{ @@ -420,7 +419,7 @@ export function TransactionView({ <br /> {transaction.refunds.length > 0 ? ( <Part - title={<i18n.Translate>Refunds</i18n.Translate>} + title={i18n.str`Refunds`} text={ <table> {transaction.refunds.map((r, i) => { @@ -462,7 +461,7 @@ export function TransactionView({ picked up. </i18n.Translate> <Part - title={<i18n.Translate>Offer</i18n.Translate>} + title={i18n.str`Offer`} text={<Amount value={pendingRefund} />} kind="positive" /> @@ -480,17 +479,17 @@ export function TransactionView({ </InfoBox> )} <Part - title={<i18n.Translate>Merchant</i18n.Translate>} + title={i18n.str`Merchant`} text={<MerchantDetails merchant={transaction.info.merchant} />} kind="neutral" /> <Part - title={<i18n.Translate>Invoice ID</i18n.Translate>} - text={transaction.info.orderId} + title={i18n.str`Invoice ID`} + text={transaction.info.orderId as TranslatedString} kind="neutral" /> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <PurchaseDetails price={price} @@ -520,12 +519,12 @@ export function TransactionView({ </Header> {payto && <PartPayto payto={payto} kind="neutral" />} <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={<DepositDetails transaction={transaction} />} kind="neutral" /> <Part - title={<i18n.Translate>Wire transfer deadline</i18n.Translate>} + title={i18n.str`Wire transfer deadline`} text={ <Time timestamp={AbsoluteTime.fromTimestamp( @@ -557,7 +556,7 @@ export function TransactionView({ {transaction.exchangeBaseUrl} </Header> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={<RefreshDetails transaction={transaction} />} /> </TransactionTemplate> @@ -578,12 +577,12 @@ export function TransactionView({ {transaction.merchantBaseUrl} </Header> {/* <Part - title={<i18n.Translate>Merchant</i18n.Translate>} + title={i18n.str`Merchant`} text={<MerchantDetails merchant={transaction.merchant} />} kind="neutral" /> */} <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={<TipDetails transaction={transaction} />} /> </TransactionTemplate> @@ -604,12 +603,12 @@ export function TransactionView({ </Header> <Part - title={<i18n.Translate>Merchant</i18n.Translate>} - text={transaction.info.merchant.name} + title={i18n.str`Merchant`} + text={transaction.info.merchant.name as TranslatedString} kind="neutral" /> <Part - title={<i18n.Translate>Original order ID</i18n.Translate>} + title={i18n.str`Original order ID`} text={ <a href={Pages.balanceTransaction({ @@ -622,12 +621,12 @@ export function TransactionView({ kind="neutral" /> <Part - title={<i18n.Translate>Purchase summary</i18n.Translate>} - text={transaction.info.summary} + title={i18n.str`Purchase summary`} + text={transaction.info.summary as TranslatedString} kind="neutral" /> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={<RefundDetails transaction={transaction} />} /> </TransactionTemplate> @@ -683,25 +682,25 @@ export function TransactionView({ {transaction.info.summary ? ( <Part - title={<i18n.Translate>Subject</i18n.Translate>} - text={transaction.info.summary} + title={i18n.str`Subject`} + text={transaction.info.summary as TranslatedString} kind="neutral" /> ) : undefined} <Part - title={<i18n.Translate>Exchange</i18n.Translate>} - text={transaction.exchangeBaseUrl} + title={i18n.str`Exchange`} + text={transaction.exchangeBaseUrl as TranslatedString} kind="neutral" /> {transaction.pending /** pending is not-pay */ && ( <Part - title={<i18n.Translate>URI</i18n.Translate>} + title={i18n.str`URI`} text={<ShowQrWithCopy text={transaction.talerUri} />} kind="neutral" /> )} <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <InvoiceDetails amount={{ @@ -730,18 +729,18 @@ export function TransactionView({ {transaction.info.summary ? ( <Part - title={<i18n.Translate>Subject</i18n.Translate>} - text={transaction.info.summary} + title={i18n.str`Subject`} + text={transaction.info.summary as TranslatedString} kind="neutral" /> ) : undefined} <Part - title={<i18n.Translate>Exchange</i18n.Translate>} - text={transaction.exchangeBaseUrl} + title={i18n.str`Exchange`} + text={transaction.exchangeBaseUrl as TranslatedString} kind="neutral" /> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <InvoiceDetails amount={{ @@ -769,25 +768,25 @@ export function TransactionView({ {transaction.info.summary ? ( <Part - title={<i18n.Translate>Subject</i18n.Translate>} - text={transaction.info.summary} + title={i18n.str`Subject`} + text={transaction.info.summary as TranslatedString} kind="neutral" /> ) : undefined} <Part - title={<i18n.Translate>Exchange</i18n.Translate>} - text={transaction.exchangeBaseUrl} + title={i18n.str`Exchange`} + text={transaction.exchangeBaseUrl as TranslatedString} kind="neutral" /> {/* {transaction.pending && ( //pending is not-received )} */} <Part - title={<i18n.Translate>URI</i18n.Translate>} + title={i18n.str`URI`} text={<ShowQrWithCopy text={transaction.talerUri} />} kind="neutral" /> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <TransferDetails amount={{ @@ -816,18 +815,18 @@ export function TransactionView({ {transaction.info.summary ? ( <Part - title={<i18n.Translate>Subject</i18n.Translate>} - text={transaction.info.summary} + title={i18n.str`Subject`} + text={transaction.info.summary as TranslatedString} kind="neutral" /> ) : undefined} <Part - title={<i18n.Translate>Exchange</i18n.Translate>} - text={transaction.exchangeBaseUrl} + title={i18n.str`Exchange`} + text={transaction.exchangeBaseUrl as TranslatedString} kind="neutral" /> <Part - title={<i18n.Translate>Details</i18n.Translate>} + title={i18n.str`Details`} text={ <TransferDetails amount={{ @@ -1245,7 +1244,7 @@ export function PurchaseDetails({ <td colSpan={2}> <PartCollapsible big - title={<i18n.Translate>Products</i18n.Translate>} + title={i18n.str`Products`} text={ <ListOfProducts> {info.products?.map((p, k) => ( @@ -1274,7 +1273,7 @@ export function PurchaseDetails({ <td colSpan={2}> <PartCollapsible big - title={<i18n.Translate>Delivery</i18n.Translate>} + title={i18n.str`Delivery`} text={ <DeliveryDetails date={info.delivery_date} @@ -1508,7 +1507,7 @@ function Header({ total: AmountJson; children: ComponentChildren; kind: Kind; - type: string; + type: TranslatedString; }): VNode { return ( <div diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx index 0b64417b8..b243eaa1c 100644 --- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx @@ -91,11 +91,7 @@ export function View({ <i18n.Translate>Permissions</i18n.Translate> </SubTitle> <Checkbox - label={ - <i18n.Translate> - Automatically open wallet based on page content - </i18n.Translate> - } + label={i18n.str`Automatically open wallet based on page content`} name="perm" description={ <i18n.Translate> diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 5c2a577b3..a41372e37 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -25,6 +25,8 @@ import { CoreApiResponse, Logger, NotificationType, + TalerErrorCode, + TalerErrorDetail, WalletDiagnostics, } from "@gnu-taler/taler-util"; import { @@ -33,11 +35,14 @@ import { WalletCoreOpKeys, WalletCoreRequestType, WalletCoreResponseType, + WalletOperations, } from "@gnu-taler/taler-wallet-core"; -import { MessageFromBackend, MessageFromFrontendBackground, MessageFromFrontendWallet } from "./platform/api.js"; import { - platform, -} from "./platform/foreground.js"; + MessageFromBackend, + MessageFromFrontendBackground, + MessageFromFrontendWallet, +} from "./platform/api.js"; +import { platform } from "./platform/foreground.js"; /** * @@ -88,6 +93,25 @@ export interface BackgroundApiClient { ): Promise<BackgroundOperations[Op]["response"]>; } +export class WalletError extends Error { + public errorDetail: TalerError; + + constructor(op: string, e: TalerError) { + super(`Wallet operation "${op}" failed: ${e.message}`); + this.errorDetail = e; + // Object.setPrototypeOf(this, WalletError.prototype); + } +} +export class BackgroundError extends Error { + public errorDetail: TalerErrorDetail; + + constructor(op: string, e: TalerErrorDetail) { + super(`Background operation "${op}" failed: ${e.message}`); + this.errorDetail = e; + // Object.setPrototypeOf(this, BackgroundError.prototype); + } +} + /** * BackgroundApiClient integration with browser platform */ @@ -106,13 +130,18 @@ class BackgroundApiClientImpl implements BackgroundApiClient { try { response = await platform.sendMessageToBackground(message); - } catch (e) { + } catch (error) { console.log("Error calling backend"); - throw new Error(`Error contacting backend: ${e}`); + if (error instanceof Error) { + throw new BackgroundError(operation, { + code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + }); + } + throw error; } logger.info("got response", response); if (response.type === "error") { - throw TalerError.fromUncheckedDetail(response.error); + throw new BackgroundError(operation, response.error); } return response.result as any; } @@ -140,7 +169,8 @@ class WalletApiClientImpl implements WalletCoreApiClient { } logger.info("got response", response); if (response.type === "error") { - throw TalerError.fromUncheckedDetail(response.error); + const error = TalerError.fromUncheckedDetail(response.error); + throw new WalletError(operation, error); } return response.result as any; } diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index b75e92004..c7b964c28 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -46,9 +46,7 @@ import { WalletStoresV1, } from "@gnu-taler/taler-wallet-core"; import { BrowserHttpLib } from "./browserHttpLib.js"; -import { - platform, -} from "./platform/background.js"; +import { platform } from "./platform/background.js"; import { MessageFromBackend, MessageFromFrontend, @@ -199,13 +197,22 @@ async function dispatch<Op extends WalletOperations | BackgroundOperations>( ), }; } - const result = await handler(req.payload); - return { - type: "response", - id: req.id, - operation: String(req.operation), - result, - }; + try { + const result = await handler(req.payload); + return { + type: "response", + id: req.id, + operation: String(req.operation), + result, + }; + } catch (er) { + return { + type: "error", + id: req.id, + error: getErrorDetailFromException(er), + operation: String(req.operation), + }; + } } if (req.channel === "wallet") { @@ -232,7 +239,9 @@ async function dispatch<Op extends WalletOperations | BackgroundOperations>( id: anyReq.id, operation: String(anyReq.operation), error: getErrorDetailFromException( - Error(`unknown channel ${anyReq.channel}`), + Error( + `unknown channel ${anyReq.channel}, should be "background" or "wallet"`, + ), ), }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6983ba177..0355f1535 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -649,7 +649,7 @@ importers: qrcode-generator: ^1.4.4 rimraf: ^3.0.2 tslib: ^2.4.0 - typescript: ^4.8.4 + typescript: 4.9.4 dependencies: '@gnu-taler/taler-util': link:../taler-util '@gnu-taler/taler-wallet-core': link:../taler-wallet-core @@ -687,7 +687,7 @@ importers: preact-cli: 3.4.1_i2jslynuqxjzp37vlc24guk7gu preact-render-to-string: 5.2.6_preact@10.11.3 rimraf: 3.0.2 - typescript: 4.8.4 + typescript: 4.9.4 packages/web-util: specifiers: @@ -8412,7 +8412,7 @@ packages: minipass-pipeline: 1.2.4 mkdirp: 1.0.4 p-map: 4.0.0 - promise-inflight: 1.0.1 + promise-inflight: 1.0.1_bluebird@3.7.2 rimraf: 3.0.2 ssri: 8.0.1 tar: 6.1.11 @@ -18096,15 +18096,6 @@ packages: engines: {node: '>=0.4.0'} dev: true - /promise-inflight/1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} - peerDependencies: - bluebird: '*' - peerDependenciesMeta: - bluebird: - optional: true - dev: true - /promise-inflight/1.0.1_bluebird@3.7.2: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} peerDependencies: @@ -20691,6 +20682,12 @@ packages: hasBin: true dev: true + /typescript/4.9.4: + resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + /uglify-js/3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} |