feat: enhance navigation and URL state management in SecretDashboardPage and input components

This commit is contained in:
Victor Santos
2025-11-04 16:15:23 -03:00
parent 1e857c04b7
commit 4d974db485
3 changed files with 96 additions and 44 deletions
@@ -2,6 +2,7 @@ import { forwardRef, TextareaHTMLAttributes, useCallback, useMemo, useRef, useSt
import { faFolder, faKey, faLayerGroup, faSearch } from "@fortawesome/free-solid-svg-icons"; import { faFolder, faKey, faLayerGroup, faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Popover from "@radix-ui/react-popover"; import * as Popover from "@radix-ui/react-popover";
import { useNavigate } from "@tanstack/react-router";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
import { useProject } from "@app/context"; import { useProject } from "@app/context";
@@ -81,6 +82,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
) => { ) => {
const { currentProject } = useProject(); const { currentProject } = useProject();
const projectId = currentProject?.id || ""; const projectId = currentProject?.id || "";
const navigate = useNavigate({ from: ROUTE_PATHS.SecretManager.SecretDashboardPage.path });
const [debouncedValue] = useDebounce(value, 100); const [debouncedValue] = useDebounce(value, 100);
@@ -310,37 +312,49 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
const handleClickSegment = useCallback( const handleClickSegment = useCallback(
(segment: string, allSegments: string[]) => { (segment: string, allSegments: string[]) => {
// Single segment: search in current environment and path
if (allSegments.length === 1) { if (allSegments.length === 1) {
const currentEnv = propEnvironment || ""; navigate({
const currentPath = propSecretPath || "/"; search: (prev) => ({
const encodedPath = encodeURIComponent(currentPath); ...prev,
const url = ROUTE_PATHS.SecretManager.SecretDashboardPage.path search: segment,
.replace("$projectId", projectId) filterBy: "secret"
.replace("$envSlug", currentEnv); })
window.open( });
`${url}?secretPath=${encodedPath}&search=${encodeURIComponent(segment)}&filterBy=secret`,
"_blank",
"noopener,noreferrer"
);
return; return;
} }
// Multiple segments: first is env, last is secret, middle are folders
const environmentSlug = allSegments[0]; const environmentSlug = allSegments[0];
const secretName = allSegments[allSegments.length - 1]; const secretName = allSegments[allSegments.length - 1];
const folderPath = allSegments.length > 2 ? `/${allSegments.slice(1, -1).join("/")}` : "/"; let folderPath = "/";
const encodedPath = encodeURIComponent(folderPath);
const url = ROUTE_PATHS.SecretManager.SecretDashboardPage.path if (allSegments.length > 2) {
.replace("$projectId", projectId) const pathSegments = allSegments.slice(1, -1);
.replace("$envSlug", environmentSlug); for (let i = 0; i < pathSegments.length; i += 1) {
window.open( const pathSegment = pathSegments[i];
`${url}?secretPath=${encodedPath}&search=${encodeURIComponent(secretName)}&filterBy=secret`, folderPath += `${pathSegment}`;
"_blank", if (pathSegment === segment) {
"noopener,noreferrer" folderPath += "/";
); break;
}
folderPath += "/";
}
}
navigate({
to: ROUTE_PATHS.SecretManager.SecretDashboardPage.path,
params: {
projectId,
envSlug: environmentSlug
},
search: (prev) => ({
...prev,
secretPath: segment === environmentSlug ? "/" : folderPath,
search: segment === secretName ? secretName : prev.search,
filterBy: segment === secretName ? "secret" : prev.filterBy
})
});
}, },
[projectId, propEnvironment, propSecretPath] [navigate, projectId]
); );
return ( return (
@@ -53,10 +53,16 @@ const syntaxHighlight = (
} ${shouldShowHoverStyle ? "cursor-pointer underline decoration-yellow-400" : ""}`} } ${shouldShowHoverStyle ? "cursor-pointer underline decoration-yellow-400" : ""}`}
onMouseEnter={() => onHoverPart?.(segmentKey)} onMouseEnter={() => onHoverPart?.(segmentKey)}
onMouseLeave={() => onHoverPart?.("")} onMouseLeave={() => onHoverPart?.("")}
onClick={(e) => { onMouseDown={(e) => {
if (isCmdOrCtrlPressed) { if (isCmdOrCtrlPressed) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}
}}
onClick={(e) => {
e.stopPropagation();
if (isCmdOrCtrlPressed) {
e.preventDefault();
onClickSegment?.(segment, parts); onClickSegment?.(segment, parts);
} }
}} }}
@@ -475,34 +475,73 @@ const Page = () => {
); );
const handleTagToggle = useCallback( const handleTagToggle = useCallback(
(tagSlug: string) => (tagSlug: string) => {
setFilter((state) => { setFilter((state) => {
const isTagPresent = Boolean(state.tags?.[tagSlug]); const isTagPresent = Boolean(state.tags?.[tagSlug]);
const newTagFilter = { ...state.tags }; const newTagFilter = { ...state.tags };
if (isTagPresent) delete newTagFilter[tagSlug]; if (isTagPresent) delete newTagFilter[tagSlug];
else newTagFilter[tagSlug] = true; else newTagFilter[tagSlug] = true;
// Update URL to match filter state
const tagsList = Object.keys(newTagFilter).filter((tag) => newTagFilter[tag]);
navigate({
search: (prev) => ({
...prev,
tags: tagsList.length > 0 ? tagsList.join(",") : ""
})
});
return { ...state, tags: newTagFilter }; return { ...state, tags: newTagFilter };
}), });
[] },
[navigate]
); );
const handleToggleRowType = useCallback( const handleToggleRowType = useCallback(
(rowType: RowType) => (rowType: RowType) => {
setFilter((state) => { setFilter((state) => {
const newInclude = {
...state.include,
[rowType]: !state.include[rowType]
};
// Update URL to match filter state
const filterByList: string[] = [];
if (newInclude[RowType.Folder]) filterByList.push("folder");
if (newInclude[RowType.Import]) filterByList.push("import");
if (newInclude[RowType.DynamicSecret]) filterByList.push("dynamic");
if (newInclude[RowType.Secret]) filterByList.push("secret");
if (newInclude[RowType.SecretRotation]) filterByList.push("rotation");
navigate({
search: (prev) => ({
...prev,
filterBy: filterByList.length > 0 ? filterByList.join(",") : ""
})
});
return { return {
...state, ...state,
include: { include: newInclude
...state.include,
[rowType]: !state.include[rowType]
}
}; };
}), });
[] },
[navigate]
); );
const handleSearchChange = useCallback( const handleSearchChange = useCallback(
(searchFilter: string) => setFilter((state) => ({ ...state, searchFilter })), (searchFilter: string) => {
[] setFilter((state) => ({ ...state, searchFilter }));
// Update URL to match filter state
navigate({
search: (prev) => ({
...prev,
search: searchFilter || ""
})
});
},
[navigate]
); );
const handleToggleVisibility = useCallback(() => setIsVisible((state) => !state), []); const handleToggleVisibility = useCallback(() => setIsVisible((state) => !state), []);
@@ -564,13 +603,6 @@ const Page = () => {
include: includeFilter include: includeFilter
})); }));
setDebouncedSearchFilter(routerQueryParams.search as string); setDebouncedSearchFilter(routerQueryParams.search as string);
// this is a temp workaround until we fully transition state to query params,
navigate({
search: (state) => {
const { search, tags: qTags, filterBy: qFilterBy, ...query } = state;
return query;
}
});
}, [routerQueryParams.search, routerQueryParams.tags, routerQueryParams.filterBy]); }, [routerQueryParams.search, routerQueryParams.tags, routerQueryParams.filterBy]);
const selectedSecrets = useSelectedSecrets(); const selectedSecrets = useSelectedSecrets();