commit f86ecf35c1fe9a5a94d17d1af10946cd24762f92
parent a7b347c956727d82c119dd3fa4d7ef8ec27b142e
Author: Sebastian <sebasjm@gmail.com>
Date: Wed, 31 Jul 2024 14:19:59 -0300
fix #8946
Diffstat:
1 file changed, 73 insertions(+), 79 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx
@@ -25,18 +25,19 @@ import {
createRFC8959AccessTokenPlain,
} from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { VNode, h } from "preact";
+import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../../components/exception/AsyncButton.js";
import {
FormErrors,
FormProvider,
} from "../../../components/form/FormProvider.js";
+import { Input } from "../../../components/form/Input.js";
import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js";
-import { SetTokenNewInstanceModal } from "../../../components/modal/index.js";
import { usePreference } from "../../../hooks/preference.js";
import { INSTANCE_ID_REGEX } from "../../../utils/constants.js";
import { undefinedIfEmpty } from "../../../utils/table.js";
+import { InputToggle } from "../../../components/form/InputToggle.js";
export type Entity = TalerMerchantApi.InstanceConfigurationMessage & {
auth_token?: string;
@@ -63,15 +64,15 @@ function with_defaults(id?: string): Partial<Entity> {
};
}
+type TokenForm = { accessControl: boolean; token: string; repeat: string };
+
export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
const [pref, updatePref] = usePreference();
const { i18n } = useTranslationContext();
const [value, valueHandler] = useState(with_defaults(forceId));
- const [isTokenSet, updateIsTokenSet] = useState<boolean>(false);
- const [isTokenDialogActive, updateIsTokenDialogActive] =
- useState<boolean>(false);
+ const [tokenForm, setTokenForm] = useState<Partial<TokenForm>>({});
- const errors: FormErrors<Entity> = {
+ const errors = undefinedIfEmpty<FormErrors<Entity>>({
id: !value.id
? i18n.str`Required`
: !INSTANCE_ID_REGEX.test(value.id)
@@ -117,62 +118,49 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
? i18n.str`Max 7 lines`
: undefined,
}),
- };
+ });
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as Record<string, unknown>)[k] !== undefined,
- );
+ const hasErrors = errors !== undefined;
+
+ const tokenFormErrors = undefinedIfEmpty<FormErrors<TokenForm>>({
+ token:
+ tokenForm.accessControl === false
+ ? undefined
+ : !tokenForm.token
+ ? i18n.str`Required`
+ : undefined,
+ repeat:
+ tokenForm.accessControl === false
+ ? undefined
+ : !tokenForm.repeat
+ ? i18n.str`Required`
+ : tokenForm.repeat !== tokenForm.token
+ ? i18n.str`Doesn't match`
+ : undefined,
+ });
+
+ const hasTokenErrors = tokenFormErrors === undefined;
const submit = (): Promise<void> => {
// use conversion instead of this
const newValue = structuredClone(value);
- const newToken = newValue.auth_token;
+ const accessControl = !!tokenForm.accessControl;
newValue.auth_token = undefined;
- newValue.auth =
- newToken === null || newToken === undefined
- ? { method: "external" }
- : { method: "token", token: createRFC8959AccessTokenPlain(newToken) };
+ newValue.auth = !accessControl
+ ? { method: "external" }
+ : {
+ method: "token",
+ token: createRFC8959AccessTokenPlain(tokenForm.token!),
+ };
if (!newValue.address) newValue.address = {};
if (!newValue.jurisdiction) newValue.jurisdiction = {};
return onCreate(newValue as TalerMerchantApi.InstanceConfigurationMessage);
};
- function updateToken(token: string | null) {
- valueHandler((old) => ({
- ...old,
- auth_token: token === null ? undefined : token,
- }));
- }
-
return (
<div>
- <div class="columns">
- <div class="column" />
- <div class="column is-four-fifths">
- {isTokenDialogActive && (
- <SetTokenNewInstanceModal
- onCancel={() => {
- updateIsTokenDialogActive(false);
- updateIsTokenSet(false);
- }}
- onClear={() => {
- updateToken(null);
- updateIsTokenDialogActive(false);
- updateIsTokenSet(true);
- }}
- onConfirm={(newToken) => {
- updateToken(newToken);
- updateIsTokenDialogActive(false);
- updateIsTokenSet(true);
- }}
- />
- )}
- </div>
- <div class="column" />
- </div>
-
<section class="section is-main-section">
<div class="tabs is-toggle is-fullwidth is-small">
<ul>
@@ -216,51 +204,57 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
showLessFields={!pref.advanceInstanceMode}
/>
</FormProvider>
-
- <div class="level">
- <div class="level-item has-text-centered">
- <h1 class="title">
- <button
- class={
- !isTokenSet
- ? "button is-danger has-tooltip-bottom"
- : !value.auth_token
- ? "button has-tooltip-bottom"
- : "button is-info has-tooltip-bottom"
- }
- data-tooltip={i18n.str`Change authorization configuration`}
- onClick={() => updateIsTokenDialogActive(true)}
- >
- <div class="icon is-centered">
- <i class="mdi mdi-lock-reset" />
- </div>
- <span>
- <i18n.Translate>Set access token</i18n.Translate>
- </span>
- </button>
- </h1>
- </div>
- </div>
+ <FormProvider
+ errors={tokenFormErrors}
+ object={tokenForm}
+ valueHandler={setTokenForm}
+ >
+ <InputToggle<TokenForm>
+ name="accessControl"
+ threeState={tokenForm.accessControl === undefined}
+ label={i18n.str`Enable access control`}
+ help={i18n.str`Choose if the backend server should authenticate access.`}
+ />
+ <Input<TokenForm>
+ name="token"
+ label={i18n.str`New access token`}
+ tooltip={i18n.str`Next access token to be used`}
+ readonly={
+ tokenForm.accessControl === false ||
+ tokenForm.accessControl === undefined
+ }
+ inputType="password"
+ />
+ <Input<TokenForm>
+ name="repeat"
+ label={i18n.str`Repeat access token`}
+ tooltip={i18n.str`Confirm the same access token`}
+ readonly={
+ tokenForm.accessControl === false ||
+ tokenForm.accessControl === undefined
+ }
+ inputType="password"
+ />
+ </FormProvider>
<div class="level">
<div class="level-item has-text-centered">
- {!isTokenSet ? (
+ {tokenForm.accessControl === undefined ? (
<p class="is-size-6">
<i18n.Translate>
- Access token is not yet configured. This instance can't be
+ Access control is not yet decided. This instance can't be
created.
</i18n.Translate>
</p>
- ) : value.auth_token === undefined ? (
+ ) : !tokenForm.accessControl ? (
<p class="is-size-6">
<i18n.Translate>
- No access token. Authorization must be handled externally.
+ Authorization must be handled externally.
</i18n.Translate>
</p>
) : (
<p class="is-size-6">
<i18n.Translate>
- Access token is set. Authorization is handled by the
- merchant backend.
+ Authorization is handled by the backend server.
</i18n.Translate>
</p>
)}
@@ -274,7 +268,7 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
)}
<AsyncButton
onClick={submit}
- disabled={hasErrors || !isTokenSet}
+ disabled={hasErrors || !hasTokenErrors}
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields and choose authorization method`