Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38d242add9 | ||
|
|
c272e281cf | ||
|
|
d4e19b0fa4 | ||
|
|
c12877a58f | ||
|
|
af83350d04 | ||
|
|
9d0cf6be0b | ||
|
|
2fe9c782d4 | ||
|
|
d0a3d8d144 | ||
|
|
cefcb1eaf8 | ||
|
|
83b60de640 | ||
|
|
a076418989 | ||
|
|
64ecce1ccb | ||
|
|
cd56a5d4f0 | ||
|
|
d5ca121ac6 |
+1
-1
Submodule apps/console updated: 66e2663070...2d577dd9ed
@@ -23,14 +23,14 @@ import {
|
||||
TextArea,
|
||||
Tooltip,
|
||||
} from "@calcom/ui";
|
||||
import { ActionType, TableActions } from "@calcom/ui";
|
||||
|
||||
import useMeQuery from "@lib/hooks/useMeQuery";
|
||||
|
||||
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
|
||||
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
|
||||
import TableActions, { ActionType } from "@components/ui/TableActions";
|
||||
|
||||
type BookingListingStatus = RouterInputs["viewer"]["bookings"]["get"]["status"];
|
||||
type BookingListingStatus = RouterInputs["viewer"]["bookings"]["get"]["filters"]["status"];
|
||||
|
||||
type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number];
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
useIsBackgroundTransparent,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import DatePicker from "@calcom/features/calendars/DatePicker";
|
||||
import CustomBranding from "@calcom/lib/CustomBranding";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import getStripeAppData from "@calcom/lib/getStripeAppData";
|
||||
@@ -27,7 +28,7 @@ import { getRecurringFreq } from "@calcom/lib/recurringStrings";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Icon, DatePicker } from "@calcom/ui";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
import { timeZone as localStorageTimeZone } from "@lib/clock";
|
||||
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||
|
||||
@@ -6,13 +6,13 @@ import short from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
import CustomInputItem from "@calcom/features/eventtypes/components/CustomInputItem";
|
||||
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
CustomInputItem,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
|
||||
@@ -290,7 +290,7 @@ export const EventSetupTab = (
|
||||
setDefaultDuration(
|
||||
selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null
|
||||
);
|
||||
formMethods.setValue("length", Number(option?.value));
|
||||
if (option) formMethods.setValue("length", option.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -298,14 +298,11 @@ export const EventSetupTab = (
|
||||
) : (
|
||||
<TextField
|
||||
required
|
||||
name="length"
|
||||
type="number"
|
||||
label={t("duration")}
|
||||
addOnSuffix={<>{t("minutes")}</>}
|
||||
defaultValue={eventType.length ?? 15}
|
||||
onChange={(e) => {
|
||||
formMethods.setValue("length", Number(e.target.value));
|
||||
}}
|
||||
{...formMethods.register("length")}
|
||||
addOnSuffix={<>{t("minutes")}</>}
|
||||
/>
|
||||
)}
|
||||
<div className="!mt-4 [&_label]:my-1 [&_label]:font-normal">
|
||||
|
||||
@@ -3,9 +3,10 @@ import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
|
||||
import { useMemo } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTeamSelect";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Avatar, Button, CheckedTeamSelect, Icon, Label, Select } from "@calcom/ui";
|
||||
import { Avatar, Button, Icon, Label, Select } from "@calcom/ui";
|
||||
|
||||
interface IMemberToValue {
|
||||
id: number | null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
|
||||
import { useState } from "react";
|
||||
import { TbWebhook } from "react-icons/tb";
|
||||
|
||||
import { WebhookForm } from "@calcom/features/webhooks/components";
|
||||
import { WebhookFormSubmitData } from "@calcom/features/webhooks/components/WebhookForm";
|
||||
@@ -117,7 +118,7 @@ export const EventTeamWebhooksTab = ({
|
||||
</>
|
||||
) : (
|
||||
<EmptyScreen
|
||||
Icon={Icon.FiLink}
|
||||
Icon={TbWebhook}
|
||||
headline={t("create_your_first_webhook")}
|
||||
description={t("create_your_first_team_webhook_description", { appName: APP_NAME })}
|
||||
buttonRaw={<NewWebhookButton />}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useRouter } from "next/router";
|
||||
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
|
||||
import { useMemo, useState } from "react";
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
import { TbWebhook } from "react-icons/tb";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
@@ -159,7 +160,7 @@ function EventTypeSingleLayout({
|
||||
navigation.push({
|
||||
name: "webhooks",
|
||||
href: `/event-types/${eventType.id}?tabName=webhooks`,
|
||||
icon: Icon.FiLink,
|
||||
icon: TbWebhook,
|
||||
info: `${eventType.webhooks.filter((webhook) => webhook.active).length} ${t("active")}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "2.4.1",
|
||||
"version": "2.4.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
useEmbedStyles,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
||||
import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage";
|
||||
import CustomBranding from "@calcom/lib/CustomBranding";
|
||||
import defaultEvents, {
|
||||
@@ -26,7 +27,7 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
|
||||
import prisma from "@calcom/prisma";
|
||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { BadgeCheckIcon, EventTypeDescriptionLazy as EventTypeDescription, Icon } from "@calcom/ui";
|
||||
import { BadgeCheckIcon, Icon } from "@calcom/ui";
|
||||
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import { EmbedProps } from "@lib/withEmbedSsr";
|
||||
|
||||
+19
-11
@@ -5,6 +5,7 @@ import { z } from "zod";
|
||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { TRPCError } from "@calcom/trpc/server";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer";
|
||||
|
||||
@@ -27,7 +28,7 @@ const decryptedSchema = z.object({
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
|
||||
const { action, token, reason } = querySchema.parse(req.query);
|
||||
const { bookingUid, userId } = decryptedSchema.parse(
|
||||
JSON.parse(symmetricDecrypt(token, process.env.CALENDSO_ENCRYPTION_KEY || ""))
|
||||
JSON.parse(symmetricDecrypt(decodeURIComponent(token), process.env.CALENDSO_ENCRYPTION_KEY || ""))
|
||||
);
|
||||
|
||||
/** We shape the session as required by tRPC router */
|
||||
@@ -47,17 +48,24 @@ async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
|
||||
where: { uid: bookingUid },
|
||||
});
|
||||
|
||||
/** @see https://trpc.io/docs/server-side-calls */
|
||||
const ctx = await createContext({ req, res }, sessionGetter);
|
||||
const caller = viewerRouter.createCaller(ctx);
|
||||
await caller.bookings.confirm({
|
||||
bookingId: booking.id,
|
||||
recurringEventId: booking.recurringEventId || undefined,
|
||||
confirmed: action === DirectAction.ACCEPT,
|
||||
reason,
|
||||
});
|
||||
try {
|
||||
/** @see https://trpc.io/docs/server-side-calls */
|
||||
const ctx = await createContext({ req, res }, sessionGetter);
|
||||
const caller = viewerRouter.createCaller(ctx);
|
||||
await caller.bookings.confirm({
|
||||
bookingId: booking.id,
|
||||
recurringEventId: booking.recurringEventId || undefined,
|
||||
confirmed: action === DirectAction.ACCEPT,
|
||||
reason,
|
||||
});
|
||||
} catch (e) {
|
||||
let message = "Error confirming booking";
|
||||
if (e instanceof TRPCError) message = e.message;
|
||||
res.redirect(`/booking/${bookingUid}?error=${encodeURIComponent(message)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return res.redirect(`/booking/${bookingUid}`);
|
||||
res.redirect(`/booking/${bookingUid}`);
|
||||
}
|
||||
|
||||
export default defaultResponder(handler);
|
||||
|
||||
@@ -7,12 +7,13 @@ import { useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { FaGoogle } from "react-icons/fa";
|
||||
|
||||
import { SAMLLogin } from "@calcom/features/auth/SAMLLogin";
|
||||
import { isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Alert, Button, EmailField, Icon, PasswordField, SAMLLogin } from "@calcom/ui";
|
||||
import { Alert, Button, EmailField, Icon, PasswordField } from "@calcom/ui";
|
||||
|
||||
import { ErrorCode, getSession } from "@lib/auth";
|
||||
import { WEBAPP_URL, WEBSITE_URL } from "@lib/config/constants";
|
||||
|
||||
@@ -5,9 +5,11 @@ import { Fragment } from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components";
|
||||
import BookingLayout from "@calcom/features/bookings/layout/BookingLayout";
|
||||
import { filterQuerySchema, useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterInputs, RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Alert, BookingLayout, Button, EmptyScreen, Icon } from "@calcom/ui";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Alert, Button, EmptyScreen, Icon } from "@calcom/ui";
|
||||
|
||||
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
|
||||
|
||||
@@ -16,7 +18,7 @@ import SkeletonLoader from "@components/booking/SkeletonLoader";
|
||||
|
||||
import { ssgInit } from "@server/lib/ssg";
|
||||
|
||||
type BookingListingStatus = RouterInputs["viewer"]["bookings"]["get"]["status"];
|
||||
type BookingListingStatus = z.infer<typeof filterQuerySchema>["status"];
|
||||
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
|
||||
|
||||
type RecurringInfo = {
|
||||
@@ -41,12 +43,19 @@ const querySchema = z.object({
|
||||
});
|
||||
|
||||
export default function Bookings() {
|
||||
const { data: filterQuery } = useFilterQuery();
|
||||
const router = useRouter();
|
||||
const { status } = router.isReady ? querySchema.parse(router.query) : { status: "upcoming" as const };
|
||||
const { t } = useLocale();
|
||||
|
||||
const query = trpc.viewer.bookings.get.useInfiniteQuery(
|
||||
{ status, limit: 10 },
|
||||
{
|
||||
limit: 10,
|
||||
filters: {
|
||||
...filterQuery,
|
||||
status: filterQuery.status ?? status,
|
||||
},
|
||||
},
|
||||
{
|
||||
// first render has status `undefined`
|
||||
enabled: router.isReady,
|
||||
@@ -165,7 +174,7 @@ export default function Bookings() {
|
||||
</>
|
||||
)}
|
||||
{query.status === "success" && isEmpty && (
|
||||
<div className="flex items-center justify-center pt-2 xl:mx-6 xl:pt-0">
|
||||
<div className="flex items-center justify-center pt-2 xl:pt-0">
|
||||
<EmptyScreen
|
||||
Icon={Icon.FiCalendar}
|
||||
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PeriodType, Prisma, SchedulingType } from "@prisma/client";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -204,6 +205,16 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
||||
),
|
||||
metadata,
|
||||
},
|
||||
resolver: zodResolver(
|
||||
z
|
||||
.object({
|
||||
// Length if string, is converted to a number or it can be a number
|
||||
// Make it optional because it's not submitted from all tabs of the page
|
||||
length: z.union([z.string().transform((val) => +val), z.number()]).optional(),
|
||||
})
|
||||
// TODO: Add schema for other fields later.
|
||||
.passthrough()
|
||||
),
|
||||
});
|
||||
|
||||
const appsMetadata = formMethods.getValues("metadata")?.apps;
|
||||
|
||||
@@ -4,10 +4,12 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
|
||||
import CreateEventTypeButton from "@calcom/features/eventtypes/components/CreateEventTypeButton";
|
||||
import {
|
||||
CreateEventTypeButton,
|
||||
EventTypeDescriptionLazy as EventTypeDescription,
|
||||
} from "@calcom/features/eventtypes/components";
|
||||
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import isCalcom from "@calcom/lib/isCalcom";
|
||||
import { RouterOutputs, trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||
import {
|
||||
Badge,
|
||||
@@ -23,13 +25,11 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
EmptyScreen,
|
||||
EventTypeDescription,
|
||||
Icon,
|
||||
Shell,
|
||||
showToast,
|
||||
Switch,
|
||||
Tooltip,
|
||||
TipBanner,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { withQuery } from "@lib/QueryCell";
|
||||
@@ -589,7 +589,6 @@ const EventTypesPage = () => {
|
||||
customLoader={<SkeletonLoader />}
|
||||
success={({ data }) => (
|
||||
<>
|
||||
{isCalcom && <TipBanner />}
|
||||
{data.eventTypeGroups.map((group, index) => (
|
||||
<Fragment key={group.profile.slug}>
|
||||
{/* hide list heading when there is only one (current user) */}
|
||||
@@ -608,6 +607,7 @@ const EventTypesPage = () => {
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
{data.eventTypeGroups.length === 0 && <CreateFirstEventTypeView />}
|
||||
<EmbedDialog />
|
||||
</>
|
||||
|
||||
@@ -5,13 +5,14 @@ import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { Avatar, Button, EventTypeDescription, Icon } from "@calcom/ui";
|
||||
import { Avatar, Button, Icon } from "@calcom/ui";
|
||||
|
||||
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
@@ -1443,6 +1443,9 @@
|
||||
"enter_option": "Enter Option {{index}}",
|
||||
"add_an_option": "Add an option",
|
||||
"radio": "Radio",
|
||||
"individual":"Individual",
|
||||
"all_bookings_filter_label":"All Bookings",
|
||||
"all_users_filter_label":"All Users",
|
||||
"meeting_url_workflow": "Meeting url",
|
||||
"meeting_url_info": "The event meeting conference url",
|
||||
"date_overrides": "Date overrides",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 697 KiB |
+1
-1
Submodule apps/website updated: 0f2767e731...ba98a7c980
@@ -7,7 +7,7 @@ import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
|
||||
export const OrganizerRequestEmail = (props: React.ComponentProps<typeof OrganizerScheduledEmail>) => {
|
||||
const seedData = { bookingUid: props.calEvent.uid, userId: props.calEvent.organizer.id };
|
||||
const token = symmetricEncrypt(JSON.stringify(seedData), process.env.CALENDSO_ENCRYPTION_KEY || "");
|
||||
const actionHref = `${WEBAPP_URL}/api/link/?token=${token}`;
|
||||
const actionHref = `${WEBAPP_URL}/api/link/?token=${encodeURIComponent(token)}`;
|
||||
return (
|
||||
<OrganizerScheduledEmail
|
||||
title={
|
||||
|
||||
@@ -7,8 +7,7 @@ import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
import { Button, Icon } from "../../..";
|
||||
import { Button, Icon } from "@calcom/ui";
|
||||
|
||||
interface Props {
|
||||
samlTenantID: string;
|
||||
@@ -20,7 +19,7 @@ const schema = z.object({
|
||||
email: z.string().email({ message: "Please enter a valid email" }),
|
||||
});
|
||||
|
||||
export default function SAMLLogin({ samlTenantID, samlProductID, setErrorMessage }: Props) {
|
||||
export function SAMLLogin({ samlTenantID, samlProductID, setErrorMessage }: Props) {
|
||||
const { t } = useLocale();
|
||||
const methods = useFormContext();
|
||||
const telemetry = useTelemetry();
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useSession } from "next-auth/react";
|
||||
import { Fragment, useState, useEffect } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc, RouterOutputs } from "@calcom/trpc/react";
|
||||
import { AnimatedPopover } from "@calcom/ui";
|
||||
|
||||
import { groupBy } from "../groupBy";
|
||||
|
||||
export type IEventTypesFilters = RouterOutputs["viewer"]["eventTypes"]["listWithTeam"];
|
||||
export type IEventTypeFilter = IEventTypesFilters[0];
|
||||
|
||||
type GroupedEventTypeState = Record<
|
||||
string,
|
||||
{
|
||||
team: {
|
||||
id: number;
|
||||
name: string;
|
||||
} | null;
|
||||
id: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
}[]
|
||||
>;
|
||||
|
||||
export const EventTypeFilter = () => {
|
||||
const { t } = useLocale();
|
||||
const { data: user } = useSession();
|
||||
const eventTypes = trpc.viewer.eventTypes.listWithTeam.useQuery();
|
||||
const [groupedEventTypes, setGroupedEventTypes] = useState<GroupedEventTypeState>();
|
||||
// Will be handled up the tree to redirect
|
||||
useEffect(() => {
|
||||
if (!eventTypes.data) return;
|
||||
// Group event types by team
|
||||
const grouped = groupBy<IEventTypeFilter>(
|
||||
eventTypes.data.filter((el) => el.team),
|
||||
(item) => item?.team?.name || ""
|
||||
); // Add the team name
|
||||
const individualEvents = eventTypes.data.filter((el) => !el.team);
|
||||
// push indivdual events to the start of grouped array
|
||||
setGroupedEventTypes({ user_own_event_types: individualEvents, ...grouped });
|
||||
}, [eventTypes.data, user]);
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<AnimatedPopover text={t("event_type")}>
|
||||
<div className="">
|
||||
{groupedEventTypes &&
|
||||
Object.keys(groupedEventTypes).map((teamName) => (
|
||||
<Fragment key={teamName}>
|
||||
<div className="p-4 text-xs font-medium uppercase leading-none text-gray-500">
|
||||
{teamName === "user_own_event_types" ? t("individual") : teamName}
|
||||
</div>
|
||||
{groupedEventTypes[teamName].map((eventType) => (
|
||||
<Fragment key={eventType.id}>
|
||||
<div className="item-center flex px-4 py-[6px]">
|
||||
<p className="block self-center truncate text-sm font-medium text-gray-700">
|
||||
{eventType.title}
|
||||
</p>
|
||||
<div className="ml-auto">
|
||||
<input
|
||||
type="checkbox"
|
||||
name=""
|
||||
id=""
|
||||
className="text-primary-600 focus:ring-primary-500 mr-2 h-4 w-4 rounded border-gray-300 "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</AnimatedPopover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Fragment, ReactNode } from "react";
|
||||
|
||||
import { PeopleFilter } from "./PeopleFilter";
|
||||
import { TeamsMemberFilter } from "./TeamFilter";
|
||||
|
||||
type FilterTypes = "teams" | "people";
|
||||
|
||||
type Filter = {
|
||||
name: FilterTypes;
|
||||
controllingQueryParams?: string[]; // this is what the filter controls - but also we show the filter if any of these query params are present
|
||||
component: ReactNode;
|
||||
showByDefault?: boolean;
|
||||
};
|
||||
|
||||
const filters: Filter[] = [
|
||||
{
|
||||
name: "teams",
|
||||
component: <TeamsMemberFilter />,
|
||||
controllingQueryParams: ["teamId"],
|
||||
showByDefault: true,
|
||||
},
|
||||
{
|
||||
name: "people",
|
||||
component: <PeopleFilter />,
|
||||
controllingQueryParams: ["usersId"],
|
||||
showByDefault: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function FiltersContainer() {
|
||||
return (
|
||||
<div className="flex w-full space-x-2 overflow-x-scroll lg:justify-end ">
|
||||
{filters.map((filter) => {
|
||||
if (!filter.showByDefault) {
|
||||
// TODO: check if any of the controllingQueryParams are present in the query params and show the filter if so
|
||||
// TODO: Also check state to see if the user has toggled the filter
|
||||
return null;
|
||||
}
|
||||
return <Fragment key={filter.name}>{filter.component}</Fragment>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { AnimatedPopover, Avatar, Icon } from "@calcom/ui";
|
||||
|
||||
import { useFilterQuery } from "../lib/useFilterQuery";
|
||||
|
||||
export const PeopleFilter = () => {
|
||||
const { t } = useLocale();
|
||||
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeByKey } = useFilterQuery();
|
||||
const { data } = trpc.viewer.teams.listMembers.useQuery({});
|
||||
|
||||
if (!data || !data.length) return null;
|
||||
|
||||
// Get user names from query
|
||||
const userNames = data?.filter((user) => query.userIds?.includes(user.id)).map((user) => user.name);
|
||||
|
||||
return (
|
||||
<AnimatedPopover
|
||||
text={userNames && userNames.length > 0 ? `${userNames.join(", ")}` : t("all_users_filter_label")}>
|
||||
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100">
|
||||
<div className="mr-2 flex h-6 w-6 items-center justify-center">
|
||||
<Icon.FiUser className="h-full w-full" />
|
||||
</div>
|
||||
<label htmlFor="allUsers" className="mr-auto self-center truncate text-sm font-medium text-gray-700">
|
||||
{t("all_users_filter_label")}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="allUsers"
|
||||
type="checkbox"
|
||||
checked={!query.userIds}
|
||||
onChange={() => {
|
||||
// Always clear userIds on toggle as this is the toggle box for all users. No params means we are currently selecting all users
|
||||
removeByKey("userIds");
|
||||
}}
|
||||
className="text-primary-600 focus:ring-primary-500 inline-flex h-4 w-4 place-self-center justify-self-end rounded border-gray-300 "
|
||||
/>
|
||||
</div>
|
||||
{data &&
|
||||
data.map((user) => (
|
||||
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100" key={`${user.id}`}>
|
||||
<Avatar
|
||||
imageSrc={user.avatar}
|
||||
size="sm"
|
||||
alt={`${user.name} Avatar`}
|
||||
gravatarFallbackMd5="fallback"
|
||||
className="self-center"
|
||||
asChild
|
||||
/>
|
||||
<label
|
||||
htmlFor={user.name ?? "NamelessUser"}
|
||||
className="ml-2 mr-auto self-center truncate text-sm font-medium text-gray-700">
|
||||
{user.name}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id={user.name ?? "NamelessUser"}
|
||||
name={user.name ?? "NamelessUser"}
|
||||
type="checkbox"
|
||||
checked={query.userIds?.includes(user.id)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
pushItemToKey("userIds", user.id);
|
||||
} else if (!e.target.checked) {
|
||||
removeItemByKeyAndValue("userIds", user.id);
|
||||
}
|
||||
}}
|
||||
className="text-primary-600 focus:ring-primary-500 inline-flex h-4 w-4 place-self-center justify-self-end rounded border-gray-300 "
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</AnimatedPopover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { AnimatedPopover, Avatar, Icon } from "@calcom/ui";
|
||||
|
||||
import { useFilterQuery } from "../lib/useFilterQuery";
|
||||
|
||||
export const TeamsMemberFilter = () => {
|
||||
const { t } = useLocale();
|
||||
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeByKey } = useFilterQuery();
|
||||
const { data } = trpc.viewer.teams.list.useQuery();
|
||||
|
||||
if (!data || !data.length) return null;
|
||||
|
||||
// get team names from query
|
||||
const teamNames = data?.filter((team) => query.teamIds?.includes(team.id)).map((team) => team.name);
|
||||
|
||||
return (
|
||||
<AnimatedPopover
|
||||
text={teamNames && teamNames.length > 0 ? `${teamNames.join(", ")}` : t("all_bookings_filter_label")}>
|
||||
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100">
|
||||
<div className="mr-2 flex h-6 w-6 items-center justify-center">
|
||||
<Icon.FiLayers className="h-full w-full" />
|
||||
</div>
|
||||
<label
|
||||
htmlFor="allBookings"
|
||||
className="mr-auto self-center truncate text-sm font-medium text-gray-700">
|
||||
{t("all_bookings_filter_label")}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="allBookings"
|
||||
type="checkbox"
|
||||
checked={!query.teamIds}
|
||||
onChange={() => {
|
||||
removeByKey("teamIds"); // Always clear on toggle or not toggle (seems weird but when you know the behviour it works well )
|
||||
}}
|
||||
className="text-primary-600 focus:ring-primary-500 inline-flex h-4 w-4 place-self-center justify-self-end rounded border-gray-300 "
|
||||
/>
|
||||
</div>
|
||||
{data &&
|
||||
data.map((team) => (
|
||||
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100" key={`${team.id}`}>
|
||||
<Avatar
|
||||
imageSrc={team.logo}
|
||||
size="sm"
|
||||
alt={`${team.name} Avatar`}
|
||||
gravatarFallbackMd5="fallback"
|
||||
className="self-center"
|
||||
asChild
|
||||
/>
|
||||
<label
|
||||
htmlFor={team.name}
|
||||
className="ml-2 mr-auto self-center truncate text-sm font-medium text-gray-700">
|
||||
{team.name}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id={team.name}
|
||||
name={team.name}
|
||||
type="checkbox"
|
||||
checked={query.teamIds?.includes(team.id)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
pushItemToKey("teamIds", team.id);
|
||||
} else if (!e.target.checked) {
|
||||
removeItemByKeyAndValue("teamIds", team.id);
|
||||
}
|
||||
}}
|
||||
className="text-primary-600 focus:ring-primary-500 inline-flex h-4 w-4 place-self-center justify-self-end rounded border-gray-300 "
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</AnimatedPopover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
type KeySelector<T> = (item: T) => string;
|
||||
|
||||
export function groupBy<T>(array: Iterable<T>, keySelector: KeySelector<T>): Record<string, T[]> {
|
||||
return Array.from(array).reduce(
|
||||
(acc: Record<string, T[]>, item: T) => {
|
||||
const key = keySelector(item);
|
||||
if (key in acc) {
|
||||
// found key, push new item into existing array
|
||||
acc[key].push(item);
|
||||
} else {
|
||||
// did not find key, create new array
|
||||
acc[key] = [item];
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} // start with empty object
|
||||
);
|
||||
}
|
||||
+7
-15
@@ -1,36 +1,30 @@
|
||||
import React, { ComponentProps } from "react";
|
||||
|
||||
import type { VerticalTabItemProps } from "../";
|
||||
import { HorizontalTabs, VerticalTabs } from "../";
|
||||
import { Icon } from "../../../Icon";
|
||||
import Shell from "../Shell";
|
||||
import type { HorizontalTabItemProps } from "../navigation/tabs/HorizontalTabItem";
|
||||
import { HorizontalTabs, Shell } from "@calcom/ui";
|
||||
import { VerticalTabItemProps, HorizontalTabItemProps } from "@calcom/ui/v2";
|
||||
|
||||
import { FiltersContainer } from "../components/FiltersContainer";
|
||||
|
||||
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
|
||||
{
|
||||
name: "upcoming",
|
||||
href: "/bookings/upcoming",
|
||||
icon: Icon.FiCalendar,
|
||||
},
|
||||
{
|
||||
name: "unconfirmed",
|
||||
href: "/bookings/unconfirmed",
|
||||
icon: Icon.FiInbox,
|
||||
},
|
||||
{
|
||||
name: "recurring",
|
||||
href: "/bookings/recurring",
|
||||
icon: Icon.FiRotateCcw,
|
||||
},
|
||||
{
|
||||
name: "past",
|
||||
href: "/bookings/past",
|
||||
icon: Icon.FiSunset,
|
||||
},
|
||||
{
|
||||
name: "cancelled",
|
||||
href: "/bookings/cancelled",
|
||||
icon: Icon.FiSlash,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -40,12 +34,10 @@ export default function BookingLayout({
|
||||
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
||||
return (
|
||||
<Shell {...rest}>
|
||||
<div className="flex flex-col sm:space-x-2 xl:flex-row">
|
||||
<div className="hidden xl:block">
|
||||
<VerticalTabs tabs={tabs} sticky />
|
||||
</div>
|
||||
<div className="block xl:hidden">
|
||||
<div className="flex max-w-6xl flex-col sm:space-x-2">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<HorizontalTabs tabs={tabs} />
|
||||
<FiltersContainer />
|
||||
</div>
|
||||
<main className="w-full max-w-6xl">{children}</main>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
import z from "zod";
|
||||
|
||||
import { queryNumberArray, useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||
|
||||
// TODO: Move this to zod utils
|
||||
export const filterQuerySchema = z.object({
|
||||
teamIds: queryNumberArray.optional(),
|
||||
userIds: queryNumberArray.optional(),
|
||||
status: z.enum(["upcoming", "recurring", "past", "cancelled", "unconfirmed"]),
|
||||
eventTypeIds: queryNumberArray.optional(),
|
||||
});
|
||||
|
||||
export function useFilterQuery() {
|
||||
return useTypedQuery(filterQuerySchema);
|
||||
}
|
||||
+1
-3
@@ -6,9 +6,7 @@ import classNames from "@calcom/lib/classNames";
|
||||
import { daysInMonth, yyyymmdd } from "@calcom/lib/date-fns";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { weekdayNames } from "@calcom/lib/weekday";
|
||||
import { Button, Icon } from "@calcom/ui";
|
||||
|
||||
import { SkeletonText } from "../../..";
|
||||
import { Button, Icon, SkeletonText } from "@calcom/ui";
|
||||
|
||||
export type DatePickerProps = {
|
||||
/** which day of the week to render the calendar. Usually Sunday (=0) or Monday (=1) - default: Sunday */
|
||||
+1
-2
@@ -3,8 +3,7 @@ import { Props } from "react-select";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import { Avatar, Icon, Select } from "../../..";
|
||||
import { Avatar, Icon, Select } from "@calcom/ui";
|
||||
|
||||
type CheckedSelectOption = {
|
||||
avatar: string;
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import { Badge, Button, ButtonGroup, Icon } from "../../..";
|
||||
import { Badge, Button, ButtonGroup, Icon } from "@calcom/ui";
|
||||
|
||||
type Props = {
|
||||
required?: boolean;
|
||||
+1
-2
@@ -8,8 +8,7 @@ import getStripeAppData from "@calcom/lib/getStripeAppData";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { baseEventTypeSelect } from "@calcom/prisma";
|
||||
import { EventTypeModel } from "@calcom/prisma/zod";
|
||||
|
||||
import { Badge, Icon } from "../../..";
|
||||
import { Badge, Icon } from "@calcom/ui";
|
||||
|
||||
export type EventTypeDescriptionProps = {
|
||||
eventType: Pick<
|
||||
+2
@@ -2,4 +2,6 @@ import dynamic from "next/dynamic";
|
||||
|
||||
export { default as CheckedTeamSelect } from "./CheckedTeamSelect";
|
||||
export { default as CustomInputItem } from "./CustomInputItem";
|
||||
export { default as CreateEventTypeButton } from "./CreateEventTypeButton";
|
||||
export { default as EventTypeDescription } from "./EventTypeDescription";
|
||||
export const EventTypeDescriptionLazy = dynamic(() => import("./EventTypeDescription"));
|
||||
@@ -13,11 +13,11 @@ import {
|
||||
DialogHeader,
|
||||
DialogClose,
|
||||
Switch,
|
||||
DatePicker,
|
||||
Form,
|
||||
Button,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import DatePicker from "../../calendars/DatePicker";
|
||||
import { DayRanges, TimeRange } from "./Schedule";
|
||||
|
||||
const ALL_DAY_RANGE = {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
import { Button, Icon } from "@calcom/ui";
|
||||
|
||||
import Card from "./Card";
|
||||
import { Card } from "@calcom/ui";
|
||||
|
||||
export const tips = [
|
||||
{
|
||||
@@ -59,30 +56,20 @@ export const tips = [
|
||||
},
|
||||
];
|
||||
|
||||
function useLocalList<T extends { id: number }>(storageKey: string, initialValue: T[]) {
|
||||
const [list, setList] = useState<T[]>(initialValue);
|
||||
export default function Tips() {
|
||||
const [animationRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
/**
|
||||
This use effect is used to filter out the items that have been removed from the list
|
||||
we get this from local storage and then we reverse the list so that the newest items
|
||||
are at the top. This is done so that the items are displayed in the order they were
|
||||
added
|
||||
**/
|
||||
useEffect(() => {
|
||||
const reversedList = initialValue.slice(0).reverse();
|
||||
const removedListString = localStorage.getItem(storageKey) || "";
|
||||
const removedListIds = removedListString.split(",").map((id) => parseInt(id, 10));
|
||||
const filteredList = reversedList.filter((tip) => removedListIds.indexOf(tip.id) === -1);
|
||||
setList(() => [...filteredList]);
|
||||
}, [initialValue, storageKey]);
|
||||
const { t } = useLocale();
|
||||
|
||||
const [list, setList] = useState<typeof tips>([]);
|
||||
|
||||
const handleRemoveItem = (id: number) => {
|
||||
setList((currentItems) => {
|
||||
const items = localStorage.getItem(storageKey) || "";
|
||||
const items = localStorage.getItem("removedTipsIds") || "";
|
||||
const itemToRemoveIndex = currentItems.findIndex((item) => item.id === id);
|
||||
|
||||
localStorage.setItem(
|
||||
storageKey,
|
||||
"removedTipsIds",
|
||||
`${currentItems[itemToRemoveIndex].id.toString()}${items.length > 0 ? `,${items.split(",")}` : ""}`
|
||||
);
|
||||
currentItems.splice(itemToRemoveIndex, 1);
|
||||
@@ -90,20 +77,14 @@ function useLocalList<T extends { id: number }>(storageKey: string, initialValue
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
list,
|
||||
handleRemoveItem,
|
||||
};
|
||||
}
|
||||
export default function Tips() {
|
||||
//const [animationRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
const { t } = useLocale();
|
||||
|
||||
const { list, handleRemoveItem } = useLocalList<typeof tips[0]>("removedTipsIds", tips);
|
||||
|
||||
useEffect(() => {
|
||||
const reversedTips = tips.slice(0).reverse();
|
||||
const removedTipsString = localStorage.getItem("removedTipsIds") || "";
|
||||
const removedTipsIds = removedTipsString.split(",").map((id) => parseInt(id, 10));
|
||||
const filteredTips = reversedTips.filter((tip) => removedTipsIds.indexOf(tip.id) === -1);
|
||||
setList(() => [...filteredTips]);
|
||||
}, []);
|
||||
const baseOriginalList = list.slice(0).reverse();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="mb-4 hidden lg:grid"
|
||||
@@ -143,59 +124,3 @@ export default function Tips() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TipBanner() {
|
||||
const { t } = useLocale();
|
||||
const [animationRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
const bannerTips = [
|
||||
{
|
||||
id: 1,
|
||||
img: "/team-banner-background-small.jpg",
|
||||
title: t("calcom_is_better_with_team"),
|
||||
subtitle: t("add_your_team_members"),
|
||||
href: `${WEBAPP_URL}/settings/teams/new`,
|
||||
learnMoreHref: "https://go.cal.com/teams-video",
|
||||
},
|
||||
];
|
||||
const { list, handleRemoveItem } = useLocalList<typeof bannerTips[0]>("removedBannerIds", bannerTips);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{list.map((tip) => {
|
||||
return (
|
||||
<div
|
||||
key={tip.id}
|
||||
ref={animationRef}
|
||||
className="relative -mt-6 mb-6 min-h-[186px] rounded-md"
|
||||
style={{ backgroundImage: "url(" + tip.img + ")", backgroundSize: "cover" }}>
|
||||
<div className="p-6 text-white">
|
||||
<h1 className="font-cal text-lg">{tip.title}</h1>
|
||||
<p className="my-4 max-w-xl text-sm">{tip.subtitle}</p>
|
||||
<div className="space-x-2">
|
||||
<Button className="bg-white" color="secondary" href={tip.href}>
|
||||
{t("create_team")}
|
||||
</Button>
|
||||
{tip.learnMoreHref && (
|
||||
<Button
|
||||
className="!bg-transparent text-white"
|
||||
color="minimal"
|
||||
target="_blank"
|
||||
href={tip.learnMoreHref}>
|
||||
{t("learn_more")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleRemoveItem(tip.id)}
|
||||
className="!focus:border-transparent !focus:ring-0 absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-md !border-transparent text-white hover:bg-white/10">
|
||||
<span className="sr-only">Hide Banner</span>
|
||||
<Icon.FiX className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Tips } from "./Tips";
|
||||
@@ -162,7 +162,7 @@ export const getUsernameList = (users: string | string[] | undefined): string[]
|
||||
|
||||
const allUsers = users.map((user) =>
|
||||
user
|
||||
//.toLowerCase() // This was causing 404s for mixed-case usernames
|
||||
.toLowerCase()
|
||||
.replace(/( |%20)/g, "+")
|
||||
.split("+")
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ export const queryStringArray = z
|
||||
.preprocess((a) => z.string().parse(a).split(","), z.string().array())
|
||||
.or(z.string().array());
|
||||
|
||||
export function useTypedQuery<T extends z.ZodType>(schema: T) {
|
||||
export function useTypedQuery<T extends z.AnyZodObject>(schema: T) {
|
||||
type Output = z.infer<typeof schema>;
|
||||
type FullOutput = Required<Output>;
|
||||
type OutputKeys = Required<keyof FullOutput>;
|
||||
@@ -65,8 +65,10 @@ export function useTypedQuery<T extends z.ZodType>(schema: T) {
|
||||
const existingValue = parsedQuery[key];
|
||||
if (Array.isArray(existingValue)) {
|
||||
if (existingValue.includes(value)) return; // prevent adding the same value to the array
|
||||
// @ts-expect-error this is too much for TS it seems
|
||||
setQuery(key, [...existingValue, value]);
|
||||
} else {
|
||||
// @ts-expect-error this is too much for TS it seems
|
||||
setQuery(key, [value]);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +76,7 @@ export function useTypedQuery<T extends z.ZodType>(schema: T) {
|
||||
// Remove item by key and value
|
||||
function removeItemByKeyAndValue<J extends ArrayOutputKeys>(key: J, value: ArrayOutput[J][number]) {
|
||||
const existingValue = parsedQuery[key];
|
||||
if (Array.isArray(existingValue)) {
|
||||
if (Array.isArray(existingValue) && existingValue.length > 1) {
|
||||
// @ts-expect-error this is too much for TS it seems
|
||||
const newValue = existingValue.filter((item) => item !== value);
|
||||
setQuery(key, newValue);
|
||||
|
||||
@@ -103,7 +103,12 @@ export const bookingsRouter = router({
|
||||
get: authedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
status: z.enum(["upcoming", "recurring", "past", "cancelled", "unconfirmed"]),
|
||||
filters: z.object({
|
||||
teamIds: z.number().array().optional(),
|
||||
userIds: z.number().array().optional(),
|
||||
status: z.enum(["upcoming", "recurring", "past", "cancelled", "unconfirmed"]),
|
||||
eventTypeIds: z.number().array().optional(),
|
||||
}),
|
||||
limit: z.number().min(1).max(100).nullish(),
|
||||
cursor: z.number().nullish(), // <-- "cursor" needs to exist when using useInfiniteQuery, but can be any type
|
||||
})
|
||||
@@ -114,7 +119,7 @@ export const bookingsRouter = router({
|
||||
const take = input.limit ?? 10;
|
||||
const skip = input.cursor ?? 0;
|
||||
const { prisma, user } = ctx;
|
||||
const bookingListingByStatus = input.status;
|
||||
const bookingListingByStatus = input.filters.status;
|
||||
const bookingListingFilters: Record<typeof bookingListingByStatus, Prisma.BookingWhereInput> = {
|
||||
upcoming: {
|
||||
endTime: { gte: new Date() },
|
||||
@@ -175,7 +180,46 @@ export const bookingsRouter = router({
|
||||
cancelled: { startTime: "desc" },
|
||||
unconfirmed: { startTime: "asc" },
|
||||
};
|
||||
const passedBookingsFilter = bookingListingFilters[bookingListingByStatus];
|
||||
|
||||
// TODO: Fix record typing
|
||||
const bookingWhereInputFilters: Record<string, Prisma.BookingWhereInput> = {
|
||||
teamIds: {
|
||||
AND: [
|
||||
{
|
||||
eventType: {
|
||||
team: {
|
||||
id: {
|
||||
in: input.filters?.teamIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
userIds: {
|
||||
AND: [
|
||||
{
|
||||
eventType: {
|
||||
users: {
|
||||
some: {
|
||||
id: {
|
||||
in: input.filters?.userIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const filtersCombined: Prisma.BookingWhereInput[] =
|
||||
input.filters &&
|
||||
Object.keys(input.filters).map((key) => {
|
||||
return bookingWhereInputFilters[key];
|
||||
});
|
||||
|
||||
const passedBookingsStatusFilter = bookingListingFilters[bookingListingByStatus];
|
||||
const orderBy = bookingListingOrderby[bookingListingByStatus];
|
||||
|
||||
const bookingsQuery = await prisma.booking.findMany({
|
||||
@@ -204,7 +248,7 @@ export const bookingsRouter = router({
|
||||
},
|
||||
},
|
||||
],
|
||||
AND: [passedBookingsFilter],
|
||||
AND: [passedBookingsStatusFilter, ...(filtersCombined ?? [])],
|
||||
},
|
||||
select: {
|
||||
...bookingMinimalSelect,
|
||||
@@ -647,14 +691,10 @@ export const bookingsRouter = router({
|
||||
|
||||
const tOrganizer = await getTranslation(user.locale ?? "en", "common");
|
||||
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUniqueOrThrow({
|
||||
where: {
|
||||
id: bookingId,
|
||||
},
|
||||
rejectOnNotFound() {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Booking not found" });
|
||||
},
|
||||
// should trpc handle this error ?
|
||||
select: {
|
||||
title: true,
|
||||
description: true,
|
||||
|
||||
@@ -356,6 +356,35 @@ export const eventTypesRouter = router({
|
||||
},
|
||||
});
|
||||
}),
|
||||
listWithTeam: authedProcedure.query(async ({ ctx }) => {
|
||||
return await ctx.prisma.eventType.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ userId: ctx.user.id },
|
||||
{
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId: ctx.user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
title: true,
|
||||
slug: true,
|
||||
},
|
||||
});
|
||||
}),
|
||||
create: authedProcedure.input(createEventTypeInput).mutation(async ({ ctx, input }) => {
|
||||
const { schedulingType, teamId, ...rest } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
@@ -642,4 +642,45 @@ export const viewerTeamsRouter = router({
|
||||
});
|
||||
return teams;
|
||||
}),
|
||||
listMembers: authedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
teamIds: z.number().array().optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const teams = await ctx.prisma.team.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: input.teamIds,
|
||||
},
|
||||
members: {
|
||||
some: {
|
||||
user: {
|
||||
id: ctx.user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
members: {
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
type UserMap = Record<number, typeof teams[number]["members"][number]["user"]>;
|
||||
// flattern users to be unique by id
|
||||
const users = teams
|
||||
.flatMap((t) => t.members)
|
||||
.reduce((acc, m) => (m.user.id in acc ? acc : { ...acc, [m.user.id]: m.user }), {} as UserMap);
|
||||
return Object.values(users);
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import superjson from "superjson";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import rateLimit from "@calcom/lib/rateLimit";
|
||||
|
||||
|
||||
@@ -9,48 +9,56 @@ import { Maybe } from "@trpc/server";
|
||||
|
||||
export type AvatarProps = {
|
||||
className?: string;
|
||||
size: "sm" | "md" | "mdLg" | "lg";
|
||||
size: "xs" | "sm" | "md" | "mdLg" | "lg";
|
||||
imageSrc?: Maybe<string>;
|
||||
title?: string;
|
||||
alt: string;
|
||||
gravatarFallbackMd5?: string;
|
||||
fallback?: React.ReactNode;
|
||||
accepted?: boolean;
|
||||
asChild?: boolean; // Added to ignore the outer span on the fallback component - messes up styling
|
||||
};
|
||||
|
||||
const sizesPropsBySize = {
|
||||
sm: "w-6", // 24px
|
||||
md: "w-8", // 32px
|
||||
mdLg: "w-10", //40px
|
||||
lg: "w-16", // 64px
|
||||
xs: "w-4 h-4", // 16px
|
||||
sm: "w-6 h-6", // 24px
|
||||
md: "w-8 h-8", // 32px
|
||||
mdLg: "w-10 h-10", //40px
|
||||
lg: "w-16 h-16", // 64px
|
||||
} as const;
|
||||
|
||||
export function Avatar(props: AvatarProps) {
|
||||
const { imageSrc, gravatarFallbackMd5, size, alt, title } = props;
|
||||
const sizeClassname = sizesPropsBySize[size];
|
||||
const rootClass = classNames("rounded-full aspect-square", sizeClassname, "h-auto");
|
||||
const rootClass = classNames("rounded-full aspect-square ", sizeClassname);
|
||||
const avatar = (
|
||||
<AvatarPrimitive.Root
|
||||
className={classNames(
|
||||
sizeClassname,
|
||||
"dark:bg-darkgray-300 relative inline-block aspect-square overflow-hidden rounded-full"
|
||||
"dark:bg-darkgray-300 item-center relative inline-flex aspect-square justify-center overflow-hidden rounded-full"
|
||||
)}>
|
||||
<AvatarPrimitive.Image src={imageSrc ?? undefined} alt={alt} className={rootClass} />
|
||||
<AvatarPrimitive.Fallback delayMs={600}>
|
||||
{gravatarFallbackMd5 && (
|
||||
<img src={defaultAvatarSrc({ md5: gravatarFallbackMd5 })} alt={alt} className={rootClass} />
|
||||
)}
|
||||
</AvatarPrimitive.Fallback>
|
||||
{props.accepted && (
|
||||
<div
|
||||
className={classNames(
|
||||
"absolute bottom-0 right-0 block rounded-full bg-green-400 text-white ring-2 ring-white",
|
||||
size === "lg" ? "h-5 w-5" : "h-2 w-2"
|
||||
)}>
|
||||
<div className="flex h-full items-center justify-center p-[2px]">
|
||||
{size === "lg" && <Icon.FiCheck className="" />}
|
||||
<>
|
||||
<AvatarPrimitive.Image src={imageSrc ?? undefined} alt={alt} className={rootClass} />
|
||||
<AvatarPrimitive.Fallback delayMs={600} asChild={props.asChild}>
|
||||
<>
|
||||
{props.fallback && !gravatarFallbackMd5 && props.fallback}
|
||||
{gravatarFallbackMd5 && (
|
||||
<img src={defaultAvatarSrc({ md5: gravatarFallbackMd5 })} alt={alt} className={rootClass} />
|
||||
)}
|
||||
</>
|
||||
</AvatarPrimitive.Fallback>
|
||||
{props.accepted && (
|
||||
<div
|
||||
className={classNames(
|
||||
"absolute bottom-0 right-0 block rounded-full bg-green-400 text-white ring-2 ring-white",
|
||||
size === "lg" ? "h-5 w-5" : "h-2 w-2"
|
||||
)}>
|
||||
<div className="flex h-full items-center justify-center p-[2px]">
|
||||
{size === "lg" && <Icon.FiCheck />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</>
|
||||
</AvatarPrimitive.Root>
|
||||
);
|
||||
|
||||
|
||||
@@ -29,3 +29,6 @@ export {
|
||||
} from "./form";
|
||||
export { TopBanner } from "./top-banner";
|
||||
export type { TopBannerProps } from "./top-banner";
|
||||
export { AnimatedPopover } from "./popover/index";
|
||||
export { TableActions, DropdownActions } from "./table/TableActions";
|
||||
export type { ActionType } from "./table/TableActions";
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { List, ListItem, ListItemText, ListItemTitle } from "./List";
|
||||
export type { ListItemProps, ListProps } from "./List";
|
||||
@@ -0,0 +1,72 @@
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import React from "react";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
export const AnimatedPopover = ({
|
||||
text,
|
||||
count,
|
||||
children,
|
||||
}: {
|
||||
text: string;
|
||||
count?: number;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
// calculate which aligment to open the popover with based on which half of the screen it is on (left or right)
|
||||
const [align, setAlign] = React.useState<"start" | "end">("start");
|
||||
React.useEffect(() => {
|
||||
const handleResize = () => {
|
||||
const halfWidth = window.innerWidth / 2;
|
||||
const { x, y } = ref?.current?.getBoundingClientRect() || {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
if (x > halfWidth) {
|
||||
setAlign("end");
|
||||
} else {
|
||||
setAlign("start");
|
||||
}
|
||||
};
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [setAlign]);
|
||||
|
||||
return (
|
||||
<Popover.Root onOpenChange={setOpen} modal={true}>
|
||||
<Popover.Trigger asChild>
|
||||
<div
|
||||
ref={ref}
|
||||
className="item-center mb-2 flex h-9 justify-between whitespace-nowrap rounded-md border border-gray-300 py-2 px-3
|
||||
text-sm placeholder:text-gray-400 hover:cursor-pointer hover:border-gray-400
|
||||
focus:border-neutral-300 focus:outline-none focus:ring-2 focus:ring-neutral-800 focus:ring-offset-1">
|
||||
<div className="max-w-36 flex items-center">
|
||||
<div className="truncate">
|
||||
{text}
|
||||
{count && count > 0 && (
|
||||
<div className="flex h-4 w-4 items-center justify-center rounded-full">{count}</div>
|
||||
)}
|
||||
</div>
|
||||
<Icon.FiChevronDown
|
||||
className={classNames("mt-auto ml-2 transition-transform duration-150", open && "rotate-180")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Trigger>
|
||||
<Popover.Content side="bottom" align={align} asChild>
|
||||
<div
|
||||
className={classNames(
|
||||
"absolute z-50 mt-2 w-56 rounded-md bg-white shadow-sm ring-1 ring-black ring-opacity-5 focus-within:outline-none",
|
||||
align === "end" && "-translate-x-[228px]"
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
);
|
||||
};
|
||||
+8
-10
@@ -1,4 +1,5 @@
|
||||
import React, { FC } from "react";
|
||||
import { IconType } from "react-icons/lib";
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -7,18 +8,17 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuTrigger,
|
||||
ButtonBaseProps,
|
||||
Icon,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { SVGComponent } from "@lib/types/SVGComponent";
|
||||
|
||||
export type ActionType = {
|
||||
id: string;
|
||||
icon?: SVGComponent;
|
||||
icon?: IconType;
|
||||
iconClassName?: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
color?: "primary" | "secondary";
|
||||
color?: ButtonBaseProps["color"];
|
||||
} & (
|
||||
| { href: string; onClick?: never; actions?: never }
|
||||
| { href?: never; onClick: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void; actions?: never }
|
||||
@@ -33,7 +33,7 @@ const defaultAction = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const DropdownActions = ({
|
||||
export const DropdownActions = ({
|
||||
actions,
|
||||
actionTrigger,
|
||||
}: {
|
||||
@@ -52,10 +52,10 @@ const DropdownActions = ({
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent>
|
||||
{actions.map((action) => (
|
||||
<DropdownMenuItem key={action.id} className="focus-visible:outline-none">
|
||||
<DropdownMenuItem key={action.id}>
|
||||
<Button
|
||||
type="button"
|
||||
color="minimal"
|
||||
color={action.color || "minimal"}
|
||||
className="w-full rounded-none font-normal"
|
||||
href={action.href}
|
||||
StartIcon={action.icon}
|
||||
@@ -71,7 +71,7 @@ const DropdownActions = ({
|
||||
);
|
||||
};
|
||||
|
||||
const TableActions: FC<Props> = ({ actions }) => {
|
||||
export const TableActions: FC<Props> = ({ actions }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex space-x-2 rtl:space-x-reverse">
|
||||
@@ -99,5 +99,3 @@ const TableActions: FC<Props> = ({ actions }) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableActions;
|
||||
@@ -24,10 +24,14 @@ export {
|
||||
TextAreaField,
|
||||
TextField,
|
||||
TopBanner,
|
||||
AnimatedPopover,
|
||||
Select,
|
||||
SelectField,
|
||||
SelectWithValidation,
|
||||
TableActions,
|
||||
DropdownActions,
|
||||
} from "./components";
|
||||
export type { ActionType } from "./components";
|
||||
export type { AvatarProps, BadgeProps, ButtonBaseProps, ButtonProps, TopBannerProps } from "./components";
|
||||
export { default as CheckboxField } from "./components/form/checkbox/Checkbox";
|
||||
/** ⬇️ TODO - Move these to components */
|
||||
@@ -42,8 +46,6 @@ export { default as TimezoneChangeDialog } from "./TimezoneChangeDialog";
|
||||
// export { default as Tooltip } from "./Tooltip";
|
||||
export {
|
||||
Alert,
|
||||
CheckedTeamSelect,
|
||||
CustomInputItem,
|
||||
EmptyScreen,
|
||||
HorizontalTabs,
|
||||
SettingsToggle,
|
||||
@@ -54,8 +56,8 @@ export {
|
||||
SkeletonText,
|
||||
Swatch,
|
||||
Switch,
|
||||
Card,
|
||||
VerticalTabs,
|
||||
TipBanner,
|
||||
} from "./v2";
|
||||
export type { AlertProps } from "./v2";
|
||||
export { Segment, SegmentOption } from "./v2/core";
|
||||
@@ -98,7 +100,6 @@ export { ToggleGroup } from "./v2/core/form/ToggleGroup";
|
||||
export { default as ImageUploader } from "./v2/core/ImageUploader";
|
||||
export { default as AdminLayout, getLayout as getAdminLayout } from "./v2/core/layouts/AdminLayout";
|
||||
export { default as AppsLayout } from "./v2/core/layouts/AppsLayout";
|
||||
export { default as BookingLayout } from "./v2/core/layouts/BookingLayout";
|
||||
export { default as InstalledAppsLayout } from "./v2/core/layouts/InstalledAppsLayout";
|
||||
export { default as SettingsLayout, getLayout as getSettingsLayout } from "./v2/core/layouts/SettingsLayout";
|
||||
export { default as WizardLayout, getLayout as getWizardLayout } from "./v2/core/layouts/WizardLayout";
|
||||
@@ -116,8 +117,3 @@ export type { ITimezone, ITimezoneOption } from "./v2/core/TimezoneSelect";
|
||||
export { Tooltip } from "./v2/core/Tooltip";
|
||||
export { default as VerticalDivider } from "./v2/core/VerticalDivider";
|
||||
export { default as WizardForm } from "./v2/core/WizardForm";
|
||||
export { default as SAMLLogin } from "./v2/modules/auth/SAMLLogin";
|
||||
export { default as DatePicker } from "./v2/modules/booker/DatePicker";
|
||||
export { EventTypeDescriptionLazy } from "./v2/modules/event-types";
|
||||
export { EventTypeDescription } from "./v2/modules/event-types/EventTypeDescription";
|
||||
export { default as Tips } from "./v2/core/Tips";
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"@calcom/trpc": "*",
|
||||
"@formkit/auto-animate": "^1.0.0-beta.5",
|
||||
"@radix-ui/react-dialog": "^1.0.0",
|
||||
"@radix-ui/react-popover": "^1.0.2",
|
||||
"@radix-ui/react-portal": "^1.0.0",
|
||||
"@radix-ui/react-select": "^0.1.1",
|
||||
"@tanstack/react-query": "^4.3.9",
|
||||
|
||||
@@ -12,6 +12,7 @@ import UnconfirmedBookingBadge from "@calcom/features/bookings/UnconfirmedBookin
|
||||
import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner";
|
||||
import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem";
|
||||
import { TeamsUpgradeBanner } from "@calcom/features/ee/teams/components";
|
||||
import { Tips } from "@calcom/features/tips";
|
||||
import CustomBranding from "@calcom/lib/CustomBranding";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import {
|
||||
@@ -40,7 +41,6 @@ import {
|
||||
Icon,
|
||||
showToast,
|
||||
TimezoneChangeDialog,
|
||||
Tips,
|
||||
} from "../..";
|
||||
|
||||
/* TODO: Get this from endpoint */
|
||||
|
||||
@@ -46,4 +46,3 @@ export { default as Switch } from "./Switch";
|
||||
export { default as Tooltip } from "./Tooltip";
|
||||
export { default as VerticalDivider } from "./VerticalDivider";
|
||||
export { default as WizardForm } from "./WizardForm";
|
||||
export { TipBanner } from "./Tips";
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
/* TODO: Make this index explicit */
|
||||
export * from "./core";
|
||||
export * from "./modules";
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# To all Cal.com contributors
|
||||
|
||||
All these components should live in `packages/features`
|
||||
|
||||
With love,
|
||||
|
||||
zomars 💕
|
||||
@@ -1 +0,0 @@
|
||||
export { default as SAMLLogin } from "./SAMLLogin";
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./DatePicker";
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from "./auth";
|
||||
export * from "./booker";
|
||||
export * from "./event-types";
|
||||
@@ -5195,6 +5195,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
|
||||
"@radix-ui/react-arrow@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.1.tgz#5246adf79e97f89e819af68da51ddcf349ecf1c4"
|
||||
integrity sha512-1yientwXqXcErDHEv8av9ZVNEBldH8L9scVR3is20lL+jOCfcJyMFZFEY5cgIrgexsq1qggSXqiEL/d/4f+QXA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
|
||||
"@radix-ui/react-avatar@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.0.tgz#74ff3c0b39a74e07a5b2f1a1718ee480f6ed4e2b"
|
||||
@@ -5403,6 +5411,16 @@
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
|
||||
"@radix-ui/react-focus-scope@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.1.tgz#faea8c25f537c5a5c38c50914b63722db0e7f951"
|
||||
integrity sha512-Ej2MQTit8IWJiS2uuujGUmxXjF/y5xZptIIQnyd2JHLwtV0R2j9NRVoRj/1j/gJ7e3REdaBw4Hjf4a1ImhkZcQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
|
||||
"@radix-ui/react-id@0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.1.5.tgz#010d311bedd5a2884c1e9bb6aaaa4e6cc1d1d3b8"
|
||||
@@ -5509,6 +5527,28 @@
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.4"
|
||||
|
||||
"@radix-ui/react-popover@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.2.tgz#f761c8aafb61519522b8e80501c589e619073a2c"
|
||||
integrity sha512-4tqZEl9w95R5mlZ/sFdgBnfhCBOEPepLIurBA5kt/qaAhldJ1tNQd0ngr0ET0AHbPotT4mwxMPr7a+MA/wbK0g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.0"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.2"
|
||||
"@radix-ui/react-focus-guards" "1.0.0"
|
||||
"@radix-ui/react-focus-scope" "1.0.1"
|
||||
"@radix-ui/react-id" "1.0.0"
|
||||
"@radix-ui/react-popper" "1.0.1"
|
||||
"@radix-ui/react-portal" "1.0.1"
|
||||
"@radix-ui/react-presence" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
"@radix-ui/react-slot" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.0"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
|
||||
"@radix-ui/react-popper@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.0.0.tgz#fb4f937864bf39c48f27f55beee61fa9f2bef93c"
|
||||
@@ -5525,6 +5565,22 @@
|
||||
"@radix-ui/react-use-size" "1.0.0"
|
||||
"@radix-ui/rect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-popper@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.0.1.tgz#9fa8a6a493404afa225866a5cd75af23d141baa0"
|
||||
integrity sha512-J4Vj7k3k+EHNWgcKrE+BLlQfpewxA7Zd76h5I0bIa+/EqaIZ3DuwrbPj49O3wqN+STnXsBuxiHLiF0iU3yfovw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@floating-ui/react-dom" "0.7.2"
|
||||
"@radix-ui/react-arrow" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
"@radix-ui/react-use-rect" "1.0.0"
|
||||
"@radix-ui/react-use-size" "1.0.0"
|
||||
"@radix-ui/rect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-portal@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.4.tgz#17bdce3d7f1a9a0b35cb5e935ab8bc562441a7d2"
|
||||
@@ -5542,6 +5598,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
|
||||
"@radix-ui/react-portal@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.1.tgz#169c5a50719c2bb0079cf4c91a27aa6d37e5dd33"
|
||||
integrity sha512-NY2vUWI5WENgAT1nfC6JS7RU5xRYBfjZVLq0HmgEN1Ezy3rk/UruMV4+Rd0F40PEaFC5SrLS1ixYvcYIQrb4Ig==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
|
||||
"@radix-ui/react-presence@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz#814fe46df11f9a468808a6010e3f3ca7e0b2e84a"
|
||||
@@ -22513,6 +22577,17 @@ react-remove-scroll@2.5.4:
|
||||
use-callback-ref "^1.3.0"
|
||||
use-sidecar "^1.1.2"
|
||||
|
||||
react-remove-scroll@2.5.5:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77"
|
||||
integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==
|
||||
dependencies:
|
||||
react-remove-scroll-bar "^2.3.3"
|
||||
react-style-singleton "^2.2.1"
|
||||
tslib "^2.1.0"
|
||||
use-callback-ref "^1.3.0"
|
||||
use-sidecar "^1.1.2"
|
||||
|
||||
react-remove-scroll@^2.4.0:
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.4.tgz#2dfff377cf17efc00de39dad51c143fc7a1b9e3e"
|
||||
|
||||
Reference in New Issue
Block a user