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}
>
);
}