feat: organizations sidebar (#9395)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Efraín Rochín <roae.85@gmail.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: Keith Williams <keithwillcode@gmail.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
parent
955de3133c
commit
b4fa9826f2
|
@ -1873,6 +1873,8 @@
|
||||||
"set_up": "Set up",
|
"set_up": "Set up",
|
||||||
"set_up_your_profile": "Set up your profile",
|
"set_up_your_profile": "Set up your profile",
|
||||||
"set_up_your_profile_description": "Let people know who you are within {{orgName}}, and when they engage with your public link.",
|
"set_up_your_profile_description": "Let people know who you are within {{orgName}}, and when they engage with your public link.",
|
||||||
|
"my_profile": "My Profile",
|
||||||
|
"my_settings": "My Settings",
|
||||||
"sender_id_info": "Name or number shown as the sender of an SMS (some countries do not allow alphanumeric sender IDs)",
|
"sender_id_info": "Name or number shown as the sender of an SMS (some countries do not allow alphanumeric sender IDs)",
|
||||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,7 +273,7 @@ export const KBarTrigger = () => {
|
||||||
<button
|
<button
|
||||||
color="minimal"
|
color="minimal"
|
||||||
onClick={query.toggle}
|
onClick={query.toggle}
|
||||||
className="text-default hover:bg-subtle lg:hover:bg-emphasis lg:hover:text-emphasis group flex rounded-md py-2 px-3 text-sm font-medium lg:p-1">
|
className="text-default hover:bg-subtle lg:hover:bg-emphasis lg:hover:text-emphasis group flex rounded-md px-3 py-2 text-sm font-medium lg:px-2">
|
||||||
<Search className="h-4 w-4 flex-shrink-0 text-inherit" />
|
<Search className="h-4 w-4 flex-shrink-0 text-inherit" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { User } from "@prisma/client";
|
import type { User as UserAuth } from "next-auth";
|
||||||
import { signOut, useSession } from "next-auth/react";
|
import { signOut, useSession } from "next-auth/react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
@ -26,6 +26,7 @@ import getBrandColours from "@calcom/lib/getBrandColours";
|
||||||
import { useIsomorphicLayoutEffect } from "@calcom/lib/hooks/useIsomorphicLayoutEffect";
|
import { useIsomorphicLayoutEffect } from "@calcom/lib/hooks/useIsomorphicLayoutEffect";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { isKeyInObject } from "@calcom/lib/isKeyInObject";
|
import { isKeyInObject } from "@calcom/lib/isKeyInObject";
|
||||||
|
import type { User } from "@calcom/prisma/client";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import useAvatarQuery from "@calcom/trpc/react/hooks/useAvatarQuery";
|
import useAvatarQuery from "@calcom/trpc/react/hooks/useAvatarQuery";
|
||||||
import useEmailVerifyCheck from "@calcom/trpc/react/hooks/useEmailVerifyCheck";
|
import useEmailVerifyCheck from "@calcom/trpc/react/hooks/useEmailVerifyCheck";
|
||||||
|
@ -49,6 +50,7 @@ import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
showToast,
|
showToast,
|
||||||
useCalcomTheme,
|
useCalcomTheme,
|
||||||
|
ButtonOrLink,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
@ -66,11 +68,13 @@ import {
|
||||||
Map,
|
Map,
|
||||||
Moon,
|
Moon,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
MoreVertical,
|
ChevronDown,
|
||||||
|
Copy,
|
||||||
Settings,
|
Settings,
|
||||||
Slack,
|
Slack,
|
||||||
Users,
|
Users,
|
||||||
Zap,
|
Zap,
|
||||||
|
User as UserIcon,
|
||||||
} from "@calcom/ui/components/icon";
|
} from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import FreshChatProvider from "../ee/support/lib/freshchat/FreshChatProvider";
|
import FreshChatProvider from "../ee/support/lib/freshchat/FreshChatProvider";
|
||||||
|
@ -287,12 +291,14 @@ export default function Shell(props: LayoutProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function UserDropdown({ small }: { small?: boolean }) {
|
interface UserDropdownProps {
|
||||||
|
small?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserDropdown({ small }: UserDropdownProps) {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const { data: user } = useMeQuery();
|
const { data: user } = useMeQuery();
|
||||||
const { data: avatar } = useAvatarQuery();
|
const { data: avatar } = useAvatarQuery();
|
||||||
const orgBranding = useOrgBrandingValues();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
|
@ -329,43 +335,36 @@ function UserDropdown({ small }: { small?: boolean }) {
|
||||||
<Dropdown open={menuOpen}>
|
<Dropdown open={menuOpen}>
|
||||||
<div className="ltr:sm:-ml-5 rtl:sm:-mr-5">
|
<div className="ltr:sm:-ml-5 rtl:sm:-mr-5">
|
||||||
<DropdownMenuTrigger asChild onClick={() => setMenuOpen((menuOpen) => !menuOpen)}>
|
<DropdownMenuTrigger asChild onClick={() => setMenuOpen((menuOpen) => !menuOpen)}>
|
||||||
<button className="radix-state-open:bg-emphasis hover:bg-emphasis group mx-0 flex w-full cursor-pointer appearance-none items-center rounded-full p-2 text-left outline-none focus:outline-none focus:ring-0 sm:mx-2.5 sm:pl-3 md:rounded-none lg:rounded lg:pl-2">
|
<button
|
||||||
|
className={classNames(
|
||||||
|
"hover:bg-emphasis group mx-0 ml-7 flex cursor-pointer appearance-none items-center rounded-full text-left outline-none focus:outline-none focus:ring-0 md:rounded-none lg:rounded",
|
||||||
|
small ? "p-2" : "px-2 py-1"
|
||||||
|
)}>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
small ? "h-6 w-6 md:ml-3" : "h-8 w-8 ltr:mr-2 rtl:ml-2",
|
small ? "h-4 w-4" : "h-6 w-6 ltr:mr-2 rtl:ml-2",
|
||||||
"relative flex-shrink-0 rounded-full bg-gray-300 "
|
"relative flex-shrink-0 rounded-full bg-gray-300 "
|
||||||
)}>
|
)}>
|
||||||
{
|
<Avatar
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
size={small ? "xs" : "sm"}
|
||||||
<img
|
imageSrc={avatar?.avatar || WEBAPP_URL + "/" + user.username + "/avatar.png"}
|
||||||
className="rounded-full"
|
|
||||||
src={avatar?.avatar || WEBAPP_URL + "/" + user.username + "/avatar.png"}
|
|
||||||
alt={user.username || "Nameless User"}
|
alt={user.username || "Nameless User"}
|
||||||
/>
|
/>
|
||||||
}
|
<span
|
||||||
{!user.away && (
|
className={classNames(
|
||||||
<div className="border-muted absolute bottom-0 right-0 h-3 w-3 rounded-full border-2 bg-green-500" />
|
"border-muted absolute -bottom-1 -right-1 rounded-full border-2 bg-green-500",
|
||||||
)}
|
user.away ? "bg-yellow-500" : "bg-green-500",
|
||||||
{user.away && (
|
small ? "-bottom-0.5 -right-0.5 h-2.5 w-2.5" : "bottom-0 right-0 h-3 w-3"
|
||||||
<div className="border-muted absolute bottom-0 right-0 h-3 w-3 rounded-full border-2 bg-yellow-500" />
|
|
||||||
)}
|
)}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
{!small && (
|
{!small && (
|
||||||
<span className="flex flex-grow items-center truncate">
|
<span className="flex flex-grow items-center">
|
||||||
<span className="flex-grow truncate text-sm leading-none">
|
<span className="line-clamp-1 flex-grow text-sm leading-none">
|
||||||
<span className="text-emphasis mb-1 block truncate font-medium">
|
<span className="text-emphasis block font-medium">{user.name || "Nameless User"}</span>
|
||||||
{user.name || "Nameless User"}
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-default truncate pb-1 font-normal">
|
<ChevronDown
|
||||||
{user.username
|
className="group-hover:text-subtle text-muted h-4 w-4 flex-shrink-0 rtl:mr-4"
|
||||||
? process.env.NEXT_PUBLIC_WEBSITE_URL === "https://cal.com"
|
|
||||||
? `${orgBranding && orgBranding.slug}cal.com/${user.username}`
|
|
||||||
: `${orgBranding && orgBranding.slug}/${user.username}`
|
|
||||||
: "No public page"}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<MoreVertical
|
|
||||||
className="group-hover:text-subtle text-muted h-4 w-4 flex-shrink-0 ltr:mr-2 rtl:ml-2 rtl:mr-4"
|
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
@ -377,6 +376,7 @@ function UserDropdown({ small }: { small?: boolean }) {
|
||||||
<DropdownMenuPortal>
|
<DropdownMenuPortal>
|
||||||
<FreshChatProvider>
|
<FreshChatProvider>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
|
align="start"
|
||||||
onInteractOutside={() => {
|
onInteractOutside={() => {
|
||||||
setMenuOpen(false);
|
setMenuOpen(false);
|
||||||
setHelpOpen(false);
|
setHelpOpen(false);
|
||||||
|
@ -386,6 +386,26 @@ function UserDropdown({ small }: { small?: boolean }) {
|
||||||
<HelpMenuItem onHelpItemSelect={() => onHelpItemSelect()} />
|
<HelpMenuItem onHelpItemSelect={() => onHelpItemSelect()} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<DropdownItem
|
||||||
|
type="button"
|
||||||
|
StartIcon={(props) => (
|
||||||
|
<UserIcon className={classNames("text-default", props.className)} aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
href="/settings/my-account/profile">
|
||||||
|
{t("my_profile")}
|
||||||
|
</DropdownItem>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<DropdownItem
|
||||||
|
type="button"
|
||||||
|
StartIcon={(props) => (
|
||||||
|
<Settings className={classNames("text-default", props.className)} aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
href="/settings/my-account/general">
|
||||||
|
{t("my_settings")}
|
||||||
|
</DropdownItem>
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -400,34 +420,6 @@ function UserDropdown({ small }: { small?: boolean }) {
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{user.username && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<DropdownItem
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href={`${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}`}
|
|
||||||
StartIcon={ExternalLink}>
|
|
||||||
{t("view_public_page")}
|
|
||||||
</DropdownItem>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<DropdownItem
|
|
||||||
type="button"
|
|
||||||
StartIcon={LinkIcon}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
navigator.clipboard.writeText(
|
|
||||||
`${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}`
|
|
||||||
);
|
|
||||||
showToast(t("link_copied"), "success");
|
|
||||||
}}>
|
|
||||||
{t("copy_public_page_link")}
|
|
||||||
</DropdownItem>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
StartIcon={(props) => <Slack strokeWidth={1.5} {...props} />}
|
StartIcon={(props) => <Slack strokeWidth={1.5} {...props} />}
|
||||||
|
@ -458,12 +450,6 @@ function UserDropdown({ small }: { small?: boolean }) {
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<DropdownItem type="button" href="/settings/my-account/profile" StartIcon={Settings}>
|
|
||||||
{t("settings")}
|
|
||||||
</DropdownItem>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
|
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -484,6 +470,8 @@ function UserDropdown({ small }: { small?: boolean }) {
|
||||||
export type NavigationItemType = {
|
export type NavigationItemType = {
|
||||||
name: string;
|
name: string;
|
||||||
href: string;
|
href: string;
|
||||||
|
onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLButtonElement>;
|
||||||
|
target?: HTMLAnchorElement["target"];
|
||||||
badge?: React.ReactNode;
|
badge?: React.ReactNode;
|
||||||
icon?: SVGComponent;
|
icon?: SVGComponent;
|
||||||
child?: NavigationItemType[];
|
child?: NavigationItemType[];
|
||||||
|
@ -495,7 +483,7 @@ export type NavigationItemType = {
|
||||||
isChild,
|
isChild,
|
||||||
router,
|
router,
|
||||||
}: {
|
}: {
|
||||||
item: NavigationItemType;
|
item: Pick<NavigationItemType, "href">;
|
||||||
isChild?: boolean;
|
isChild?: boolean;
|
||||||
router: NextRouter;
|
router: NextRouter;
|
||||||
}) => boolean;
|
}) => boolean;
|
||||||
|
@ -590,11 +578,6 @@ const navigation: NavigationItemType[] = [
|
||||||
href: "/insights",
|
href: "/insights",
|
||||||
icon: BarChart,
|
icon: BarChart,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "settings",
|
|
||||||
href: "/settings/my-account/profile",
|
|
||||||
icon: Settings,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const moreSeparatorIndex = navigation.findIndex((item) => item.name === MORE_SEPARATOR_NAME);
|
const moreSeparatorIndex = navigation.findIndex((item) => item.name === MORE_SEPARATOR_NAME);
|
||||||
|
@ -606,9 +589,12 @@ const { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMor
|
||||||
// We filter out the "more" separator in` desktop navigation
|
// We filter out the "more" separator in` desktop navigation
|
||||||
if (item.name !== MORE_SEPARATOR_NAME) items.desktopNavigationItems.push(item);
|
if (item.name !== MORE_SEPARATOR_NAME) items.desktopNavigationItems.push(item);
|
||||||
// Items for mobile bottom navigation
|
// Items for mobile bottom navigation
|
||||||
if (index < moreSeparatorIndex + 1 && !item.onlyDesktop) items.mobileNavigationBottomItems.push(item);
|
if (index < moreSeparatorIndex + 1 && !item.onlyDesktop) {
|
||||||
// Items for the "more" menu in mobile navigation
|
items.mobileNavigationBottomItems.push(item);
|
||||||
else items.mobileNavigationMoreItems.push(item);
|
} // Items for the "more" menu in mobile navigation
|
||||||
|
else {
|
||||||
|
items.mobileNavigationMoreItems.push(item);
|
||||||
|
}
|
||||||
return items;
|
return items;
|
||||||
},
|
},
|
||||||
{ desktopNavigationItems: [], mobileNavigationBottomItems: [], mobileNavigationMoreItems: [] }
|
{ desktopNavigationItems: [], mobileNavigationBottomItems: [], mobileNavigationMoreItems: [] }
|
||||||
|
@ -642,7 +628,7 @@ function useShouldDisplayNavigationItem(item: NavigationItemType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultIsCurrent: NavigationItemType["isCurrent"] = ({ isChild, item, router }) => {
|
const defaultIsCurrent: NavigationItemType["isCurrent"] = ({ isChild, item, router }) => {
|
||||||
return isChild ? item.href === router.asPath : router.asPath.startsWith(item.href);
|
return isChild ? item.href === router.asPath : item.href ? router.asPath.startsWith(item.href) : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NavigationItem: React.FC<{
|
const NavigationItem: React.FC<{
|
||||||
|
@ -785,10 +771,11 @@ type SideBarContainerProps = {
|
||||||
|
|
||||||
type SideBarProps = {
|
type SideBarProps = {
|
||||||
bannersHeight: number;
|
bannersHeight: number;
|
||||||
|
user?: UserAuth | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function SideBarContainer({ bannersHeight }: SideBarContainerProps) {
|
function SideBarContainer({ bannersHeight }: SideBarContainerProps) {
|
||||||
const { status } = useSession();
|
const { status, data } = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Make sure that Sidebar is rendered optimistically so that a refresh of pages when logged in have SideBar from the beginning.
|
// Make sure that Sidebar is rendered optimistically so that a refresh of pages when logged in have SideBar from the beginning.
|
||||||
|
@ -796,29 +783,83 @@ function SideBarContainer({ bannersHeight }: SideBarContainerProps) {
|
||||||
// Though when logged out, app store pages would temporarily show SideBar until session status is confirmed.
|
// Though when logged out, app store pages would temporarily show SideBar until session status is confirmed.
|
||||||
if (status !== "loading" && status !== "authenticated") return null;
|
if (status !== "loading" && status !== "authenticated") return null;
|
||||||
if (router.route.startsWith("/v2/settings/")) return null;
|
if (router.route.startsWith("/v2/settings/")) return null;
|
||||||
return <SideBar bannersHeight={bannersHeight} />;
|
return <SideBar bannersHeight={bannersHeight} user={data?.user} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SideBar({ bannersHeight }: SideBarProps) {
|
const getOrganizationUrl = (slug: string) =>
|
||||||
|
`${slug}.${process.env.NEXT_PUBLIC_WEBSITE_URL?.replace?.(/http(s*):\/\//, "")}`;
|
||||||
|
|
||||||
|
function SideBar({ bannersHeight, user }: SideBarProps) {
|
||||||
|
const { t, isLocaleReady } = useLocale();
|
||||||
|
const router = useRouter();
|
||||||
const orgBranding = useOrgBrandingValues();
|
const orgBranding = useOrgBrandingValues();
|
||||||
|
const publicPageUrl = orgBranding?.slug ? getOrganizationUrl(orgBranding?.slug) : "";
|
||||||
|
const bottomNavItems: NavigationItemType[] = [
|
||||||
|
...(user?.username
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: "view_public_page",
|
||||||
|
href: !!user?.organizationId
|
||||||
|
? publicPageUrl
|
||||||
|
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}`,
|
||||||
|
icon: ExternalLink,
|
||||||
|
target: "__blank",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "copy_public_page_link",
|
||||||
|
href: "",
|
||||||
|
onClick: (e: { preventDefault: () => void }) => {
|
||||||
|
e.preventDefault();
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
!!user?.organizationId
|
||||||
|
? publicPageUrl
|
||||||
|
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}`
|
||||||
|
);
|
||||||
|
showToast(t("link_copied"), "success");
|
||||||
|
},
|
||||||
|
icon: Copy,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
name: "settings",
|
||||||
|
href: user?.organizationId
|
||||||
|
? `/settings/teams/${user.organizationId}/profile`
|
||||||
|
: "/settings/my-account/profile",
|
||||||
|
icon: Settings,
|
||||||
|
},
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<aside
|
<aside
|
||||||
style={{ maxHeight: `calc(100vh - ${bannersHeight}px)`, top: `${bannersHeight}px` }}
|
style={{ maxHeight: `calc(100vh - ${bannersHeight}px)`, top: `${bannersHeight}px` }}
|
||||||
className="desktop-transparent bg-muted border-muted fixed left-0 hidden h-full max-h-screen w-14 flex-col overflow-y-auto overflow-x-hidden border-r dark:bg-gradient-to-tr dark:from-[#2a2a2a] dark:to-[#1c1c1c] md:sticky md:flex lg:w-56 lg:px-4">
|
className="desktop-transparent bg-muted border-muted fixed left-0 hidden h-full max-h-screen w-14 flex-col overflow-y-auto overflow-x-hidden border-r dark:bg-gradient-to-tr dark:from-[#2a2a2a] dark:to-[#1c1c1c] md:sticky md:flex lg:w-56 lg:px-3">
|
||||||
<div className="flex h-full flex-col justify-between py-3 lg:pt-6 ">
|
<div className="flex h-full flex-col justify-between py-3 lg:pt-6 ">
|
||||||
<header className="items-center justify-between md:hidden lg:flex">
|
<header className="items-center justify-between md:hidden lg:flex">
|
||||||
|
{orgBranding ? (
|
||||||
<Link href="/event-types" className="px-2">
|
<Link href="/event-types" className="px-2">
|
||||||
{orgBranding ? (
|
{orgBranding ? (
|
||||||
<div className="flex items-center gap-2 font-medium">
|
<div className="flex items-center gap-2 font-medium">
|
||||||
{orgBranding.logo && <Avatar alt="" imageSrc={orgBranding.logo} size="sm" />}
|
{orgBranding.logo && <Avatar alt="" imageSrc={orgBranding.logo} size="sm" />}
|
||||||
<p className="text text-sm">{orgBranding.name}</p>
|
<p className="text line-clamp-1 text-sm">
|
||||||
|
<span>{orgBranding.name}</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Logo small />
|
<Logo small />
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex space-x-2 rtl:space-x-reverse">
|
) : (
|
||||||
|
<div data-testid="user-dropdown-trigger">
|
||||||
|
<span className="hidden lg:inline">
|
||||||
|
<UserDropdown />
|
||||||
|
</span>
|
||||||
|
<span className="hidden md:inline lg:hidden">
|
||||||
|
<UserDropdown small />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex space-x-1 rtl:space-x-reverse">
|
||||||
<button
|
<button
|
||||||
color="minimal"
|
color="minimal"
|
||||||
onClick={() => window.history.back()}
|
onClick={() => window.history.back()}
|
||||||
|
@ -831,6 +872,11 @@ function SideBar({ bannersHeight }: SideBarProps) {
|
||||||
className="desktop-only hover:text-emphasis text-subtle group flex text-sm font-medium">
|
className="desktop-only hover:text-emphasis text-subtle group flex text-sm font-medium">
|
||||||
<ArrowRight className="group-hover:text-emphasis text-subtle h-4 w-4 flex-shrink-0" />
|
<ArrowRight className="group-hover:text-emphasis text-subtle h-4 w-4 flex-shrink-0" />
|
||||||
</button>
|
</button>
|
||||||
|
{!!orgBranding && (
|
||||||
|
<div data-testid="user-dropdown-trigger" className="flex items-center">
|
||||||
|
<UserDropdown small />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<KBarTrigger />
|
<KBarTrigger />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -847,14 +893,45 @@ function SideBar({ bannersHeight }: SideBarProps) {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Tips />
|
<Tips />
|
||||||
<div data-testid="user-dropdown-trigger">
|
{bottomNavItems.map(({ icon: Icon, ...item }) => (
|
||||||
<span className="hidden lg:inline">
|
<Tooltip side="right" content={t(item.name)} className="lg:hidden" key={item.name}>
|
||||||
<UserDropdown />
|
<ButtonOrLink
|
||||||
|
href={item.href || undefined}
|
||||||
|
aria-label={t(item.name)}
|
||||||
|
target={item.target}
|
||||||
|
className={classNames(
|
||||||
|
"text-left",
|
||||||
|
"[&[aria-current='page']]:bg-emphasis text-default group flex items-center rounded-md py-2 px-3 text-sm font-medium",
|
||||||
|
"[&[aria-current='page']]:text-emphasis mt-0.5 text-sm",
|
||||||
|
isLocaleReady ? "hover:bg-emphasis hover:text-emphasis" : ""
|
||||||
|
)}
|
||||||
|
aria-current={
|
||||||
|
defaultIsCurrent && defaultIsCurrent({ item: { href: item.href }, router })
|
||||||
|
? "page"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onClick={item.onClick}>
|
||||||
|
{!!Icon && (
|
||||||
|
<Icon
|
||||||
|
className="mr-2 h-4 w-4 flex-shrink-0 ltr:mr-2 rtl:ml-2 [&[aria-current='page']]:text-inherit"
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-current={
|
||||||
|
defaultIsCurrent && defaultIsCurrent({ item: { href: item.href }, router })
|
||||||
|
? "page"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isLocaleReady ? (
|
||||||
|
<span className="hidden w-full justify-between lg:flex">
|
||||||
|
<div className="flex">{t(item.name)}</div>
|
||||||
</span>
|
</span>
|
||||||
<span className="hidden md:inline lg:hidden">
|
) : (
|
||||||
<UserDropdown small />
|
<SkeletonText style={{ width: `${item.name.length * 10}px` }} className="h-[20px]" />
|
||||||
</span>
|
)}
|
||||||
</div>
|
</ButtonOrLink>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
<Credits />
|
<Credits />
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
import prisma, { baseEventTypeSelect } from "@calcom/prisma";
|
import prisma, { baseEventTypeSelect } from "@calcom/prisma";
|
||||||
import { SchedulingType } from "@calcom/prisma/enums";
|
import { SchedulingType } from "@calcom/prisma/enums";
|
||||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
import { WEBAPP_URL } from "../../../constants";
|
import { WEBAPP_URL } from "../../../constants";
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ export async function getTeamWithMembers(id?: number, slug?: string, userId?: nu
|
||||||
...eventType,
|
...eventType,
|
||||||
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
|
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
|
||||||
}));
|
}));
|
||||||
return { ...team, eventTypes, members };
|
return { ...team, metadata: teamMetadataSchema.parse(team.metadata), eventTypes, members };
|
||||||
}
|
}
|
||||||
|
|
||||||
// also returns team
|
// also returns team
|
||||||
|
|
|
@ -24,12 +24,12 @@ export type AvatarProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const sizesPropsBySize = {
|
const sizesPropsBySize = {
|
||||||
xs: "w-4 h-4", // 16px
|
xs: "w-4 h-4 min-w-4 min-h-4", // 16px
|
||||||
sm: "w-6 h-6", // 24px
|
sm: "w-6 h-6 min-w-6 min-h-6", // 24px
|
||||||
md: "w-8 h-8", // 32px
|
md: "w-8 h-8 min-w-8 min-h-8", // 32px
|
||||||
mdLg: "w-10 h-10", //40px
|
mdLg: "w-10 h-10 min-w-10 min-h-10", //40px
|
||||||
lg: "w-16 h-16", // 64px
|
lg: "w-16 h-16 min-w-16 min-h-16", // 64px
|
||||||
xl: "w-24 h-24", // 96px
|
xl: "w-24 h-24 min-w-24 min-h-24", // 96px
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export function Avatar(props: AvatarProps) {
|
export function Avatar(props: AvatarProps) {
|
||||||
|
@ -48,7 +48,7 @@ export function Avatar(props: AvatarProps) {
|
||||||
alt={alt}
|
alt={alt}
|
||||||
className={classNames("aspect-square rounded-full", sizesPropsBySize[size])}
|
className={classNames("aspect-square rounded-full", sizesPropsBySize[size])}
|
||||||
/>
|
/>
|
||||||
<AvatarPrimitive.Fallback delayMs={600} asChild={props.asChild}>
|
<AvatarPrimitive.Fallback delayMs={600} asChild={props.asChild} className="flex items-center">
|
||||||
<>
|
<>
|
||||||
{props.fallback && !gravatarFallbackMd5 && props.fallback}
|
{props.fallback && !gravatarFallbackMd5 && props.fallback}
|
||||||
{gravatarFallbackMd5 && (
|
{gravatarFallbackMd5 && (
|
||||||
|
|
Loading…
Reference in New Issue
Block a user