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);
useEffect(() => {
const id = setTimeout(() => {
setTooLong(true);
}, 500);
return () => {
clearTimeout(id);
};
});
if (tooLong) {
return ( return (
<section style={{ margin: "auto" }}> <section style={{ margin: "auto" }}>
<CenteredText> <CenteredText class={fadeIn}>
<i18n.Translate>Loading</i18n.Translate>... <i18n.Translate>Loading</i18n.Translate>...
</CenteredText> </CenteredText>
{/* <div class={ripple} style={{ "--size": "250px" }}>
<div></div>
<div></div>
</div> */}
<div class={fadeIn} dangerouslySetInnerHTML={{ __html: ProgressIcon }} />
</section> </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;
@ -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

@ -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,7 +306,6 @@ function openWalletPageFromPopup(page: string): void {
}); });
} }
let nextMessageIndex = 0; let nextMessageIndex = 0;
/** /**
@ -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 };
@ -597,7 +595,7 @@ interface OffscreenCanvasRenderingContext2D
} }
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>
<AlertProvider>
<CurrentAlerts />
{children} {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