diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance/update')
3 files changed, 108 insertions, 184 deletions
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/Update.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/Update.stories.tsx index 8c4717275..5bd12e4e9 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/update/Update.stories.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/update/Update.stories.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021 Taler Systems S.A. + (C) 2021-2024 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 @@ -19,7 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { h, VNode, FunctionalComponent } from "preact"; +import { FunctionalComponent, h } from "preact"; import { UpdatePage as TestedComponent } from "./UpdatePage.js"; export default { @@ -33,7 +33,7 @@ export default { function createExample<Props>( Component: FunctionalComponent<Props>, - props: Partial<Props> + props: Partial<Props>, ) { const r = (args: any) => <Component {...args} />; r.args = props; @@ -42,19 +42,17 @@ function createExample<Props>( export const Example = createExample(TestedComponent, { selected: { - accounts: [], name: "name", auth: { method: "external" }, address: {}, + user_type: "business", + use_stefan: true, jurisdiction: {}, - default_max_deposit_fee: "TESTKUDOS:2", - default_max_wire_fee: "TESTKUDOS:1", default_pay_delay: { - d_us: 1000000, + d_us: 1000 * 1000, //one second }, - default_wire_fee_amortization: 1, default_wire_transfer_delay: { - d_us: 100000, + d_us: 1000 * 1000, //one second }, merchant_pub: "ASDWQEKASJDKSADJ", }, diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx index b5328249a..cde58967f 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021 Taler Systems S.A. + (C) 2021-2024 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 @@ -19,137 +19,101 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { h, VNode } from "preact"; +import { Duration, TalerMerchantApi } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { VNode, h } from "preact"; import { useState } from "preact/hooks"; -import * as yup from "yup"; import { AsyncButton } from "../../../components/exception/AsyncButton.js"; import { - FormProvider, FormErrors, + FormProvider, } from "../../../components/form/FormProvider.js"; -import { UpdateTokenModal } from "../../../components/modal/index.js"; -import { useInstanceContext } from "../../../context/instance.js"; -import { MerchantBackend } from "../../../declaration.js"; -import { Translate, useTranslator } from "../../../i18n/index.js"; import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js"; -import { PAYTO_REGEX } from "../../../utils/constants.js"; -import { Amounts } from "@gnu-taler/taler-util"; +import { useSessionContext } from "../../../context/session.js"; import { undefinedIfEmpty } from "../../../utils/table.js"; -type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & { - auth_token?: string; +export type Entity = Omit<Omit<TalerMerchantApi.InstanceReconfigurationMessage, "default_pay_delay">, "default_wire_transfer_delay"> & { + default_pay_delay: Duration, + default_wire_transfer_delay: Duration, }; -//MerchantBackend.Instances.InstanceAuthConfigurationMessage +//TalerMerchantApi.InstanceAuthConfigurationMessage interface Props { - onUpdate: (d: Entity) => void; - onChangeAuth: ( - d: MerchantBackend.Instances.InstanceAuthConfigurationMessage - ) => Promise<void>; - selected: MerchantBackend.Instances.QueryInstancesResponse; + onUpdate: (d: TalerMerchantApi.InstanceReconfigurationMessage) => void; + selected: TalerMerchantApi.QueryInstancesResponse; isLoading: boolean; onBack: () => void; } function convert( - from: MerchantBackend.Instances.QueryInstancesResponse + from: TalerMerchantApi.QueryInstancesResponse, ): Entity { - const { accounts, ...rest } = from; - const payto_uris = accounts.filter((a) => a.active).map((a) => a.payto_uri); + const { default_pay_delay, default_wire_transfer_delay, ...rest } = from; + const defaults = { - default_wire_fee_amortization: 1, - default_pay_delay: { d_us: 2 * 1000 * 1000 * 60 * 60 }, //two hours - default_wire_transfer_delay: { d_us: 2 * 1000 * 1000 * 60 * 60 * 2 }, //two hours + use_stefan: false, + default_pay_delay: Duration.fromTalerProtocolDuration(default_pay_delay), + default_wire_transfer_delay: Duration.fromTalerProtocolDuration(default_wire_transfer_delay), }; - return { ...defaults, ...rest, payto_uris }; -} - -function getTokenValuePart(t?: string): string | undefined { - if (!t) return t; - const match = /secret-token:(.*)/.exec(t); - if (!match || !match[1]) return undefined; - return match[1]; + return { ...defaults, ...rest }; } export function UpdatePage({ onUpdate, - onChangeAuth, selected, onBack, }: Props): VNode { - const { id, token } = useInstanceContext(); - const currentTokenValue = getTokenValuePart(token); - - function updateToken(token: string | undefined | null) { - const value = - token && token.startsWith("secret-token:") - ? token.substring("secret-token:".length) - : token; - - if (!token) { - onChangeAuth({ method: "external" }); - } else { - onChangeAuth({ method: "token", token: `secret-token:${value}` }); - } - } + const { state } = useSessionContext(); const [value, valueHandler] = useState<Partial<Entity>>(convert(selected)); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const errors: FormErrors<Entity> = { - name: !value.name ? i18n`required` : undefined, - payto_uris: - !value.payto_uris || !value.payto_uris.length - ? i18n`required` - : undefinedIfEmpty( - value.payto_uris.map((p) => { - return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined; - }) - ), - default_max_deposit_fee: !value.default_max_deposit_fee - ? i18n`required` - : !Amounts.parse(value.default_max_deposit_fee) - ? i18n`invalid format` - : undefined, - default_max_wire_fee: !value.default_max_wire_fee - ? i18n`required` - : !Amounts.parse(value.default_max_wire_fee) - ? i18n`invalid format` - : undefined, - default_wire_fee_amortization: - value.default_wire_fee_amortization === undefined - ? i18n`required` - : isNaN(value.default_wire_fee_amortization) - ? i18n`is not a number` - : value.default_wire_fee_amortization < 1 - ? i18n`must be 1 or greater` + 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, - default_pay_delay: !value.default_pay_delay ? i18n`required` : 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`required` + ? i18n.str`required` : undefined, address: undefinedIfEmpty({ address_lines: value.address?.address_lines && value.address?.address_lines.length > 7 - ? i18n`max 7 lines` + ? i18n.str`max 7 lines` : undefined, }), jurisdiction: undefinedIfEmpty({ address_lines: value.address?.address_lines && value.address?.address_lines.length > 7 - ? i18n`max 7 lines` + ? i18n.str`max 7 lines` : undefined, }), }; const hasErrors = Object.keys(errors).some( - (k) => (errors as any)[k] !== undefined + (k) => (errors as any)[k] !== undefined, ); + const submit = async (): Promise<void> => { - await onUpdate(value as Entity); + const { default_pay_delay, default_wire_transfer_delay, ...rest } = value as Required<Entity>; + const result: TalerMerchantApi.InstanceReconfigurationMessage = { + default_pay_delay: Duration.toTalerProtocolDuration(default_pay_delay), + default_wire_transfer_delay: Duration.toTalerProtocolDuration(default_wire_transfer_delay), + ...rest, + } + await onUpdate(result); }; - const [active, setActive] = useState(false); + // const [active, setActive] = useState(false); return ( <div> @@ -160,56 +124,14 @@ export function UpdatePage({ <div class="level-left"> <div class="level-item"> <span class="is-size-4"> - <Translate>Instance id</Translate>: <b>{id}</b> + <i18n.Translate>Instance id</i18n.Translate>: <b>{state.instance}</b> </span> </div> </div> - <div class="level-right"> - <div class="level-item"> - <h1 class="title"> - <button - class="button is-danger" - data-tooltip={i18n`Change the authorization method use for this instance.`} - onClick={(): void => { - setActive(!active); - }} - > - <div class="icon is-left"> - <i class="mdi mdi-lock-reset" /> - </div> - <span> - <Translate>Manage access token</Translate> - </span> - </button> - </h1> - </div> - </div> </div> </div> </section> - <div class="columns"> - <div class="column" /> - <div class="column is-four-fifths"> - {active && ( - <UpdateTokenModal - oldToken={currentTokenValue} - onCancel={() => { - setActive(false); - }} - onClear={() => { - updateToken(null); - setActive(false); - }} - onConfirm={(newToken) => { - updateToken(newToken); - setActive(false); - }} - /> - )} - </div> - <div class="column" /> - </div> <hr /> <div class="columns"> @@ -229,19 +151,19 @@ export function UpdatePage({ onClick={onBack} data-tooltip="cancel operation" > - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> <AsyncButton onClick={submit} data-tooltip={ hasErrors - ? i18n`Need to complete marked fields` + ? i18n.str`Need to complete marked fields` : "confirm operation" } disabled={hasErrors} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </AsyncButton> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx index c0780fadf..9da7f7efb 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021 Taler Systems S.A. + (C) 2021-2024 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 @@ -13,69 +13,80 @@ 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/> */ -import { Fragment, h, VNode } from "preact"; +import { HttpStatusCode, TalerError, TalerMerchantApi, TalerMerchantInstanceHttpClient, TalerMerchantManagementResultByMethod, assertUnreachable } from "@gnu-taler/taler-util"; +import { + useTranslationContext +} from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; +import { ErrorLoadingMerchant } from "../../../components/ErrorLoadingMerchant.js"; import { Loading } from "../../../components/exception/loading.js"; import { NotificationCard } from "../../../components/menu/index.js"; -import { useInstanceContext } from "../../../context/instance.js"; -import { MerchantBackend } from "../../../declaration.js"; -import { HttpError, HttpResponse } from "../../../hooks/backend.js"; +import { useSessionContext } from "../../../context/session.js"; import { - useInstanceAPI, useInstanceDetails, useManagedInstanceDetails, - useManagementAPI, } from "../../../hooks/instance.js"; -import { useTranslator } from "../../../i18n/index.js"; import { Notification } from "../../../utils/types.js"; +import { LoginPage } from "../../login/index.js"; +import { NotFoundPageOrAdminCreate } from "../../notfound/index.js"; import { UpdatePage } from "./UpdatePage.js"; export interface Props { onBack: () => void; onConfirm: () => void; - onUnauthorized: () => VNode; - onNotFound: () => VNode; - onLoadError: (e: HttpError) => VNode; - onUpdateError: (e: HttpError) => void; + // onUnauthorized: () => VNode; + // onNotFound: () => VNode; + // onLoadError: (e: HttpError<TalerErrorDetail>) => VNode; + // onUpdateError: (e: HttpError<TalerErrorDetail>) => void; } export default function Update(props: Props): VNode { - const { updateInstance, clearToken, setNewToken } = useInstanceAPI(); + const { lib } = useSessionContext(); + const updateInstance = lib.instance.updateCurrentInstance.bind(lib.instance) const result = useInstanceDetails(); - return CommonUpdate(props, result, updateInstance, clearToken, setNewToken); + return CommonUpdate(props, result, updateInstance,); } export function AdminUpdate(props: Props & { instanceId: string }): VNode { - const { updateInstance, clearToken, setNewToken } = useManagementAPI( - props.instanceId - ); + const { lib } = useSessionContext(); + const t = lib.subInstanceApi(props.instanceId).instance; + const updateInstance = t.updateCurrentInstance.bind(t) const result = useManagedInstanceDetails(props.instanceId); - return CommonUpdate(props, result, updateInstance, clearToken, setNewToken); + return CommonUpdate(props, result, updateInstance,); } + function CommonUpdate( { onBack, onConfirm, - onLoadError, - onNotFound, - onUpdateError, - onUnauthorized, }: Props, - result: HttpResponse<MerchantBackend.Instances.QueryInstancesResponse>, - updateInstance: any, - clearToken: any, - setNewToken: any + result: TalerMerchantManagementResultByMethod<"getInstanceDetails"> | TalerError | undefined, + updateInstance: typeof TalerMerchantInstanceHttpClient.prototype.updateCurrentInstance, ): VNode { - const { changeToken } = useInstanceContext(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); + const { state } = useSessionContext(); - if (result.clientError && result.isUnauthorized) return onUnauthorized(); - if (result.clientError && result.isNotfound) return onNotFound(); - if (result.loading) return <Loading />; - if (!result.ok) return onLoadError(result); + if (!result) return <Loading /> + if (result instanceof TalerError) { + return <ErrorLoadingMerchant error={result} /> + } + if (result.type === "fail") { + switch(result.case) { + case HttpStatusCode.Unauthorized: { + return <LoginPage /> + } + case HttpStatusCode.NotFound: { + return <NotFoundPageOrAdminCreate />; + } + default: { + assertUnreachable(result) + } + } + } return ( <Fragment> @@ -83,30 +94,23 @@ function CommonUpdate( <UpdatePage onBack={onBack} isLoading={false} - selected={result.data} + selected={result.body} onUpdate={( - d: MerchantBackend.Instances.InstanceReconfigurationMessage + d: TalerMerchantApi.InstanceReconfigurationMessage, ): Promise<void> => { - return updateInstance(d) + if (state.status !== "loggedIn") { + return Promise.resolve(); + } + return updateInstance(state.token, d) .then(onConfirm) .catch((error: Error) => setNotif({ - message: i18n`Failed to create instance`, + message: i18n.str`Failed to update instance`, type: "ERROR", description: error.message, - }) + }), ); }} - onChangeAuth={( - d: MerchantBackend.Instances.InstanceAuthConfigurationMessage - ): Promise<void> => { - const apiCall = - d.method === "external" ? clearToken() : setNewToken(d.token!); - return apiCall - .then(() => changeToken(d.token)) - .then(onConfirm) - .catch(onUpdateError); - }} /> </Fragment> ); |