aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-06-03 12:04:31 -0300
committerSebastian <sebasjm@gmail.com>2024-06-03 12:12:24 -0300
commit43857ec597880cbb4d8c6e477fab192b827b962f (patch)
tree07ee840977dd0232e5ecd1af7745f89edf32741c /packages
parent361a6f9d36542a8ce616b33676e96f769ae34d4d (diff)
downloadwallet-core-43857ec597880cbb4d8c6e477fab192b827b962f.tar.xz
fix errors msg and import account letter
Diffstat (limited to 'packages')
-rw-r--r--packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx45
-rw-r--r--packages/merchant-backoffice-ui/src/components/modal/index.tsx89
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx31
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx143
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx39
5 files changed, 233 insertions, 114 deletions
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
index a0c15c77c..3fc4fcbf1 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
@@ -18,13 +18,10 @@
*
* @author Sebastian Javier Marchano (sebasjm)
*/
-import {
- parsePaytoUri,
- PaytoUriGeneric,
- stringifyPaytoUri,
-} from "@gnu-taler/taler-util";
+import { parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
import { COUNTRY_TABLE } from "../../utils/constants.js";
import { undefinedIfEmpty } from "../../utils/table.js";
import { FormErrors, FormProvider } from "./FormProvider.js";
@@ -32,7 +29,6 @@ import { Input } from "./Input.js";
import { InputGroup } from "./InputGroup.js";
import { InputSelector } from "./InputSelector.js";
import { InputProps, useField } from "./useField.js";
-import { useEffect, useState } from "preact/hooks";
export interface Props<T> extends InputProps<T> {
isValid?: (e: any) => boolean;
@@ -108,13 +104,14 @@ function validateEthereum_path1(
* bank.com/path
* bank.com/path/subpath/
*/
-const DOMAIN_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+(\/[a-zA-Z0-9-.]+)*\/?$/
+const DOMAIN_REGEX =
+ /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+(:[0-9]+)?(\/[a-zA-Z0-9-.]+)*\/?$/;
function validateTalerBank_path1(
addr: string,
i18n: ReturnType<typeof useTranslationContext>["i18n"],
): string | undefined {
- console.log(addr, DOMAIN_REGEX.test(addr))
+ console.log(addr, DOMAIN_REGEX.test(addr));
try {
const valid = DOMAIN_REGEX.test(addr);
if (valid) return undefined;
@@ -206,6 +203,7 @@ export function InputPaytoForm<T>({
const { value: initialValueStr, onChange } = useField<T>(name);
const initialPayto = parsePaytoUri(initialValueStr ?? "");
+
const paths = !initialPayto ? [] : initialPayto.targetPath.split("/");
const initialPath1 = paths.length >= 1 ? paths[0] : undefined;
const initialPath2 = paths.length >= 2 ? paths[1] : undefined;
@@ -219,6 +217,18 @@ export function InputPaytoForm<T>({
path2: initialPath2,
};
const [value, setValue] = useState<Partial<Entity>>(initial);
+ useEffect(() => {
+ const nv = parsePaytoUri(initialValueStr ?? "");
+ const paths = !initialPayto ? [] : initialPayto.targetPath.split("/");
+ if (nv !== undefined && nv.isKnown) {
+ setValue({
+ target: nv.targetType,
+ params: nv.params,
+ path1: paths.length >= 1 ? paths[0] : undefined,
+ path2: paths.length >= 2 ? paths[1] : undefined,
+ });
+ }
+ }, [initialValueStr]);
const { i18n } = useTranslationContext();
@@ -252,7 +262,8 @@ export function InputPaytoForm<T>({
(k) => (errors as any)[k] !== undefined,
);
- const path1WithSlash = value.path1 && !value.path1.endsWith("/") ? value.path1 + "/" : value.path1
+ const path1WithSlash =
+ value.path1 && !value.path1.endsWith("/") ? value.path1 + "/" : value.path1;
const str =
hasErrors || !value.target
? undefined
@@ -413,11 +424,17 @@ export function InputPaytoForm<T>({
return v;
}}
tooltip={i18n.str`Bank host.`}
- help={<Fragment>
- <div><i18n.Translate>Without scheme and may include subpath:</i18n.Translate></div>
- <div>bank.com/</div>
- <div>bank.com/path/subpath/</div>
- </Fragment>}
+ help={
+ <Fragment>
+ <div>
+ <i18n.Translate>
+ Without scheme and may include subpath:
+ </i18n.Translate>
+ </div>
+ <div>bank.com/</div>
+ <div>bank.com/path/subpath/</div>
+ </Fragment>
+ }
/>
<Input<Entity>
name="path2"
diff --git a/packages/merchant-backoffice-ui/src/components/modal/index.tsx b/packages/merchant-backoffice-ui/src/components/modal/index.tsx
index 1335d0f77..43062d13e 100644
--- a/packages/merchant-backoffice-ui/src/components/modal/index.tsx
+++ b/packages/merchant-backoffice-ui/src/components/modal/index.tsx
@@ -24,9 +24,14 @@ import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants.js";
import { Spinner } from "../exception/loading.js";
-import { FormProvider } from "../form/FormProvider.js";
+import { FormErrors, FormProvider } from "../form/FormProvider.js";
import { Input } from "../form/Input.js";
import { useSessionContext } from "../../context/session.js";
+import {
+ AccountLetter,
+ codecForAccountLetter,
+ PaytoString,
+} from "@gnu-taler/taler-util";
interface Props {
active?: boolean;
@@ -201,6 +206,88 @@ export function ClearConfirmModal({
);
}
+interface ImportingAccountModalProps {
+ onCancel: () => void;
+ onConfirm: (account: AccountLetter) => void;
+}
+
+export function ImportingAccountModal({
+ onCancel,
+ onConfirm,
+}: ImportingAccountModalProps): VNode {
+ const { i18n } = useTranslationContext();
+ const [letter, setLetter] = useState<string>();
+ let parsed = undefined;
+ try {
+ parsed = JSON.parse(letter ?? "");
+ } catch (e) {
+ parsed = undefined;
+ }
+ let account: AccountLetter | undefined = undefined;
+ let parsingError: string | undefined = undefined;
+ try {
+ account =
+ parsed !== undefined ? codecForAccountLetter().decode(parsed) : undefined;
+ } catch (e) {
+ account = undefined;
+ if (e instanceof Error) {
+ parsingError = e.message;
+ }
+ }
+ const errors: FormErrors<{ letter: string }> = {
+ letter: !letter
+ ? i18n.str`required`
+ : parsed === undefined
+ ? i18n.str`letter should be a JSON string`
+ : account === undefined
+ ? i18n.str`JSON string is invalid`
+ : undefined,
+ };
+ return (
+ <ConfirmModal
+ label={i18n.str`Import`}
+ description={i18n.str`Importing an account from the bank`}
+ active
+ onCancel={onCancel}
+ disabled={account === undefined}
+ onConfirm={() => onConfirm(account!)}
+ >
+ <p>
+ <i18n.Translate>
+ You can export your account settings from the Libeufin Bank's account
+ profile. Paste the content in the next field.
+ </i18n.Translate>
+ </p>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ <i18n.Translate>Account information</i18n.Translate>
+ </label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input
+ class="input"
+ value={letter ?? ""}
+ onChange={(e) => {
+ setLetter(e.currentTarget.value);
+ }}
+ />
+ </p>
+ {letter !== undefined && errors.letter && (
+ <p class="help is-danger">{errors.letter}</p>
+ )}
+ {parsingError !== undefined && (
+ <p class="help is-danger">{parsingError}</p>
+ )}
+ </div>
+ </div>
+ </div>
+ </ConfirmModal>
+ );
+}
+
interface DeleteModalProps {
element: { id: string; name: string };
onCancel: () => void;
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx
index d05375b6c..63ed01565 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx
@@ -31,6 +31,7 @@ import {
import { Input } from "../../../../components/form/Input.js";
import { InputPaytoForm } from "../../../../components/form/InputPaytoForm.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
+import { ImportingAccountModal } from "../../../../components/modal/index.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
import { safeConvertURL } from "../update/UpdatePage.js";
@@ -46,6 +47,7 @@ const accountAuthType = ["none", "basic"];
export function CreatePage({ onCreate, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
+ const [importing, setImporting] = useState(false);
const [state, setState] = useState<Partial<Entity>>({});
const facadeURL = safeConvertURL(state.credit_facade_url);
const errors: FormErrors<Entity> = {
@@ -115,9 +117,26 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
credit_facade_url,
});
};
-
+ // {"accountURI":"payto://x-taler-bank/bank-fir.taler.test:1180/sebasjm?receiver-name=Sebastian%20Javier","infoURL":"http://sebasjm:secret-token%3AM4XNDC6HMD0Z3N3S74G5W64H3PNM4XCYHE0CHQE7DG29GAH2NYMG@bank-fir.taler.test:1180/accounts/sebasjm/taler-revenue/"}
return (
<div>
+ {importing && <ImportingAccountModal onCancel={()=> {setImporting(false)}} onConfirm={(ac) => {
+ state.payto_uri = ac.accountURI
+ const u = new URL(ac.infoURL)
+ u.password = ""
+ if (u.username || u.password) {
+ state.credit_facade_credentials = {
+ type: "basic",
+ password: u.password,
+ username: u.username,
+ }
+ state.repeatPassword = u.password
+ }
+ u.password = ""
+ u.username = ""
+ state.credit_facade_url = u.href;
+ setImporting(false)
+ }} />}
<section class="section is-main-section">
<div class="columns">
<div class="column" />
@@ -171,6 +190,16 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
</FormProvider>
<div class="buttons is-right mt-5">
+ <button
+ class="button is-info"
+ data-tooltip={i18n.str`Need to complete marked fields`}
+ onClick={() => {
+ setImporting(true)
+ }}
+ >
+ <i18n.Translate>Import from bank</i18n.Translate>
+ </button>
+
{onBack && (
<button class="button" onClick={onBack}>
<i18n.Translate>Cancel</i18n.Translate>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx
index 9bab33f6f..fa86271c3 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx
@@ -68,50 +68,45 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
revenueAPI,
request.credit_facade_credentials,
);
+ if (resp instanceof TalerError) {
+ setNotif({
+ message: i18n.str`Could not create account`,
+ type: "ERROR",
+ description: i18n.str`The request to check the revenue API failed.`,
+ details: resp.errorDetail
+ });
+ return;
+ }
if (resp.type === "fail") {
switch (resp.case) {
- case TestRevenueErrorType.NO_CONFIG: {
- setNotif({
- message: i18n.str`Could not create account`,
- type: "ERROR",
- description: i18n.str`The endpoint doesn't seems to be a Taler Revenue API`,
- });
- return;
- }
- case TestRevenueErrorType.CLIENT_BAD_REQUEST: {
+ case HttpStatusCode.BadRequest: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
description: i18n.str`Server replied with "bad request".`,
});
return;
+
}
- case TestRevenueErrorType.UNAUTHORIZED: {
+ case HttpStatusCode.Unauthorized: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
description: i18n.str`Unauthorized, try with another credentials.`,
});
return;
+
}
- case TestRevenueErrorType.NOT_FOUND: {
- setNotif({
- message: i18n.str`Could not create account`,
- type: "ERROR",
- description: i18n.str`Check facade URL, server replied with "not found".`,
- });
- return;
- }
- case TestRevenueErrorType.GENERIC_ERROR: {
+ case HttpStatusCode.NotFound: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
- description: resp.detail.hint,
+ description: i18n.str`The endpoint doesn't seems to be a Taler Revenue API`,
});
return;
}
default: {
- assertUnreachable(resp.case);
+ assertUnreachable(resp);
}
}
}
@@ -146,7 +141,10 @@ export enum TestRevenueErrorType {
export async function testRevenueAPI(
revenueAPI: URL,
creds: FacadeCredentials | undefined,
-): Promise<OperationOk<void> | OperationFail<TestRevenueErrorType>> {
+): Promise<OperationOk<void> | OperationFail<HttpStatusCode.NotFound>
+| OperationFail<HttpStatusCode.Unauthorized>
+| OperationFail<HttpStatusCode.BadRequest>
+| TalerError> {
const api = new TalerRevenueHttpClient(
revenueAPI.href,
new BrowserFetchHttpLib(),
@@ -167,68 +165,61 @@ export async function testRevenueAPI(
const config = await api.getConfig(auth);
if (config.type === "fail") {
- switch (config.case) {
- case HttpStatusCode.Unauthorized: {
- return {
- type: "fail",
- case: TestRevenueErrorType.UNAUTHORIZED,
- detail: {
- code: 1,
- },
- };
- }
- case HttpStatusCode.NotFound: {
- return {
- type: "fail",
- case: TestRevenueErrorType.NO_CONFIG,
- detail: {
- code: 1,
- },
- };
- }
- }
+ return config;
+ // switch (config.case) {
+ // case HttpStatusCode.Unauthorized: {
+ // return {
+ // type: "fail",
+ // case: TestRevenueErrorType.UNAUTHORIZED,
+ // detail: config.detail,
+ // };
+ // }
+ // case HttpStatusCode.NotFound: {
+ // return {
+ // type: "fail",
+ // case: TestRevenueErrorType.NO_CONFIG,
+ // detail: config.detail,
+ // };
+ // }
+ // }
}
const history = await api.getHistory(auth);
if (history.type === "fail") {
- switch (history.case) {
- case HttpStatusCode.BadRequest: {
- return {
- type: "fail",
- case: TestRevenueErrorType.CLIENT_BAD_REQUEST,
- detail: {
- code: 1,
- },
- };
- }
- case HttpStatusCode.Unauthorized: {
- return {
- type: "fail",
- case: TestRevenueErrorType.UNAUTHORIZED,
- detail: {
- code: 1,
- },
- };
- }
- case HttpStatusCode.NotFound: {
- return {
- type: "fail",
- case: TestRevenueErrorType.NOT_FOUND,
- detail: {
- code: 1,
- },
- };
- }
- }
+ return history;
+ // switch (history.case) {
+ // case HttpStatusCode.BadRequest: {
+ // return {
+ // type: "fail",
+ // case: TestRevenueErrorType.CLIENT_BAD_REQUEST,
+ // detail: history.detail,
+ // };
+ // }
+ // case HttpStatusCode.Unauthorized: {
+ // return {
+ // type: "fail",
+ // case: TestRevenueErrorType.UNAUTHORIZED,
+ // detail: history.detail,
+ // };
+ // }
+ // case HttpStatusCode.NotFound: {
+ // return {
+ // type: "fail",
+ // case: TestRevenueErrorType.NOT_FOUND,
+ // detail: history.detail,
+ // };
+ // }
+ // }
}
} catch (err) {
if (err instanceof TalerError) {
- return {
- type: "fail",
- case: TestRevenueErrorType.GENERIC_ERROR,
- detail: err.errorDetail,
- };
+ return err;
+ // return {
+ // type: "fail",
+ // case: TestRevenueErrorType.GENERIC_ERROR,
+ // detail: err.errorDetail,
+ // };
}
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx
index 70942fd55..d1a0c9ba4 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx
@@ -33,7 +33,7 @@ import { useBankAccountDetails } from "../../../../hooks/bank.js";
import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
-import { TestRevenueErrorType, testRevenueAPI } from "../create/index.js";
+import { testRevenueAPI } from "../create/index.js";
import { UpdatePage } from "./UpdatePage.js";
export type Entity = TalerMerchantApi.AccountPatchDetails & WithId;
@@ -89,50 +89,45 @@ export default function UpdateValidator({
revenueAPI,
request.credit_facade_credentials,
);
+ if (resp instanceof TalerError) {
+ setNotif({
+ message: i18n.str`Could not create account`,
+ type: "ERROR",
+ description: i18n.str`The request to check the revenue API failed.`,
+ details: resp.errorDetail
+ });
+ return;
+ }
if (resp.type === "fail") {
switch (resp.case) {
- case TestRevenueErrorType.NO_CONFIG: {
- setNotif({
- message: i18n.str`Could not create account`,
- type: "ERROR",
- description: i18n.str`The endpoint doesn't seems to be a Taler Revenue API`,
- });
- return;
- }
- case TestRevenueErrorType.CLIENT_BAD_REQUEST: {
+ case HttpStatusCode.BadRequest: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
description: i18n.str`Server replied with "bad request".`,
});
return;
+
}
- case TestRevenueErrorType.UNAUTHORIZED: {
+ case HttpStatusCode.Unauthorized: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
description: i18n.str`Unauthorized, try with another credentials.`,
});
return;
+
}
- case TestRevenueErrorType.NOT_FOUND: {
- setNotif({
- message: i18n.str`Could not create account`,
- type: "ERROR",
- description: i18n.str`Check facade URL, server replied with "not found".`,
- });
- return;
- }
- case TestRevenueErrorType.GENERIC_ERROR: {
+ case HttpStatusCode.NotFound: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
- description: resp.detail.hint,
+ description: i18n.str`The endpoint doesn't seems to be a Taler Revenue API`,
});
return;
}
default: {
- assertUnreachable(resp.case)
+ assertUnreachable(resp);
}
}
}