import type { User as UserAuth } from "next-auth"; import { useSession } from "next-auth/react"; import dynamic from "next/dynamic"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { classNames } from "@calcom/lib"; import { IS_CALCOM, IS_VISUAL_REGRESSION_TESTING, ENABLE_PROFILE_SWITCHER } from "@calcom/lib/constants"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client"; import { useCopy } from "@calcom/lib/hooks/useCopy"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useNotifications, ButtonState } from "@calcom/lib/hooks/useNotifications"; import { Avatar, Button, ButtonOrLink, Credits, Icon, SkeletonText, Tooltip, Logo, showToast, } from "@calcom/ui"; import { KBarTrigger } from "../kbar/Kbar"; import type { LayoutProps } from "./Shell"; import { Navigation } from "./navigation/Navigation"; import { type NavigationItemType } from "./navigation/NavigationItem"; import { ProfileDropdown } from "./user-dropdown/ProfileDropdown"; import { UserDropdown } from "./user-dropdown/UserDropdown"; // need to import without ssr to prevent hydration errors const Tips = dynamic(() => import("@calcom/features/tips").then((mod) => mod.Tips), { ssr: false, }); export type SideBarContainerProps = { bannersHeight: number; isPlatformUser?: boolean; }; export type SideBarProps = { bannersHeight: number; user?: UserAuth | null; isPlatformUser?: boolean; }; export function SideBarContainer({ bannersHeight, isPlatformUser = false }: SideBarContainerProps) { const { status, data } = useSession(); // Make sure that Sidebar is rendered optimistically so that a refresh of pages when logged in have SideBar from the beginning. // This improves the experience of refresh on app store pages(when logged in) which are SSG. // Though when logged out, app store pages would temporarily show SideBar until session status is confirmed. if (status !== "loading" && status !== "authenticated") return null; return ; } export function SideBar({ bannersHeight, user }: SideBarProps) { const { fetchAndCopyToClipboard } = useCopy(); const { t, isLocaleReady } = useLocale(); const pathname = usePathname(); const isPlatformPages = pathname?.startsWith("/settings/platform"); const [isReferalLoading, setIsReferalLoading] = useState(false); const publicPageUrl = `${getBookerBaseUrlSync(user?.org?.slug ?? null)}/${user?.username}`; const sidebarStylingAttributes = { maxHeight: `calc(100vh - ${bannersHeight}px)`, top: `${bannersHeight}px`, }; // Todo: extract this to a hook const bottomNavItems: NavigationItemType[] = [ { name: "view_public_page", href: publicPageUrl, icon: "external-link", target: "__blank", }, { name: "copy_public_page_link", href: "", onClick: (e: { preventDefault: () => void }) => { e.preventDefault(); navigator.clipboard.writeText(publicPageUrl); showToast(t("link_copied"), "success"); }, icon: "copy", }, IS_CALCOM ? { name: "copy_referral_link", href: "", onClick: (e: { preventDefault: () => void }) => { e.preventDefault(); setIsReferalLoading(true); // Create an artificial delay to show the loading state so it doesnt flicker if this request is fast setTimeout(() => { fetchAndCopyToClipboard( fetch("/api/generate-referral-link", { method: "POST", }) .then((res) => res.json()) .then((res) => res.shortLink), { onSuccess: () => showToast(t("link_copied"), "success"), onFailure: () => showToast("Copy to clipboard failed", "error"), } ); setIsReferalLoading(false); }, 1000); }, icon: "gift", isLoading: isReferalLoading, } : null, { name: "settings", href: user?.org ? `/settings/organizations/profile` : "/settings/my-account/profile", icon: "settings", }, ].filter(Boolean) as NavigationItemType[]; return (
); } export function ShellMain(props: LayoutProps) { const router = useRouter(); const { isLocaleReady, t } = useLocale(); const { buttonToShow, isLoading, enableNotifications, disableNotifications } = useNotifications(); return ( <> {(props.heading || !!props.backPath) && (
{!!props.backPath && ( )} )}
)} {props.afterHeading && <>{props.afterHeading}}
{props.children}
); }