perf: reduce bundle size (#8337)

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
Udit Takkar
2023-04-19 19:45:08 +05:30
committed by GitHub
co-authored by zomars Hariom Balhara Peer Richelsen Keith Williams
parent ccd589ef38
commit bdbf9bd04d
32 changed files with 117 additions and 1014 deletions
-167
View File
@@ -1,167 +0,0 @@
import type { FormEvent } from "react";
import { useCallback, useEffect, useState } from "react";
import Cropper from "react-easy-crop";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui";
import type { Area } from "@lib/cropImage";
import { getCroppedImg } from "@lib/cropImage";
import { useFileReader } from "@lib/hooks/useFileReader";
import Slider from "@components/Slider";
type ImageUploaderProps = {
id: string;
buttonMsg: string;
handleAvatarChange: (imageSrc: string) => void;
imageSrc?: string;
target: string;
};
interface FileEvent<T = Element> extends FormEvent<T> {
target: EventTarget & T;
}
// This is separate to prevent loading the component until file upload
function CropContainer({
onCropComplete,
imageSrc,
}: {
imageSrc: string;
onCropComplete: (croppedAreaPixels: Area) => void;
}) {
const { t } = useLocale();
const [crop, setCrop] = useState({ x: 0, y: 0 });
const [zoom, setZoom] = useState(1);
const handleZoomSliderChange = (value: number) => {
value < 1 ? setZoom(1) : setZoom(value);
};
return (
<div className="crop-container h-40 max-h-40 w-40 rounded-full">
<div className="relative h-40 w-40 rounded-full">
<Cropper
image={imageSrc}
crop={crop}
zoom={zoom}
aspect={1}
onCropChange={setCrop}
onCropComplete={(croppedArea, croppedAreaPixels) => onCropComplete(croppedAreaPixels)}
onZoomChange={setZoom}
/>
</div>
<Slider
value={zoom}
min={1}
max={3}
step={0.1}
label={t("slide_zoom_drag_instructions")}
changeHandler={handleZoomSliderChange}
/>
</div>
);
}
/** @deprecated Use `packages/ui/v2/core/ImageUploader.tsx` */
export default function ImageUploader({
target,
id,
buttonMsg,
handleAvatarChange,
...props
}: ImageUploaderProps) {
const { t } = useLocale();
const [imageSrc, setImageSrc] = useState<string | null>(null);
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
const [{ result }, setFile] = useFileReader({
method: "readAsDataURL",
});
useEffect(() => {
if (props.imageSrc) setImageSrc(props.imageSrc);
}, [props.imageSrc]);
const onInputFile = (e: FileEvent<HTMLInputElement>) => {
if (!e.target.files?.length) {
return;
}
setFile(e.target.files[0]);
};
const showCroppedImage = useCallback(
async (croppedAreaPixels: Area | null) => {
try {
if (!croppedAreaPixels) return;
const croppedImage = await getCroppedImg(
result as string /* result is always string when using readAsDataUrl */,
croppedAreaPixels
);
setImageSrc(croppedImage);
handleAvatarChange(croppedImage);
} catch (e) {
console.error(e);
}
},
[result, handleAvatarChange]
);
return (
<Dialog
onOpenChange={
(opened) => !opened && setFile(null) // unset file on close
}>
<DialogTrigger asChild>
<div className="flex items-center">
<Button color="secondary" type="button" className="py-1 text-xs">
{buttonMsg}
</Button>
</div>
</DialogTrigger>
<DialogContent>
<div className="mb-4 sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:text-left">
<h3 className="font-cal text-emphasis text-lg leading-6" id="modal-title">
{t("upload_target", { target })}
</h3>
</div>
</div>
<div className="mb-4">
<div className="cropper mt-6 flex flex-col items-center justify-center p-8">
{!result && (
<div className="bg-muted flex h-20 max-h-20 w-20 items-center justify-start rounded-full">
{!imageSrc && (
<p className="text-inverted w-full text-center text-sm sm:text-xs">
{t("no_target", { target })}
</p>
)}
{imageSrc && (
// eslint-disable-next-line @next/next/no-img-element
<img className="h-20 w-20 rounded-full" src={imageSrc} alt={target} />
)}
</div>
)}
{result && <CropContainer imageSrc={result as string} onCropComplete={setCroppedAreaPixels} />}
<label className="border-default bg-default hover:bg-mutedover:text-emphasis dark:hover:bg-inverted text-default dark:text-inverted focus:ring-empthasis mt-8 rounded-sm border px-3 py-1 text-xs font-medium leading-4 focus:outline-none focus:ring-2 focus:ring-offset-1 dark:border-gray-800 dark:bg-transparent">
<input
onInput={onInputFile}
type="file"
name={id}
placeholder={t("upload_image")}
className="pointer-events-none absolute mt-4 opacity-0"
accept="image/*"
/>
{t("choose_a_file")}
</label>
</div>
</div>
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogClose onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</DialogClose>
<DialogClose color="secondary">{t("cancel")}</DialogClose>
</div>
</DialogContent>
</Dialog>
);
}
+2 -2
View File
@@ -1,4 +1,3 @@
import { AdminRequired } from "components/ui/AdminRequired";
import { noop } from "lodash";
import type { LinkProps } from "next/link";
import Link from "next/link";
@@ -6,6 +5,7 @@ import { useRouter } from "next/router";
import type { FC, MouseEventHandler } from "react";
import { Fragment } from "react";
import { PermissionContainer } from "@calcom/features/auth/PermissionContainer";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@lib/classNames";
@@ -60,7 +60,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
}
: noop;
const Component = tab.adminRequired ? AdminRequired : Fragment;
const Component = tab.adminRequired ? PermissionContainer : Fragment;
const className = tab.className || "";
return (
<Component key={tab.name}>
-27
View File
@@ -1,27 +0,0 @@
import * as SliderPrimitive from "@radix-ui/react-slider";
import React from "react";
const Slider = ({
value,
label,
changeHandler,
...props
}: Omit<SliderPrimitive.SliderProps, "value"> & {
value: number;
label: string;
changeHandler: (value: number) => void;
}) => (
<SliderPrimitive.Root
className="slider mt-2"
value={[value]}
aria-label={label}
onValueChange={(value: number[]) => changeHandler(value[0] ?? value)}
{...props}>
<SliderPrimitive.Track className="slider-track">
<SliderPrimitive.Range className="slider-range" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="slider-thumb" />
</SliderPrimitive.Root>
);
export default Slider;
+1 -1
View File
@@ -6,7 +6,7 @@ import React, { useState } from "react";
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { InstallAppButton, AppDependencyComponent } from "@calcom/app-store/components";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import Shell from "@calcom/features/shell/Shell";
import classNames from "@calcom/lib/classNames";
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
-17
View File
@@ -1,17 +0,0 @@
import { useSession } from "next-auth/react";
import type { FC } from "react";
import { Fragment } from "react";
type AdminRequiredProps = {
as?: keyof JSX.IntrinsicElements;
children?: React.ReactNode;
};
/** @deprecated use PermssionContainer instead. Will delete once V2 goes live */
export const AdminRequired: FC<AdminRequiredProps> = ({ children, as, ...rest }) => {
const session = useSession();
if (session.data?.user.role !== "ADMIN") return null;
const Component = as ?? Fragment;
return <Component {...rest}>{children}</Component>;
};
-4
View File
@@ -115,8 +115,6 @@
"react-select": "^5.7.0",
"react-timezone-select": "^1.4.0",
"react-use-intercom": "1.5.1",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.7",
"remark": "^14.0.2",
"rrule": "^2.7.1",
"sanitize-html": "^2.10.0",
@@ -155,8 +153,6 @@
"@types/qrcode": "^1.4.3",
"@types/react": "18.0.26",
"@types/react-phone-number-input": "^3.0.14",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@types/sanitize-html": "^2.9.0",
"@types/stripe": "^8.0.417",
"@types/uuid": "8.3.1",
@@ -1,7 +1,7 @@
import { useState } from "react";
import type { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
import LicenseRequired from "@calcom/ee/common/components/v2/LicenseRequired";
import LicenseRequired from "@calcom/ee/common/components/LicenseRequired";
import ApiKeyDialogForm from "@calcom/features/ee/api-keys/components/ApiKeyDialogForm";
import ApiKeyListItem from "@calcom/features/ee/api-keys/components/ApiKeyListItem";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
+1 -1
View File
@@ -5,7 +5,7 @@ import type { CSSProperties } from "react";
import type { SubmitHandler } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -19,7 +19,8 @@ export const PermissionContainer: FC<AdminRequiredProps> = ({
const session = useSession();
// Admin can do everything
if (session.data?.user.role !== roleRequired || !UserPermissionRole.ADMIN) return null;
if (session.data?.user.role !== roleRequired && session.data?.user.role != UserPermissionRole.ADMIN)
return null;
const Component = as ?? Fragment;
return <Component {...rest}>{children}</Component>;
};
@@ -3,7 +3,7 @@ import { Controller, useForm } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import type { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
import LicenseRequired from "@calcom/ee/common/components/v2/LicenseRequired";
import LicenseRequired from "@calcom/ee/common/components/LicenseRequired";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, DatePicker, DialogFooter, Form, showToast, Switch, TextField, Tooltip } from "@calcom/ui";
@@ -1,14 +1,9 @@
/**
* @deprecated file
* All new changes should be made to the V2 file in
* `/packages/features/ee/common/components/v2/LicenseRequired.tsx`
*/
import DOMPurify from "dompurify";
import { useSession } from "next-auth/react";
import type { AriaRole, ComponentType } from "react";
import React, { Fragment } from "react";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { APP_NAME, CONSOLE_URL, SUPPORT_MAIL_ADDRESS, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EmptyScreen } from "@calcom/ui";
import { AlertTriangle } from "@calcom/ui/components/icon";
@@ -20,20 +15,15 @@ type LicenseRequiredProps = {
children: React.ReactNode;
};
/**
* @deprecated file
* All new changes should be made to the V2 file in
* `/packages/features/ee/common/components/v2/LicenseRequired.tsx`
* This component will only render it's children if the installation has a valid
* license.
*/
const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) => {
const { t } = useLocale();
const session = useSession();
const { t } = useLocale();
const Component = as || Fragment;
const hasValidLicense = session.data ? session.data.hasValidLicense : null;
return (
<Component {...rest}>
{session.data?.hasValidLicense ? (
{hasValidLicense === null || hasValidLicense ? (
children
) : (
<EmptyScreen
@@ -44,13 +34,12 @@ const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) =
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(
t("enterprise_license_description", {
consoleUrl: `<a href="https://go.cal.com/console" target="_blank" class="underline">
consoleUrl: `<a href="${CONSOLE_URL}" target="_blank" rel="noopener noreferrer" class="underline">
${APP_NAME}
</a>`,
setupUrl: `<a href="${WEBAPP_URL}/auth/setup">/auth/setup</a>`,
supportMail: `<a href="mailto:sales@cal.com" class="underline">
sales@cal.com
</a>`,
setupUrl: `<a href="${WEBAPP_URL}/auth/setup" class="underline">/auth/setup</a>`,
supportMail: `<a href="mailto:${SUPPORT_MAIL_ADDRESS}" class="underline">
${SUPPORT_MAIL_ADDRESS}</a>`,
})
),
}}
@@ -1,64 +0,0 @@
import DOMPurify from "dompurify";
import { useSession } from "next-auth/react";
import type { AriaRole, ComponentType } from "react";
import React, { Fragment } from "react";
import { APP_NAME, CONSOLE_URL, SUPPORT_MAIL_ADDRESS, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EmptyScreen } from "@calcom/ui";
import { AlertTriangle } from "@calcom/ui/components/icon";
type LicenseRequiredProps = {
as?: keyof JSX.IntrinsicElements | "";
className?: string;
role?: AriaRole | undefined;
children: React.ReactNode;
};
const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) => {
const session = useSession();
const { t } = useLocale();
const Component = as || Fragment;
const hasValidLicense = session.data ? session.data.hasValidLicense : null;
return (
<Component {...rest}>
{hasValidLicense === null || hasValidLicense ? (
children
) : (
<EmptyScreen
Icon={AlertTriangle}
headline={t("enterprise_license")}
description={
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(
t("enterprise_license_description", {
consoleUrl: `<a href="${CONSOLE_URL}" target="_blank" rel="noopener noreferrer" class="underline">
${APP_NAME}
</a>`,
setupUrl: `<a href="${WEBAPP_URL}/auth/setup" class="underline">/auth/setup</a>`,
supportMail: `<a href="mailto:${SUPPORT_MAIL_ADDRESS}" class="underline">
${SUPPORT_MAIL_ADDRESS}</a>`,
})
),
}}
/>
}
/>
)}
</Component>
);
};
export const withLicenseRequired =
<T extends JSX.IntrinsicAttributes>(Component: ComponentType<T>) =>
// eslint-disable-next-line react/display-name
(hocProps: T) =>
(
<LicenseRequired>
<Component {...hocProps} />
</LicenseRequired>
);
export default LicenseRequired;
@@ -1,7 +1,7 @@
import { useState } from "react";
import ConnectionInfo from "@calcom/ee/sso/components/ConnectionInfo";
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import OIDCConnection from "@calcom/features/ee/sso/components/OIDCConnection";
import SAMLConnection from "@calcom/features/ee/sso/components/SAMLConnection";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -29,8 +29,8 @@ import {
import { ExternalLink, MoreHorizontal, Edit2, Lock, Trash } from "@calcom/ui/components/icon";
import MemberChangeRoleModal from "./MemberChangeRoleModal";
import TeamAvailabilityModal from "./TeamAvailabilityModal";
import TeamPill, { TeamRole } from "./TeamPill";
import TeamAvailabilityModal from "./v2/TeamAvailabilityModal";
interface Props {
team: RouterOutputs["viewer"]["teams"]["get"];
@@ -1,13 +1,14 @@
import { useEffect, useState } from "react";
import dayjs from "@calcom/dayjs";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { ITimezone } from "@calcom/ui";
import { Avatar, DatePickerField as DatePicker, Select, TimezoneSelect } from "@calcom/ui";
import { Avatar, DatePicker, Label, Select, TimezoneSelect } from "@calcom/ui";
import LicenseRequired from "../../common/components/LicenseRequired";
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
interface Props {
@@ -21,6 +22,9 @@ export default function TeamAvailabilityModal(props: Props) {
const [selectedTimeZone, setSelectedTimeZone] = useState<ITimezone>(
localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()
);
const { t } = useLocale();
const [frequency, setFrequency] = useState<15 | 30 | 60>(30);
useEffect(() => {
@@ -29,66 +33,71 @@ export default function TeamAvailabilityModal(props: Props) {
return (
<LicenseRequired>
<div className="flex max-h-[500px] min-h-[500px] flex-row space-x-8 rtl:space-x-reverse">
<div className="min-w-64 w-64 space-y-5 p-5 pr-0">
<div className="flex">
<Avatar
size="sm"
imageSrc={WEBAPP_URL + "/" + props.member?.username + "/avatar.png"}
alt={props.member?.name || ""}
className="h-14 w-14 rounded-full"
/>
<div className="ms-3 inline-block pt-1">
<span className="text-default text-lg font-bold">{props.member?.name}</span>
<span className="text-muted -mt-1 block text-sm">{props.member?.email}</span>
<>
<div className="grid h-[400px] grid-cols-2 space-x-11 rtl:space-x-reverse">
<div className="col-span-1">
<div className="flex">
<Avatar
size="md"
imageSrc={WEBAPP_URL + "/" + props.member?.username + "/avatar.png"}
alt={props.member?.name || ""}
/>
<div className="flex items-center justify-center ">
<span className="text-subtle ml-2 text-base font-semibold leading-4">
{props.member?.name}
</span>
</div>
</div>
<div>
<div className="text-brand-900 mt-4 mb-5 text-2xl font-semibold">{t("availability")}</div>
<DatePicker
minDate={new Date()}
date={selectedDate.toDate() || dayjs().toDate()}
onDatesChange={(newDate) => {
setSelectedDate(dayjs(newDate));
}}
/>
<Label className="mt-4">{t("timezone")}</Label>
<TimezoneSelect
id="timeZone"
autoFocus
value={selectedTimeZone}
className="w-64 rounded-md"
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
classNamePrefix="react-select"
/>
</div>
<div className="mt-3">
<Label>{t("slot_length")}</Label>
<Select
options={[
{ value: 15, label: "15 minutes" },
{ value: 30, label: "30 minutes" },
{ value: 60, label: "60 minutes" },
]}
isSearchable={false}
classNamePrefix="react-select"
className="w-64"
value={{ value: frequency, label: `${frequency} minutes` }}
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
/>
</div>
</div>
<div className="flex flex-col">
<span className="text-default font-bold">Date</span>
<DatePicker
date={selectedDate.toDate()}
onDatesChange={(newDate) => {
setSelectedDate(dayjs(newDate));
}}
/>
</div>
<div>
<span className="text-default font-bold">Timezone</span>
<TimezoneSelect
id="timeZone"
value={selectedTimeZone}
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
classNamePrefix="react-select"
className="react-select-container border-default mt-1 block w-full rounded-sm border text-sm focus:border-neutral-800 focus:ring-neutral-800"
/>
</div>
<div>
<span className="text-default font-bold">Slot Length</span>
<Select
options={[
{ value: 15, label: "15 minutes" },
{ value: 30, label: "30 minutes" },
{ value: 60, label: "60 minutes" },
]}
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:ring-primary-500 focus:border-primary-500 border-default block w-full min-w-0 flex-1 rounded-sm border text-sm"
value={{ value: frequency, label: `${frequency} minutes` }}
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
/>
<div className="col-span-1 max-h-[500px]">
{props.team && props.member && (
<TeamAvailabilityTimes
teamId={props.team.id}
memberId={props.member.id}
frequency={frequency}
selectedDate={selectedDate}
selectedTimeZone={selectedTimeZone}
/>
)}
</div>
</div>
{props.team && props.member && (
<TeamAvailabilityTimes
className="overflow-scroll"
teamId={props.team.id}
memberId={props.member.id}
frequency={frequency}
selectedDate={selectedDate}
selectedTimeZone={selectedTimeZone}
/>
)}
</div>
</>
</LicenseRequired>
);
}
@@ -1,118 +0,0 @@
import type { CSSProperties } from "react";
import { useEffect, useState } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import { FixedSizeList as List } from "react-window";
import dayjs from "@calcom/dayjs";
import { CAL_URL } from "@calcom/lib/constants";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { ITimezone } from "@calcom/ui";
import { Avatar, DatePickerField as DatePicker, Select, TimezoneSelect } from "@calcom/ui";
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
interface Props {
team?: RouterOutputs["viewer"]["teams"]["get"];
}
export default function TeamAvailabilityScreen(props: Props) {
const utils = trpc.useContext();
const [selectedDate, setSelectedDate] = useState(dayjs());
const [selectedTimeZone, setSelectedTimeZone] = useState<ITimezone>(
localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()
);
const [frequency, setFrequency] = useState<15 | 30 | 60>(30);
useEffect(() => {
utils.viewer.teams.getMemberAvailability.invalidate();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedTimeZone, selectedDate]);
const Item = ({ index, style }: { index: number; style: CSSProperties }) => {
const member = props.team?.members?.[index];
if (!member) return <></>;
return (
<div key={member.id} style={style} className="border-subtle flex border-r pl-4 ">
<TeamAvailabilityTimes
teamId={props.team?.id as number}
memberId={member.id}
frequency={frequency}
selectedDate={selectedDate}
selectedTimeZone={selectedTimeZone}
HeaderComponent={
<div className="mb-6 flex items-center">
<Avatar
size="sm"
imageSrc={CAL_URL + "/" + member.username + "/avatar.png"}
alt={member?.name || ""}
className="min-w-10 min-h-10 mt-1 h-10 w-10 rounded-full"
/>
<div className="ms-3 inline-block overflow-hidden pt-1">
<span className="text-default truncate text-lg font-bold">{member?.name}</span>
<span className="text-muted -mt-1 block truncate text-sm">{member?.email}</span>
</div>
</div>
}
/>
</div>
);
};
return (
<div className="bg-default flex flex-1 flex-col rounded-sm border border-neutral-200">
<div className="border-subtle flex w-full space-x-5 border-b p-4 rtl:space-x-reverse">
<div className="flex flex-col">
<span className="text-default text-sm font-medium">Date</span>
<DatePicker
date={selectedDate.toDate()}
className="p-1.5"
onDatesChange={(newDate) => {
setSelectedDate(dayjs(newDate));
}}
/>
</div>
<div className="flex flex-col">
<span className="text-default text-sm font-medium">Timezone</span>
<TimezoneSelect
id="timeZone"
value={selectedTimeZone}
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
classNamePrefix="react-select"
className="react-select-container border-default w-full rounded-sm border text-sm"
/>
</div>
<div className="hidden sm:block">
<span className="text-default text-sm font-medium">Slot Length</span>
<Select
options={[
{ value: 15, label: "15 minutes" },
{ value: 30, label: "30 minutes" },
{ value: 60, label: "60 minutes" },
]}
isSearchable={false}
className="border-default block w-full min-w-0 flex-1 rounded-sm border text-sm"
value={{ value: frequency, label: `${frequency} minutes` }}
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
/>
</div>
</div>
<div className="flex h-full flex-1">
<AutoSizer>
{({ height, width }) => (
<List
itemSize={240}
itemCount={props.team?.members?.length ?? 0}
className="List"
height={height}
layout="horizontal"
width={width}>
{Item}
</List>
)}
</AutoSizer>
</div>
</div>
);
}
@@ -3,9 +3,11 @@ import React from "react";
import type { ITimezone } from "react-timezone-select";
import type { Dayjs } from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import getSlots from "@calcom/lib/slots";
import { trpc } from "@calcom/trpc/react";
import { Loader } from "@calcom/ui";
import SkeletonLoader from "./SkeletonLoaderAvailabilityTimes";
interface Props {
teamId: number;
@@ -18,6 +20,8 @@ interface Props {
}
export default function TeamAvailabilityTimes(props: Props) {
const { t } = useLocale();
const { data, isLoading } = trpc.viewer.teams.getMemberAvailability.useQuery(
{
teamId: props.teamId,
@@ -38,27 +42,32 @@ export default function TeamAvailabilityTimes(props: Props) {
workingHours: data?.workingHours || [],
minimumBookingNotice: 0,
eventLength: props.frequency,
organizerTimeZone: `${data?.timeZone}`,
})
: [];
return (
<div className={classNames("min-w-60 flex-grow p-5 pl-0", props.className)}>
<div className={classNames("min-w-60 flex-grow pl-0", props.className)}>
{props.HeaderComponent}
{isLoading && slots.length === 0 && <Loader />}
{!isLoading && slots.length === 0 && (
{isLoading && slots.length === 0 && <SkeletonLoader />}
{!isLoading && slots.length === 0 ? (
<div className="flex flex-col items-center justify-center pt-4">
<span className="text-subtle text-sm">No Available Slots</span>
<span className="text-subtle text-sm">{t("no_available_slots")}</span>
</div>
) : (
<>{!isLoading && <p className="text-default mb-3 text-sm">{t("time_available")}</p>}</>
)}
{slots.map((slot) => (
<div key={slot.time.format()} className="flex flex-row items-center">
<a
className="min-w-48 border-brand-default text-bookingdarker hover:bg-brand hover:text-brandcontrast dark:hover:bg-darkmodebrand dark:hover:text-darkmodebrandcontrast bg-default dark:hover:text-inverted mb-2 mr-3 block flex-grow rounded-sm border py-2 text-center font-medium dark:border-transparent dark:bg-gray-600 dark:hover:border-black dark:hover:bg-black"
data-testid="time">
{slot.time.format("HH:mm")}
</a>
</div>
))}
<div className="max-h-[390px] overflow-scroll">
{slots.map((slot) => (
<div key={slot.time.format()} className="flex flex-row items-center ">
<a
className="min-w-48 border-brand-default text-bookingdarker bg-default mb-2 mr-3 block flex-grow rounded-md border py-2 text-center font-medium dark:border-transparent dark:bg-gray-600 "
data-testid="time">
{slot.time.tz(props.selectedTimeZone.toString()).format("HH:mm")}
</a>
</div>
))}
</div>
</div>
);
}
@@ -1,103 +0,0 @@
import { useEffect, useState } from "react";
import dayjs from "@calcom/dayjs";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { ITimezone } from "@calcom/ui";
import { Avatar, DatePicker, Label, Select, TimezoneSelect } from "@calcom/ui";
import LicenseRequired from "../../../common/components/LicenseRequired";
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
interface Props {
team?: RouterOutputs["viewer"]["teams"]["get"];
member?: RouterOutputs["viewer"]["teams"]["get"]["members"][number];
}
export default function TeamAvailabilityModal(props: Props) {
const utils = trpc.useContext();
const [selectedDate, setSelectedDate] = useState(dayjs());
const [selectedTimeZone, setSelectedTimeZone] = useState<ITimezone>(
localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()
);
const { t } = useLocale();
const [frequency, setFrequency] = useState<15 | 30 | 60>(30);
useEffect(() => {
utils.viewer.teams.getMemberAvailability.invalidate();
}, [utils, selectedTimeZone, selectedDate]);
return (
<LicenseRequired>
<>
<div className="grid h-[400px] grid-cols-2 space-x-11 rtl:space-x-reverse">
<div className="col-span-1">
<div className="flex">
<Avatar
size="md"
imageSrc={WEBAPP_URL + "/" + props.member?.username + "/avatar.png"}
alt={props.member?.name || ""}
/>
<div className="flex items-center justify-center ">
<span className="text-subtle ml-2 text-base font-semibold leading-4">
{props.member?.name}
</span>
</div>
</div>
<div>
<div className="text-brand-900 mt-4 mb-5 text-2xl font-semibold">{t("availability")}</div>
<DatePicker
minDate={new Date()}
date={selectedDate.toDate() || dayjs().toDate()}
onDatesChange={(newDate) => {
setSelectedDate(dayjs(newDate));
}}
/>
<Label className="mt-4">{t("timezone")}</Label>
<TimezoneSelect
id="timeZone"
autoFocus
value={selectedTimeZone}
className="w-64 rounded-md"
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
classNamePrefix="react-select"
/>
</div>
<div className="mt-3">
<Label>{t("slot_length")}</Label>
<Select
options={[
{ value: 15, label: "15 minutes" },
{ value: 30, label: "30 minutes" },
{ value: 60, label: "60 minutes" },
]}
isSearchable={false}
classNamePrefix="react-select"
className="w-64"
value={{ value: frequency, label: `${frequency} minutes` }}
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
/>
</div>
</div>
<div className="col-span-1 max-h-[500px]">
{props.team && props.member && (
<TeamAvailabilityTimes
teamId={props.team.id}
memberId={props.member.id}
frequency={frequency}
selectedDate={selectedDate}
selectedTimeZone={selectedTimeZone}
/>
)}
</div>
</div>
</>
</LicenseRequired>
);
}
@@ -1,118 +0,0 @@
import type { CSSProperties } from "react";
import { useEffect, useState } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import { FixedSizeList as List } from "react-window";
import dayjs from "@calcom/dayjs";
import { CAL_URL } from "@calcom/lib/constants";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { ITimezone } from "@calcom/ui";
import { Avatar, DatePickerField as DatePicker, Select, TimezoneSelect } from "@calcom/ui";
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
interface Props {
team?: RouterOutputs["viewer"]["teams"]["get"];
}
export default function TeamAvailabilityScreen(props: Props) {
const utils = trpc.useContext();
const [selectedDate, setSelectedDate] = useState(dayjs());
const [selectedTimeZone, setSelectedTimeZone] = useState<ITimezone>(
localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()
);
const [frequency, setFrequency] = useState<15 | 30 | 60>(30);
useEffect(() => {
utils.viewer.teams.getMemberAvailability.invalidate();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedTimeZone, selectedDate]);
const Item = ({ index, style }: { index: number; style: CSSProperties }) => {
const member = props.team?.members?.[index];
if (!member) return <></>;
return (
<div key={member.id} style={style} className="border-subtle flex border-r pl-4 ">
<TeamAvailabilityTimes
teamId={props.team?.id as number}
memberId={member.id}
frequency={frequency}
selectedDate={selectedDate}
selectedTimeZone={selectedTimeZone}
HeaderComponent={
<div className="mb-6 flex items-center">
<Avatar
size="sm"
imageSrc={CAL_URL + "/" + member.username + "/avatar.png"}
alt={member?.name || ""}
className="min-w-10 min-h-10 mt-1 h-10 w-10 rounded-full"
/>
<div className="ms-3 inline-block overflow-hidden pt-1">
<span className="text-default truncate text-lg font-bold">{member?.name}</span>
<span className="text-muted -mt-1 block truncate text-sm">{member?.email}</span>
</div>
</div>
}
/>
</div>
);
};
return (
<div className="bg-default flex flex-1 flex-col rounded-sm border border-neutral-200">
<div className="border-subtle flex w-full space-x-5 border-b p-4 rtl:space-x-reverse">
<div className="flex flex-col">
<span className="text-default text-sm font-medium">Date</span>
<DatePicker
date={selectedDate.toDate()}
className="p-1.5"
onDatesChange={(newDate) => {
setSelectedDate(dayjs(newDate));
}}
/>
</div>
<div className="flex flex-col">
<span className="text-default text-sm font-medium">Timezone</span>
<TimezoneSelect
id="timeZone"
value={selectedTimeZone}
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
classNamePrefix="react-select"
className="react-select-container border-default w-full rounded-sm border text-sm"
/>
</div>
<div className="hidden sm:block">
<span className="text-default text-sm font-medium">Slot Length</span>
<Select
options={[
{ value: 15, label: "15 minutes" },
{ value: 30, label: "30 minutes" },
{ value: 60, label: "60 minutes" },
]}
isSearchable={false}
className="border-default block w-full min-w-0 flex-1 rounded-sm border text-sm"
value={{ value: frequency, label: `${frequency} minutes` }}
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
/>
</div>
</div>
<div className="flex h-full flex-1">
<AutoSizer>
{({ height, width }) => (
<List
itemSize={240}
itemCount={props.team?.members?.length ?? 0}
className="List"
height={height}
layout="horizontal"
width={width}>
{Item}
</List>
)}
</AutoSizer>
</div>
</div>
);
}
@@ -1,73 +0,0 @@
import classNames from "classnames";
import React from "react";
import type { ITimezone } from "react-timezone-select";
import type { Dayjs } from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import getSlots from "@calcom/lib/slots";
import { trpc } from "@calcom/trpc/react";
import SkeletonLoader from "./SkeletonLoaderAvailabilityTimes";
interface Props {
teamId: number;
memberId: number;
selectedDate: Dayjs;
selectedTimeZone: ITimezone;
frequency: number;
HeaderComponent?: React.ReactNode;
className?: string;
}
export default function TeamAvailabilityTimes(props: Props) {
const { t } = useLocale();
const { data, isLoading } = trpc.viewer.teams.getMemberAvailability.useQuery(
{
teamId: props.teamId,
memberId: props.memberId,
dateFrom: props.selectedDate.toString(),
dateTo: props.selectedDate.add(1, "day").toString(),
timezone: `${props.selectedTimeZone.toString()}`,
},
{
refetchOnWindowFocus: false,
}
);
const slots = !isLoading
? getSlots({
frequency: props.frequency,
inviteeDate: props.selectedDate,
workingHours: data?.workingHours || [],
minimumBookingNotice: 0,
eventLength: props.frequency,
organizerTimeZone: `${data?.timeZone}`,
})
: [];
return (
<div className={classNames("min-w-60 flex-grow pl-0", props.className)}>
{props.HeaderComponent}
{isLoading && slots.length === 0 && <SkeletonLoader />}
{!isLoading && slots.length === 0 ? (
<div className="flex flex-col items-center justify-center pt-4">
<span className="text-subtle text-sm">{t("no_available_slots")}</span>
</div>
) : (
<>{!isLoading && <p className="text-default mb-3 text-sm">{t("time_available")}</p>}</>
)}
<div className="max-h-[390px] overflow-scroll">
{slots.map((slot) => (
<div key={slot.time.format()} className="flex flex-row items-center ">
<a
className="min-w-48 border-brand-default text-bookingdarker bg-default mb-2 mr-3 block flex-grow rounded-md border py-2 text-center font-medium dark:border-transparent dark:bg-gray-600 "
data-testid="time">
{slot.time.tz(props.selectedTimeZone.toString()).format("HH:mm")}
</a>
</div>
))}
</div>
</div>
);
}
@@ -1,56 +0,0 @@
import { useRouter } from "next/router";
import { useMemo, useState } from "react";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { trpc } from "@calcom/trpc/react";
import { Alert, Avatar, Loader, Shell } from "@calcom/ui";
import LicenseRequired from "../../common/components/LicenseRequired";
import TeamAvailabilityScreen from "../components/TeamAvailabilityScreen";
export function TeamAvailabilityPage() {
const router = useRouter();
const [errorMessage, setErrorMessage] = useState("");
const { data: team, isLoading } = trpc.viewer.teams.get.useQuery(
{ teamId: Number(router.query.id) },
{
refetchOnWindowFocus: false,
onError: (e) => {
setErrorMessage(e.message);
},
}
);
// prevent unnecessary re-renders due to shell queries
const TeamAvailability = useMemo(() => {
return <TeamAvailabilityScreen team={team} />;
}, [team]);
return (
<Shell
backPath={!errorMessage ? `/settings/teams/${team?.id}` : undefined}
heading={team?.name}
flexChildrenContainer
subtitle={team && "Your team's availability at a glance"}
HeadingLeftIcon={
team && (
<Avatar
size="sm"
imageSrc={getPlaceholderAvatar(team?.logo, team?.name as string)}
alt="Team Logo"
className="mt-1"
/>
)
}>
<LicenseRequired>
{!!errorMessage && <Alert className="-mt-24 border" severity="error" title={errorMessage} />}
{isLoading && <Loader />}
{TeamAvailability}
</LicenseRequired>
</Shell>
);
}
export default TeamAvailabilityPage;
@@ -4,7 +4,7 @@ import { trpc } from "@calcom/trpc/react";
import { Badge, ConfirmationDialogContent, Dialog, DropdownActions, showToast, Table } from "@calcom/ui";
import { Edit, Trash } from "@calcom/ui/components/icon";
import { withLicenseRequired } from "../../common/components/v2/LicenseRequired";
import { withLicenseRequired } from "../../common/components/LicenseRequired";
const { Body, Cell, ColumnTitle, Header, Row } = Table;
@@ -5,7 +5,7 @@ import { trpc } from "@calcom/trpc/react";
import { Meta, showToast } from "@calcom/ui";
import { getLayout } from "../../../settings/layouts/SettingsLayout";
import LicenseRequired from "../../common/components/v2/LicenseRequired";
import LicenseRequired from "../../common/components/LicenseRequired";
import { UserForm } from "../components/UserForm";
import { userBodySchema } from "../schemas/userBodySchema";
@@ -7,7 +7,7 @@ import { trpc } from "@calcom/trpc/react";
import { Meta, showToast } from "@calcom/ui";
import { getLayout } from "../../../settings/layouts/SettingsLayout";
import LicenseRequired from "../../common/components/v2/LicenseRequired";
import LicenseRequired from "../../common/components/LicenseRequired";
import { UserForm } from "../components/UserForm";
import { userBodySchema } from "../schemas/userBodySchema";
@@ -2,7 +2,7 @@ import NoSSR from "@calcom/core/components/NoSSR";
import { Button, Meta } from "@calcom/ui";
import { getLayout } from "../../../settings/layouts/SettingsLayout";
import LicenseRequired from "../../common/components/v2/LicenseRequired";
import LicenseRequired from "../../common/components/LicenseRequired";
import { UsersTable } from "../components/UsersTable";
const DeploymentUsersListPage = () => {
@@ -1,7 +1,7 @@
import { useState, Suspense } from "react";
import dayjs from "@calcom/dayjs";
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import useHasPaidPlan from "@calcom/lib/hooks/useHasPaidPlan";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RecordingItemSchema } from "@calcom/prisma/zod-utils";
@@ -12,7 +12,7 @@ import { trpc } from "@calcom/trpc/react";
import { Button, EmptyScreen, showToast, Switch, Tooltip, Alert } from "@calcom/ui";
import { ExternalLink, Zap, Lock } from "@calcom/ui/components/icon";
import LicenseRequired from "../../common/components/v2/LicenseRequired";
import LicenseRequired from "../../common/components/LicenseRequired";
import { getActionIcon } from "../lib/getActionIcon";
import SkeletonLoader from "./SkeletonLoaderEventWorkflowsTab";
import type { WorkflowType } from "./WorkflowListPage";
@@ -11,7 +11,7 @@ import { HttpError } from "@calcom/lib/http-error";
import { trpc } from "@calcom/trpc/react";
import { AnimatedPopover, Avatar, CreateButton, showToast } from "@calcom/ui";
import LicenseRequired from "../../common/components/v2/LicenseRequired";
import LicenseRequired from "../../common/components/LicenseRequired";
import SkeletonLoader from "../components/SkeletonLoaderList";
import type { WorkflowType } from "../components/WorkflowListPage";
import WorkflowList from "../components/WorkflowListPage";
@@ -24,7 +24,7 @@ import { trpc } from "@calcom/trpc/react";
import type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui";
import { Alert, Button, Form, showToast, Badge } from "@calcom/ui";
import LicenseRequired from "../../common/components/v2/LicenseRequired";
import LicenseRequired from "../../common/components/LicenseRequired";
import SkeletonLoader from "../components/SkeletonLoaderEdit";
import WorkflowDetailsPage from "../components/WorkflowDetailsPage";
import { isSMSAction } from "../lib/actionHelperFunctions";
-106
View File
@@ -1,106 +0,0 @@
/**
* @deprecated file
* All new changes should be made to the V2 file in
* `/packages/ui/v2/core/form/MultiSelectCheckboxes.tsx`
*/
import type { Dispatch, SetStateAction } from "react";
import React from "react";
import type { GroupBase, OptionProps } from "react-select";
import { components } from "react-select";
import type { Props } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Select from "./Select";
export type Option = {
value: string;
label: string;
};
const InputOption: React.FC<OptionProps<any, boolean, GroupBase<any>>> = ({
isDisabled,
isFocused,
isSelected,
children,
innerProps,
...rest
}) => {
const style = {
alignItems: "center",
backgroundColor: isFocused ? "rgba(244, 245, 246, var(--tw-bg-opacity))" : "transparent",
color: "inherit",
display: "flex ",
};
const props = {
...innerProps,
style,
};
return (
<components.Option
{...rest}
isDisabled={isDisabled}
isFocused={isFocused}
isSelected={isSelected}
innerProps={props}>
<input
type="checkbox"
className="text-primary-600 focus:ring-primary-500 border-default h-4 w-4 rounded ltr:mr-2 rtl:ml-2"
checked={isSelected}
readOnly
/>
{children}
</components.Option>
);
};
type MultiSelectionCheckboxesProps = {
options: { label: string; value: string }[];
setSelected: Dispatch<SetStateAction<Option[]>>;
selected: Option[];
setValue: (s: Option[]) => unknown;
};
const MultiValue = ({ index, getValue }: { index: number; getValue: any }) => {
const { t } = useLocale();
return <>{!index && <div>{t("nr_event_type", { count: getValue().length })}</div>}</>;
};
/**
* @deprecated file
* All new changes should be made to the V2 file in
* `/packages/ui/v2/core/form/MultiSelectCheckboxes.tsx`
*/
export default function MultiSelectCheckboxes({
options,
isLoading,
selected,
setSelected,
setValue,
}: Omit<Props, "options"> & MultiSelectionCheckboxesProps) {
const additonalComponents = { MultiValue };
return (
<Select
value={selected}
onChange={(s: any) => {
setSelected(s);
setValue(s);
}}
options={options}
isMulti
className="w-64 text-sm"
isSearchable={false}
closeMenuOnSelect={false}
hideSelectedOptions={false}
isLoading={isLoading}
components={{
...additonalComponents,
Option: InputOption,
}}
/>
);
}
-52
View File
@@ -4905,8 +4905,6 @@ __metadata:
"@types/qrcode": ^1.4.3
"@types/react": 18.0.26
"@types/react-phone-number-input": ^3.0.14
"@types/react-virtualized-auto-sizer": ^1.0.1
"@types/react-window": ^1.8.5
"@types/sanitize-html": ^2.9.0
"@types/stripe": ^8.0.417
"@types/turndown": ^5.0.1
@@ -4973,8 +4971,6 @@ __metadata:
react-select: ^5.7.0
react-timezone-select: ^1.4.0
react-use-intercom: 1.5.1
react-virtualized-auto-sizer: ^1.0.6
react-window: ^1.8.7
remark: ^14.0.2
rrule: ^2.7.1
sanitize-html: ^2.10.0
@@ -13054,24 +13050,6 @@ __metadata:
languageName: node
linkType: hard
"@types/react-virtualized-auto-sizer@npm:^1.0.1":
version: 1.0.1
resolution: "@types/react-virtualized-auto-sizer@npm:1.0.1"
dependencies:
"@types/react": "*"
checksum: 67eff0670a1991c2b16992274ada5f0b3f9d5c2d6209ef38e8f8ae2c0218211a3292882f5b7dd6f09519000dc20847629f049c9acc267e000626b7141e5927a6
languageName: node
linkType: hard
"@types/react-window@npm:^1.8.5":
version: 1.8.5
resolution: "@types/react-window@npm:1.8.5"
dependencies:
"@types/react": "*"
checksum: 5f519e1402300d11b6e6595223feb6499f3227e38da166cbc565773cd7f71862abcf855b7835d391b5119fcfacdfba79d73a965b45f60a293fc1ff25986319ed
languageName: node
linkType: hard
"@types/react@npm:18.0.26":
version: 18.0.26
resolution: "@types/react@npm:18.0.26"
@@ -27995,13 +27973,6 @@ __metadata:
languageName: node
linkType: hard
"memoize-one@npm:>=3.1.1 <6":
version: 5.2.1
resolution: "memoize-one@npm:5.2.1"
checksum: a3cba7b824ebcf24cdfcd234aa7f86f3ad6394b8d9be4c96ff756dafb8b51c7f71320785fbc2304f1af48a0467cbbd2a409efc9333025700ed523f254cb52e3d
languageName: node
linkType: hard
"memoize-one@npm:^6.0.0":
version: 6.0.0
resolution: "memoize-one@npm:6.0.0"
@@ -33414,29 +33385,6 @@ __metadata:
languageName: node
linkType: hard
"react-virtualized-auto-sizer@npm:^1.0.6":
version: 1.0.6
resolution: "react-virtualized-auto-sizer@npm:1.0.6"
peerDependencies:
react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0
react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0
checksum: 81270e9d328e9b779515f42b6aa8dea2f21097f69fc776906d6b7d31ada03706d34408a6318baf59531f180402fbf74fdb9928ab65d53442a134b7ee5ba01265
languageName: node
linkType: hard
"react-window@npm:^1.8.7":
version: 1.8.7
resolution: "react-window@npm:1.8.7"
dependencies:
"@babel/runtime": ^7.0.0
memoize-one: ">=3.1.1 <6"
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 1e122c29224781e70359978287a2e850ccdf509cd71ba16b16ea258725687a62f5c16ab69f52f732b4ed20df583196dbe2a04804f3e4a176bb3e62f3fc910452
languageName: node
linkType: hard
"react@npm:^18.2.0":
version: 18.2.0
resolution: "react@npm:18.2.0"