diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index 1bb23c81d8..068ca4e765 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -318,3 +318,39 @@ export const copyTextToClipboard = async (text: string): Promise => { } await navigator.clipboard.writeText(text); }; + +/** + * @description Joins URL path segments properly, removing duplicate slashes + * @param {...string} segments - URL path segments to join + * @returns {string} Properly joined URL path + * @example + * joinUrlPath("/workspace", "/projects") => "/workspace/projects" + * joinUrlPath("/workspace", "projects") => "/workspace/projects" + * joinUrlPath("workspace", "projects") => "workspace/projects" + * joinUrlPath("/workspace/", "/projects/") => "/workspace/projects/" + */ +export const joinUrlPath = (...segments: string[]): string => { + if (segments.length === 0) return ""; + + // Filter out empty segments + const validSegments = segments.filter((segment) => segment !== ""); + if (validSegments.length === 0) return ""; + + // Join segments and normalize slashes + const joined = validSegments + .map((segment, index) => { + // Remove leading slashes from all segments except the first + if (index > 0) { + segment = segment.replace(/^\/+/, ""); + } + // Remove trailing slashes from all segments except the last + if (index < validSegments.length - 1) { + segment = segment.replace(/\/+$/, ""); + } + return segment; + }) + .join("/"); + + // Clean up any duplicate slashes that might have been created + return joined.replace(/\/+/g, "/"); +}; diff --git a/web/core/components/settings/sidebar/nav-item.tsx b/web/core/components/settings/sidebar/nav-item.tsx index 53ebcc97a5..529739e641 100644 --- a/web/core/components/settings/sidebar/nav-item.tsx +++ b/web/core/components/settings/sidebar/nav-item.tsx @@ -6,7 +6,7 @@ import { Disclosure } from "@headlessui/react"; // plane imports import { EUserWorkspaceRoles } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import { cn } from "@plane/utils"; +import { cn, joinUrlPath } from "@plane/utils"; // hooks import { useUserSettings } from "@/hooks/store"; @@ -72,7 +72,11 @@ const SettingsSidebarNavItem = observer((props: TSettingsSidebarNavItemProps) => {renderChildren ? (
{titleElement}
) : ( - toggleSidebar(true)}> + toggleSidebar(true)} + > {titleElement} )} diff --git a/web/core/components/settings/sidebar/root.tsx b/web/core/components/settings/sidebar/root.tsx index b29425f9e3..c681a8cc11 100644 --- a/web/core/components/settings/sidebar/root.tsx +++ b/web/core/components/settings/sidebar/root.tsx @@ -46,10 +46,11 @@ export const SettingsSidebar = observer((props: SettingsSidebarProps) => { {/* Navigation */}
- {categories.map((category) => ( -
- {t(category)} - {groupedSettings[category].length > 0 && ( + {categories.map((category) => { + if (groupedSettings[category].length === 0) return null; + return ( +
+ {t(category)}
{groupedSettings[category].map( (setting) => @@ -66,9 +67,9 @@ export const SettingsSidebar = observer((props: SettingsSidebarProps) => { ) )}
- )} -
- ))} +
+ ); + })}
);