From 49bac1091a5c3ec50116bc4962fcc9552a7d935e Mon Sep 17 00:00:00 2001 From: Colin Dawson Date: Sun, 1 Feb 2026 00:13:01 +0000 Subject: [PATCH] Added support for redirecting to a specific control --- src/App.tsx | 75 ++++++++++--------- src/components/common/HorizionalTabs.tsx | 37 ++++++--- .../manager/domains/DomainsDetails.tsx | 4 +- .../domains/components/AddUserToRole.tsx | 4 +- .../domains/components/RolesEditor.tsx | 17 ++++- .../domains/components/SecurityRolesTab.tsx | 9 ++- src/utils/HashNavigationContext.tsx | 44 +++++++++++ 7 files changed, 135 insertions(+), 55 deletions(-) create mode 100644 src/utils/HashNavigationContext.tsx diff --git a/src/App.tsx b/src/App.tsx index aac23b5..868dad9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ import LoginForm from "./modules/frame/components/LoginForm"; import Redirect from "./components/common/Redirect"; import Mainframe from "./modules/frame/components/Mainframe"; import EmailUserAction from "./modules/frame/components/EmailUserAction"; +import { HashNavigationProvider } from "./utils/HashNavigationContext"; import HomePage from "./modules/homepage/HomePage"; import Profile from "./modules/profile/Profile"; @@ -458,42 +459,44 @@ function App() { return ( - - {config.applicationName} - -
- - } /> - {loginRoute} - - - - } - /> - - - - } - /> - - - - } - /> - {secureRoutes} - } /> - - -
+ + + {config.applicationName} + +
+ + } /> + {loginRoute} + + + + } + /> + + + + } + /> + + + + } + /> + {secureRoutes} + } /> + + +
+
); } diff --git a/src/components/common/HorizionalTabs.tsx b/src/components/common/HorizionalTabs.tsx index 53b92d3..50a2b1f 100644 --- a/src/components/common/HorizionalTabs.tsx +++ b/src/components/common/HorizionalTabs.tsx @@ -1,35 +1,53 @@ import React, { useEffect, useState, useCallback, useMemo } from "react"; -import { useLocation } from "react-router-dom"; +import { useHashSegment } from "../../utils/HashNavigationContext"; import TabHeader from "./TabHeader"; interface HorizontalTabsProps { children: JSX.Element[]; initialTab?: string; + hashSegment?: number; } const HorizontalTabs: React.FC = ({ children, initialTab, + hashSegment, }) => { - const location = useLocation(); + const hashValue = useHashSegment( + hashSegment !== undefined ? hashSegment : -1, + ); const [activeTab, setActiveTab] = useState(""); // Set initial tab on mount useEffect(() => { if (children.length > 0) { - // Check for hash in URL first, then fall back to initialTab prop - const hashTab = location.hash.slice(1); // Remove the # character - const tabToSelect = hashTab || initialTab || children[0].props.id; + // Only use hash if hashSegment was explicitly provided + const useHash = hashSegment !== undefined; + + // Validate that the hash matches one of our tab IDs + const hashMatchesTab = + useHash && + hashValue && + children.some((child) => child.props.id === hashValue); + + // Use id if available, otherwise fall back to label + const firstTabId = children[0].props.id || children[0].props.label; + + const tabToSelect = + (hashMatchesTab ? hashValue : initialTab) || firstTabId; setActiveTab(tabToSelect); } - }, [children, initialTab, location.hash]); + }, [children, initialTab, hashValue, hashSegment]); const onClickTabItem = useCallback((tab: string) => { setActiveTab((prev) => (prev !== tab ? tab : prev)); }, []); const activeTabChildren = useMemo(() => { - const match = children.find((child) => child.props.id === activeTab); + const match = children.find((child) => { + const tabId = child.props.id || child.props.label; + return tabId === activeTab; + }); return match ? match.props.children : <>; }, [children, activeTab]); @@ -43,12 +61,13 @@ const HorizontalTabs: React.FC = ({
    {children.map((child) => { const { id, label } = child.props; + const tabId = id || label; return ( onClickTabItem(id)} + isActive={tabId === activeTab} + onClick={() => onClickTabItem(tabId)} /> ); })} diff --git a/src/modules/manager/domains/DomainsDetails.tsx b/src/modules/manager/domains/DomainsDetails.tsx index cab7661..96dccca 100644 --- a/src/modules/manager/domains/DomainsDetails.tsx +++ b/src/modules/manager/domains/DomainsDetails.tsx @@ -53,7 +53,9 @@ const DomainsDetails: React.FC = ({ isEditMode }) => { return (

    {heading}

    - {tabs} + + {tabs} +
    ); }; diff --git a/src/modules/manager/domains/components/AddUserToRole.tsx b/src/modules/manager/domains/components/AddUserToRole.tsx index 7b8d8c4..0408990 100644 --- a/src/modules/manager/domains/components/AddUserToRole.tsx +++ b/src/modules/manager/domains/components/AddUserToRole.tsx @@ -53,9 +53,9 @@ const AddUserToRole: React.FC = ({ isEditMode }) => { if (response) { toast.info(t("UserAddedToRole")); - if (buttonName === labelSave) + if (buttonName === "save") form.setState({ - redirect: `/domains/edit/${domainId}?tab=SecurityRoles&roleId=${roleId}&innerTab=Users`, + redirect: `/domains/edit/${domainId}#securityRoles/${roleId}/users`, }); } } catch (ex: any) { diff --git a/src/modules/manager/domains/components/RolesEditor.tsx b/src/modules/manager/domains/components/RolesEditor.tsx index 46f34ac..02aa417 100644 --- a/src/modules/manager/domains/components/RolesEditor.tsx +++ b/src/modules/manager/domains/components/RolesEditor.tsx @@ -10,6 +10,7 @@ import RolesTable from "./RolesTable"; import Button, { ButtonType } from "../../../../components/common/Button"; import Loading from "../../../../components/common/Loading"; import Permission from "../../../../components/common/Permission"; +import { useHashSegment } from "../../../../utils/HashNavigationContext"; const initialPagedData: Paginated = { page: 1, @@ -24,6 +25,7 @@ interface RolesEditorProps { onSelectRole?: (keyValue: any) => void; onUnselectRole?: () => void; initialRoleId?: string; + hashSegment?: number; } const RolesEditor: React.FC = ({ @@ -31,8 +33,12 @@ const RolesEditor: React.FC = ({ onSelectRole, onUnselectRole, initialRoleId, + hashSegment, }) => { const { t } = useTranslation(); + const hashRoleId = useHashSegment( + hashSegment !== undefined ? hashSegment : -1, + ); const [loaded, setLoaded] = useState(false); const [pagedData, setPagedData] = useState>(initialPagedData); @@ -79,17 +85,20 @@ const RolesEditor: React.FC = ({ void loadInitial(); }, [changePage]); // eslint-disable-line react-hooks/exhaustive-deps - // Auto-select role when initialRoleId is provided + // Auto-select role when initialRoleId or hashRoleId is provided useEffect(() => { - if (initialRoleId && pagedData.data.length > 0 && onSelectRole) { + const roleIdToSelect = + hashSegment !== undefined ? hashRoleId : initialRoleId; + + if (roleIdToSelect && pagedData.data.length > 0 && onSelectRole) { const roleToSelect = pagedData.data.find( - (role) => role.id.toString() === initialRoleId, + (role) => role.id.toString() === roleIdToSelect, ); if (roleToSelect) { onSelectRole(roleToSelect); } } - }, [initialRoleId, pagedData.data, onSelectRole]); + }, [hashRoleId, initialRoleId, pagedData.data, onSelectRole, hashSegment]); const onSort = async (nextSortColumn: Column) => { const { page, pageSize } = pagedData; diff --git a/src/modules/manager/domains/components/SecurityRolesTab.tsx b/src/modules/manager/domains/components/SecurityRolesTab.tsx index 8f28b6f..0dab34f 100644 --- a/src/modules/manager/domains/components/SecurityRolesTab.tsx +++ b/src/modules/manager/domains/components/SecurityRolesTab.tsx @@ -38,14 +38,14 @@ const SecurityRolesTab: React.FC = ({ if (canViewRoleAccess) { tabs.push( - + , ); } if (canViewRoleUsers) { tabs.push( - + , ); @@ -59,12 +59,15 @@ const SecurityRolesTab: React.FC = ({ onSelectRole={onSelectRow} onUnselectRole={onUnselectRow} initialRoleId={initialRoleId} + hashSegment={1} />
    {selectedRole !== undefined && (canViewRoleAccess || canViewRoleUsers) && ( - {tabs} + + {tabs} + )}
    diff --git a/src/utils/HashNavigationContext.tsx b/src/utils/HashNavigationContext.tsx new file mode 100644 index 0000000..4049bc2 --- /dev/null +++ b/src/utils/HashNavigationContext.tsx @@ -0,0 +1,44 @@ +import React, { createContext, useContext, useMemo } from "react"; +import { useLocation } from "react-router-dom"; + +interface HashNavigationContextValue { + segments: string[]; + getSegment: (index: number) => string | undefined; +} + +const HashNavigationContext = createContext({ + segments: [], + getSegment: () => undefined, +}); + +export const HashNavigationProvider: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { + const location = useLocation(); + + const value = useMemo(() => { + // Parse hash into segments, removing the leading # + const hash = location.hash.slice(1); + const segments = hash ? hash.split("/") : []; + + return { + segments, + getSegment: (index: number) => segments[index], + }; + }, [location.hash]); + + return ( + + {children} + + ); +}; + +export const useHashNavigation = () => { + return useContext(HashNavigationContext); +}; + +export const useHashSegment = (index: number): string | undefined => { + const { getSegment } = useHashNavigation(); + return getSegment(index); +};