diff options
Diffstat (limited to 'packages/auditor-backoffice-ui/src/paths/admin/create/CreatePage.tsx')
-rw-r--r-- | packages/auditor-backoffice-ui/src/paths/admin/create/CreatePage.tsx | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/packages/auditor-backoffice-ui/src/paths/admin/create/CreatePage.tsx b/packages/auditor-backoffice-ui/src/paths/admin/create/CreatePage.tsx new file mode 100644 index 000000000..d13b7e929 --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/admin/create/CreatePage.tsx @@ -0,0 +1,257 @@ +/* + This file is part of GNU Taler + (C) 2021-2023 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AsyncButton } from "../../../components/exception/AsyncButton.js"; +import { + FormErrors, + FormProvider, +} from "../../../components/form/FormProvider.js"; +import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js"; +import { MerchantBackend } from "../../../declaration.js"; +import { INSTANCE_ID_REGEX } from "../../../utils/constants.js"; +import { undefinedIfEmpty } from "../../../utils/table.js"; +import { SetTokenNewInstanceModal } from "../../../components/modal/index.js"; +import { Duration } from "@gnu-taler/taler-util"; + +export type Entity = Omit<Omit<MerchantBackend.Instances.InstanceConfigurationMessage, "default_pay_delay">, "default_wire_transfer_delay"> & { + auth_token?: string; + default_pay_delay: Duration, + default_wire_transfer_delay: Duration, +}; + +interface Props { + onCreate: (d: MerchantBackend.Instances.InstanceConfigurationMessage) => Promise<void>; + onBack?: () => void; + forceId?: string; +} + +function with_defaults(id?: string): Partial<Entity> { + return { + id, + // accounts: [], + user_type: "business", + use_stefan: true, + default_pay_delay: { d_ms: 2 * 60 * 60 * 1000 }, // two hours + default_wire_transfer_delay: { d_ms: 2 * 60 * 60 * 24 * 1000 }, // two days + }; +} + +export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { + const [value, valueHandler] = useState(with_defaults(forceId)); + const [isTokenSet, updateIsTokenSet] = useState<boolean>(false); + const [isTokenDialogActive, updateIsTokenDialogActive] = + useState<boolean>(false); + + const { i18n } = useTranslationContext(); + + const errors: FormErrors<Entity> = { + id: !value.id + ? i18n.str`required` + : !INSTANCE_ID_REGEX.test(value.id) + ? i18n.str`is not valid` + : undefined, + name: !value.name ? i18n.str`required` : undefined, + + user_type: !value.user_type + ? i18n.str`required` + : value.user_type !== "business" && value.user_type !== "individual" + ? i18n.str`should be business or individual` + : undefined, + // accounts: + // !value.accounts || !value.accounts.length + // ? i18n.str`required` + // : undefinedIfEmpty( + // value.accounts.map((p) => { + // return !PAYTO_REGEX.test(p.payto_uri) + // ? i18n.str`is not valid` + // : undefined; + // }), + // ), + default_pay_delay: !value.default_pay_delay + ? i18n.str`required` + : !!value.default_wire_transfer_delay && + value.default_wire_transfer_delay.d_ms !== "forever" && + value.default_pay_delay.d_ms !== "forever" && + value.default_pay_delay.d_ms > value.default_wire_transfer_delay.d_ms ? + i18n.str`pay delay can't be greater than wire transfer delay` : undefined, + default_wire_transfer_delay: !value.default_wire_transfer_delay + ? i18n.str`required` + : undefined, + address: undefinedIfEmpty({ + address_lines: + value.address?.address_lines && value.address?.address_lines.length > 7 + ? i18n.str`max 7 lines` + : undefined, + }), + jurisdiction: undefinedIfEmpty({ + address_lines: + value.address?.address_lines && value.address?.address_lines.length > 7 + ? i18n.str`max 7 lines` + : undefined, + }), + }; + + const hasErrors = Object.keys(errors).some( + (k) => (errors as any)[k] !== undefined, + ); + + const submit = (): Promise<void> => { + // use conversion instead of this + const newToken = value.auth_token; + value.auth_token = undefined; + value.auth = newToken === null || newToken === undefined + ? { method: "external" } + : { method: "token", token: `secret-token:${newToken}` }; + if (!value.address) value.address = {}; + if (!value.jurisdiction) value.jurisdiction = {}; + // remove above use conversion + // schema.validateSync(value, { abortEarly: false }) + value.default_pay_delay = Duration.toTalerProtocolDuration(value.default_pay_delay!) as any + value.default_wire_transfer_delay = Duration.toTalerProtocolDuration(value.default_wire_transfer_delay!) as any + // delete value.default_pay_delay; + // delete value.default_wire_transfer_delay; + + return onCreate(value as any as MerchantBackend.Instances.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="columns"> + <div class="column" /> + <div class="column is-four-fifths"> + <FormProvider<Entity> + errors={errors} + object={value} + valueHandler={valueHandler} + > + <DefaultInstanceFormFields readonlyId={!!forceId} showId={true} /> + </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> + <div class="level"> + <div class="level-item has-text-centered"> + {!isTokenSet ? ( + <p class="is-size-6"> + <i18n.Translate> + Access token is not yet configured. This instance can't be + created. + </i18n.Translate> + </p> + ) : value.auth_token === undefined ? ( + <p class="is-size-6"> + <i18n.Translate> + No access token. 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. + </i18n.Translate> + </p> + )} + </div> + </div> + <div class="buttons is-right mt-5"> + {onBack && ( + <button class="button" onClick={onBack}> + <i18n.Translate>Cancel</i18n.Translate> + </button> + )} + <AsyncButton + onClick={submit} + disabled={hasErrors || !isTokenSet} + data-tooltip={ + hasErrors + ? i18n.str`Need to complete marked fields and choose authorization method` + : "confirm operation" + } + > + <i18n.Translate>Confirm</i18n.Translate> + </AsyncButton> + </div> + </div> + <div class="column" /> + </div> + </section> + </div> + ); +} |