Added support for redirecting to a specific control
This commit is contained in:
parent
c7428be21b
commit
49bac1091a
75
src/App.tsx
75
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 (
|
||||
<HelmetProvider>
|
||||
<Helmet htmlAttributes={htmlAttributes}>
|
||||
<title>{config.applicationName}</title>
|
||||
</Helmet>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="/env" element={<EnvPage />} />
|
||||
{loginRoute}
|
||||
<Route
|
||||
path="/forgot-password"
|
||||
element={
|
||||
<LoginFrame>
|
||||
<ForgotPassword />
|
||||
</LoginFrame>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/404"
|
||||
element={
|
||||
<LoginFrame>
|
||||
<NotFound />
|
||||
</LoginFrame>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/emailuseraction/:token"
|
||||
element={
|
||||
<LoginFrame>
|
||||
<EmailUserAction />
|
||||
</LoginFrame>
|
||||
}
|
||||
/>
|
||||
{secureRoutes}
|
||||
<Route path="*" element={<Navigate replace to="/404" />} />
|
||||
</Routes>
|
||||
<ToastContainer />
|
||||
</main>
|
||||
<HashNavigationProvider>
|
||||
<Helmet htmlAttributes={htmlAttributes}>
|
||||
<title>{config.applicationName}</title>
|
||||
</Helmet>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="/env" element={<EnvPage />} />
|
||||
{loginRoute}
|
||||
<Route
|
||||
path="/forgot-password"
|
||||
element={
|
||||
<LoginFrame>
|
||||
<ForgotPassword />
|
||||
</LoginFrame>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/404"
|
||||
element={
|
||||
<LoginFrame>
|
||||
<NotFound />
|
||||
</LoginFrame>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/emailuseraction/:token"
|
||||
element={
|
||||
<LoginFrame>
|
||||
<EmailUserAction />
|
||||
</LoginFrame>
|
||||
}
|
||||
/>
|
||||
{secureRoutes}
|
||||
<Route path="*" element={<Navigate replace to="/404" />} />
|
||||
</Routes>
|
||||
<ToastContainer />
|
||||
</main>
|
||||
</HashNavigationProvider>
|
||||
</HelmetProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<HorizontalTabsProps> = ({
|
||||
children,
|
||||
initialTab,
|
||||
hashSegment,
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const hashValue = useHashSegment(
|
||||
hashSegment !== undefined ? hashSegment : -1,
|
||||
);
|
||||
const [activeTab, setActiveTab] = useState<string>("");
|
||||
|
||||
// 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<HorizontalTabsProps> = ({
|
||||
<ul className="tab-list">
|
||||
{children.map((child) => {
|
||||
const { id, label } = child.props;
|
||||
const tabId = id || label;
|
||||
return (
|
||||
<TabHeader
|
||||
key={label}
|
||||
label={label}
|
||||
isActive={id === activeTab}
|
||||
onClick={() => onClickTabItem(id)}
|
||||
isActive={tabId === activeTab}
|
||||
onClick={() => onClickTabItem(tabId)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -53,7 +53,9 @@ const DomainsDetails: React.FC<DomainsDetailsProps> = ({ isEditMode }) => {
|
||||
return (
|
||||
<div>
|
||||
<h1>{heading}</h1>
|
||||
<HorizontalTabs initialTab={initialTab}>{tabs}</HorizontalTabs>
|
||||
<HorizontalTabs initialTab={initialTab} hashSegment={0}>
|
||||
{tabs}
|
||||
</HorizontalTabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -53,9 +53,9 @@ const AddUserToRole: React.FC<LocAddUserToRoleProps> = ({ 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) {
|
||||
|
||||
@ -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<GetRoleResponse> = {
|
||||
page: 1,
|
||||
@ -24,6 +25,7 @@ interface RolesEditorProps {
|
||||
onSelectRole?: (keyValue: any) => void;
|
||||
onUnselectRole?: () => void;
|
||||
initialRoleId?: string;
|
||||
hashSegment?: number;
|
||||
}
|
||||
|
||||
const RolesEditor: React.FC<RolesEditorProps> = ({
|
||||
@ -31,8 +33,12 @@ const RolesEditor: React.FC<RolesEditorProps> = ({
|
||||
onSelectRole,
|
||||
onUnselectRole,
|
||||
initialRoleId,
|
||||
hashSegment,
|
||||
}) => {
|
||||
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||
const hashRoleId = useHashSegment(
|
||||
hashSegment !== undefined ? hashSegment : -1,
|
||||
);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [pagedData, setPagedData] =
|
||||
useState<Paginated<GetRoleResponse>>(initialPagedData);
|
||||
@ -79,17 +85,20 @@ const RolesEditor: React.FC<RolesEditorProps> = ({
|
||||
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<GetRoleResponse>) => {
|
||||
const { page, pageSize } = pagedData;
|
||||
|
||||
@ -38,14 +38,14 @@ const SecurityRolesTab: React.FC<SecurityRolesTabProps> = ({
|
||||
|
||||
if (canViewRoleAccess) {
|
||||
tabs.push(
|
||||
<Tab key={1} label={t("RoleAccess")}>
|
||||
<Tab id="roleAccess" key={1} label={t("RoleAccess")}>
|
||||
<RoleAccessEditor role={selectedRole} />
|
||||
</Tab>,
|
||||
);
|
||||
}
|
||||
if (canViewRoleUsers) {
|
||||
tabs.push(
|
||||
<Tab key={2} label={t("Users")}>
|
||||
<Tab id="users" key={2} label={t("Users")}>
|
||||
<UserRoleEditor role={selectedRole} />
|
||||
</Tab>,
|
||||
);
|
||||
@ -59,12 +59,15 @@ const SecurityRolesTab: React.FC<SecurityRolesTabProps> = ({
|
||||
onSelectRole={onSelectRow}
|
||||
onUnselectRole={onUnselectRow}
|
||||
initialRoleId={initialRoleId}
|
||||
hashSegment={1}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{selectedRole !== undefined &&
|
||||
(canViewRoleAccess || canViewRoleUsers) && (
|
||||
<HorizontalTabs initialTab={initialInnerTab}>{tabs}</HorizontalTabs>
|
||||
<HorizontalTabs initialTab={initialInnerTab} hashSegment={2}>
|
||||
{tabs}
|
||||
</HorizontalTabs>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
44
src/utils/HashNavigationContext.tsx
Normal file
44
src/utils/HashNavigationContext.tsx
Normal file
@ -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<HashNavigationContextValue>({
|
||||
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 (
|
||||
<HashNavigationContext.Provider value={value}>
|
||||
{children}
|
||||
</HashNavigationContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useHashNavigation = () => {
|
||||
return useContext(HashNavigationContext);
|
||||
};
|
||||
|
||||
export const useHashSegment = (index: number): string | undefined => {
|
||||
const { getSegment } = useHashNavigation();
|
||||
return getSegment(index);
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user