mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 16:16:21 -06:00
fix: breadcrumb dropdown active state and loading indicators (#6714)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
This commit is contained in:
@@ -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 = ({
|
||||
<div className="flex items-center gap-1">
|
||||
<BuildingIcon className="h-3 w-3" strokeWidth={1.5} />
|
||||
<span>{currentOrganization.name}</span>
|
||||
{isLoading && <Loader2 className="h-3 w-3 animate-spin" strokeWidth={1.5} />}
|
||||
{isPending && <Loader2 className="h-3 w-3 animate-spin" strokeWidth={1.5} />}
|
||||
{isOrganizationDropdownOpen ? (
|
||||
<ChevronDownIcon className="h-3 w-3" strokeWidth={1.5} />
|
||||
) : (
|
||||
@@ -156,9 +175,9 @@ export const OrganizationBreadcrumb = ({
|
||||
return setting.hidden ? null : (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={setting.id}
|
||||
checked={pathname.includes(setting.id)}
|
||||
checked={isActiveOrganizationSetting(pathname, setting.id)}
|
||||
hidden={setting.hidden}
|
||||
onClick={() => router.push(setting.href)}
|
||||
onClick={() => handleSettingChange(setting.href)}
|
||||
className="cursor-pointer">
|
||||
{setting.label}
|
||||
</DropdownMenuCheckboxItem>
|
||||
|
||||
@@ -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 = ({
|
||||
<div className="flex items-center gap-1">
|
||||
<FolderOpenIcon className="h-3 w-3" strokeWidth={1.5} />
|
||||
<span>{currentProject.name}</span>
|
||||
{isLoading && <Loader2 className="h-3 w-3 animate-spin" strokeWidth={1.5} />}
|
||||
{isPending && <Loader2 className="h-3 w-3 animate-spin" strokeWidth={1.5} />}
|
||||
{isProjectDropdownOpen ? (
|
||||
<ChevronDownIcon className="h-3 w-3" strokeWidth={1.5} />
|
||||
) : (
|
||||
@@ -194,8 +211,8 @@ export const ProjectBreadcrumb = ({
|
||||
{projectSettings.map((setting) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={setting.id}
|
||||
checked={pathname.includes(setting.id)}
|
||||
onClick={() => router.push(setting.href)}
|
||||
checked={isActiveProjectSetting(pathname, setting.id)}
|
||||
onClick={() => handleProjectSettingsNavigation(setting.id)}
|
||||
className="cursor-pointer">
|
||||
{setting.label}
|
||||
</DropdownMenuCheckboxItem>
|
||||
|
||||
Reference in New Issue
Block a user