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 : (
router.push(setting.href)}
+ onClick={() => handleSettingChange(setting.href)}
className="cursor-pointer">
{setting.label}
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}