From 0b0a5478fb39e1ccd009ccdb7cbe2e7a93471a1f Mon Sep 17 00:00:00 2001 From: Abhay Mishra Date: Sat, 7 Feb 2026 19:27:24 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20Add=20action=E2=80=91item=20support=20t?= =?UTF-8?q?o=20mobile=20=E2=80=9CMore=E2=80=9D=20page=20and=20added=20navi?= =?UTF-8?q?gation=20to=20refer=20page=20(#26431)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add action‑item support to mobile “More” page and hide arrow on copy‑link button * updated code * fix code * updated code * remove unused import and formatting --------- Co-authored-by: Dhairyashil --- .../modules/shell/navigation/Navigation.tsx | 66 ++++++--- .../shell/navigation/NavigationItem.tsx | 125 +++++++----------- .../shell/navigation/useMobileMoreItems.ts | 23 ++++ 3 files changed, 120 insertions(+), 94 deletions(-) create mode 100644 apps/web/modules/shell/navigation/useMobileMoreItems.ts diff --git a/apps/web/modules/shell/navigation/Navigation.tsx b/apps/web/modules/shell/navigation/Navigation.tsx index ed436401b5..8f38eeb453 100644 --- a/apps/web/modules/shell/navigation/Navigation.tsx +++ b/apps/web/modules/shell/navigation/Navigation.tsx @@ -6,6 +6,7 @@ import { useOrgBranding, type OrganizationBranding, } from "@calcom/features/ee/organizations/context/provider"; +import { useMobileMoreItems } from "./useMobileMoreItems"; import { useIsStandalone } from "@calcom/lib/hooks/useIsStandalone"; import classNames from "@calcom/ui/classNames"; import { useHasPaidPlan } from "@calcom/web/modules/billing/hooks/useHasPaidPlan"; @@ -14,7 +15,11 @@ import UnconfirmedBookingBadge from "../../bookings/components/UnconfirmedBookin import { KBarTrigger } from "../Kbar"; import { TeamInviteBadge } from "../TeamInviteBadge"; import type { NavigationItemType } from "./NavigationItem"; -import { NavigationItem, MobileNavigationItem, MobileNavigationMoreItem } from "./NavigationItem"; +import { + NavigationItem, + MobileNavigationItem, + MobileNavigationMoreItem, +} from "./NavigationItem"; export const MORE_SEPARATOR_NAME = "more"; @@ -63,7 +68,10 @@ const getNavigationItems = ( moreOnMobile: true, isCurrent: ({ pathname: path, item }) => { // During Server rendering path is /v2/apps but on client it becomes /apps(weird..) - return (path?.startsWith(item.href) ?? false) && !(path?.includes("routing-forms/") ?? false); + return ( + (path?.startsWith(item.href) ?? false) && + !(path?.includes("routing-forms/") ?? false) + ); }, child: [ { @@ -109,7 +117,8 @@ const getNavigationItems = ( name: "insights", href: "/insights", icon: "chart-bar", - isCurrent: ({ pathname: path, item }) => path?.startsWith(item.href) ?? false, + isCurrent: ({ pathname: path, item }) => + path?.startsWith(item.href) ?? false, moreOnMobile: true, child: hasAllInsightsAccess ? [ @@ -121,18 +130,21 @@ const getNavigationItems = ( { name: "routing", href: "/insights/routing", - isCurrent: ({ pathname: path }) => path?.startsWith("/insights/routing") ?? false, + isCurrent: ({ pathname: path }) => + path?.startsWith("/insights/routing") ?? false, }, { name: "router_position", href: "/insights/router-position", - isCurrent: ({ pathname: path }) => path?.startsWith("/insights/router-position") ?? false, + isCurrent: ({ pathname: path }) => + path?.startsWith("/insights/router-position") ?? false, }, { name: "call_history", href: "/insights/call-history", // icon: "phone", - isCurrent: ({ pathname: path }) => path?.startsWith("/insights/call-history") ?? false, + isCurrent: ({ pathname: path }) => + path?.startsWith("/insights/call-history") ?? false, }, ] : [ @@ -205,19 +217,34 @@ const useNavigationItems = (isPlatformNavigation = false) => { ? getNavigationItems(orgBranding, hasAllInsightsAccess) : platformNavigationItems; - const desktopNavigationItems = items.filter((item) => item.name !== MORE_SEPARATOR_NAME); + const desktopNavigationItems = items.filter( + (item) => item.name !== MORE_SEPARATOR_NAME + ); const mobileNavigationBottomItems = items.filter( - (item) => (!item.moreOnMobile && !item.onlyDesktop) || item.name === MORE_SEPARATOR_NAME + (item) => + (!item.moreOnMobile && !item.onlyDesktop) || + item.name === MORE_SEPARATOR_NAME ); const mobileNavigationMoreItems = items.filter( - (item) => item.moreOnMobile && !item.onlyDesktop && item.name !== MORE_SEPARATOR_NAME + (item) => + item.moreOnMobile && + !item.onlyDesktop && + item.name !== MORE_SEPARATOR_NAME ); - return { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems }; + return { + desktopNavigationItems, + mobileNavigationBottomItems, + mobileNavigationMoreItems, + }; }, [hasPaidPlan, isPending, isPlatformNavigation, orgBranding]); }; -export const Navigation = ({ isPlatformNavigation = false }: { isPlatformNavigation?: boolean }) => { +export const Navigation = ({ + isPlatformNavigation = false, +}: { + isPlatformNavigation?: boolean; +}) => { const { desktopNavigationItems } = useNavigationItems(isPlatformNavigation); return ( @@ -243,9 +270,14 @@ export function MobileNavigationContainer({ return ; } -const MobileNavigation = ({ isPlatformNavigation = false }: { isPlatformNavigation?: boolean }) => { +const MobileNavigation = ({ + isPlatformNavigation = false, +}: { + isPlatformNavigation?: boolean; +}) => { const isEmbed = useIsEmbed(); - const { mobileNavigationBottomItems } = useNavigationItems(isPlatformNavigation); + const { mobileNavigationBottomItems } = + useNavigationItems(isPlatformNavigation); return ( <> @@ -253,7 +285,8 @@ const MobileNavigation = ({ isPlatformNavigation = false }: { isPlatformNavigati className={classNames( "pwa:pb-[max(0.25rem,env(safe-area-inset-bottom))] pwa:-mx-2 bg-cal-muted/40 border-subtle fixed bottom-0 left-0 z-30 flex w-full border-t px-1 shadow backdrop-blur-md md:hidden", isEmbed && "hidden" - )}> + )} + > {mobileNavigationBottomItems.map((item) => ( ))} @@ -266,10 +299,13 @@ const MobileNavigation = ({ isPlatformNavigation = false }: { isPlatformNavigati export const MobileNavigationMoreItems = () => { const { mobileNavigationMoreItems } = useNavigationItems(); + const bottomItems = useMobileMoreItems(); + + const allItems = [...mobileNavigationMoreItems, ...bottomItems]; return (
    - {mobileNavigationMoreItems.map((item) => ( + {allItems.map((item) => ( ))}
diff --git a/apps/web/modules/shell/navigation/NavigationItem.tsx b/apps/web/modules/shell/navigation/NavigationItem.tsx index 41a344bb06..61ca747227 100644 --- a/apps/web/modules/shell/navigation/NavigationItem.tsx +++ b/apps/web/modules/shell/navigation/NavigationItem.tsx @@ -27,10 +27,7 @@ const usePersistedExpansionState = (itemName: string) => { const setPersistedExpansion = (expanded: boolean) => { setIsExpanded(expanded); - sessionStorage.setItem( - `nav-expansion-${itemName}`, - JSON.stringify(expanded) - ); + sessionStorage.setItem(`nav-expansion-${itemName}`, JSON.stringify(expanded)); }; return [isExpanded, setPersistedExpansion] as const; @@ -67,16 +64,8 @@ export type NavigationItemType = { }) => boolean; }; -const defaultIsCurrent: NavigationItemType["isCurrent"] = ({ - isChild, - item, - pathname, -}) => { - return isChild - ? item.href === pathname - : item.href - ? pathname?.startsWith(item.href) ?? false - : false; +const defaultIsCurrent: NavigationItemType["isCurrent"] = ({ isChild, item, pathname }) => { + return isChild ? item.href === pathname : item.href ? (pathname?.startsWith(item.href) ?? false) : false; }; export const NavigationItem: React.FC<{ @@ -87,12 +76,9 @@ export const NavigationItem: React.FC<{ const { item, isChild } = props; const { t, isLocaleReady } = useLocale(); const pathname = usePathname(); - const isCurrent: NavigationItemType["isCurrent"] = - item.isCurrent || defaultIsCurrent; + const isCurrent: NavigationItemType["isCurrent"] = item.isCurrent || defaultIsCurrent; const current = isCurrent({ isChild: !!isChild, item, pathname }); - const shouldDisplayNavigationItem = useShouldDisplayNavigationItem( - props.item - ); + const shouldDisplayNavigationItem = useShouldDisplayNavigationItem(props.item); const [isExpanded, setIsExpanded] = usePersistedExpansionState(item.name); const isTablet = useMediaQuery("(max-width: 1024px)"); @@ -102,12 +88,8 @@ export const NavigationItem: React.FC<{ const hasChildren = item.child && item.child.length > 0; const hasActiveChild = - hasChildren && - item.child?.some((child) => - isCurrent({ isChild: true, item: child, pathname }) - ); - const shouldShowChildren = - isExpanded || hasActiveChild || isCurrent({ pathname, isChild, item }); + hasChildren && item.child?.some((child) => isCurrent({ isChild: true, item: child, pathname })); + const shouldShowChildren = isExpanded || hasActiveChild || isCurrent({ pathname, isChild, item }); const shouldShowChevron = hasChildren && !hasActiveChild; const isParentNavigationItem = hasChildren && !isChild; @@ -151,8 +133,7 @@ export const NavigationItem: React.FC<{ childIsCurrent ? "bg-emphasis text-white" : "hover:bg-emphasis text-mute hover:text-emphasis" - )} - > + )}> {t(childItem.name)} ); @@ -163,8 +144,7 @@ export const NavigationItem: React.FC<{ t(item.name) ) } - className="lg:hidden" - > + className="lg:hidden">
+ + ) : isActionItem ? ( + ) : ( - - {item.icon && ( - - + target={item.target} + className="hover:bg-subtle flex items-center justify-between p-5 transition"> + {itemContent} )} diff --git a/apps/web/modules/shell/navigation/useMobileMoreItems.ts b/apps/web/modules/shell/navigation/useMobileMoreItems.ts new file mode 100644 index 0000000000..138a0fdde8 --- /dev/null +++ b/apps/web/modules/shell/navigation/useMobileMoreItems.ts @@ -0,0 +1,23 @@ +import { getBookerBaseUrlSync } from "@calcom/features/ee/organizations/lib/getBookerBaseUrlSync"; +import { useBottomNavItems } from "../useBottomNavItems"; +import { UserPermissionRole } from "@calcom/prisma/enums"; +import type { NavigationItemType } from "./NavigationItem"; +import { useSession } from "next-auth/react"; + +export function useMobileMoreItems(): NavigationItemType[] { + const { data: session } = useSession(); + const user = session?.user; + const isAdmin = user?.role === UserPermissionRole.ADMIN; + const publicPageUrl = `${getBookerBaseUrlSync(user?.org?.slug ?? null)}/${user?.orgAwareUsername ?? user?.username}`; + + const bottomNavItems = useBottomNavItems({ + publicPageUrl, + isAdmin, + user, + }); + + const filteredBottomNavItems = bottomNavItems.filter( + (item: NavigationItemType) => item.name !== "settings" + ); + return filteredBottomNavItems; +} \ No newline at end of file