some fixes afte testing demobank with ms
This commit is contained in:
parent
dd2599ff94
commit
1723f16b9c
@ -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 };
|
||||
|
@ -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
|
||||
|
@ -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,6 +123,7 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
|
||||
setShowCashouts(undefined);
|
||||
}}
|
||||
/>
|
||||
<p>
|
||||
<input
|
||||
class="pure-button"
|
||||
type="submit"
|
||||
@ -125,6 +133,7 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
|
||||
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 ?? ""}
|
||||
|
@ -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}
|
||||
/>
|
||||
|
||||
<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}
|
||||
/>
|
||||
|
||||
<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}
|
||||
/>
|
||||
|
||||
<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}
|
||||
/>
|
||||
|
||||
<input
|
||||
@ -543,23 +555,26 @@ 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) {
|
||||
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 (
|
||||
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) {
|
||||
} else if (e.cause.status === HttpStatusCode.Conflict) {
|
||||
saveError({
|
||||
title: i18n.str`No contact information for this channel`,
|
||||
description: errorData.error.description,
|
||||
@ -572,6 +587,38 @@ function CreateCashout({
|
||||
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({
|
||||
title: i18n.str`Cashout failed, please report`,
|
||||
@ -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,10 +748,19 @@ export function ShowCashoutDetails({
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
if (error instanceof RequestError) {
|
||||
const errorData: SandboxBackend.SandboxError =
|
||||
error.info.error;
|
||||
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 (
|
||||
error.info.status === HttpStatusCode.PreconditionFailed
|
||||
e.cause.status === HttpStatusCode.PreconditionFailed
|
||||
) {
|
||||
saveError({
|
||||
title: i18n.str`Cashout was already aborted`,
|
||||
@ -716,6 +774,34 @@ export function ShowCashoutDetails({
|
||||
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({
|
||||
title: i18n.str`Aborting failed, please report`,
|
||||
@ -741,13 +827,44 @@ export function ShowCashoutDetails({
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof RequestError) {
|
||||
const errorData: SandboxBackend.SandboxError =
|
||||
error.info.error;
|
||||
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.info),
|
||||
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");
|
||||
}
|
||||
|
@ -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) {
|
||||
switch (result.type) {
|
||||
case ErrorType.TIMEOUT: {
|
||||
onErrorHandler({
|
||||
title: i18n.str`The backend reported a problem: HTTP status #${result.status}`,
|
||||
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");
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
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.info),
|
||||
debug: JSON.stringify(error.cause),
|
||||
});
|
||||
} else {
|
||||
onError({
|
||||
title: i18n.str`New registration gave response error`,
|
||||
description: errorData.error.description,
|
||||
debug: JSON.stringify(error.info),
|
||||
debug: JSON.stringify(error.cause),
|
||||
});
|
||||
}
|
||||
} else if (error instanceof Error) {
|
||||
break;
|
||||
}
|
||||
case ErrorType.SERVER: {
|
||||
const errorData = e.cause.error;
|
||||
onError({
|
||||
title: i18n.str`Registration failed, please report`,
|
||||
description: error.message,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
@ -222,3 +249,7 @@ function RegistrationForm({
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export function assertUnreachable(x: never): never {
|
||||
throw new Error("Didn't expect to get here");
|
||||
}
|
||||
|
@ -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,13 +51,46 @@ function handleNotOkResult(
|
||||
}
|
||||
if (result.loading) return <Loading />;
|
||||
if (!result.ok) {
|
||||
switch (result.type) {
|
||||
case ErrorType.TIMEOUT: {
|
||||
saveError({
|
||||
title: i18n.str`The backend reported a problem: HTTP status #${result.status}`,
|
||||
description: i18n.str`Diagnostic from ${result.info?.url} is "${result.message}"`,
|
||||
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");
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,6 +164,14 @@ export function InstanceRoutes({
|
||||
return function ServerErrorRedirectToImpl(
|
||||
error: HttpError<MerchantBackend.ErrorDetail>,
|
||||
) {
|
||||
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}"`,
|
||||
@ -173,6 +182,7 @@ export function InstanceRoutes({
|
||||
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>
|
||||
);
|
||||
|
@ -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",
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user