some fixes afte testing demobank with ms

This commit is contained in:
Sebastian 2023-02-25 19:43:45 -03:00
parent dd2599ff94
commit 1723f16b9c
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
12 changed files with 462 additions and 159 deletions

View File

@ -33,7 +33,7 @@ const useSWR = _useSWR as unknown as SWRHook;
export function useAdminAccountAPI(): AdminAccountAPI {
const { request } = useAuthenticatedBackend();
const mutateAll = useMatchMutate();
const { state } = useBackendContext();
const { state, logIn } = useBackendContext();
if (state.status === "loggedOut") {
throw Error("access-api can't be used when the user is not logged In");
}
@ -81,6 +81,13 @@ export function useAdminAccountAPI(): AdminAccountAPI {
data,
contentType: "json",
});
if (account === state.username) {
await mutateAll(/.*/)
logIn({
username: account,
password: data.new_password
})
}
return res;
};
@ -288,6 +295,12 @@ export function useRatiosAndFeeConfig(): HttpResponse<
keepPreviousData: true,
});
if (data) {
// data.data.ratios_and_fees.sell_out_fee = 2
if (!data.data.ratios_and_fees.fiat_currency) {
data.data.ratios_and_fees.fiat_currency = "FIAT"
}
}
if (data) return data;
if (error) return error.info;
return { loading: true };

View File

