diff --git a/packages/web/src/pages/ProfilesPage.jsx b/packages/web/src/pages/ProfilesPage.jsx
index 0fb4314..12f5224 100644
--- a/packages/web/src/pages/ProfilesPage.jsx
+++ b/packages/web/src/pages/ProfilesPage.jsx
@@ -7,6 +7,7 @@ import { FormSelect } from "../components/FormControls.jsx";
import { Modal } from "../components/Modal.jsx";
import { ProfileEditorModal } from "../components/ProfileEditorModal.jsx";
import { ScriptIcon } from "../components/ScriptIcon.jsx";
+import { useRouteDrivenModal } from "../hooks/useRouteDrivenModal.js";
import { formatTime } from "../lib/format.jsx";
function previewConfig(config) {
@@ -42,84 +43,74 @@ export function ProfilesPage() {
);
const { data: profiles = [], isLoading } = useProfiles(ownerFilter || undefined);
const del = useDeleteProfile();
- const [editor, setEditor] = useState(null);
const [confirmDelete, setConfirmDelete] = useState(null);
const [highlightName, setHighlightName] = useState(() => routeName || "");
const highlightRef = useRef(null);
- const openedRouteKey = useRef(null);
const listPath = ownerFilter
? `/profiles?owner=${encodeURIComponent(ownerFilter)}`
: "/profiles";
- function closeEditor() {
- setEditor(null);
- openedRouteKey.current = null;
- if (isNewRoute || isEditRoute) {
- navigate(listPath, { replace: true });
- }
- }
-
- useEffect(() => {
- if (!isNewRoute) return;
- const key = `new:${params.get("owner") || ""}:${params.get("name") || ""}`;
- if (openedRouteKey.current === key) return;
- if (!params.get("owner") && !ownerFilter && owners.length === 0) return;
- const owner = params.get("owner") || ownerFilter || owners[0] || "default";
- if (params.get("owner")) setOwnerFilter(params.get("owner"));
- openedRouteKey.current = key;
- setEditor({
- mode: "add",
- initial: {
- owner,
- name: params.get("name") || "",
- script: params.get("script") || "",
- config: {},
- description: "",
- },
- });
- }, [isNewRoute, params, owners, ownerFilter]);
-
- useEffect(() => {
- if (!isEditRoute) {
- if (!isNewRoute) openedRouteKey.current = null;
- return;
- }
- if (ownerFilter !== routeOwner) {
- setOwnerFilter(routeOwner);
- return;
- }
- if (isLoading) return;
- const key = `edit:${routeOwner}/${routeName}`;
- if (openedRouteKey.current === key) return;
- openedRouteKey.current = key;
- setHighlightName(routeName);
- const row = profiles.find((p) => p.owner === routeOwner && p.name === routeName);
- if (row) {
- setEditor({
- mode: "edit",
+ const { editor, closeEditor } = useRouteDrivenModal({
+ isNewRoute,
+ isEditRoute,
+ listPath,
+ newRouteKey: () => `new:${params.get("owner") || ""}:${params.get("name") || ""}`,
+ canOpenNew: () => Boolean(params.get("owner") || ownerFilter || owners.length > 0),
+ onBeforeOpenNew: () => {
+ if (params.get("owner")) setOwnerFilter(params.get("owner"));
+ },
+ buildNewEditor: () => {
+ const owner = params.get("owner") || ownerFilter || owners[0] || "default";
+ return {
+ mode: "add",
initial: {
- owner: row.owner,
- name: row.name,
- script: row.script,
- config: row.config,
- description: row.description,
+ owner,
+ name: params.get("name") || "",
+ script: params.get("script") || "",
+ config: {},
+ description: "",
},
- usageCount: row.usageCount ?? 0,
- });
- return;
- }
- setEditor({
- mode: "add",
- initial: {
- owner: routeOwner,
- name: routeName,
- script: "",
- config: {},
- description: "",
- },
- });
- }, [isEditRoute, isNewRoute, routeOwner, routeName, ownerFilter, isLoading, profiles]);
+ };
+ },
+ editRouteKey: () => `edit:${routeOwner}/${routeName}`,
+ canOpenEdit: () => {
+ if (ownerFilter !== routeOwner) {
+ setOwnerFilter(routeOwner);
+ return false;
+ }
+ return !isLoading;
+ },
+ onOpenEdit: () => setHighlightName(routeName),
+ buildEditEditor: () => {
+ const row = profiles.find((p) => p.owner === routeOwner && p.name === routeName);
+ if (row) {
+ return {
+ mode: "edit",
+ initial: {
+ owner: row.owner,
+ name: row.name,
+ script: row.script,
+ config: row.config,
+ description: row.description,
+ },
+ usageCount: row.usageCount ?? 0,
+ };
+ }
+ return {
+ mode: "add",
+ initial: {
+ owner: routeOwner,
+ name: routeName,
+ script: "",
+ config: {},
+ description: "",
+ },
+ };
+ },
+ newDeps: [params, owners, ownerFilter],
+ editDeps: [routeOwner, routeName, ownerFilter, isLoading, profiles],
+ });
useEffect(() => {
if (!highlightName || isLoading) return;
diff --git a/packages/web/src/pages/SecretsPage.jsx b/packages/web/src/pages/SecretsPage.jsx
index 5333ead..7bc5325 100644
--- a/packages/web/src/pages/SecretsPage.jsx
+++ b/packages/web/src/pages/SecretsPage.jsx
@@ -6,6 +6,7 @@ import { useDeleteSecret, useOwners, useSecrets } from "../api/hooks.js";
import { ConfirmDialog } from "../components/ConfirmDialog.jsx";
import { FormSelect } from "../components/FormControls.jsx";
import { SecretEditorModal } from "../components/SecretEditorModal.jsx";
+import { useRouteDrivenModal } from "../hooks/useRouteDrivenModal.js";
import { formatTime } from "../lib/format.jsx";
export function SecretsPage() {
@@ -22,56 +23,46 @@ export function SecretsPage() {
);
const { data: secrets = [], isLoading } = useSecrets(ownerFilter || undefined);
const del = useDeleteSecret();
- const [editor, setEditor] = useState(null);
const [confirmDelete, setConfirmDelete] = useState(null);
const [highlightName, setHighlightName] = useState(() => routeName || "");
const highlightRef = useRef(null);
- const openedRouteKey = useRef(null);
const listPath = ownerFilter
? `/secrets?owner=${encodeURIComponent(ownerFilter)}`
: "/secrets";
- function closeEditor() {
- setEditor(null);
- openedRouteKey.current = null;
- if (isNewRoute || isEditRoute) {
- navigate(listPath, { replace: true });
- }
- }
-
- useEffect(() => {
- if (!isNewRoute) return;
- const key = `new:${params.get("owner") || ""}:${params.get("name") || ""}`;
- if (openedRouteKey.current === key) return;
- if (!params.get("owner") && !ownerFilter && owners.length === 0) return;
- const owner = params.get("owner") || ownerFilter || owners[0] || "default";
- if (params.get("owner")) setOwnerFilter(params.get("owner"));
- openedRouteKey.current = key;
- setEditor({
- mode: "add",
- initial: { owner, name: params.get("name") || "" },
- });
- }, [isNewRoute, params, owners, ownerFilter]);
-
- useEffect(() => {
- if (!isEditRoute) {
- if (!isNewRoute) openedRouteKey.current = null;
- return;
- }
- if (ownerFilter !== routeOwner) {
- setOwnerFilter(routeOwner);
- return;
- }
- const key = `edit:${routeOwner}/${routeName}`;
- if (openedRouteKey.current === key) return;
- openedRouteKey.current = key;
- setHighlightName(routeName);
- setEditor({
+ const { editor, closeEditor } = useRouteDrivenModal({
+ isNewRoute,
+ isEditRoute,
+ listPath,
+ newRouteKey: () => `new:${params.get("owner") || ""}:${params.get("name") || ""}`,
+ canOpenNew: () => Boolean(params.get("owner") || ownerFilter || owners.length > 0),
+ onBeforeOpenNew: () => {
+ if (params.get("owner")) setOwnerFilter(params.get("owner"));
+ },
+ buildNewEditor: () => {
+ const owner = params.get("owner") || ownerFilter || owners[0] || "default";
+ return {
+ mode: "add",
+ initial: { owner, name: params.get("name") || "" },
+ };
+ },
+ editRouteKey: () => `edit:${routeOwner}/${routeName}`,
+ canOpenEdit: () => {
+ if (ownerFilter !== routeOwner) {
+ setOwnerFilter(routeOwner);
+ return false;
+ }
+ return true;
+ },
+ onOpenEdit: () => setHighlightName(routeName),
+ buildEditEditor: () => ({
mode: "replace",
initial: { owner: routeOwner, name: routeName },
- });
- }, [isEditRoute, isNewRoute, routeOwner, routeName, ownerFilter]);
+ }),
+ newDeps: [params, owners, ownerFilter],
+ editDeps: [routeOwner, routeName, ownerFilter],
+ });
useEffect(() => {
if (!highlightName || isLoading) return;
diff --git a/packages/web/src/pages/UsersPage.jsx b/packages/web/src/pages/UsersPage.jsx
index 114ddb7..9b5acd0 100644
--- a/packages/web/src/pages/UsersPage.jsx
+++ b/packages/web/src/pages/UsersPage.jsx
@@ -1,10 +1,11 @@
-import { useEffect, useRef, useState } from "react";
+import { useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { LuPencil, LuPlus, LuTrash2 } from "react-icons/lu";
import { errorMessage } from "../api/client.js";
import { useDeleteUser, useUsers } from "../api/hooks.js";
import { ConfirmDialog } from "../components/ConfirmDialog.jsx";
import { UserEditorModal } from "../components/UserEditorModal.jsx";
+import { useRouteDrivenModal } from "../hooks/useRouteDrivenModal.js";
export function UsersPage() {
const navigate = useNavigate();
@@ -15,41 +16,22 @@ export function UsersPage() {
const { data: users = [], isLoading } = useUsers();
const del = useDeleteUser();
- const [editor, setEditor] = useState(null);
const [confirmDelete, setConfirmDelete] = useState(null);
- const openedRouteKey = useRef(null);
- function closeEditor() {
- setEditor(null);
- openedRouteKey.current = null;
- if (isNewRoute || isEditRoute) {
- navigate("/users", { replace: true });
- }
- }
-
- useEffect(() => {
- if (!isNewRoute) return;
- if (openedRouteKey.current === "new") return;
- openedRouteKey.current = "new";
- setEditor({ mode: "add" });
- }, [isNewRoute]);
-
- useEffect(() => {
- if (!isEditRoute) {
- if (!isNewRoute) openedRouteKey.current = null;
- return;
- }
- if (isLoading) return;
- const key = `edit:${routeUsername}`;
- if (openedRouteKey.current === key) return;
- openedRouteKey.current = key;
- const user = users.find((u) => u.username === routeUsername);
- if (!user) {
- setEditor({ mode: "add" });
- return;
- }
- setEditor({ mode: "edit", user });
- }, [isEditRoute, isNewRoute, routeUsername, isLoading, users]);
+ const { editor, closeEditor } = useRouteDrivenModal({
+ isNewRoute,
+ isEditRoute,
+ listPath: "/users",
+ buildNewEditor: () => ({ mode: "add" }),
+ editRouteKey: () => `edit:${routeUsername}`,
+ canOpenEdit: () => !isLoading,
+ buildEditEditor: () => {
+ const user = users.find((u) => u.username === routeUsername);
+ if (!user) return { mode: "add" };
+ return { mode: "edit", user };
+ },
+ editDeps: [routeUsername, isLoading, users],
+ });
return (
diff --git a/packages/web/src/pages/VariablesPage.jsx b/packages/web/src/pages/VariablesPage.jsx
index 48b5bf5..7266ee4 100644
--- a/packages/web/src/pages/VariablesPage.jsx
+++ b/packages/web/src/pages/VariablesPage.jsx
@@ -6,6 +6,7 @@ import { useDeleteVariable, useOwners, useVariables } from "../api/hooks.js";
import { ConfirmDialog } from "../components/ConfirmDialog.jsx";
import { FormSelect } from "../components/FormControls.jsx";
import { VariableEditorModal } from "../components/VariableEditorModal.jsx";
+import { useRouteDrivenModal } from "../hooks/useRouteDrivenModal.js";
import { formatTime } from "../lib/format.jsx";
function displayValue(value) {
@@ -27,80 +28,70 @@ export function VariablesPage() {
);
const { data: variables = [], isLoading } = useVariables(ownerFilter || undefined);
const del = useDeleteVariable();
- const [editor, setEditor] = useState(null);
const [confirmDelete, setConfirmDelete] = useState(null);
const [highlightName, setHighlightName] = useState(() => routeName || "");
const highlightRef = useRef(null);
- const openedRouteKey = useRef(null);
const listPath = ownerFilter
? `/variables?owner=${encodeURIComponent(ownerFilter)}`
: "/variables";
- function closeEditor() {
- setEditor(null);
- openedRouteKey.current = null;
- if (isNewRoute || isEditRoute) {
- navigate(listPath, { replace: true });
- }
- }
-
- useEffect(() => {
- if (!isNewRoute) return;
- const key = `new:${params.get("owner") || ""}:${params.get("name") || ""}`;
- if (openedRouteKey.current === key) return;
- if (!params.get("owner") && !ownerFilter && owners.length === 0) return;
- const owner = params.get("owner") || ownerFilter || owners[0] || "default";
- if (params.get("owner")) setOwnerFilter(params.get("owner"));
- openedRouteKey.current = key;
- setEditor({
- mode: "add",
- initial: {
- owner,
- name: params.get("name") || "",
- type: "string",
- value: "",
- },
- });
- }, [isNewRoute, params, owners, ownerFilter]);
-
- useEffect(() => {
- if (!isEditRoute) {
- if (!isNewRoute) openedRouteKey.current = null;
- return;
- }
- if (ownerFilter !== routeOwner) {
- setOwnerFilter(routeOwner);
- return;
- }
- if (isLoading) return;
- const key = `edit:${routeOwner}/${routeName}`;
- if (openedRouteKey.current === key) return;
- openedRouteKey.current = key;
- setHighlightName(routeName);
- const row = variables.find((v) => v.owner === routeOwner && v.name === routeName);
- if (row) {
- setEditor({
- mode: "edit",
+ const { editor, closeEditor } = useRouteDrivenModal({
+ isNewRoute,
+ isEditRoute,
+ listPath,
+ newRouteKey: () => `new:${params.get("owner") || ""}:${params.get("name") || ""}`,
+ canOpenNew: () => Boolean(params.get("owner") || ownerFilter || owners.length > 0),
+ onBeforeOpenNew: () => {
+ if (params.get("owner")) setOwnerFilter(params.get("owner"));
+ },
+ buildNewEditor: () => {
+ const owner = params.get("owner") || ownerFilter || owners[0] || "default";
+ return {
+ mode: "add",
initial: {
- owner: row.owner,
- name: row.name,
- type: row.type,
- value: row.value,
+ owner,
+ name: params.get("name") || "",
+ type: "string",
+ value: "",
},
- });
- return;
- }
- setEditor({
- mode: "add",
- initial: {
- owner: routeOwner,
- name: routeName,
- type: "string",
- value: "",
- },
- });
- }, [isEditRoute, isNewRoute, routeOwner, routeName, ownerFilter, isLoading, variables]);
+ };
+ },
+ editRouteKey: () => `edit:${routeOwner}/${routeName}`,
+ canOpenEdit: () => {
+ if (ownerFilter !== routeOwner) {
+ setOwnerFilter(routeOwner);
+ return false;
+ }
+ return !isLoading;
+ },
+ onOpenEdit: () => setHighlightName(routeName),
+ buildEditEditor: () => {
+ const row = variables.find((v) => v.owner === routeOwner && v.name === routeName);
+ if (row) {
+ return {
+ mode: "edit",
+ initial: {
+ owner: row.owner,
+ name: row.name,
+ type: row.type,
+ value: row.value,
+ },
+ };
+ }
+ return {
+ mode: "add",
+ initial: {
+ owner: routeOwner,
+ name: routeName,
+ type: "string",
+ value: "",
+ },
+ };
+ },
+ newDeps: [params, owners, ownerFilter],
+ editDeps: [routeOwner, routeName, ownerFilter, isLoading, variables],
+ });
useEffect(() => {
if (!highlightName || isLoading) return;