Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da67be475f | ||
|
|
e639237088 | ||
|
|
5bc1fbb8c2 | ||
|
|
e310931417 | ||
|
|
17944311c3 | ||
|
|
d82f87aa2a | ||
|
|
6aa21aceb8 | ||
|
|
870ad57a7e | ||
|
|
fddee71829 | ||
|
|
6f15a5ab0f | ||
|
|
1fd64c5d7b | ||
|
|
f8614f11ff | ||
|
|
5e5e77236a | ||
|
|
4cee53b07c | ||
|
|
c289fac008 | ||
|
|
68f9e86cdd | ||
|
|
5da754db65 |
@@ -111,7 +111,7 @@ The event types page UI components are located in `apps/web/modules/event-types/
|
||||
|
||||
Changes to shared UI patterns (like tab layouts and button alignments) need to be checked across multiple views to maintain consistency:
|
||||
- Event types page layout: `apps/web/modules/event-types/views/event-types-listing-view.tsx`
|
||||
- Bookings page layout: `apps/web/modules/bookings/views/bookings-listing-view.tsx`
|
||||
- Bookings page layout: `apps/web/modules/bookings/views/bookings-view.tsx`
|
||||
- Common elements like tabs, search bars, and filter buttons should maintain consistent alignment across views
|
||||
|
||||
## When working on workflow triggers or similar enum-based features in the Cal.com codebase
|
||||
|
||||
Vendored
@@ -234,9 +234,10 @@ for Logger level to be set at info, for example.
|
||||
|
||||
</details>
|
||||
|
||||
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
|
||||
If you don't want to create a local DB. Then you can also consider using services like railway.app, Northflank or render.
|
||||
|
||||
- [Setup postgres DB with railway.app](https://docs.railway.app/guides/postgresql)
|
||||
- [Setup postgres DB with Northflank](https://northflank.com/guides/deploy-postgres-database-on-northflank)
|
||||
- [Setup postgres DB with render](https://render.com/docs/databases)
|
||||
|
||||
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
||||
@@ -390,6 +391,12 @@ Cal.com, Inc. does not provide official support for Docker, but we will accept f
|
||||
|
||||
You can deploy Cal.com on [Railway](https://railway.app) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal.com on their platform.
|
||||
|
||||
### Northflank
|
||||
|
||||
[](https://northflank.com/stacks/deploy-calcom)
|
||||
|
||||
You can deploy Cal.com on [Northflank](https://northflank.com) using the button above. The team at Northflank also have a [detailed blog post](https://northflank.com/guides/deploy-calcom-with-northflank) on deploying Cal.com on their platform.
|
||||
|
||||
### Vercel
|
||||
|
||||
Currently Vercel Pro Plan is required to be able to Deploy this application with Vercel, due to limitations on the number of serverless functions on the free plan.
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { get } from "@vercel/edge-config";
|
||||
import type { NextMiddleware } from "next-api-middleware";
|
||||
|
||||
const safeGet = async <T = unknown>(key: string): Promise<T | undefined> => {
|
||||
try {
|
||||
return get<T>(key);
|
||||
} catch (error) {
|
||||
// Don't crash if EDGE_CONFIG env var is missing
|
||||
}
|
||||
};
|
||||
|
||||
export const config = { matcher: "/:path*" };
|
||||
|
||||
export const checkIsInMaintenanceMode: NextMiddleware = async (req, res, next) => {
|
||||
const isInMaintenanceMode = await safeGet<boolean>("isInMaintenanceMode");
|
||||
if (isInMaintenanceMode) {
|
||||
return res
|
||||
.status(503)
|
||||
.json({ message: "API is currently under maintenance. Please try again at a later time." });
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import { label } from "next-api-middleware";
|
||||
import { addRequestId } from "./addRequestid";
|
||||
import { captureErrors } from "./captureErrors";
|
||||
import { captureUserId } from "./captureUserId";
|
||||
import { checkIsInMaintenanceMode } from "./checkIsInMaintenanceMode";
|
||||
import { extendRequest } from "./extendRequest";
|
||||
import {
|
||||
HTTP_DELETE,
|
||||
@@ -26,7 +25,6 @@ const middleware = {
|
||||
HTTP_POST,
|
||||
HTTP_DELETE,
|
||||
addRequestId,
|
||||
checkIsInMaintenanceMode,
|
||||
verifyApiKey,
|
||||
rateLimitApiKey,
|
||||
extendRequest,
|
||||
@@ -38,16 +36,14 @@ const middleware = {
|
||||
|
||||
type Middleware = keyof typeof middleware;
|
||||
|
||||
const middlewareOrder = [
|
||||
// The order here, determines the order of execution
|
||||
"checkIsInMaintenanceMode",
|
||||
const middlewareOrder: Middleware[] = [
|
||||
"extendRequest",
|
||||
"captureErrors",
|
||||
"verifyApiKey",
|
||||
"rateLimitApiKey",
|
||||
"addRequestId",
|
||||
"captureUserId",
|
||||
] as Middleware[]; // <-- Provide a list of middleware to call automatically
|
||||
];
|
||||
|
||||
const withMiddleware = label(middleware, middlewareOrder);
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { BookingCancelService } from "@/lib/services/booking-cancel.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [BookingCancelService],
|
||||
exports: [BookingCancelService],
|
||||
})
|
||||
export class BookingCancelModule {}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { BookingCancelService as BaseBookingCancelService } from "@calcom/features/bookings/lib/handleCancelBooking";
|
||||
|
||||
@Injectable()
|
||||
export class BookingCancelService extends BaseBookingCancelService {
|
||||
constructor(
|
||||
prismaWriteService: PrismaWriteService,
|
||||
) {
|
||||
super({
|
||||
prismaClient: prismaWriteService.prisma,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
import { PrismaAttributeRepository } from "@/lib/repositories/prisma-attribute.repository";
|
||||
import { PrismaBookingRepository } from "@/lib/repositories/prisma-booking.repository";
|
||||
import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository";
|
||||
import { PrismaHostRepository } from "@/lib/repositories/prisma-host.repository";
|
||||
import { PrismaOOORepository } from "@/lib/repositories/prisma-ooo.repository";
|
||||
import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository";
|
||||
import { CacheService } from "@/lib/services/cache.service";
|
||||
import { CheckBookingAndDurationLimitsService } from "@/lib/services/check-booking-and-duration-limits.service";
|
||||
import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service";
|
||||
import { LuckyUserService } from "@/lib/services/lucky-user.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
@@ -21,26 +16,16 @@ export class RegularBookingService extends BaseRegularBookingService {
|
||||
checkBookingAndDurationLimitsService: CheckBookingAndDurationLimitsService,
|
||||
prismaWriteService: PrismaWriteService,
|
||||
bookingRepository: PrismaBookingRepository,
|
||||
featuresRepository: PrismaFeaturesRepository,
|
||||
checkBookingLimitsService: CheckBookingLimitsService,
|
||||
luckyUserService: LuckyUserService,
|
||||
hostRepository: PrismaHostRepository,
|
||||
oooRepository: PrismaOOORepository,
|
||||
userRepository: PrismaUserRepository,
|
||||
attributeRepository: PrismaAttributeRepository
|
||||
userRepository: PrismaUserRepository
|
||||
) {
|
||||
super({
|
||||
cacheService,
|
||||
checkBookingAndDurationLimitsService,
|
||||
prismaClient: prismaWriteService.prisma as unknown as PrismaClient,
|
||||
bookingRepository,
|
||||
featuresRepository,
|
||||
checkBookingLimitsService,
|
||||
luckyUserService,
|
||||
hostRepository,
|
||||
oooRepository,
|
||||
userRepository,
|
||||
attributeRepository,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+17
-2
@@ -404,7 +404,7 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
||||
invalidWorkflow.steps = [
|
||||
{
|
||||
stepNumber: 1,
|
||||
action: "sms_number",
|
||||
action: "cal_ai_phone_call",
|
||||
recipient: PHONE_NUMBER,
|
||||
template: REMINDER,
|
||||
verifiedPhoneId: verifiedPhoneId,
|
||||
@@ -439,6 +439,18 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
||||
html: "<p>Reminder for your event {EVENT_NAME}.</p>",
|
||||
},
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
action: "sms_attendee",
|
||||
recipient: EMAIL,
|
||||
template: REMINDER,
|
||||
phoneRequired: false,
|
||||
sender: "updatedSender",
|
||||
message: {
|
||||
subject: "Update Upcoming: {EVENT_NAME}",
|
||||
text: "Update Reminder for your event {EVENT_NAME}.</p>",
|
||||
},
|
||||
},
|
||||
];
|
||||
return request(app.getHttpServer())
|
||||
.post(`${basePath}/routing-form`)
|
||||
@@ -459,12 +471,15 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
||||
}
|
||||
|
||||
expect(responseBody.data.trigger.type).toEqual(sampleCreateWorkflowRoutingFormDto.trigger.type);
|
||||
expect(responseBody.data.steps).toHaveLength(sampleCreateWorkflowRoutingFormDto.steps.length);
|
||||
expect(responseBody.data.steps).toHaveLength(validWorkflow.steps.length);
|
||||
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.id).toBeDefined();
|
||||
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.sender).toEqual(
|
||||
"CalcomE2EStep1"
|
||||
);
|
||||
|
||||
expect(responseBody.data.steps.find((step) => step.stepNumber === 2)?.id).toBeDefined();
|
||||
expect(responseBody.data.steps.find((step) => step.action === "sms_attendee")).toBeDefined();
|
||||
|
||||
const trigger = sampleCreateWorkflowRoutingFormDto.trigger as OnFormSubmittedTriggerDto;
|
||||
expect(responseBody.data.trigger?.type).toEqual(trigger.type);
|
||||
|
||||
|
||||
@@ -7,8 +7,12 @@ import {
|
||||
EMAIL_ADDRESS,
|
||||
EMAIL_ATTENDEE,
|
||||
FORM_ALLOWED_STEP_ACTIONS,
|
||||
SMS_NUMBER,
|
||||
WorkflowEmailAddressStepDto,
|
||||
WorkflowEmailAttendeeStepDto,
|
||||
WorkflowPhoneNumberStepDto,
|
||||
WorkflowPhoneAttendeeStepDto,
|
||||
SMS_ATTENDEE,
|
||||
} from "./workflow-step.input";
|
||||
import {
|
||||
RoutingFormWorkflowTriggerDto,
|
||||
@@ -85,6 +89,8 @@ export class CreateFormWorkflowDto {
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(WorkflowEmailAddressStepDto) },
|
||||
{ $ref: getSchemaPath(WorkflowEmailAttendeeStepDto) },
|
||||
{ $ref: getSchemaPath(WorkflowPhoneAttendeeStepDto) },
|
||||
{ $ref: getSchemaPath(WorkflowPhoneNumberStepDto) },
|
||||
],
|
||||
type: "array",
|
||||
})
|
||||
@@ -99,8 +105,15 @@ export class CreateFormWorkflowDto {
|
||||
subTypes: [
|
||||
{ value: WorkflowEmailAddressStepDto, name: EMAIL_ADDRESS },
|
||||
{ value: WorkflowEmailAttendeeStepDto, name: EMAIL_ATTENDEE },
|
||||
{ value: WorkflowPhoneAttendeeStepDto, name: SMS_ATTENDEE },
|
||||
{ value: WorkflowPhoneNumberStepDto, name: SMS_NUMBER },
|
||||
],
|
||||
},
|
||||
})
|
||||
steps!: (WorkflowEmailAddressStepDto | WorkflowEmailAttendeeStepDto)[];
|
||||
steps!: (
|
||||
| WorkflowEmailAddressStepDto
|
||||
| WorkflowEmailAttendeeStepDto
|
||||
| WorkflowPhoneAttendeeStepDto
|
||||
| WorkflowPhoneNumberStepDto
|
||||
)[];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
EMAIL_ADDRESS,
|
||||
EMAIL_ATTENDEE,
|
||||
FORM_ALLOWED_STEP_ACTIONS,
|
||||
SMS_ATTENDEE,
|
||||
SMS_NUMBER,
|
||||
UpdateEmailAddressWorkflowStepDto,
|
||||
UpdateEmailAttendeeWorkflowStepDto,
|
||||
UpdateEmailHostWorkflowStepDto,
|
||||
@@ -75,6 +77,8 @@ export class UpdateFormWorkflowDto {
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(UpdateEmailAddressWorkflowStepDto) },
|
||||
{ $ref: getSchemaPath(UpdateEmailAttendeeWorkflowStepDto) },
|
||||
{ $ref: getSchemaPath(UpdatePhoneAttendeeWorkflowStepDto) },
|
||||
{ $ref: getSchemaPath(UpdatePhoneNumberWorkflowStepDto) },
|
||||
],
|
||||
type: "array",
|
||||
})
|
||||
@@ -90,8 +94,15 @@ export class UpdateFormWorkflowDto {
|
||||
subTypes: [
|
||||
{ value: UpdateEmailAddressWorkflowStepDto, name: EMAIL_ADDRESS },
|
||||
{ value: UpdateEmailAttendeeWorkflowStepDto, name: EMAIL_ATTENDEE },
|
||||
{ value: UpdatePhoneAttendeeWorkflowStepDto, name: SMS_ATTENDEE },
|
||||
{ value: UpdatePhoneNumberWorkflowStepDto, name: SMS_NUMBER },
|
||||
],
|
||||
},
|
||||
})
|
||||
steps?: (UpdateEmailAddressWorkflowStepDto | UpdateEmailAttendeeWorkflowStepDto)[];
|
||||
steps?: (
|
||||
| UpdateEmailAddressWorkflowStepDto
|
||||
| UpdateEmailAttendeeWorkflowStepDto
|
||||
| UpdatePhoneNumberWorkflowStepDto
|
||||
| UpdatePhoneAttendeeWorkflowStepDto
|
||||
)[];
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export const STEP_ACTIONS = [
|
||||
CAL_AI_PHONE_CALL,
|
||||
] as const;
|
||||
|
||||
export const FORM_ALLOWED_STEP_ACTIONS = [EMAIL_ATTENDEE, EMAIL_ADDRESS] as const;
|
||||
export const FORM_ALLOWED_STEP_ACTIONS = [EMAIL_ATTENDEE, EMAIL_ADDRESS, SMS_ATTENDEE, SMS_NUMBER] as const;
|
||||
|
||||
export const STEP_ACTIONS_TO_ENUM = {
|
||||
[EMAIL_HOST]: WorkflowActions.EMAIL_HOST,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { validStatuses } from "~/bookings/lib/validStatuses";
|
||||
import BookingsList from "~/bookings/views/bookings-listing-view";
|
||||
import BookingsList from "~/bookings/views/bookings-view";
|
||||
|
||||
const querySchema = z.object({
|
||||
status: z.enum(validStatuses),
|
||||
|
||||
@@ -26,11 +26,20 @@ async function handler(req: NextRequest) {
|
||||
cookieStore.delete("calcom.csrf_token");
|
||||
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
const result = await handleCancelBooking({
|
||||
bookingData,
|
||||
userId: session?.user?.id || -1,
|
||||
});
|
||||
|
||||
// const bookingCancelService = getBookingCancelService();
|
||||
// const result = await bookingCancelService.cancelBooking({
|
||||
// bookingData: bookingData,
|
||||
// bookingMeta: {
|
||||
// userId: session?.user?.id || -1,
|
||||
// },
|
||||
// });
|
||||
|
||||
const statusCode = result.success ? 200 : 400;
|
||||
|
||||
return NextResponse.json(result, { status: statusCode });
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { RegularBookingCreateResult } from "@calcom/features/bookings/lib/dto/types";
|
||||
|
||||
export type BookingResponse = RegularBookingCreateResult;
|
||||
@@ -11,11 +11,11 @@ import { z } from "zod";
|
||||
|
||||
import { SAMLLogin } from "@calcom/features/auth/SAMLLogin";
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { LastUsed, useLastUsed } from "@calcom/features/auth/lib/hooks/useLastUsed";
|
||||
import { HOSTED_CAL_FEATURES, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { emailRegex } from "@calcom/lib/emailSchema";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
import { LastUsed, useLastUsed } from "@calcom/lib/hooks/useLastUsed";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useTelemetry } from "@calcom/lib/hooks/useTelemetry";
|
||||
import { collectPageParameters, telemetryEventTypes } from "@calcom/lib/telemetry";
|
||||
@@ -62,7 +62,7 @@ export default function Login({
|
||||
.string()
|
||||
.min(1, `${t("error_required_field")}`)
|
||||
.regex(emailRegex, `${t("enter_valid_email")}`),
|
||||
...(!!totpEmail ? {} : { password: z.string().min(1, `${t("error_required_field")}`) }),
|
||||
...(totpEmail ? {} : { password: z.string().min(1, `${t("error_required_field")}`) }),
|
||||
})
|
||||
// Passthrough other fields like totpCode
|
||||
.passthrough();
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
|
||||
import { DataTableFilters, DataTableSegment } from "@calcom/features/data-table";
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
|
||||
import type { RowData, BookingListingStatus } from "../types";
|
||||
|
||||
type BookingsCalendarViewProps = {
|
||||
status: BookingListingStatus;
|
||||
table: ReactTable<RowData>;
|
||||
};
|
||||
|
||||
export function BookingsCalendar({ table }: BookingsCalendarViewProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<DataTableFilters.FilterBar table={table} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<DataTableFilters.ClearFiltersButton />
|
||||
<DataTableSegment.SaveButton />
|
||||
<DataTableSegment.Select />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center pt-2 xl:pt-0">
|
||||
<EmptyScreen Icon="calendar" headline="Calendar view" description="Calendar view is coming soon." />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
|
||||
import { DataTableWrapper, DataTableFilters, DataTableSegment } from "@calcom/features/data-table";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
|
||||
import SkeletonLoader from "@components/booking/SkeletonLoader";
|
||||
|
||||
import type { RowData, BookingListingStatus } from "../types";
|
||||
|
||||
const descriptionByStatus: Record<BookingListingStatus, string> = {
|
||||
upcoming: "upcoming_bookings",
|
||||
recurring: "recurring_bookings",
|
||||
past: "past_bookings",
|
||||
cancelled: "cancelled_bookings",
|
||||
unconfirmed: "unconfirmed_bookings",
|
||||
};
|
||||
|
||||
type BookingsListViewProps = {
|
||||
status: BookingListingStatus;
|
||||
table: ReactTable<RowData>;
|
||||
isPending: boolean;
|
||||
totalRowCount?: number;
|
||||
};
|
||||
|
||||
export function BookingsList({ status, table, isPending, totalRowCount }: BookingsListViewProps) {
|
||||
const { t } = useLocale();
|
||||
|
||||
return (
|
||||
<DataTableWrapper
|
||||
className="mb-6"
|
||||
table={table}
|
||||
testId={`${status}-bookings`}
|
||||
bodyTestId="bookings"
|
||||
headerClassName="hidden"
|
||||
isPending={isPending}
|
||||
totalRowCount={totalRowCount}
|
||||
variant="compact"
|
||||
paginationMode="standard"
|
||||
ToolbarLeft={
|
||||
<>
|
||||
<DataTableFilters.FilterBar table={table} />
|
||||
</>
|
||||
}
|
||||
ToolbarRight={
|
||||
<>
|
||||
<DataTableFilters.ClearFiltersButton />
|
||||
<DataTableSegment.SaveButton />
|
||||
<DataTableSegment.Select />
|
||||
</>
|
||||
}
|
||||
LoaderView={<SkeletonLoader />}
|
||||
EmptyView={
|
||||
<div className="flex items-center justify-center pt-2 xl:pt-0">
|
||||
<EmptyScreen
|
||||
Icon="calendar"
|
||||
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
|
||||
description={t("no_status_bookings_yet_description", {
|
||||
status: t(status).toLowerCase(),
|
||||
description: t(descriptionByStatus[status]),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
|
||||
import type { validStatuses } from "~/bookings/lib/validStatuses";
|
||||
|
||||
export type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
|
||||
|
||||
export type RecurringInfo = {
|
||||
recurringEventId: string | null;
|
||||
count: number;
|
||||
firstDate: Date | null;
|
||||
bookings: { [key: string]: Date[] };
|
||||
};
|
||||
|
||||
export type RowData =
|
||||
| {
|
||||
type: "data";
|
||||
booking: BookingOutput;
|
||||
isToday: boolean;
|
||||
recurringInfo?: RecurringInfo;
|
||||
}
|
||||
| {
|
||||
type: "today" | "next";
|
||||
};
|
||||
|
||||
export type BookingListingStatus = (typeof validStatuses)[number];
|
||||
@@ -121,13 +121,22 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
: eventTypeRaw.users;
|
||||
|
||||
if (!eventTypeRaw.users.length) {
|
||||
if (!eventTypeRaw.owner)
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
eventTypeRaw.users.push({
|
||||
...eventTypeRaw.owner,
|
||||
});
|
||||
if (!eventTypeRaw.owner) {
|
||||
if (bookingInfoRaw.user) {
|
||||
eventTypeRaw.users.push({
|
||||
...bookingInfoRaw.user,
|
||||
hideBranding: false,
|
||||
theme: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
isPlatformManaged: false,
|
||||
});
|
||||
} else {
|
||||
return { notFound: true } as const;
|
||||
}
|
||||
} else {
|
||||
eventTypeRaw.users.push({ ...eventTypeRaw.owner });
|
||||
}
|
||||
}
|
||||
|
||||
const eventType = {
|
||||
|
||||
+28
-74
@@ -2,57 +2,38 @@
|
||||
|
||||
import { useReactTable, getCoreRowModel, getSortedRowModel, createColumnHelper } from "@tanstack/react-table";
|
||||
import { useSearchParams, usePathname } from "next/navigation";
|
||||
import { useMemo, useRef } from "react";
|
||||
import { createParser, useQueryState } from "nuqs";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import {
|
||||
useDataTable,
|
||||
DataTableProvider,
|
||||
DataTableWrapper,
|
||||
DataTableFilters,
|
||||
DataTableSegment,
|
||||
type SystemFilterSegment,
|
||||
useDataTable,
|
||||
ColumnFilterType,
|
||||
useFilterValue,
|
||||
ZMultiSelectFilterValue,
|
||||
ZDateRangeFilterValue,
|
||||
ZTextFilterValue,
|
||||
type SystemFilterSegment,
|
||||
} from "@calcom/features/data-table";
|
||||
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
import type { HorizontalTabItemProps } from "@calcom/ui/components/navigation";
|
||||
import { HorizontalTabs } from "@calcom/ui/components/navigation";
|
||||
import type { VerticalTabItemProps } from "@calcom/ui/components/navigation";
|
||||
import { WipeMyCalActionButton } from "@calcom/web/components/apps/wipemycalother/wipeMyCalActionButton";
|
||||
|
||||
import BookingListItem from "@components/booking/BookingListItem";
|
||||
import SkeletonLoader from "@components/booking/SkeletonLoader";
|
||||
|
||||
import { useFacetedUniqueValues } from "~/bookings/hooks/useFacetedUniqueValues";
|
||||
import type { validStatuses } from "~/bookings/lib/validStatuses";
|
||||
|
||||
type BookingListingStatus = (typeof validStatuses)[number];
|
||||
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
|
||||
|
||||
type RecurringInfo = {
|
||||
recurringEventId: string | null;
|
||||
count: number;
|
||||
firstDate: Date | null;
|
||||
bookings: { [key: string]: Date[] };
|
||||
};
|
||||
|
||||
const descriptionByStatus: Record<BookingListingStatus, string> = {
|
||||
upcoming: "upcoming_bookings",
|
||||
recurring: "recurring_bookings",
|
||||
past: "past_bookings",
|
||||
cancelled: "cancelled_bookings",
|
||||
unconfirmed: "unconfirmed_bookings",
|
||||
};
|
||||
import { BookingsCalendar } from "../components/BookingsCalendar";
|
||||
import { BookingsList } from "../components/BookingsList";
|
||||
import type { RowData, BookingOutput } from "../types";
|
||||
|
||||
type BookingsProps = {
|
||||
status: (typeof validStatuses)[number];
|
||||
@@ -103,21 +84,18 @@ export default function Bookings(props: BookingsProps) {
|
||||
);
|
||||
}
|
||||
|
||||
type RowData =
|
||||
| {
|
||||
type: "data";
|
||||
booking: BookingOutput;
|
||||
isToday: boolean;
|
||||
recurringInfo?: RecurringInfo;
|
||||
}
|
||||
| {
|
||||
type: "today" | "next";
|
||||
};
|
||||
const viewParser = createParser({
|
||||
parse: (value: string) => {
|
||||
if (value === "calendar") return "calendar";
|
||||
return "list";
|
||||
},
|
||||
serialize: (value: "list" | "calendar") => value,
|
||||
});
|
||||
|
||||
function BookingsContent({ status, permissions }: BookingsProps) {
|
||||
const [view] = useQueryState("view", viewParser.withDefault("list"));
|
||||
const { t } = useLocale();
|
||||
const user = useMeQuery().data;
|
||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
// Generate dynamic tabs that preserve query parameters
|
||||
@@ -411,6 +389,9 @@ function BookingsContent({ status, permissions }: BookingsProps) {
|
||||
getFacetedUniqueValues,
|
||||
});
|
||||
|
||||
const isPending = query.isPending;
|
||||
const totalRowCount = query.data?.totalCount;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row flex-wrap justify-between">
|
||||
@@ -431,43 +412,16 @@ function BookingsContent({ status, permissions }: BookingsProps) {
|
||||
{!!bookingsToday.length && status === "upcoming" && (
|
||||
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
|
||||
)}
|
||||
<DataTableWrapper
|
||||
className="mb-6"
|
||||
tableContainerRef={tableContainerRef}
|
||||
table={table}
|
||||
testId={`${status}-bookings`}
|
||||
bodyTestId="bookings"
|
||||
headerClassName="hidden"
|
||||
isPending={query.isPending}
|
||||
totalRowCount={query.data?.totalCount}
|
||||
variant="compact"
|
||||
paginationMode="standard"
|
||||
ToolbarLeft={
|
||||
<>
|
||||
<DataTableFilters.FilterBar table={table} />
|
||||
</>
|
||||
}
|
||||
ToolbarRight={
|
||||
<>
|
||||
<DataTableFilters.ClearFiltersButton />
|
||||
<DataTableSegment.SaveButton />
|
||||
<DataTableSegment.Select />
|
||||
</>
|
||||
}
|
||||
LoaderView={<SkeletonLoader />}
|
||||
EmptyView={
|
||||
<div className="flex items-center justify-center pt-2 xl:pt-0">
|
||||
<EmptyScreen
|
||||
Icon="calendar"
|
||||
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
|
||||
description={t("no_status_bookings_yet_description", {
|
||||
status: t(status).toLowerCase(),
|
||||
description: t(descriptionByStatus[status]),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{view === "list" ? (
|
||||
<BookingsList
|
||||
status={status}
|
||||
table={table}
|
||||
isPending={isPending}
|
||||
totalRowCount={totalRowCount}
|
||||
/>
|
||||
) : (
|
||||
<BookingsCalendar status={status} table={table} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable */
|
||||
require("dotenv").config({ path: "../../.env" });
|
||||
const englishTranslation = require("./public/static/locales/en/common.json");
|
||||
const { withAxiom } = require("next-axiom");
|
||||
@@ -223,8 +224,6 @@ const nextConfig = (phase) => {
|
||||
"@calcom/dayjs",
|
||||
"@calcom/emails",
|
||||
"@calcom/embed-core",
|
||||
"@calcom/embed-react",
|
||||
"@calcom/embed-snippet",
|
||||
"@calcom/features",
|
||||
"@calcom/lib",
|
||||
"@calcom/prisma",
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tailwindcss-animate": "^1.0.6",
|
||||
"ts-node": "^10.9.1",
|
||||
"turbo": "^2.5.5",
|
||||
"typescript": "^5.9.0-beta"
|
||||
},
|
||||
"nextBundleAnalysis": {
|
||||
|
||||
@@ -1460,6 +1460,7 @@
|
||||
"email_host_action": "send email to host",
|
||||
"email_attendee_action": "send email to attendees",
|
||||
"email_attendee_action_form": "Send email to submitted email address",
|
||||
"sms_attendee_action_form": "Send SMS to submitted phone number",
|
||||
"sms_attendee_action": "Send SMS to attendee",
|
||||
"sms_number_action": "send SMS to a specific number",
|
||||
"send_reminder_sms": "Easily send meeting reminders via SMS to your attendees",
|
||||
@@ -1893,7 +1894,7 @@
|
||||
"only_show_first_available_slot_description": "This will limit your availability for this event type to one slot per day, scheduled at the earliest available time.",
|
||||
"limit_total_booking_duration": "Limit total booking duration",
|
||||
"limit_total_booking_duration_description": "Limit total amount of time that this event can be booked",
|
||||
"booker_booking_limit": "Booker active booking limit",
|
||||
"booker_upcoming_limit": "Limit number of upcoming bookings per booker",
|
||||
"booker_booking_limit_description": "Limit the number of active bookings a booker can make for this event type",
|
||||
"recurring_event_doesnt_support_booker_booking_limit": "Recurring events don't support booker booking limit",
|
||||
"add_limit": "Add Limit",
|
||||
@@ -3636,8 +3637,8 @@
|
||||
"error_downloading_expense_log": "Error downloading expense log",
|
||||
"require_booker_email_verification": "Prevent Impersonation on Bookings",
|
||||
"require_booker_email_verification_description": "When enabled, anyone trying to book events using your email address must verify they own it via a one time code or be logged in to prevent impersonation",
|
||||
"offer_to_reschedule_last_booking": "Offer to reschedule last active booking to chosen time slot",
|
||||
"booker_limit_exceeded_error": "Booker maximum active booking limit exceeded",
|
||||
"reschedule_last_booking_offer": "Offer to reschedule the last booking to the new time slot",
|
||||
"booker_upcoming_limit_reached": "You've reached the limit of {{count}} upcoming bookings for this event type",
|
||||
"booker_limit_exceeded_error_reschedule": "You already have a booking for this event on {{date}}. Would you like to reschedule to the new selected time?",
|
||||
"duration_min_error": "Duration must be at least {{min}} minute(s)",
|
||||
"duration_max_error": "Duration cannot exceed {{max}} minutes (24 hours)",
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { createMocks } from "node-mocks-http";
|
||||
|
||||
import type {
|
||||
CustomNextApiRequest,
|
||||
CustomNextApiResponse,
|
||||
} from "@calcom/features/bookings/lib/handleNewBooking/test/fresh-booking.test";
|
||||
import type { CustomNextApiRequest, CustomNextApiResponse } from "./types";
|
||||
|
||||
export function createMockNextJsRequest(...args: Parameters<typeof createMocks>) {
|
||||
return createMocks<CustomNextApiRequest, CustomNextApiResponse>(...args);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { Request, Response } from "express";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export type CustomNextApiRequest = NextApiRequest & Request;
|
||||
|
||||
export type CustomNextApiResponse = NextApiResponse & Response;
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 161 KiB |
@@ -140,6 +140,7 @@
|
||||
"self-hosting/deployments/azure",
|
||||
"self-hosting/deployments/elestio",
|
||||
"self-hosting/deployments/gcp",
|
||||
"self-hosting/deployments/northflank",
|
||||
"self-hosting/deployments/railway",
|
||||
"self-hosting/deployments/render",
|
||||
"self-hosting/deployments/vercel"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: "Northflank"
|
||||
icon: "square-n"
|
||||
---
|
||||
|
||||
You can deploy Cal.com on Northflank using the button below.
|
||||
The team at Northflank also have a [detailed blog post](https://northflank.com/guides/deploy-calcom-with-northflank) on deploying Cal.com on their platform.
|
||||
|
||||
## One Click Deployment
|
||||
|
||||
<a href="https://northflank.com/stacks/deploy-calcom" target="_blank">
|
||||
<img src="/images/deploy-northflank.svg" noZoom alt="Deploy on Northflank" />
|
||||
</a>
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "calcom-monorepo",
|
||||
"version": "0.0.0",
|
||||
"version": "5.8.2",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { TFunction } from "i18next";
|
||||
|
||||
export interface IBookingRedirect {
|
||||
language: TFunction;
|
||||
fromEmail: string;
|
||||
eventOwner: string;
|
||||
toEmail: string;
|
||||
toName: string;
|
||||
oldDates?: string;
|
||||
dates: string;
|
||||
action: "add" | "update" | "cancel";
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { TFunction } from "i18next";
|
||||
|
||||
export type OrganizationCreation = {
|
||||
language: TFunction;
|
||||
from: string;
|
||||
to: string;
|
||||
ownerNewUsername: string;
|
||||
ownerOldUsername: string | null;
|
||||
orgDomain: string;
|
||||
orgName: string;
|
||||
prevLink: string | null;
|
||||
newLink: string;
|
||||
};
|
||||
|
||||
export type EmailVerifyCode = {
|
||||
language: TFunction;
|
||||
user: {
|
||||
name?: string | null;
|
||||
email: string;
|
||||
};
|
||||
verificationEmailCode: string;
|
||||
isVerifyingEmail?: boolean;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
export function getFormattedDate(calEvent: CalendarEvent, attendee: Person): string {
|
||||
const inviteeTimeFormat = calEvent.organizer.timeFormat || TimeFormat.TWELVE_HOUR;
|
||||
const timezone = attendee.timeZone;
|
||||
const locale = attendee.language.locale;
|
||||
const t = attendee.language.translate;
|
||||
|
||||
const getFormattedRecipientTime = (time: string, format: string) => {
|
||||
return dayjs(time).tz(timezone).locale(locale).format(format);
|
||||
};
|
||||
|
||||
const getInviteeStart = (format: string) => {
|
||||
return getFormattedRecipientTime(calEvent.startTime, format);
|
||||
};
|
||||
|
||||
const getInviteeEnd = (format: string) => {
|
||||
return getFormattedRecipientTime(calEvent.endTime, format);
|
||||
};
|
||||
|
||||
return `${getInviteeStart(inviteeTimeFormat)} - ${getInviteeEnd(inviteeTimeFormat)}, ${t(
|
||||
getInviteeStart("dddd").toLowerCase()
|
||||
)}, ${t(getInviteeStart("MMMM").toLowerCase())} ${getInviteeStart("D, YYYY")}`;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { TFunction } from "i18next";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
export type TeamInvite = {
|
||||
language: TFunction;
|
||||
from: string;
|
||||
to: string;
|
||||
teamName: string;
|
||||
joinLink: string;
|
||||
isCalcomMember: boolean;
|
||||
/**
|
||||
* We ideally should have a separate email for auto-join(when a user is automatically accepted into a team/org), but we don't have one yet.
|
||||
*/
|
||||
isAutoJoin: boolean;
|
||||
isOrg: boolean;
|
||||
parentTeamName: string | undefined;
|
||||
isExistingUserMovedToOrg: boolean;
|
||||
prevLink: string | null;
|
||||
newLink: string | null;
|
||||
};
|
||||
|
||||
export function getTypeOfInvite(teamInviteEvent: TeamInvite) {
|
||||
if (teamInviteEvent.isOrg) {
|
||||
return "TO_ORG";
|
||||
}
|
||||
|
||||
if (teamInviteEvent.parentTeamName) {
|
||||
return "TO_SUBTEAM";
|
||||
}
|
||||
|
||||
if (teamInviteEvent.isAutoJoin) {
|
||||
throw new Error("Auto-join is not supported for regular teams");
|
||||
}
|
||||
|
||||
return "TO_REGULAR_TEAM";
|
||||
}
|
||||
|
||||
export const getSubject = (teamInviteEvent: TeamInvite) => {
|
||||
const typeOfInvite = getTypeOfInvite(teamInviteEvent);
|
||||
const type = teamInviteEvent.isAutoJoin ? "added" : "invited";
|
||||
const variables = {
|
||||
user: teamInviteEvent.from,
|
||||
team: teamInviteEvent.teamName,
|
||||
appName: APP_NAME,
|
||||
parentTeamName: teamInviteEvent.parentTeamName,
|
||||
entity: teamInviteEvent.language(teamInviteEvent.isOrg ? "organization" : "team").toLowerCase(),
|
||||
};
|
||||
|
||||
if (typeOfInvite === "TO_ORG") {
|
||||
return teamInviteEvent.language(`email_team_invite|subject|${type}_to_org`, variables);
|
||||
}
|
||||
|
||||
if (typeOfInvite === "TO_SUBTEAM") {
|
||||
return teamInviteEvent.language(`email_team_invite|subject|${type}_to_subteam`, variables);
|
||||
}
|
||||
|
||||
return teamInviteEvent.language(`email_team_invite|subject|${type}_to_regular_team`, variables);
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import AttendeeScheduledEmailClass from "../../templates/attendee-rescheduled-email";
|
||||
import { getFormattedDate } from "../../lib/utils/date-formatting";
|
||||
import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
||||
|
||||
export const AttendeeRequestEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => {
|
||||
const date = new AttendeeScheduledEmailClass(props.calEvent, props.attendee).getFormattedDate();
|
||||
const date = getFormattedDate(props.calEvent, props.attendee);
|
||||
|
||||
return (
|
||||
<AttendeeScheduledEmail
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IBookingRedirect } from "../../templates/booking-redirect-notification";
|
||||
import type { IBookingRedirect } from "../../lib/types/booking-redirect-types";
|
||||
import { BaseEmailHtml } from "../components";
|
||||
|
||||
export const BookingRedirectEmailNotification = (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ServerTrans from "@calcom/lib/components/ServerTrans";
|
||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
import type { OrganizationCreation } from "../../templates/organization-creation-email";
|
||||
import type { OrganizationCreation } from "../../lib/types/email-types";
|
||||
import { V2BaseEmailHtml } from "../components";
|
||||
|
||||
export const OrganizationCreationEmail = (
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { TFunction } from "i18next";
|
||||
import ServerTrans from "@calcom/lib/components/ServerTrans";
|
||||
import { APP_NAME, WEBAPP_URL, IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
|
||||
import { getSubject, getTypeOfInvite } from "../../templates/team-invite-email";
|
||||
import { getSubject, getTypeOfInvite } from "../../lib/utils/team-invite-utils";
|
||||
import { V2BaseEmailHtml, CallToAction } from "../components";
|
||||
|
||||
type TeamInvite = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { APP_NAME, SENDER_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
|
||||
|
||||
import type { EmailVerifyCode } from "../../templates/attendee-verify-email";
|
||||
import type { EmailVerifyCode } from "../../lib/types/email-types";
|
||||
import { BaseEmailHtml } from "../components";
|
||||
|
||||
export const VerifyEmailByCode = (
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
||||
|
||||
import { APP_NAME, COMPANY_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export type EmailVerifyLink = {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
||||
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export type OrganizationNotification = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class AttendeeAddGuestsEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class AttendeeAwaitingPaymentEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class AttendeeCancelledEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class AttendeeCancelledSeatEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class AttendeeDailyVideoDownloadRecordingEmail extends BaseEmail {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class AttendeeDailyVideoDownloadTranscriptEmail extends BaseEmail {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class AttendeeDeclinedEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class AttendeeLocationChangeEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class AttendeeRequestEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -6,8 +6,8 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class AttendeeScheduledEmail extends BaseEmail {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class AttendeeUpdatedEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import type { TFunction } from "i18next";
|
||||
|
||||
import { APP_NAME, COMPANY_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import type { EmailVerifyCode } from "../lib/types/email-types";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export type EmailVerifyCode = {
|
||||
language: TFunction;
|
||||
user: {
|
||||
name?: string | null;
|
||||
email: string;
|
||||
};
|
||||
verificationEmailCode: string;
|
||||
isVerifyingEmail?: boolean;
|
||||
};
|
||||
export type { EmailVerifyCode } from "../lib/types/email-types";
|
||||
|
||||
export default class AttendeeVerifyEmail extends BaseEmail {
|
||||
verifyAccountInput: EmailVerifyCode;
|
||||
|
||||
@@ -2,8 +2,8 @@ import { getManageLink } from "@calcom/lib/CalEventParser";
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerScheduledEmail {
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import type { TFunction } from "i18next";
|
||||
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import type { IBookingRedirect } from "../lib/types/booking-redirect-types";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export interface IBookingRedirect {
|
||||
language: TFunction;
|
||||
fromEmail: string;
|
||||
eventOwner: string;
|
||||
toEmail: string;
|
||||
toName: string;
|
||||
oldDates?: string;
|
||||
dates: string;
|
||||
action: "add" | "update" | "cancel";
|
||||
}
|
||||
export type { IBookingRedirect } from "../lib/types/booking-redirect-types";
|
||||
|
||||
export default class BookingRedirectNotification extends BaseEmail {
|
||||
bookingRedirect: IBookingRedirect;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class BrokenIntegrationEmail extends BaseEmail {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
||||
|
||||
import { APP_NAME, COMPANY_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export type ChangeOfEmailVerifyLink = {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
||||
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class CreditBalanceLimitReachedEmail extends BaseEmail {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
||||
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class CreditBalanceLowWarningEmail extends BaseEmail {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
||||
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class DisabledAppEmail extends BaseEmail {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export interface Feedback {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
||||
|
||||
import { APP_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export type PasswordReset = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { APP_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import type { MonthlyDigestEmailData } from "../src/templates/MonthlyDigestEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
|
||||
export default class NoShowFeeChargedEmail extends AttendeeScheduledEmail {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
||||
|
||||
import { APP_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export type OrgAutoInvite = {
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
import type { TFunction } from "i18next";
|
||||
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import type { OrganizationCreation } from "../lib/types/email-types";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export type OrganizationCreation = {
|
||||
language: TFunction;
|
||||
from: string;
|
||||
to: string;
|
||||
ownerNewUsername: string;
|
||||
ownerOldUsername: string | null;
|
||||
orgDomain: string;
|
||||
orgName: string;
|
||||
prevLink: string | null;
|
||||
newLink: string;
|
||||
};
|
||||
export type { OrganizationCreation } from "../lib/types/email-types";
|
||||
|
||||
export default class OrganizationCreationEmail extends BaseEmail {
|
||||
organizationCreationEvent: OrganizationCreation;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
export default class OrganizerAddGuestsEmail extends OrganizerScheduledEmail {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
export default class OrganizerCancelledEmail extends OrganizerScheduledEmail {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
import type { Reassigned } from "./organizer-scheduled-email";
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class OrganizerDailyVideoDownloadRecordingEmail extends BaseEmail {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class OrganizerDailyVideoDownloadTranscriptEmail extends BaseEmail {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
export default class OrganizerLocationChangeEmail extends OrganizerScheduledEmail {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
export default class OrganizerPaymentRefundFailedEmail extends OrganizerScheduledEmail {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
import type { Reassigned } from "./organizer-scheduled-email";
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerRequestEmail from "./organizer-request-email";
|
||||
|
||||
export default class OrganizerRequestReminderEmail extends OrganizerRequestEmail {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
export default class OrganizerRequestedToRescheduleEmail extends OrganizerScheduledEmail {
|
||||
|
||||
@@ -2,8 +2,8 @@ import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail {
|
||||
|
||||
@@ -7,9 +7,9 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile from "../lib/generateIcsFile";
|
||||
import { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export type Reassigned = { name: string | null; email: string; reason?: string; byUser?: string };
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
||||
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class SlugReplacementEmail extends BaseEmail {
|
||||
|
||||
@@ -1,65 +1,10 @@
|
||||
import type { TFunction } from "i18next";
|
||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { APP_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import { getSubject, type TeamInvite } from "../lib/utils/team-invite-utils";
|
||||
import renderEmail from "../src/renderEmail";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export type TeamInvite = {
|
||||
language: TFunction;
|
||||
from: string;
|
||||
to: string;
|
||||
teamName: string;
|
||||
joinLink: string;
|
||||
isCalcomMember: boolean;
|
||||
/**
|
||||
* We ideally should have a separate email for auto-join(when a user is automatically accepted into a team/org), but we don't have one yet.
|
||||
*/
|
||||
isAutoJoin: boolean;
|
||||
isOrg: boolean;
|
||||
parentTeamName: string | undefined;
|
||||
isExistingUserMovedToOrg: boolean;
|
||||
prevLink: string | null;
|
||||
newLink: string | null;
|
||||
};
|
||||
|
||||
export function getTypeOfInvite(teamInviteEvent: TeamInvite) {
|
||||
if (teamInviteEvent.isOrg) {
|
||||
return "TO_ORG";
|
||||
}
|
||||
|
||||
if (teamInviteEvent.parentTeamName) {
|
||||
return "TO_SUBTEAM";
|
||||
}
|
||||
|
||||
if (teamInviteEvent.isAutoJoin) {
|
||||
throw new Error("Auto-join is not supported for regular teams");
|
||||
}
|
||||
|
||||
return "TO_REGULAR_TEAM";
|
||||
}
|
||||
|
||||
export const getSubject = (teamInviteEvent: TeamInvite) => {
|
||||
const typeOfInvite = getTypeOfInvite(teamInviteEvent);
|
||||
const type = teamInviteEvent.isAutoJoin ? "added" : "invited";
|
||||
const variables = {
|
||||
user: teamInviteEvent.from,
|
||||
team: teamInviteEvent.teamName,
|
||||
appName: APP_NAME,
|
||||
parentTeamName: teamInviteEvent.parentTeamName,
|
||||
entity: teamInviteEvent.language(teamInviteEvent.isOrg ? "organization" : "team").toLowerCase(),
|
||||
};
|
||||
|
||||
if (typeOfInvite === "TO_ORG") {
|
||||
return teamInviteEvent.language(`email_team_invite|subject|${type}_to_org`, variables);
|
||||
}
|
||||
|
||||
if (typeOfInvite === "TO_SUBTEAM") {
|
||||
return teamInviteEvent.language(`email_team_invite|subject|${type}_to_subteam`, variables);
|
||||
}
|
||||
|
||||
return teamInviteEvent.language(`email_team_invite|subject|${type}_to_regular_team`, variables);
|
||||
};
|
||||
export { getSubject, getTypeOfInvite, type TeamInvite } from "../lib/utils/team-invite-utils";
|
||||
|
||||
export default class TeamInviteEmail extends BaseEmail {
|
||||
teamInviteEvent: TeamInvite;
|
||||
|
||||
@@ -3,9 +3,9 @@ import type { Dispatch, SetStateAction } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import z from "zod";
|
||||
|
||||
import { LastUsed, useLastUsed } from "@calcom/features/auth/lib/hooks/useLastUsed";
|
||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
import { emailRegex } from "@calcom/lib/emailSchema";
|
||||
import { LastUsed, useLastUsed } from "@calcom/lib/hooks/useLastUsed";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { ButtonProps } from "@calcom/ui/components/button";
|
||||
|
||||
@@ -301,6 +301,7 @@ const getError = ({
|
||||
const error = dataError;
|
||||
|
||||
let date = "";
|
||||
let count = 0;
|
||||
|
||||
if (error.message === ErrorCode.BookerLimitExceededReschedule) {
|
||||
const formattedDate = formatEventFromTime({
|
||||
@@ -312,9 +313,16 @@ const getError = ({
|
||||
date = `${formattedDate.date} ${formattedDate.time}`;
|
||||
}
|
||||
|
||||
if (error.message === ErrorCode.BookerLimitExceeded && error.data?.count) {
|
||||
count = error.data.count;
|
||||
}
|
||||
|
||||
const messageKey =
|
||||
error.message === ErrorCode.BookerLimitExceeded ? "booker_upcoming_limit_reached" : error.message;
|
||||
|
||||
return error?.message ? (
|
||||
<>
|
||||
{responseVercelIdHeader ?? ""} {t(error.message, { date })}
|
||||
{responseVercelIdHeader ?? ""} {t(messageKey, { date, count })}
|
||||
</>
|
||||
) : (
|
||||
<>{t("can_you_try_again")}</>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useMemo, useEffect } from "react";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||
import { useAvailableTimeSlots } from "@calcom/features/bookings/Booker/components/hooks/useAvailableTimeSlots";
|
||||
import { useBookerTime } from "@calcom/features/bookings/Booker/components/hooks/useBookerTime";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { Calendar } from "@calcom/features/calendars/weeklyview";
|
||||
import type { CalendarEvent } from "@calcom/features/calendars/weeklyview/types/events";
|
||||
@@ -31,6 +32,7 @@ export const LargeCalendar = ({
|
||||
const overlayEvents = useOverlayCalendarStore((state) => state.overlayBusyDates);
|
||||
const displayOverlay =
|
||||
getQueryParam("overlayCalendar") === "true" || localStorage?.getItem("overlayCalendarSwitchDefault");
|
||||
const { timezone } = useBookerTime();
|
||||
|
||||
const eventDuration = selectedEventDuration || event?.data?.length || 30;
|
||||
|
||||
@@ -43,7 +45,6 @@ export const LargeCalendar = ({
|
||||
|
||||
// HACK: force rerender when overlay events change
|
||||
// Sine we dont use react router here we need to force rerender (ATOM SUPPORT)
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
useEffect(() => {}, [displayOverlay]);
|
||||
|
||||
const overlayEventsForDate = useMemo(() => {
|
||||
@@ -75,6 +76,7 @@ export const LargeCalendar = ({
|
||||
gridCellsPerHour={60 / eventDuration}
|
||||
hoverEventDuration={eventDuration}
|
||||
hideHeader
|
||||
timezone={timezone}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createContainer } from "@calcom/lib/di/di";
|
||||
|
||||
import {
|
||||
type BookingCancelService,
|
||||
moduleLoader as bookingCancelServiceModule,
|
||||
} from "../modules/BookingCancelService.module";
|
||||
|
||||
const bookingCancelServiceContainer = createContainer();
|
||||
|
||||
export function getBookingCancelService(): BookingCancelService {
|
||||
bookingCancelServiceModule.loadModule(bookingCancelServiceContainer);
|
||||
|
||||
return bookingCancelServiceContainer.get<BookingCancelService>(bookingCancelServiceModule.token);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { BookingCancelService } from "@calcom/features/bookings/lib/handleCancelBooking";
|
||||
import { bindModuleToClassOnToken, createModule } from "@calcom/lib/di/di";
|
||||
import { DI_TOKENS } from "@calcom/lib/di/tokens";
|
||||
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
|
||||
|
||||
const thisModule = createModule();
|
||||
const token = DI_TOKENS.BOOKING_CANCEL_SERVICE;
|
||||
const moduleToken = DI_TOKENS.BOOKING_CANCEL_SERVICE_MODULE;
|
||||
const loadModule = bindModuleToClassOnToken({
|
||||
module: thisModule,
|
||||
moduleToken,
|
||||
token,
|
||||
classs: BookingCancelService,
|
||||
depsMap: {
|
||||
prismaClient: prismaModuleLoader,
|
||||
},
|
||||
});
|
||||
|
||||
export const moduleLoader = {
|
||||
token,
|
||||
loadModule,
|
||||
};
|
||||
|
||||
export type { BookingCancelService };
|
||||
@@ -1,6 +1,7 @@
|
||||
import { InstantBookingCreateService } from "@calcom/features/bookings/lib/service/InstantBookingCreateService";
|
||||
import { createModule, bindModuleToClassOnToken } from "@calcom/features/di/di";
|
||||
import { DI_TOKENS } from "@calcom/features/di/tokens";
|
||||
import { InstantBookingCreateService } from "@calcom/features/instant-meeting/handleInstantMeeting";
|
||||
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
|
||||
|
||||
export const instantBookingCreateServiceModule = createModule();
|
||||
const token = DI_TOKENS.INSTANT_BOOKING_CREATE_SERVICE;
|
||||
@@ -10,7 +11,10 @@ const loadModule = bindModuleToClassOnToken({
|
||||
moduleToken,
|
||||
token,
|
||||
classs: InstantBookingCreateService,
|
||||
depsMap: {},
|
||||
depsMap: {
|
||||
// TODO: In a followup PR, we aim to remove prisma dependency and instead inject the repositories as dependencies.
|
||||
prismaClient: prismaModuleLoader,
|
||||
},
|
||||
});
|
||||
|
||||
export type { InstantBookingCreateService };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RecurringBookingService } from "@calcom/features/bookings/lib/handleNewRecurringBooking";
|
||||
import { RecurringBookingService } from "@calcom/features/bookings/lib/service/RecurringBookingService";
|
||||
|
||||
import { createModule, bindModuleToClassOnToken } from "@calcom/features/di/di";
|
||||
import { DI_TOKENS } from "@calcom/features/di/tokens";
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { RegularBookingService } from "@calcom/features/bookings/lib/handleNewBooking";
|
||||
|
||||
import { RegularBookingService } from "@calcom/features/bookings/lib/service/RegularBookingService";
|
||||
import { bindModuleToClassOnToken, createModule } from "@calcom/features/di/di";
|
||||
import { moduleLoader as attributeRepositoryModuleLoader } from "@calcom/features/di/modules/Attribute";
|
||||
import { moduleLoader as bookingRepositoryModuleLoader } from "@calcom/features/di/modules/Booking";
|
||||
import { moduleLoader as cacheModuleLoader } from "@calcom/features/di/modules/Cache";
|
||||
import { moduleLoader as checkBookingAndDurationLimitsModuleLoader } from "@calcom/features/di/modules/CheckBookingAndDurationLimits";
|
||||
import { moduleLoader as checkBookingLimitsModuleLoader } from "@calcom/features/di/modules/CheckBookingLimits";
|
||||
import { moduleLoader as featuresRepositoryModuleLoader } from "@calcom/features/di/modules/Features";
|
||||
import { moduleLoader as hostRepositoryModuleLoader } from "@calcom/features/di/modules/Host";
|
||||
|
||||
import { moduleLoader as luckyUserServiceModuleLoader } from "@calcom/features/di/modules/LuckyUser";
|
||||
import { moduleLoader as oooRepositoryModuleLoader } from "@calcom/features/di/modules/Ooo";
|
||||
import { moduleLoader as userRepositoryModuleLoader } from "@calcom/features/di/modules/User";
|
||||
import { DI_TOKENS } from "@calcom/features/di/tokens";
|
||||
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
|
||||
@@ -22,17 +19,13 @@ const loadModule = bindModuleToClassOnToken({
|
||||
token,
|
||||
classs: RegularBookingService,
|
||||
depsMap: {
|
||||
// TODO: In a followup PR, we aim to remove prisma dependency and instead inject the repositories as dependencies.
|
||||
prismaClient: prismaModuleLoader,
|
||||
cacheService: cacheModuleLoader,
|
||||
checkBookingAndDurationLimitsService: checkBookingAndDurationLimitsModuleLoader,
|
||||
prismaClient: prismaModuleLoader,
|
||||
bookingRepository: bookingRepositoryModuleLoader,
|
||||
featuresRepository: featuresRepositoryModuleLoader,
|
||||
checkBookingLimitsService: checkBookingLimitsModuleLoader,
|
||||
luckyUserService: luckyUserServiceModuleLoader,
|
||||
hostRepository: hostRepositoryModuleLoader,
|
||||
oooRepository: oooRepositoryModuleLoader,
|
||||
userRepository: userRepositoryModuleLoader,
|
||||
attributeRepository: attributeRepositoryModuleLoader,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,4 +5,6 @@ export const BOOKING_DI_TOKENS = {
|
||||
RECURRING_BOOKING_SERVICE_MODULE: Symbol("RecurringBookingServiceModule"),
|
||||
INSTANT_BOOKING_CREATE_SERVICE: Symbol("InstantBookingCreateService"),
|
||||
INSTANT_BOOKING_CREATE_SERVICE_MODULE: Symbol("InstantBookingCreateServiceModule"),
|
||||
BOOKING_CANCEL_SERVICE: Symbol("BookingCancelService"),
|
||||
BOOKING_CANCEL_SERVICE_MODULE: Symbol("BookingCancelServiceModule"),
|
||||
};
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import { getRoutedTeamMemberIdsFromSearchParams } from "@calcom/lib/bookings/get
|
||||
import { parseRecurringDates } from "@calcom/lib/parse-dates";
|
||||
import type { RoutingFormSearchParams } from "@calcom/platform-types";
|
||||
|
||||
import type { BookerEvent, BookingCreateBody, RecurringBookingCreateBody } from "../../types";
|
||||
import type { Tracking } from "../handleNewBooking/types";
|
||||
import type { BookerEvent, BookingCreateBody, RecurringBookingCreateBody } from "../../../types";
|
||||
import type { Tracking } from "../../handleNewBooking/types";
|
||||
|
||||
export type BookingOptions = {
|
||||
values: Record<string, unknown>;
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Domain types for Booking Cancellation Service
|
||||
* These types are framework-agnostic and contain only the data required for booking cancellation operations
|
||||
*/
|
||||
import type { z } from "zod";
|
||||
|
||||
import type { bookingCancelInput } from "@calcom/prisma/zod-utils";
|
||||
|
||||
/**
|
||||
* The booking data required for cancellation
|
||||
*/
|
||||
export type CancelRegularBookingData = z.infer<typeof bookingCancelInput>;
|
||||
|
||||
/**
|
||||
* Additional metadata for booking cancellation, typically used for platform integrations
|
||||
*/
|
||||
export type CancelBookingMeta = {
|
||||
userId?: number;
|
||||
platformClientId?: string;
|
||||
platformRescheduleUrl?: string;
|
||||
platformCancelUrl?: string;
|
||||
platformBookingUrl?: string;
|
||||
arePlatformEmailsEnabled?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* The response returned after attempting to cancel a booking
|
||||
*/
|
||||
export type HandleCancelBookingResponse = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
onlyRemovedAttendee: boolean;
|
||||
bookingId: number;
|
||||
bookingUid: string;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const instantMeetingSubscriptionSchema = z.object({
|
||||
endpoint: z.string(),
|
||||
expirationTime: z.any().optional(),
|
||||
keys: z.object({
|
||||
auth: z.string(),
|
||||
p256dh: z.string(),
|
||||
}),
|
||||
});
|
||||
+5
-6
@@ -7,9 +7,8 @@ import type getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBo
|
||||
import type { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import type { ExtendedBookingCreateBody } from "../bookingCreateBodySchema";
|
||||
import type { RegularBookingService } from "../handleNewBooking";
|
||||
import type { RegularBookingService } from "../service/RegularBookingService";
|
||||
|
||||
export type { BookingCreateBody } from "../bookingCreateBodySchema";
|
||||
export type BookingDataSchemaGetter = typeof getBookingDataSchema | typeof getBookingDataSchemaForApi;
|
||||
|
||||
export type CreateRegularBookingData = ExtendedBookingCreateBody;
|
||||
@@ -44,14 +43,14 @@ export type BookingHandlerInput = {
|
||||
bookingData: CreateRegularBookingData;
|
||||
} & CreateBookingMeta;
|
||||
|
||||
// TODO: In a followup PR, we working on defining the type here itself instead of inferring it.
|
||||
export type RegularBookingCreateResult = Awaited<ReturnType<RegularBookingService["createBooking"]>>;
|
||||
|
||||
export type InstantBookingCreateResult = {
|
||||
message: string;
|
||||
message: "Success";
|
||||
meetingTokenId: number;
|
||||
bookingId: number;
|
||||
bookingUid: string;
|
||||
expires: Date;
|
||||
userId: number | null;
|
||||
};
|
||||
|
||||
// TODO: In a followup PR, we working on defining the type here itself instead of inferring it.
|
||||
export type RegularBookingCreateResult = Awaited<ReturnType<RegularBookingService["createBooking"]>>;
|
||||
|
||||
@@ -5,10 +5,13 @@ import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApi
|
||||
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { sendCancelledEmailsAndSMS } from "@calcom/emails";
|
||||
import EventManager from "@calcom/features/bookings/lib/EventManager";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation";
|
||||
import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund";
|
||||
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
|
||||
import { sendCancelledReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
|
||||
import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import {
|
||||
@@ -17,8 +20,6 @@ import {
|
||||
} from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||
import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
|
||||
import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import EventManager from "@calcom/features/bookings/lib/EventManager";
|
||||
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
|
||||
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
|
||||
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
@@ -27,10 +28,10 @@ import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
|
||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
// TODO: Prisma import would be used from DI in a followup PR when we remove `handler` export
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Prisma, WorkflowReminder } from "@calcom/prisma/client";
|
||||
import type { Prisma, PrismaClient, WorkflowReminder } from "@calcom/prisma/client";
|
||||
import type { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { bookingMetadataSchema, bookingCancelInput } from "@calcom/prisma/zod-utils";
|
||||
@@ -38,10 +39,16 @@ import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
|
||||
import { getAllWorkflowsFromEventType } from "@calcom/trpc/server/routers/viewer/workflows/util";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import type {
|
||||
CancelRegularBookingData,
|
||||
CancelBookingMeta,
|
||||
HandleCancelBookingResponse,
|
||||
} from "./dto/BookingCancel";
|
||||
import { getAllCredentialsIncludeServiceAccountKey } from "./getAllCredentialsForUsersOnEvent/getAllCredentials";
|
||||
import { getBookingToDelete } from "./getBookingToDelete";
|
||||
import { handleInternalNote } from "./handleInternalNote";
|
||||
import cancelAttendeeSeat from "./handleSeats/cancel/cancelAttendeeSeat";
|
||||
import type { IBookingCancelService } from "./interfaces/IBookingCancelService";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["handleCancelBooking"] });
|
||||
|
||||
@@ -60,14 +67,6 @@ export type CancelBookingInput = {
|
||||
bookingData: z.infer<typeof bookingCancelInput>;
|
||||
} & PlatformParams;
|
||||
|
||||
export type HandleCancelBookingResponse = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
onlyRemovedAttendee: boolean;
|
||||
bookingId: number;
|
||||
bookingUid: string;
|
||||
};
|
||||
|
||||
async function handler(input: CancelBookingInput) {
|
||||
const body = input.bookingData;
|
||||
const {
|
||||
@@ -118,7 +117,12 @@ async function handler(input: CancelBookingInput) {
|
||||
const isCancellationUserHost =
|
||||
bookingToDelete.userId == userId || bookingToDelete.user.email === cancelledBy;
|
||||
|
||||
if (!platformClientId && !cancellationReason?.trim() && isCancellationUserHost && !skipCancellationReasonValidation) {
|
||||
if (
|
||||
!platformClientId &&
|
||||
!cancellationReason?.trim() &&
|
||||
isCancellationUserHost &&
|
||||
!skipCancellationReasonValidation
|
||||
) {
|
||||
throw new HttpError({
|
||||
statusCode: 400,
|
||||
message: "Cancellation reason is required when you are the host",
|
||||
@@ -588,4 +592,24 @@ async function handler(input: CancelBookingInput) {
|
||||
} satisfies HandleCancelBookingResponse;
|
||||
}
|
||||
|
||||
type BookingCancelServiceDependencies = {
|
||||
prismaClient: PrismaClient;
|
||||
};
|
||||
/**
|
||||
* Takes care of cancelling bookings. This includes regular bookings, recurring bookings, seated bookings, etc.
|
||||
* Handles both individual booking cancellations and bulk cancellations for recurring events.
|
||||
*/
|
||||
export class BookingCancelService implements IBookingCancelService {
|
||||
constructor(private readonly deps: BookingCancelServiceDependencies) {}
|
||||
|
||||
async cancelBooking(input: { bookingData: CancelRegularBookingData; bookingMeta?: CancelBookingMeta }) {
|
||||
const cancelBookingInput: CancelBookingInput = {
|
||||
bookingData: input.bookingData,
|
||||
...(input.bookingMeta || {}),
|
||||
};
|
||||
// TODO: Deps to be passed to it later when we stop exporting handler
|
||||
return handler(cancelBookingInput);
|
||||
}
|
||||
}
|
||||
|
||||
export default handler;
|
||||
|
||||
+3
-1
@@ -61,7 +61,9 @@ const checkActiveBookingsLimit = async ({
|
||||
|
||||
if (bookingsCount >= maxActiveBookingsPerBooker) {
|
||||
log.warn(`Maximum booking limit reached for ${bookerEmail} for event type ${eventTypeId}`);
|
||||
throw new ErrorWithCode(ErrorCode.BookerLimitExceeded, ErrorCode.BookerLimitExceeded);
|
||||
throw new ErrorWithCode(ErrorCode.BookerLimitExceeded, ErrorCode.BookerLimitExceeded, {
|
||||
count: maxActiveBookingsPerBooker,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest";
|
||||
|
||||
import { BookingStatus, CreationSource } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildDryRunBooking } from "../../handleNewBooking";
|
||||
import { buildDryRunBooking } from "../../service/RegularBookingService";
|
||||
|
||||
vi.mock("@calcom/prisma", () => ({
|
||||
default: {}, // empty object as default export
|
||||
|
||||
+7
-7
@@ -3,7 +3,7 @@ import { vi, describe, it, expect, beforeEach } from "vitest";
|
||||
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildEventForTeamEventType } from "../../handleNewBooking";
|
||||
import { buildEventForTeamEventType } from "../../service/RegularBookingService";
|
||||
|
||||
vi.mock("@calcom/lib/server/i18n", () => ({
|
||||
getTranslation: vi.fn().mockResolvedValue("translated"),
|
||||
@@ -22,12 +22,12 @@ const withDestinationCalendarSpy = vi.fn().mockReturnThis();
|
||||
vi.mock("@calcom/features/CalendarEventBuilder", () => {
|
||||
return {
|
||||
CalendarEventBuilder: {
|
||||
fromEvent: vi.fn().mockImplementation((evt) => ({
|
||||
fromEvent: vi.fn().mockImplementation((_evt) => ({
|
||||
withDestinationCalendar: withDestinationCalendarSpy,
|
||||
withTeam: withTeamSpy,
|
||||
build: vi.fn().mockImplementation(() => ({
|
||||
destinationCalendar: [],
|
||||
team: {}, // <- you won’t use this result anyway
|
||||
team: {}, // <- you won't use this result anyway
|
||||
})),
|
||||
})),
|
||||
},
|
||||
@@ -72,7 +72,7 @@ describe("buildEventForTeamEventType", () => {
|
||||
});
|
||||
|
||||
it("filters out the organizer", async () => {
|
||||
const result = await buildEventForTeamEventType({
|
||||
await buildEventForTeamEventType({
|
||||
existingEvent: {},
|
||||
users: [baseUser({ email: "organizer@example.com" })],
|
||||
organizerUser: { email: "organizer@example.com" },
|
||||
@@ -80,7 +80,7 @@ describe("buildEventForTeamEventType", () => {
|
||||
});
|
||||
|
||||
const teamArgs = withTeamSpy.mock.calls[0][0];
|
||||
const memberEmails = teamArgs.members.map((m: any) => m.email);
|
||||
const memberEmails = teamArgs.members.map((m: { email: string }) => m.email);
|
||||
|
||||
expect(memberEmails).not.toContain("organizer@example.com");
|
||||
});
|
||||
@@ -123,7 +123,7 @@ describe("buildEventForTeamEventType", () => {
|
||||
});
|
||||
|
||||
const teamArgs = withTeamSpy.mock.calls[0][0];
|
||||
const memberEmails = teamArgs.members.map((m: any) => m.email);
|
||||
const memberEmails = teamArgs.members.map((m: { email: string }) => m.email);
|
||||
|
||||
expect(memberEmails).toContain("fixed@example.com");
|
||||
expect(memberEmails).toContain("nonfixed@example.com");
|
||||
@@ -142,7 +142,7 @@ describe("buildEventForTeamEventType", () => {
|
||||
});
|
||||
|
||||
const teamArgs = withTeamSpy.mock.calls[0][0];
|
||||
const memberEmails = teamArgs.members.map((m: any) => m.email);
|
||||
const memberEmails = teamArgs.members.map((m: { email: string }) => m.email);
|
||||
|
||||
expect(memberEmails).toContain("fixed@example.com");
|
||||
expect(memberEmails).toContain("nonfixed1@example.com");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user