@ -28,7 +28,9 @@ import { PaymentOptions } from "./PaymentOptions.js";
interface Props {
account: string;
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
}
/**
* Query account information and show QR code if there is pending withdrawal

View File

@ -59,7 +59,9 @@ function randomPassword(): string {
}
interface Props {
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
}
/**
* Query account information and show QR code if there is pending withdrawal
@ -109,6 +111,11 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
if (showCashouts) {
return (
<div>
<div>
<h1 class="nav welcome-text">
<i18n.Translate>Cashout for account {showCashouts}</i18n.Translate>
</h1>
</div>
<Cashouts
account={showCashouts}
onSelected={(id) => {
@ -116,15 +123,17 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
setShowCashouts(undefined);
}}
/>
<input
class="pure-button"
type="submit"
value={i18n.str`Close`}
onClick={async (e) => {
e.preventDefault();
setShowCashouts(undefined);
}}
/>
<p>
<input
class="pure-button"
type="submit"
value={i18n.str`Close`}
onClick={async (e) => {
e.preventDefault();
setShowCashouts(undefined);
}}
/>
</p>
</div>
);
}
@ -184,7 +193,7 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
onClose={() => setCreateAccount(false)}
onCreateSuccess={(password) => {
showInfoMessage(
i18n.str`Account created with password "${password}"`,
i18n.str`Account created with password "${password}". The user must change the password on the next login.`,
);
setCreateAccount(false);
}}
@ -326,7 +335,9 @@ export function UpdateAccountPassword({
onUpdateSuccess,
onLoadNotOk,
}: {
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
onClear: () => void;
onUpdateSuccess: () => void;
account: string;
@ -521,7 +532,9 @@ export function ShowAccountDetails({
onLoadNotOk,
onChangePassword,
}: {
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
onClear?: () => void;
onChangePassword: () => void;
onUpdateSuccess: () => void;
@ -628,7 +641,9 @@ function RemoveAccount({
onUpdateSuccess,
onLoadNotOk,
}: {
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
onClear: () => void;
onUpdateSuccess: () => void;
account: string;
@ -806,7 +821,7 @@ function AccountForm({
/>
</fieldset>
<fieldset>
<label>{i18n.str`IBAN`}</label>
<label>{i18n.str`Internal IBAN`}</label>
<input
disabled={purpose !== "create"}
value={form.iban ?? ""}

View File

@ -20,6 +20,7 @@ import {
TranslatedString,
} from "@gnu-taler/taler-util";
import {
ErrorType,
HttpResponsePaginated,
RequestError,
useTranslationContext,
@ -44,7 +45,9 @@ import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
interface Props {
onClose: () => void;
onRegister: () => void;
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
}
export function BusinessAccount({
onClose,
@ -79,6 +82,9 @@ export function BusinessAccount({
setNewcashout(false);
}}
onComplete={(id) => {
showInfoMessage(
i18n.str`Cashout created. You need to confirm the operation to complete the transaction.`,
);
setNewcashout(false);
setShowCashoutDetails(id);
}}
@ -156,7 +162,9 @@ interface PropsCashout {
account: string;
onComplete: (id: string) => void;
onCancel: () => void;
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
}
type FormType = {
@ -180,7 +188,7 @@ function CreateCashout({
const result = useAccountDetails(account);
const [error, saveError] = useState<ErrorMessage | undefined>();
const [form, setForm] = useState<Partial<FormType>>({});
const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
const { createCashout } = useCircuitAccountAPI();
if (!result.ok) return onLoadNotOk(result);
@ -277,10 +285,14 @@ function CreateCashout({
type="text"
readonly
class="currency-indicator"
size={balance.currency.length}
maxLength={balance.currency.length}
size={
!form.isDebit ? fiatCurrency.length : balance.currency.length
}
maxLength={
!form.isDebit ? fiatCurrency.length : balance.currency.length
}
tabIndex={-1}
value={balance.currency}
value={!form.isDebit ? fiatCurrency : balance.currency}
/>
&nbsp;
<input
@ -389,16 +401,16 @@ function CreateCashout({
{Amounts.isZero(sellFee) ? undefined : (
<Fragment>
<fieldset>
<label>{i18n.str`Transfer before fee`}</label>
<label>{i18n.str`Amount after conversion`}</label>
<div style={{ width: "max-content" }}>
<input
type="text"
readonly
class="currency-indicator"
size={balance.currency.length}
maxLength={balance.currency.length}
size={fiatCurrency.length}
maxLength={fiatCurrency.length}
tabIndex={-1}
value={balance.currency}
value={fiatCurrency}
/>
&nbsp;
<input
@ -417,10 +429,10 @@ function CreateCashout({
type="text"
readonly
class="currency-indicator"
size={balance.currency.length}
maxLength={balance.currency.length}
size={fiatCurrency.length}
maxLength={fiatCurrency.length}
tabIndex={-1}
value={balance.currency}
value={fiatCurrency}
/>
&nbsp;
<input
@ -442,10 +454,10 @@ function CreateCashout({
type="text"
readonly
class="currency-indicator"
size={balance.currency.length}
maxLength={balance.currency.length}
size={fiatCurrency.length}
maxLength={fiatCurrency.length}
tabIndex={-1}
value={balance.currency}
value={fiatCurrency}
/>
&nbsp;
<input
@ -543,34 +555,69 @@ function CreateCashout({
onComplete(res.data.uuid);
} catch (error) {
if (error instanceof RequestError) {
const errorData: SandboxBackend.SandboxError =
error.info.error;
if (error.info.status === HttpStatusCode.PreconditionFailed) {
saveError({
title: i18n.str`The account does not have sufficient funds`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
} else if (
error.info.status === HttpStatusCode.ServiceUnavailable
) {
saveError({
title: i18n.str`The bank does not support the TAN channel for this operation`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
} else if (error.info.status === HttpStatusCode.Conflict) {
saveError({
title: i18n.str`No contact information for this channel`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
} else {
saveError({
title: i18n.str`New cashout gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
const e = error as RequestError<SandboxBackend.SandboxError>;
switch (e.cause.type) {
case ErrorType.TIMEOUT: {
saveError({
title: i18n.str`Request timeout, try again later.`,
});
break;
}
case ErrorType.CLIENT: {
const errorData = e.cause.error;
if (
e.cause.status === HttpStatusCode.PreconditionFailed
) {
saveError({
title: i18n.str`The account does not have sufficient funds`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
} else if (e.cause.status === HttpStatusCode.Conflict) {
saveError({
title: i18n.str`No contact information for this channel`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
} else {
saveError({
title: i18n.str`New cashout gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
}
break;
}
case ErrorType.SERVER: {
const errorData = e.cause.error;
if (
e.cause.status === HttpStatusCode.ServiceUnavailable
) {
saveError({
title: i18n.str`The bank does not support the TAN channel for this operation`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
} else {
saveError({
title: i18n.str`Creating cashout returned with a server error`,
description: errorData.error.description,
debug: JSON.stringify(error.cause),
});
}
break;
}
case ErrorType.UNEXPECTED: {
saveError({
title: i18n.str`Unexpected error trying to create cashout.`,
debug: JSON.stringify(error.cause),
});
break;
}
default: {
assertUnreachable(e.cause);
}
}
} else if (error instanceof Error) {
saveError({
@ -592,7 +639,9 @@ function CreateCashout({
interface ShowCashoutProps {
id: string;
onCancel: () => void;
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
}
export function ShowCashoutDetails({
id,
@ -699,22 +748,59 @@ export function ShowCashoutDetails({
onCancel();
} catch (error) {
if (error instanceof RequestError) {
const errorData: SandboxBackend.SandboxError =
error.info.error;
if (
error.info.status === HttpStatusCode.PreconditionFailed
) {
saveError({
title: i18n.str`Cashout was already aborted`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
} else {
saveError({
title: i18n.str`Aborting cashout gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
const e =
error as RequestError<SandboxBackend.SandboxError>;
switch (e.cause.type) {
case ErrorType.TIMEOUT: {
saveError({
title: i18n.str`Request timeout, try again later.`,
});
break;
}
case ErrorType.CLIENT: {
const errorData = e.cause.error;
if (
e.cause.status === HttpStatusCode.PreconditionFailed
) {
saveError({
title: i18n.str`Cashout was already aborted`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
} else {
saveError({
title: i18n.str`Aborting cashout gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
}
saveError({
title: i18n.str`Aborting cashout gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.cause),
});
break;
}
case ErrorType.SERVER: {
const errorData = e.cause.error;
saveError({
title: i18n.str`Aborting cashout returned with a server error`,
description: errorData.error.description,
debug: JSON.stringify(error.cause),
});
break;
}
case ErrorType.UNEXPECTED: {
saveError({
title: i18n.str`Unexpected error trying to abort cashout.`,
debug: JSON.stringify(error.cause),
});
break;
}
default: {
assertUnreachable(e.cause);
}
}
} else if (error instanceof Error) {
saveError({
@ -741,13 +827,44 @@ export function ShowCashoutDetails({
});
} catch (error) {
if (error instanceof RequestError) {
const errorData: SandboxBackend.SandboxError =
error.info.error;
saveError({
title: i18n.str`Confirmation of cashout gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
const e =
error as RequestError<SandboxBackend.SandboxError>;
switch (e.cause.type) {
case ErrorType.TIMEOUT: {
saveError({
title: i18n.str`Request timeout, try again later.`,
});
break;
}
case ErrorType.CLIENT: {
const errorData = e.cause.error;
saveError({
title: i18n.str`Confirmation of cashout gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.cause),
});
break;
}
case ErrorType.SERVER: {
const errorData = e.cause.error;
saveError({
title: i18n.str`Confirmation of cashout gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.cause),
});
break;
}
case ErrorType.UNEXPECTED: {
saveError({
title: i18n.str`Unexpected error trying to cashout.`,
debug: JSON.stringify(error.cause),
});
break;
}
default: {
assertUnreachable(e.cause);
}
}
} else if (error instanceof Error) {
saveError({
title: i18n.str`Confirmation failed, please report`,
@ -767,3 +884,7 @@ export function ShowCashoutDetails({
</div>
);
}
export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}

View File

@ -14,9 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { Logger } from "@gnu-taler/taler-util";
import { HttpStatusCode, Logger } from "@gnu-taler/taler-util";
import {
ErrorType,
HttpResponsePaginated,
RequestError,
useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact";
@ -119,9 +121,9 @@ function handleNotOkResult(
onErrorHandler: (state: PageStateType["error"]) => void,
i18n: ReturnType<typeof useTranslationContext>["i18n"],
onRegister: () => void,
): <T, E>(result: HttpResponsePaginated<T, E>) => VNode {
return function handleNotOkResult2<T, E>(
result: HttpResponsePaginated<T, E>,
): <T>(result: HttpResponsePaginated<T, SandboxBackend.SandboxError>) => VNode {
return function handleNotOkResult2<T>(
result: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
): VNode {
if (result.clientError && result.isUnauthorized) {
onErrorHandler({
@ -137,13 +139,49 @@ function handleNotOkResult(
}
if (result.loading) return <Loading />;
if (!result.ok) {
onErrorHandler({
title: i18n.str`The backend reported a problem: HTTP status #${result.status}`,
description: `Diagnostic from ${result.info?.url} is "${result.message}"`,
debug: JSON.stringify(result.error),
});
switch (result.type) {
case ErrorType.TIMEOUT: {
onErrorHandler({
title: i18n.str`Request timeout, try again later.`,
});
break;
}
case ErrorType.CLIENT: {
const errorData = result.error;
onErrorHandler({
title: i18n.str`Could not load due to a client error`,
description: errorData.error.description,
debug: JSON.stringify(result),
});
break;
}
case ErrorType.SERVER: {
const errorData = result.error;
onErrorHandler({
title: i18n.str`Server returned with error`,
description: errorData.error.description,
debug: JSON.stringify(result),
});
break;
}
case ErrorType.UNEXPECTED: {
onErrorHandler({
title: i18n.str`Unexpected error.`,
description: `Diagnostic from ${result.info?.url} is "${result.message}"`,
debug: JSON.stringify(result.error),
});
break;
}
default: {
assertUnreachable(result);
}
}
return <LoginForm onRegister={onRegister} />;
}
return <div />;
};
}
export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}

View File

@ -48,7 +48,7 @@ export function PaytoWireTransferForm({
onSuccess: () => void;
currency: string;
}): VNode {
const backend = useBackendContext();
// const backend = useBackendContext();
// const { pageState, pageStateSetter } = usePageContext(); // NOTE: used for go-back button?
const [isRawPayto, setIsRawPayto] = useState(false);
@ -188,17 +188,7 @@ export function PaytoWireTransferForm({
paytoUri,
amount: `${currency}:${amount}`,
});
// return await createTransactionCall(
// transactionData,
// backend.state,
// pageStateSetter,
// () => {
// setAmount(undefined);
// setIban(undefined);
// setSubject(undefined);
// },
// i18n,
// );
onSuccess();
}}
/>
<input

View File

@ -36,7 +36,9 @@ const logger = new Logger("PublicHistoriesPage");
// }
interface Props {
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
}
/**

View File

@ -15,6 +15,7 @@
*/
import { HttpStatusCode, Logger } from "@gnu-taler/taler-util";
import {
ErrorType,
RequestError,
useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
@ -176,26 +177,52 @@ function RegistrationForm({
onComplete();
} catch (error) {
if (error instanceof RequestError) {
const errorData: SandboxBackend.SandboxError =
error.info.error;
if (error.info.status === HttpStatusCode.Conflict) {
onError({
title: i18n.str`That username is already taken`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
} else {
onError({
title: i18n.str`New registration gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.info),
});
const e =
error as RequestError<SandboxBackend.SandboxError>;
switch (e.cause.type) {
case ErrorType.TIMEOUT: {
onError({
title: i18n.str`Request timeout, try again later.`,
});
break;
}
case ErrorType.CLIENT: {
const errorData = e.cause.error;
if (e.cause.status === HttpStatusCode.Conflict) {
onError({
title: i18n.str`That username is already taken`,
description: errorData.error.description,
debug: JSON.stringify(error.cause),
});
} else {
onError({
title: i18n.str`New registration gave response error`,
description: errorData.error.description,
debug: JSON.stringify(error.cause),
});
}
break;
}
case ErrorType.SERVER: {
const errorData = e.cause.error;
onError({
title: i18n.str`New registration gave response error`,
description: errorData?.error?.description,
debug: JSON.stringify(error.cause),
});
break;
}
case ErrorType.UNEXPECTED: {
onError({
title: i18n.str`Unexpected error doing the registration.`,
debug: JSON.stringify(error.cause),
});
break;
}
default: {
assertUnreachable(e.cause);
}
}
} else if (error instanceof Error) {
onError({
title: i18n.str`Registration failed, please report`,
description: error.message,
});
}
}
}}
@ -222,3 +249,7 @@ function RegistrationForm({
</Fragment>
);
}
export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}

View File

@ -15,6 +15,7 @@
*/
import {
ErrorType,
HttpResponsePaginated,
useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
@ -34,9 +35,9 @@ function handleNotOkResult(
safe: string,
saveError: (state: PageStateType["error"]) => void,
i18n: ReturnType<typeof useTranslationContext>["i18n"],
): <T, E>(result: HttpResponsePaginated<T, E>) => VNode {
return function handleNotOkResult2<T, E>(
result: HttpResponsePaginated<T, E>,
): <T>(result: HttpResponsePaginated<T, SandboxBackend.SandboxError>) => VNode {
return function handleNotOkResult2<T>(
result: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
): VNode {
if (result.clientError && result.isUnauthorized) {
route(safe);
@ -50,12 +51,45 @@ function handleNotOkResult(
}
if (result.loading) return <Loading />;
if (!result.ok) {
saveError({
title: i18n.str`The backend reported a problem: HTTP status #${result.status}`,
description: i18n.str`Diagnostic from ${result.info?.url} is "${result.message}"`,
debug: JSON.stringify(result.error),
});
route(safe);
switch (result.type) {
case ErrorType.TIMEOUT: {
saveError({
title: i18n.str`Request timeout, try again later.`,
});
break;
}
case ErrorType.CLIENT: {
const errorData = result.error;
saveError({
title: i18n.str`Could not load due to a client error`,
description: errorData.error.description,
debug: JSON.stringify(result),
});
break;
}
case ErrorType.SERVER: {
const errorData = result.error;
saveError({
title: i18n.str`Server returned with error`,
description: errorData.error.description,
debug: JSON.stringify(result),
});
break;
}
case ErrorType.UNEXPECTED: {
saveError({
title: i18n.str`Unexpected error.`,
description: `Diagnostic from ${result.info?.url} is "${result.message}"`,
debug: JSON.stringify(result.error),
});
break;
}
default:
{
assertUnreachable(result);
}
route(safe);
}
}
return <div />;
};
@ -137,3 +171,7 @@ function Redirect({ to }: { to: string }): VNode {
}, []);
return <div>being redirected to {to}</div>;
}
export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}

View File

@ -33,7 +33,9 @@ interface Props {
withdrawalId: string;
talerWithdrawUri: string;
onAbort: () => void;
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
onLoadNotOk: <T>(
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
}
/**
* Offer the QR code (and a clickable taler://-link) to

View File

@ -22,6 +22,7 @@
import {
useTranslationContext,
HttpError,
ErrorType,
} from "@gnu-taler/web-util/lib/index.browser";
import { format } from "date-fns";
import { Fragment, FunctionComponent, h, VNode } from "preact";
@ -163,16 +164,25 @@ export function InstanceRoutes({
return function ServerErrorRedirectToImpl(
error: HttpError<MerchantBackend.ErrorDetail>,
) {
setGlobalNotification({
message: i18n.str`The backend reported a problem: HTTP status #${error.status}`,
description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
details:
error.clientError || error.serverError
? error.error?.detail
: undefined,
type: "ERROR",
to,
});
if (error.type === ErrorType.TIMEOUT) {
setGlobalNotification({
message: i18n.str`The request to the backend take too long and was cancelled`,
description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
type: "ERROR",
to,
});
} else {
setGlobalNotification({
message: i18n.str`The backend reported a problem: HTTP status #${error.status}`,
description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
details:
error.clientError || error.serverError
? error.error?.detail
: undefined,
type: "ERROR",
to,
});
}
return <Redirect to={to} />;
};
}
@ -572,19 +582,25 @@ function AdminInstanceUpdatePage({
{...rest}
instanceId={id}
onLoadError={(error: HttpError<MerchantBackend.ErrorDetail>) => {
return (
<Fragment>
<NotificationCard
notification={{
const notif =
error.type === ErrorType.TIMEOUT
? {
message: i18n.str`The request to the backend take too long and was cancelled`,
description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
type: "ERROR" as const,
}
: {
message: i18n.str`The backend reported a problem: HTTP status #${error.status}`,
description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
details:
error.clientError || error.serverError
? error.error?.detail
: undefined,
type: "ERROR",
}}
/>
type: "ERROR" as const,
};
return (
<Fragment>
<NotificationCard notification={notif} />
<LoginPage onConfirm={updateLoginStatus} />
</Fragment>
);

View File

@ -17,6 +17,10 @@
import { HttpStatusCode } from "@gnu-taler/taler-util";
import { base64encode } from "./base64.js";
export enum ErrorType {
CLIENT, SERVER, TIMEOUT, UNEXPECTED
}
/**
*
* @param baseUrl URL where the service is located
@ -39,7 +43,7 @@ export async function defaultRequestHandler<T>(
const requestMethod = options?.method ?? "GET";
const requestBody = options?.data;
const requestTimeout = options?.timeout ?? 2 * 1000;
const requestTimeout = options?.timeout ?? 5 * 1000;
const requestParams = options.params ?? {};
const _url = new URL(`${baseUrl}${endpoint}`);
@ -85,10 +89,13 @@ export async function defaultRequestHandler<T>(
hasToken: !!options.token,
status: 0,
};
const error: HttpResponseUnexpectedError = {
const error: HttpRequestTimeoutError = {
clientError: true,
isNotfound: false,
isUnauthorized: false,
error: undefined,
info,
status: 0,
error: ex,
type: ErrorType.TIMEOUT,
message: "Request timeout",
};
throw new RequestError(error);
@ -166,32 +173,50 @@ export interface WithPagination {
}
export type HttpError<ErrorDetail> =
| HttpRequestTimeoutError
| HttpResponseClientError<ErrorDetail>
| HttpResponseServerError<ErrorDetail>
| HttpResponseUnexpectedError;
export interface HttpResponseServerError<ErrorDetail> {
ok?: false;
loading?: false;
clientError?: false;
serverError: true;
type: ErrorType.SERVER,
error?: ErrorDetail;
error: ErrorDetail;
status: HttpStatusCode;
message: string;
info?: RequestInfo;
}
interface HttpRequestTimeoutError {
ok?: false;
loading?: false;
clientError: true;
serverError?: false;
type: ErrorType.TIMEOUT,
info?: RequestInfo;
error: undefined,
isUnauthorized: false;
isNotfound: false;
message: string;
}
interface HttpResponseClientError<ErrorDetail> {
ok?: false;
loading?: false;
clientError: true;
serverError?: false;
type: ErrorType.CLIENT,
info?: RequestInfo;
isUnauthorized: boolean;
isNotfound: boolean;
status: HttpStatusCode;
error?: ErrorDetail;
error: ErrorDetail;
message: string;
}
@ -200,6 +225,7 @@ interface HttpResponseUnexpectedError {
loading?: false;
clientError?: false;
serverError?: false;
type: ErrorType.UNEXPECTED,
info?: RequestInfo;
status?: HttpStatusCode;
@ -208,10 +234,15 @@ interface HttpResponseUnexpectedError {
}
export class RequestError<ErrorDetail> extends Error {
/**
* @deprecated use cause
*/
info: HttpError<ErrorDetail>;
cause: HttpError<ErrorDetail>;
constructor(d: HttpError<ErrorDetail>) {
super(d.message)
this.info = d
this.cause = d
}
}
@ -277,6 +308,7 @@ async function buildRequestFailed<ErrorDetail>(
clientError: true,
isNotfound: status === 404,
isUnauthorized: status === 401,
type: ErrorType.CLIENT,
status,
info,
message: data?.hint,
@ -287,6 +319,7 @@ async function buildRequestFailed<ErrorDetail>(
if (status && status >= 500 && status < 600) {
const error: HttpResponseServerError<ErrorDetail> = {
serverError: true,
type: ErrorType.SERVER,
status,
info,
message: `${data?.hint} (code ${data?.code})`,
@ -296,6 +329,7 @@ async function buildRequestFailed<ErrorDetail>(
}
return {
info,
type: ErrorType.UNEXPECTED,
status,
error: {},
message: "NOT DEFINED",
@ -304,6 +338,7 @@ async function buildRequestFailed<ErrorDetail>(
const error: HttpResponseUnexpectedError = {
info,
status,
type: ErrorType.UNEXPECTED,
error: ex,
message: "NOT DEFINED",
};