import { MembershipRole, UserPermissionRole } from "@prisma/client"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible"; import { useSession } from "next-auth/react"; import Link from "next/link"; import { useRouter } from "next/router"; import React, { ComponentProps, useEffect, useState } from "react"; import Shell from "@calcom/features/shell/Shell"; import { classNames } from "@calcom/lib"; import { HOSTED_CAL_FEATURES, WEBAPP_URL } from "@calcom/lib/constants"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Badge, Button, ErrorBoundary, VerticalTabItemProps, VerticalTabItem, Skeleton, useMeta, } from "@calcom/ui"; import { FiUser, FiKey, FiCreditCard, FiTerminal, FiUsers, FiLock, FiArrowLeft, FiChevronDown, FiChevronRight, FiPlus, FiMenu, } from "@calcom/ui/components/icon"; const tabs: VerticalTabItemProps[] = [ { name: "my_account", href: "/settings/my-account", icon: FiUser, children: [ { name: "profile", href: "/settings/my-account/profile" }, { name: "general", href: "/settings/my-account/general" }, { name: "calendars", href: "/settings/my-account/calendars" }, { name: "conferencing", href: "/settings/my-account/conferencing" }, { name: "appearance", href: "/settings/my-account/appearance" }, // TODO // { name: "referrals", href: "/settings/my-account/referrals" }, ], }, { name: "security", href: "/settings/security", icon: FiKey, children: [ { name: "password", href: "/settings/security/password" }, { name: "2fa_auth", href: "/settings/security/two-factor-auth" }, { name: "impersonation", href: "/settings/security/impersonation" }, ], }, { name: "billing", href: "/settings/billing", icon: FiCreditCard, children: [{ name: "manage_billing", href: "/settings/billing" }], }, { name: "developer", href: "/settings/developer", icon: FiTerminal, children: [ // { name: "webhooks", href: "/settings/developer/webhooks" }, { name: "api_keys", href: "/settings/developer/api-keys" }, // TODO: Add profile level for embeds // { name: "embeds", href: "/v2/settings/developer/embeds" }, ], }, { name: "teams", href: "/settings/teams", icon: FiUsers, children: [], }, { name: "admin", href: "/settings/admin", icon: FiLock, children: [ // { name: "impersonation", href: "/settings/admin/impersonation" }, { name: "apps", href: "/settings/admin/apps/calendar" }, { name: "users", href: "/settings/admin/users" }, ], }, ]; tabs.find((tab) => { // Add "SAML SSO" to the tab if (tab.name === "security" && !HOSTED_CAL_FEATURES) { tab.children?.push({ name: "saml_config", href: "/settings/security/sso" }); } }); // The following keys are assigned to admin only const adminRequiredKeys = ["admin"]; const useTabs = () => { const session = useSession(); const isAdmin = session.data?.user.role === UserPermissionRole.ADMIN; tabs.map((tab) => { if (tab.name === "my_account") { tab.name = session.data?.user?.name || "my_account"; tab.icon = undefined; tab.avatar = WEBAPP_URL + "/" + session.data?.user?.username + "/avatar.png"; } return tab; }); // check if name is in adminRequiredKeys return tabs.filter((tab) => { if (isAdmin) return true; return !adminRequiredKeys.includes(tab.name); }); }; const BackButtonInSidebar = ({ name }: { name: string }) => { return ( {name} ); }; interface SettingsSidebarContainerProps { className?: string; navigationIsOpenedOnMobile?: boolean; } const SettingsSidebarContainer = ({ className = "", navigationIsOpenedOnMobile, }: SettingsSidebarContainerProps) => { const { t } = useLocale(); const router = useRouter(); const tabsWithPermissions = useTabs(); const [teamMenuState, setTeamMenuState] = useState<{ teamId: number | undefined; teamMenuOpen: boolean }[]>(); const { data: teams } = trpc.viewer.teams.list.useQuery(); useEffect(() => { if (teams) { const teamStates = teams?.map((team) => ({ teamId: team.id, teamMenuOpen: String(team.id) === router.query.id, })); setTeamMenuState(teamStates); setTimeout(() => { const tabMembers = Array.from(document.getElementsByTagName("a")).filter( (bottom) => bottom.dataset.testid === "vertical-tab-Members" )[1]; tabMembers?.scrollIntoView({ behavior: "smooth" }); }, 100); } }, [router.query.id, teams]); return ( <> {tabsWithPermissions.map((tab) => { return tab.name !== "teams" ? ( {tab && tab.icon && ( )} {!tab.icon && tab?.avatar && ( )} {t(tab.name)} {tab.children?.map((child, index) => ( ))} ) : ( {tab && tab.icon && ( )} {t(tab.name)} {teams && teamMenuState && teams.map((team, index: number) => { if (teamMenuState.some((teamState) => teamState.teamId === team.id)) return ( setTeamMenuState([ ...teamMenuState, (teamMenuState[index] = { ...teamMenuState[index], teamMenuOpen: !teamMenuState[index].teamMenuOpen, }), ]) }> setTeamMenuState([ ...teamMenuState, (teamMenuState[index] = { ...teamMenuState[index], teamMenuOpen: !teamMenuState[index].teamMenuOpen, }), ]) }> {teamMenuState[index].teamMenuOpen ? : } {team.name} {!team.accepted && ( Inv. )} {team.accepted && ( )} {(team.role === MembershipRole.OWNER || team.role === MembershipRole.ADMIN) && ( <> {/* TODO */} {/* */} {HOSTED_CAL_FEATURES && ( )} > )} ); })} ); })} > ); }; const MobileSettingsContainer = (props: { onSideContainerOpen?: () => void }) => { const { t } = useLocale(); return ( <> {t("show_navigation")} {t("settings")} > ); }; export default function SettingsLayout({ children, ...rest }: { children: React.ReactNode } & ComponentProps) { const router = useRouter(); const state = useState(false); const { t } = useLocale(); const [sideContainerOpen, setSideContainerOpen] = state; useEffect(() => { const closeSideContainer = () => { if (window.innerWidth >= 1024) { setSideContainerOpen(false); } }; window.addEventListener("resize", closeSideContainer); return () => { window.removeEventListener("resize", closeSideContainer); }; }, []); useEffect(() => { if (sideContainerOpen) { setSideContainerOpen(!sideContainerOpen); } }, [router.asPath]); return ( {/* Mobile backdrop */} {sideContainerOpen && ( setSideContainerOpen(false)} className="fixed top-0 left-0 z-10 h-full w-full bg-black/50"> {t("hide_navigation")} )} > } drawerState={state} MobileNavigationContainer={null} TopNavContainer={ setSideContainerOpen(!sideContainerOpen)} /> }> {children} ); } export const getLayout = (page: React.ReactElement) => {page}; function ShellHeader() { const { meta } = useMeta(); const { t, isLocaleReady } = useLocale(); return ( {meta.backButton && ( )} {meta.title && isLocaleReady ? ( {t(meta.title)} ) : ( )} {meta.description && isLocaleReady ? ( {t(meta.description)} ) : ( )} {meta.CTA} ); }
{t(tab.name)}
{team.name}
{t("settings")}
{t(meta.description)}