diff --git a/apps/web/app/(app)/environments/[environmentId]/components/organization-breadcrumb.tsx b/apps/web/app/(app)/environments/[environmentId]/components/organization-breadcrumb.tsx index 4a0ce2a79b..bd7e1a9a83 100644 --- a/apps/web/app/(app)/environments/[environmentId]/components/organization-breadcrumb.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/components/organization-breadcrumb.tsx @@ -10,7 +10,7 @@ import { SettingsIcon, } from "lucide-react"; import { usePathname, useRouter } from "next/navigation"; -import { useState } from "react"; +import { useState, useTransition } from "react"; import { useTranslation } from "react-i18next"; import { logger } from "@formbricks/logger"; import { CreateOrganizationModal } from "@/modules/organization/components/CreateOrganizationModal"; @@ -34,6 +34,17 @@ interface OrganizationBreadcrumbProps { isOwnerOrManager: boolean; } +const isActiveOrganizationSetting = (pathname: string, settingId: string): boolean => { + // Match /settings/{settingId} or /settings/{settingId}/... but exclude account settings + // Exclude paths with /(account)/ + if (pathname.includes("/(account)/")) { + return false; + } + // Check if path matches /settings/{settingId} (with optional trailing path) + const pattern = new RegExp(`/settings/${settingId}(?:/|$)`); + return pattern.test(pathname); +}; + export const OrganizationBreadcrumb = ({ currentOrganizationId, organizations, @@ -48,7 +59,7 @@ export const OrganizationBreadcrumb = ({ const [openCreateOrganizationModal, setOpenCreateOrganizationModal] = useState(false); const pathname = usePathname(); const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); + const [isPending, startTransition] = useTransition(); const currentOrganization = organizations.find((org) => org.id === currentOrganizationId); if (!currentOrganization) { @@ -60,13 +71,21 @@ export const OrganizationBreadcrumb = ({ const handleOrganizationChange = (organizationId: string) => { if (organizationId === currentOrganizationId) return; - setIsLoading(true); - router.push(`/organizations/${organizationId}/`); + startTransition(() => { + router.push(`/organizations/${organizationId}/`); + }); }; // Hide organization dropdown for single org setups (on-premise) const showOrganizationDropdown = isMultiOrgEnabled || organizations.length > 1; + const handleSettingChange = (href: string) => { + startTransition(() => { + setIsOrganizationDropdownOpen(false); + router.push(href); + }); + }; + const organizationSettings = [ { id: "general", @@ -108,7 +127,7 @@ export const OrganizationBreadcrumb = ({
{currentOrganization.name} - {isLoading && } + {isPending && } {isOrganizationDropdownOpen ? ( ) : ( @@ -156,9 +175,9 @@ export const OrganizationBreadcrumb = ({ return setting.hidden ? null : ( diff --git a/apps/web/app/(app)/environments/[environmentId]/components/project-breadcrumb.tsx b/apps/web/app/(app)/environments/[environmentId]/components/project-breadcrumb.tsx index ea4491eb10..f6411b0bbb 100644 --- a/apps/web/app/(app)/environments/[environmentId]/components/project-breadcrumb.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/components/project-breadcrumb.tsx @@ -3,7 +3,7 @@ import * as Sentry from "@sentry/nextjs"; import { ChevronDownIcon, ChevronRightIcon, CogIcon, FolderOpenIcon, Loader2, PlusIcon } from "lucide-react"; import { usePathname, useRouter } from "next/navigation"; -import { useState } from "react"; +import { useState, useTransition } from "react"; import { useTranslation } from "react-i18next"; import { logger } from "@formbricks/logger"; import { CreateProjectModal } from "@/modules/projects/components/create-project-modal"; @@ -32,6 +32,16 @@ interface ProjectBreadcrumbProps { isEnvironmentBreadcrumbVisible: boolean; } +const isActiveProjectSetting = (pathname: string, settingId: string): boolean => { + // Match /project/{settingId} or /project/{settingId}/... but exclude settings paths + if (pathname.includes("/settings/")) { + return false; + } + // Check if path matches /project/{settingId} (with optional trailing path) + const pattern = new RegExp(`/project/${settingId}(?:/|$)`); + return pattern.test(pathname); +}; + export const ProjectBreadcrumb = ({ currentProjectId, projects, @@ -49,7 +59,7 @@ export const ProjectBreadcrumb = ({ const [openCreateProjectModal, setOpenCreateProjectModal] = useState(false); const [openLimitModal, setOpenLimitModal] = useState(false); const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); + const [isPending, startTransition] = useTransition(); const pathname = usePathname(); const projectSettings = [ @@ -101,8 +111,9 @@ export const ProjectBreadcrumb = ({ const handleProjectChange = (projectId: string) => { if (projectId === currentProjectId) return; - setIsLoading(true); - router.push(`/projects/${projectId}/`); + startTransition(() => { + router.push(`/projects/${projectId}/`); + }); }; const handleAddProject = () => { @@ -113,6 +124,12 @@ export const ProjectBreadcrumb = ({ setOpenCreateProjectModal(true); }; + const handleProjectSettingsNavigation = (settingId: string) => { + startTransition(() => { + router.push(`/environments/${currentEnvironmentId}/project/${settingId}`); + }); + }; + const LimitModalButtons = (): [ModalButton, ModalButton] => { if (isFormbricksCloud) { return [ @@ -150,7 +167,7 @@ export const ProjectBreadcrumb = ({
{currentProject.name} - {isLoading && } + {isPending && } {isProjectDropdownOpen ? ( ) : ( @@ -194,8 +211,8 @@ export const ProjectBreadcrumb = ({ {projectSettings.map((setting) => ( router.push(setting.href)} + checked={isActiveProjectSetting(pathname, setting.id)} + onClick={() => handleProjectSettingsNavigation(setting.id)} className="cursor-pointer"> {setting.label}