commit b9539673aafbab021fa2dd0ecd1dd781c8e4df4e
parent f61a654888b9ee3312c7064502fdde18ebf19088
Author: Sebastian <sebasjm@gmail.com>
Date: Wed, 9 Jul 2025 11:40:26 -0300
fix #10108
Diffstat:
6 files changed, 94 insertions(+), 81 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
@@ -263,7 +263,7 @@ export function Sidebar({ mobile }: Props): VNode {
<i class="mdi mdi-security" />
</span>
<span class="menu-item-label">
- <i18n.Translate>Access Tokens</i18n.Translate>
+ <i18n.Translate>Active sessions</i18n.Translate>
</span>
</a>
</li>
diff --git a/packages/merchant-backoffice-ui/src/components/modal/index.tsx b/packages/merchant-backoffice-ui/src/components/modal/index.tsx
@@ -62,7 +62,7 @@ export function ConfirmModal({
const { i18n } = useTranslationContext();
return (
<div class={active ? "modal is-active" : "modal"}>
- <div class="modal-background " onClick={onCancel} />
+ <div class="modal-background " onClick={onCancel ?? onConfirm} />
<div class="modal-card" style={{ maxWidth: 700 }}>
<header class="modal-card-head">
{!description ? null : (
@@ -70,16 +70,22 @@ export function ConfirmModal({
<b>{description}</b>
</p>
)}
- <button class="delete " aria-label="close" onClick={onCancel} />
+ <button
+ class="delete "
+ aria-label="close"
+ onClick={onCancel ?? onConfirm}
+ />
</header>
<section class="modal-card-body">{children}</section>
<footer class="modal-card-foot">
<div class="buttons is-right" style={{ width: "100%" }}>
{onConfirm ? (
<Fragment>
- <button class="button " onClick={onCancel}>
- <i18n.Translate>Cancel</i18n.Translate>
- </button>
+ {onCancel ? (
+ <button class="button " onClick={onCancel}>
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+ ) : undefined}
<button
class={danger ? "button is-danger " : "button is-info "}
@@ -580,7 +586,7 @@ export function ValidBankAccount({
);
}
-function Row({
+export function Row({
name,
value,
literal,
diff --git a/packages/merchant-backoffice-ui/src/hooks/access-tokens.ts b/packages/merchant-backoffice-ui/src/hooks/access-tokens.ts
@@ -53,7 +53,7 @@ export function useInstanceAccessTokens() {
const { data, error } = useSWR<
TalerMerchantManagementResultByMethod<"listAccessTokens">,
TalerHttpError
- >([state.token, "offset", "listAccessTokens"], fetcher);
+ >([state.token, offset, "listAccessTokens"], fetcher);
if (error) return error;
if (data === undefined) return undefined;
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx
@@ -65,7 +65,7 @@ function with_defaults(id?: string): Partial<Entity> {
};
}
-type TokenForm = { accessControl: boolean; password: string; repeat: string };
+type TokenForm = { password: string; repeat: string };
export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
const [pref, updatePref] = usePreference();
@@ -124,20 +124,12 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
const hasErrors = errors !== undefined;
const tokenFormErrors = undefinedIfEmpty<FormErrors<TokenForm>>({
- password:
- tokenForm.accessControl === false
- ? undefined
- : !tokenForm.password
- ? i18n.str`Required`
- : undefined,
- repeat:
- tokenForm.accessControl === false
- ? undefined
- : !tokenForm.repeat
- ? i18n.str`Required`
- : tokenForm.repeat !== tokenForm.password
- ? i18n.str`Doesn't match`
- : undefined,
+ password: !tokenForm.password ? i18n.str`Required` : undefined,
+ repeat: !tokenForm.repeat
+ ? i18n.str`Required`
+ : tokenForm.repeat !== tokenForm.password
+ ? i18n.str`Doesn't match`
+ : undefined,
});
const hasTokenErrors = tokenFormErrors === undefined;
@@ -146,14 +138,11 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
// use conversion instead of this
const newValue = structuredClone(value);
- const accessControl = !!tokenForm.accessControl;
newValue.auth_token = undefined;
- newValue.auth = !accessControl
- ? { method: MerchantAuthMethod.EXTERNAL }
- : {
- method: MerchantAuthMethod.TOKEN,
- password: tokenForm.password!,
- };
+ newValue.auth = {
+ method: MerchantAuthMethod.TOKEN,
+ password: tokenForm.password!,
+ };
if (!newValue.address) newValue.address = {};
if (!newValue.jurisdiction) newValue.jurisdiction = {};
@@ -210,57 +199,19 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
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="password"
label={i18n.str`New password`}
tooltip={i18n.str`Next password to be used`}
- readonly={
- tokenForm.accessControl === false ||
- tokenForm.accessControl === undefined
- }
inputType="password"
/>
<Input<TokenForm>
name="repeat"
label={i18n.str`Repeat password`}
tooltip={i18n.str`Confirm the same password`}
- readonly={
- tokenForm.accessControl === false ||
- tokenForm.accessControl === undefined
- }
inputType="password"
/>
</FormProvider>
- <div class="level">
- <div class="level-item has-text-centered">
- {tokenForm.accessControl === undefined ? (
- <p class="is-size-6">
- <i18n.Translate>
- Access control is not yet decided. This instance can't be
- created.
- </i18n.Translate>
- </p>
- ) : !tokenForm.accessControl ? (
- <p class="is-size-6">
- <i18n.Translate>
- Authorization must be handled externally.
- </i18n.Translate>
- </p>
- ) : (
- <p class="is-size-6">
- <i18n.Translate>
- Authorization is handled by the backend server.
- </i18n.Translate>
- </p>
- )}
- </div>
- </div>
<div class="buttons is-right mt-5">
{onBack && (
<button class="button" onClick={onBack}>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/CreatePage.tsx
@@ -21,6 +21,7 @@
import {
assertUnreachable,
+ Duration,
LoginTokenScope,
TalerMerchantApi,
} from "@gnu-taler/taler-util";
@@ -38,7 +39,9 @@ import { InputDuration } from "../../../../components/form/InputDuration.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
-type Entity = TalerMerchantApi.LoginTokenRequest & {
+type Entity = {
+ scope: TalerMerchantApi.LoginTokenRequest["scope"];
+ duration: Duration;
password: string;
} & TalerForm;
@@ -57,6 +60,12 @@ const VALID_TOKEN_SCOPE = [
LoginTokenScope.OrderPos,
LoginTokenScope.OrderSimple,
LoginTokenScope.ReadOnly,
+ LoginTokenScope.All_Refreshable,
+ LoginTokenScope.OrderFull_Refreshable,
+ LoginTokenScope.OrderManagement_Refreshable,
+ LoginTokenScope.OrderPos_Refreshable,
+ LoginTokenScope.OrderSimple_Refreshable,
+ LoginTokenScope.ReadOnly_Refreshable,
];
export function CreatePage({ onCreate, onBack }: Props): VNode {
@@ -77,7 +86,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
return onCreate(state.password!, {
scope: state.scope!,
- duration: state.duration!,
+ duration: Duration.toTalerProtocolDuration(state.duration!),
});
};
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/index.tsx
@@ -19,18 +19,15 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import {
- TalerMerchantApi
-} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+import { AbsoluteTime, TalerMerchantApi } from "@gnu-taler/taler-util";
+import { Time, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { NotificationCard } from "../../../../components/menu/index.js";
import { useSessionContext } from "../../../../context/session.js";
import { Notification } from "../../../../utils/types.js";
import { CreatePage } from "./CreatePage.js";
+import { ConfirmModal, Row } from "../../../../components/modal/index.js";
export type Entity = TalerMerchantApi.LoginTokenRequest;
interface Props {
@@ -38,14 +35,60 @@ interface Props {
onConfirm: () => void;
}
-export default function AccessTokenCreatePage({ onConfirm, onBack }: Props): VNode {
+export default function AccessTokenCreatePage({
+ onConfirm,
+ onBack,
+}: Props): VNode {
const { state, lib } = useSessionContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
+ const [ok, setOk] = useState<{ token: string; expiration: AbsoluteTime }>();
+
return (
- <>
+ <Fragment>
<NotificationCard notification={notif} />
+ {!ok ? undefined : (
+ <ConfirmModal
+ label={`I understand`}
+ active
+ onConfirm={() => onConfirm()}
+ >
+ <div class="table-container">
+ <table>
+ <tbody>
+ <tr>
+ <td colSpan={3}>
+ <i18n.Translate>
+ Copy the access token velue and save it because you won't
+ be able to get it again.
+ </i18n.Translate>
+ </td>
+ </tr>
+ <Row name={i18n.str`Token`} value={ok.token} literal />
+
+ <tr>
+ <td colSpan={3}>
+ {AbsoluteTime.isNever(ok.expiration) ? (
+ <i18n.Translate>
+ This token will never expire
+ </i18n.Translate>
+ ) : (
+ <i18n.Translate>
+ This token will be available until{" "}
+ <Time
+ format="dd/MM/yyyy HH:mm:ss"
+ timestamp={ok.expiration}
+ />
+ </i18n.Translate>
+ )}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </ConfirmModal>
+ )}
<CreatePage
onBack={onBack}
onCreate={async (pwd: string, request: Entity) => {
@@ -60,7 +103,12 @@ export default function AccessTokenCreatePage({ onConfirm, onBack }: Props): VNo
});
return;
}
- onConfirm();
+ setOk({
+ expiration: AbsoluteTime.fromProtocolTimestamp(
+ resp.body.expiration,
+ ),
+ token: resp.body.access_token,
+ });
})
.catch((error) => {
setNotif({
@@ -72,7 +120,6 @@ export default function AccessTokenCreatePage({ onConfirm, onBack }: Props): VNo
});
}}
/>
- </>
+ </Fragment>
);
}
-