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:
|
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`
|
- 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
|
- 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
|
## 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>
|
</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 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)
|
- [Setup postgres DB with render](https://render.com/docs/databases)
|
||||||
|
|
||||||
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
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.
|
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
|
### 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.
|
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 { addRequestId } from "./addRequestid";
|
||||||
import { captureErrors } from "./captureErrors";
|
import { captureErrors } from "./captureErrors";
|
||||||
import { captureUserId } from "./captureUserId";
|
import { captureUserId } from "./captureUserId";
|
||||||
import { checkIsInMaintenanceMode } from "./checkIsInMaintenanceMode";
|
|
||||||
import { extendRequest } from "./extendRequest";
|
import { extendRequest } from "./extendRequest";
|
||||||
import {
|
import {
|
||||||
HTTP_DELETE,
|
HTTP_DELETE,
|
||||||
@@ -26,7 +25,6 @@ const middleware = {
|
|||||||
HTTP_POST,
|
HTTP_POST,
|
||||||
HTTP_DELETE,
|
HTTP_DELETE,
|
||||||
addRequestId,
|
addRequestId,
|
||||||
checkIsInMaintenanceMode,
|
|
||||||
verifyApiKey,
|
verifyApiKey,
|
||||||
rateLimitApiKey,
|
rateLimitApiKey,
|
||||||
extendRequest,
|
extendRequest,
|
||||||
@@ -38,16 +36,14 @@ const middleware = {
|
|||||||
|
|
||||||
type Middleware = keyof typeof middleware;
|
type Middleware = keyof typeof middleware;
|
||||||
|
|
||||||
const middlewareOrder = [
|
const middlewareOrder: Middleware[] = [
|
||||||
// The order here, determines the order of execution
|
|
||||||
"checkIsInMaintenanceMode",
|
|
||||||
"extendRequest",
|
"extendRequest",
|
||||||
"captureErrors",
|
"captureErrors",
|
||||||
"verifyApiKey",
|
"verifyApiKey",
|
||||||
"rateLimitApiKey",
|
"rateLimitApiKey",
|
||||||
"addRequestId",
|
"addRequestId",
|
||||||
"captureUserId",
|
"captureUserId",
|
||||||
] as Middleware[]; // <-- Provide a list of middleware to call automatically
|
];
|
||||||
|
|
||||||
const withMiddleware = label(middleware, middlewareOrder);
|
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 { 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 { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository";
|
||||||
import { CacheService } from "@/lib/services/cache.service";
|
import { CacheService } from "@/lib/services/cache.service";
|
||||||
import { CheckBookingAndDurationLimitsService } from "@/lib/services/check-booking-and-duration-limits.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 { LuckyUserService } from "@/lib/services/lucky-user.service";
|
||||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
@@ -21,26 +16,16 @@ export class RegularBookingService extends BaseRegularBookingService {
|
|||||||
checkBookingAndDurationLimitsService: CheckBookingAndDurationLimitsService,
|
checkBookingAndDurationLimitsService: CheckBookingAndDurationLimitsService,
|
||||||
prismaWriteService: PrismaWriteService,
|
prismaWriteService: PrismaWriteService,
|
||||||
bookingRepository: PrismaBookingRepository,
|
bookingRepository: PrismaBookingRepository,
|
||||||
featuresRepository: PrismaFeaturesRepository,
|
|
||||||
checkBookingLimitsService: CheckBookingLimitsService,
|
|
||||||
luckyUserService: LuckyUserService,
|
luckyUserService: LuckyUserService,
|
||||||
hostRepository: PrismaHostRepository,
|
userRepository: PrismaUserRepository
|
||||||
oooRepository: PrismaOOORepository,
|
|
||||||
userRepository: PrismaUserRepository,
|
|
||||||
attributeRepository: PrismaAttributeRepository
|
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
cacheService,
|
cacheService,
|
||||||
checkBookingAndDurationLimitsService,
|
checkBookingAndDurationLimitsService,
|
||||||
prismaClient: prismaWriteService.prisma as unknown as PrismaClient,
|
prismaClient: prismaWriteService.prisma as unknown as PrismaClient,
|
||||||
bookingRepository,
|
bookingRepository,
|
||||||
featuresRepository,
|
|
||||||
checkBookingLimitsService,
|
|
||||||
luckyUserService,
|
luckyUserService,
|
||||||
hostRepository,
|
|
||||||
oooRepository,
|
|
||||||
userRepository,
|
userRepository,
|
||||||
attributeRepository,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-2
@@ -404,7 +404,7 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
|||||||
invalidWorkflow.steps = [
|
invalidWorkflow.steps = [
|
||||||
{
|
{
|
||||||
stepNumber: 1,
|
stepNumber: 1,
|
||||||
action: "sms_number",
|
action: "cal_ai_phone_call",
|
||||||
recipient: PHONE_NUMBER,
|
recipient: PHONE_NUMBER,
|
||||||
template: REMINDER,
|
template: REMINDER,
|
||||||
verifiedPhoneId: verifiedPhoneId,
|
verifiedPhoneId: verifiedPhoneId,
|
||||||
@@ -439,6 +439,18 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
|||||||
html: "<p>Reminder for your event {EVENT_NAME}.</p>",
|
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())
|
return request(app.getHttpServer())
|
||||||
.post(`${basePath}/routing-form`)
|
.post(`${basePath}/routing-form`)
|
||||||
@@ -459,12 +471,15 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(responseBody.data.trigger.type).toEqual(sampleCreateWorkflowRoutingFormDto.trigger.type);
|
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)?.id).toBeDefined();
|
||||||
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.sender).toEqual(
|
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.sender).toEqual(
|
||||||
"CalcomE2EStep1"
|
"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;
|
const trigger = sampleCreateWorkflowRoutingFormDto.trigger as OnFormSubmittedTriggerDto;
|
||||||
expect(responseBody.data.trigger?.type).toEqual(trigger.type);
|
expect(responseBody.data.trigger?.type).toEqual(trigger.type);
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import {
|
|||||||
EMAIL_ADDRESS,
|
EMAIL_ADDRESS,
|
||||||
EMAIL_ATTENDEE,
|
EMAIL_ATTENDEE,
|
||||||
FORM_ALLOWED_STEP_ACTIONS,
|
FORM_ALLOWED_STEP_ACTIONS,
|
||||||
|
SMS_NUMBER,
|
||||||
WorkflowEmailAddressStepDto,
|
WorkflowEmailAddressStepDto,
|
||||||
WorkflowEmailAttendeeStepDto,
|
WorkflowEmailAttendeeStepDto,
|
||||||
|
WorkflowPhoneNumberStepDto,
|
||||||
|
WorkflowPhoneAttendeeStepDto,
|
||||||
|
SMS_ATTENDEE,
|
||||||
} from "./workflow-step.input";
|
} from "./workflow-step.input";
|
||||||
import {
|
import {
|
||||||
RoutingFormWorkflowTriggerDto,
|
RoutingFormWorkflowTriggerDto,
|
||||||
@@ -85,6 +89,8 @@ export class CreateFormWorkflowDto {
|
|||||||
oneOf: [
|
oneOf: [
|
||||||
{ $ref: getSchemaPath(WorkflowEmailAddressStepDto) },
|
{ $ref: getSchemaPath(WorkflowEmailAddressStepDto) },
|
||||||
{ $ref: getSchemaPath(WorkflowEmailAttendeeStepDto) },
|
{ $ref: getSchemaPath(WorkflowEmailAttendeeStepDto) },
|
||||||
|
{ $ref: getSchemaPath(WorkflowPhoneAttendeeStepDto) },
|
||||||
|
{ $ref: getSchemaPath(WorkflowPhoneNumberStepDto) },
|
||||||
],
|
],
|
||||||
type: "array",
|
type: "array",
|
||||||
})
|
})
|
||||||
@@ -99,8 +105,15 @@ export class CreateFormWorkflowDto {
|
|||||||
subTypes: [
|
subTypes: [
|
||||||
{ value: WorkflowEmailAddressStepDto, name: EMAIL_ADDRESS },
|
{ value: WorkflowEmailAddressStepDto, name: EMAIL_ADDRESS },
|
||||||
{ value: WorkflowEmailAttendeeStepDto, name: EMAIL_ATTENDEE },
|
{ 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_ADDRESS,
|
||||||
EMAIL_ATTENDEE,
|
EMAIL_ATTENDEE,
|
||||||
FORM_ALLOWED_STEP_ACTIONS,
|
FORM_ALLOWED_STEP_ACTIONS,
|
||||||
|
SMS_ATTENDEE,
|
||||||
|
SMS_NUMBER,
|
||||||
UpdateEmailAddressWorkflowStepDto,
|
UpdateEmailAddressWorkflowStepDto,
|
||||||
UpdateEmailAttendeeWorkflowStepDto,
|
UpdateEmailAttendeeWorkflowStepDto,
|
||||||
UpdateEmailHostWorkflowStepDto,
|
UpdateEmailHostWorkflowStepDto,
|
||||||
@@ -75,6 +77,8 @@ export class UpdateFormWorkflowDto {
|
|||||||
oneOf: [
|
oneOf: [
|
||||||
{ $ref: getSchemaPath(UpdateEmailAddressWorkflowStepDto) },
|
{ $ref: getSchemaPath(UpdateEmailAddressWorkflowStepDto) },
|
||||||
{ $ref: getSchemaPath(UpdateEmailAttendeeWorkflowStepDto) },
|
{ $ref: getSchemaPath(UpdateEmailAttendeeWorkflowStepDto) },
|
||||||
|
{ $ref: getSchemaPath(UpdatePhoneAttendeeWorkflowStepDto) },
|
||||||
|
{ $ref: getSchemaPath(UpdatePhoneNumberWorkflowStepDto) },
|
||||||
],
|
],
|
||||||
type: "array",
|
type: "array",
|
||||||
})
|
})
|
||||||
@@ -90,8 +94,15 @@ export class UpdateFormWorkflowDto {
|
|||||||
subTypes: [
|
subTypes: [
|
||||||
{ value: UpdateEmailAddressWorkflowStepDto, name: EMAIL_ADDRESS },
|
{ value: UpdateEmailAddressWorkflowStepDto, name: EMAIL_ADDRESS },
|
||||||
{ value: UpdateEmailAttendeeWorkflowStepDto, name: EMAIL_ATTENDEE },
|
{ 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,
|
CAL_AI_PHONE_CALL,
|
||||||
] as const;
|
] 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 = {
|
export const STEP_ACTIONS_TO_ENUM = {
|
||||||
[EMAIL_HOST]: WorkflowActions.EMAIL_HOST,
|
[EMAIL_HOST]: WorkflowActions.EMAIL_HOST,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { MembershipRole } from "@calcom/prisma/enums";
|
|||||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
import { validStatuses } from "~/bookings/lib/validStatuses";
|
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({
|
const querySchema = z.object({
|
||||||
status: z.enum(validStatuses),
|
status: z.enum(validStatuses),
|
||||||
|
|||||||
@@ -26,11 +26,20 @@ async function handler(req: NextRequest) {
|
|||||||
cookieStore.delete("calcom.csrf_token");
|
cookieStore.delete("calcom.csrf_token");
|
||||||
|
|
||||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||||
|
|
||||||
const result = await handleCancelBooking({
|
const result = await handleCancelBooking({
|
||||||
bookingData,
|
bookingData,
|
||||||
userId: session?.user?.id || -1,
|
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;
|
const statusCode = result.success ? 200 : 400;
|
||||||
|
|
||||||
return NextResponse.json(result, { status: statusCode });
|
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 { SAMLLogin } from "@calcom/features/auth/SAMLLogin";
|
||||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
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 { HOSTED_CAL_FEATURES, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||||
import { emailRegex } from "@calcom/lib/emailSchema";
|
import { emailRegex } from "@calcom/lib/emailSchema";
|
||||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||||
import { LastUsed, useLastUsed } from "@calcom/lib/hooks/useLastUsed";
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { useTelemetry } from "@calcom/lib/hooks/useTelemetry";
|
import { useTelemetry } from "@calcom/lib/hooks/useTelemetry";
|
||||||
import { collectPageParameters, telemetryEventTypes } from "@calcom/lib/telemetry";
|
import { collectPageParameters, telemetryEventTypes } from "@calcom/lib/telemetry";
|
||||||
@@ -62,7 +62,7 @@ export default function Login({
|
|||||||
.string()
|
.string()
|
||||||
.min(1, `${t("error_required_field")}`)
|
.min(1, `${t("error_required_field")}`)
|
||||||
.regex(emailRegex, `${t("enter_valid_email")}`),
|
.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 other fields like totpCode
|
||||||
.passthrough();
|
.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;
|
: eventTypeRaw.users;
|
||||||
|
|
||||||
if (!eventTypeRaw.users.length) {
|
if (!eventTypeRaw.users.length) {
|
||||||
if (!eventTypeRaw.owner)
|
if (!eventTypeRaw.owner) {
|
||||||
return {
|
if (bookingInfoRaw.user) {
|
||||||
notFound: true,
|
|
||||||
} as const;
|
|
||||||
eventTypeRaw.users.push({
|
eventTypeRaw.users.push({
|
||||||
...eventTypeRaw.owner,
|
...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 = {
|
const eventType = {
|
||||||
|
|||||||
+26
-72
@@ -2,57 +2,38 @@
|
|||||||
|
|
||||||
import { useReactTable, getCoreRowModel, getSortedRowModel, createColumnHelper } from "@tanstack/react-table";
|
import { useReactTable, getCoreRowModel, getSortedRowModel, createColumnHelper } from "@tanstack/react-table";
|
||||||
import { useSearchParams, usePathname } from "next/navigation";
|
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 dayjs from "@calcom/dayjs";
|
||||||
import {
|
import {
|
||||||
useDataTable,
|
|
||||||
DataTableProvider,
|
DataTableProvider,
|
||||||
DataTableWrapper,
|
type SystemFilterSegment,
|
||||||
DataTableFilters,
|
useDataTable,
|
||||||
DataTableSegment,
|
|
||||||
ColumnFilterType,
|
ColumnFilterType,
|
||||||
useFilterValue,
|
useFilterValue,
|
||||||
ZMultiSelectFilterValue,
|
ZMultiSelectFilterValue,
|
||||||
ZDateRangeFilterValue,
|
ZDateRangeFilterValue,
|
||||||
ZTextFilterValue,
|
ZTextFilterValue,
|
||||||
type SystemFilterSegment,
|
|
||||||
} from "@calcom/features/data-table";
|
} from "@calcom/features/data-table";
|
||||||
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
|
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||||
import { Alert } from "@calcom/ui/components/alert";
|
import { Alert } from "@calcom/ui/components/alert";
|
||||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
|
||||||
import type { HorizontalTabItemProps } from "@calcom/ui/components/navigation";
|
import type { HorizontalTabItemProps } from "@calcom/ui/components/navigation";
|
||||||
import { HorizontalTabs } from "@calcom/ui/components/navigation";
|
import { HorizontalTabs } from "@calcom/ui/components/navigation";
|
||||||
import type { VerticalTabItemProps } from "@calcom/ui/components/navigation";
|
import type { VerticalTabItemProps } from "@calcom/ui/components/navigation";
|
||||||
import { WipeMyCalActionButton } from "@calcom/web/components/apps/wipemycalother/wipeMyCalActionButton";
|
import { WipeMyCalActionButton } from "@calcom/web/components/apps/wipemycalother/wipeMyCalActionButton";
|
||||||
|
|
||||||
import BookingListItem from "@components/booking/BookingListItem";
|
import BookingListItem from "@components/booking/BookingListItem";
|
||||||
import SkeletonLoader from "@components/booking/SkeletonLoader";
|
|
||||||
|
|
||||||
import { useFacetedUniqueValues } from "~/bookings/hooks/useFacetedUniqueValues";
|
import { useFacetedUniqueValues } from "~/bookings/hooks/useFacetedUniqueValues";
|
||||||
import type { validStatuses } from "~/bookings/lib/validStatuses";
|
import type { validStatuses } from "~/bookings/lib/validStatuses";
|
||||||
|
|
||||||
type BookingListingStatus = (typeof validStatuses)[number];
|
import { BookingsCalendar } from "../components/BookingsCalendar";
|
||||||
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
|
import { BookingsList } from "../components/BookingsList";
|
||||||
|
import type { RowData, BookingOutput } from "../types";
|
||||||
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",
|
|
||||||
};
|
|
||||||
|
|
||||||
type BookingsProps = {
|
type BookingsProps = {
|
||||||
status: (typeof validStatuses)[number];
|
status: (typeof validStatuses)[number];
|
||||||
@@ -103,21 +84,18 @@ export default function Bookings(props: BookingsProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type RowData =
|
const viewParser = createParser({
|
||||||
| {
|
parse: (value: string) => {
|
||||||
type: "data";
|
if (value === "calendar") return "calendar";
|
||||||
booking: BookingOutput;
|
return "list";
|
||||||
isToday: boolean;
|
},
|
||||||
recurringInfo?: RecurringInfo;
|
serialize: (value: "list" | "calendar") => value,
|
||||||
}
|
});
|
||||||
| {
|
|
||||||
type: "today" | "next";
|
|
||||||
};
|
|
||||||
|
|
||||||
function BookingsContent({ status, permissions }: BookingsProps) {
|
function BookingsContent({ status, permissions }: BookingsProps) {
|
||||||
|
const [view] = useQueryState("view", viewParser.withDefault("list"));
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const user = useMeQuery().data;
|
const user = useMeQuery().data;
|
||||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// Generate dynamic tabs that preserve query parameters
|
// Generate dynamic tabs that preserve query parameters
|
||||||
@@ -411,6 +389,9 @@ function BookingsContent({ status, permissions }: BookingsProps) {
|
|||||||
getFacetedUniqueValues,
|
getFacetedUniqueValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isPending = query.isPending;
|
||||||
|
const totalRowCount = query.data?.totalCount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-row flex-wrap justify-between">
|
<div className="flex flex-row flex-wrap justify-between">
|
||||||
@@ -431,43 +412,16 @@ function BookingsContent({ status, permissions }: BookingsProps) {
|
|||||||
{!!bookingsToday.length && status === "upcoming" && (
|
{!!bookingsToday.length && status === "upcoming" && (
|
||||||
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
|
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
|
||||||
)}
|
)}
|
||||||
<DataTableWrapper
|
{view === "list" ? (
|
||||||
className="mb-6"
|
<BookingsList
|
||||||
tableContainerRef={tableContainerRef}
|
status={status}
|
||||||
table={table}
|
table={table}
|
||||||
testId={`${status}-bookings`}
|
isPending={isPending}
|
||||||
bodyTestId="bookings"
|
totalRowCount={totalRowCount}
|
||||||
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>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<BookingsCalendar status={status} table={table} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable */
|
||||||
require("dotenv").config({ path: "../../.env" });
|
require("dotenv").config({ path: "../../.env" });
|
||||||
const englishTranslation = require("./public/static/locales/en/common.json");
|
const englishTranslation = require("./public/static/locales/en/common.json");
|
||||||
const { withAxiom } = require("next-axiom");
|
const { withAxiom } = require("next-axiom");
|
||||||
@@ -223,8 +224,6 @@ const nextConfig = (phase) => {
|
|||||||
"@calcom/dayjs",
|
"@calcom/dayjs",
|
||||||
"@calcom/emails",
|
"@calcom/emails",
|
||||||
"@calcom/embed-core",
|
"@calcom/embed-core",
|
||||||
"@calcom/embed-react",
|
|
||||||
"@calcom/embed-snippet",
|
|
||||||
"@calcom/features",
|
"@calcom/features",
|
||||||
"@calcom/lib",
|
"@calcom/lib",
|
||||||
"@calcom/prisma",
|
"@calcom/prisma",
|
||||||
|
|||||||
@@ -201,6 +201,7 @@
|
|||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"tailwindcss-animate": "^1.0.6",
|
"tailwindcss-animate": "^1.0.6",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
"turbo": "^2.5.5",
|
||||||
"typescript": "^5.9.0-beta"
|
"typescript": "^5.9.0-beta"
|
||||||
},
|
},
|
||||||
"nextBundleAnalysis": {
|
"nextBundleAnalysis": {
|
||||||
|
|||||||
@@ -1460,6 +1460,7 @@
|
|||||||
"email_host_action": "send email to host",
|
"email_host_action": "send email to host",
|
||||||
"email_attendee_action": "send email to attendees",
|
"email_attendee_action": "send email to attendees",
|
||||||
"email_attendee_action_form": "Send email to submitted email address",
|
"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_attendee_action": "Send SMS to attendee",
|
||||||
"sms_number_action": "send SMS to a specific number",
|
"sms_number_action": "send SMS to a specific number",
|
||||||
"send_reminder_sms": "Easily send meeting reminders via SMS to your attendees",
|
"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.",
|
"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": "Limit total booking duration",
|
||||||
"limit_total_booking_duration_description": "Limit total amount of time that this event can be booked",
|
"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",
|
"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",
|
"recurring_event_doesnt_support_booker_booking_limit": "Recurring events don't support booker booking limit",
|
||||||
"add_limit": "Add Limit",
|
"add_limit": "Add Limit",
|
||||||
@@ -3636,8 +3637,8 @@
|
|||||||
"error_downloading_expense_log": "Error downloading expense log",
|
"error_downloading_expense_log": "Error downloading expense log",
|
||||||
"require_booker_email_verification": "Prevent Impersonation on Bookings",
|
"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",
|
"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",
|
"reschedule_last_booking_offer": "Offer to reschedule the last booking to the new time slot",
|
||||||
"booker_limit_exceeded_error": "Booker maximum active booking limit exceeded",
|
"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?",
|
"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_min_error": "Duration must be at least {{min}} minute(s)",
|
||||||
"duration_max_error": "Duration cannot exceed {{max}} minutes (24 hours)",
|
"duration_max_error": "Duration cannot exceed {{max}} minutes (24 hours)",
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { createMocks } from "node-mocks-http";
|
import { createMocks } from "node-mocks-http";
|
||||||
|
|
||||||
import type {
|
import type { CustomNextApiRequest, CustomNextApiResponse } from "./types";
|
||||||
CustomNextApiRequest,
|
|
||||||
CustomNextApiResponse,
|
|
||||||
} from "@calcom/features/bookings/lib/handleNewBooking/test/fresh-booking.test";
|
|
||||||
|
|
||||||
export function createMockNextJsRequest(...args: Parameters<typeof createMocks>) {
|
export function createMockNextJsRequest(...args: Parameters<typeof createMocks>) {
|
||||||
return createMocks<CustomNextApiRequest, CustomNextApiResponse>(...args);
|
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/azure",
|
||||||
"self-hosting/deployments/elestio",
|
"self-hosting/deployments/elestio",
|
||||||
"self-hosting/deployments/gcp",
|
"self-hosting/deployments/gcp",
|
||||||
|
"self-hosting/deployments/northflank",
|
||||||
"self-hosting/deployments/railway",
|
"self-hosting/deployments/railway",
|
||||||
"self-hosting/deployments/render",
|
"self-hosting/deployments/render",
|
||||||
"self-hosting/deployments/vercel"
|
"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",
|
"name": "calcom-monorepo",
|
||||||
"version": "0.0.0",
|
"version": "5.8.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"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";
|
import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
||||||
|
|
||||||
export const AttendeeRequestEmail = (props: React.ComponentProps<typeof 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 (
|
return (
|
||||||
<AttendeeScheduledEmail
|
<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";
|
import { BaseEmailHtml } from "../components";
|
||||||
|
|
||||||
export const BookingRedirectEmailNotification = (
|
export const BookingRedirectEmailNotification = (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import ServerTrans from "@calcom/lib/components/ServerTrans";
|
import ServerTrans from "@calcom/lib/components/ServerTrans";
|
||||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
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";
|
import { V2BaseEmailHtml } from "../components";
|
||||||
|
|
||||||
export const OrganizationCreationEmail = (
|
export const OrganizationCreationEmail = (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { TFunction } from "i18next";
|
|||||||
import ServerTrans from "@calcom/lib/components/ServerTrans";
|
import ServerTrans from "@calcom/lib/components/ServerTrans";
|
||||||
import { APP_NAME, WEBAPP_URL, IS_PRODUCTION } from "@calcom/lib/constants";
|
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";
|
import { V2BaseEmailHtml, CallToAction } from "../components";
|
||||||
|
|
||||||
type TeamInvite = {
|
type TeamInvite = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { APP_NAME, SENDER_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
|
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";
|
import { BaseEmailHtml } from "../components";
|
||||||
|
|
||||||
export const VerifyEmailByCode = (
|
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 { APP_NAME, COMPANY_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export type EmailVerifyLink = {
|
export type EmailVerifyLink = {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
|||||||
|
|
||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export type OrganizationNotification = {
|
export type OrganizationNotification = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeAddGuestsEmail extends AttendeeScheduledEmail {
|
export default class AttendeeAddGuestsEmail extends AttendeeScheduledEmail {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeAwaitingPaymentEmail extends AttendeeScheduledEmail {
|
export default class AttendeeAwaitingPaymentEmail extends AttendeeScheduledEmail {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeCancelledEmail extends AttendeeScheduledEmail {
|
export default class AttendeeCancelledEmail extends AttendeeScheduledEmail {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeCancelledSeatEmail extends AttendeeScheduledEmail {
|
export default class AttendeeCancelledSeatEmail extends AttendeeScheduledEmail {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
|||||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class AttendeeDailyVideoDownloadRecordingEmail extends BaseEmail {
|
export default class AttendeeDailyVideoDownloadRecordingEmail extends BaseEmail {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
|||||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class AttendeeDailyVideoDownloadTranscriptEmail extends BaseEmail {
|
export default class AttendeeDailyVideoDownloadTranscriptEmail extends BaseEmail {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeDeclinedEmail extends AttendeeScheduledEmail {
|
export default class AttendeeDeclinedEmail extends AttendeeScheduledEmail {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeLocationChangeEmail extends AttendeeScheduledEmail {
|
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 { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeRequestEmail extends AttendeeScheduledEmail {
|
export default class AttendeeRequestEmail extends AttendeeScheduledEmail {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
|
export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
|||||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class AttendeeScheduledEmail extends BaseEmail {
|
export default class AttendeeScheduledEmail extends BaseEmail {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeUpdatedEmail extends AttendeeScheduledEmail {
|
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 { 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";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export type EmailVerifyCode = {
|
export type { EmailVerifyCode } from "../lib/types/email-types";
|
||||||
language: TFunction;
|
|
||||||
user: {
|
|
||||||
name?: string | null;
|
|
||||||
email: string;
|
|
||||||
};
|
|
||||||
verificationEmailCode: string;
|
|
||||||
isVerifyingEmail?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class AttendeeVerifyEmail extends BaseEmail {
|
export default class AttendeeVerifyEmail extends BaseEmail {
|
||||||
verifyAccountInput: EmailVerifyCode;
|
verifyAccountInput: EmailVerifyCode;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { getManageLink } from "@calcom/lib/CalEventParser";
|
|||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "..";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
|
|
||||||
export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerScheduledEmail {
|
export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerScheduledEmail {
|
||||||
|
|||||||
@@ -1,20 +1,10 @@
|
|||||||
import type { TFunction } from "i18next";
|
|
||||||
|
|
||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
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";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export interface IBookingRedirect {
|
export type { IBookingRedirect } from "../lib/types/booking-redirect-types";
|
||||||
language: TFunction;
|
|
||||||
fromEmail: string;
|
|
||||||
eventOwner: string;
|
|
||||||
toEmail: string;
|
|
||||||
toName: string;
|
|
||||||
oldDates?: string;
|
|
||||||
dates: string;
|
|
||||||
action: "add" | "update" | "cancel";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class BookingRedirectNotification extends BaseEmail {
|
export default class BookingRedirectNotification extends BaseEmail {
|
||||||
bookingRedirect: IBookingRedirect;
|
bookingRedirect: IBookingRedirect;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
|||||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "..";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class BrokenIntegrationEmail extends BaseEmail {
|
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 { APP_NAME, COMPANY_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export type ChangeOfEmailVerifyLink = {
|
export type ChangeOfEmailVerifyLink = {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
|||||||
|
|
||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "..";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class CreditBalanceLimitReachedEmail extends BaseEmail {
|
export default class CreditBalanceLimitReachedEmail extends BaseEmail {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
|||||||
|
|
||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "..";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class CreditBalanceLowWarningEmail extends BaseEmail {
|
export default class CreditBalanceLowWarningEmail extends BaseEmail {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
|||||||
|
|
||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "..";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class DisabledAppEmail extends BaseEmail {
|
export default class DisabledAppEmail extends BaseEmail {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export interface Feedback {
|
export interface Feedback {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { TFunction } from "i18next";
|
|||||||
|
|
||||||
import { APP_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { APP_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export type PasswordReset = {
|
export type PasswordReset = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { APP_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
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 type { MonthlyDigestEmailData } from "../src/templates/MonthlyDigestEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||||
|
|
||||||
export default class NoShowFeeChargedEmail extends AttendeeScheduledEmail {
|
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 { APP_NAME, EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "..";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export type OrgAutoInvite = {
|
export type OrgAutoInvite = {
|
||||||
|
|||||||
@@ -1,21 +1,10 @@
|
|||||||
import type { TFunction } from "i18next";
|
|
||||||
|
|
||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
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";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export type OrganizationCreation = {
|
export type { OrganizationCreation } from "../lib/types/email-types";
|
||||||
language: TFunction;
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
ownerNewUsername: string;
|
|
||||||
ownerOldUsername: string | null;
|
|
||||||
orgDomain: string;
|
|
||||||
orgName: string;
|
|
||||||
prevLink: string | null;
|
|
||||||
newLink: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class OrganizationCreationEmail extends BaseEmail {
|
export default class OrganizationCreationEmail extends BaseEmail {
|
||||||
organizationCreationEvent: OrganizationCreation;
|
organizationCreationEvent: OrganizationCreation;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
|
|
||||||
export default class OrganizerAddGuestsEmail extends OrganizerScheduledEmail {
|
export default class OrganizerAddGuestsEmail extends OrganizerScheduledEmail {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
|
|
||||||
export default class OrganizerCancelledEmail extends OrganizerScheduledEmail {
|
export default class OrganizerCancelledEmail extends OrganizerScheduledEmail {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
import type { Reassigned } 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 { TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class OrganizerDailyVideoDownloadRecordingEmail extends BaseEmail {
|
export default class OrganizerDailyVideoDownloadRecordingEmail extends BaseEmail {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
|||||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "..";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class OrganizerDailyVideoDownloadTranscriptEmail extends BaseEmail {
|
export default class OrganizerDailyVideoDownloadTranscriptEmail extends BaseEmail {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
|
|
||||||
export default class OrganizerLocationChangeEmail extends OrganizerScheduledEmail {
|
export default class OrganizerLocationChangeEmail extends OrganizerScheduledEmail {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
|
|
||||||
export default class OrganizerPaymentRefundFailedEmail extends OrganizerScheduledEmail {
|
export default class OrganizerPaymentRefundFailedEmail extends OrganizerScheduledEmail {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
import type { Reassigned } 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 { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerRequestEmail from "./organizer-request-email";
|
import OrganizerRequestEmail from "./organizer-request-email";
|
||||||
|
|
||||||
export default class OrganizerRequestReminderEmail extends OrganizerRequestEmail {
|
export default class OrganizerRequestReminderEmail extends OrganizerRequestEmail {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "..";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
|
|
||||||
export default class OrganizerRequestedToRescheduleEmail extends OrganizerScheduledEmail {
|
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 { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||||
|
|
||||||
export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail {
|
export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail {
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { getReplyToHeader } from "@calcom/lib/getReplyToHeader";
|
|||||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { renderEmail } from "../";
|
|
||||||
import generateIcsFile from "../lib/generateIcsFile";
|
import generateIcsFile from "../lib/generateIcsFile";
|
||||||
import { GenerateIcsRole } from "../lib/generateIcsFile";
|
import { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export type Reassigned = { name: string | null; email: string; reason?: string; byUser?: string };
|
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 { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { renderEmail } from "..";
|
import renderEmail from "../src/renderEmail";
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export default class SlugReplacementEmail extends BaseEmail {
|
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 { getSubject, type TeamInvite } from "../lib/utils/team-invite-utils";
|
||||||
|
import renderEmail from "../src/renderEmail";
|
||||||
import { renderEmail } from "../";
|
|
||||||
import BaseEmail from "./_base-email";
|
import BaseEmail from "./_base-email";
|
||||||
|
|
||||||
export type TeamInvite = {
|
export { getSubject, getTypeOfInvite, type TeamInvite } from "../lib/utils/team-invite-utils";
|
||||||
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 default class TeamInviteEmail extends BaseEmail {
|
export default class TeamInviteEmail extends BaseEmail {
|
||||||
teamInviteEvent: TeamInvite;
|
teamInviteEvent: TeamInvite;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import type { Dispatch, SetStateAction } from "react";
|
|||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
|
import { LastUsed, useLastUsed } from "@calcom/features/auth/lib/hooks/useLastUsed";
|
||||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||||
import { emailRegex } from "@calcom/lib/emailSchema";
|
import { emailRegex } from "@calcom/lib/emailSchema";
|
||||||
import { LastUsed, useLastUsed } from "@calcom/lib/hooks/useLastUsed";
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import type { ButtonProps } from "@calcom/ui/components/button";
|
import type { ButtonProps } from "@calcom/ui/components/button";
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ const getError = ({
|
|||||||
const error = dataError;
|
const error = dataError;
|
||||||
|
|
||||||
let date = "";
|
let date = "";
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
if (error.message === ErrorCode.BookerLimitExceededReschedule) {
|
if (error.message === ErrorCode.BookerLimitExceededReschedule) {
|
||||||
const formattedDate = formatEventFromTime({
|
const formattedDate = formatEventFromTime({
|
||||||
@@ -312,9 +313,16 @@ const getError = ({
|
|||||||
date = `${formattedDate.date} ${formattedDate.time}`;
|
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 ? (
|
return error?.message ? (
|
||||||
<>
|
<>
|
||||||
{responseVercelIdHeader ?? ""} {t(error.message, { date })}
|
{responseVercelIdHeader ?? ""} {t(messageKey, { date, count })}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>{t("can_you_try_again")}</>
|
<>{t("can_you_try_again")}</>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useMemo, useEffect } from "react";
|
|||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useAvailableTimeSlots } from "@calcom/features/bookings/Booker/components/hooks/useAvailableTimeSlots";
|
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 type { BookerEvent } from "@calcom/features/bookings/types";
|
||||||
import { Calendar } from "@calcom/features/calendars/weeklyview";
|
import { Calendar } from "@calcom/features/calendars/weeklyview";
|
||||||
import type { CalendarEvent } from "@calcom/features/calendars/weeklyview/types/events";
|
import type { CalendarEvent } from "@calcom/features/calendars/weeklyview/types/events";
|
||||||
@@ -31,6 +32,7 @@ export const LargeCalendar = ({
|
|||||||
const overlayEvents = useOverlayCalendarStore((state) => state.overlayBusyDates);
|
const overlayEvents = useOverlayCalendarStore((state) => state.overlayBusyDates);
|
||||||
const displayOverlay =
|
const displayOverlay =
|
||||||
getQueryParam("overlayCalendar") === "true" || localStorage?.getItem("overlayCalendarSwitchDefault");
|
getQueryParam("overlayCalendar") === "true" || localStorage?.getItem("overlayCalendarSwitchDefault");
|
||||||
|
const { timezone } = useBookerTime();
|
||||||
|
|
||||||
const eventDuration = selectedEventDuration || event?.data?.length || 30;
|
const eventDuration = selectedEventDuration || event?.data?.length || 30;
|
||||||
|
|
||||||
@@ -43,7 +45,6 @@ export const LargeCalendar = ({
|
|||||||
|
|
||||||
// HACK: force rerender when overlay events change
|
// HACK: force rerender when overlay events change
|
||||||
// Sine we dont use react router here we need to force rerender (ATOM SUPPORT)
|
// 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]);
|
useEffect(() => {}, [displayOverlay]);
|
||||||
|
|
||||||
const overlayEventsForDate = useMemo(() => {
|
const overlayEventsForDate = useMemo(() => {
|
||||||
@@ -75,6 +76,7 @@ export const LargeCalendar = ({
|
|||||||
gridCellsPerHour={60 / eventDuration}
|
gridCellsPerHour={60 / eventDuration}
|
||||||
hoverEventDuration={eventDuration}
|
hoverEventDuration={eventDuration}
|
||||||
hideHeader
|
hideHeader
|
||||||
|
timezone={timezone}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 { createModule, bindModuleToClassOnToken } from "@calcom/features/di/di";
|
||||||
import { DI_TOKENS } from "@calcom/features/di/tokens";
|
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();
|
export const instantBookingCreateServiceModule = createModule();
|
||||||
const token = DI_TOKENS.INSTANT_BOOKING_CREATE_SERVICE;
|
const token = DI_TOKENS.INSTANT_BOOKING_CREATE_SERVICE;
|
||||||
@@ -10,7 +11,10 @@ const loadModule = bindModuleToClassOnToken({
|
|||||||
moduleToken,
|
moduleToken,
|
||||||
token,
|
token,
|
||||||
classs: InstantBookingCreateService,
|
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 };
|
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 { createModule, bindModuleToClassOnToken } from "@calcom/features/di/di";
|
||||||
import { DI_TOKENS } from "@calcom/features/di/tokens";
|
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 { 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 bookingRepositoryModuleLoader } from "@calcom/features/di/modules/Booking";
|
||||||
import { moduleLoader as cacheModuleLoader } from "@calcom/features/di/modules/Cache";
|
import { moduleLoader as cacheModuleLoader } from "@calcom/features/di/modules/Cache";
|
||||||
import { moduleLoader as checkBookingAndDurationLimitsModuleLoader } from "@calcom/features/di/modules/CheckBookingAndDurationLimits";
|
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 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 { moduleLoader as userRepositoryModuleLoader } from "@calcom/features/di/modules/User";
|
||||||
import { DI_TOKENS } from "@calcom/features/di/tokens";
|
import { DI_TOKENS } from "@calcom/features/di/tokens";
|
||||||
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
|
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
|
||||||
@@ -22,17 +19,13 @@ const loadModule = bindModuleToClassOnToken({
|
|||||||
token,
|
token,
|
||||||
classs: RegularBookingService,
|
classs: RegularBookingService,
|
||||||
depsMap: {
|
depsMap: {
|
||||||
|
// TODO: In a followup PR, we aim to remove prisma dependency and instead inject the repositories as dependencies.
|
||||||
|
prismaClient: prismaModuleLoader,
|
||||||
cacheService: cacheModuleLoader,
|
cacheService: cacheModuleLoader,
|
||||||
checkBookingAndDurationLimitsService: checkBookingAndDurationLimitsModuleLoader,
|
checkBookingAndDurationLimitsService: checkBookingAndDurationLimitsModuleLoader,
|
||||||
prismaClient: prismaModuleLoader,
|
|
||||||
bookingRepository: bookingRepositoryModuleLoader,
|
bookingRepository: bookingRepositoryModuleLoader,
|
||||||
featuresRepository: featuresRepositoryModuleLoader,
|
|
||||||
checkBookingLimitsService: checkBookingLimitsModuleLoader,
|
|
||||||
luckyUserService: luckyUserServiceModuleLoader,
|
luckyUserService: luckyUserServiceModuleLoader,
|
||||||
hostRepository: hostRepositoryModuleLoader,
|
|
||||||
oooRepository: oooRepositoryModuleLoader,
|
|
||||||
userRepository: userRepositoryModuleLoader,
|
userRepository: userRepositoryModuleLoader,
|
||||||
attributeRepository: attributeRepositoryModuleLoader,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ export const BOOKING_DI_TOKENS = {
|
|||||||
RECURRING_BOOKING_SERVICE_MODULE: Symbol("RecurringBookingServiceModule"),
|
RECURRING_BOOKING_SERVICE_MODULE: Symbol("RecurringBookingServiceModule"),
|
||||||
INSTANT_BOOKING_CREATE_SERVICE: Symbol("InstantBookingCreateService"),
|
INSTANT_BOOKING_CREATE_SERVICE: Symbol("InstantBookingCreateService"),
|
||||||
INSTANT_BOOKING_CREATE_SERVICE_MODULE: Symbol("InstantBookingCreateServiceModule"),
|
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 { parseRecurringDates } from "@calcom/lib/parse-dates";
|
||||||
import type { RoutingFormSearchParams } from "@calcom/platform-types";
|
import type { RoutingFormSearchParams } from "@calcom/platform-types";
|
||||||
|
|
||||||
import type { BookerEvent, BookingCreateBody, RecurringBookingCreateBody } from "../../types";
|
import type { BookerEvent, BookingCreateBody, RecurringBookingCreateBody } from "../../../types";
|
||||||
import type { Tracking } from "../handleNewBooking/types";
|
import type { Tracking } from "../../handleNewBooking/types";
|
||||||
|
|
||||||
export type BookingOptions = {
|
export type BookingOptions = {
|
||||||
values: Record<string, unknown>;
|
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 { SchedulingType } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import type { ExtendedBookingCreateBody } from "../bookingCreateBodySchema";
|
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 BookingDataSchemaGetter = typeof getBookingDataSchema | typeof getBookingDataSchemaForApi;
|
||||||
|
|
||||||
export type CreateRegularBookingData = ExtendedBookingCreateBody;
|
export type CreateRegularBookingData = ExtendedBookingCreateBody;
|
||||||
@@ -44,14 +43,14 @@ export type BookingHandlerInput = {
|
|||||||
bookingData: CreateRegularBookingData;
|
bookingData: CreateRegularBookingData;
|
||||||
} & CreateBookingMeta;
|
} & 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 = {
|
export type InstantBookingCreateResult = {
|
||||||
message: string;
|
message: "Success";
|
||||||
meetingTokenId: number;
|
meetingTokenId: number;
|
||||||
bookingId: number;
|
bookingId: number;
|
||||||
bookingUid: string;
|
bookingUid: string;
|
||||||
expires: Date;
|
expires: Date;
|
||||||
userId: number | null;
|
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 { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { sendCancelledEmailsAndSMS } from "@calcom/emails";
|
import { sendCancelledEmailsAndSMS } from "@calcom/emails";
|
||||||
|
import EventManager from "@calcom/features/bookings/lib/EventManager";
|
||||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||||
import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation";
|
import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation";
|
||||||
import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund";
|
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 { 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 type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks";
|
||||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||||
import {
|
import {
|
||||||
@@ -17,8 +20,6 @@ import {
|
|||||||
} from "@calcom/features/webhooks/lib/scheduleTrigger";
|
} from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||||
import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
|
import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
|
||||||
import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
|
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 getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
|
||||||
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
|
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
@@ -27,10 +28,10 @@ import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
|
|||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||||
import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
|
|
||||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
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 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 type { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||||
import { BookingStatus } from "@calcom/prisma/enums";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
import { bookingMetadataSchema, bookingCancelInput } from "@calcom/prisma/zod-utils";
|
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 { getAllWorkflowsFromEventType } from "@calcom/trpc/server/routers/viewer/workflows/util";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CancelRegularBookingData,
|
||||||
|
CancelBookingMeta,
|
||||||
|
HandleCancelBookingResponse,
|
||||||
|
} from "./dto/BookingCancel";
|
||||||
import { getAllCredentialsIncludeServiceAccountKey } from "./getAllCredentialsForUsersOnEvent/getAllCredentials";
|
import { getAllCredentialsIncludeServiceAccountKey } from "./getAllCredentialsForUsersOnEvent/getAllCredentials";
|
||||||
import { getBookingToDelete } from "./getBookingToDelete";
|
import { getBookingToDelete } from "./getBookingToDelete";
|
||||||
import { handleInternalNote } from "./handleInternalNote";
|
import { handleInternalNote } from "./handleInternalNote";
|
||||||
import cancelAttendeeSeat from "./handleSeats/cancel/cancelAttendeeSeat";
|
import cancelAttendeeSeat from "./handleSeats/cancel/cancelAttendeeSeat";
|
||||||
|
import type { IBookingCancelService } from "./interfaces/IBookingCancelService";
|
||||||
|
|
||||||
const log = logger.getSubLogger({ prefix: ["handleCancelBooking"] });
|
const log = logger.getSubLogger({ prefix: ["handleCancelBooking"] });
|
||||||
|
|
||||||
@@ -60,14 +67,6 @@ export type CancelBookingInput = {
|
|||||||
bookingData: z.infer<typeof bookingCancelInput>;
|
bookingData: z.infer<typeof bookingCancelInput>;
|
||||||
} & PlatformParams;
|
} & PlatformParams;
|
||||||
|
|
||||||
export type HandleCancelBookingResponse = {
|
|
||||||
success: boolean;
|
|
||||||
message: string;
|
|
||||||
onlyRemovedAttendee: boolean;
|
|
||||||
bookingId: number;
|
|
||||||
bookingUid: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handler(input: CancelBookingInput) {
|
async function handler(input: CancelBookingInput) {
|
||||||
const body = input.bookingData;
|
const body = input.bookingData;
|
||||||
const {
|
const {
|
||||||
@@ -118,7 +117,12 @@ async function handler(input: CancelBookingInput) {
|
|||||||
const isCancellationUserHost =
|
const isCancellationUserHost =
|
||||||
bookingToDelete.userId == userId || bookingToDelete.user.email === cancelledBy;
|
bookingToDelete.userId == userId || bookingToDelete.user.email === cancelledBy;
|
||||||
|
|
||||||
if (!platformClientId && !cancellationReason?.trim() && isCancellationUserHost && !skipCancellationReasonValidation) {
|
if (
|
||||||
|
!platformClientId &&
|
||||||
|
!cancellationReason?.trim() &&
|
||||||
|
isCancellationUserHost &&
|
||||||
|
!skipCancellationReasonValidation
|
||||||
|
) {
|
||||||
throw new HttpError({
|
throw new HttpError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
message: "Cancellation reason is required when you are the host",
|
message: "Cancellation reason is required when you are the host",
|
||||||
@@ -588,4 +592,24 @@ async function handler(input: CancelBookingInput) {
|
|||||||
} satisfies HandleCancelBookingResponse;
|
} 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;
|
export default handler;
|
||||||
|
|||||||
+3
-1
@@ -61,7 +61,9 @@ const checkActiveBookingsLimit = async ({
|
|||||||
|
|
||||||
if (bookingsCount >= maxActiveBookingsPerBooker) {
|
if (bookingsCount >= maxActiveBookingsPerBooker) {
|
||||||
log.warn(`Maximum booking limit reached for ${bookerEmail} for event type ${eventTypeId}`);
|
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 { BookingStatus, CreationSource } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { buildDryRunBooking } from "../../handleNewBooking";
|
import { buildDryRunBooking } from "../../service/RegularBookingService";
|
||||||
|
|
||||||
vi.mock("@calcom/prisma", () => ({
|
vi.mock("@calcom/prisma", () => ({
|
||||||
default: {}, // empty object as default export
|
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 { SchedulingType } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { buildEventForTeamEventType } from "../../handleNewBooking";
|
import { buildEventForTeamEventType } from "../../service/RegularBookingService";
|
||||||
|
|
||||||
vi.mock("@calcom/lib/server/i18n", () => ({
|
vi.mock("@calcom/lib/server/i18n", () => ({
|
||||||
getTranslation: vi.fn().mockResolvedValue("translated"),
|
getTranslation: vi.fn().mockResolvedValue("translated"),
|
||||||
@@ -22,12 +22,12 @@ const withDestinationCalendarSpy = vi.fn().mockReturnThis();
|
|||||||
vi.mock("@calcom/features/CalendarEventBuilder", () => {
|
vi.mock("@calcom/features/CalendarEventBuilder", () => {
|
||||||
return {
|
return {
|
||||||
CalendarEventBuilder: {
|
CalendarEventBuilder: {
|
||||||
fromEvent: vi.fn().mockImplementation((evt) => ({
|
fromEvent: vi.fn().mockImplementation((_evt) => ({
|
||||||
withDestinationCalendar: withDestinationCalendarSpy,
|
withDestinationCalendar: withDestinationCalendarSpy,
|
||||||
withTeam: withTeamSpy,
|
withTeam: withTeamSpy,
|
||||||
build: vi.fn().mockImplementation(() => ({
|
build: vi.fn().mockImplementation(() => ({
|
||||||
destinationCalendar: [],
|
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 () => {
|
it("filters out the organizer", async () => {
|
||||||
const result = await buildEventForTeamEventType({
|
await buildEventForTeamEventType({
|
||||||
existingEvent: {},
|
existingEvent: {},
|
||||||
users: [baseUser({ email: "organizer@example.com" })],
|
users: [baseUser({ email: "organizer@example.com" })],
|
||||||
organizerUser: { email: "organizer@example.com" },
|
organizerUser: { email: "organizer@example.com" },
|
||||||
@@ -80,7 +80,7 @@ describe("buildEventForTeamEventType", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const teamArgs = withTeamSpy.mock.calls[0][0];
|
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");
|
expect(memberEmails).not.toContain("organizer@example.com");
|
||||||
});
|
});
|
||||||
@@ -123,7 +123,7 @@ describe("buildEventForTeamEventType", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const teamArgs = withTeamSpy.mock.calls[0][0];
|
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("fixed@example.com");
|
||||||
expect(memberEmails).toContain("nonfixed@example.com");
|
expect(memberEmails).toContain("nonfixed@example.com");
|
||||||
@@ -142,7 +142,7 @@ describe("buildEventForTeamEventType", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const teamArgs = withTeamSpy.mock.calls[0][0];
|
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("fixed@example.com");
|
||||||
expect(memberEmails).toContain("nonfixed1@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