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:
co-authored by
zomars
Hariom Balhara
Peer Richelsen
Keith Williams
parent
ccd589ef38
commit
bdbf9bd04d
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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}>
|
||||
|
||||
@@ -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;
|
||||
@@ -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";
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user