summaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui/src/paths/instance/update
diff options
context:
space:
mode:
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance/update')
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/update/Update.stories.tsx16
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx178
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx98
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>
);