Files
Benny JooandDevin AI ab21c7f805 refactor: Cal.diy (#28903)
* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com

This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main:

- Rebrand Cal.com to Cal.diy across the entire codebase
- Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions
- Switch license from AGPL-3.0 to MIT
- Remove docs/ directory (migrated to Nextra at cal.diy)
- Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc.
- Clean up .env.example for self-hosted Cal.diy
- Update Docker image references to calcom/cal.diy
- Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork
- Add PR welcome bot for Cal.diy contributors
- Fix API v2 breaking changes oasdiff ignore entries
- Replace Blacksmith CI runners with default GitHub Actions

3893 files changed, 20789 insertions(+), 411020 deletions(-)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* rip out org related comments in api v2

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-04-15 09:52:36 -03:00

216 lines
9.1 KiB
TypeScript

import {
CalendarStoreContext,
createCalendarStore,
useCalendarStore,
} from "@calcom/features/calendars/weeklyview/state/store";
import classNames from "@calcom/ui/classNames";
import type React from "react";
import { useEffect, useMemo, useRef } from "react";
import "@calcom/features/calendars/weeklyview/styles/styles.css";
import type { CalendarComponentProps } from "@calcom/features/calendars/weeklyview/types/state";
import { getDaysBetweenDates, getHoursToDisplay } from "@calcom/features/calendars/weeklyview/utils";
import { CurrentTime } from "./currentTime";
import { DateValues } from "./DateValues";
import { AvailableCellsForDay, EmptyCell } from "./event/Empty";
import { EventList } from "./event/EventList";
import { SchedulerColumns } from "./grid";
import { SchedulerHeading } from "./heading/SchedulerHeading";
import { HorizontalLines } from "./horizontalLines";
import { Spinner } from "./spinner/Spinner";
import { VerticalLines } from "./verticalLines";
function CalendarInner(props: CalendarComponentProps) {
const container = useRef<HTMLDivElement | null>(null);
const containerNav = useRef<HTMLDivElement | null>(null);
const containerOffset = useRef<HTMLDivElement | null>(null);
const schedulerGrid = useRef<HTMLOListElement | null>(null);
const initialState = useCalendarStore((state) => state.initState);
const startDate = useCalendarStore((state) => state.startDate);
const endDate = useCalendarStore((state) => state.endDate);
const startHour = useCalendarStore((state) => state.startHour || 0);
const endHour = useCalendarStore((state) => state.endHour || 23);
const usersCellsStopsPerHour = useCalendarStore((state) => state.gridCellsPerHour || 4);
const availableTimeslots = useCalendarStore((state) => state.availableTimeslots);
const hideHeader = useCalendarStore((state) => state.hideHeader);
const timezone = useCalendarStore((state) => state.timezone);
const showBackgroundPattern = useCalendarStore((state) => state.showBackgroundPattern);
const showBorder = useCalendarStore((state) => state.showBorder ?? true);
const borderColor = useCalendarStore((state) => state.borderColor ?? "default");
const scrollToCurrentTime = useCalendarStore((state) => state.scrollToCurrentTime ?? true);
const updateCurrentTimeOnFocus = useCalendarStore((state) => state.updateCurrentTimeOnFocus ?? false);
const days = useMemo(() => getDaysBetweenDates(startDate, endDate), [startDate, endDate]);
const hours = useMemo(
() => getHoursToDisplay(startHour || 0, endHour || 23, timezone),
[startHour, endHour, timezone]
);
const numberOfGridStopsPerDay = hours.length * usersCellsStopsPerHour;
const hourSize = 58;
// Initalise State on initial mount and when props change
useEffect(() => {
initialState(props);
}, [props, initialState]);
return (
<MobileNotSupported>
<div
className={classNames("scheduler-wrapper flex h-full w-full flex-col")}
style={
{
"--one-minute-height": `calc(${hourSize}px/60)`,
"--gridDefaultSize": `${hourSize}px`,
} as React.CSSProperties // This can't live in the css file because it's a dynamic value and css variable gets super
}>
{hideHeader !== true && <SchedulerHeading />}
{props.isPending && <Spinner />}
<div
ref={container}
className="bg-default dark:bg-cal-muted relative isolate flex h-full flex-auto flex-col">
<div
style={{ width: "165%" }}
className="flex max-w-full flex-none flex-col sm:max-w-none md:max-w-full">
<DateValues
containerNavRef={containerNav}
days={days}
showBorder={showBorder}
borderColor={borderColor}
/>
<div className="relative flex flex-auto">
<CurrentTime
timezone={timezone}
scrollToCurrentTime={scrollToCurrentTime}
updateOnFocus={updateCurrentTimeOnFocus}
/>
<div
className={classNames(
"bg-default dark:bg-cal-muted ring-muted sticky left-0 z-10 w-16 flex-none ring-1",
showBorder &&
(borderColor === "subtle"
? "border-subtle border-l border-r"
: "border-default border-l border-r")
)}
/>
<div
className="grid flex-auto grid-cols-1 grid-rows-1 [--disabled-gradient-background:#F8F9FB] [--disabled-gradient-foreground:#E6E7EB] dark:[--disabled-gradient-background:#262626] dark:[--disabled-gradient-foreground:#393939]"
style={
showBackgroundPattern === false
? undefined
: {
backgroundColor: "var(--disabled-gradient-background)",
background:
"repeating-linear-gradient(-45deg, var(--disabled-gradient-background), var(--disabled-gradient-background) 2.5px, var(--disabled-gradient-foreground) 2.5px, var(--disabled-gradient-foreground) 5px)",
}
}>
<HorizontalLines
hours={hours}
numberOfGridStopsPerCell={usersCellsStopsPerHour}
containerOffsetRef={containerOffset}
borderColor={borderColor}
/>
<VerticalLines days={days} borderColor={borderColor} />
<SchedulerColumns
offsetHeight={containerOffset.current?.offsetHeight}
gridStopsPerDay={numberOfGridStopsPerDay}>
{/*Loop over events per day */}
{days.map((day, i) => {
return (
<li key={day.toISOString()} className="relative" style={{ gridColumnStart: i + 1 }}>
<EventList day={day} />
{/* <BlockedList day={day} containerRef={container} /> */}
</li>
);
})}
</SchedulerColumns>
{/* Empty Cells */}
<SchedulerColumns
ref={schedulerGrid}
offsetHeight={containerOffset.current?.offsetHeight}
gridStopsPerDay={numberOfGridStopsPerDay}>
<>
{[...Array(days.length)].map((_, i) => (
<li
className="relative"
key={i}
style={{
gridColumnStart: i + 1,
gridRow: `1 / span ${numberOfGridStopsPerDay}`,
}}>
{/* While startDate < endDate: */}
{availableTimeslots ? (
<AvailableCellsForDay
key={days[i].toISOString()}
timezone={timezone}
day={days[i]}
startHour={startHour}
availableSlots={availableTimeslots}
/>
) : (
<>
{[...Array(numberOfGridStopsPerDay)].map((_, j) => {
const key = `${i}-${j}`;
return (
<EmptyCell
key={key}
day={days[i]}
gridCellIdx={j}
totalGridCells={numberOfGridStopsPerDay}
selectionLength={endHour - startHour}
startHour={startHour}
timezone={timezone}
/>
);
})}
</>
)}
</li>
))}
</>
</SchedulerColumns>
</div>
</div>
</div>
</div>
</div>
</MobileNotSupported>
);
}
export function Calendar(props: CalendarComponentProps) {
const storeRef = useRef<ReturnType<typeof createCalendarStore> | null>(null);
if (!storeRef.current) {
storeRef.current = createCalendarStore();
storeRef.current.getState().initState(props);
}
useEffect(() => {
if (storeRef.current) {
storeRef.current.getState().initState(props);
}
}, [props]);
return (
<CalendarStoreContext.Provider value={storeRef.current}>
<CalendarInner {...props} />
</CalendarStoreContext.Provider>
);
}
/** @todo Will be removed once we have mobile support */
const MobileNotSupported = ({ children }: { children: React.ReactNode }) => {
return (
<>
<div className="flex h-full flex-col items-center justify-center sm:hidden">
<h1 className="text-2xl font-bold">Mobile not supported yet </h1>
<p className="text-subtle">Please use a desktop browser to view this page</p>
</div>
<div className="hidden h-full sm:block">{children}</div>
</>
);
};