refactor: remove circular dependencies from CalendarView atom (#27850)

* fix: decouple large calendar from features

* fix: move calendar to features since this is the better approach

* chore: move OOO slots to features

* feat: add DefaultOutOfOfficeSlot fallback for calendar OOO rendering

Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com>

* chore: cleanup unused LargeCalendar component

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Rajiv Sahal
2026-02-17 08:14:33 +00:00
committed by GitHub
co-authored by rajiv@cal.com Devin AI
parent 9603831def
commit 38b43f75ba
26 changed files with 155 additions and 58 deletions
@@ -3,7 +3,7 @@
import { useState } from "react";
import dayjs from "@calcom/dayjs";
import { Calendar } from "@calcom/web/modules/calendars/weeklyview/components/Calendar";
import { Calendar } from "@calcom/features/calendars/weeklyview/components/Calendar";
import type { CalendarEvent } from "@calcom/features/calendars/weeklyview/types/events";
import type { CalendarComponentProps, Hours } from "@calcom/features/calendars/weeklyview/types/state";
@@ -4,7 +4,7 @@ import dayjs from "@calcom/dayjs";
import { useTimePreferences } from "@calcom/features/bookings/lib";
import type { CalendarEvent } from "@calcom/features/calendars/weeklyview/types/events";
import { useGetTheme } from "@calcom/lib/hooks/useTheme";
import { Calendar } from "@calcom/web/modules/calendars/weeklyview/components/Calendar";
import { Calendar } from "@calcom/features/calendars/weeklyview/components/Calendar";
import { useBanners } from "@calcom/web/modules/shell/banners/useBanners";
import { useEffect, useMemo } from "react";
import { useBookingDetailsSheetStore } from "../store/bookingDetailsSheetStore";
@@ -1,17 +1,19 @@
import { useMemo, useEffect } from "react";
import { useEffect, useMemo } from "react";
import dayjs from "@calcom/dayjs";
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
import { useOverlayCalendarStore } from "@calcom/features/bookings/Booker/components/OverlayCalendar/store";
import { useAvailableTimeSlots } from "@calcom/features/bookings/Booker/hooks/useAvailableTimeSlots";
import { useBookerTime } from "@calcom/features/bookings/Booker/hooks/useBookerTime";
import { getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
import type { BookerEvent } from "@calcom/features/bookings/types";
import { Calendar } from "@calcom/web/modules/calendars/weeklyview/components/Calendar";
import { Calendar } from "@calcom/features/calendars/weeklyview/components/Calendar";
import type { CalendarEvent } from "@calcom/features/calendars/weeklyview/types/events";
import { localStorage } from "@calcom/lib/webstorage";
import type { useScheduleForEventReturnType } from "@calcom/web/modules/schedules/hooks/useEvent";
import { getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
import { useOverlayCalendarStore } from "@calcom/features/bookings/Booker/components/OverlayCalendar/store";
import { OutOfOfficeInSlots } from "./OutOfOfficeInSlots";
export const LargeCalendar = ({
extraDays,
@@ -78,6 +80,7 @@ export const LargeCalendar = ({
hoverEventDuration={eventDuration}
hideHeader
timezone={timezone}
renderOutOfOffice={(props) => <OutOfOfficeInSlots {...props} />}
/>
</div>
);
@@ -5,7 +5,7 @@ import { usePathname } from "next/navigation";
import { useMemo } from "react";
import dayjs from "@calcom/dayjs";
import { Calendar } from "@calcom/web/modules/calendars/weeklyview/components/Calendar";
import { Calendar } from "@calcom/features/calendars/weeklyview/components/Calendar";
import type { CalendarComponentProps, Hours } from "@calcom/features/calendars/weeklyview/types/state";
import { weekdayDates } from "@calcom/features/calendars/weeklyview/utils";
import { CURRENT_TIMEZONE } from "@calcom/lib/timezoneConstants";
@@ -1,13 +1,17 @@
import { useSession } from "next-auth/react";
import { useMemo } from "react";
import dayjs from "@calcom/dayjs";
import { useAvailableTimeSlots } from "@calcom/features/bookings/Booker/hooks/useAvailableTimeSlots";
import { useTimePreferences } from "@calcom/features/bookings/lib/timePreferences";
import { useSchedule } from "~/schedules/hooks/useSchedule";
import { Calendar } from "@calcom/features/calendars/weeklyview/components/Calendar";
import { useTroubleshooterStore } from "@calcom/features/troubleshooter/store";
import { BookingStatus } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import { Calendar } from "@calcom/web/modules/calendars/weeklyview/components/Calendar";
import { useSession } from "next-auth/react";
import { useMemo } from "react";
import { useSchedule } from "~/schedules/hooks/useSchedule";
import { OutOfOfficeInSlots } from "../../bookings/components/OutOfOfficeInSlots";
export const LargeCalendar = ({ extraDays }: { extraDays: number }) => {
const { timezone } = useTimePreferences();
@@ -125,6 +129,7 @@ export const LargeCalendar = ({ extraDays }: { extraDays: number }) => {
hoverEventDuration={30}
hideHeader
timezone={timezone}
renderOutOfOffice={(props) => <OutOfOfficeInSlots {...props} />}
/>
</div>
);
@@ -39,6 +39,7 @@ function CalendarInner(props: CalendarComponentProps) {
const borderColor = useCalendarStore((state) => state.borderColor ?? "default");
const scrollToCurrentTime = useCalendarStore((state) => state.scrollToCurrentTime ?? true);
const updateCurrentTimeOnFocus = useCalendarStore((state) => state.updateCurrentTimeOnFocus ?? false);
const renderOutOfOffice = useCalendarStore((state) => state.renderOutOfOffice);
const days = useMemo(() => getDaysBetweenDates(startDate, endDate), [startDate, endDate]);
@@ -148,6 +149,7 @@ function CalendarInner(props: CalendarComponentProps) {
day={days[i]}
startHour={startHour}
availableSlots={availableTimeslots}
renderOutOfOffice={renderOutOfOffice}
/>
) : (
<>
@@ -1,11 +1,9 @@
import React from "react";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
import { useCalendarStore } from "@calcom/features/calendars/weeklyview/state/store";
import type { BorderColor } from "@calcom/features/calendars/weeklyview/types/common";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
import type React from "react";
type Props = {
showBorder: boolean;
@@ -0,0 +1,67 @@
import type { OutOfOfficeRenderProps } from "@calcom/features/calendars/weeklyview/types/state";
import ServerTrans from "@calcom/lib/components/ServerTrans";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
export function DefaultOutOfOfficeSlot(props: OutOfOfficeRenderProps) {
const { t } = useLocale();
const {
fromUser,
toUser,
emoji = "\u{1F3DD}\uFE0F",
reason,
borderDashed = true,
className,
notes,
showNotePublicly,
} = props;
const isHoliday = !fromUser && reason;
if (!isHoliday && !fromUser) return null;
return (
<div className={classNames("relative h-full pb-5", className)}>
<div
className={classNames(
"flex h-full flex-col items-center justify-start rounded-md border bg-white px-4 py-4 font-normal dark:bg-transparent",
borderDashed && "border-dashed"
)}>
<div className="bg-emphasis flex h-14 w-14 flex-col items-center justify-center rounded-full">
<span className="m-auto text-center text-lg">{emoji}</span>
</div>
<div className="stack-y-2 max-h-[300px] w-full overflow-y-auto text-center">
{isHoliday ? (
<>
<p className="mt-2 text-base font-bold">{reason}</p>
<p className="text-subtle text-center text-sm">{t("holiday_no_availability")}</p>
</>
) : (
<>
<p className="mt-2 text-base font-bold">
{t("ooo_user_is_ooo", { displayName: fromUser?.displayName })}
</p>
{notes && showNotePublicly && (
<p className="text-subtle mt-2 max-h-[120px] overflow-y-auto break-words px-2 text-center text-sm italic">
{notes}
</p>
)}
{fromUser?.displayName && toUser?.displayName && (
<p className="text-center text-sm">
<ServerTrans
t={t}
i18nKey="ooo_slots_returning"
values={{ displayName: toUser.displayName }}
components={[<strong key="username">username</strong>]}
/>
</p>
)}
</>
)}
</div>
</div>
</div>
);
}
@@ -1,9 +1,7 @@
import dayjs from "@calcom/dayjs";
import { useCalendarStore } from "@calcom/features/calendars/weeklyview/state/store";
import { useMemo } from "react";
import { shallow } from "zustand/shallow";
import dayjs from "@calcom/dayjs";
import { useCalendarStore } from "@calcom/features/calendars/weeklyview/state/store";
import { BlockedTimeCell } from "./BlockedTimeCell";
type Props = {
@@ -1,16 +1,18 @@
import { useMemo } from "react";
import { shallow } from "zustand/shallow";
import type { Dayjs } from "@calcom/dayjs";
import dayjs from "@calcom/dayjs";
import { useTimePreferences } from "@calcom/features/bookings/lib";
import classNames from "@calcom/ui/classNames";
import { OutOfOfficeInSlots } from "@calcom/web/modules/bookings/components/OutOfOfficeInSlots";
import { DefaultOutOfOfficeSlot } from "@calcom/features/calendars/weeklyview/components/DefaultOutOfOfficeSlot";
import { useCalendarStore } from "@calcom/features/calendars/weeklyview/state/store";
import type { CalendarAvailableTimeslots } from "@calcom/features/calendars/weeklyview/types/state";
import type {
CalendarAvailableTimeslots,
OutOfOfficeRenderProps,
} from "@calcom/features/calendars/weeklyview/types/state";
import type { GridCellToDateProps } from "@calcom/features/calendars/weeklyview/utils";
import { gridCellToDateTime } from "@calcom/features/calendars/weeklyview/utils";
import classNames from "@calcom/ui/classNames";
import type { ReactNode } from "react";
import { useMemo } from "react";
import { shallow } from "zustand/shallow";
type EmptyCellProps = GridCellToDateProps & {
isDisabled?: boolean;
@@ -37,9 +39,16 @@ type AvailableCellProps = {
availableSlots: CalendarAvailableTimeslots;
day: GridCellToDateProps["day"];
startHour: GridCellToDateProps["startHour"];
renderOutOfOffice?: (props: OutOfOfficeRenderProps) => ReactNode;
};
export function AvailableCellsForDay({ timezone, availableSlots, day, startHour }: AvailableCellProps) {
export function AvailableCellsForDay({
timezone,
availableSlots,
day,
startHour,
renderOutOfOffice,
}: AvailableCellProps) {
const date = dayjs(day);
const dateFormatted = date.format("YYYY-MM-DD");
const slotsForToday = availableSlots && availableSlots[dateFormatted];
@@ -98,22 +107,23 @@ export function AvailableCellsForDay({ timezone, availableSlots, day, startHour
if (slots.startEndTimeDuration) {
const { firstSlot, startEndTimeDuration } = slots;
const renderOOO = renderOutOfOffice ?? ((p: OutOfOfficeRenderProps) => <DefaultOutOfOfficeSlot {...p} />);
return (
<CustomCell
timeSlot={dayjs(firstSlot?.start).tz(slots.timezone)}
topOffsetMinutes={slots.slots[0]?.topOffsetMinutes}
startEndTimeDuration={startEndTimeDuration}>
<OutOfOfficeInSlots
fromUser={firstSlot?.fromUser}
toUser={firstSlot?.toUser}
reason={firstSlot?.reason}
emoji={firstSlot?.emoji}
notes={firstSlot?.notes}
showNotePublicly={firstSlot?.showNotePublicly}
borderDashed={false}
date={dateFormatted}
className="pb-0"
/>
{renderOOO({
fromUser: firstSlot?.fromUser,
toUser: firstSlot?.toUser,
reason: firstSlot?.reason,
emoji: firstSlot?.emoji,
notes: firstSlot?.notes,
showNotePublicly: firstSlot?.showNotePublicly,
borderDashed: false,
date: dateFormatted,
className: "pb-0",
})}
</CustomCell>
);
}
@@ -1,11 +1,9 @@
import { useEffect, useMemo, useState } from "react";
import { shallow } from "zustand/shallow";
import dayjs from "@calcom/dayjs";
import classNames from "@calcom/ui/classNames";
import { useCalendarStore } from "@calcom/features/calendars/weeklyview/state/store";
import { calculateEventLayouts, createLayoutMap } from "@calcom/features/calendars/weeklyview/utils/overlap";
import classNames from "@calcom/ui/classNames";
import { useEffect, useMemo, useState } from "react";
import { shallow } from "zustand/shallow";
import { Event } from "./Event";
type Props = {
@@ -1,9 +1,8 @@
import dayjs from "@calcom/dayjs";
import { useCalendarStore } from "@calcom/features/calendars/weeklyview/state/store";
import { Button } from "@calcom/ui/components/button";
import { ButtonGroup } from "@calcom/ui/components/buttonGroup";
import { useCalendarStore } from "@calcom/features/calendars/weeklyview/state/store";
export function SchedulerHeading() {
const { startDate, endDate, handleDateChange } = useCalendarStore((state) => ({
startDate: dayjs(state.startDate),
@@ -1,10 +1,8 @@
import { useId } from "react";
import type dayjs from "@calcom/dayjs";
import { useTimePreferences } from "@calcom/features/bookings/lib";
import classNames from "@calcom/ui/classNames";
import type { BorderColor } from "@calcom/features/calendars/weeklyview/types/common";
import classNames from "@calcom/ui/classNames";
import { useId } from "react";
export const HorizontalLines = ({
hours,
@@ -1,7 +1,6 @@
import type dayjs from "@calcom/dayjs";
import classNames from "@calcom/ui/classNames";
import type { BorderColor } from "@calcom/features/calendars/weeklyview/types/common";
import classNames from "@calcom/ui/classNames";
export const VerticalLines = ({ days, borderColor }: { days: dayjs.Dayjs[]; borderColor: BorderColor }) => {
const isRTL = () => {
@@ -1,8 +1,23 @@
import type { ReactNode } from "react";
import type { IFromUser, IToUser } from "@calcom/features/availability/lib/getUserAvailability";
import type { TimeRange } from "@calcom/types/schedule";
import type { BorderColor } from "./common";
import type { CalendarEvent } from "./events";
export type OutOfOfficeRenderProps = {
date: string;
fromUser?: IFromUser;
toUser?: IToUser;
emoji?: string;
reason?: string;
notes?: string | null;
showNotePublicly?: boolean;
borderDashed?: boolean;
className?: string;
};
export type View = "month" | "week" | "day";
export type Hours =
| 0
@@ -162,6 +177,11 @@ export type CalendarState = {
* @default false
*/
updateCurrentTimeOnFocus?: boolean;
/**
* Render function for out-of-office slots.
* Web consumers pass OutOfOfficeInSlots, atoms/platform can pass their own or null.
*/
renderOutOfOffice?: (props: OutOfOfficeRenderProps) => ReactNode;
};
export type CalendarComponentProps = CalendarPublicActions & CalendarState & { isPending?: boolean };
@@ -1,6 +1,6 @@
import { Header } from "@calcom/features/bookings/components/Header";
import { BookerSection } from "@calcom/features/bookings/components/Section";
import { LargeCalendar } from "@calcom/web/modules/calendar-view/components/LargeCalendar";
import { LargeCalendar } from "./components/LargeCalendar";
import { bookerLayoutOptions } from "@calcom/prisma/zod-utils";
import { AtomsWrapper } from "../src/components/atoms-wrapper";
@@ -10,7 +10,7 @@ import { BookerSection } from "@calcom/features/bookings/components/Section";
import { useAvailableTimeSlots } from "@calcom/features/bookings/Booker/hooks/useAvailableTimeSlots";
import { useBookerLayout } from "@calcom/features/bookings/Booker/hooks/useBookerLayout";
import { useTimePreferences } from "@calcom/features/bookings/lib";
import { LargeCalendar } from "@calcom/web/modules/calendar-view/components/LargeCalendar";
import { LargeCalendar } from "./components/LargeCalendar";
import { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents";
import { useTimesForSchedule } from "@calcom/features/schedules/hooks/useTimesForSchedule";
@@ -4,14 +4,14 @@ import dayjs from "@calcom/dayjs";
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
import { useBookerTime } from "@calcom/features/bookings/Booker/hooks/useBookerTime";
import type { BookerEvent } from "@calcom/features/bookings/types";
import { Calendar } from "@calcom/web/modules/calendars/weeklyview/components/Calendar";
import { Calendar } from "@calcom/features/calendars/weeklyview/components/Calendar";
import type { CalendarAvailableTimeslots } from "@calcom/features/calendars/weeklyview/types/state";
import { localStorage } from "@calcom/lib/webstorage";
import type { BookingStatus } from "@calcom/prisma/enums";
import { useBookings } from "@calcom/atoms/hooks/bookings/useBookings";
import { useCalendarsBusyTimes } from "@calcom/atoms/hooks/useCalendarsBusyTimes";
import { useConnectedCalendars } from "@calcom/atoms/hooks/useConnectedCalendars";
import { useBookings } from "../../hooks/bookings/useBookings";
import { useCalendarsBusyTimes } from "../../hooks/useCalendarsBusyTimes";
import { useConnectedCalendars } from "../../hooks/useConnectedCalendars";
import { getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
export const LargeCalendar = ({