taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit f86ecf35c1fe9a5a94d17d1af10946cd24762f92
parent a7b347c956727d82c119dd3fa4d7ef8ec27b142e
Author: Sebastian <sebasjm@gmail.com>
Date:   Wed, 31 Jul 2024 14:19:59 -0300

fix #8946

Diffstat:
Mpackages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx | 152++++++++++++++++++++++++++++++++++++++-----------------------------------------
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`