Merge branch 'main' into connect-component
This commit is contained in:
commit
eade205ed3
13
README.md
13
README.md
|
@ -253,6 +253,8 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||
|
||||
#### Setting up your first user
|
||||
|
||||
##### Approach 1
|
||||
|
||||
1. Open [Prisma Studio](https://prisma.io/studio) to look at or modify the database content:
|
||||
|
||||
```sh
|
||||
|
@ -264,6 +266,17 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||
> New users are set on a `TRIAL` plan by default. You might want to adjust this behavior to your needs in the `packages/prisma/schema.prisma` file.
|
||||
1. Open a browser to [http://localhost:3000](http://localhost:3000) and login with your just created, first user.
|
||||
|
||||
##### Approach 2
|
||||
|
||||
Seed the local db by running
|
||||
|
||||
```sh
|
||||
cd packages/prisma
|
||||
yarn db-seed
|
||||
```
|
||||
|
||||
The above command will populate the local db with dummy users.
|
||||
|
||||
### E2E-Testing
|
||||
|
||||
Be sure to set the environment variable `NEXTAUTH_URL` to the correct value. If you are running locally, as the documentation within `.env.example` mentions, the value should be `http://localhost:3000`.
|
||||
|
|
|
@ -24,6 +24,11 @@ const hostSchema = _HostModel.pick({
|
|||
userId: true,
|
||||
});
|
||||
|
||||
export const childrenSchema = z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
});
|
||||
|
||||
export const schemaEventTypeBaseBodyParams = EventType.pick({
|
||||
title: true,
|
||||
description: true,
|
||||
|
@ -45,6 +50,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
|
|||
disableGuests: true,
|
||||
hideCalendarNotes: true,
|
||||
minimumBookingNotice: true,
|
||||
parentId: true,
|
||||
beforeEventBuffer: true,
|
||||
afterEventBuffer: true,
|
||||
teamId: true,
|
||||
|
@ -56,7 +62,12 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
|
|||
bookingLimits: true,
|
||||
durationLimits: true,
|
||||
})
|
||||
.merge(z.object({ hosts: z.array(hostSchema).optional().default([]) }))
|
||||
.merge(
|
||||
z.object({
|
||||
children: z.array(childrenSchema).optional().default([]),
|
||||
hosts: z.array(hostSchema).optional().default([]),
|
||||
})
|
||||
)
|
||||
.partial()
|
||||
.strict();
|
||||
|
||||
|
@ -73,6 +84,7 @@ const schemaEventTypeCreateParams = z
|
|||
seatsShowAvailabilityCount: z.boolean().optional(),
|
||||
bookingFields: eventTypeBookingFields.optional(),
|
||||
scheduleId: z.number().optional(),
|
||||
parentId: z.number().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
@ -125,6 +137,7 @@ export const schemaEventTypeReadPublic = EventType.pick({
|
|||
price: true,
|
||||
currency: true,
|
||||
slotInterval: true,
|
||||
parentId: true,
|
||||
successRedirectUrl: true,
|
||||
description: true,
|
||||
locations: true,
|
||||
|
@ -137,6 +150,8 @@ export const schemaEventTypeReadPublic = EventType.pick({
|
|||
durationLimits: true,
|
||||
}).merge(
|
||||
z.object({
|
||||
children: z.array(childrenSchema).optional().default([]),
|
||||
hosts: z.array(hostSchema).optional().default([]),
|
||||
locations: z
|
||||
.array(
|
||||
z.object({
|
||||
|
|
|
@ -52,6 +52,7 @@ export async function getHandler(req: NextApiRequest) {
|
|||
team: { select: { slug: true } },
|
||||
users: true,
|
||||
owner: { select: { username: true, id: true } },
|
||||
children: { select: { id: true, userId: true } },
|
||||
},
|
||||
});
|
||||
await checkPermissions(req, eventType);
|
||||
|
|
|
@ -46,6 +46,7 @@ async function getHandler(req: NextApiRequest) {
|
|||
team: { select: { slug: true } },
|
||||
users: true,
|
||||
owner: { select: { username: true, id: true } },
|
||||
children: { select: { id: true, userId: true } },
|
||||
},
|
||||
});
|
||||
// this really should return [], but backwards compatibility..
|
||||
|
|
|
@ -6,7 +6,9 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||
|
||||
import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type";
|
||||
|
||||
import checkParentEventOwnership from "./_utils/checkParentEventOwnership";
|
||||
import checkTeamEventEditPermission from "./_utils/checkTeamEventEditPermission";
|
||||
import checkUserMembership from "./_utils/checkUserMembership";
|
||||
import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
|
||||
|
||||
/**
|
||||
|
@ -118,10 +120,13 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
|
|||
* schedulingType:
|
||||
* type: string
|
||||
* description: The type of scheduling if a Team event. Required for team events only
|
||||
* enum: [ROUND_ROBIN, COLLECTIVE]
|
||||
* enum: [ROUND_ROBIN, COLLECTIVE, MANAGED]
|
||||
* price:
|
||||
* type: integer
|
||||
* description: Price of the event type booking
|
||||
* parentId:
|
||||
* type: integer
|
||||
* description: EventTypeId of the parent managed event
|
||||
* currency:
|
||||
* type: string
|
||||
* description: Currency acronym. Eg- usd, eur, gbp, etc.
|
||||
|
@ -276,6 +281,11 @@ async function postHandler(req: NextApiRequest) {
|
|||
|
||||
await checkPermissions(req);
|
||||
|
||||
if (parsedBody.parentId) {
|
||||
await checkParentEventOwnership(parsedBody.parentId, userId);
|
||||
await checkUserMembership(parsedBody.parentId, parsedBody.userId);
|
||||
}
|
||||
|
||||
if (isAdmin && parsedBody.userId) {
|
||||
data = { ...parsedBody, users: { connect: { id: parsedBody.userId } } };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
||||
/**
|
||||
* Checks if a user, identified by the provided userId, has ownership (or admin rights) over
|
||||
* the team associated with the event type identified by the parentId.
|
||||
*
|
||||
* @param parentId - The ID of the parent event type.
|
||||
* @param userId - The ID of the user.
|
||||
*
|
||||
* @throws {HttpError} If the parent event type is not found,
|
||||
* if the parent event type doesn't belong to any team,
|
||||
* or if the user doesn't have ownership or admin rights to the associated team.
|
||||
*/
|
||||
export default async function checkParentEventOwnership(parentId: number, userId: number) {
|
||||
const parentEventType = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id: parentId,
|
||||
},
|
||||
select: {
|
||||
teamId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!parentEventType) {
|
||||
throw new HttpError({
|
||||
statusCode: 404,
|
||||
message: "Parent event type not found.",
|
||||
});
|
||||
}
|
||||
|
||||
if (!parentEventType.teamId) {
|
||||
throw new HttpError({
|
||||
statusCode: 400,
|
||||
message: "This event type is not capable of having children",
|
||||
});
|
||||
}
|
||||
|
||||
const teamMember = await prisma.membership.findFirst({
|
||||
where: {
|
||||
teamId: parentEventType.teamId,
|
||||
userId: userId,
|
||||
OR: [{ role: "OWNER" }, { role: "ADMIN" }],
|
||||
},
|
||||
});
|
||||
|
||||
if (!teamMember) {
|
||||
throw new HttpError({
|
||||
statusCode: 403,
|
||||
message: "User is not authorized to access the team to which the parent event type belongs.",
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
||||
/**
|
||||
* Checks if a user, identified by the provided userId, is a member of the team associated
|
||||
* with the event type identified by the parentId.
|
||||
*
|
||||
* @param parentId - The ID of the event type.
|
||||
* @param userId - The ID of the user.
|
||||
*
|
||||
* @throws {HttpError} If the event type is not found,
|
||||
* if the event type doesn't belong to any team,
|
||||
* or if the user isn't a member of the associated team.
|
||||
*/
|
||||
export default async function checkUserMembership(parentId: number, userId: number) {
|
||||
const parentEventType = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id: parentId,
|
||||
},
|
||||
select: {
|
||||
teamId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!parentEventType) {
|
||||
throw new HttpError({
|
||||
statusCode: 404,
|
||||
message: "Event type not found.",
|
||||
});
|
||||
}
|
||||
|
||||
if (!parentEventType.teamId) {
|
||||
throw new HttpError({
|
||||
statusCode: 400,
|
||||
message: "This event type is not capable of having children.",
|
||||
});
|
||||
}
|
||||
|
||||
const teamMember = await prisma.membership.findFirst({
|
||||
where: {
|
||||
teamId: parentEventType.teamId,
|
||||
userId: userId,
|
||||
accepted: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!teamMember) {
|
||||
throw new HttpError({
|
||||
statusCode: 400,
|
||||
message: "User is not a team member.",
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
|
||||
import { slotsRouter } from "@calcom/trpc/server/routers/viewer/slots/_router";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
||||
|
@ -11,10 +11,10 @@ import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
|||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
/** @see https://trpc.io/docs/server-side-calls */
|
||||
const ctx = await createContext({ req, res });
|
||||
const caller = viewerRouter.createCaller(ctx);
|
||||
const caller = slotsRouter.createCaller(ctx);
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return await caller.slots.getSchedule(req.query as any /* Let tRPC handle this */);
|
||||
return await caller.getSchedule(req.query as any /* Let tRPC handle this */);
|
||||
} catch (cause) {
|
||||
if (cause instanceof TRPCError) {
|
||||
const statusCode = getHTTPStatusCodeFromError(cause);
|
||||
|
|
|
@ -189,7 +189,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||
value: 0,
|
||||
},
|
||||
...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({
|
||||
label: `minutes ${t("minutes")}`,
|
||||
label: `${minutes} ${t("minutes")}`,
|
||||
value: minutes,
|
||||
})),
|
||||
];
|
||||
|
@ -225,7 +225,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||
value: 0,
|
||||
},
|
||||
...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({
|
||||
label: `minutes ${t("minutes")}`,
|
||||
label: `${minutes} ${t("minutes")}`,
|
||||
value: minutes,
|
||||
})),
|
||||
];
|
||||
|
@ -272,7 +272,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||
value: -1,
|
||||
},
|
||||
...[5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120].map((minutes) => ({
|
||||
label: `minutes ${t("minutes")}`,
|
||||
label: `${minutes} ${t("minutes")}`,
|
||||
value: minutes,
|
||||
})),
|
||||
];
|
||||
|
|
|
@ -294,7 +294,6 @@ export const EventSetupTab = (
|
|||
|
||||
const eventLabel =
|
||||
location[eventLocationType.defaultValueVariable] || t(eventLocationType.label);
|
||||
|
||||
return (
|
||||
<li
|
||||
key={`${location.type}${index}`}
|
||||
|
|
|
@ -47,6 +47,11 @@ export default function RecurringEventController({
|
|||
<Alert severity="warning" title={t("warning_payment_recurring_event")} />
|
||||
) : (
|
||||
<>
|
||||
<Alert
|
||||
className="mb-4"
|
||||
severity="warning"
|
||||
title="Experimental: Recurring Events are currently experimental and causes some issues sometimes when checking for availability. We are working on fixing this."
|
||||
/>
|
||||
<SettingsToggle
|
||||
toggleSwitchAtTheEnd={true}
|
||||
switchContainerClassName={classNames(
|
||||
|
|
|
@ -15,7 +15,7 @@ interface Props {
|
|||
|
||||
export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col justify-center bg-[#f3f4f6] py-12 sm:px-6 lg:px-8">
|
||||
<div className="bg-subtle dark:bg-darkgray-50 flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<HeadSeo title={props.title} description={props.description} />
|
||||
{props.showLogo && <Logo small inline={false} className="mx-auto mb-auto" />}
|
||||
|
||||
|
@ -28,7 +28,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
|||
</div>
|
||||
)}
|
||||
<div className="mb-auto mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-default border-subtle mx-2 rounded-md border px-4 py-10 sm:px-10">
|
||||
<div className="bg-default dark:bg-muted border-subtle mx-2 rounded-md border px-4 py-10 sm:px-10">
|
||||
{props.children}
|
||||
</div>
|
||||
<div className="text-default mt-8 text-center text-sm">{props.footerText}</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "3.3.4",
|
||||
"version": "3.3.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
@ -49,6 +49,7 @@
|
|||
"@radix-ui/react-collapsible": "^1.0.0",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-hover-card": "^1.0.7",
|
||||
"@radix-ui/react-id": "^1.0.0",
|
||||
"@radix-ui/react-popover": "^1.0.2",
|
||||
"@radix-ui/react-radio-group": "^1.0.0",
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next";
|
|||
import { z } from "zod";
|
||||
|
||||
import { Booker } from "@calcom/atoms";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
import {
|
||||
|
@ -43,6 +44,7 @@ export default function Type({
|
|||
hideBranding={isBrandingHidden}
|
||||
isSEOIndexable={isSEOIndexable ?? true}
|
||||
entity={entity}
|
||||
bookingData={booking}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
|
@ -61,6 +63,7 @@ Type.isBookingPage = true;
|
|||
Type.PageWrapper = PageWrapper;
|
||||
|
||||
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||
const session = await getServerSession(context);
|
||||
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
|
||||
const { rescheduleUid, bookingUid, duration: queryDuration } = context.query;
|
||||
|
||||
|
@ -96,7 +99,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
|||
|
||||
let booking: GetBookingType | null = null;
|
||||
if (rescheduleUid) {
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`);
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||
} else if (bookingUid) {
|
||||
booking = await getBookingForSeatedEvent(`${bookingUid}`);
|
||||
}
|
||||
|
@ -138,6 +141,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
|||
}
|
||||
|
||||
async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
const session = await getServerSession(context);
|
||||
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
|
||||
const username = usernames[0];
|
||||
const { rescheduleUid, bookingUid, duration: queryDuration } = context.query;
|
||||
|
@ -168,7 +172,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||
|
||||
let booking: GetBookingType | null = null;
|
||||
if (rescheduleUid) {
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`);
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||
} else if (bookingUid) {
|
||||
booking = await getBookingForSeatedEvent(`${bookingUid}`);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import prisma from "@calcom/prisma";
|
|||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||
import { TRPCError } from "@calcom/trpc/server";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
|
||||
import { bookingsRouter } from "@calcom/trpc/server/routers/viewer/bookings/_router";
|
||||
|
||||
enum DirectAction {
|
||||
ACCEPT = "accept",
|
||||
|
@ -55,13 +55,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
|
|||
try {
|
||||
/** @see https://trpc.io/docs/server-side-calls */
|
||||
const ctx = await createContext({ req, res }, sessionGetter);
|
||||
const caller = viewerRouter.createCaller({
|
||||
const caller = bookingsRouter.createCaller({
|
||||
...ctx,
|
||||
req,
|
||||
res,
|
||||
user: { ...user, locale: user?.locale ?? "en" },
|
||||
});
|
||||
await caller.bookings.confirm({
|
||||
await caller.confirm({
|
||||
bookingId: booking.id,
|
||||
recurringEventId: booking.recurringEventId || undefined,
|
||||
confirmed: action === DirectAction.ACCEPT,
|
||||
|
|
|
@ -141,7 +141,6 @@ export default function Page({ requestId, isRequestExpired, csrfToken }: Props)
|
|||
);
|
||||
}
|
||||
|
||||
Page.isThemeSupported = false;
|
||||
Page.PageWrapper = PageWrapper;
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const id = context.params?.id as string;
|
||||
|
|
|
@ -126,8 +126,9 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
|||
/>
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
className="w-full justify-center"
|
||||
className="w-full justify-center dark:bg-white dark:text-black"
|
||||
type="submit"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
aria-label={t("request_password_reset")}
|
||||
loading={loading}>
|
||||
|
@ -141,7 +142,6 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
ForgotPassword.isThemeSupported = false;
|
||||
ForgotPassword.PageWrapper = PageWrapper;
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
|
|
|
@ -225,7 +225,7 @@ export default function Login({
|
|||
type="submit"
|
||||
color="primary"
|
||||
disabled={formState.isSubmitting}
|
||||
className="w-full justify-center">
|
||||
className="w-full justify-center dark:bg-white dark:text-black">
|
||||
{twoFactorRequired ? t("submit") : t("sign_in")}
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -337,7 +337,6 @@ const _getServerSideProps = async function getServerSideProps(context: GetServer
|
|||
};
|
||||
};
|
||||
|
||||
Login.isThemeSupported = false;
|
||||
Login.PageWrapper = PageWrapper;
|
||||
|
||||
export const getServerSideProps = withNonce(_getServerSideProps);
|
||||
|
|
|
@ -57,7 +57,6 @@ export function Logout(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
Logout.isThemeSupported = false;
|
||||
Logout.PageWrapper = PageWrapper;
|
||||
export default Logout;
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ export default function Success(props: SuccessProps) {
|
|||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
||||
const [calculatedDuration, setCalculatedDuration] = useState<number | undefined>(undefined);
|
||||
|
||||
const { requiresLoginToUpdate } = props;
|
||||
function setIsCancellationMode(value: boolean) {
|
||||
const _searchParams = new URLSearchParams(searchParams);
|
||||
|
||||
|
@ -532,7 +532,28 @@ export default function Success(props: SuccessProps) {
|
|||
})}
|
||||
</div>
|
||||
</div>
|
||||
{(!needsConfirmation || !userIsOwner) &&
|
||||
{requiresLoginToUpdate && (
|
||||
<>
|
||||
<hr className="border-subtle mb-8" />
|
||||
<div className="text-center">
|
||||
<span className="text-emphasis ltr:mr-2 rtl:ml-2">{t("need_to_make_a_change")}</span>
|
||||
{/* Login button but redirect to here */}
|
||||
<span className="text-default inline">
|
||||
<span className="underline" data-testid="reschedule-link">
|
||||
<Link
|
||||
href={`/auth/login?callbackUrl=${encodeURIComponent(
|
||||
`/booking/${bookingInfo?.uid}`
|
||||
)}`}
|
||||
legacyBehavior>
|
||||
{t("login")}
|
||||
</Link>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!requiresLoginToUpdate &&
|
||||
(!needsConfirmation || !userIsOwner) &&
|
||||
!isCancelled &&
|
||||
(!isCancellationMode ? (
|
||||
<>
|
||||
|
@ -540,28 +561,30 @@ export default function Success(props: SuccessProps) {
|
|||
<div className="text-center last:pb-0">
|
||||
<span className="text-emphasis ltr:mr-2 rtl:ml-2">{t("need_to_make_a_change")}</span>
|
||||
|
||||
{!props.recurringBookings && (
|
||||
<span className="text-default inline">
|
||||
<span className="underline" data-testid="reschedule-link">
|
||||
<Link
|
||||
href={`/reschedule/${seatReferenceUid || bookingInfo?.uid}`}
|
||||
legacyBehavior>
|
||||
{t("reschedule")}
|
||||
</Link>
|
||||
<>
|
||||
{!props.recurringBookings && (
|
||||
<span className="text-default inline">
|
||||
<span className="underline" data-testid="reschedule-link">
|
||||
<Link
|
||||
href={`/reschedule/${seatReferenceUid || bookingInfo?.uid}`}
|
||||
legacyBehavior>
|
||||
{t("reschedule")}
|
||||
</Link>
|
||||
</span>
|
||||
<span className="mx-2">{t("or_lowercase")}</span>
|
||||
</span>
|
||||
<span className="mx-2">{t("or_lowercase")}</span>
|
||||
</span>
|
||||
)}
|
||||
|
||||
<button
|
||||
data-testid="cancel"
|
||||
className={classNames(
|
||||
"text-default underline",
|
||||
props.recurringBookings && "ltr:mr-2 rtl:ml-2"
|
||||
)}
|
||||
onClick={() => setIsCancellationMode(true)}>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="cancel"
|
||||
className={classNames(
|
||||
"text-default underline",
|
||||
props.recurringBookings && "ltr:mr-2 rtl:ml-2"
|
||||
)}
|
||||
onClick={() => setIsCancellationMode(true)}>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
@ -1010,7 +1033,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
const session = await getServerSession(context);
|
||||
let tz: string | null = null;
|
||||
let userTimeFormat: number | null = null;
|
||||
|
||||
let requiresLoginToUpdate = false;
|
||||
if (session) {
|
||||
const user = await ssr.viewer.me.fetch();
|
||||
tz = user.timeZone;
|
||||
|
@ -1022,9 +1045,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
if (!parsedQuery.success) return { notFound: true };
|
||||
const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data;
|
||||
|
||||
const { uid: maybeUid } = await maybeGetBookingUidFromSeat(prisma, uid);
|
||||
const bookingInfoRaw = await prisma.booking.findFirst({
|
||||
where: {
|
||||
uid: await maybeGetBookingUidFromSeat(prisma, uid),
|
||||
uid: maybeUid,
|
||||
},
|
||||
select: {
|
||||
title: true,
|
||||
|
@ -1088,6 +1112,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
};
|
||||
}
|
||||
|
||||
if (eventTypeRaw.seatsPerTimeSlot && !seatReferenceUid && !session) {
|
||||
requiresLoginToUpdate = true;
|
||||
}
|
||||
|
||||
const bookingInfo = getBookingWithResponses(bookingInfoRaw);
|
||||
// @NOTE: had to do this because Server side cant return [Object objects]
|
||||
// probably fixable with json.stringify -> json.parse
|
||||
|
@ -1156,6 +1184,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
paymentStatus: payment,
|
||||
...(tz && { tz }),
|
||||
userTimeFormat,
|
||||
requiresLoginToUpdate,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next";
|
|||
import { z } from "zod";
|
||||
|
||||
import { Booker } from "@calcom/atoms";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
||||
|
@ -27,6 +28,7 @@ export default function Type({
|
|||
isTeamEvent,
|
||||
entity,
|
||||
duration,
|
||||
hashedLink,
|
||||
}: PageProps) {
|
||||
return (
|
||||
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
|
||||
|
@ -46,6 +48,7 @@ export default function Type({
|
|||
isTeamEvent={isTeamEvent}
|
||||
entity={entity}
|
||||
duration={duration}
|
||||
hashedLink={hashedLink}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
|
@ -55,6 +58,7 @@ Type.PageWrapper = PageWrapper;
|
|||
Type.isBookingPage = true;
|
||||
|
||||
async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
const session = await getServerSession(context);
|
||||
const { link, slug } = paramsSchema.parse(context.params);
|
||||
const { rescheduleUid, duration: queryDuration } = context.query;
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
|
||||
|
@ -117,7 +121,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||
|
||||
let booking: GetBookingType | null = null;
|
||||
if (rescheduleUid) {
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`);
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||
}
|
||||
|
||||
const isTeamEvent = !!hashedLink.eventType?.team?.id;
|
||||
|
@ -149,6 +153,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||
// Sending the team event from the server, because this template file
|
||||
// is reused for both team and user events.
|
||||
isTeamEvent,
|
||||
hashedLink: link,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next";
|
|||
import { URLSearchParams } from "url";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
||||
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
|
@ -12,11 +13,16 @@ export default function Type() {
|
|||
}
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const { uid: bookingId, seatReferenceUid } = z
|
||||
const session = await getServerSession(context);
|
||||
|
||||
const { uid: bookingUid, seatReferenceUid } = z
|
||||
.object({ uid: z.string(), seatReferenceUid: z.string().optional() })
|
||||
.parse(context.query);
|
||||
|
||||
const uid = await maybeGetBookingUidFromSeat(prisma, bookingId);
|
||||
const { uid, seatReferenceUid: maybeSeatReferenceUid } = await maybeGetBookingUidFromSeat(
|
||||
prisma,
|
||||
bookingUid
|
||||
);
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid,
|
||||
|
@ -37,6 +43,21 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
},
|
||||
},
|
||||
seatsPerTimeSlot: true,
|
||||
userId: true,
|
||||
owner: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
hosts: {
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dynamicEventSlugRef: true,
|
||||
|
@ -53,7 +74,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
}
|
||||
|
||||
if (!booking?.eventType && !booking?.dynamicEventSlugRef) {
|
||||
// TODO: Show something in UI to let user know that this booking is not rescheduleable.
|
||||
// TODO: Show something in UI to let user know that this booking is not rescheduleable
|
||||
return {
|
||||
notFound: true,
|
||||
} as {
|
||||
|
@ -61,6 +82,33 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
};
|
||||
}
|
||||
|
||||
// if booking event type is for a seated event and no seat reference uid is provided, throw not found
|
||||
if (booking?.eventType?.seatsPerTimeSlot && !maybeSeatReferenceUid) {
|
||||
const userId = session?.user?.id;
|
||||
|
||||
if (!userId && !seatReferenceUid) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: `/auth/login?callbackUrl=/reschedule/${bookingUid}`,
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
const userIsHost = booking?.eventType.hosts.find((host) => {
|
||||
if (host.user.id === userId) return true;
|
||||
});
|
||||
|
||||
const userIsOwnerOfEventType = booking?.eventType.owner?.id === userId;
|
||||
|
||||
if (!userIsHost && !userIsOwnerOfEventType) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as {
|
||||
notFound: true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const eventType = booking.eventType ? booking.eventType : getDefaultEvent(dynamicEventSlugRef);
|
||||
|
||||
const eventPage = `${
|
||||
|
@ -72,7 +120,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
}/${eventType?.slug}`;
|
||||
const destinationUrl = new URLSearchParams();
|
||||
|
||||
destinationUrl.set("rescheduleUid", seatReferenceUid || bookingId);
|
||||
destinationUrl.set("rescheduleUid", seatReferenceUid || bookingUid);
|
||||
|
||||
return {
|
||||
redirect: {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { usePathname } from "next/navigation";
|
|||
import { useEffect } from "react";
|
||||
|
||||
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
|
@ -43,6 +44,7 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
|
|||
const teamName = team.name || "Nameless Team";
|
||||
const isBioEmpty = !team.bio || !team.bio.replace("<p><br></p>", "").length;
|
||||
const metadata = teamMetadataSchema.parse(team.metadata);
|
||||
const orgBranding = useOrgBranding();
|
||||
|
||||
useEffect(() => {
|
||||
telemetry.event(
|
||||
|
@ -124,12 +126,6 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
|
|||
<li key={i} className="hover:bg-muted w-full">
|
||||
<Link href={`/${ch.slug}`} className="flex items-center justify-between">
|
||||
<div className="flex items-center px-5 py-5">
|
||||
<Avatar
|
||||
size="md"
|
||||
imageSrc={`/team/${ch.slug}/avatar.png`}
|
||||
alt="Team Logo"
|
||||
className="inline-flex justify-center"
|
||||
/>
|
||||
<div className="ms-3 inline-block truncate">
|
||||
<span className="text-default text-sm font-bold">{ch.name}</span>
|
||||
<span className="text-subtle block text-xs">
|
||||
|
@ -185,9 +181,11 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
|
|||
<div className="relative">
|
||||
<Avatar
|
||||
alt={teamName}
|
||||
imageSrc={`${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${
|
||||
team.slug
|
||||
}/avatar.png`}
|
||||
imageSrc={
|
||||
!!team.parent && !!orgBranding
|
||||
? `${orgBranding?.fullDomain}/org/${orgBranding?.slug}/avatar.png`
|
||||
: `${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next";
|
|||
import { z } from "zod";
|
||||
|
||||
import { Booker } from "@calcom/atoms";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
||||
|
@ -37,6 +38,7 @@ export default function Type({
|
|||
hideBranding={isBrandingHidden}
|
||||
isTeamEvent
|
||||
entity={entity}
|
||||
bookingData={booking}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
|
@ -64,6 +66,7 @@ const paramsSchema = z.object({
|
|||
// 1. Check if team exists, to show 404
|
||||
// 2. If rescheduling, get the booking details
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const session = await getServerSession(context);
|
||||
const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params);
|
||||
const { rescheduleUid, duration: queryDuration } = context.query;
|
||||
const { ssrInit } = await import("@server/lib/ssr");
|
||||
|
@ -92,7 +95,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
|
||||
let booking: GetBookingType | null = null;
|
||||
if (rescheduleUid) {
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`);
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||
}
|
||||
|
||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||
|
|
|
@ -46,7 +46,7 @@ export default function JoinCall(props: JoinCallPageProps) {
|
|||
baseText: "#FFF",
|
||||
border: "#292929",
|
||||
mainAreaBg: "#111111",
|
||||
mainAreaBgAccent: "#111111",
|
||||
mainAreaBgAccent: "#1A1A1A",
|
||||
mainAreaText: "#FFF",
|
||||
supportiveText: "#FFF",
|
||||
},
|
||||
|
|
|
@ -28,6 +28,7 @@ test.describe("Availablity tests", () => {
|
|||
await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).click();
|
||||
await page.locator('[data-testid="date-override-mark-unavailable"]').click();
|
||||
await page.locator('[data-testid="add-override-submit-btn"]').click();
|
||||
await page.locator('[data-testid="dialog-rejection"]').click();
|
||||
await expect(page.locator('[data-testid="date-overrides-list"] > li')).toHaveCount(1);
|
||||
await page.locator('[form="availability-form"][type="submit"]').click();
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import { expect } from "@playwright/test";
|
|||
import { uuid } from "short-uuid";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { randomString } from "@calcom/lib/random";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
|
||||
|
@ -96,7 +97,7 @@ test.describe("Booking with Seats", () => {
|
|||
});
|
||||
|
||||
test(`Attendees can cancel a seated event time slot`, async ({ page, users, bookings }) => {
|
||||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
|
@ -143,6 +144,19 @@ test.describe("Booking with Seats", () => {
|
|||
expect(attendeeIds).not.toContain(bookingAttendees[0].id);
|
||||
});
|
||||
|
||||
await test.step("Attendee #2 shouldn't be able to cancel booking using only booking/uid", async () => {
|
||||
await page.goto(`/booking/${booking.uid}`);
|
||||
|
||||
await expect(page.locator("[text=Cancel]")).toHaveCount(0);
|
||||
});
|
||||
|
||||
await test.step("Attendee #2 shouldn't be able to cancel booking using randomString for seatReferenceUId", async () => {
|
||||
await page.goto(`/booking/${booking.uid}?seatReferenceUid=${randomString(10)}`);
|
||||
|
||||
// expect cancel button to don't be in the page
|
||||
await expect(page.locator("[text=Cancel]")).toHaveCount(0);
|
||||
});
|
||||
|
||||
await test.step("All attendees cancelling should delete the booking for the user", async () => {
|
||||
// The remaining 2 attendees cancel
|
||||
for (let i = 1; i < bookingSeats.length; i++) {
|
||||
|
@ -166,6 +180,47 @@ test.describe("Booking with Seats", () => {
|
|||
expect(updatedBooking?.status).toBe(BookingStatus.CANCELLED);
|
||||
});
|
||||
});
|
||||
|
||||
test("Owner shouldn't be able to cancel booking without login in", async ({ page, bookings, users }) => {
|
||||
const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
await page.goto(`/booking/${booking.uid}?cancel=true`);
|
||||
await expect(page.locator("[text=Cancel]")).toHaveCount(0);
|
||||
|
||||
// expect login text to be in the page, not data-testid
|
||||
await expect(page.locator("text=Login")).toHaveCount(1);
|
||||
|
||||
// click on login button text
|
||||
await page.locator("text=Login").click();
|
||||
|
||||
// expect to be redirected to login page with query parameter callbackUrl
|
||||
await expect(page).toHaveURL(/\/auth\/login\?callbackUrl=.*/);
|
||||
|
||||
await user.apiLogin();
|
||||
|
||||
// manual redirect to booking page
|
||||
await page.goto(`/booking/${booking.uid}?cancel=true`);
|
||||
|
||||
// expect login button to don't be in the page
|
||||
await expect(page.locator("text=Login")).toHaveCount(0);
|
||||
|
||||
// fill reason for cancellation
|
||||
await page.fill('[data-testid="cancel_reason"]', "Double booked!");
|
||||
|
||||
// confirm cancellation
|
||||
await page.locator('[data-testid="confirm_cancel"]').click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
const updatedBooking = await prisma.booking.findFirst({
|
||||
where: { id: booking.id },
|
||||
});
|
||||
|
||||
expect(updatedBooking).not.toBeNull();
|
||||
expect(updatedBooking?.status).toBe(BookingStatus.CANCELLED);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Reschedule for booking with seats", () => {
|
||||
|
@ -543,4 +598,113 @@ test.describe("Reschedule for booking with seats", () => {
|
|||
.first();
|
||||
await expect(foundFirstAttendeeAgain).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("Owner shouldn't be able to reschedule booking without login in", async ({
|
||||
page,
|
||||
bookings,
|
||||
users,
|
||||
}) => {
|
||||
const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
const getBooking = await booking.self();
|
||||
|
||||
await page.goto(`/booking/${booking.uid}`);
|
||||
await expect(page.locator('[data-testid="reschedule"]')).toHaveCount(0);
|
||||
|
||||
// expect login text to be in the page, not data-testid
|
||||
await expect(page.locator("text=Login")).toHaveCount(1);
|
||||
|
||||
// click on login button text
|
||||
await page.locator("text=Login").click();
|
||||
|
||||
// expect to be redirected to login page with query parameter callbackUrl
|
||||
await expect(page).toHaveURL(/\/auth\/login\?callbackUrl=.*/);
|
||||
|
||||
await user.apiLogin();
|
||||
|
||||
// manual redirect to booking page
|
||||
await page.goto(`/booking/${booking.uid}`);
|
||||
|
||||
// expect login button to don't be in the page
|
||||
await expect(page.locator("text=Login")).toHaveCount(0);
|
||||
|
||||
// reschedule-link click
|
||||
await page.locator('[data-testid="reschedule-link"]').click();
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
// data displayed in form should be user owner
|
||||
const nameElement = await page.locator("input[name=name]");
|
||||
const name = await nameElement.inputValue();
|
||||
expect(name).toBe(user.username);
|
||||
|
||||
//same for email
|
||||
const emailElement = await page.locator("input[name=email]");
|
||||
const email = await emailElement.inputValue();
|
||||
expect(email).toBe(user.email);
|
||||
|
||||
// reason to reschedule input should be visible textfield with name rescheduleReason
|
||||
const reasonElement = await page.locator("textarea[name=rescheduleReason]");
|
||||
await expect(reasonElement).toBeVisible();
|
||||
|
||||
// expect to be redirected to reschedule page
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
// should wait for URL but that path starts with booking/
|
||||
await page.waitForURL(/\/booking\/.*/);
|
||||
|
||||
await expect(page).toHaveURL(/\/booking\/.*/);
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
const updatedBooking = await prisma.booking.findFirst({
|
||||
where: { id: booking.id },
|
||||
});
|
||||
|
||||
expect(updatedBooking).not.toBeNull();
|
||||
expect(getBooking?.startTime).not.toBe(updatedBooking?.startTime);
|
||||
expect(getBooking?.endTime).not.toBe(updatedBooking?.endTime);
|
||||
expect(updatedBooking?.status).toBe(BookingStatus.ACCEPTED);
|
||||
});
|
||||
|
||||
test("Owner shouldn't be able to reschedule when going directly to booking/rescheduleUid", async ({
|
||||
page,
|
||||
bookings,
|
||||
users,
|
||||
}) => {
|
||||
const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
const getBooking = await booking.self();
|
||||
|
||||
await page.goto(`/${user.username}/seats?rescheduleUid=${getBooking?.uid}&bookingUid=null`);
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
// expect textarea with name notes to be visible
|
||||
const notesElement = await page.locator("textarea[name=notes]");
|
||||
await expect(notesElement).toBeVisible();
|
||||
|
||||
// expect button confirm instead of reschedule
|
||||
await expect(page.locator('[data-testid="confirm-book-button"]')).toHaveCount(1);
|
||||
|
||||
// now login and try again
|
||||
await user.apiLogin();
|
||||
|
||||
await page.goto(`/${user.username}/seats?rescheduleUid=${getBooking?.uid}&bookingUid=null`);
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
await expect(page).toHaveTitle(/(?!.*reschedule).*/);
|
||||
|
||||
// expect button reschedule
|
||||
await expect(page.locator('[data-testid="confirm-reschedule-button"]')).toHaveCount(1);
|
||||
});
|
||||
|
||||
// @TODO: force 404 when rescheduleUid is not found
|
||||
});
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
import { expect } from "@playwright/test";
|
||||
|
||||
import { randomString } from "@calcom/lib/random";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
|
||||
const createTeamsAndMembership = async (userIdOne: number, userIdTwo: number) => {
|
||||
const teamOne = await prisma.team.create({
|
||||
data: {
|
||||
name: "test-insights",
|
||||
slug: `test-insights-${Date.now()}-${randomString(5)}}`,
|
||||
},
|
||||
});
|
||||
|
||||
const teamTwo = await prisma.team.create({
|
||||
data: {
|
||||
name: "test-insights-2",
|
||||
slug: `test-insights-2-${Date.now()}-${randomString(5)}}`,
|
||||
},
|
||||
});
|
||||
if (!userIdOne || !userIdTwo || !teamOne || !teamTwo) {
|
||||
throw new Error("Failed to create test data");
|
||||
}
|
||||
|
||||
// create memberships
|
||||
await prisma.membership.create({
|
||||
data: {
|
||||
userId: userIdOne,
|
||||
teamId: teamOne.id,
|
||||
accepted: true,
|
||||
role: "ADMIN",
|
||||
},
|
||||
});
|
||||
await prisma.membership.create({
|
||||
data: {
|
||||
teamId: teamTwo.id,
|
||||
userId: userIdOne,
|
||||
accepted: true,
|
||||
role: "ADMIN",
|
||||
},
|
||||
});
|
||||
await prisma.membership.create({
|
||||
data: {
|
||||
teamId: teamOne.id,
|
||||
userId: userIdTwo,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
});
|
||||
await prisma.membership.create({
|
||||
data: {
|
||||
teamId: teamTwo.id,
|
||||
userId: userIdTwo,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
});
|
||||
return { teamOne, teamTwo };
|
||||
};
|
||||
|
||||
test.afterAll(async ({ users }) => {
|
||||
await users.deleteAll();
|
||||
});
|
||||
|
||||
test.describe("Insights", async () => {
|
||||
test("should be able to go to insights as admins", async ({ page, users }) => {
|
||||
const user = await users.create();
|
||||
const userTwo = await users.create();
|
||||
await createTeamsAndMembership(user.id, userTwo.id);
|
||||
|
||||
await user.apiLogin();
|
||||
|
||||
// go to insights page
|
||||
await page.goto("/insights");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// expect url to have isAll and TeamId in query params
|
||||
expect(page.url()).toContain("isAll=false");
|
||||
expect(page.url()).toContain("teamId=");
|
||||
});
|
||||
|
||||
test("should be able to go to insights as members", async ({ page, users }) => {
|
||||
const user = await users.create();
|
||||
const userTwo = await users.create();
|
||||
|
||||
await userTwo.apiLogin();
|
||||
|
||||
await createTeamsAndMembership(user.id, userTwo.id);
|
||||
// go to insights page
|
||||
await page.goto("/insights");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// expect url to have isAll and TeamId in query params
|
||||
|
||||
expect(page.url()).toContain("isAll=false");
|
||||
expect(page.url()).not.toContain("teamId=");
|
||||
});
|
||||
|
||||
test("team select filter should have 2 teams and your account option only as member", async ({
|
||||
page,
|
||||
users,
|
||||
}) => {
|
||||
const user = await users.create();
|
||||
const userTwo = await users.create();
|
||||
|
||||
await user.apiLogin();
|
||||
|
||||
await createTeamsAndMembership(user.id, userTwo.id);
|
||||
// go to insights page
|
||||
await page.goto("/insights");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// get div from team select filter with this class flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1
|
||||
await page.getByTestId("dashboard-shell").getByText("Team: test-insights").click();
|
||||
await page
|
||||
.locator('div[class="flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1"]')
|
||||
.click();
|
||||
const teamSelectFilter = await page.locator(
|
||||
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
|
||||
);
|
||||
|
||||
await expect(teamSelectFilter).toHaveCount(3);
|
||||
});
|
||||
|
||||
test("Insights Organization should have isAll option true", async ({ users, page }) => {
|
||||
const owner = await users.create(undefined, {
|
||||
hasTeam: true,
|
||||
isUnpublished: true,
|
||||
isOrg: true,
|
||||
hasSubteam: true,
|
||||
});
|
||||
await owner.apiLogin();
|
||||
|
||||
await page.goto("/insights");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.getByTestId("dashboard-shell").getByText("All").nth(1).click();
|
||||
|
||||
const teamSelectFilter = await page.locator(
|
||||
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
|
||||
);
|
||||
|
||||
await expect(teamSelectFilter).toHaveCount(4);
|
||||
});
|
||||
|
||||
test("should have all option in team-and-self filter as admin", async ({ page, users }) => {
|
||||
const owner = await users.create();
|
||||
const member = await users.create();
|
||||
|
||||
await createTeamsAndMembership(owner.id, member.id);
|
||||
|
||||
await owner.apiLogin();
|
||||
|
||||
await page.goto("/insights");
|
||||
|
||||
// get div from team select filter with this class flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1
|
||||
await page.getByTestId("dashboard-shell").getByText("Team: test-insights").click();
|
||||
await page
|
||||
.locator('div[class="flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1"]')
|
||||
.click();
|
||||
const teamSelectFilter = await page.locator(
|
||||
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
|
||||
);
|
||||
|
||||
await expect(teamSelectFilter).toHaveCount(3);
|
||||
});
|
||||
|
||||
test("should be able to switch between teams and self profile for insights", async ({ page, users }) => {
|
||||
const owner = await users.create();
|
||||
const member = await users.create();
|
||||
|
||||
await createTeamsAndMembership(owner.id, member.id);
|
||||
|
||||
await owner.apiLogin();
|
||||
|
||||
await page.goto("/insights");
|
||||
|
||||
// get div from team select filter with this class flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1
|
||||
await page.getByTestId("dashboard-shell").getByText("Team: test-insights").click();
|
||||
await page
|
||||
.locator('div[class="flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1"]')
|
||||
.click();
|
||||
const teamSelectFilter = await page.locator(
|
||||
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
|
||||
);
|
||||
|
||||
await expect(teamSelectFilter).toHaveCount(3);
|
||||
|
||||
// switch to self profile
|
||||
await page.getByTestId("dashboard-shell").getByText("Your Account").click();
|
||||
|
||||
// switch to team 1
|
||||
await page.getByTestId("dashboard-shell").getByText("test-insights").nth(0).click();
|
||||
|
||||
// switch to team 2
|
||||
await page.getByTestId("dashboard-shell").getByText("test-insights-2").click();
|
||||
});
|
||||
|
||||
test("should be able to switch between memberUsers", async ({ page, users }) => {
|
||||
const owner = await users.create();
|
||||
const member = await users.create();
|
||||
|
||||
await createTeamsAndMembership(owner.id, member.id);
|
||||
|
||||
await owner.apiLogin();
|
||||
|
||||
await page.goto("/insights");
|
||||
|
||||
await page.getByText("Add filter").click();
|
||||
|
||||
await page.getByRole("button", { name: "User" }).click();
|
||||
// <div class="flex select-none truncate font-medium" data-state="closed">People</div>
|
||||
await page.locator('div[class="flex select-none truncate font-medium"]').getByText("People").click();
|
||||
|
||||
await page
|
||||
.locator('div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]')
|
||||
.nth(0)
|
||||
.click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page
|
||||
.locator('div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]')
|
||||
.nth(1)
|
||||
.click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
// press escape button to close the filter
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page.getByRole("button", { name: "Clear" }).click();
|
||||
|
||||
// expect for "Team: test-insight" text in page
|
||||
expect(await page.locator("text=Team: test-insights").isVisible()).toBeTruthy();
|
||||
});
|
||||
});
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 322 B |
|
@ -288,6 +288,7 @@
|
|||
"when": "Wann",
|
||||
"where": "Wo",
|
||||
"add_to_calendar": "Zum Kalender hinzufügen",
|
||||
"add_to_calendar_description": "Legen Sie fest, wo neue Termine hinzugefügt werden sollen, wenn Sie gebucht werden.",
|
||||
"add_another_calendar": "Einen weiteren Kalender hinzufügen",
|
||||
"other": "Sonstige",
|
||||
"email_sign_in_subject": "Ihr Anmelde-Link für {{appName}}",
|
||||
|
@ -599,6 +600,7 @@
|
|||
"hide_book_a_team_member": "„Ein Teammitglied buchen“-Schaltfläche ausblenden",
|
||||
"hide_book_a_team_member_description": "Blendet die „Ein Teammitglied buchen“-Schaltflächen auf Ihren öffentlichen Seiten aus.",
|
||||
"danger_zone": "Achtung",
|
||||
"account_deletion_cannot_be_undone": "Vorsicht. Löschen eines Kontos kann nicht rückgängig gemacht werden.",
|
||||
"back": "Zurück",
|
||||
"cancel": "Absagen",
|
||||
"cancel_all_remaining": "Alle verbleibenden absagen",
|
||||
|
@ -688,6 +690,7 @@
|
|||
"people": "Personen",
|
||||
"your_email": "Ihre E-Mail-Adresse",
|
||||
"change_avatar": "Profilbild ändern",
|
||||
"upload_avatar": "Avatar hochladen",
|
||||
"language": "Sprache",
|
||||
"timezone": "Zeitzone",
|
||||
"first_day_of_week": "Erster Tag der Woche",
|
||||
|
@ -1276,6 +1279,7 @@
|
|||
"personal_cal_url": "Meine persönliche {{appName}}-URL",
|
||||
"bio_hint": "Schreiben Sie eine kurze Beschreibung, welche auf Ihrer persönlichen Profil-Seite erscheinen wird.",
|
||||
"user_has_no_bio": "Dieser Benutzer hat noch keine Bio hinzugefügt.",
|
||||
"bio": "Biografie",
|
||||
"delete_account_modal_title": "Account löschen",
|
||||
"confirm_delete_account_modal": "Sind Sie sicher, dass Sie Ihr {{appName}}-Konto löschen möchten?",
|
||||
"delete_my_account": "Meinen Account löschen",
|
||||
|
@ -1530,6 +1534,7 @@
|
|||
"problem_registering_domain": "Es gab ein Problem bei der Registrierung der Subdomain, bitte versuchen Sie es erneut oder kontaktieren Sie einen Administrator",
|
||||
"team_publish": "Team veröffentlichen",
|
||||
"number_text_notifications": "Telefonnummer (Textbenachrichtigungen)",
|
||||
"number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)",
|
||||
"attendee_email_variable": "Teilnehmer E-Mail",
|
||||
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
|
||||
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie ...",
|
||||
|
@ -1639,6 +1644,7 @@
|
|||
"minimum_round_robin_hosts_count": "Anzahl der Veranstalter, die teilnehmen müssen",
|
||||
"hosts": "Veranstalter",
|
||||
"upgrade_to_enable_feature": "Sie müssen ein Team erstellen, um diese Funktion zu aktivieren. Klicken Sie hier, um ein Team zu erstellen.",
|
||||
"orgs_upgrade_to_enable_feature": "Sie müssen auf unsere Enterprise-Lizenz aktualisieren, um diese Funktion zu aktivieren.",
|
||||
"new_attendee": "Neuer Teilnehmer",
|
||||
"awaiting_approval": "Wartet auf Genehmigung",
|
||||
"requires_google_calendar": "Diese App erfordert eine Verbindung mit Google Calendar",
|
||||
|
@ -1743,6 +1749,7 @@
|
|||
"show_on_booking_page": "Auf der Buchungsseite anzeigen",
|
||||
"get_started_zapier_templates": "Legen Sie mit Zapier-Vorlagen los",
|
||||
"team_is_unpublished": "{{team}} ist unveröffentlicht",
|
||||
"org_is_unpublished_description": "Dieser Organisations-Link ist derzeit nicht verfügbar. Bitte kontaktieren Sie den Organisations-Besitzer oder fragen Sie ihn, ob er ihn veröffentlicht.",
|
||||
"team_is_unpublished_description": "Dieser {{entity}}-Link ist derzeit nicht verfügbar. Bitte kontaktieren Sie den {{entity}}-Besitzer oder fragen Sie ihn, ob er ihn veröffentlicht.",
|
||||
"team_member": "Teammitglied",
|
||||
"a_routing_form": "Ein Weiterleitungsformular",
|
||||
|
@ -1877,6 +1884,7 @@
|
|||
"edit_invite_link": "Linkeinstellungen bearbeiten",
|
||||
"invite_link_copied": "Einladungslink kopiert",
|
||||
"invite_link_deleted": "Einladungslink gelöscht",
|
||||
"api_key_deleted": "API-Schlüssel gelöscht",
|
||||
"invite_link_updated": "Einladungslink-Einstellungen gespeichert",
|
||||
"link_expires_after": "Links verfallen nach...",
|
||||
"one_day": "1 Tag",
|
||||
|
@ -2011,7 +2019,13 @@
|
|||
"attendee_last_name_variable": "Nachname des Teilnehmers",
|
||||
"attendee_first_name_info": "Vorname der buchenden Person",
|
||||
"attendee_last_name_info": "Nachname der buchenden Person",
|
||||
"your_monthly_digest": "Ihre monatliche Statistik",
|
||||
"member_name": "Mitgliedsname",
|
||||
"most_popular_events": "Beliebteste Termine",
|
||||
"summary_of_events_for_your_team_for_the_last_30_days": "Hier ist Ihre Zusammenfassung der beliebten Termine für Ihr Team {{teamName}} der letzten 30 Tage",
|
||||
"me": "Ich",
|
||||
"monthly_digest_email": "Monatliche Statistik E-Mail",
|
||||
"monthly_digest_email_for_teams": "Monatliche Statistik E-Mail für Teams",
|
||||
"verify_team_tooltip": "Verifizieren Sie Ihr Team, um das Senden von Nachrichten an Teilnehmer zu aktivieren",
|
||||
"member_removed": "Mitglied entfernt",
|
||||
"my_availability": "Meine Verfügbarkeit",
|
||||
|
@ -2041,12 +2055,28 @@
|
|||
"team_no_event_types": "Dieses Team hat keine Ereignistypen",
|
||||
"seat_options_doesnt_multiple_durations": "Platzoption unterstützt mehrere Dauern nicht",
|
||||
"include_calendar_event": "Kalenderereignis hinzufügen",
|
||||
"oAuth": "OAuth",
|
||||
"recently_added": "Kürzlich hinzugefügt",
|
||||
"no_members_found": "Keine Mitglieder gefunden",
|
||||
"event_setup_length_error": "Ereignis-Einrichtung: Die Dauer muss mindestens 1 Minute betragen.",
|
||||
"availability_schedules": "Verfügbarkeitspläne",
|
||||
"unauthorized": "Nicht authorisiert",
|
||||
"access_cal_account": "{{clientName}} möchte auf Ihr {{appName}} Konto zugreifen",
|
||||
"select_account_team": "Konto oder Team auswählen",
|
||||
"allow_client_to": "Dies wird {{clientName}} erlauben",
|
||||
"see_personal_info": "Ihre persönlichen Daten einzusehen, einschließlich persönlicher Informationen, die Sie öffentlich zugänglich gemacht haben",
|
||||
"see_primary_email_address": "Ihre primäre E-Mail-Adresse einzusehen",
|
||||
"connect_installed_apps": "Sich mit Ihren installierten Apps zu verbinden",
|
||||
"access_event_type": "Lesen, Bearbeiten, Löschen Ihrer Ereignis-Typen",
|
||||
"access_availability": "Lesen, Bearbeiten, Löschen Ihrer Verfügbarkeiten",
|
||||
"access_bookings": "Lesen, Bearbeiten, Löschen Ihrer Termine",
|
||||
"allow_client_to_do": "{{clientName}} zulassen, dies zu tun?",
|
||||
"allow": "Zulassen",
|
||||
"view_only_edit_availability_not_onboarded": "Dieser Benutzer hat das Onboarding noch nicht abgeschlossen. Sie können seine Verfügbarkeit erst festlegen, wenn er das Onboarding abgeschlossen hat.",
|
||||
"view_only_edit_availability": "Sie sehen die Verfügbarkeit dieses Benutzers. Sie können nur Ihre eigene Verfügbarkeit bearbeiten.",
|
||||
"edit_users_availability": "Benutzerverfügbarkeit bearbeiten: {{username}}",
|
||||
"resend_invitation": "Einladung erneut senden",
|
||||
"invitation_resent": "Die Einladung wurde erneut gesendet.",
|
||||
"this_app_is_not_setup_already": "Diese App wurde noch nicht eingerichtet",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Fügen Sie Ihre neuen Code-Zeilen über dieser hinzu ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
|
|
@ -1535,6 +1535,7 @@
|
|||
"problem_registering_domain": "There was a problem with registering the subdomain, please try again or contact an administrator",
|
||||
"team_publish": "Publish team",
|
||||
"number_text_notifications": "Phone number (Text notifications)",
|
||||
"number_sms_notifications": "Phone number (SMS notifications)",
|
||||
"attendee_email_variable": "Attendee email",
|
||||
"attendee_email_info": "The person booking's email",
|
||||
"kbar_search_placeholder": "Type a command or search...",
|
||||
|
@ -1618,6 +1619,7 @@
|
|||
"date_overrides_mark_all_day_unavailable_other": "Mark unavailable on selected dates",
|
||||
"date_overrides_add_btn": "Add Override",
|
||||
"date_overrides_update_btn": "Update Override",
|
||||
"date_successfully_added": "Date override added successfully",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-copy",
|
||||
"set_as_default": "Set as default",
|
||||
"hide_eventtype_details": "Hide event type details",
|
||||
|
@ -1658,7 +1660,7 @@
|
|||
"no_recordings_found": "No recordings found",
|
||||
"new_workflow_subtitle": "New workflow for...",
|
||||
"reporting": "Reporting",
|
||||
"reporting_feature": "See all incoming from data and download it as a CSV",
|
||||
"reporting_feature": "See all incoming form data and download it as a CSV",
|
||||
"teams_plan_required": "Teams plan required",
|
||||
"routing_forms_are_a_great_way": "Routing forms are a great way to route your incoming leads to the right person. Upgrade to a Teams plan to access this feature.",
|
||||
"choose_a_license": "Choose a license",
|
||||
|
|
|
@ -7,17 +7,32 @@
|
|||
"second_other": "{{count}} segundo",
|
||||
"upgrade_now": "Eguneratu orain",
|
||||
"accept_invitation": "Onartu gonbidapena",
|
||||
"calcom_explained": "{{appName}}-ek bilerak programatzeko azpiegitura eskaintzen du guztiontzat.",
|
||||
"calcom_explained_new_user": "Bukatu zure {{appName}} kontua konfiguratzen! Bileren programazio-arazo guztiak konpontzeko urrats gutxi batzuk besterik ez zaizkizu geratzen.",
|
||||
"have_any_questions": "Galderarik? Laguntzeko gaude.",
|
||||
"reset_password_subject": "{{appName}}: Pasahitza berrezartzeko argibideak",
|
||||
"verify_email_subject": "{{appName}}: egiaztatu zure kontua",
|
||||
"check_your_email": "Begiratu zure emaila",
|
||||
"verify_email_page_body": "Email bat bidali dugu {{email}} helbidera. Garrantzitsua da zure email helbidea egiaztatzea, {{appName}}-tik mezuak eta egutegiko eguneratzeak ahalik eta hobekien jasoko dituzula bermatzeko.",
|
||||
"verify_email_banner_body": "Egiaztatu zure email helbidea mezuak eta egutegiko eguneratzeak ahalik eta hobekien jasoko dituzula bermatzeko",
|
||||
"verify_email_email_header": "Egiaztatu zure email helbidea",
|
||||
"verify_email_email_button": "Egiaztatu emaila",
|
||||
"verify_email_email_body": "Mesedez, egiaztatu zure email helbidea beheko botoia sakatuz.",
|
||||
"verify_email_by_code_email_body": "Mesedez, egiaztatu zure email helbidea beheko kodea erabiliz.",
|
||||
"verify_email_email_link_text": "Hemen duzu esteka, botoiak sakatzea gustuko ez baduzu:",
|
||||
"email_verification_code": "Sartu egiaztatze-kodea",
|
||||
"email_verification_code_placeholder": "Sartu zure email helbidera bidalitako egiaztatze-kodea",
|
||||
"incorrect_email_verification_code": "Egiaztatze-kodea ez da zuzena.",
|
||||
"email_sent": "Email mezua zuzen bidali da",
|
||||
"email_not_sent": "Errore bat gertatu da email mezua bidaltzerakoan",
|
||||
"event_declined_subject": "Baztertua: {{title}} {{date}}(e)an",
|
||||
"event_cancelled_subject": "Bertan behera: {{title}} {{date}}(e)an",
|
||||
"event_request_declined": "Zure gertaera-eskaera baztertua izan da",
|
||||
"event_request_declined_recurring": "Zure gertaera errepikari-eskaera baztertua izan da",
|
||||
"event_request_cancelled": "Zure programatutako gertaera bertan behera utzi da",
|
||||
"organizer": "Antolatzailea",
|
||||
"need_to_reschedule_or_cancel": "Programazioa aldatu edo bertan behera utzi behar duzu?",
|
||||
"no_options_available": "Ez dago aukerarik eskuragarri",
|
||||
"cancellation_reason": "Bertan behera uztearen arrazoia (aukerakoa)",
|
||||
"cancellation_reason_placeholder": "Zergatik utzi duzu bertan behera?",
|
||||
"rejection_reason": "Errefusatzeko arrazoia",
|
||||
|
@ -25,7 +40,11 @@
|
|||
"rejection_reason_description": "Ziur zaude erreserba errefusatu nahi duzula? Erreserba-eskaera egin duen pertsonari jakinaraziko zaio. Arrazoi bat adieraz dezakezu behean.",
|
||||
"rejection_confirmation": "Errefusatu erreserba",
|
||||
"manage_this_event": "Kudeatu gertaera hau",
|
||||
"invite_team_member": "Gonbidatu taldekidea",
|
||||
"invite_team_individual_segment": "Gonbidatu norbanakoa",
|
||||
"invite_team_notifcation_badge": "Gon.",
|
||||
"your_event_has_been_scheduled": "Zure gertaera programatu da",
|
||||
"your_event_has_been_scheduled_recurring": "Zure gertaera errepikaria programatu da",
|
||||
"error_message": "Errore-mezua honakoa ian da: '{{errorMessage}}'",
|
||||
"refund_failed_subject": "Itzulketak huts egin du: {{name}} - {{date}} - {{eventType}}",
|
||||
"refund_failed": "Huts egin du itzulketak {{eventType}} gertaerarako, {{userName}}(r)ekin {{date}}(e)an.",
|
||||
|
@ -37,26 +56,79 @@
|
|||
"refunded": "Itzulita",
|
||||
"payment": "Ordainketa",
|
||||
"pay_now": "Ordaindu orain",
|
||||
"still_waiting_for_approval": "Gertaera bat onarpenaren zain dago",
|
||||
"event_is_still_waiting": "Gertaera-eskaera oraindik zain dago: {{attendeeName}} - {{date}} - {{eventType}}",
|
||||
"no_more_results": "Emaitza gehiagorik ez",
|
||||
"no_results": "Emaitzarik ez",
|
||||
"load_more_results": "Kargatu emaitza gehiago",
|
||||
"integration_meeting_id": "{{integrationName}} bileraren IDa: {{meetingId}}",
|
||||
"confirmed_event_type_subject": "Baieztatua: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
|
||||
"new_event_request": "Gertaera berriaren eskaera: {{attendeeName}} - {{date}} - {{eventType}}",
|
||||
"confirm_or_reject_request": "Baieztatu edo errefusatu eskaera",
|
||||
"check_bookings_page_to_confirm_or_reject": "Begiratu zure erreserba-orrialdea erreserba baieztatu edo errefusatzeko.",
|
||||
"event_awaiting_approval": "Gertaera bat zure onarpenaren zain dago",
|
||||
"event_awaiting_approval_recurring": "Gertaera errepikari bat zure onarpenaren zain dago",
|
||||
"someone_requested_an_event": "Norbaitek zure egutegian gertaera bat programatzeko eskaera egin du.",
|
||||
"someone_requested_password_reset": "Norbaitek zure pasahitza aldatzeko esteka bat eskatu du.",
|
||||
"password_reset_email_sent": "Email helbide hau gure sisteman baldin badago, berrezartzeko email mezu bat jaso behar zenuke.",
|
||||
"password_reset_instructions": "Ez baduzu eskaera hau egin, segurua da email mezu honi kasurik ez egitea, eta zure pasahitza ez da aldatuko.",
|
||||
"event_awaiting_approval_subject": "Onarpenaren zain: {{title}} {{date}}(e)an",
|
||||
"event_still_awaiting_approval": "Gertaera bat zure onarpenaren zain dago oraindik",
|
||||
"booking_submitted_subject": "Erreserba bidalita: {{title}} {{date}}(e)an",
|
||||
"download_recording_subject": "Deskargatu grabaketa: {{title}} {{date}}(e)an",
|
||||
"download_your_recording": "Deskargatu zure grabaketa",
|
||||
"your_meeting_has_been_booked": "Zure bileraren erreserba egin da",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Zure {{title}} getaeraren programazioa aldatu egin da {{date}}(e)ra.",
|
||||
"event_has_been_rescheduled": "Eguneratuta - Zure gertaeraren programazioa aldatu egin da",
|
||||
"request_reschedule_subtitle": "{{organizer}}(e)k erreserba bertan behera utzi du eta beste denbora-tarte bat hautatzeko eskatu dizu.",
|
||||
"request_reschedule_title_organizer": "Beste denbora-tarte bat hautatzeko eskatu diozu {{attendee}}(r)i",
|
||||
"hi_user_name": "Kaixo {{name}}",
|
||||
"ics_event_title": "{{eventType}} {{name}}(r)ekin",
|
||||
"notes": "Oharrak",
|
||||
"manage_my_bookings": "Kudeatu nire erreserbak",
|
||||
"rejected_event_type_with_organizer": "Errefusatua: {{eventType}} {{organizer}}(r)ekin {{date}}(e)an",
|
||||
"hi": "Kaixo",
|
||||
"use_link_to_reset_password": "Erabili beheko esteka pasahitza berrezartzeko",
|
||||
"hey_there": "Kaixo,",
|
||||
"forgot_your_password_calcom": "Pasahitza ahaztu duzu? - {{appName}}",
|
||||
"dismiss": "Alde batera utzi",
|
||||
"no_data_yet": "Ez dago daturik",
|
||||
"ping_test": "Ping testa",
|
||||
"upcoming": "Laster",
|
||||
"recurring": "Errepikariak",
|
||||
"past": "Iraganekoak",
|
||||
"choose_a_file": "Hautatu fitxategi bat...",
|
||||
"upload_image": "Igo irudia",
|
||||
"upload_target": "Igo {{target}}",
|
||||
"no_target": "Ez dago {{target}}(r)ik",
|
||||
"view_notifications": "Ikusi jakinarazpenak",
|
||||
"view_public_page": "Ikusi orrialde publikoa",
|
||||
"copy_public_page_link": "Kopiatu orrialde publikoaren esteka",
|
||||
"sign_out": "Saioa itxi",
|
||||
"add_another": "Gehitu beste bat",
|
||||
"install_another": "Instalatu beste bat",
|
||||
"unavailable": "Ez eskuragarri",
|
||||
"set_work_schedule": "Ezarri zure laneko ordutegia",
|
||||
"change_bookings_availability": "Aldatu noiz zauden prest erreserbak jasotzeko",
|
||||
"select": "Hautatu...",
|
||||
"text": "Testua",
|
||||
"multiline_text": "Lerro ugaritako testua",
|
||||
"number": "Zenbakia",
|
||||
"checkbox": "Kontrol-laukia",
|
||||
"is_required": "Derrigorrezkoa da",
|
||||
"required": "Derrigorrezkoa",
|
||||
"optional": "Hautazkoa",
|
||||
"input_type": "Sarrera-mota",
|
||||
"rejected": "Baztertua",
|
||||
"unconfirmed": "Baieztatu gabea",
|
||||
"guests": "Gonbidatuak",
|
||||
"create_account": "Sortu kontua",
|
||||
"confirm_password": "Baieztatu pasahitza",
|
||||
"create_booking_link_with_calcom": "Sor ezazu zeure erreserba-esteka {{appName}}(e)kin",
|
||||
"user_needs_to_confirm_or_reject_booking": "{{user}}(e)k erreserba baieztatu edo errefusatu behar du oraindik.",
|
||||
"booking_submitted": "Zure erreserba bidali da",
|
||||
"booking_confirmed": "Zure erreserba baieztatu da",
|
||||
"bookerlayout_column_view": "Zutabea",
|
||||
"back_to_bookings": "Itzuli erreserbatara",
|
||||
"really_cancel_booking": "Benetan bertan behera utzi nahi duzu zure erreserba?",
|
||||
"cannot_cancel_booking": "Ezin duzu erreserba hau bertan behera utzi",
|
||||
|
|
|
@ -1530,6 +1530,7 @@
|
|||
"problem_registering_domain": "Un problème est survenu lors de l'enregistrement du sous-domaine, veuillez réessayer ou contacter un administrateur",
|
||||
"team_publish": "Publier l'équipe",
|
||||
"number_text_notifications": "Numéro de téléphone (notifications par SMS)",
|
||||
"number_sms_notifications": "Numéro de téléphone (notifications par SMS)",
|
||||
"attendee_email_variable": "Adresse e-mail du participant",
|
||||
"attendee_email_info": "Adresse e-mail du participant",
|
||||
"kbar_search_placeholder": "Saisissez une commande ou une recherche...",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -1530,6 +1530,7 @@
|
|||
"problem_registering_domain": "Houve um problema ao registar o subdomínio. Tente novamente ou contacte um administrador",
|
||||
"team_publish": "Publicar equipa",
|
||||
"number_text_notifications": "Número de telefone (notificações de texto)",
|
||||
"number_sms_notifications": "Número de telefone (notificações SMS)",
|
||||
"attendee_email_variable": "E-mail do participante",
|
||||
"attendee_email_info": "O e-mail do responsável pela reserva",
|
||||
"kbar_search_placeholder": "Digite um comando ou pesquise...",
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import type { CredentialOwner } from "types";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import type { RouterOutputs } from "@calcom/trpc";
|
||||
|
||||
import { DynamicComponent } from "./DynamicComponent";
|
||||
import { EventTypeAppCard } from "./EventTypeAppCardInterface";
|
||||
|
||||
vi.mock("./DynamicComponent", async () => {
|
||||
const actual = (await vi.importActual("./DynamicComponent")) as object;
|
||||
return {
|
||||
...actual,
|
||||
DynamicComponent: vi.fn(() => <div>MockedDynamicComponent</div>),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const getAppDataMock = vi.fn();
|
||||
const setAppDataMock = vi.fn();
|
||||
const mockProps = {
|
||||
app: {
|
||||
name: "TestApp",
|
||||
slug: "testapp",
|
||||
credentialOwner: {},
|
||||
} as RouterOutputs["viewer"]["integrations"]["items"][number] & { credentialOwner?: CredentialOwner },
|
||||
eventType: {},
|
||||
getAppData: getAppDataMock,
|
||||
setAppData: setAppDataMock,
|
||||
LockedIcon: <div>MockedIcon</div>,
|
||||
disabled: false,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any;
|
||||
|
||||
describe("Tests for EventTypeAppCard component", () => {
|
||||
test("Should render DynamicComponent with correct slug", () => {
|
||||
render(<EventTypeAppCard {...mockProps} />);
|
||||
|
||||
expect(DynamicComponent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
slug: mockProps.app.slug,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
expect(screen.getByText("MockedDynamicComponent")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Should invoke getAppData and setAppData from context on render", () => {
|
||||
render(
|
||||
<EventTypeAppCard
|
||||
{...mockProps}
|
||||
value={{
|
||||
getAppData: getAppDataMock(),
|
||||
setAppData: setAppDataMock(),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getAppDataMock).toHaveBeenCalled();
|
||||
expect(setAppDataMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Should render DynamicComponent with 'stripepayment' slug for stripe app", () => {
|
||||
const stripeProps = {
|
||||
...mockProps,
|
||||
app: {
|
||||
...mockProps.app,
|
||||
slug: "stripe",
|
||||
},
|
||||
};
|
||||
|
||||
render(<EventTypeAppCard {...stripeProps} />);
|
||||
|
||||
expect(DynamicComponent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
slug: "stripepayment",
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
expect(screen.getByText("MockedDynamicComponent")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Should display error boundary message on child component error", () => {
|
||||
(DynamicComponent as jest.Mock).mockImplementation(() => {
|
||||
return Error("Mocked error from DynamicComponent");
|
||||
});
|
||||
|
||||
render(<EventTypeAppCard {...mockProps} />);
|
||||
const errorMessage = screen.getByText(`There is some problem with ${mockProps.app.name} App`);
|
||||
expect(errorMessage).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -122,9 +122,9 @@ function AlbySetupPage(props: IAlbySetupProps) {
|
|||
|
||||
const albyIcon = (
|
||||
<>
|
||||
<img className="h-16 w-16 dark:hidden" src="/api/app-store/alby/icon-borderless.svg" alt="Alby Icon" />
|
||||
<img className="h-12 w-12 dark:hidden" src="/api/app-store/alby/icon-borderless.svg" alt="Alby Icon" />
|
||||
<img
|
||||
className="hidden h-16 w-16 dark:block"
|
||||
className="hidden h-12 w-12 dark:block"
|
||||
src="/api/app-store/alby/icon-borderless-dark.svg"
|
||||
alt="Alby Icon"
|
||||
/>
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
|
||||
import { apiKeysRouter } from "@calcom/trpc/server/routers/viewer/apiKeys/_router";
|
||||
|
||||
import checkSession from "../../_utils/auth";
|
||||
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
|
||||
|
@ -15,9 +15,9 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||
const appType = appConfig.type;
|
||||
|
||||
const ctx = await createContext({ req, res });
|
||||
const caller = viewerRouter.createCaller(ctx);
|
||||
const caller = apiKeysRouter.createCaller(ctx);
|
||||
|
||||
const apiKey = await caller.apiKeys.create({
|
||||
const apiKey = await caller.create({
|
||||
note: "Cal.ai",
|
||||
expiresAt: null,
|
||||
appId: "cal-ai",
|
||||
|
|
|
@ -284,9 +284,10 @@ export default class GoogleCalendarService implements Calendar {
|
|||
|
||||
const calendar = await this.authedCalendar();
|
||||
|
||||
const selectedCalendar = externalCalendarId
|
||||
? externalCalendarId
|
||||
: event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId;
|
||||
const selectedCalendar =
|
||||
(externalCalendarId
|
||||
? event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId
|
||||
: undefined) || "primary";
|
||||
|
||||
try {
|
||||
const evt = await calendar.events.update({
|
||||
|
@ -337,14 +338,15 @@ export default class GoogleCalendarService implements Calendar {
|
|||
|
||||
async deleteEvent(uid: string, event: CalendarEvent, externalCalendarId?: string | null): Promise<void> {
|
||||
const calendar = await this.authedCalendar();
|
||||
const defaultCalendarId = "primary";
|
||||
const calendarId = externalCalendarId
|
||||
? externalCalendarId
|
||||
: event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId;
|
||||
|
||||
const selectedCalendar =
|
||||
(externalCalendarId
|
||||
? event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId
|
||||
: undefined) || "primary";
|
||||
|
||||
try {
|
||||
const event = await calendar.events.delete({
|
||||
calendarId: calendarId ? calendarId : defaultCalendarId,
|
||||
calendarId: selectedCalendar,
|
||||
eventId: uid,
|
||||
sendNotifications: false,
|
||||
sendUpdates: "none",
|
||||
|
|
|
@ -91,7 +91,7 @@ export const defaultLocations: DefaultEventLocationType[] = [
|
|||
attendeeInputType: "attendeeAddress",
|
||||
attendeeInputPlaceholder: "enter_address",
|
||||
defaultValueVariable: "attendeeAddress",
|
||||
iconUrl: "/map-pin.svg",
|
||||
iconUrl: "/map-pin-dark.svg",
|
||||
category: "in person",
|
||||
},
|
||||
{
|
||||
|
@ -103,7 +103,7 @@ export const defaultLocations: DefaultEventLocationType[] = [
|
|||
// HACK:
|
||||
variable: "locationAddress",
|
||||
defaultValueVariable: "address",
|
||||
iconUrl: "/map-pin.svg",
|
||||
iconUrl: "/map-pin-dark.svg",
|
||||
category: "in person",
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import matchers from "@testing-library/jest-dom/matchers";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
import { afterEach, expect, vi } from "vitest";
|
||||
|
||||
vi.mock("@calcom/lib/OgImages", async () => {
|
||||
return {};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/lib/hooks/useLocale", () => ({
|
||||
useLocale: () => {
|
||||
return {
|
||||
t: (str: string) => str,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
||||
observe: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
}));
|
||||
|
||||
expect.extend(matchers);
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
|
@ -44,6 +44,7 @@ const BookerComponent = ({
|
|||
isTeamEvent,
|
||||
entity,
|
||||
duration,
|
||||
hashedLink,
|
||||
}: BookerProps) => {
|
||||
/**
|
||||
* Prioritize dateSchedule load
|
||||
|
@ -115,6 +116,7 @@ const BookerComponent = ({
|
|||
columnViewExtraDays.current =
|
||||
Math.abs(dayjs(selectedDate).diff(availableSlots[availableSlots.length - 2], "day")) + addonDays;
|
||||
const prefetchNextMonth =
|
||||
layout === BookerLayouts.COLUMN_VIEW &&
|
||||
dayjs(date).month() !== dayjs(date).add(columnViewExtraDays.current, "day").month();
|
||||
const monthCount =
|
||||
dayjs(date).add(1, "month").month() !== dayjs(date).add(columnViewExtraDays.current, "day").month()
|
||||
|
@ -284,6 +286,7 @@ const BookerComponent = ({
|
|||
setSeatedEventData({ ...seatedEventData, bookingUid: undefined, attendees: undefined });
|
||||
}
|
||||
}}
|
||||
hashedLink={hashedLink}
|
||||
/>
|
||||
</BookerSection>
|
||||
|
||||
|
|
|
@ -41,11 +41,12 @@ import { FormSkeleton } from "./Skeleton";
|
|||
|
||||
type BookEventFormProps = {
|
||||
onCancel?: () => void;
|
||||
hashedLink?: string | null;
|
||||
};
|
||||
|
||||
type DefaultValues = Record<string, unknown>;
|
||||
|
||||
export const BookEventForm = ({ onCancel }: BookEventFormProps) => {
|
||||
export const BookEventForm = ({ onCancel, hashedLink }: BookEventFormProps) => {
|
||||
const [slotReservationId, setSlotReservationId] = useSlotReservationId();
|
||||
const reserveSlotMutation = trpc.viewer.public.slots.reserveSlot.useMutation({
|
||||
trpc: {
|
||||
|
@ -114,6 +115,7 @@ export const BookEventForm = ({ onCancel }: BookEventFormProps) => {
|
|||
isRescheduling={isRescheduling}
|
||||
eventQuery={eventQuery}
|
||||
rescheduleUid={rescheduleUid}
|
||||
hashedLink={hashedLink}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -124,11 +126,13 @@ export const BookEventFormChild = ({
|
|||
isRescheduling,
|
||||
eventQuery,
|
||||
rescheduleUid,
|
||||
hashedLink,
|
||||
}: BookEventFormProps & {
|
||||
initialValues: DefaultValues;
|
||||
isRescheduling: boolean;
|
||||
eventQuery: ReturnType<typeof useEvent>;
|
||||
rescheduleUid: string | null;
|
||||
hashedLink?: string | null;
|
||||
}) => {
|
||||
const eventType = eventQuery.data;
|
||||
const bookingFormSchema = z
|
||||
|
@ -332,6 +336,7 @@ export const BookEventFormChild = ({
|
|||
}),
|
||||
{}
|
||||
),
|
||||
hashedLink,
|
||||
};
|
||||
|
||||
if (eventQuery.data?.recurringEvent?.freq && recurringEventCount) {
|
||||
|
@ -370,6 +375,7 @@ export const BookEventFormChild = ({
|
|||
fields={eventType.bookingFields}
|
||||
locations={eventType.locations}
|
||||
rescheduleUid={rescheduleUid || undefined}
|
||||
bookingData={bookingData}
|
||||
/>
|
||||
{(createBookingMutation.isError ||
|
||||
createRecurringBookingMutation.isError ||
|
||||
|
@ -399,8 +405,8 @@ export const BookEventFormChild = ({
|
|||
type="submit"
|
||||
color="primary"
|
||||
loading={createBookingMutation.isLoading || createRecurringBookingMutation.isLoading}
|
||||
data-testid={rescheduleUid ? "confirm-reschedule-button" : "confirm-book-button"}>
|
||||
{rescheduleUid
|
||||
data-testid={rescheduleUid && bookingData ? "confirm-reschedule-button" : "confirm-book-button"}>
|
||||
{rescheduleUid && bookingData
|
||||
? t("reschedule")
|
||||
: renderConfirmNotVerifyEmailButtonCond
|
||||
? t("confirm")
|
||||
|
@ -492,12 +498,18 @@ function useInitialFormValues({
|
|||
});
|
||||
|
||||
const defaultUserValues = {
|
||||
email: rescheduleUid
|
||||
? bookingData?.attendees[0].email
|
||||
: parsedQuery["email"] || session.data?.user?.email || "",
|
||||
name: rescheduleUid
|
||||
? bookingData?.attendees[0].name
|
||||
: parsedQuery["name"] || session.data?.user?.name || "",
|
||||
email:
|
||||
rescheduleUid && bookingData && bookingData.attendees.length > 0
|
||||
? bookingData?.attendees[0].email
|
||||
: !!parsedQuery["email"]
|
||||
? parsedQuery["email"]
|
||||
: session.data?.user?.email ?? "",
|
||||
name:
|
||||
rescheduleUid && bookingData && bookingData.attendees.length > 0
|
||||
? bookingData?.attendees[0].name
|
||||
: !!parsedQuery["name"]
|
||||
? parsedQuery["name"]
|
||||
: session.data?.user?.name ?? session.data?.user?.username ?? "",
|
||||
};
|
||||
|
||||
if (!isRescheduling) {
|
||||
|
@ -521,14 +533,12 @@ function useInitialFormValues({
|
|||
setDefaultValues(defaults);
|
||||
}
|
||||
|
||||
if ((!rescheduleUid && !bookingData) || !bookingData?.attendees.length) {
|
||||
return {};
|
||||
}
|
||||
const primaryAttendee = bookingData.attendees[0];
|
||||
if (!primaryAttendee) {
|
||||
if (!rescheduleUid && !bookingData) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// We should allow current session user as default values for booking form
|
||||
|
||||
const defaults = {
|
||||
responses: {} as Partial<z.infer<ReturnType<typeof getBookingResponsesSchema>>>,
|
||||
};
|
||||
|
@ -536,7 +546,7 @@ function useInitialFormValues({
|
|||
const responses = eventType.bookingFields.reduce((responses, field) => {
|
||||
return {
|
||||
...responses,
|
||||
[field.name]: bookingData.responses[field.name],
|
||||
[field.name]: bookingData?.responses[field.name],
|
||||
};
|
||||
}, {});
|
||||
defaults.responses = {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import type { LocationObject } from "@calcom/app-store/locations";
|
||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||
import getLocationOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
|
||||
import { FormBuilderField } from "@calcom/features/form-builder/FormBuilderField";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
@ -13,10 +14,12 @@ export const BookingFields = ({
|
|||
locations,
|
||||
rescheduleUid,
|
||||
isDynamicGroupBooking,
|
||||
bookingData,
|
||||
}: {
|
||||
fields: NonNullable<RouterOutputs["viewer"]["public"]["event"]>["bookingFields"];
|
||||
locations: LocationObject[];
|
||||
rescheduleUid?: string;
|
||||
bookingData?: GetBookingType | null;
|
||||
isDynamicGroupBooking: boolean;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
|
@ -32,7 +35,9 @@ export const BookingFields = ({
|
|||
// During reschedule by default all system fields are readOnly. Make them editable on case by case basis.
|
||||
// Allowing a system field to be edited might require sending emails to attendees, so we need to be careful
|
||||
let readOnly =
|
||||
(field.editable === "system" || field.editable === "system-but-optional") && !!rescheduleUid;
|
||||
(field.editable === "system" || field.editable === "system-but-optional") &&
|
||||
!!rescheduleUid &&
|
||||
bookingData !== null;
|
||||
|
||||
let hidden = !!field.hidden;
|
||||
const fieldViews = field.views;
|
||||
|
@ -42,6 +47,9 @@ export const BookingFields = ({
|
|||
}
|
||||
|
||||
if (field.name === SystemField.Enum.rescheduleReason) {
|
||||
if (bookingData === null) {
|
||||
return null;
|
||||
}
|
||||
// rescheduleReason is a reschedule specific field and thus should be editable during reschedule
|
||||
readOnly = false;
|
||||
}
|
||||
|
@ -64,8 +72,8 @@ export const BookingFields = ({
|
|||
hidden = isDynamicGroupBooking ? true : !!field.hidden;
|
||||
}
|
||||
|
||||
// We don't show `notes` field during reschedule
|
||||
if (field.name === SystemField.Enum.notes && !!rescheduleUid) {
|
||||
// We don't show `notes` field during reschedule but since it's a query param we better valid if rescheduleUid brought any bookingData
|
||||
if (field.name === SystemField.Enum.notes && bookingData !== null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -64,6 +64,10 @@ export interface BookerProps {
|
|||
* otherwise, the default value is selected
|
||||
*/
|
||||
duration?: number | null;
|
||||
/**
|
||||
* Refers to the private link from event types page.
|
||||
*/
|
||||
hashedLink?: string | null;
|
||||
}
|
||||
|
||||
export type BookerState = "loading" | "selecting_date" | "selecting_time" | "booking";
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
|
@ -14,10 +15,20 @@ interface BookerSeoProps {
|
|||
teamSlug?: string | null;
|
||||
name?: string | null;
|
||||
};
|
||||
bookingData?: GetBookingType | null;
|
||||
}
|
||||
|
||||
export const BookerSeo = (props: BookerSeoProps) => {
|
||||
const { eventSlug, username, rescheduleUid, hideBranding, isTeamEvent, entity, isSEOIndexable } = props;
|
||||
const {
|
||||
eventSlug,
|
||||
username,
|
||||
rescheduleUid,
|
||||
hideBranding,
|
||||
isTeamEvent,
|
||||
entity,
|
||||
isSEOIndexable,
|
||||
bookingData,
|
||||
} = props;
|
||||
const { t } = useLocale();
|
||||
const { data: event } = trpc.viewer.public.event.useQuery(
|
||||
{ username, eventSlug, isTeamEvent, org: entity.orgSlug ?? null },
|
||||
|
@ -29,7 +40,7 @@ export const BookerSeo = (props: BookerSeoProps) => {
|
|||
const title = event?.title ?? "";
|
||||
return (
|
||||
<HeadSeo
|
||||
title={`${rescheduleUid ? t("reschedule") : ""} ${title} | ${profileName}`}
|
||||
title={`${rescheduleUid && !!bookingData ? t("reschedule") : ""} ${title} | ${profileName}`}
|
||||
description={`${rescheduleUid ? t("reschedule") : ""} ${title}`}
|
||||
meeting={{
|
||||
title: title,
|
||||
|
|
|
@ -96,7 +96,7 @@ export function AvailableEventLocations({ locations }: { locations: LocationObje
|
|||
return filteredLocations.length > 1 ? (
|
||||
<div className="flex flex-row items-center text-sm font-medium">
|
||||
<img
|
||||
src="/map-pin.svg"
|
||||
src="/map-pin-dark.svg"
|
||||
className={classNames("me-[10px] h-4 w-4 opacity-70 dark:invert")}
|
||||
alt="map-pin"
|
||||
/>
|
||||
|
|
|
@ -61,7 +61,7 @@ export const EventMembers = ({ schedulingType, users, profile, entity }: EventMe
|
|||
image: "logo" in profile && profile.logo ? `${profile.logo}` : undefined,
|
||||
alt: profile.name || undefined,
|
||||
href: profile.username
|
||||
? `${CAL_URL}` + (pathname.indexOf("/team/") !== -1 ? "/team" : "") + `/${profile.username}`
|
||||
? `${CAL_URL}${pathname.indexOf("/team/") !== -1 ? "/team" : ""}/${profile.username}`
|
||||
: undefined,
|
||||
});
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ type BookingOptions = {
|
|||
metadata?: Record<string, string>;
|
||||
bookingUid?: string;
|
||||
seatReferenceUid?: string;
|
||||
hashedLink?: string | null;
|
||||
};
|
||||
|
||||
export const mapBookingToMutationInput = ({
|
||||
|
@ -32,6 +33,7 @@ export const mapBookingToMutationInput = ({
|
|||
metadata,
|
||||
bookingUid,
|
||||
seatReferenceUid,
|
||||
hashedLink,
|
||||
}: BookingOptions): BookingCreateBody => {
|
||||
return {
|
||||
...values,
|
||||
|
@ -47,11 +49,10 @@ export const mapBookingToMutationInput = ({
|
|||
language: language,
|
||||
rescheduleUid,
|
||||
metadata: metadata || {},
|
||||
hasHashedBookingLink: false,
|
||||
hasHashedBookingLink: hashedLink ? true : false,
|
||||
bookingUid,
|
||||
seatReferenceUid,
|
||||
// hasHashedBookingLink,
|
||||
// hashedLink,
|
||||
hashedLink,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ export const getBookingWithResponses = <
|
|||
|
||||
export default getBooking;
|
||||
|
||||
export const getBookingForReschedule = async (uid: string) => {
|
||||
export const getBookingForReschedule = async (uid: string, userId?: number) => {
|
||||
let rescheduleUid: string | null = null;
|
||||
const theBooking = await prisma.booking.findFirst({
|
||||
where: {
|
||||
|
@ -117,8 +117,25 @@ export const getBookingForReschedule = async (uid: string) => {
|
|||
},
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
eventType: {
|
||||
select: {
|
||||
seatsPerTimeSlot: true,
|
||||
hosts: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
owner: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let bookingSeatReferenceUid: number | null = null;
|
||||
|
||||
// If no booking is found via the uid, it's probably a booking seat
|
||||
// that its being rescheduled, which we query next.
|
||||
|
@ -144,11 +161,26 @@ export const getBookingForReschedule = async (uid: string) => {
|
|||
},
|
||||
});
|
||||
if (bookingSeat) {
|
||||
bookingSeatReferenceUid = bookingSeat.id;
|
||||
rescheduleUid = bookingSeat.booking.uid;
|
||||
attendeeEmail = bookingSeat.attendee.email;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have the booking and not bookingSeat, we need to make sure the booking belongs to the userLoggedIn
|
||||
// Otherwise, we return null here.
|
||||
let hasOwnershipOnBooking = false;
|
||||
if (theBooking && theBooking?.eventType?.seatsPerTimeSlot && bookingSeatReferenceUid === null) {
|
||||
const isOwnerOfBooking = theBooking.userId === userId;
|
||||
|
||||
const isHostOfEventType = theBooking?.eventType?.hosts.some((host) => host.userId === userId);
|
||||
|
||||
const isUserIdInBooking = theBooking.userId === userId;
|
||||
|
||||
if (!isOwnerOfBooking && !isHostOfEventType && !isUserIdInBooking) return null;
|
||||
hasOwnershipOnBooking = true;
|
||||
}
|
||||
|
||||
// If we don't have a booking and no rescheduleUid, the ID is invalid,
|
||||
// and we return null here.
|
||||
if (!theBooking && !rescheduleUid) return null;
|
||||
|
@ -161,6 +193,8 @@ export const getBookingForReschedule = async (uid: string) => {
|
|||
...booking,
|
||||
attendees: rescheduleUid
|
||||
? booking.attendees.filter((attendee) => attendee.email === attendeeEmail)
|
||||
: hasOwnershipOnBooking
|
||||
? []
|
||||
: booking.attendees,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -136,6 +136,19 @@ async function handler(req: CustomRequest) {
|
|||
throw new HttpError({ statusCode: 400, message: "User not found" });
|
||||
}
|
||||
|
||||
// If the booking is a seated event and there is no seatReferenceUid we should validate that logged in user is host
|
||||
if (bookingToDelete.eventType?.seatsPerTimeSlot && !seatReferenceUid) {
|
||||
const userIsHost = bookingToDelete.eventType.hosts.find((host) => {
|
||||
if (host.user.id === userId) return true;
|
||||
});
|
||||
|
||||
const userIsOwnerOfEventType = bookingToDelete.eventType.owner?.id === userId;
|
||||
|
||||
if (!userIsHost && !userIsOwnerOfEventType) {
|
||||
throw new HttpError({ statusCode: 401, message: "User not a host of this event" });
|
||||
}
|
||||
}
|
||||
|
||||
// get webhooks
|
||||
const eventTrigger: WebhookTriggerEvents = "BOOKING_CANCELLED";
|
||||
|
||||
|
|
|
@ -996,7 +996,7 @@ async function handler(
|
|||
if (isTeamEventType && locationBodyString === OrganizerDefaultConferencingAppType) {
|
||||
const metadataParseResult = userMetadataSchema.safeParse(organizerUser.metadata);
|
||||
const organizerMetadata = metadataParseResult.success ? metadataParseResult.data : undefined;
|
||||
if (organizerMetadata) {
|
||||
if (organizerMetadata?.defaultConferencingApp?.appSlug) {
|
||||
const app = getAppFromSlug(organizerMetadata?.defaultConferencingApp?.appSlug);
|
||||
locationBodyString = app?.appData?.location?.type || locationBodyString;
|
||||
organizerOrFirstDynamicGroupMemberDefaultLocationUrl =
|
||||
|
@ -2148,6 +2148,7 @@ async function handler(
|
|||
id: originalRescheduledBooking.id,
|
||||
},
|
||||
data: {
|
||||
rescheduled: true,
|
||||
status: BookingStatus.CANCELLED,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -176,30 +176,37 @@ const ProfileView = () => {
|
|||
mutation.mutate({ id: team.id, ...variables });
|
||||
}
|
||||
}}>
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="logo"
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<Avatar alt="" imageSrc={getPlaceholderAvatar(value, team?.name as string)} size="lg" />
|
||||
<div className="ms-4">
|
||||
<ImageUploader
|
||||
target="avatar"
|
||||
id="avatar-upload"
|
||||
buttonMsg={t("update")}
|
||||
handleAvatarChange={(newLogo) => {
|
||||
form.setValue("logo", newLogo);
|
||||
}}
|
||||
imageSrc={value}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr className="border-subtle my-8" />
|
||||
{!team.parent && (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="logo"
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<Avatar
|
||||
alt=""
|
||||
imageSrc={getPlaceholderAvatar(value, team?.name as string)}
|
||||
size="lg"
|
||||
/>
|
||||
<div className="ms-4">
|
||||
<ImageUploader
|
||||
target="avatar"
|
||||
id="avatar-upload"
|
||||
buttonMsg={t("update")}
|
||||
handleAvatarChange={(newLogo) => {
|
||||
form.setValue("logo", newLogo);
|
||||
}}
|
||||
imageSrc={value}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<hr className="border-subtle my-8" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
control={form.control}
|
||||
|
|
|
@ -114,17 +114,19 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
|||
selectedFilter,
|
||||
isAll,
|
||||
dateRange,
|
||||
initialConfig,
|
||||
} = newConfigFilters;
|
||||
const [startTime, endTime] = dateRange || [null, null];
|
||||
const newSearchParams = new URLSearchParams(searchParams);
|
||||
const newSearchParams = new URLSearchParams(searchParams.toString());
|
||||
function setParamsIfDefined(key: string, value: string | number | boolean | null | undefined) {
|
||||
if (value !== undefined && value !== null) newSearchParams.set(key, value.toString());
|
||||
}
|
||||
|
||||
setParamsIfDefined("memberUserId", selectedMemberUserId);
|
||||
setParamsIfDefined("teamId", selectedTeamId);
|
||||
setParamsIfDefined("userId", selectedUserId);
|
||||
setParamsIfDefined("teamId", selectedTeamId || initialConfig?.teamId);
|
||||
setParamsIfDefined("userId", selectedUserId || initialConfig?.userId);
|
||||
setParamsIfDefined("eventTypeId", selectedEventTypeId);
|
||||
setParamsIfDefined("isAll", isAll);
|
||||
setParamsIfDefined("isAll", isAll || initialConfig?.isAll);
|
||||
setParamsIfDefined("startTime", startTime?.toISOString());
|
||||
setParamsIfDefined("endTime", endTime?.toISOString());
|
||||
setParamsIfDefined("filter", selectedFilter?.[0]);
|
||||
|
|
|
@ -22,6 +22,11 @@ export const TeamAndSelfList = () => {
|
|||
const { data, isSuccess } = trpc.viewer.insights.teamListForUser.useQuery(undefined, {
|
||||
// Teams don't change that frequently
|
||||
refetchOnWindowFocus: false,
|
||||
trpc: {
|
||||
context: {
|
||||
skipBatch: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -48,6 +53,7 @@ export const TeamAndSelfList = () => {
|
|||
} else if (session.data?.user.id) {
|
||||
// default to user
|
||||
setConfigFilters({
|
||||
selectedUserId: session.data?.user.id,
|
||||
initialConfig: {
|
||||
teamId: null,
|
||||
userId: session.data?.user.id,
|
||||
|
|
|
@ -3,7 +3,6 @@ import { useForm } from "react-hook-form";
|
|||
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { yyyymmdd } from "@calcom/lib/date-fns";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
|
@ -15,6 +14,7 @@ import {
|
|||
DialogHeader,
|
||||
DialogClose,
|
||||
Switch,
|
||||
showToast,
|
||||
Form,
|
||||
Button,
|
||||
} from "@calcom/ui";
|
||||
|
@ -23,15 +23,11 @@ import DatePicker from "../../calendars/DatePicker";
|
|||
import type { TimeRange } from "./Schedule";
|
||||
import { DayRanges } from "./Schedule";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const noop = () => {};
|
||||
|
||||
const DateOverrideForm = ({
|
||||
value,
|
||||
workingHours,
|
||||
excludedDates,
|
||||
onChange,
|
||||
onClose = noop,
|
||||
}: {
|
||||
workingHours?: WorkingHours[];
|
||||
onChange: (newValue: TimeRange[]) => void;
|
||||
|
@ -137,14 +133,10 @@ const DateOverrideForm = ({
|
|||
})
|
||||
: datesInRanges
|
||||
);
|
||||
onClose();
|
||||
setSelectedDates([]);
|
||||
}}
|
||||
className="p-6 sm:flex sm:p-0 md:flex-col lg:flex-col xl:flex-row">
|
||||
<div
|
||||
className={classNames(
|
||||
selectedDates[0] && "sm:border-subtle w-full sm:border-r sm:pr-6",
|
||||
"sm:p-4 md:p-8"
|
||||
)}>
|
||||
<div className="sm:border-subtle w-full sm:border-r sm:p-4 sm:pr-6 md:p-8">
|
||||
<DialogHeader title={t("date_overrides_dialog_title")} />
|
||||
<DatePicker
|
||||
excludedDates={excludedDates}
|
||||
|
@ -160,39 +152,48 @@ const DateOverrideForm = ({
|
|||
locale={isLocaleReady ? i18n.language : "en"}
|
||||
/>
|
||||
</div>
|
||||
{selectedDates[0] && (
|
||||
<div className="relative mt-8 flex w-full flex-col sm:mt-0 sm:p-4 md:p-8">
|
||||
<div className="mb-4 flex-grow space-y-4">
|
||||
<p className="text-medium text-emphasis text-sm">{t("date_overrides_dialog_which_hours")}</p>
|
||||
<div>
|
||||
{datesUnavailable ? (
|
||||
<p className="text-subtle border-default rounded border p-2 text-sm">
|
||||
{t("date_overrides_unavailable")}
|
||||
</p>
|
||||
) : (
|
||||
<DayRanges name="range" />
|
||||
)}
|
||||
<div className="relative mt-8 flex w-full flex-col sm:mt-0 sm:p-4 md:p-8">
|
||||
{selectedDates[0] ? (
|
||||
<>
|
||||
<div className="mb-4 flex-grow space-y-4">
|
||||
<p className="text-medium text-emphasis text-sm">{t("date_overrides_dialog_which_hours")}</p>
|
||||
<div>
|
||||
{datesUnavailable ? (
|
||||
<p className="text-subtle border-default rounded border p-2 text-sm">
|
||||
{t("date_overrides_unavailable")}
|
||||
</p>
|
||||
) : (
|
||||
<DayRanges name="range" />
|
||||
)}
|
||||
</div>
|
||||
<Switch
|
||||
label={t("date_overrides_mark_all_day_unavailable_one")}
|
||||
checked={datesUnavailable}
|
||||
onCheckedChange={setDatesUnavailable}
|
||||
data-testid="date-override-mark-unavailable"
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
label={t("date_overrides_mark_all_day_unavailable_one")}
|
||||
checked={datesUnavailable}
|
||||
onCheckedChange={setDatesUnavailable}
|
||||
data-testid="date-override-mark-unavailable"
|
||||
/>
|
||||
<div className="mt-4 flex flex-row-reverse sm:mt-0">
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
showToast(t("date_successfully_added"), "success");
|
||||
}}
|
||||
disabled={selectedDates.length === 0}
|
||||
data-testid="add-override-submit-btn">
|
||||
{value ? t("date_overrides_update_btn") : t("date_overrides_add_btn")}
|
||||
</Button>
|
||||
<DialogClose />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="bottom-7 right-8 flex flex-row-reverse sm:absolute">
|
||||
<DialogClose />
|
||||
</div>
|
||||
<div className="mt-4 flex flex-row-reverse sm:mt-0">
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={selectedDates.length === 0}
|
||||
data-testid="add-override-submit-btn">
|
||||
{value ? t("date_overrides_update_btn") : t("date_overrides_add_btn")}
|
||||
</Button>
|
||||
<DialogClose onClick={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
@ -220,7 +221,7 @@ const DateOverrideInputDialog = ({
|
|||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{Trigger}</DialogTrigger>
|
||||
|
||||
<DialogContent enableOverflow={enableOverflow} size="md" className="p-0 md:w-auto">
|
||||
<DialogContent enableOverflow={enableOverflow} size="md" className="p-0">
|
||||
<DateOverrideForm
|
||||
excludedDates={excludedDates}
|
||||
{...passThroughProps}
|
||||
|
|
|
@ -375,11 +375,13 @@ const SettingsSidebarContainer = ({
|
|||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
<img
|
||||
src={getPlaceholderAvatar(team.logo, team?.name as string)}
|
||||
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0"
|
||||
alt={team.name || "Team logo"}
|
||||
/>
|
||||
{!team.parentId && (
|
||||
<img
|
||||
src={getPlaceholderAvatar(team.logo, team?.name as string)}
|
||||
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0"
|
||||
alt={team.name || "Team logo"}
|
||||
/>
|
||||
)}
|
||||
<p className="w-1/2 truncate">{team.name}</p>
|
||||
{!team.accepted && (
|
||||
<Badge className="ms-3" variant="orange">
|
||||
|
@ -517,11 +519,13 @@ const SettingsSidebarContainer = ({
|
|||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
<img
|
||||
src={getPlaceholderAvatar(otherTeam.logo, otherTeam?.name as string)}
|
||||
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0"
|
||||
alt={otherTeam.name || "Team logo"}
|
||||
/>
|
||||
{!otherTeam.parentId && (
|
||||
<img
|
||||
src={getPlaceholderAvatar(otherTeam.logo, otherTeam?.name as string)}
|
||||
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0"
|
||||
alt={otherTeam.name || "Team logo"}
|
||||
/>
|
||||
)}
|
||||
<p className="w-1/2 truncate">{otherTeam.name}</p>
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
|
|
|
@ -1008,7 +1008,7 @@ function MainContainer({
|
|||
<main className="bg-default relative z-0 flex-1 focus:outline-none">
|
||||
{/* show top navigation for md and smaller (tablet and phones) */}
|
||||
{TopNavContainerProp}
|
||||
<div className="max-w-full px-2 py-4 md:py-12 lg:px-6">
|
||||
<div className="max-w-full px-2 py-4 lg:px-6">
|
||||
<ErrorBoundary>
|
||||
{!props.withoutMain ? <ShellMain {...props}>{props.children}</ShellMain> : props.children}
|
||||
</ErrorBoundary>
|
||||
|
|
|
@ -7,7 +7,7 @@ export const useUrlMatchesCurrentUrl = (url: string) => {
|
|||
// It can certainly have null value https://nextjs.org/docs/app/api-reference/functions/use-pathname#:~:text=usePathname%20can%20return%20null%20when%20a%20fallback%20route%20is%20being%20rendered%20or%20when%20a%20pages%20directory%20page%20has%20been%20automatically%20statically%20optimized%20by%20Next.js%20and%20the%20router%20is%20not%20ready.
|
||||
const pathname = usePathname() as null | string;
|
||||
const searchParams = useSearchParams();
|
||||
const query = searchParams.toString();
|
||||
const query = searchParams?.toString();
|
||||
let pathnameWithQuery;
|
||||
if (query) {
|
||||
pathnameWithQuery = `${pathname}?${query}`;
|
||||
|
|
|
@ -15,6 +15,6 @@ export async function maybeGetBookingUidFromSeat(prisma: PrismaClient, uid: stri
|
|||
},
|
||||
},
|
||||
});
|
||||
if (bookingSeat) return bookingSeat.booking.uid;
|
||||
return uid;
|
||||
if (bookingSeat) return { uid: bookingSeat.booking.uid, seatReferenceUid: uid };
|
||||
return { uid };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
-- View: public.BookingsTimeStatus
|
||||
|
||||
-- DROP VIEW public."BookingsTimeStatus";
|
||||
|
||||
CREATE OR REPLACE VIEW public."BookingTimeStatus"
|
||||
AS
|
||||
SELECT "Booking".id,
|
||||
"Booking".uid,
|
||||
"Booking"."eventTypeId",
|
||||
"Booking".title,
|
||||
"Booking".description,
|
||||
"Booking"."startTime",
|
||||
"Booking"."endTime",
|
||||
"Booking"."createdAt",
|
||||
"Booking".location,
|
||||
"Booking".paid,
|
||||
"Booking".status,
|
||||
"Booking".rescheduled,
|
||||
"Booking"."userId",
|
||||
"et"."teamId",
|
||||
"et"."length" as "eventLength",
|
||||
CASE
|
||||
WHEN "Booking".rescheduled IS TRUE THEN 'rescheduled'::text
|
||||
WHEN "Booking".status = 'cancelled'::"BookingStatus" AND "Booking".rescheduled IS NULL THEN 'cancelled'::text
|
||||
WHEN "Booking"."endTime" < now() THEN 'completed'::text
|
||||
WHEN "Booking"."endTime" > now() THEN 'uncompleted'::text
|
||||
ELSE NULL::text
|
||||
END AS "timeStatus",
|
||||
"et"."parentId" as "eventParentId"
|
||||
FROM "Booking"
|
||||
LEFT JOIN "EventType" et ON "Booking"."eventTypeId" = et.id
|
||||
LEFT JOIN "Membership" mb ON "mb"."userId" = "Booking"."userId";
|
||||
|
||||
|
||||
|
|
@ -36,10 +36,10 @@ export function DataTableToolbar<TData>({
|
|||
const isFiltered = table.getState().columnFilters.length > 0;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<div className="bg-default sticky top-[3rem] z-10 flex items-center justify-end space-x-2 py-4 md:top-0">
|
||||
{searchKey && (
|
||||
<Input
|
||||
className="max-w-64 mb-0 mr-auto"
|
||||
className="max-w-64 mb-0 mr-auto rounded-md"
|
||||
placeholder="Search"
|
||||
value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ""}
|
||||
onChange={(event) => table.getColumn(searchKey)?.setFilterValue(event.target.value)}
|
||||
|
|
|
@ -102,21 +102,9 @@ export function DataTable<TData, TValue>({
|
|||
searchKey={searchKey}
|
||||
tableCTA={tableCTA}
|
||||
/>
|
||||
<div
|
||||
className="border-subtle rounded-md border"
|
||||
ref={tableContainerRef}
|
||||
onScroll={onScroll}
|
||||
style={{
|
||||
height: "calc(100vh - 30vh)",
|
||||
overflow: "auto",
|
||||
}}>
|
||||
<div className="border-subtle border" ref={tableContainerRef} onScroll={onScroll}>
|
||||
<Table>
|
||||
<TableHeader
|
||||
style={{
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
}}>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
|
|
|
@ -187,7 +187,7 @@ export function DialogClose(
|
|||
return (
|
||||
<DialogPrimitive.Close asChild {...props.dialogCloseProps}>
|
||||
{/* This will require the i18n string passed in */}
|
||||
<Button color={props.color || "minimal"} {...props}>
|
||||
<Button data-testid="dialog-rejection" color={props.color || "minimal"} {...props}>
|
||||
{props.children ? props.children : t("Close")}
|
||||
</Button>
|
||||
</DialogPrimitive.Close>
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
|
||||
import { Canvas, Meta, Story } from "@storybook/addon-docs";
|
||||
|
||||
import {
|
||||
Examples,
|
||||
Example,
|
||||
Note,
|
||||
Title,
|
||||
CustomArgsTable,
|
||||
VariantRow,
|
||||
VariantsTable,
|
||||
} from "@calcom/storybook/components";
|
||||
|
||||
import { Select, UnstyledSelect } from "../select";
|
||||
import { InputFieldWithSelect } from "./Input";
|
||||
import { InputField } from "./Input";
|
||||
import { InputFieldWithSelect } from "./InputFieldWithSelect";
|
||||
import { InputField } from "./TextField";
|
||||
|
||||
<Meta title="UI/Form/Input Field" component={InputField} />
|
||||
|
||||
<Title title="Inputs" suffix="Brief" subtitle="Version 2.0 — Last Update: 22 Aug 2022" />
|
||||
<Title title="Inputs" suffix="Brief" subtitle="Version 2.0 — Last Update: 24 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
import { Canvas, Meta, Story } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import {
|
||||
Title,
|
||||
VariantRow,
|
||||
VariantsTable,
|
||||
CustomArgsTable,
|
||||
Examples,
|
||||
Example,
|
||||
} from "@calcom/storybook/components";
|
||||
import { Plus } from "@calcom/ui/components/icon";
|
||||
|
||||
import HorizontalTabs from "../HorizontalTabs";
|
||||
|
||||
<Meta title="UI/Navigation/HorizontalTabs" component={HorizontalTabs} />
|
||||
|
||||
<Title title="Horizontal Tabs" suffix="Brief" subtitle="Version 1.0 — Last Update: 17 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
The HorizontalTabs component is a user interface element used for displaying a horizontal set of tabs, often employed for navigation or organization purposes within a web application.
|
||||
|
||||
## Structure
|
||||
|
||||
The HorizontalTabs component is designed to work alongside the HorizontalTabItem component, which represents individual tabs within the tab bar.
|
||||
|
||||
export const tabs = [
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
icon: Plus,
|
||||
},
|
||||
{
|
||||
name: "Tab 2",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab2",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
avatar: "Avatar",
|
||||
},
|
||||
{
|
||||
name: "Tab 3",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab3",
|
||||
disabled: true,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
},
|
||||
];
|
||||
|
||||
<CustomArgsTable of={HorizontalTabs} />
|
||||
|
||||
<Examples title="Default">
|
||||
<Example title="Default">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="With avatar">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
avatar: "Avatar",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="With icon">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
icon: Plus,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="Disabled">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
disabled: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
</Examples>
|
||||
|
||||
## HorizontalTabs Story
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Horizontal Tabs"
|
||||
args={{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disabled: false,
|
||||
className: "",
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
icon: "",
|
||||
avatar: "",
|
||||
}}
|
||||
argTypes={{
|
||||
name: { control: "text", description: "Tab name" },
|
||||
href: { control: "text", description: "Tab link" },
|
||||
disabled: { control: "boolean", description: "Whether the tab is disabled" },
|
||||
className: { control: "text", description: "Additional CSS class" },
|
||||
linkShallow: { control: "boolean", description: "Whether to use shallow links" },
|
||||
linkScroll: { control: "boolean", description: "Whether to scroll to links" },
|
||||
icon: { control: "text", description: "SVGComponent icon" },
|
||||
avatar: { control: "text", description: "Avatar image URL" },
|
||||
}}>
|
||||
{(...props) => (
|
||||
<VariantsTable titles={["Default"]} columnMinWidth={150}>
|
||||
<VariantRow>
|
||||
<HorizontalTabs tabs={tabs} className="overflow-hidden" actions={<button>Click me</button>} />
|
||||
</VariantRow>
|
||||
</VariantsTable>
|
||||
)}
|
||||
</Story>
|
||||
</Canvas>
|
|
@ -0,0 +1,158 @@
|
|||
import { Meta, Story } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import {
|
||||
Title,
|
||||
CustomArgsTable,
|
||||
Examples,
|
||||
Example,
|
||||
VariantsTable,
|
||||
VariantsRow,
|
||||
} from "@calcom/storybook/components";
|
||||
import { Plus } from "@calcom/ui/components/icon";
|
||||
|
||||
import VerticalTabs from "../VerticalTabs";
|
||||
|
||||
<Meta title="UI/Navigation/VerticalTabs" component={VerticalTabs} />
|
||||
|
||||
<Title title="Vertical Tabs Brief" subtitle="Version 1.0 — Last Update: 17 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
The VerticalTabs component is a user interface element utilized to present a vertical set of tabs, commonly employed for navigation or organizing content within a web application.
|
||||
|
||||
## Structure
|
||||
|
||||
The VerticalTabs component is designed to complement the HorizontalTabItem component, which represents individual tabs within the tab bar. This combination allows for creating intuitive navigation experiences and organized content presentation.
|
||||
|
||||
export const tabs = [
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
disableChevron: true,
|
||||
icon: Plus,
|
||||
},
|
||||
{
|
||||
name: "Tab 2",
|
||||
href: "/tab2",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
avatar: "Avatar",
|
||||
},
|
||||
{
|
||||
name: "Tab 3",
|
||||
href: "/tab3",
|
||||
disabled: true,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
},
|
||||
];
|
||||
|
||||
<CustomArgsTable of={VerticalTabs} />
|
||||
|
||||
<Examples title="Default">
|
||||
<Example title="Default">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "tab 1",
|
||||
href: "/tab1",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="Disabled chevron">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disableChevron: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="With icon">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
icon: Plus,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="Disabled">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disabled: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
</Examples>
|
||||
|
||||
## VerticalTabs Story
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Vertical Tabs"
|
||||
args={{
|
||||
name: "Tab 1",
|
||||
info: "Tab information",
|
||||
icon: Plus,
|
||||
disabled: false,
|
||||
children: [
|
||||
{
|
||||
name: "Sub Tab 1",
|
||||
href: "/sub-tab1",
|
||||
disabled: false,
|
||||
className: "sub-tab",
|
||||
},
|
||||
],
|
||||
textClassNames: "",
|
||||
className: "",
|
||||
isChild: false,
|
||||
hidden: false,
|
||||
disableChevron: true,
|
||||
href: "/tab1",
|
||||
isExternalLink: true,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
avatar: "",
|
||||
iconClassName: "",
|
||||
}}
|
||||
argTypes={{
|
||||
name: { control: "text", description: "Tab name" },
|
||||
info: { control: "text", description: "Tab information" },
|
||||
icon: { control: "object", description: "SVGComponent icon" },
|
||||
disabled: { control: "boolean", description: "Whether the tab is disabled" },
|
||||
children: { control: "object", description: "Array of child tabs" },
|
||||
textClassNames: { control: "text", description: "Additional text class names" },
|
||||
className: { control: "text", description: "Additional CSS class" },
|
||||
isChild: { control: "boolean", description: "Whether the tab is a child tab" },
|
||||
hidden: { control: "boolean", description: "Whether the tab is hidden" },
|
||||
disableChevron: { control: "boolean", description: "Whether to disable the chevron" },
|
||||
href: { control: "text", description: "Tab link" },
|
||||
isExternalLink: { control: "boolean", description: "Whether the link is external" },
|
||||
linkShallow: { control: "boolean", description: "Whether to use shallow links" },
|
||||
linkScroll: { control: "boolean", description: "Whether to scroll to links" },
|
||||
avatar: { control: "text", description: "Avatar image URL" },
|
||||
iconClassName: { control: "text", description: "Additional icon class name" },
|
||||
}}>
|
||||
{(...props) => (
|
||||
<VariantsTable titles={["Default"]} columnMinWidth={150}>
|
||||
<VariantRow>
|
||||
<VerticalTabs tabs={tabs} className="overflow-hidden" />
|
||||
</VariantRow>
|
||||
</VariantsTable>
|
||||
)}
|
||||
</Story>
|
||||
</Canvas>
|
|
@ -4,7 +4,7 @@ import { classNames } from "@calcom/lib";
|
|||
|
||||
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div className="w-full overflow-auto">
|
||||
<div className="w-full overflow-auto md:overflow-visible">
|
||||
<table ref={ref} className={classNames("w-full caption-bottom text-sm", className)} {...props} />
|
||||
</div>
|
||||
)
|
||||
|
@ -13,7 +13,11 @@ Table.displayName = "Table";
|
|||
|
||||
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={classNames("[&_tr]:bg-subtle [&_tr]:border-b", className)} {...props} />
|
||||
<thead
|
||||
ref={ref}
|
||||
className={classNames("[&_tr]:bg-subtle md:sticky md:top-[4.25rem] md:z-10 [&_tr]:border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
|
|
@ -37,6 +37,15 @@ const workspaces = packagedEmbedTestsOnly
|
|||
setupFiles: ["packages/ui/components/test-setup.ts"],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: {
|
||||
globals: true,
|
||||
name: "EventTypeAppCardInterface components",
|
||||
include: ["packages/app-store/_components/**/*.{test,spec}.{ts,js,tsx}"],
|
||||
environment: "jsdom",
|
||||
setupFiles: ["packages/app-store/test-setup.ts"],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default defineWorkspace(workspaces);
|
||||
|
|
547
yarn.lock
547
yarn.lock
|
@ -91,6 +91,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@alloc/quick-lru@npm:^5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "@alloc/quick-lru@npm:5.2.0"
|
||||
checksum: bdc35758b552bcf045733ac047fb7f9a07c4678b944c641adfbd41f798b4b91fffd0fdc0df2578d9b0afc7b4d636aa6e110ead5d6281a2adc1ab90efd7f057f8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ampproject/remapping@npm:^2.2.0":
|
||||
version: 2.2.1
|
||||
resolution: "@ampproject/remapping@npm:2.2.1"
|
||||
|
@ -3183,7 +3190,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.21.0":
|
||||
"@babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.21.0":
|
||||
version: 7.23.1
|
||||
resolution: "@babel/runtime@npm:7.23.1"
|
||||
dependencies:
|
||||
|
@ -3535,15 +3542,13 @@ __metadata:
|
|||
"@calcom/ui": "*"
|
||||
"@types/node": 16.9.1
|
||||
"@types/react": 18.0.26
|
||||
"@types/react-dom": ^18.0.9
|
||||
"@types/react-dom": 18.0.9
|
||||
eslint: ^8.34.0
|
||||
eslint-config-next: ^13.2.1
|
||||
next: ^13.4.6
|
||||
next-auth: ^4.22.1
|
||||
postcss: ^8.4.18
|
||||
next: ^13.2.1
|
||||
next-auth: ^4.20.1
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
tailwindcss: ^3.3.1
|
||||
typescript: ^4.9.4
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
@ -3637,7 +3642,7 @@ __metadata:
|
|||
"@calcom/ui": "*"
|
||||
"@headlessui/react": ^1.5.0
|
||||
"@heroicons/react": ^1.0.6
|
||||
"@prisma/client": ^5.0.0
|
||||
"@prisma/client": ^4.13.0
|
||||
"@tailwindcss/forms": ^0.5.2
|
||||
"@types/node": 16.9.1
|
||||
"@types/react": 18.0.26
|
||||
|
@ -3645,21 +3650,21 @@ __metadata:
|
|||
chart.js: ^3.7.1
|
||||
client-only: ^0.0.1
|
||||
eslint: ^8.34.0
|
||||
next: ^13.4.6
|
||||
next-auth: ^4.22.1
|
||||
next-i18next: ^13.2.2
|
||||
next: ^13.2.1
|
||||
next-auth: ^4.20.1
|
||||
next-i18next: ^11.3.0
|
||||
postcss: ^8.4.18
|
||||
prisma: ^5.0.0
|
||||
prisma: ^4.13.0
|
||||
prisma-field-encryption: ^1.4.0
|
||||
react: ^18.2.0
|
||||
react-chartjs-2: ^4.0.1
|
||||
react-dom: ^18.2.0
|
||||
react-hook-form: ^7.43.3
|
||||
react-live-chat-loader: ^2.8.1
|
||||
react-live-chat-loader: ^2.7.3
|
||||
swr: ^1.2.2
|
||||
tailwindcss: ^3.3.1
|
||||
tailwindcss: ^3.2.1
|
||||
typescript: ^4.9.4
|
||||
zod: ^3.22.2
|
||||
zod: ^3.20.2
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
|
@ -3991,6 +3996,15 @@ __metadata:
|
|||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/intercom@workspace:packages/app-store/intercom":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@calcom/intercom@workspace:packages/app-store/intercom"
|
||||
dependencies:
|
||||
"@calcom/lib": "*"
|
||||
"@calcom/types": "*"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/jitsivideo@workspace:packages/app-store/jitsivideo":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@calcom/jitsivideo@workspace:packages/app-store/jitsivideo"
|
||||
|
@ -4491,6 +4505,7 @@ __metadata:
|
|||
"@radix-ui/react-collapsible": ^1.0.0
|
||||
"@radix-ui/react-dialog": ^1.0.4
|
||||
"@radix-ui/react-dropdown-menu": ^2.0.5
|
||||
"@radix-ui/react-hover-card": ^1.0.7
|
||||
"@radix-ui/react-id": ^1.0.0
|
||||
"@radix-ui/react-popover": ^1.0.2
|
||||
"@radix-ui/react-radio-group": ^1.0.0
|
||||
|
@ -7768,6 +7783,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/env@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/env@npm:13.5.4"
|
||||
checksum: 95ec7108bc88a01fed5389fb33e4b9eb34937908859d9f0aa87930c660f4395d90dafe10e54830faae5bc0a1b799be544c6455a2c8054499569d1e9296369076
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/eslint-plugin-next@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/eslint-plugin-next@npm:13.2.1"
|
||||
|
@ -7784,6 +7806,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-arm64@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/swc-darwin-arm64@npm:13.5.4"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-x64@npm:13.4.6":
|
||||
version: 13.4.6
|
||||
resolution: "@next/swc-darwin-x64@npm:13.4.6"
|
||||
|
@ -7791,6 +7820,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-x64@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/swc-darwin-x64@npm:13.5.4"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-gnu@npm:13.4.6":
|
||||
version: 13.4.6
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:13.4.6"
|
||||
|
@ -7798,6 +7834,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-gnu@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:13.5.4"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-musl@npm:13.4.6":
|
||||
version: 13.4.6
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:13.4.6"
|
||||
|
@ -7805,6 +7848,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-musl@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:13.5.4"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-gnu@npm:13.4.6":
|
||||
version: 13.4.6
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:13.4.6"
|
||||
|
@ -7812,6 +7862,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-gnu@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:13.5.4"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-musl@npm:13.4.6":
|
||||
version: 13.4.6
|
||||
resolution: "@next/swc-linux-x64-musl@npm:13.4.6"
|
||||
|
@ -7819,6 +7876,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-musl@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/swc-linux-x64-musl@npm:13.5.4"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-arm64-msvc@npm:13.4.6":
|
||||
version: 13.4.6
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:13.4.6"
|
||||
|
@ -7826,6 +7890,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-arm64-msvc@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:13.5.4"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-ia32-msvc@npm:13.4.6":
|
||||
version: 13.4.6
|
||||
resolution: "@next/swc-win32-ia32-msvc@npm:13.4.6"
|
||||
|
@ -7833,6 +7904,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-ia32-msvc@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/swc-win32-ia32-msvc@npm:13.5.4"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-x64-msvc@npm:13.4.6":
|
||||
version: 13.4.6
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:13.4.6"
|
||||
|
@ -7840,6 +7918,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-x64-msvc@npm:13.5.4":
|
||||
version: 13.5.4
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:13.5.4"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@noble/curves@npm:1.1.0"
|
||||
|
@ -8147,17 +8232,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/client@npm:^5.0.0":
|
||||
version: 5.3.1
|
||||
resolution: "@prisma/client@npm:5.3.1"
|
||||
"@prisma/client@npm:^4.13.0":
|
||||
version: 4.16.2
|
||||
resolution: "@prisma/client@npm:4.16.2"
|
||||
dependencies:
|
||||
"@prisma/engines-version": 5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59
|
||||
"@prisma/engines-version": 4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81
|
||||
peerDependencies:
|
||||
prisma: "*"
|
||||
peerDependenciesMeta:
|
||||
prisma:
|
||||
optional: true
|
||||
checksum: 8017b721a231ab7b2b0f932507b02bf075aae2c6f6e630a69d7089ff33bab44fa50b50dd8a81655b7202092ffc19717d484ae5f183fc4f2a1822b0d228991a7c
|
||||
checksum: 38e1356644a764946c69c8691ea4bbed0ba37739d833a435625bd5435912bed4b9bdd7c384125f3a4ab8128faf566027985c0f0840a42741c338d72e40b5d565
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -8219,6 +8304,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/engines-version@npm:4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81":
|
||||
version: 4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81
|
||||
resolution: "@prisma/engines-version@npm:4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81"
|
||||
checksum: b42c6abe7c1928e546f15449e40ffa455701ef2ab1f62973628ecb4e19ff3652e34609a0d83196d1cbd0864adb44c55e082beec852b11929acf1c15fb57ca45a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/engines-version@npm:5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f":
|
||||
version: 5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f
|
||||
resolution: "@prisma/engines-version@npm:5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f"
|
||||
|
@ -8226,10 +8318,10 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/engines-version@npm:5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59":
|
||||
version: 5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59
|
||||
resolution: "@prisma/engines-version@npm:5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59"
|
||||
checksum: c1adf540c9330a54a000c3005a4621c5d8355f2e3b159121587d33f82bf5992f567ac453f0ce76dd48fac427ec4dbc55942228fcee10d522b0d2c03bddbe422a
|
||||
"@prisma/engines@npm:4.16.2":
|
||||
version: 4.16.2
|
||||
resolution: "@prisma/engines@npm:4.16.2"
|
||||
checksum: f423e6092c3e558cd089a68ae87459fba7fd390c433df087342b3269c3b04163965b50845150dfe47d01f811781bfff89d5ae81c95ca603c59359ab69ebd810f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -8247,13 +8339,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/engines@npm:5.3.1":
|
||||
version: 5.3.1
|
||||
resolution: "@prisma/engines@npm:5.3.1"
|
||||
checksum: a231adad60ac42569b560ea9782bc181818d8ad15e65283d1317bda5d7aa754e5b536a3f9365ce1eda8961e1eff4eca5978c456fa9764a867fe4339d123e7371
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/extension-accelerate@npm:^0.6.2":
|
||||
version: 0.6.2
|
||||
resolution: "@prisma/extension-accelerate@npm:0.6.2"
|
||||
|
@ -8952,6 +9037,34 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-hover-card@npm:^1.0.7":
|
||||
version: 1.0.7
|
||||
resolution: "@radix-ui/react-hover-card@npm:1.0.7"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.13.10
|
||||
"@radix-ui/primitive": 1.0.1
|
||||
"@radix-ui/react-compose-refs": 1.0.1
|
||||
"@radix-ui/react-context": 1.0.1
|
||||
"@radix-ui/react-dismissable-layer": 1.0.5
|
||||
"@radix-ui/react-popper": 1.1.3
|
||||
"@radix-ui/react-portal": 1.0.4
|
||||
"@radix-ui/react-presence": 1.0.1
|
||||
"@radix-ui/react-primitive": 1.0.3
|
||||
"@radix-ui/react-use-controllable-state": 1.0.1
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 812c348d8331348774b0460cd9058fdb34e0a4e167cc3ab7350d60d0ac374c673e8159573919da299f58860b8eeb9d43c21ccb679cf6db70f5db0386359871ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-id@npm:0.1.5":
|
||||
version: 0.1.5
|
||||
resolution: "@radix-ui/react-id@npm:0.1.5"
|
||||
|
@ -9140,6 +9253,35 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-popper@npm:1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "@radix-ui/react-popper@npm:1.1.3"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.13.10
|
||||
"@floating-ui/react-dom": ^2.0.0
|
||||
"@radix-ui/react-arrow": 1.0.3
|
||||
"@radix-ui/react-compose-refs": 1.0.1
|
||||
"@radix-ui/react-context": 1.0.1
|
||||
"@radix-ui/react-primitive": 1.0.3
|
||||
"@radix-ui/react-use-callback-ref": 1.0.1
|
||||
"@radix-ui/react-use-layout-effect": 1.0.1
|
||||
"@radix-ui/react-use-rect": 1.0.1
|
||||
"@radix-ui/react-use-size": 1.0.1
|
||||
"@radix-ui/rect": 1.0.1
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: b18a15958623f9222b6ed3e24b9fbcc2ba67b8df5a5272412f261de1592b3f05002af1c8b94c065830c3c74267ce00cf6c1d70d4d507ec92ba639501f98aa348
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-portal@npm:0.1.4":
|
||||
version: 0.1.4
|
||||
resolution: "@radix-ui/react-portal@npm:0.1.4"
|
||||
|
@ -9187,6 +9329,26 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-portal@npm:1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "@radix-ui/react-portal@npm:1.0.4"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.13.10
|
||||
"@radix-ui/react-primitive": 1.0.3
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: c4cf35e2f26a89703189d0eef3ceeeb706ae0832e98e558730a5e929ca7c72c7cb510413a24eca94c7732f8d659a1e81942bec7b90540cb73ce9e4885d040b64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-presence@npm:1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@radix-ui/react-presence@npm:1.0.0"
|
||||
|
@ -12049,6 +12211,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:0.5.2":
|
||||
version: 0.5.2
|
||||
resolution: "@swc/helpers@npm:0.5.2"
|
||||
dependencies:
|
||||
tslib: ^2.4.0
|
||||
checksum: 51d7e3d8bd56818c49d6bfbd715f0dbeedc13cf723af41166e45c03e37f109336bbcb57a1f2020f4015957721aeb21e1a7fff281233d797ff7d3dd1f447fa258
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@szmarczak/http-timer@npm:^4.0.5":
|
||||
version: 4.0.6
|
||||
resolution: "@szmarczak/http-timer@npm:4.0.6"
|
||||
|
@ -23897,6 +24068,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"i18next-fs-backend@npm:^1.1.4":
|
||||
version: 1.2.0
|
||||
resolution: "i18next-fs-backend@npm:1.2.0"
|
||||
checksum: da74d20f2b007f8e34eaf442fa91ad12aaff3b9891e066c6addd6d111b37e370c62370dfbc656730ab2f8afd988f2e7ea1c48301ebb19ccb716fb5965600eddf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"i18next-fs-backend@npm:^2.1.1":
|
||||
version: 2.1.3
|
||||
resolution: "i18next-fs-backend@npm:2.1.3"
|
||||
|
@ -23904,6 +24082,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"i18next@npm:^21.8.13":
|
||||
version: 21.10.0
|
||||
resolution: "i18next@npm:21.10.0"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.17.2
|
||||
checksum: f997985e2d4d15a62a0936a82ff6420b97f3f971e776fe685bdd50b4de0cb4dc2198bc75efe6b152844794ebd5040d8060d6d152506a687affad534834836d81
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"i18next@npm:^23.2.3":
|
||||
version: 23.2.3
|
||||
resolution: "i18next@npm:23.2.3"
|
||||
|
@ -24576,7 +24763,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-core-module@npm:^2.11.0":
|
||||
"is-core-module@npm:^2.11.0, is-core-module@npm:^2.13.0":
|
||||
version: 2.13.0
|
||||
resolution: "is-core-module@npm:2.13.0"
|
||||
dependencies:
|
||||
|
@ -26711,6 +26898,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lilconfig@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "lilconfig@npm:2.1.0"
|
||||
checksum: 8549bb352b8192375fed4a74694cd61ad293904eee33f9d4866c2192865c44c4eb35d10782966242634e0cbc1e91fe62b1247f148dc5514918e3a966da7ea117
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"limiter@npm:^1.1.5":
|
||||
version: 1.1.5
|
||||
resolution: "limiter@npm:1.1.5"
|
||||
|
@ -29155,6 +29349,31 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-auth@npm:^4.20.1":
|
||||
version: 4.23.2
|
||||
resolution: "next-auth@npm:4.23.2"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.20.13
|
||||
"@panva/hkdf": ^1.0.2
|
||||
cookie: ^0.5.0
|
||||
jose: ^4.11.4
|
||||
oauth: ^0.9.15
|
||||
openid-client: ^5.4.0
|
||||
preact: ^10.6.3
|
||||
preact-render-to-string: ^5.1.19
|
||||
uuid: ^8.3.2
|
||||
peerDependencies:
|
||||
next: ^12.2.5 || ^13
|
||||
nodemailer: ^6.6.5
|
||||
react: ^17.0.2 || ^18
|
||||
react-dom: ^17.0.2 || ^18
|
||||
peerDependenciesMeta:
|
||||
nodemailer:
|
||||
optional: true
|
||||
checksum: 4820fdc8d9f066afd2dfe64012d7aba727fd7b82fec3a94e85ea5c1651cb4bf532d8742bfd253d9910055833f00c1c8f8f17212661f7648ecff4dd1f3e002e80
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-auth@npm:^4.22.1":
|
||||
version: 4.22.1
|
||||
resolution: "next-auth@npm:4.22.1"
|
||||
|
@ -29204,6 +29423,24 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-i18next@npm:^11.3.0":
|
||||
version: 11.3.0
|
||||
resolution: "next-i18next@npm:11.3.0"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.18.6
|
||||
"@types/hoist-non-react-statics": ^3.3.1
|
||||
core-js: ^3
|
||||
hoist-non-react-statics: ^3.3.2
|
||||
i18next: ^21.8.13
|
||||
i18next-fs-backend: ^1.1.4
|
||||
react-i18next: ^11.18.0
|
||||
peerDependencies:
|
||||
next: ">= 10.0.0"
|
||||
react: ">= 16.8.0"
|
||||
checksum: fbce97a4fbf9ad846c08652471a833c7f173c3e7ddc7cafa1423625b4a684715bb85f76ae06fe9cbed3e70f12b8e78e2459e5bc1a3c3f5c517743f17648f8939
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-i18next@npm:^13.2.2":
|
||||
version: 13.3.0
|
||||
resolution: "next-i18next@npm:13.3.0"
|
||||
|
@ -29285,6 +29522,61 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next@npm:^13.2.1":
|
||||
version: 13.5.4
|
||||
resolution: "next@npm:13.5.4"
|
||||
dependencies:
|
||||
"@next/env": 13.5.4
|
||||
"@next/swc-darwin-arm64": 13.5.4
|
||||
"@next/swc-darwin-x64": 13.5.4
|
||||
"@next/swc-linux-arm64-gnu": 13.5.4
|
||||
"@next/swc-linux-arm64-musl": 13.5.4
|
||||
"@next/swc-linux-x64-gnu": 13.5.4
|
||||
"@next/swc-linux-x64-musl": 13.5.4
|
||||
"@next/swc-win32-arm64-msvc": 13.5.4
|
||||
"@next/swc-win32-ia32-msvc": 13.5.4
|
||||
"@next/swc-win32-x64-msvc": 13.5.4
|
||||
"@swc/helpers": 0.5.2
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: ^1.0.30001406
|
||||
postcss: 8.4.31
|
||||
styled-jsx: 5.1.1
|
||||
watchpack: 2.4.0
|
||||
peerDependencies:
|
||||
"@opentelemetry/api": ^1.1.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
sass: ^1.3.0
|
||||
dependenciesMeta:
|
||||
"@next/swc-darwin-arm64":
|
||||
optional: true
|
||||
"@next/swc-darwin-x64":
|
||||
optional: true
|
||||
"@next/swc-linux-arm64-gnu":
|
||||
optional: true
|
||||
"@next/swc-linux-arm64-musl":
|
||||
optional: true
|
||||
"@next/swc-linux-x64-gnu":
|
||||
optional: true
|
||||
"@next/swc-linux-x64-musl":
|
||||
optional: true
|
||||
"@next/swc-win32-arm64-msvc":
|
||||
optional: true
|
||||
"@next/swc-win32-ia32-msvc":
|
||||
optional: true
|
||||
"@next/swc-win32-x64-msvc":
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
"@opentelemetry/api":
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
bin:
|
||||
next: dist/bin/next
|
||||
checksum: f8e964ee9bbabd0303f9d807c9193833fcc47960be029c3721db9a5a35cc4ff690313e30fc6ee497f959a9141048957dddf6eb038b4a23c78c8762b0cd9d0ae0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next@npm:^13.4.6":
|
||||
version: 13.4.6
|
||||
resolution: "next@npm:13.4.6"
|
||||
|
@ -31318,6 +31610,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-import@npm:^15.1.0":
|
||||
version: 15.1.0
|
||||
resolution: "postcss-import@npm:15.1.0"
|
||||
dependencies:
|
||||
postcss-value-parser: ^4.0.0
|
||||
read-cache: ^1.0.0
|
||||
resolve: ^1.1.7
|
||||
peerDependencies:
|
||||
postcss: ^8.0.0
|
||||
checksum: 7bd04bd8f0235429009d0022cbf00faebc885de1d017f6d12ccb1b021265882efc9302006ba700af6cab24c46bfa2f3bc590be3f9aee89d064944f171b04e2a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-js@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "postcss-js@npm:4.0.0"
|
||||
|
@ -31329,6 +31634,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-js@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "postcss-js@npm:4.0.1"
|
||||
dependencies:
|
||||
camelcase-css: ^2.0.1
|
||||
peerDependencies:
|
||||
postcss: ^8.4.21
|
||||
checksum: 5c1e83efeabeb5a42676193f4357aa9c88f4dc1b3c4a0332c132fe88932b33ea58848186db117cf473049fc233a980356f67db490bd0a7832ccba9d0b3fd3491
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-load-config@npm:^3.1.4":
|
||||
version: 3.1.4
|
||||
resolution: "postcss-load-config@npm:3.1.4"
|
||||
|
@ -31347,6 +31663,24 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-load-config@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "postcss-load-config@npm:4.0.1"
|
||||
dependencies:
|
||||
lilconfig: ^2.0.5
|
||||
yaml: ^2.1.1
|
||||
peerDependencies:
|
||||
postcss: ">=8.0.9"
|
||||
ts-node: ">=9.0.0"
|
||||
peerDependenciesMeta:
|
||||
postcss:
|
||||
optional: true
|
||||
ts-node:
|
||||
optional: true
|
||||
checksum: b61f890499ed7dcda1e36c20a9582b17d745bad5e2b2c7bc96942465e406bc43ae03f270c08e60d1e29dab1ee50cb26970b5eb20c9aae30e066e20bd607ae4e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-loader@npm:^4.2.0":
|
||||
version: 4.3.0
|
||||
resolution: "postcss-loader@npm:4.3.0"
|
||||
|
@ -31473,6 +31807,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-nested@npm:^6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "postcss-nested@npm:6.0.1"
|
||||
dependencies:
|
||||
postcss-selector-parser: ^6.0.11
|
||||
peerDependencies:
|
||||
postcss: ^8.2.14
|
||||
checksum: 7ddb0364cd797de01e38f644879189e0caeb7ea3f78628c933d91cc24f327c56d31269384454fc02ecaf503b44bfa8e08870a7c4cc56b23bc15640e1894523fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-pseudo-companion-classes@npm:^0.1.1":
|
||||
version: 0.1.1
|
||||
resolution: "postcss-pseudo-companion-classes@npm:0.1.1"
|
||||
|
@ -31527,6 +31872,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss@npm:8.4.31":
|
||||
version: 8.4.31
|
||||
resolution: "postcss@npm:8.4.31"
|
||||
dependencies:
|
||||
nanoid: ^3.3.6
|
||||
picocolors: ^1.0.0
|
||||
source-map-js: ^1.0.2
|
||||
checksum: 1d8611341b073143ad90486fcdfeab49edd243377b1f51834dc4f6d028e82ce5190e4f11bb2633276864503654fb7cab28e67abdc0fbf9d1f88cad4a0ff0beea
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss@npm:^7.0.14, postcss@npm:^7.0.26, postcss@npm:^7.0.32, postcss@npm:^7.0.36, postcss@npm:^7.0.5, postcss@npm:^7.0.6":
|
||||
version: 7.0.39
|
||||
resolution: "postcss@npm:7.0.39"
|
||||
|
@ -31856,14 +32212,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prisma@npm:^5.0.0":
|
||||
version: 5.3.1
|
||||
resolution: "prisma@npm:5.3.1"
|
||||
"prisma@npm:^4.13.0":
|
||||
version: 4.16.2
|
||||
resolution: "prisma@npm:4.16.2"
|
||||
dependencies:
|
||||
"@prisma/engines": 5.3.1
|
||||
"@prisma/engines": 4.16.2
|
||||
bin:
|
||||
prisma: build/index.js
|
||||
checksum: e825adbcb4eec81de276de5507fb7e5486db7788c8c9de36ba6ed73f9e87d9f56b64d0e183a31dc6b80f6737ae1fbcdb110aac44ab89299af646aeb966655bef
|
||||
prisma2: build/index.js
|
||||
checksum: 1d0ed616abd7f8de22441e333b976705f1cb05abcb206965df3fc6a7ea03911ef467dd484a4bc51fdc6cff72dd9857b9852be5f232967a444af0a98c49bfdb76
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -32764,6 +33121,24 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-i18next@npm:^11.18.0":
|
||||
version: 11.18.6
|
||||
resolution: "react-i18next@npm:11.18.6"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.14.5
|
||||
html-parse-stringify: ^3.0.1
|
||||
peerDependencies:
|
||||
i18next: ">= 19.0.0"
|
||||
react: ">= 16.8.0"
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
checksum: 624c0a0313fac4e0d18560b83c99a8bd0a83abc02e5db8d01984e0643ac409d178668aa3a4720d01f7a0d9520d38598dcbff801d6f69a970bae67461de6cd852
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-i18next@npm:^12.2.0":
|
||||
version: 12.3.1
|
||||
resolution: "react-i18next@npm:12.3.1"
|
||||
|
@ -32887,7 +33262,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-live-chat-loader@npm:^2.8.1":
|
||||
"react-live-chat-loader@npm:^2.7.3, react-live-chat-loader@npm:^2.8.1":
|
||||
version: 2.8.1
|
||||
resolution: "react-live-chat-loader@npm:2.8.1"
|
||||
peerDependencies:
|
||||
|
@ -34139,6 +34514,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@npm:^1.22.2":
|
||||
version: 1.22.6
|
||||
resolution: "resolve@npm:1.22.6"
|
||||
dependencies:
|
||||
is-core-module: ^2.13.0
|
||||
path-parse: ^1.0.7
|
||||
supports-preserve-symlinks-flag: ^1.0.0
|
||||
bin:
|
||||
resolve: bin/resolve
|
||||
checksum: d13bf66d4e2ee30d291491f16f2fa44edd4e0cefb85d53249dd6f93e70b2b8c20ec62f01b18662e3cd40e50a7528f18c4087a99490048992a3bb954cf3201a5b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@npm:^2.0.0-next.3":
|
||||
version: 2.0.0-next.3
|
||||
resolution: "resolve@npm:2.0.0-next.3"
|
||||
|
@ -34188,6 +34576,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@patch:resolve@^1.22.2#~builtin<compat/resolve>":
|
||||
version: 1.22.6
|
||||
resolution: "resolve@patch:resolve@npm%3A1.22.6#~builtin<compat/resolve>::version=1.22.6&hash=c3c19d"
|
||||
dependencies:
|
||||
is-core-module: ^2.13.0
|
||||
path-parse: ^1.0.7
|
||||
supports-preserve-symlinks-flag: ^1.0.0
|
||||
bin:
|
||||
resolve: bin/resolve
|
||||
checksum: 9d3b3c67aefd12cecbe5f10ca4d1f51ea190891096497c43f301b086883b426466918c3a64f1bbf1788fabb52b579d58809614006c5d0b49186702b3b8fb746a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@patch:resolve@^2.0.0-next.3#~builtin<compat/resolve>":
|
||||
version: 2.0.0-next.3
|
||||
resolution: "resolve@patch:resolve@npm%3A2.0.0-next.3#~builtin<compat/resolve>::version=2.0.0-next.3&hash=c3c19d"
|
||||
|
@ -36319,6 +36720,24 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sucrase@npm:^3.32.0":
|
||||
version: 3.34.0
|
||||
resolution: "sucrase@npm:3.34.0"
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping": ^0.3.2
|
||||
commander: ^4.0.0
|
||||
glob: 7.1.6
|
||||
lines-and-columns: ^1.1.6
|
||||
mz: ^2.7.0
|
||||
pirates: ^4.0.1
|
||||
ts-interface-checker: ^0.1.9
|
||||
bin:
|
||||
sucrase: bin/sucrase
|
||||
sucrase-node: bin/sucrase-node
|
||||
checksum: 61860063bdf6103413698e13247a3074d25843e91170825a9752e4af7668ffadd331b6e99e92fc32ee5b3c484ee134936f926fa9039d5711fafff29d017a2110
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"superagent@npm:^5.1.1":
|
||||
version: 5.3.1
|
||||
resolution: "superagent@npm:5.3.1"
|
||||
|
@ -36680,6 +37099,39 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tailwindcss@npm:^3.2.1":
|
||||
version: 3.3.3
|
||||
resolution: "tailwindcss@npm:3.3.3"
|
||||
dependencies:
|
||||
"@alloc/quick-lru": ^5.2.0
|
||||
arg: ^5.0.2
|
||||
chokidar: ^3.5.3
|
||||
didyoumean: ^1.2.2
|
||||
dlv: ^1.1.3
|
||||
fast-glob: ^3.2.12
|
||||
glob-parent: ^6.0.2
|
||||
is-glob: ^4.0.3
|
||||
jiti: ^1.18.2
|
||||
lilconfig: ^2.1.0
|
||||
micromatch: ^4.0.5
|
||||
normalize-path: ^3.0.0
|
||||
object-hash: ^3.0.0
|
||||
picocolors: ^1.0.0
|
||||
postcss: ^8.4.23
|
||||
postcss-import: ^15.1.0
|
||||
postcss-js: ^4.0.1
|
||||
postcss-load-config: ^4.0.1
|
||||
postcss-nested: ^6.0.1
|
||||
postcss-selector-parser: ^6.0.11
|
||||
resolve: ^1.22.2
|
||||
sucrase: ^3.32.0
|
||||
bin:
|
||||
tailwind: lib/cli.js
|
||||
tailwindcss: lib/cli.js
|
||||
checksum: 0195c7a3ebb0de5e391d2a883d777c78a4749f0c532d204ee8aea9129f2ed8e701d8c0c276aa5f7338d07176a3c2a7682c1d0ab9c8a6c2abe6d9325c2954eb50
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tailwindcss@npm:^3.3.1":
|
||||
version: 3.3.1
|
||||
resolution: "tailwindcss@npm:3.3.1"
|
||||
|
@ -40575,6 +41027,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.1.1, yaml@npm:^2.3.1":
|
||||
version: 2.3.2
|
||||
resolution: "yaml@npm:2.3.2"
|
||||
checksum: acd80cc24df12c808c6dec8a0176d404ef9e6f08ad8786f746ecc9d8974968c53c6e8a67fdfabcc5f99f3dc59b6bb0994b95646ff03d18e9b1dcd59eccc02146
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.2.1":
|
||||
version: 2.3.1
|
||||
resolution: "yaml@npm:2.3.1"
|
||||
|
@ -40582,13 +41041,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.3.1":
|
||||
version: 2.3.2
|
||||
resolution: "yaml@npm:2.3.2"
|
||||
checksum: acd80cc24df12c808c6dec8a0176d404ef9e6f08ad8786f746ecc9d8974968c53c6e8a67fdfabcc5f99f3dc59b6bb0994b95646ff03d18e9b1dcd59eccc02146
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs-parser@npm:^18.1.2, yargs-parser@npm:^18.1.3":
|
||||
version: 18.1.3
|
||||
resolution: "yargs-parser@npm:18.1.3"
|
||||
|
@ -40822,6 +41274,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.20.2":
|
||||
version: 3.22.4
|
||||
resolution: "zod@npm:3.22.4"
|
||||
checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.21.4, zod@npm:^3.22.2":
|
||||
version: 3.22.2
|
||||
resolution: "zod@npm:3.22.2"
|
||||
|
|
Loading…
Reference in New Issue
Block a user