This commit is contained in:
Sebastian 2023-01-09 08:38:48 -03:00
parent 14f3d1e06d
commit 9b04d8bf35
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
104 changed files with 1076 additions and 1030 deletions

View File

@ -69,7 +69,7 @@
"preact-cli": "^3.3.5", "preact-cli": "^3.3.5",
"preact-render-to-string": "^5.1.19", "preact-render-to-string": "^5.1.19",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^4.8.4" "typescript": "4.9.4"
}, },
"nyc": { "nyc": {
"include": [ "include": [

View File

@ -49,7 +49,7 @@ function RenderAmount(): VNode {
<Fragment> <Fragment>
<AmountField <AmountField
required required
label={<i18n.Translate>Amount</i18n.Translate>} label={i18n.str`Amount`}
highestDenom={2000000} highestDenom={2000000}
lowestDenom={0.01} lowestDenom={0.01}
handler={handler} handler={handler}

View File

@ -21,6 +21,7 @@ import {
amountMaxValue, amountMaxValue,
Amounts, Amounts,
Result, Result,
TranslatedString,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
@ -37,7 +38,7 @@ export function AmountField({
highestDenom = 1, highestDenom = 1,
required, required,
}: { }: {
label: VNode; label: TranslatedString;
lowestDenom?: number; lowestDenom?: number;
highestDenom?: number; highestDenom?: number;
required?: boolean; required?: boolean;

View File

@ -19,6 +19,7 @@ import {
Amounts, Amounts,
PaytoUri, PaytoUri,
segwitMinAmount, segwitMinAmount,
TranslatedString,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks"; import { useEffect, useRef, useState } from "preact/hooks";
@ -106,27 +107,18 @@ export function BankDetailsByPaytoType({
} }
const accountPart = !payto.isKnown ? ( const accountPart = !payto.isKnown ? (
<Row <Row name={i18n.str`Account`} value={payto.targetPath} />
name={<i18n.Translate>Account</i18n.Translate>}
value={payto.targetPath}
/>
) : payto.targetType === "x-taler-bank" ? ( ) : payto.targetType === "x-taler-bank" ? (
<Fragment> <Fragment>
<Row <Row name={i18n.str`Bank host`} value={payto.host} />
name={<i18n.Translate>Bank host</i18n.Translate>} <Row name={i18n.str`Bank account`} value={payto.account} />
value={payto.host}
/>
<Row
name={<i18n.Translate>Bank account</i18n.Translate>}
value={payto.account}
/>
</Fragment> </Fragment>
) : payto.targetType === "iban" ? ( ) : payto.targetType === "iban" ? (
<Fragment> <Fragment>
{payto.bic !== undefined ? ( {payto.bic !== undefined ? (
<Row name={<i18n.Translate>BIC</i18n.Translate>} value={payto.bic} /> <Row name={i18n.str`BIC`} value={payto.bic} />
) : undefined} ) : undefined}
<Row name={<i18n.Translate>IBAN</i18n.Translate>} value={payto.iban} /> <Row name={i18n.str`IBAN`} value={payto.iban} />
</Fragment> </Fragment>
) : undefined; ) : undefined;
@ -146,19 +138,12 @@ export function BankDetailsByPaytoType({
<table> <table>
{accountPart} {accountPart}
<Row <Row
name={<i18n.Translate>Amount</i18n.Translate>} name={i18n.str`Amount`}
value={<Amount value={amount} hideCurrency />} value={<Amount value={amount} hideCurrency />}
/> />
<Row <Row name={i18n.str`Subject`} value={subject} literal />
name={<i18n.Translate>Subject</i18n.Translate>}
value={subject}
literal
/>
{receiver ? ( {receiver ? (
<Row <Row name={i18n.str`Receiver name`} value={receiver} />
name={<i18n.Translate>Receiver name</i18n.Translate>}
value={receiver}
/>
) : undefined} ) : undefined}
</table> </table>
</div> </div>
@ -200,7 +185,7 @@ function Row({
value, value,
literal, literal,
}: { }: {
name: VNode; name: TranslatedString;
value: string | VNode; value: string | VNode;
literal?: boolean; literal?: boolean;
}): VNode { }): VNode {

View File

@ -13,6 +13,7 @@
You should have received a copy of the GNU General Public License along with 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/> 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 { ComponentChildren, Fragment, h, JSX, VNode } from "preact";
import { Button } from "../mui/Button.js"; import { Button } from "../mui/Button.js";
import { Divider } from "../mui/Divider.js"; import { Divider } from "../mui/Divider.js";
@ -20,7 +21,7 @@ import { Grid } from "../mui/Grid.js";
import { Paper } from "../mui/Paper.js"; import { Paper } from "../mui/Paper.js";
interface Props extends JSX.HTMLAttributes<HTMLDivElement> { interface Props extends JSX.HTMLAttributes<HTMLDivElement> {
titleHead?: VNode; titleHead?: VNode | TranslatedString;
children: ComponentChildren; children: ComponentChildren;
// elements: { // elements: {
// icon?: VNode; // icon?: VNode;

View File

@ -14,14 +14,15 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { h, VNode } from "preact";
interface Props { interface Props {
enabled?: boolean; enabled?: boolean;
onToggle?: () => Promise<void>; onToggle?: () => Promise<void>;
label: VNode; label: TranslatedString;
name: string; name: string;
description?: VNode; description?: VNode | TranslatedString;
} }
export function Checkbox({ export function Checkbox({
name, name,

View File

@ -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>;
}

View File

@ -13,6 +13,7 @@
You should have received a copy of the GNU General Public License along with 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/> 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 { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import arrowDown from "../svg/chevron-down.svg"; import arrowDown from "../svg/chevron-down.svg";
@ -22,7 +23,7 @@ export function ErrorMessage({
title, title,
description, description,
}: { }: {
title: VNode; title: TranslatedString;
description?: string | VNode; description?: string | VNode;
}): VNode | null { }): VNode | null {
const [showErrorDetail, setShowErrorDetail] = useState(false); const [showErrorDetail, setShowErrorDetail] = useState(false);

View File

@ -13,7 +13,7 @@
You should have received a copy of the GNU General Public License along with 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/> 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 { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import arrowDown from "../svg/chevron-down.svg"; import arrowDown from "../svg/chevron-down.svg";
@ -24,7 +24,7 @@ export function ErrorTalerOperation({
title, title,
error, error,
}: { }: {
title?: VNode; title?: TranslatedString;
error?: TalerErrorDetail; error?: TalerErrorDetail;
}): VNode | null { }): VNode | null {
const { devMode } = useDevContext(); const { devMode } = useDevContext();

View File

@ -13,30 +13,88 @@
You should have received a copy of the GNU General Public License along with 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/> 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 { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import ProgressIcon from "../svg/progress.svg";
import { CenteredText } from "./styled/index.js"; import { CenteredText } from "./styled/index.js";
const fadeIn = css`
& {
animation: fadein 3s;
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
`;
export function Loading(): VNode { export function Loading(): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const [tooLong, setTooLong] = useState(false); return (
useEffect(() => { <section style={{ margin: "auto" }}>
const id = setTimeout(() => { <CenteredText class={fadeIn}>
setTooLong(true); <i18n.Translate>Loading</i18n.Translate>...
}, 500); </CenteredText>
return () => { {/* <div class={ripple} style={{ "--size": "250px" }}>
clearTimeout(id); <div></div>
}; <div></div>
}); </div> */}
if (tooLong) { <div class={fadeIn} dangerouslySetInnerHTML={{ __html: ProgressIcon }} />
return ( </section>
<section style={{ margin: "auto" }}> );
<CenteredText>
<i18n.Translate>Loading</i18n.Translate>...
</CenteredText>
</section>
);
}
return <Fragment />;
} }
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;
}
}
`;

View File

@ -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} />;
}

View File

@ -13,7 +13,7 @@
You should have received a copy of the GNU General Public License along with 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/> 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 { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Button } from "../mui/Button.js"; import { Button } from "../mui/Button.js";
@ -21,7 +21,7 @@ import arrowDown from "../svg/chevron-down.svg";
import { ParagraphClickable } from "./styled/index.js"; import { ParagraphClickable } from "./styled/index.js";
export interface Props { export interface Props {
label: (s: string) => VNode; label: (s: string) => TranslatedString;
actions: string[]; actions: string[];
onClick: (s: string) => Promise<void>; onClick: (s: string) => Promise<void>;
} }

View File

@ -14,7 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
@ -27,8 +31,8 @@ import {
export type Kind = "positive" | "negative" | "neutral"; export type Kind = "positive" | "negative" | "neutral";
interface Props { interface Props {
title: VNode | string; title: VNode | TranslatedString;
text: VNode | string; text: VNode | TranslatedString;
kind?: Kind; kind?: Kind;
big?: boolean; big?: boolean;
showSign?: boolean; showSign?: boolean;

View File

@ -19,6 +19,7 @@ import {
Amounts, Amounts,
PreparePayResult, PreparePayResult,
PreparePayResultType, PreparePayResultType,
TranslatedString,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
@ -109,8 +110,10 @@ export function PaymentButtons({
<section> <section>
{payStatus.paid && payStatus.contractTerms.fulfillment_message && ( {payStatus.paid && payStatus.contractTerms.fulfillment_message && (
<Part <Part
title={<i18n.Translate>Merchant message</i18n.Translate>} title={i18n.str`Merchant message`}
text={payStatus.contractTerms.fulfillment_message} text={
payStatus.contractTerms.fulfillment_message as TranslatedString
}
kind="neutral" kind="neutral"
/> />
)} )}
@ -131,11 +134,7 @@ function PayWithMobile({ uri }: { uri: string }): VNode {
return ( return (
<section> <section>
<LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}> <LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}>
{!showQR ? ( {!showQR ? i18n.str`Pay with a mobile phone` : i18n.str`Hide QR`}
<i18n.Translate>Pay with a mobile phone</i18n.Translate>
) : (
<i18n.Translate>Hide QR</i18n.Translate>
)}
</LinkSuccess> </LinkSuccess>
{showQR && ( {showQR && (
<div> <div>

View File

@ -87,7 +87,7 @@ export function PendingTransactionsView({
}} }}
> >
<Banner <Banner
titleHead={<i18n.Translate>PENDING OPERATIONS</i18n.Translate>} titleHead={i18n.str`PENDING OPERATIONS`}
style={{ style={{
backgroundColor: "lightcyan", backgroundColor: "lightcyan",
maxHeight: 150, maxHeight: 150,

View File

@ -14,6 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { Fragment, h, VNode } from "preact";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { NiceSelect } from "./styled/index.js"; import { NiceSelect } from "./styled/index.js";
@ -21,7 +22,7 @@ import { NiceSelect } from "./styled/index.js";
interface Props { interface Props {
value?: string; value?: string;
onChange?: (s: string) => void; onChange?: (s: string) => void;
label: VNode; label: VNode | TranslatedString;
list: { list: {
[label: string]: string; [label: string]: string;
}; };

View File

@ -94,7 +94,10 @@ export const Error = createExample(ErrorView, {
error: { error: {
hasError: true, hasError: true,
message: "message", message: "message",
operational: false, // details: {
// co
// },
type: "error",
// details: { // details: {
// code: 123, // code: 123,
// }, // },

View File

@ -22,15 +22,16 @@ import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
import { Modal } from "../components/Modal.js"; import { Modal } from "../components/Modal.js";
import { Time } from "../components/Time.js"; import { Time } from "../components/Time.js";
import { alertFromError } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../mui/handlers.js"; import { ButtonHandler } from "../mui/handlers.js";
import { compose, StateViewMap } from "../utils/index.js"; import { compose, StateViewMap } from "../utils/index.js";
import { Amount } from "./Amount.js"; import { Amount } from "./Amount.js";
import { AlertView } from "./CurrentAlerts.js";
import { Link } from "./styled/index.js"; import { Link } from "./styled/index.js";
const ContractTermsTable = styled.table` const ContractTermsTable = styled.table`
@ -160,13 +161,12 @@ export function ErrorView({
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
return ( return (
<Modal title="Full detail" onClose={hideHandler}> <Modal title="Full detail" onClose={hideHandler}>
<LoadingError <AlertView
title={ alert={alertFromError(
<i18n.Translate> i18n.str`Could not load purchase proposal details`,
Could not load purchase proposal details error,
</i18n.Translate> { proposalId },
} )}
error={error}
/> />
</Modal> </Modal>
); );

View File

@ -15,14 +15,13 @@
*/ */
import { Loading } from "../../components/Loading.js"; 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 { ToggleHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { ErrorAlertView } from "../CurrentAlerts.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { TermsState } from "./utils.js"; import { TermsState } from "./utils.js";
import { import {
ErrorAcceptingView,
LoadingUriView,
ShowButtonsAcceptedTosView, ShowButtonsAcceptedTosView,
ShowButtonsNonAcceptedTosView, ShowButtonsNonAcceptedTosView,
ShowTosContentView, ShowTosContentView,
@ -35,8 +34,7 @@ export interface Props {
export type State = export type State =
| State.Loading | State.Loading
| State.LoadingUriError | State.Error
| State.ErrorAccepting
| State.ShowButtonsAccepted | State.ShowButtonsAccepted
| State.ShowButtonsNotAccepted | State.ShowButtonsNotAccepted
| State.ShowContent; | State.ShowContent;
@ -47,14 +45,9 @@ export namespace State {
error: undefined; error: undefined;
} }
export interface LoadingUriError { export interface Error {
status: "loading-error"; status: "error";
error: HookError; error: ErrorAlert;
}
export interface ErrorAccepting {
status: "error-accepting";
error: HookError;
} }
export interface BaseInfo { export interface BaseInfo {
@ -79,11 +72,10 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-error": LoadingUriView, error: ErrorAlertView,
"show-content": ShowTosContentView, "show-content": ShowTosContentView,
"show-buttons-accepted": ShowButtonsAcceptedTosView, "show-buttons-accepted": ShowButtonsAcceptedTosView,
"show-buttons-not-accepted": ShowButtonsNonAcceptedTosView, "show-buttons-not-accepted": ShowButtonsNonAcceptedTosView,
"error-accepting": ErrorAcceptingView,
}; };
export const TermsOfService = compose( export const TermsOfService = compose(

View File

@ -16,7 +16,9 @@
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
import { buildTermsOfServiceState } from "./utils.js"; import { buildTermsOfServiceState } from "./utils.js";
@ -25,9 +27,8 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const readOnly = !onChange; const readOnly = !onChange;
const [showContent, setShowContent] = useState<boolean>(readOnly); const [showContent, setShowContent] = useState<boolean>(readOnly);
const [errorAccepting, setErrorAccepting] = useState<Error | undefined>( const { i18n } = useTranslationContext();
undefined, const { pushAlert } = useAlertContext();
);
/** /**
* For the exchange selected, bring the status of the terms of service * 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) { if (terms.hasError) {
return { return {
status: "loading-error", status: "error",
error: terms, 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; const { state } = terms.response;
async function onUpdate(accepted: boolean): Promise<void> { async function onUpdate(accepted: boolean): Promise<void> {
@ -77,13 +69,13 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
try { try {
if (accepted) { if (accepted) {
api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { await api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
exchangeBaseUrl: exchangeUrl, exchangeBaseUrl: exchangeUrl,
etag: state.version, etag: state.version,
}); });
} else { } else {
// mark as not accepted // mark as not accepted
api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { await api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
exchangeBaseUrl: exchangeUrl, exchangeBaseUrl: exchangeUrl,
etag: undefined, etag: undefined,
}); });
@ -91,11 +83,7 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
// setAccepted(accepted); // setAccepted(accepted);
if (!readOnly) onChange(accepted); //external update if (!readOnly) onChange(accepted); //external update
} catch (e) { } catch (e) {
if (e instanceof Error) { pushAlert(alertFromError(i18n.str`Could not accept terms of service`, e));
//FIXME: uncomment this and display error
// setErrorAccepting(e.message);
setErrorAccepting(e);
}
} }
} }

View File

@ -14,49 +14,23 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { 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 { CheckboxOutlined } from "../../components/CheckboxOutlined.js";
import { ExchangeXmlTos } from "../../components/ExchangeToS.js";
import { import {
LinkSuccess, LinkSuccess,
TermsOfService, TermsOfService,
WarningBox, WarningBox,
WarningText, WarningText,
} from "../../components/styled/index.js"; } from "../../components/styled/index.js";
import { ExchangeXmlTos } from "../../components/ExchangeToS.js"; import { useTranslationContext } from "../../context/translation.js";
import { ToggleHandler } from "../../mui/handlers.js";
import { Button } from "../../mui/Button.js"; import { Button } from "../../mui/Button.js";
import { ExchangeTosStatus } from "@gnu-taler/taler-util"; 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 ErrorAcceptingView({ error }: State.ErrorAccepting): VNode {
const { i18n } = useTranslationContext();
return (
<LoadingError
title={<i18n.Translate>Could not load</i18n.Translate>}
error={error}
/>
);
}
export function ShowButtonsAcceptedTosView({ export function ShowButtonsAcceptedTosView({
termsAccepted, termsAccepted,
showingTermsOfService, showingTermsOfService,
terms,
}: State.ShowButtonsAccepted): VNode { }: State.ShowButtonsAccepted): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const ableToReviewTermsOfService = const ableToReviewTermsOfService =

View File

@ -535,7 +535,7 @@ export const LinkDestructive = styled(Link)`
`; `;
export const LinkPrimary = styled(Link)` export const LinkPrimary = styled(Link)`
color: rgb(66, 184, 221); color: black;
`; `;
export const ButtonPrimary = styled(ButtonVariant)<{ small?: boolean }>` export const ButtonPrimary = styled(ButtonVariant)<{ small?: boolean }>`

View File

@ -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,
};
}

View File

@ -15,12 +15,13 @@
*/ */
import { AmountJson, AmountString } from "@gnu-taler/taler-util"; import { AmountJson, AmountString } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.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 { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export interface Props { export interface Props {
talerDepositUri: string | undefined; talerDepositUri: string | undefined;
@ -37,8 +38,8 @@ export namespace State {
error: undefined; error: undefined;
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-uri"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface Ready { export interface Ready {
status: "ready"; status: "ready";
@ -57,7 +58,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-uri": LoadingUriView, error: ErrorAlertView,
ready: ReadyView, ready: ReadyView,
}; };

View File

@ -16,7 +16,9 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -38,12 +40,16 @@ export function useComponentState({
}); });
return { deposit, uri: talerDepositUri, amount }; return { deposit, uri: talerDepositUri, amount };
}); });
const { i18n } = useTranslationContext();
if (!info) return { status: "loading", error: undefined }; if (!info) return { status: "loading", error: undefined };
if (info.hasError) { if (info.hasError) {
return { return {
status: "loading-uri", status: "error",
error: info, error: alertFromError(
i18n.str`Could not load the status of the term of service`,
info,
),
}; };
} }

View File

@ -50,12 +50,12 @@ describe("Deposit CTA states", () => {
expect(status).equals("loading"); expect(status).equals("loading");
}, },
({ status, error }) => { ({ status, error }) => {
expect(status).equals("loading-uri"); expect(status).equals("error");
if (!error) expect.fail(); if (!error) expect.fail();
if (!error.hasError) expect.fail(); // if (!error.hasError) expect.fail();
if (error.operational) expect.fail(); // if (error.operational) expect.fail();
expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); expect(error.cause?.message).eq("ERROR_NO-URI-FOR-DEPOSIT");
}, },
], ],
TestingContext, TestingContext,

View File

@ -17,7 +17,6 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { LoadingError } from "../../components/LoadingError.js";
import { LogoHeader } from "../../components/LogoHeader.js"; import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { SubTitle, WalletAction } from "../../components/styled/index.js"; import { SubTitle, WalletAction } from "../../components/styled/index.js";
@ -30,17 +29,6 @@ import { State } from "./index.js";
* @author sebasjm * @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 { export function ReadyView(state: State.Ready): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -55,7 +43,7 @@ export function ReadyView(state: State.Ready): VNode {
{Amounts.isNonZero(state.cost) && ( {Amounts.isNonZero(state.cost) && (
<Part <Part
big big
title={<i18n.Translate>Cost</i18n.Translate>} title={i18n.str`Cost`}
text={<Amount value={state.cost} />} text={<Amount value={state.cost} />}
kind="negative" kind="negative"
/> />
@ -63,14 +51,14 @@ export function ReadyView(state: State.Ready): VNode {
{Amounts.isNonZero(state.fee) && ( {Amounts.isNonZero(state.fee) && (
<Part <Part
big big
title={<i18n.Translate>Fee</i18n.Translate>} title={i18n.str`Fee`}
text={<Amount value={state.fee} />} text={<Amount value={state.fee} />}
kind="negative" kind="negative"
/> />
)} )}
<Part <Part
big big
title={<i18n.Translate>To be received</i18n.Translate>} title={i18n.str`To be received`}
text={<Amount value={state.effective} />} text={<Amount value={state.effective} />}
kind="positive" kind="positive"
/> />

View File

@ -14,16 +14,17 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { 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 { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js";
import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export interface Props { export interface Props {
amount: string; amount: string;
@ -45,8 +46,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-uri"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -63,13 +64,12 @@ export namespace State {
requestAmount: AmountJson; requestAmount: AmountJson;
exchangeUrl: string; exchangeUrl: string;
error: undefined; error: undefined;
operationError?: TalerErrorDetail;
} }
} }
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-uri": LoadingUriView, error: ErrorAlertView,
"no-exchange": NoExchangesView, "no-exchange": NoExchangesView,
"selecting-exchange": ExchangeSelectionPage, "selecting-exchange": ExchangeSelectionPage,
ready: ReadyView, ready: ReadyView,

View File

@ -23,7 +23,9 @@ import {
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { isFuture, parse } from "date-fns"; import { isFuture, parse } from "date-fns";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
import { RecursiveState } from "../../utils/index.js"; import { RecursiveState } from "../../utils/index.js";
@ -40,6 +42,7 @@ export function useComponentState({
const hook = useAsyncAsHook(() => const hook = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.ListExchanges, {}), api.wallet.call(WalletApiOperation.ListExchanges, {}),
); );
const { i18n } = useTranslationContext();
if (!hook) { if (!hook) {
return { return {
@ -49,10 +52,19 @@ export function useComponentState({
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-uri", status: "error",
error: hook, 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; const exchangeList = hook.response.exchanges;
@ -60,10 +72,6 @@ export function useComponentState({
const [subject, setSubject] = useState<string | undefined>(); const [subject, setSubject] = useState<string | undefined>();
const [timestamp, setTimestamp] = useState<string | undefined>(); const [timestamp, setTimestamp] = useState<string | undefined>();
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>(undefined);
const selectedExchange = useSelectedExchange({ const selectedExchange = useSelectedExchange({
currency: amount.currency, currency: amount.currency,
defaultExchange: undefined, defaultExchange: undefined,
@ -93,11 +101,19 @@ export function useComponentState({
error: undefined, error: undefined,
}; };
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-uri", status: "error",
error: hook, 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; const { amountEffective, amountRaw } = hook.response;
@ -160,8 +176,8 @@ export function useComponentState({
subject === undefined subject === undefined
? undefined ? undefined
: !subject : !subject
? "Can't be empty" ? "Can't be empty"
: undefined, : undefined,
value: subject ?? "", value: subject ?? "",
onInput: async (e) => setSubject(e), onInput: async (e) => setSubject(e),
}, },
@ -183,7 +199,6 @@ export function useComponentState({
requestAmount, requestAmount,
toBeReceived, toBeReceived,
error: undefined, error: undefined,
operationError,
}; };
}; };
} }

View File

@ -17,41 +17,24 @@
import { format } from "date-fns"; import { format } from "date-fns";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LoadingError } from "../../components/LoadingError.js";
import { LogoHeader } from "../../components/LogoHeader.js"; import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { QR } from "../../components/QR.js";
import { import {
Link,
SubTitle, SubTitle,
SvgIcon, SvgIcon,
WalletAction, WalletAction,
} from "../../components/styled/index.js"; } from "../../components/styled/index.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.js"; import { Button } from "../../mui/Button.js";
import { Grid } from "../../mui/Grid.js";
import { TextField } from "../../mui/TextField.js"; import { TextField } from "../../mui/TextField.js";
import editIcon from "../../svg/edit_24px.svg"; import editIcon from "../../svg/edit_24px.svg";
import { ExchangeDetails, InvoiceDetails } from "../../wallet/Transaction.js"; import { ExchangeDetails, InvoiceDetails } from "../../wallet/Transaction.js";
import { State } from "./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</i18n.Translate>}
error={error}
/>
);
}
export function ReadyView({ export function ReadyView({
exchangeUrl, exchangeUrl,
subject, subject,
expiration, expiration,
cancel,
operationError,
create, create,
toBeReceived, toBeReceived,
requestAmount, requestAmount,
@ -59,7 +42,7 @@ export function ReadyView({
}: State.Ready): VNode { }: State.Ready): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
async function oneDayExpiration() { async function oneDayExpiration(): Promise<void> {
if (expiration.onInput) { if (expiration.onInput) {
expiration.onInput( expiration.onInput(
format(new Date().getTime() + 1000 * 60 * 60 * 24, "dd/MM/yyyy"), 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) { if (expiration.onInput) {
expiration.onInput( expiration.onInput(
format(new Date().getTime() + 1000 * 60 * 60 * 24 * 7, "dd/MM/yyyy"), format(new Date().getTime() + 1000 * 60 * 60 * 24 * 7, "dd/MM/yyyy"),
); );
} }
} }
async function _20DaysExpiration() { async function _20DaysExpiration(): Promise<void> {
if (expiration.onInput) { if (expiration.onInput) {
expiration.onInput( expiration.onInput(
format(new Date().getTime() + 1000 * 60 * 60 * 24 * 20, "dd/MM/yyyy"), format(new Date().getTime() + 1000 * 60 * 60 * 24 * 20, "dd/MM/yyyy"),
@ -87,16 +70,6 @@ export function ReadyView({
<SubTitle> <SubTitle>
<i18n.Translate>Digital invoice</i18n.Translate> <i18n.Translate>Digital invoice</i18n.Translate>
</SubTitle> </SubTitle>
{operationError && (
<ErrorTalerOperation
title={
<i18n.Translate>
Could not finish the invoice creation
</i18n.Translate>
}
error={operationError}
/>
)}
<section style={{ textAlign: "left" }}> <section style={{ textAlign: "left" }}>
<Part <Part
title={ title={
@ -125,9 +98,7 @@ export function ReadyView({
label="Subject" label="Subject"
variant="filled" variant="filled"
error={subject.error} error={subject.error}
helperText={ helperText={i18n.str`Short description of the invoice`}
<i18n.Translate>Short description of the invoice</i18n.Translate>
}
required required
fullWidth fullWidth
value={subject.value} value={subject.value}
@ -171,7 +142,7 @@ export function ReadyView({
</p> </p>
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<InvoiceDetails <InvoiceDetails
amount={{ amount={{
@ -187,11 +158,6 @@ export function ReadyView({
<i18n.Translate>Create</i18n.Translate> <i18n.Translate>Create</i18n.Translate>
</Button> </Button>
</section> </section>
<section>
<Link upperCased onClick={cancel.onClick}>
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
</WalletAction> </WalletAction>
); );
} }

View File

@ -20,12 +20,13 @@ import {
PreparePayResult, PreparePayResult,
TalerErrorDetail, TalerErrorDetail,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.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 { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export interface Props { export interface Props {
talerPayPullUri: string; talerPayPullUri: string;
@ -48,8 +49,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-uri"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -83,7 +84,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-uri": LoadingUriView, error: ErrorAlertView,
"no-balance-for-currency": ReadyView, "no-balance-for-currency": ReadyView,
"no-enough-balance": ReadyView, "no-enough-balance": ReadyView,
ready: ReadyView, ready: ReadyView,

View File

@ -25,7 +25,9 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -36,6 +38,7 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, { const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, {
talerUri: talerPayPullUri, talerUri: talerPayPullUri,
@ -63,10 +66,19 @@ export function useComponentState({
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-uri", status: "error",
error: hook, 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; const { contractTerms, peerPullPaymentIncomingId } = hook.response.p2p;

View File

@ -17,26 +17,14 @@
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LoadingError } from "../../components/LoadingError.js";
import { LogoHeader } from "../../components/LogoHeader.js"; import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { PaymentButtons } from "../../components/PaymentButtons.js";
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
import { Time } from "../../components/Time.js"; import { Time } from "../../components/Time.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { PaymentButtons } from "../../components/PaymentButtons";
import { State } from "./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</i18n.Translate>}
error={error}
/>
);
}
export function ReadyView( export function ReadyView(
state: State.Ready | State.NoBalanceForCurrency | State.NoEnoughBalance, state: State.Ready | State.NoBalanceForCurrency | State.NoEnoughBalance,
): VNode { ): VNode {
@ -60,25 +48,15 @@ export function ReadyView(
</SubTitle> </SubTitle>
{operationError && ( {operationError && (
<ErrorTalerOperation <ErrorTalerOperation
title={ title={i18n.str`Could not finish the payment operation`}
<i18n.Translate>
Could not finish the payment operation
</i18n.Translate>
}
error={operationError} error={operationError}
/> />
)} )}
<section style={{ textAlign: "left" }}> <section style={{ textAlign: "left" }}>
<Part title={i18n.str`Subject`} text={<div>{summary}</div>} />
<Part title={i18n.str`Amount`} text={<Amount value={amount} />} />
<Part <Part
title={<i18n.Translate>Subject</i18n.Translate>} title={i18n.str`Valid until`}
text={<div>{summary}</div>}
/>
<Part
title={<i18n.Translate>Amount</i18n.Translate>}
text={<Amount value={amount} />}
/>
<Part
title={<i18n.Translate>Valid until</i18n.Translate>}
text={<Time timestamp={expiration} format="dd MMMM yyyy, HH:mm" />} text={<Time timestamp={expiration} format="dd MMMM yyyy, HH:mm" />}
kind="neutral" kind="neutral"
/> />
@ -91,11 +69,6 @@ export function ReadyView(
payHandler={status === "ready" ? state.accept : undefined} payHandler={status === "ready" ? state.accept : undefined}
goToWalletManualWithdraw={state.goToWalletManualWithdraw} goToWalletManualWithdraw={state.goToWalletManualWithdraw}
/> />
<section>
<Link upperCased onClick={cancel.onClick}>
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
</WalletAction> </WalletAction>
); );
} }

View File

@ -21,12 +21,13 @@ import {
PreparePayResultInsufficientBalance, PreparePayResultInsufficientBalance,
PreparePayResultPaymentPossible, PreparePayResultPaymentPossible,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.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 { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { BaseView, LoadingUriView } from "./views.js"; import { BaseView } from "./views.js";
export interface Props { export interface Props {
talerPayUri?: string; talerPayUri?: string;
@ -49,8 +50,8 @@ export namespace State {
error: undefined; error: undefined;
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-uri"; status: "error";
error: HookError; error: ErrorAlert;
} }
interface BaseInfo { interface BaseInfo {
@ -86,7 +87,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-uri": LoadingUriView, error: ErrorAlertView,
"no-balance-for-currency": BaseView, "no-balance-for-currency": BaseView,
"no-enough-balance": BaseView, "no-enough-balance": BaseView,
confirmed: BaseView, confirmed: BaseView,

View File

@ -23,7 +23,9 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -36,6 +38,7 @@ export function useComponentState({
}: Props): State { }: Props): State {
const [payErrMsg, setPayErrMsg] = useState<TalerError | undefined>(undefined); const [payErrMsg, setPayErrMsg] = useState<TalerError | undefined>(undefined);
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT"); 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) return { status: "loading", error: undefined };
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-uri", status: "error",
error: hook, 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 { payStatus } = hook.response;
const amount = Amounts.parseOrThrow(payStatus.amountRaw); const amount = Amounts.parseOrThrow(payStatus.amountRaw);

View File

@ -54,10 +54,10 @@ describe("Payment CTA states", () => {
expect(error).undefined; expect(error).undefined;
}, },
({ status, error }) => { ({ status, error }) => {
expect(status).equals("loading-uri"); expect(status).equals("error");
if (error === undefined) expect.fail(); if (error === undefined) expect.fail();
expect(error.hasError).true; // expect(error.hasError).true;
expect(error.operational).false; // expect(error.operational).false;
}, },
], ],
TestingContext, TestingContext,

View File

@ -19,28 +19,17 @@ import {
Amounts, Amounts,
MerchantContractTerms as ContractTerms, MerchantContractTerms as ContractTerms,
PreparePayResultType, PreparePayResultType,
TranslatedString,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { LoadingError } from "../../components/LoadingError.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { PaymentButtons } from "../../components/PaymentButtons.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 { Time } from "../../components/Time.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { MerchantDetails, PurchaseDetails } from "../../wallet/Transaction.js"; import { MerchantDetails, PurchaseDetails } from "../../wallet/Transaction.js";
import { State } from "./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 pay status</i18n.Translate>}
error={error}
/>
);
}
type SupportedStates = type SupportedStates =
| State.Ready | State.Ready
| State.Confirmed | State.Confirmed
@ -66,17 +55,17 @@ export function BaseView(state: SupportedStates): VNode {
<section style={{ textAlign: "left" }}> <section style={{ textAlign: "left" }}>
<Part <Part
title={<i18n.Translate>Purchase</i18n.Translate>} title={i18n.str`Purchase`}
text={contractTerms.summary} text={contractTerms.summary as TranslatedString}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Merchant</i18n.Translate>} title={i18n.str`Merchant`}
text={<MerchantDetails merchant={contractTerms.merchant} />} text={<MerchantDetails merchant={contractTerms.merchant} />}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<PurchaseDetails <PurchaseDetails
price={price} price={price}
@ -93,14 +82,14 @@ export function BaseView(state: SupportedStates): VNode {
/> />
{contractTerms.order_id && ( {contractTerms.order_id && (
<Part <Part
title={<i18n.Translate>Receipt</i18n.Translate>} title={i18n.str`Receipt`}
text={`#${contractTerms.order_id}`} text={`#${contractTerms.order_id}` as TranslatedString}
kind="neutral" kind="neutral"
/> />
)} )}
{contractTerms.pay_deadline && ( {contractTerms.pay_deadline && (
<Part <Part
title={<i18n.Translate>Valid until</i18n.Translate>} title={i18n.str`Valid until`}
text={ text={
<Time <Time
timestamp={AbsoluteTime.fromTimestamp( timestamp={AbsoluteTime.fromTimestamp(
@ -121,11 +110,6 @@ export function BaseView(state: SupportedStates): VNode {
payHandler={state.status === "ready" ? state.payHandler : undefined} payHandler={state.status === "ready" ? state.payHandler : undefined}
goToWalletManualWithdraw={state.goToWalletManualWithdraw} goToWalletManualWithdraw={state.goToWalletManualWithdraw}
/> />
<section>
<Link upperCased onClick={state.cancel}>
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
</Fragment> </Fragment>
); );
} }

View File

@ -14,12 +14,13 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ErrorAlert } from "../../context/alert.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export interface Props { export interface Props {
talerRecoveryUri?: string; talerRecoveryUri?: string;
@ -36,8 +37,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-uri"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -53,7 +54,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-uri": LoadingUriView, error: ErrorAlertView,
ready: ReadyView, ready: ReadyView,
}; };

View File

@ -16,7 +16,9 @@
import { parseRecoveryUri } from "@gnu-taler/taler-util"; import { parseRecoveryUri } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Alert } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState({ export function useComponentState({
@ -25,13 +27,16 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
if (!talerRecoveryUri) { if (!talerRecoveryUri) {
return { return {
status: "loading-uri", status: "error",
error: { error: {
operational: false, type: "error",
hasError: true, message: i18n.str`Missing URI`,
message: "Missing URI", description: i18n.str``,
cause: new Error("something"),
context: {},
}, },
}; };
} }
@ -39,11 +44,13 @@ export function useComponentState({
if (!info) { if (!info) {
return { return {
status: "loading-uri", status: "error",
error: { error: {
operational: false, type: "error",
hasError: true, message: i18n.str`Could not parse the recovery URI`,
message: "Could not be read", description: i18n.str``,
cause: new Error("something"),
context: {},
}, },
}; };
} }

View File

@ -15,28 +15,12 @@
*/ */
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { LoadingError } from "../../components/LoadingError.js";
import { LogoHeader } from "../../components/LogoHeader.js"; import { LogoHeader } from "../../components/LogoHeader.js";
import { SubTitle, WalletAction } from "../../components/styled/index.js"; import { SubTitle, WalletAction } from "../../components/styled/index.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.js"; import { Button } from "../../mui/Button.js";
import { State } from "./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 backup recovery information
</i18n.Translate>
}
error={error}
/>
);
}
export function ReadyView({ accept, cancel }: State.Ready): VNode { export function ReadyView({ accept, cancel }: State.Ready): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
return ( return (

View File

@ -15,17 +15,13 @@
*/ */
import { AmountJson, Product } from "@gnu-taler/taler-util"; import { AmountJson, Product } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.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 { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { import { IgnoredView, InProgressView, ReadyView } from "./views.js";
IgnoredView,
InProgressView,
LoadingUriView,
ReadyView,
} from "./views.js";
export interface Props { export interface Props {
talerRefundUri?: string; talerRefundUri?: string;
@ -47,8 +43,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-uri"; status: "error";
error: HookError; error: ErrorAlert;
} }
interface BaseInfo { interface BaseInfo {
@ -81,7 +77,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-uri": LoadingUriView, error: ErrorAlertView,
"in-progress": InProgressView, "in-progress": InProgressView,
ignored: IgnoredView, ignored: IgnoredView,
ready: ReadyView, ready: ReadyView,

View File

@ -17,7 +17,9 @@
import { Amounts, NotificationType } from "@gnu-taler/taler-util"; import { Amounts, NotificationType } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -27,6 +29,7 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const [ignored, setIgnored] = useState(false); const [ignored, setIgnored] = useState(false);
const info = useAsyncAsHook(async () => { const info = useAsyncAsHook(async () => {
@ -49,10 +52,19 @@ export function useComponentState({
} }
if (info.hasError) { if (info.hasError) {
return { return {
status: "loading-uri", status: "error",
error: info, 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; const { refund, uri } = info.response;

View File

@ -53,11 +53,11 @@ describe("Refund CTA states", () => {
expect(error).undefined; expect(error).undefined;
}, },
({ status, error }) => { ({ status, error }) => {
expect(status).equals("loading-uri"); expect(status).equals("error");
if (!error) expect.fail(); if (!error) expect.fail();
if (!error.hasError) expect.fail(); // if (!error.hasError) expect.fail();
if (error.operational) expect.fail(); // if (error.operational) expect.fail();
expect(error.message).eq("ERROR_NO-URI-FOR-REFUND"); expect(error.cause?.message).eq("ERROR_NO-URI-FOR-REFUND");
}, },
], ],
TestingContext, TestingContext,

View File

@ -17,26 +17,14 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { LoadingError } from "../../components/LoadingError.js";
import { LogoHeader } from "../../components/LogoHeader.js"; import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { ProductList } from "../../components/ProductList.js";
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.js"; import { Button } from "../../mui/Button.js";
import { ProductList } from "../../components/ProductList.js";
import { State } from "./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 refund status</i18n.Translate>}
error={error}
/>
);
}
export function IgnoredView(state: State.Ignored): VNode { export function IgnoredView(state: State.Ignored): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -73,13 +61,13 @@ export function InProgressView(state: State.InProgress): VNode {
<section> <section>
<Part <Part
big big
title={<i18n.Translate>Total to refund</i18n.Translate>} title={i18n.str`Total to refund`}
text={<Amount value={state.awaitingAmount} />} text={<Amount value={state.awaitingAmount} />}
kind="negative" kind="negative"
/> />
<Part <Part
big big
title={<i18n.Translate>Refunded</i18n.Translate>} title={i18n.str`Refunded`}
text={<Amount value={state.amount} />} text={<Amount value={state.amount} />}
kind="negative" kind="negative"
/> />
@ -112,21 +100,21 @@ export function ReadyView(state: State.Ready): VNode {
<section> <section>
<Part <Part
big big
title={<i18n.Translate>Order amount</i18n.Translate>} title={i18n.str`Order amount`}
text={<Amount value={state.amount} />} text={<Amount value={state.amount} />}
kind="neutral" kind="neutral"
/> />
{Amounts.isNonZero(state.granted) && ( {Amounts.isNonZero(state.granted) && (
<Part <Part
big big
title={<i18n.Translate>Already refunded</i18n.Translate>} title={i18n.str`Already refunded`}
text={<Amount value={state.granted} />} text={<Amount value={state.granted} />}
kind="neutral" kind="neutral"
/> />
)} )}
<Part <Part
big big
title={<i18n.Translate>Refund offered</i18n.Translate>} title={i18n.str`Refund offered`}
text={<Amount value={state.awaitingAmount} />} text={<Amount value={state.awaitingAmount} />}
kind="positive" kind="positive"
/> />
@ -147,11 +135,6 @@ export function ReadyView(state: State.Ready): VNode {
</i18n.Translate> </i18n.Translate>
</Button> </Button>
</section> </section>
<section>
<Link upperCased onClick={state.cancel}>
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
</WalletAction> </WalletAction>
); );
} }

View File

@ -15,17 +15,13 @@
*/ */
import { AmountJson } 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 { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ErrorAlert } from "../../context/alert.js";
import { ButtonHandler } from "../../mui/handlers.js"; import { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { import { AcceptedView, IgnoredView, ReadyView } from "./views.js";
AcceptedView,
IgnoredView,
LoadingUriView,
ReadyView,
} from "./views.js";
export interface Props { export interface Props {
talerTipUri?: string; talerTipUri?: string;
@ -48,8 +44,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-uri"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -75,7 +71,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-uri": LoadingUriView, error: ErrorAlertView,
accepted: AcceptedView, accepted: AcceptedView,
ignored: IgnoredView, ignored: IgnoredView,
ready: ReadyView, ready: ReadyView,

View File

@ -16,7 +16,9 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -26,6 +28,7 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const tipInfo = useAsyncAsHook(async () => { const tipInfo = useAsyncAsHook(async () => {
if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP"); if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP");
const tip = await api.wallet.call(WalletApiOperation.PrepareTip, { const tip = await api.wallet.call(WalletApiOperation.PrepareTip, {
@ -42,10 +45,19 @@ export function useComponentState({
} }
if (tipInfo.hasError) { if (tipInfo.hasError) {
return { return {
status: "loading-uri", status: "error",
error: tipInfo, 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; const { tip } = tipInfo.response;

View File

@ -23,8 +23,7 @@ import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js"; import { tests } from "../../../../web-util/src/index.browser.js";
import { mountHook, nullFunction } from "../../test-utils.js"; import { createWalletApiMock, nullFunction } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js";
import { Props } from "./index.js"; import { Props } from "./index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
@ -47,11 +46,9 @@ describe("Tip CTA states", () => {
expect(error).undefined; expect(error).undefined;
}, },
({ status, error }) => { ({ status, error }) => {
expect(status).equals("loading-uri"); expect(status).equals("error");
if (!error) expect.fail(); if (!error) expect.fail();
if (!error.hasError) expect.fail(); expect(error.cause?.message).eq("ERROR_NO-URI-FOR-TIP");
if (error.operational) expect.fail();
expect(error.message).eq("ERROR_NO-URI-FOR-TIP");
}, },
], ],
TestingContext, TestingContext,

View File

@ -14,9 +14,9 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { Fragment, h, VNode } from "preact";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { LoadingError } from "../../components/LoadingError.js";
import { LogoHeader } from "../../components/LogoHeader.js"; import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { Link, SubTitle, WalletAction } from "../../components/styled/index.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 { Button } from "../../mui/Button.js";
import { State } from "./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 tip status</i18n.Translate>}
error={error}
/>
);
}
export function IgnoredView(state: State.Ignored): VNode { export function IgnoredView(state: State.Ignored): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
return ( return (
@ -66,18 +55,18 @@ export function ReadyView(state: State.Ready): VNode {
<i18n.Translate>The merchant is offering you a tip</i18n.Translate> <i18n.Translate>The merchant is offering you a tip</i18n.Translate>
</p> </p>
<Part <Part
title={<i18n.Translate>Amount</i18n.Translate>} title={i18n.str`Amount`}
text={<Amount value={state.amount} />} text={<Amount value={state.amount} />}
kind="positive" kind="positive"
/> />
<Part <Part
title={<i18n.Translate>Merchant URL</i18n.Translate>} title={i18n.str`Merchant URL`}
text={state.merchantBaseUrl} text={state.merchantBaseUrl as TranslatedString}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Exchange</i18n.Translate>} title={i18n.str`Exchange`}
text={state.exchangeBaseUrl} text={state.exchangeBaseUrl as TranslatedString}
kind="neutral" kind="neutral"
/> />
</section> </section>
@ -92,11 +81,6 @@ export function ReadyView(state: State.Ready): VNode {
</i18n.Translate> </i18n.Translate>
</Button> </Button>
</section> </section>
<section>
<Link upperCased onClick={state.cancel.onClick}>
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
</WalletAction> </WalletAction>
); );
} }

View File

@ -15,12 +15,13 @@
*/ */
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util"; import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.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 { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export interface Props { export interface Props {
amount: string; amount: string;
@ -37,8 +38,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-uri"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -59,7 +60,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-uri": LoadingUriView, error: ErrorAlertView,
ready: ReadyView, ready: ReadyView,
}; };

View File

@ -22,7 +22,9 @@ import {
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { isFuture, parse } from "date-fns"; import { isFuture, parse } from "date-fns";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -33,6 +35,7 @@ export function useComponentState({
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const amount = Amounts.parseOrThrow(amountStr); const amount = Amounts.parseOrThrow(amountStr);
const { i18n } = useTranslationContext();
const [subject, setSubject] = useState<string | undefined>(); const [subject, setSubject] = useState<string | undefined>();
const [timestamp, setTimestamp] = useState<string | undefined>(); const [timestamp, setTimestamp] = useState<string | undefined>();
@ -59,10 +62,19 @@ export function useComponentState({
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-uri", status: "error",
error: hook, 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 { amountEffective, amountRaw } = hook.response;
const debitAmount = Amounts.parseOrThrow(amountRaw); const debitAmount = Amounts.parseOrThrow(amountRaw);

View File

@ -17,10 +17,8 @@
import { format } from "date-fns"; import { format } from "date-fns";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LoadingError } from "../../components/LoadingError.js";
import { LogoHeader } from "../../components/LogoHeader.js"; import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { QR } from "../../components/QR.js";
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.js"; import { Button } from "../../mui/Button.js";
@ -28,17 +26,6 @@ import { TextField } from "../../mui/TextField.js";
import { TransferDetails } from "../../wallet/Transaction.js"; import { TransferDetails } from "../../wallet/Transaction.js";
import { State } from "./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</i18n.Translate>}
error={error}
/>
);
}
export function ReadyView({ export function ReadyView({
subject, subject,
expiration, expiration,
@ -80,11 +67,7 @@ export function ReadyView({
</SubTitle> </SubTitle>
{operationError && ( {operationError && (
<ErrorTalerOperation <ErrorTalerOperation
title={ title={i18n.str`Could not finish the transfer creation`}
<i18n.Translate>
Could not finish the transfer creation
</i18n.Translate>
}
error={operationError} error={operationError}
/> />
)} )}
@ -93,9 +76,7 @@ export function ReadyView({
<TextField <TextField
label="Subject" label="Subject"
variant="filled" variant="filled"
helperText={ helperText={i18n.str`Short description of the transfer`}
<i18n.Translate>Short description of the transfer</i18n.Translate>
}
error={subject.error} error={subject.error}
required required
fullWidth fullWidth
@ -138,7 +119,7 @@ export function ReadyView({
</p> </p>
</p> </p>
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<TransferDetails <TransferDetails
amount={{ amount={{
@ -154,13 +135,6 @@ export function ReadyView({
<i18n.Translate>Create</i18n.Translate> <i18n.Translate>Create</i18n.Translate>
</Button> </Button>
</section> </section>
<section>
<section>
<Link upperCased onClick={cancel.onClick}>
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
</section>
</WalletAction> </WalletAction>
); );
} }

View File

@ -19,12 +19,13 @@ import {
AmountJson, AmountJson,
TalerErrorDetail, TalerErrorDetail,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.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 { ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export interface Props { export interface Props {
talerPayPushUri: string; talerPayPushUri: string;
@ -41,8 +42,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-uri"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -62,7 +63,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-uri": LoadingUriView, error: ErrorAlertView,
ready: ReadyView, ready: ReadyView,
}; };

View File

@ -22,7 +22,9 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -32,6 +34,7 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, { return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, {
talerUri: talerPayPushUri, talerUri: talerPayPushUri,
@ -49,10 +52,19 @@ export function useComponentState({
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-uri", status: "error",
error: hook, 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; const { contractTerms, peerPushPaymentIncomingId } = hook.response;

View File

@ -17,7 +17,6 @@
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LoadingError } from "../../components/LoadingError.js";
import { LogoHeader } from "../../components/LogoHeader.js"; import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { Link, SubTitle, WalletAction } from "../../components/styled/index.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 { Button } from "../../mui/Button.js";
import { State } from "./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</i18n.Translate>}
error={error}
/>
);
}
export function ReadyView({ export function ReadyView({
accept, accept,
summary, summary,
@ -54,25 +42,15 @@ export function ReadyView({
</SubTitle> </SubTitle>
{operationError && ( {operationError && (
<ErrorTalerOperation <ErrorTalerOperation
title={ title={i18n.str`Could not finish the pickup operation`}
<i18n.Translate>
Could not finish the pickup operation
</i18n.Translate>
}
error={operationError} error={operationError}
/> />
)} )}
<section style={{ textAlign: "left" }}> <section style={{ textAlign: "left" }}>
<Part title={i18n.str`Subject`} text={<div>{summary}</div>} />
<Part title={i18n.str`Amount`} text={<Amount value={amount} />} />
<Part <Part
title={<i18n.Translate>Subject</i18n.Translate>} title={i18n.str`Valid until`}
text={<div>{summary}</div>}
/>
<Part
title={<i18n.Translate>Amount</i18n.Translate>}
text={<Amount value={amount} />}
/>
<Part
title={<i18n.Translate>Valid until</i18n.Translate>}
text={<Time timestamp={expiration} format="dd MMMM yyyy, HH:mm" />} text={<Time timestamp={expiration} format="dd MMMM yyyy, HH:mm" />}
kind="neutral" kind="neutral"
/> />
@ -84,11 +62,6 @@ export function ReadyView({
</i18n.Translate> </i18n.Translate>
</Button> </Button>
</section> </section>
<section>
<Link upperCased onClick={cancel.onClick}>
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
</WalletAction> </WalletAction>
); );
} }

View File

@ -27,7 +27,9 @@ import {
import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js";
import { NoExchangesView } from "../../wallet/ExchangeSelection/views.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 { export interface PropsFromURI {
talerWithdrawUri: string | undefined; talerWithdrawUri: string | undefined;
@ -44,7 +46,6 @@ export interface PropsFromParams {
export type State = export type State =
| State.Loading | State.Loading
| State.LoadingUriError | State.LoadingUriError
| State.LoadingInfoError
| SelectExchangeState.NoExchange | SelectExchangeState.NoExchange
| SelectExchangeState.Selecting | SelectExchangeState.Selecting
| State.Success; | State.Success;
@ -55,12 +56,8 @@ export namespace State {
error: undefined; error: undefined;
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "uri-error"; status: "error";
error: HookError; error: ErrorAlert;
}
export interface LoadingInfoError {
status: "amount-error";
error: HookError;
} }
export type Success = { export type Success = {
@ -86,8 +83,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"uri-error": LoadingUriView, error: ErrorAlertView,
"amount-error": LoadingInfoView,
"no-exchange": NoExchangesView, "no-exchange": NoExchangesView,
"selecting-exchange": ExchangeSelectionPage, "selecting-exchange": ExchangeSelectionPage,
success: SuccessView, success: SuccessView,

View File

@ -23,7 +23,9 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
import { RecursiveState } from "../../utils/index.js"; import { RecursiveState } from "../../utils/index.js";
@ -35,6 +37,7 @@ export function useComponentStateFromParams({
onSuccess, onSuccess,
}: PropsFromParams): RecursiveState<State> { }: PropsFromParams): RecursiveState<State> {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const uriInfoHook = useAsyncAsHook(async () => { const uriInfoHook = useAsyncAsHook(async () => {
const exchanges = await api.wallet.call( const exchanges = await api.wallet.call(
WalletApiOperation.ListExchanges, WalletApiOperation.ListExchanges,
@ -47,8 +50,11 @@ export function useComponentStateFromParams({
if (uriInfoHook.hasError) { if (uriInfoHook.hasError) {
return { return {
status: "uri-error", status: "error",
error: uriInfoHook, error: alertFromError(
i18n.str`Could not load the list of exchanges`,
uriInfoHook,
),
}; };
} }
@ -95,6 +101,7 @@ export function useComponentStateFromURI({
onSuccess, onSuccess,
}: PropsFromURI): RecursiveState<State> { }: PropsFromURI): RecursiveState<State> {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
/** /**
* Ask the wallet about the withdraw URI * Ask the wallet about the withdraw URI
*/ */
@ -123,8 +130,11 @@ export function useComponentStateFromURI({
if (uriInfoHook.hasError) { if (uriInfoHook.hasError) {
return { return {
status: "uri-error", status: "error",
error: uriInfoHook, error: alertFromError(
i18n.str`Could not load info from URI`,
uriInfoHook,
),
}; };
} }
@ -194,6 +204,7 @@ function exchangeSelectionState(
} }
return () => { return () => {
const { i18n } = useTranslationContext();
const [ageRestricted, setAgeRestricted] = useState(0); const [ageRestricted, setAgeRestricted] = useState(0);
const currentExchange = selectedExchange.selected; const currentExchange = selectedExchange.selected;
const tosNeedToBeAccepted = const tosNeedToBeAccepted =
@ -255,8 +266,11 @@ function exchangeSelectionState(
} }
if (amountHook.hasError) { if (amountHook.hasError) {
return { return {
status: "amount-error", status: "error",
error: amountHook, error: alertFromError(
i18n.str`Could not load the withdrawal details`,
amountHook,
),
}; };
} }
if (!amountHook.response) { if (!amountHook.response) {

View File

@ -84,11 +84,11 @@ describe("Withdraw CTA states", () => {
expect(status).equals("loading"); expect(status).equals("loading");
}, },
({ status, error }) => { ({ status, error }) => {
if (status != "uri-error") expect.fail(); if (status != "error") expect.fail();
if (!error) expect.fail(); if (!error) expect.fail();
if (!error.hasError) expect.fail(); // if (!error.hasError) expect.fail();
if (error.operational) expect.fail(); // if (error.operational) expect.fail();
expect(error.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL"); expect(error.cause?.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL");
}, },
], ],
TestingContext, TestingContext,

View File

@ -19,16 +19,10 @@ import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js"; import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LoadingError } from "../../components/LoadingError.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { QR } from "../../components/QR.js"; import { QR } from "../../components/QR.js";
import { SelectList } from "../../components/SelectList.js"; import { SelectList } from "../../components/SelectList.js";
import { import { Input, LinkSuccess, SvgIcon } from "../../components/styled/index.js";
Input,
Link,
LinkSuccess,
SvgIcon,
} from "../../components/styled/index.js";
import { TermsOfService } from "../../components/TermsOfService/index.js"; import { TermsOfService } from "../../components/TermsOfService/index.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.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 { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js";
import { State } from "./index.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 { export function SuccessView(state: State.Success): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const currentTosVersionIsAccepted = const currentTosVersionIsAccepted =
@ -68,11 +38,7 @@ export function SuccessView(state: State.Success): VNode {
<Fragment> <Fragment>
{state.doWithdrawal.error && ( {state.doWithdrawal.error && (
<ErrorTalerOperation <ErrorTalerOperation
title={ title={i18n.str`Could not finish the withdrawal operation`}
<i18n.Translate>
Could not finish the withdrawal operation
</i18n.Translate>
}
error={state.doWithdrawal.error.errorDetail} error={state.doWithdrawal.error.errorDetail}
/> />
)} )}
@ -103,7 +69,7 @@ export function SuccessView(state: State.Success): VNode {
big big
/> />
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<WithdrawDetails <WithdrawDetails
amount={{ amount={{
@ -116,7 +82,7 @@ export function SuccessView(state: State.Success): VNode {
{state.ageRestriction && ( {state.ageRestriction && (
<Input> <Input>
<SelectList <SelectList
label={<i18n.Translate>Age restriction</i18n.Translate>} label={i18n.str`Age restriction`}
list={state.ageRestriction.list} list={state.ageRestriction.list}
name="age" name="age"
value={state.ageRestriction.value} value={state.ageRestriction.value}
@ -148,11 +114,6 @@ export function SuccessView(state: State.Success): VNode {
{state.talerWithdrawUri ? ( {state.talerWithdrawUri ? (
<WithdrawWithMobile talerWithdrawUri={state.talerWithdrawUri} /> <WithdrawWithMobile talerWithdrawUri={state.talerWithdrawUri} />
) : undefined} ) : undefined}
<section>
<Link upperCased onClick={state.cancel}>
<i18n.Translate>Cancel</i18n.Translate>
</Link>
</section>
</Fragment> </Fragment>
); );
} }
@ -168,11 +129,7 @@ function WithdrawWithMobile({
return ( return (
<section> <section>
<LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}> <LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}>
{!showQR ? ( {!showQR ? i18n.str`Withdraw to a mobile phone` : i18n.str`Hide QR`}
<i18n.Translate>Withdraw to a mobile phone</i18n.Translate>
) : (
<i18n.Translate>Hide QR</i18n.Translate>
)}
</LinkSuccess> </LinkSuccess>
{showQR && ( {showQR && (
<div> <div>

View File

@ -16,6 +16,7 @@
import { TalerErrorDetail } from "@gnu-taler/taler-util"; import { TalerErrorDetail } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useMemo, useState } from "preact/hooks"; import { useEffect, useMemo, useState } from "preact/hooks";
import { WalletError } from "../wxApi.js";
export interface HookOk<T> { export interface HookOk<T> {
hasError: false; hasError: false;
@ -26,13 +27,14 @@ export type HookError = HookGenericError | HookOperationalError;
export interface HookGenericError { export interface HookGenericError {
hasError: true; hasError: true;
operational: false; type: "error";
message: string; message: string;
} }
export interface HookOperationalError { export interface HookOperationalError {
hasError: true; hasError: true;
operational: true; type: "taler";
message: string;
details: TalerErrorDetail; details: TalerErrorDetail;
} }
@ -68,13 +70,21 @@ export function useAsyncAsHook<T>(
if (e instanceof TalerError) { if (e instanceof TalerError) {
setHookResponse({ setHookResponse({
hasError: true, hasError: true,
operational: true, type: "taler",
message: e.message,
details: e.errorDetail, 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) { } else if (e instanceof Error) {
setHookResponse({ setHookResponse({
hasError: true, hasError: true,
operational: false, type: "error",
message: e.message, message: e.message,
}); });
} }

View File

@ -17,6 +17,7 @@
import { useNotNullLocalStorage } from "./useLocalStorage.js"; import { useNotNullLocalStorage } from "./useLocalStorage.js";
function getBrowserLang(): string | undefined { function getBrowserLang(): string | undefined {
if (typeof window === "undefined") return undefined;
if (window.navigator.languages) return window.navigator.languages[0]; if (window.navigator.languages) return window.navigator.languages[0];
if (window.navigator.language) return window.navigator.language; if (window.navigator.language) return window.navigator.language;
return undefined; return undefined;

View File

@ -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]; return [storedValue, setValue, isSaved];
} }

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { TranslatedString } from "@gnu-taler/taler-util";
import { css } from "@linaria/core"; import { css } from "@linaria/core";
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
import { Alert } from "./Alert.jsx"; import { Alert } from "./Alert.jsx";
@ -53,16 +54,16 @@ export const BasicExample = (): VNode => (
export const WithTitle = (): VNode => ( export const WithTitle = (): VNode => (
<Wrapper> <Wrapper>
<Alert title="Warning" severity="warning"> <Alert title={"Warning" as TranslatedString} severity="warning">
this is an warning this is an warning
</Alert> </Alert>
<Alert title="Error" severity="error"> <Alert title={"Error" as TranslatedString} severity="error">
this is an error this is an error
</Alert> </Alert>
<Alert title="Success" severity="success"> <Alert title={"Success" as TranslatedString} severity="success">
this is an success this is an success
</Alert> </Alert>
<Alert title="Info" severity="info"> <Alert title={"Info" as TranslatedString} severity="info">
this is an info this is an info
</Alert> </Alert>
</Wrapper> </Wrapper>
@ -74,16 +75,32 @@ const showSomething = async function (): Promise<void> {
export const WithAction = (): VNode => ( export const WithAction = (): VNode => (
<Wrapper> <Wrapper>
<Alert title="Warning" severity="warning" onClose={showSomething}> <Alert
title={"Warning" as TranslatedString}
severity="warning"
onClose={showSomething}
>
this is an warning this is an warning
</Alert> </Alert>
<Alert title="Error" severity="error" onClose={showSomething}> <Alert
title={"Error" as TranslatedString}
severity="error"
onClose={showSomething}
>
this is an error this is an error
</Alert> </Alert>
<Alert title="Success" severity="success" onClose={showSomething}> <Alert
title={"Success" as TranslatedString}
severity="success"
onClose={showSomething}
>
this is an success this is an success
</Alert> </Alert>
<Alert title="Info" severity="info" onClose={showSomething}> <Alert
title={"Info" as TranslatedString}
severity="info"
onClose={showSomething}
>
this is an info this is an info
</Alert> </Alert>
</Wrapper> </Wrapper>

View File

@ -13,6 +13,7 @@
You should have received a copy of the GNU General Public License along with 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/> 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 { css } from "@linaria/core";
import { ComponentChildren, h, VNode } from "preact"; import { ComponentChildren, h, VNode } from "preact";
// eslint-disable-next-line import/extensions // eslint-disable-next-line import/extensions
@ -61,7 +62,7 @@ const colorVariant = {
}; };
interface Props { interface Props {
title?: string; title?: TranslatedString;
variant?: "filled" | "outlined" | "standard"; variant?: "filled" | "outlined" | "standard";
role?: string; role?: string;
onClose?: () => Promise<void>; onClose?: () => Promise<void>;
@ -110,20 +111,20 @@ function Message({
title, title,
children, children,
}: { }: {
title?: string; title?: TranslatedString;
children: ComponentChildren; children: ComponentChildren;
}): VNode { }): VNode {
return ( return (
<div <div
class={css` class={css`
padding: 8px 0px; padding: 8px 0px;
width: 100%; width: 90%;
`} `}
> >
{title && ( {title && (
<Typography <Typography
class={css` class={css`
font-weight: ${theme.typography.fontWeightMedium}; font-weight: ${theme.typography.fontWeightBold};
`} `}
gutterBottom gutterBottom
> >
@ -160,6 +161,7 @@ export function Alert({
"--color-main": theme.palette[severity].main, "--color-main": theme.palette[severity].main,
"--color-light": theme.palette[severity].light, "--color-light": theme.palette[severity].light,
// ...(style as any), // ...(style as any),
textAlign: "left",
}} }}
elevation={1} elevation={1}
> >

View File

@ -29,9 +29,6 @@ const borderVariant = {
`, `,
}; };
const baseStyle = css` const baseStyle = css`
background-color: ${theme.palette.background.paper};
color: ${theme.palette.text.primary};
.theme-dark & { .theme-dark & {
background-image: var(--gradient-white-elevation); background-image: var(--gradient-white-elevation);
} }

View File

@ -57,7 +57,7 @@ export function hexToRgb(color: string): string {
let colors = color.match(re); let colors = color.match(re);
if (colors && colors[0].length === 1) { if (colors && colors[0].length === 1) {
colors = colors.map((n) => n + n); colors = colors.map((n) => n + n) as RegExpMatchArray;
} }
return colors return colors

View File

@ -146,9 +146,9 @@ export interface BackgroundPlatformAPI {
*/ */
getPermissionsApi(): CrossBrowserPermissionsApi; getPermissionsApi(): CrossBrowserPermissionsApi;
/** /**
* Used by the wallet backend to send notification about new information * Used by the wallet backend to send notification about new information
* @param message * @param message
*/ */
sendMessageToAllChannels(message: MessageFromBackend): void; sendMessageToAllChannels(message: MessageFromBackend): void;
/** /**
@ -196,7 +196,6 @@ export interface ForegroundPlatformAPI {
*/ */
openWalletURIFromPopup(talerUri: string): void; openWalletURIFromPopup(talerUri: string): void;
/** /**
* Popup API * Popup API
* *
@ -248,5 +247,4 @@ export interface ForegroundPlatformAPI {
listenToWalletBackground( listenToWalletBackground(
listener: (message: MessageFromBackend) => void, listener: (message: MessageFromBackend) => void,
): () => void; ): () => void;
} }

View File

@ -14,17 +14,17 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { import { classifyTalerUri, Logger, TalerUriType } from "@gnu-taler/taler-util";
classifyTalerUri, Logger,
TalerUriType
} from "@gnu-taler/taler-util";
import { WalletOperations } from "@gnu-taler/taler-wallet-core"; import { WalletOperations } from "@gnu-taler/taler-wallet-core";
import { BackgroundOperations } from "../wxApi.js"; import { BackgroundOperations } from "../wxApi.js";
import { import {
BackgroundPlatformAPI, CrossBrowserPermissionsApi, ForegroundPlatformAPI, MessageFromBackend, BackgroundPlatformAPI,
CrossBrowserPermissionsApi,
ForegroundPlatformAPI,
MessageFromBackend,
MessageFromFrontend, MessageFromFrontend,
MessageResponse, MessageResponse,
Permissions Permissions,
} from "./api.js"; } from "./api.js";
const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { const api: BackgroundPlatformAPI & ForegroundPlatformAPI = {
@ -306,13 +306,12 @@ function openWalletPageFromPopup(page: string): void {
}); });
} }
let nextMessageIndex = 0; let nextMessageIndex = 0;
/** /**
* To be used by the foreground * To be used by the foreground
* @param message * @param message
* @returns * @returns
*/ */
async function sendMessageToBackground< async function sendMessageToBackground<
Op extends WalletOperations | BackgroundOperations, Op extends WalletOperations | BackgroundOperations,
@ -321,13 +320,13 @@ async function sendMessageToBackground<
return new Promise<any>((resolve, reject) => { return new Promise<any>((resolve, reject) => {
logger.trace("send operation to the wallet background", message); logger.trace("send operation to the wallet background", message);
let timedout = false let timedout = false;
setTimeout(() => { setTimeout(() => {
timedout = true timedout = true;
reject("timedout") reject("timedout");
}, 2000); }, 2000);
chrome.runtime.sendMessage(messageWithId, (backgroundResponse) => { chrome.runtime.sendMessage(messageWithId, (backgroundResponse) => {
if (timedout) return false if (timedout) return false;
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError.message); reject(chrome.runtime.lastError.message);
} else { } else {
@ -358,7 +357,6 @@ function listenToWalletBackground(listener: (m: any) => void): () => void {
const allPorts: chrome.runtime.Port[] = []; const allPorts: chrome.runtime.Port[] = [];
function sendMessageToAllChannels(message: MessageFromBackend): void { function sendMessageToAllChannels(message: MessageFromBackend): void {
for (const notif of allPorts) { for (const notif of allPorts) {
// const message: MessageFromBackend = { type: msg.type }; // const message: MessageFromBackend = { type: msg.type };
@ -578,26 +576,26 @@ function setAlertedIcon(): void {
interface OffscreenCanvasRenderingContext2D interface OffscreenCanvasRenderingContext2D
extends CanvasState, extends CanvasState,
CanvasTransform, CanvasTransform,
CanvasCompositing, CanvasCompositing,
CanvasImageSmoothing, CanvasImageSmoothing,
CanvasFillStrokeStyles, CanvasFillStrokeStyles,
CanvasShadowStyles, CanvasShadowStyles,
CanvasFilters, CanvasFilters,
CanvasRect, CanvasRect,
CanvasDrawPath, CanvasDrawPath,
CanvasUserInterface, CanvasUserInterface,
CanvasText, CanvasText,
CanvasDrawImage, CanvasDrawImage,
CanvasImageData, CanvasImageData,
CanvasPathDrawingStyles, CanvasPathDrawingStyles,
CanvasTextDrawingStyles, CanvasTextDrawingStyles,
CanvasPath { CanvasPath {
readonly canvas: OffscreenCanvas; readonly canvas: OffscreenCanvas;
} }
declare const OffscreenCanvasRenderingContext2D: { declare const OffscreenCanvasRenderingContext2D: {
prototype: OffscreenCanvasRenderingContext2D; prototype: OffscreenCanvasRenderingContext2D;
new(): OffscreenCanvasRenderingContext2D; new (): OffscreenCanvasRenderingContext2D;
}; };
interface OffscreenCanvas extends EventTarget { interface OffscreenCanvas extends EventTarget {
@ -610,7 +608,7 @@ interface OffscreenCanvas extends EventTarget {
} }
declare const OffscreenCanvas: { declare const OffscreenCanvas: {
prototype: OffscreenCanvas; prototype: OffscreenCanvas;
new(width: number, height: number): OffscreenCanvas; new (width: number, height: number): OffscreenCanvas;
}; };
function createCanvas(size: number): OffscreenCanvas { function createCanvas(size: number): OffscreenCanvas {

View File

@ -14,7 +14,12 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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, { import chromePlatform, {
containsHostPermissions as chromeHostContains, containsHostPermissions as chromeHostContains,
removeHostPermissions as chromeHostRemove, removeHostPermissions as chromeHostRemove,

View File

@ -19,12 +19,13 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { BalanceTable } from "../components/BalanceTable.js"; import { BalanceTable } from "../components/BalanceTable.js";
import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
import { MultiActionButton } from "../components/MultiActionButton.js"; import { MultiActionButton } from "../components/MultiActionButton.js";
import { alertFromError, ErrorAlert } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.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 { Button } from "../mui/Button.js";
import { ButtonHandler } from "../mui/handlers.js"; import { ButtonHandler } from "../mui/handlers.js";
import { compose, StateViewMap } from "../utils/index.js"; import { compose, StateViewMap } from "../utils/index.js";
@ -47,7 +48,7 @@ export namespace State {
export interface Error { export interface Error {
status: "error"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface Action { export interface Action {
@ -73,6 +74,7 @@ function useComponentState({
goToWalletManualWithdraw, goToWalletManualWithdraw,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const [addingAction, setAddingAction] = useState(false); const [addingAction, setAddingAction] = useState(false);
const state = useAsyncAsHook(() => const state = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.GetBalances, {}), api.wallet.call(WalletApiOperation.GetBalances, {}),
@ -94,7 +96,7 @@ function useComponentState({
if (state.hasError) { if (state.hasError) {
return { return {
status: "error", status: "error",
error: state, error: alertFromError(i18n.str`Could not load the balance`, state),
}; };
} }
if (addingAction) { if (addingAction) {
@ -123,7 +125,7 @@ function useComponentState({
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
error: ErrorView, error: ErrorAlertView,
action: ActionView, action: ActionView,
balance: BalanceView, balance: BalanceView,
}; };
@ -134,16 +136,6 @@ export const BalancePage = compose(
viewMapping, 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 { function ActionView({ cancel }: State.Action): VNode {
return <AddNewActionView onCancel={cancel.onClick!} />; return <AddNewActionView onCancel={cancel.onClick!} />;
} }
@ -179,7 +171,7 @@ export function BalanceView(state: State.Balances): VNode {
</Button> </Button>
{currencyWithNonZeroAmount.length > 0 && ( {currencyWithNonZeroAmount.length > 0 && (
<MultiActionButton <MultiActionButton
label={(s) => <i18n.Translate>Send {s}</i18n.Translate>} label={(s) => i18n.str`Send ${s}`}
actions={currencyWithNonZeroAmount} actions={currencyWithNonZeroAmount}
onClick={(c) => state.goToWalletDeposit(c)} onClick={(c) => state.goToWalletDeposit(c)}
/> />

View File

@ -33,7 +33,7 @@ export function NoBalanceHelp({
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
return ( return (
<Paper class={margin}> <Paper class={margin}>
<Alert title="Your wallet is empty." severity="info"> <Alert title={i18n.str`Your wallet is empty.`} severity="info">
<Button <Button
fullWidth fullWidth
color="info" color="info"

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -32,6 +32,7 @@ import {
} from "preact"; } from "preact";
import { render as renderToString } from "preact-render-to-string"; import { render as renderToString } from "preact-render-to-string";
import { BackendProvider } from "./context/backend.js"; import { BackendProvider } from "./context/backend.js";
import { TranslationProvider } from "./context/translation.js";
import { BackgroundApiClient, wxApi } from "./wxApi.js"; import { BackgroundApiClient, wxApi } from "./wxApi.js";
// When doing tests we want the requestAnimationFrame to be as fast as possible. // When doing tests we want the requestAnimationFrame to be as fast as possible.
@ -359,10 +360,12 @@ export function createWalletApiMock(): {
}; };
function TestingContext({ function TestingContext({
children, children: _cs,
}: { }: {
children: ComponentChildren; children: ComponentChildren;
}): VNode { }): VNode {
let children = _cs;
children = create(TranslationProvider, { children }, children);
return create( return create(
BackendProvider, BackendProvider,
{ {

View File

@ -16,8 +16,9 @@
import { TalerErrorDetail } from "@gnu-taler/taler-util"; import { TalerErrorDetail } from "@gnu-taler/taler-util";
import { SyncTermsOfServiceResponse } from "@gnu-taler/taler-wallet-core"; import { SyncTermsOfServiceResponse } from "@gnu-taler/taler-wallet-core";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ErrorAlert } from "../../context/alert.js";
import { import {
ButtonHandler, ButtonHandler,
TextFieldHandler, TextFieldHandler,
@ -25,11 +26,7 @@ import {
} from "../../mui/handlers.js"; } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { import { ConfirmProviderView, SelectProviderView } from "./views.js";
ConfirmProviderView,
LoadingUriView,
SelectProviderView,
} from "./views.js";
export interface Props { export interface Props {
onBack: () => Promise<void>; onBack: () => Promise<void>;
@ -50,8 +47,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-error"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface ConfirmProvider { export interface ConfirmProvider {
@ -77,7 +74,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-error": LoadingUriView, error: ErrorAlertView,
"select-provider": SelectProviderView, "select-provider": SelectProviderView,
"confirm-provider": ConfirmProviderView, "confirm-provider": ConfirmProviderView,
}; };

View File

@ -17,12 +17,10 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { Checkbox } from "../../components/Checkbox.js"; import { Checkbox } from "../../components/Checkbox.js";
import { LoadingError } from "../../components/LoadingError.js";
import { import {
LightText, LightText,
SmallLightText, SmallLightText,
SubTitle, SubTitle,
TermsOfService,
Title, Title,
} from "../../components/styled/index.js"; } from "../../components/styled/index.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
@ -30,17 +28,6 @@ import { Button } from "../../mui/Button.js";
import { TextField } from "../../mui/TextField.js"; import { TextField } from "../../mui/TextField.js";
import { State } from "./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</i18n.Translate>}
error={error}
/>
);
}
export function ConfirmProviderView({ export function ConfirmProviderView({
url, url,
provider, provider,
@ -88,9 +75,8 @@ export function ConfirmProviderView({
of service of service
</i18n.Translate> </i18n.Translate>
</p> </p>
{/* replace with <TermsOfService /> */}
<Checkbox <Checkbox
label={<i18n.Translate>Accept terms of service</i18n.Translate>} label={i18n.str`Accept terms of service`}
name="terms" name="terms"
onToggle={tos.button.onClick} onToggle={tos.button.onClick}
enabled={tos.value} enabled={tos.value}

View File

@ -25,13 +25,17 @@ import { createHashHistory } from "history";
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
import Router, { route, Route } from "preact-router"; import Router, { route, Route } from "preact-router";
import { useEffect } from "preact/hooks"; import { useEffect } from "preact/hooks";
import { CurrentAlerts } from "../components/CurrentAlerts.js";
import { LogoHeader } from "../components/LogoHeader.js"; import { LogoHeader } from "../components/LogoHeader.js";
import PendingTransactions from "../components/PendingTransactions.js"; import PendingTransactions from "../components/PendingTransactions.js";
import { import {
Link,
LinkPrimary,
SubTitle, SubTitle,
WalletAction, WalletAction,
WalletBox, WalletBox,
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { AlertProvider } from "../context/alert.js";
import { DevContextProvider } from "../context/devContext.js"; import { DevContextProvider } from "../context/devContext.js";
import { IoCProviderForRuntime } from "../context/iocContext.js"; import { IoCProviderForRuntime } from "../context/iocContext.js";
import { import {
@ -66,6 +70,7 @@ import { QrReaderPage } from "./QrReader.js";
import { SettingsPage } from "./Settings.js"; import { SettingsPage } from "./Settings.js";
import { TransactionPage } from "./Transaction.js"; import { TransactionPage } from "./Transaction.js";
import { WelcomePage } from "./Welcome.js"; import { WelcomePage } from "./Welcome.js";
import CloseIcon from "../svg/close_24px.svg";
export function Application(): VNode { export function Application(): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -495,15 +500,6 @@ function matchesRoute(url: string, route: string): boolean {
return !result ? false : true; 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({ function CallToActionTemplate({
title, title,
children, children,
@ -511,11 +507,35 @@ function CallToActionTemplate({
title: TranslatedString; title: TranslatedString;
children: ComponentChildren; children: ComponentChildren;
}): VNode { }): VNode {
const { i18n } = useTranslationContext();
return ( return (
<WalletAction> <WalletAction>
<LogoHeader /> <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> <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> </WalletAction>
); );
} }
@ -536,7 +556,10 @@ function WalletTemplate({
{goToTransaction ? ( {goToTransaction ? (
<PendingTransactions goToTransaction={goToTransaction} /> <PendingTransactions goToTransaction={goToTransaction} />
) : undefined} ) : undefined}
<WalletBox>{children}</WalletBox> <CurrentAlerts />
<WalletBox>
<AlertProvider>{children}</AlertProvider>
</WalletBox>
</Fragment> </Fragment>
); );
} }

View File

@ -29,8 +29,8 @@ import {
} from "date-fns"; } from "date-fns";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { AlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
import { QR } from "../components/QR.js"; import { QR } from "../components/QR.js";
import { import {
BoldLight, BoldLight,
@ -42,6 +42,7 @@ import {
SmallText, SmallText,
WarningBox, WarningBox,
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { alertFromError } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
@ -117,9 +118,11 @@ export function BackupPage({ onAddProvider }: Props): VNode {
} }
if (status.hasError) { if (status.hasError) {
return ( return (
<LoadingError <AlertView
title={<i18n.Translate>Could not load backup providers</i18n.Translate>} alert={alertFromError(
error={status} i18n.str`Could not load backup providers`,
status,
)}
/> />
); );
} }
@ -219,11 +222,9 @@ export function BackupView({
</div> </div>
<div> <div>
<Button variant="contained" onClick={onSyncAll}> <Button variant="contained" onClick={onSyncAll}>
{providers.length > 1 ? ( {providers.length > 1
<i18n.Translate>Sync all backups</i18n.Translate> ? i18n.str`Sync all backups`
) : ( : i18n.str`Sync now`}
<i18n.Translate>Sync now</i18n.Translate>
)}
</Button> </Button>
<Button variant="contained" color="success" onClick={onAddProvider}> <Button variant="contained" color="success" onClick={onAddProvider}>
<i18n.Translate>Add provider</i18n.Translate> <i18n.Translate>Add provider</i18n.Translate>

View File

@ -15,8 +15,9 @@
*/ */
import { AmountJson, PaytoUri } from "@gnu-taler/taler-util"; import { AmountJson, PaytoUri } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ErrorAlert } from "../../context/alert.js";
import { import {
AmountFieldHandler, AmountFieldHandler,
ButtonHandler, ButtonHandler,
@ -27,7 +28,6 @@ import { ManageAccountPage } from "../ManageAccount/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { import {
AmountOrCurrencyErrorView, AmountOrCurrencyErrorView,
LoadingErrorView,
NoAccountToDepositView, NoAccountToDepositView,
NoEnoughBalanceView, NoEnoughBalanceView,
ReadyView, ReadyView,
@ -56,8 +56,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-error"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface AddingAccount { export interface AddingAccount {
@ -107,7 +107,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-error": LoadingErrorView, error: ErrorAlertView,
"amount-or-currency-error": AmountOrCurrencyErrorView, "amount-or-currency-error": AmountOrCurrencyErrorView,
"no-enough-balance": NoEnoughBalanceView, "no-enough-balance": NoEnoughBalanceView,
"no-accounts": NoAccountToDepositView, "no-accounts": NoAccountToDepositView,

View File

@ -25,7 +25,9 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -36,6 +38,7 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr); const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr);
const currency = parsed !== undefined ? parsed.currency : currencyStr; const currency = parsed !== undefined ? parsed.currency : currencyStr;
@ -82,8 +85,8 @@ export function useComponentState({
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-error", status: "error",
error: hook, error: alertFromError(i18n.str`Could not load balance information`, hook),
}; };
} }
const { accounts, balances } = hook.response; const { accounts, balances } = hook.response;

View File

@ -18,31 +18,13 @@ import { Amounts, PaytoUri } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { AmountField } from "../../components/AmountField.js"; import { AmountField } from "../../components/AmountField.js";
import { ErrorMessage } from "../../components/ErrorMessage.js"; import { ErrorMessage } from "../../components/ErrorMessage.js";
import { LoadingError } from "../../components/LoadingError.js";
import { SelectList } from "../../components/SelectList.js"; import { SelectList } from "../../components/SelectList.js";
import { import { Input, SubTitle, WarningBox } from "../../components/styled/index.js";
ErrorText,
Input,
InputWithLabel,
SubTitle,
WarningBox,
} from "../../components/styled/index.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { Button } from "../../mui/Button.js"; import { Button } from "../../mui/Button.js";
import { Grid } from "../../mui/Grid.js"; import { Grid } from "../../mui/Grid.js";
import { State } from "./index.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( export function AmountOrCurrencyErrorView(
p: State.AmountOrCurrencyError, p: State.AmountOrCurrencyError,
): VNode { ): VNode {
@ -50,11 +32,7 @@ export function AmountOrCurrencyErrorView(
return ( return (
<ErrorMessage <ErrorMessage
title={ title={i18n.str`A currency or an amount should be indicated`}
<i18n.Translate>
A currency or an amount should be indicated
</i18n.Translate>
}
/> />
); );
} }
@ -66,11 +44,7 @@ export function NoEnoughBalanceView({
return ( return (
<ErrorMessage <ErrorMessage
title={ title={i18n.str`There is no enough balance to make a deposit for currency ${currency}`}
<i18n.Translate>
There is no enough balance to make a deposit for currency {currency}
</i18n.Translate>
}
/> />
); );
} }
@ -150,7 +124,7 @@ export function ReadyView(state: State.Ready): VNode {
> >
<Input> <Input>
<SelectList <SelectList
label={<i18n.Translate>Select account</i18n.Translate>} label={i18n.str`Select account`}
list={state.account.list} list={state.account.list}
name="account" name="account"
value={state.account.value} value={state.account.value}
@ -171,14 +145,11 @@ export function ReadyView(state: State.Ready): VNode {
</p> </p>
<Grid container spacing={2} columns={1}> <Grid container spacing={2} columns={1}>
<Grid item xs={1}> <Grid item xs={1}>
<AmountField <AmountField label={i18n.str`Amount`} handler={state.amount} />
label={<i18n.Translate>Amount</i18n.Translate>}
handler={state.amount}
/>
</Grid> </Grid>
<Grid item xs={1}> <Grid item xs={1}>
<AmountField <AmountField
label={<i18n.Translate>Deposit fee</i18n.Translate>} label={i18n.str`Deposit fee`}
handler={{ handler={{
value: state.totalFee, value: state.totalFee,
}} }}
@ -186,7 +157,7 @@ export function ReadyView(state: State.Ready): VNode {
</Grid> </Grid>
<Grid item xs={1}> <Grid item xs={1}>
<AmountField <AmountField
label={<i18n.Translate>Total deposit</i18n.Translate>} label={i18n.str`Total deposit`}
handler={{ handler={{
value: state.totalToDeposit, value: state.totalToDeposit,
}} }}

View File

@ -14,12 +14,13 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { 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 { AmountFieldHandler, ButtonHandler } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView, SelectCurrencyView } from "./views.js"; import { ReadyView, SelectCurrencyView } from "./views.js";
export type Props = PropsGet | PropsSend; export type Props = PropsGet | PropsSend;
@ -49,8 +50,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-error"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface SelectCurrency { export interface SelectCurrency {
@ -80,7 +81,7 @@ export type Contact = {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-error": LoadingUriView, error: ErrorAlertView,
"select-currency": SelectCurrencyView, "select-currency": SelectCurrencyView,
ready: ReadyView, ready: ReadyView,
}; };

View File

@ -17,7 +17,9 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { assertUnreachable, RecursiveState } from "../../utils/index.js"; import { assertUnreachable, RecursiveState } from "../../utils/index.js";
import { Contact, Props, State } from "./index.js"; import { Contact, Props, State } from "./index.js";
@ -58,6 +60,8 @@ export function useComponentState(props: Props): RecursiveState<State> {
if (!amount) { if (!amount) {
return () => { return () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { i18n } = useTranslationContext();
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
const hook = useAsyncAsHook(() => const hook = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.ListExchanges, {}), api.wallet.call(WalletApiOperation.ListExchanges, {}),
@ -71,8 +75,8 @@ export function useComponentState(props: Props): RecursiveState<State> {
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-error", status: "error",
error: hook, error: alertFromError(i18n.str`Could not load exchanges`, hook),
}; };
} }
const currencies: Record<string, string> = {}; const currencies: Record<string, string> = {};

View File

@ -16,7 +16,7 @@
import { styled } from "@linaria/react"; import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact"; 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 { SelectList } from "../../components/SelectList.js";
import { import {
Input, Input,
@ -25,24 +25,14 @@ import {
SvgIcon, SvgIcon,
} from "../../components/styled/index.js"; } from "../../components/styled/index.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { Pages } from "../../NavigationBar.js"; import { Button } from "../../mui/Button.js";
import { Contact, State } from "./index.js";
import arrowIcon from "../../svg/chevron-down.svg";
import { AmountField } from "../../components/AmountField.js";
import { Grid } from "../../mui/Grid.js"; import { Grid } from "../../mui/Grid.js";
import { Paper } from "../../mui/Paper.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"; import { assertUnreachable } from "../../utils/index.js";
import { Contact, 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 SelectCurrencyView({ export function SelectCurrencyView({
currencies, currencies,
@ -61,7 +51,7 @@ export function SelectCurrencyView({
<p> <p>
<Input> <Input>
<SelectList <SelectList
label={<i18n.Translate>Known currencies</i18n.Translate>} label={i18n.str`Known currencies`}
list={currencies} list={currencies}
name="lang" name="lang"
value={""} value={""}
@ -214,7 +204,7 @@ export function ReadyGetView({
</h1> </h1>
<Grid container columns={2} justifyContent="space-between"> <Grid container columns={2} justifyContent="space-between">
<AmountField <AmountField
label={<i18n.Translate>Amount</i18n.Translate>} label={i18n.str`Amount`}
required required
handler={amountHandler} handler={amountHandler}
/> />
@ -304,7 +294,7 @@ export function ReadySendView({
<div> <div>
<AmountField <AmountField
label={<i18n.Translate>Amount</i18n.Translate>} label={i18n.str`Amount`}
required required
handler={amountHandler} handler={amountHandler}
/> />
@ -377,7 +367,6 @@ export function ReadySendView({
</Container> </Container>
); );
} }
import bankIcon from "../../svg/ri-bank-line.svg";
function RowExample({ function RowExample({
info, info,

View File

@ -14,11 +14,12 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { 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 { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export interface Props { export interface Props {
p: string; p: string;
@ -33,8 +34,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-error"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -48,7 +49,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-error": LoadingUriView, error: ErrorAlertView,
ready: ReadyView, ready: ReadyView,
}; };

View File

@ -15,21 +15,9 @@
*/ */
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { LoadingError } from "../../components/LoadingError.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { State } from "./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</i18n.Translate>}
error={error}
/>
);
}
export function ReadyView({ error }: State.Ready): VNode { export function ReadyView({ error }: State.Ready): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();

View File

@ -20,7 +20,9 @@ import {
ExchangeListItem, ExchangeListItem,
FeeDescriptionPair, FeeDescriptionPair,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { ErrorAlert } from "../../context/alert.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.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 { useComponentState } from "./state.js";
import { import {
ComparingView, ComparingView,
ErrorLoadingView,
NoExchangesView, NoExchangesView,
PrivacyContentView, PrivacyContentView,
ReadyView, ReadyView,
@ -58,8 +59,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "error-loading"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -99,7 +100,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"error-loading": ErrorLoadingView, error: ErrorAlertView,
comparing: ComparingView, comparing: ComparingView,
"no-exchange": NoExchangesView, "no-exchange": NoExchangesView,
"showing-tos": TosContentView, "showing-tos": TosContentView,

View File

@ -20,7 +20,9 @@ import {
WalletApiOperation, WalletApiOperation,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -31,6 +33,7 @@ export function useComponentState({
currentExchange, currentExchange,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const initialValue = exchanges.findIndex( const initialValue = exchanges.findIndex(
(e) => e.exchangeBaseUrl === currentExchange, (e) => e.exchangeBaseUrl === currentExchange,
); );
@ -84,8 +87,11 @@ export function useComponentState({
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "error-loading", status: "error",
error: hook, error: alertFromError(
i18n.str`Could not load exchange details info`,
hook,
),
}; };
} }

View File

@ -20,7 +20,6 @@ import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { ErrorMessage } from "../../components/ErrorMessage.js"; import { ErrorMessage } from "../../components/ErrorMessage.js";
import { LoadingError } from "../../components/LoadingError.js";
import { SelectList } from "../../components/SelectList.js"; import { SelectList } from "../../components/SelectList.js";
import { Input, SvgIcon } from "../../components/styled/index.js"; import { Input, SvgIcon } from "../../components/styled/index.js";
import { TermsOfService } from "../../components/TermsOfService/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({ export function PrivacyContentView({
exchangeUrl, exchangeUrl,
onClose, onClose,
@ -156,19 +144,11 @@ export function NoExchangesView({
}: SelectExchangeState.NoExchange): VNode { }: SelectExchangeState.NoExchange): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
if (!currency) { if (!currency) {
return ( return <ErrorMessage title={i18n.str`Could not find any exchange`} />;
<ErrorMessage
title={<i18n.Translate>Could not find any exchange</i18n.Translate>}
/>
);
} }
return ( return (
<ErrorMessage <ErrorMessage
title={ title={i18n.str`Could not find any exchange for the currency ${currency}`}
<i18n.Translate>
Could not find any exchange for the currency {currency}
</i18n.Translate>
}
/> />
); );
} }

View File

@ -140,15 +140,13 @@ export function ExchangeSetUrlPage({
)} )}
{error && ( {error && (
<ErrorMessage <ErrorMessage
title={ title={i18n.str`Unable to verify this exchange`}
<i18n.Translate>Unable to verify this exchange</i18n.Translate>
}
description={error} description={error}
/> />
)} )}
{confirmationError && ( {confirmationError && (
<ErrorMessage <ErrorMessage
title={<i18n.Translate>Unable to add this exchange</i18n.Translate>} title={i18n.str`Unable to add this exchange`}
description={confirmationError} description={confirmationError}
/> />
)} )}

View File

@ -23,8 +23,8 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { AlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
import { import {
CenteredBoldText, CenteredBoldText,
CenteredText, CenteredText,
@ -33,6 +33,7 @@ import {
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { Time } from "../components/Time.js"; import { Time } from "../components/Time.js";
import { TransactionItem } from "../components/TransactionItem.js"; import { TransactionItem } from "../components/TransactionItem.js";
import { alertFromError } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
@ -71,13 +72,11 @@ export function HistoryPage({
if (state.hasError) { if (state.hasError) {
return ( return (
<LoadingError <AlertView
title={ alert={alertFromError(
<i18n.Translate> i18n.str`Could not load the list of transactions`,
Could not load the list of transactions state,
</i18n.Translate> )}
}
error={state}
/> />
); );
} }

View File

@ -15,8 +15,9 @@
*/ */
import { KnownBankAccountsInfo } from "@gnu-taler/taler-util"; import { KnownBankAccountsInfo } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ErrorAlert } from "../../context/alert.js";
import { import {
ButtonHandler, ButtonHandler,
SelectFieldHandler, SelectFieldHandler,
@ -24,7 +25,7 @@ import {
} from "../../mui/handlers.js"; } from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js"; import { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export interface Props { export interface Props {
currency: string; currency: string;
@ -41,8 +42,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-error"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -68,7 +69,7 @@ export type AccountByType = {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-error": LoadingUriView, error: ErrorAlertView,
ready: ReadyView, ready: ReadyView,
}; };

View File

@ -21,7 +21,9 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { AccountByType, Props, State } from "./index.js"; import { AccountByType, Props, State } from "./index.js";
@ -31,6 +33,7 @@ export function useComponentState({
onCancel, onCancel,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(() => const hook = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }), api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }),
); );
@ -47,8 +50,8 @@ export function useComponentState({
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-error", status: "error",
error: hook, error: alertFromError(i18n.str`Could not load known bank accounts`, hook),
}; };
} }

View File

@ -23,11 +23,10 @@ import {
import { styled } from "@linaria/react"; import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { LoadingError } from "../../components/LoadingError.js"; import { ErrorMessage } from "../../components/ErrorMessage.js";
import { SelectList } from "../../components/SelectList.js"; import { SelectList } from "../../components/SelectList.js";
import { import {
Input, Input,
LightText,
SubTitle, SubTitle,
SvgIcon, SvgIcon,
WarningText, WarningText,
@ -37,10 +36,9 @@ import { Button } from "../../mui/Button.js";
import { TextFieldHandler } from "../../mui/handlers.js"; import { TextFieldHandler } from "../../mui/handlers.js";
import { TextField } from "../../mui/TextField.js"; import { TextField } from "../../mui/TextField.js";
import checkIcon from "../../svg/check_24px.svg"; import checkIcon from "../../svg/check_24px.svg";
import warningIcon from "../../svg/warning_24px.svg";
import deleteIcon from "../../svg/delete_24px.svg"; import deleteIcon from "../../svg/delete_24px.svg";
import warningIcon from "../../svg/warning_24px.svg";
import { State } from "./index.js"; import { State } from "./index.js";
import { ErrorMessage } from "../../components/ErrorMessage.js";
type AccountType = "bitcoin" | "x-taler-bank" | "iban"; type AccountType = "bitcoin" | "x-taler-bank" | "iban";
type ComponentFormByAccountType = { 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({ export function ReadyView({
currency, currency,
error, error,
@ -118,14 +105,14 @@ export function ReadyView({
{error && ( {error && (
<ErrorMessage <ErrorMessage
title={<i18n.Translate>Unable add this account</i18n.Translate>} title={i18n.str`Unable add this account`}
description={error} description={error}
/> />
)} )}
<p> <p>
<Input> <Input>
<SelectList <SelectList
label={<i18n.Translate>Select account type</i18n.Translate>} label={i18n.str`Select account type`}
list={accountType.list} list={accountType.list}
name="accountType" name="accountType"
value={accountType.value} value={accountType.value}

View File

@ -15,11 +15,12 @@
*/ */
import { UserAttentionUnreadList } from "@gnu-taler/taler-util"; import { UserAttentionUnreadList } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.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 { compose, StateViewMap } from "../../utils/index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
import { LoadingUriView, ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export type Props = object; export type Props = object;
@ -32,8 +33,8 @@ export namespace State {
} }
export interface LoadingUriError { export interface LoadingUriError {
status: "loading-error"; status: "error";
error: HookError; error: ErrorAlert;
} }
export interface BaseInfo { export interface BaseInfo {
@ -49,7 +50,7 @@ export namespace State {
const viewMapping: StateViewMap<State> = { const viewMapping: StateViewMap<State> = {
loading: Loading, loading: Loading,
"loading-error": LoadingUriView, error: ErrorAlertView,
ready: ReadyView, ready: ReadyView,
}; };

View File

@ -15,12 +15,15 @@
*/ */
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { alertFromError } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState(p: Props): State { export function useComponentState(p: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
return await api.wallet.call( return await api.wallet.call(
WalletApiOperation.GetUserAttentionRequests, WalletApiOperation.GetUserAttentionRequests,
@ -34,10 +37,14 @@ export function useComponentState(p: Props): State {
error: undefined, error: undefined,
}; };
} }
if (hook.hasError) { if (hook.hasError) {
return { return {
status: "loading-error", status: "error",
error: hook, error: alertFromError(
i18n.str`Could not load user attention request`,
hook,
),
}; };
} }

View File

@ -20,7 +20,6 @@ import {
AttentionType, AttentionType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { LoadingError } from "../../components/LoadingError.js";
import { import {
Column, Column,
DateSeparator, DateSeparator,
@ -37,17 +36,6 @@ import { Pages } from "../../NavigationBar.js";
import { assertUnreachable } from "../../utils/index.js"; import { assertUnreachable } from "../../utils/index.js";
import { State } from "./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; const term = 1000 * 60 * 60 * 24;
function normalizeToDay(x: number): number { function normalizeToDay(x: number): number {
return Math.round(x / term) * term; return Math.round(x / term) * term;

View File

@ -127,11 +127,7 @@ export function SetUrlView({
</Title> </Title>
{error && ( {error && (
<ErrorMessage <ErrorMessage
title={ title={i18n.str`Could not get provider information`}
<i18n.Translate>
Could not get provider information
</i18n.Translate>
}
description={error} description={error}
/> />
)} )}
@ -223,7 +219,7 @@ export function ConfirmProviderView({
</SubTitle> </SubTitle>
<p> <p>
{Amounts.isZero(provider.annual_fee) ? ( {Amounts.isZero(provider.annual_fee) ? (
<i18n.Translate>free of charge</i18n.Translate> i18n.str`free of charge`
) : ( ) : (
<i18n.Translate> <i18n.Translate>
{provider.annual_fee} per year of service {provider.annual_fee} per year of service
@ -240,7 +236,7 @@ export function ConfirmProviderView({
</i18n.Translate> </i18n.Translate>
</p> </p>
<Checkbox <Checkbox
label={<i18n.Translate>Accept terms of service</i18n.Translate>} label={i18n.str`Accept terms of service`}
name="terms" name="terms"
onToggle={async () => setAccepted((old) => !old)} onToggle={async () => setAccepted((old) => !old)}
enabled={accepted} enabled={accepted}

View File

@ -23,11 +23,12 @@ import {
WalletApiOperation, WalletApiOperation,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { AlertView } from "../components/CurrentAlerts.js";
import { ErrorMessage } from "../components/ErrorMessage.js"; import { ErrorMessage } from "../components/ErrorMessage.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
import { PaymentStatus, SmallLightText } from "../components/styled/index.js"; import { PaymentStatus, SmallLightText } from "../components/styled/index.js";
import { Time } from "../components/Time.js"; import { Time } from "../components/Time.js";
import { alertFromError } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
@ -65,14 +66,11 @@ export function ProviderDetailPage({
} }
if (state.hasError) { if (state.hasError) {
return ( return (
<LoadingError <AlertView
title={ alert={alertFromError(
<i18n.Translate> i18n.str`There was an error loading the provider detail for &quot;${providerURL}&quot;`,
There was an error loading the provider detail for &quot; state,
{providerURL}&quot; )}
</i18n.Translate>
}
error={state}
/> />
); );
} }
@ -270,9 +268,7 @@ function Error({ info }: { info: ProviderInfo }): VNode {
if (info.lastError) { if (info.lastError) {
return ( return (
<ErrorMessage <ErrorMessage
title={ title={i18n.str`This provider has reported an error`}
<i18n.Translate>This provider has reported an error</i18n.Translate>
}
description={info.lastError.hint} description={info.lastError.hint}
/> />
); );
@ -282,32 +278,17 @@ function Error({ info }: { info: ProviderInfo }): VNode {
case "backup-conflicting-device": case "backup-conflicting-device":
return ( return (
<ErrorMessage <ErrorMessage
title={ title={i18n.str`There is conflict with another backup from &quot;${info.backupProblem.otherDeviceId}&quot;`}
<Fragment>
<i18n.Translate>
There is conflict with another backup from{" "}
<b>{info.backupProblem.otherDeviceId}</b>
</i18n.Translate>
</Fragment>
}
/> />
); );
case "backup-unreadable": case "backup-unreadable":
return ( return <ErrorMessage title={i18n.str`Backup is not readable`} />;
<ErrorMessage
title={<i18n.Translate>Backup is not readable</i18n.Translate>}
/>
);
default: default:
return ( return (
<ErrorMessage <ErrorMessage
title={ title={i18n.str`Unknown backup problem: ${JSON.stringify(
<Fragment> info.backupProblem,
<i18n.Translate> )}`}
Unknown backup problem: {JSON.stringify(info.backupProblem)}
</i18n.Translate>
</Fragment>
}
/> />
); );
} }

View File

@ -42,8 +42,8 @@ export function ReserveCreated({
if (!paytoURI) { if (!paytoURI) {
return ( return (
<ErrorMessage <ErrorMessage
title={<i18n.Translate>Could not parse the payto URI</i18n.Translate>} title={i18n.str`Could not parse the payto URI`}
description={<i18n.Translate>Please check the uri</i18n.Translate>} description={i18n.str`Please check the uri`}
/> />
); );
} }

View File

@ -111,13 +111,13 @@ export function SettingsView({
<section> <section>
{autoOpenToggle.button.error && ( {autoOpenToggle.button.error && (
<ErrorTalerOperation <ErrorTalerOperation
title={<i18n.Translate>Could not toggle auto-open</i18n.Translate>} title={i18n.str`Could not toggle auto-open`}
error={autoOpenToggle.button.error.errorDetail} error={autoOpenToggle.button.error.errorDetail}
/> />
)} )}
{/* {clipboardToggle.button.error && ( {/* {clipboardToggle.button.error && (
<ErrorTalerOperation <ErrorTalerOperation
title={<i18n.Translate>Could not toggle clipboard</i18n.Translate>} title={i18n.str`Could not toggle clipboard`}
error={clipboardToggle.button.error.errorDetail} error={clipboardToggle.button.error.errorDetail}
/> />
)} */} )} */}
@ -125,11 +125,7 @@ export function SettingsView({
<i18n.Translate>Navigator</i18n.Translate> <i18n.Translate>Navigator</i18n.Translate>
</SubTitle> </SubTitle>
<Checkbox <Checkbox
label={ label={i18n.str`Automatically open wallet based on page content`}
<i18n.Translate>
Automatically open wallet based on page content
</i18n.Translate>
}
name="autoOpen" name="autoOpen"
description={ description={
<i18n.Translate> <i18n.Translate>
@ -142,9 +138,7 @@ export function SettingsView({
/> />
{/* <Checkbox {/* <Checkbox
label={ label={
<i18n.Translate> i18n.str`Automatically check clipboard for Taler URI`
Automatically check clipboard for Taler URI
</i18n.Translate>
} }
name="clipboard" name="clipboard"
description={ description={
@ -241,13 +235,9 @@ export function SettingsView({
<i18n.Translate>Troubleshooting</i18n.Translate> <i18n.Translate>Troubleshooting</i18n.Translate>
</SubTitle> </SubTitle>
<Checkbox <Checkbox
label={<i18n.Translate>Developer mode</i18n.Translate>} label={i18n.str`Developer mode`}
name="devMode" name="devMode"
description={ description={i18n.str`More options and information useful for debugging`}
<i18n.Translate>
More options and information useful for debugging
</i18n.Translate>
}
enabled={devModeToggle.value!} enabled={devModeToggle.value!}
onToggle={devModeToggle.button.onClick!} onToggle={devModeToggle.button.onClick!}
/> />
@ -271,7 +261,7 @@ export function SettingsView({
</SubTitle> </SubTitle>
{coreVersion && ( {coreVersion && (
<Part <Part
title={<i18n.Translate>Wallet Core</i18n.Translate>} title={i18n.str`Wallet Core`}
text={ text={
<span> <span>
{coreVersion.version}{" "} {coreVersion.version}{" "}
@ -281,7 +271,7 @@ export function SettingsView({
/> />
)} )}
<Part <Part
title={<i18n.Translate>Web Extension</i18n.Translate>} title={i18n.str`Web Extension`}
text={ text={
<span> <span>
{webexVersion.version}{" "} {webexVersion.version}{" "}
@ -292,15 +282,15 @@ export function SettingsView({
{coreVersion && ( {coreVersion && (
<JustInDevMode> <JustInDevMode>
<Part <Part
title={<i18n.Translate>Exchange compatibility</i18n.Translate>} title={i18n.str`Exchange compatibility`}
text={<span>{coreVersion.exchange}</span>} text={<span>{coreVersion.exchange}</span>}
/> />
<Part <Part
title={<i18n.Translate>Merchant compatibility</i18n.Translate>} title={i18n.str`Merchant compatibility`}
text={<span>{coreVersion.merchant}</span>} text={<span>{coreVersion.merchant}</span>}
/> />
<Part <Part
title={<i18n.Translate>Bank compatibility</i18n.Translate>} title={i18n.str`Bank compatibility`}
text={<span>{coreVersion.bank}</span>} text={<span>{coreVersion.bank}</span>}
/> />
</JustInDevMode> </JustInDevMode>

View File

@ -32,6 +32,7 @@ import {
TransactionRefund, TransactionRefund,
TransactionTip, TransactionTip,
TransactionType, TransactionType,
TranslatedString,
WithdrawalType, WithdrawalType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; 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 { Amount } from "../components/Amount.js";
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js"; import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js";
import { CopyButton } from "../components/CopyButton.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 { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js"; import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
import { QR } from "../components/QR.js"; import { QR } from "../components/QR.js";
import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js"; import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js";
@ -60,6 +60,7 @@ import {
WarningBox, WarningBox,
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { Time } from "../components/Time.js"; import { Time } from "../components/Time.js";
import { alertFromError } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
@ -98,13 +99,11 @@ export function TransactionPage({
if (state.hasError) { if (state.hasError) {
return ( return (
<LoadingError <AlertView
title={ alert={alertFromError(
<i18n.Translate> i18n.str`Could not load transaction information`,
Could not load the transaction information state,
</i18n.Translate> )}
}
error={state}
/> />
); );
} }
@ -199,14 +198,14 @@ export function TransactionView({
return ( return (
<Fragment> <Fragment>
<section style={{ padding: 8, textAlign: "center" }}> <section style={{ padding: 8, textAlign: "center" }}>
<ErrorTalerOperation {transaction?.error ? (
title={ <ErrorAlertView
<i18n.Translate> error={alertFromError(
There was an error trying to complete the transaction i18n.str`There was an error trying to complete the transaction`,
</i18n.Translate> transaction.error,
} )}
error={transaction?.error} />
/> ) : undefined}
{transaction.pending && ( {transaction.pending && (
<WarningBox> <WarningBox>
<i18n.Translate>This transaction is not completed</i18n.Translate> <i18n.Translate>This transaction is not completed</i18n.Translate>
@ -367,7 +366,7 @@ export function TransactionView({
</Fragment> </Fragment>
)} )}
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<WithdrawDetails <WithdrawDetails
amount={{ amount={{
@ -420,7 +419,7 @@ export function TransactionView({
<br /> <br />
{transaction.refunds.length > 0 ? ( {transaction.refunds.length > 0 ? (
<Part <Part
title={<i18n.Translate>Refunds</i18n.Translate>} title={i18n.str`Refunds`}
text={ text={
<table> <table>
{transaction.refunds.map((r, i) => { {transaction.refunds.map((r, i) => {
@ -462,7 +461,7 @@ export function TransactionView({
picked up. picked up.
</i18n.Translate> </i18n.Translate>
<Part <Part
title={<i18n.Translate>Offer</i18n.Translate>} title={i18n.str`Offer`}
text={<Amount value={pendingRefund} />} text={<Amount value={pendingRefund} />}
kind="positive" kind="positive"
/> />
@ -480,17 +479,17 @@ export function TransactionView({
</InfoBox> </InfoBox>
)} )}
<Part <Part
title={<i18n.Translate>Merchant</i18n.Translate>} title={i18n.str`Merchant`}
text={<MerchantDetails merchant={transaction.info.merchant} />} text={<MerchantDetails merchant={transaction.info.merchant} />}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Invoice ID</i18n.Translate>} title={i18n.str`Invoice ID`}
text={transaction.info.orderId} text={transaction.info.orderId as TranslatedString}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<PurchaseDetails <PurchaseDetails
price={price} price={price}
@ -520,12 +519,12 @@ export function TransactionView({
</Header> </Header>
{payto && <PartPayto payto={payto} kind="neutral" />} {payto && <PartPayto payto={payto} kind="neutral" />}
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={<DepositDetails transaction={transaction} />} text={<DepositDetails transaction={transaction} />}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Wire transfer deadline</i18n.Translate>} title={i18n.str`Wire transfer deadline`}
text={ text={
<Time <Time
timestamp={AbsoluteTime.fromTimestamp( timestamp={AbsoluteTime.fromTimestamp(
@ -557,7 +556,7 @@ export function TransactionView({
{transaction.exchangeBaseUrl} {transaction.exchangeBaseUrl}
</Header> </Header>
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={<RefreshDetails transaction={transaction} />} text={<RefreshDetails transaction={transaction} />}
/> />
</TransactionTemplate> </TransactionTemplate>
@ -578,12 +577,12 @@ export function TransactionView({
{transaction.merchantBaseUrl} {transaction.merchantBaseUrl}
</Header> </Header>
{/* <Part {/* <Part
title={<i18n.Translate>Merchant</i18n.Translate>} title={i18n.str`Merchant`}
text={<MerchantDetails merchant={transaction.merchant} />} text={<MerchantDetails merchant={transaction.merchant} />}
kind="neutral" kind="neutral"
/> */} /> */}
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={<TipDetails transaction={transaction} />} text={<TipDetails transaction={transaction} />}
/> />
</TransactionTemplate> </TransactionTemplate>
@ -604,12 +603,12 @@ export function TransactionView({
</Header> </Header>
<Part <Part
title={<i18n.Translate>Merchant</i18n.Translate>} title={i18n.str`Merchant`}
text={transaction.info.merchant.name} text={transaction.info.merchant.name as TranslatedString}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Original order ID</i18n.Translate>} title={i18n.str`Original order ID`}
text={ text={
<a <a
href={Pages.balanceTransaction({ href={Pages.balanceTransaction({
@ -622,12 +621,12 @@ export function TransactionView({
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Purchase summary</i18n.Translate>} title={i18n.str`Purchase summary`}
text={transaction.info.summary} text={transaction.info.summary as TranslatedString}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={<RefundDetails transaction={transaction} />} text={<RefundDetails transaction={transaction} />}
/> />
</TransactionTemplate> </TransactionTemplate>
@ -683,25 +682,25 @@ export function TransactionView({
{transaction.info.summary ? ( {transaction.info.summary ? (
<Part <Part
title={<i18n.Translate>Subject</i18n.Translate>} title={i18n.str`Subject`}
text={transaction.info.summary} text={transaction.info.summary as TranslatedString}
kind="neutral" kind="neutral"
/> />
) : undefined} ) : undefined}
<Part <Part
title={<i18n.Translate>Exchange</i18n.Translate>} title={i18n.str`Exchange`}
text={transaction.exchangeBaseUrl} text={transaction.exchangeBaseUrl as TranslatedString}
kind="neutral" kind="neutral"
/> />
{transaction.pending /** pending is not-pay */ && ( {transaction.pending /** pending is not-pay */ && (
<Part <Part
title={<i18n.Translate>URI</i18n.Translate>} title={i18n.str`URI`}
text={<ShowQrWithCopy text={transaction.talerUri} />} text={<ShowQrWithCopy text={transaction.talerUri} />}
kind="neutral" kind="neutral"
/> />
)} )}
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<InvoiceDetails <InvoiceDetails
amount={{ amount={{
@ -730,18 +729,18 @@ export function TransactionView({
{transaction.info.summary ? ( {transaction.info.summary ? (
<Part <Part
title={<i18n.Translate>Subject</i18n.Translate>} title={i18n.str`Subject`}
text={transaction.info.summary} text={transaction.info.summary as TranslatedString}
kind="neutral" kind="neutral"
/> />
) : undefined} ) : undefined}
<Part <Part
title={<i18n.Translate>Exchange</i18n.Translate>} title={i18n.str`Exchange`}
text={transaction.exchangeBaseUrl} text={transaction.exchangeBaseUrl as TranslatedString}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<InvoiceDetails <InvoiceDetails
amount={{ amount={{
@ -769,25 +768,25 @@ export function TransactionView({
{transaction.info.summary ? ( {transaction.info.summary ? (
<Part <Part
title={<i18n.Translate>Subject</i18n.Translate>} title={i18n.str`Subject`}
text={transaction.info.summary} text={transaction.info.summary as TranslatedString}
kind="neutral" kind="neutral"
/> />
) : undefined} ) : undefined}
<Part <Part
title={<i18n.Translate>Exchange</i18n.Translate>} title={i18n.str`Exchange`}
text={transaction.exchangeBaseUrl} text={transaction.exchangeBaseUrl as TranslatedString}
kind="neutral" kind="neutral"
/> />
{/* {transaction.pending && ( //pending is not-received {/* {transaction.pending && ( //pending is not-received
)} */} )} */}
<Part <Part
title={<i18n.Translate>URI</i18n.Translate>} title={i18n.str`URI`}
text={<ShowQrWithCopy text={transaction.talerUri} />} text={<ShowQrWithCopy text={transaction.talerUri} />}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<TransferDetails <TransferDetails
amount={{ amount={{
@ -816,18 +815,18 @@ export function TransactionView({
{transaction.info.summary ? ( {transaction.info.summary ? (
<Part <Part
title={<i18n.Translate>Subject</i18n.Translate>} title={i18n.str`Subject`}
text={transaction.info.summary} text={transaction.info.summary as TranslatedString}
kind="neutral" kind="neutral"
/> />
) : undefined} ) : undefined}
<Part <Part
title={<i18n.Translate>Exchange</i18n.Translate>} title={i18n.str`Exchange`}
text={transaction.exchangeBaseUrl} text={transaction.exchangeBaseUrl as TranslatedString}
kind="neutral" kind="neutral"
/> />
<Part <Part
title={<i18n.Translate>Details</i18n.Translate>} title={i18n.str`Details`}
text={ text={
<TransferDetails <TransferDetails
amount={{ amount={{
@ -1245,7 +1244,7 @@ export function PurchaseDetails({
<td colSpan={2}> <td colSpan={2}>
<PartCollapsible <PartCollapsible
big big
title={<i18n.Translate>Products</i18n.Translate>} title={i18n.str`Products`}
text={ text={
<ListOfProducts> <ListOfProducts>
{info.products?.map((p, k) => ( {info.products?.map((p, k) => (
@ -1274,7 +1273,7 @@ export function PurchaseDetails({
<td colSpan={2}> <td colSpan={2}>
<PartCollapsible <PartCollapsible
big big
title={<i18n.Translate>Delivery</i18n.Translate>} title={i18n.str`Delivery`}
text={ text={
<DeliveryDetails <DeliveryDetails
date={info.delivery_date} date={info.delivery_date}
@ -1508,7 +1507,7 @@ function Header({
total: AmountJson; total: AmountJson;
children: ComponentChildren; children: ComponentChildren;
kind: Kind; kind: Kind;
type: string; type: TranslatedString;
}): VNode { }): VNode {
return ( return (
<div <div

Some files were not shown because too many files have changed in this diff Show More