Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3fb7340aa | ||
|
|
e4ef2832cd | ||
|
|
881602cf1c | ||
|
|
4c01f171ae | ||
|
|
d9817fcb07 | ||
|
|
a784af3e47 | ||
|
|
c268925330 | ||
|
|
378e8e0330 |
@@ -33,7 +33,7 @@
|
|||||||
"@sentry/nextjs": "^9.15.0",
|
"@sentry/nextjs": "^9.15.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"next": "^15.3.0",
|
"next": "^15.4.5",
|
||||||
"next-api-middleware": "^1.0.1",
|
"next-api-middleware": "^1.0.1",
|
||||||
"next-axiom": "^0.17.0",
|
"next-axiom": "^0.17.0",
|
||||||
"next-swagger-doc": "^0.3.6",
|
"next-swagger-doc": "^0.3.6",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"@axiomhq/winston": "^1.2.0",
|
"@axiomhq/winston": "^1.2.0",
|
||||||
"@calcom/platform-constants": "*",
|
"@calcom/platform-constants": "*",
|
||||||
"@calcom/platform-enums": "*",
|
"@calcom/platform-enums": "*",
|
||||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.300",
|
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.306",
|
||||||
"@calcom/platform-types": "*",
|
"@calcom/platform-types": "*",
|
||||||
"@calcom/platform-utils": "*",
|
"@calcom/platform-utils": "*",
|
||||||
"@calcom/prisma": "*",
|
"@calcom/prisma": "*",
|
||||||
|
|||||||
+8
-28
@@ -4,37 +4,21 @@ import { Permissions } from "@/modules/auth/decorators/permissions/permissions.d
|
|||||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||||
import { EventTypeOwnershipGuard } from "@/modules/event-types/guards/event-type-ownership.guard";
|
import { EventTypeOwnershipGuard } from "@/modules/event-types/guards/event-type-ownership.guard";
|
||||||
import {
|
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common";
|
||||||
Body,
|
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||||
Controller,
|
|
||||||
Delete,
|
|
||||||
Get,
|
|
||||||
Param,
|
|
||||||
ParseIntPipe,
|
|
||||||
Patch,
|
|
||||||
Post,
|
|
||||||
UseGuards,
|
|
||||||
} from "@nestjs/common";
|
|
||||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags, OmitType } from "@nestjs/swagger";
|
|
||||||
|
|
||||||
import {
|
import { EVENT_TYPE_READ, EVENT_TYPE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
EVENT_TYPE_READ,
|
|
||||||
EVENT_TYPE_WRITE,
|
|
||||||
SUCCESS_STATUS,
|
|
||||||
} from "@calcom/platform-constants";
|
|
||||||
import {
|
import {
|
||||||
CreatePrivateLinkInput,
|
CreatePrivateLinkInput,
|
||||||
CreatePrivateLinkOutput,
|
CreatePrivateLinkOutput,
|
||||||
DeletePrivateLinkOutput,
|
DeletePrivateLinkOutput,
|
||||||
GetPrivateLinksOutput,
|
GetPrivateLinksOutput,
|
||||||
UpdatePrivateLinkInput,
|
|
||||||
UpdatePrivateLinkOutput,
|
UpdatePrivateLinkOutput,
|
||||||
|
UpdatePrivateLinkBody,
|
||||||
} from "@calcom/platform-types";
|
} from "@calcom/platform-types";
|
||||||
|
|
||||||
import { PrivateLinksService } from "../services/private-links.service";
|
import { PrivateLinksService } from "../services/private-links.service";
|
||||||
|
|
||||||
class UpdatePrivateLinkBody extends OmitType(UpdatePrivateLinkInput, ["linkId"] as const) {}
|
|
||||||
|
|
||||||
@Controller({
|
@Controller({
|
||||||
path: "/v2/event-types/:eventTypeId/private-links",
|
path: "/v2/event-types/:eventTypeId/private-links",
|
||||||
})
|
})
|
||||||
@@ -85,11 +69,10 @@ export class EventTypesPrivateLinksController {
|
|||||||
async updatePrivateLink(
|
async updatePrivateLink(
|
||||||
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||||
@Param("linkId") linkId: string,
|
@Param("linkId") linkId: string,
|
||||||
@Body() body: UpdatePrivateLinkBody,
|
@Body() body: UpdatePrivateLinkBody
|
||||||
@GetUser("id") userId: number
|
|
||||||
): Promise<UpdatePrivateLinkOutput> {
|
): Promise<UpdatePrivateLinkOutput> {
|
||||||
const updateInput = { ...body, linkId };
|
const updateInput = { ...body, linkId };
|
||||||
const privateLink = await this.privateLinksService.updatePrivateLink(eventTypeId, userId, updateInput);
|
const privateLink = await this.privateLinksService.updatePrivateLink(eventTypeId, updateInput);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: SUCCESS_STATUS,
|
status: SUCCESS_STATUS,
|
||||||
@@ -104,10 +87,9 @@ export class EventTypesPrivateLinksController {
|
|||||||
@ApiOperation({ summary: "Delete a private link for an event type" })
|
@ApiOperation({ summary: "Delete a private link for an event type" })
|
||||||
async deletePrivateLink(
|
async deletePrivateLink(
|
||||||
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||||
@Param("linkId") linkId: string,
|
@Param("linkId") linkId: string
|
||||||
@GetUser("id") userId: number
|
|
||||||
): Promise<DeletePrivateLinkOutput> {
|
): Promise<DeletePrivateLinkOutput> {
|
||||||
await this.privateLinksService.deletePrivateLink(eventTypeId, userId, linkId);
|
await this.privateLinksService.deletePrivateLink(eventTypeId, linkId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: SUCCESS_STATUS,
|
status: SUCCESS_STATUS,
|
||||||
@@ -118,5 +100,3 @@ export class EventTypesPrivateLinksController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||||
|
import {
|
||||||
|
OPTIONAL_API_KEY_HEADER,
|
||||||
|
OPTIONAL_X_CAL_CLIENT_ID_HEADER,
|
||||||
|
OPTIONAL_X_CAL_SECRET_KEY_HEADER,
|
||||||
|
} from "@/lib/docs/headers";
|
||||||
|
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||||
|
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||||
|
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||||
|
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
|
||||||
|
import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard";
|
||||||
|
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
|
||||||
|
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||||
|
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
|
||||||
|
import { TeamsEventTypesService } from "@/modules/teams/event-types/services/teams-event-types.service";
|
||||||
|
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common";
|
||||||
|
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
|
import {
|
||||||
|
CreatePrivateLinkInput,
|
||||||
|
CreatePrivateLinkOutput,
|
||||||
|
DeletePrivateLinkOutput,
|
||||||
|
GetPrivateLinksOutput,
|
||||||
|
UpdatePrivateLinkInput,
|
||||||
|
UpdatePrivateLinkOutput,
|
||||||
|
} from "@calcom/platform-types";
|
||||||
|
|
||||||
|
import { PrivateLinksService } from "../services/private-links.service";
|
||||||
|
|
||||||
|
@Controller({
|
||||||
|
path: "/v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links",
|
||||||
|
version: API_VERSIONS_VALUES,
|
||||||
|
})
|
||||||
|
@DocsTags("Orgs / Teams / Event Types / Private Links")
|
||||||
|
@ApiHeader(OPTIONAL_X_CAL_CLIENT_ID_HEADER)
|
||||||
|
@ApiHeader(OPTIONAL_X_CAL_SECRET_KEY_HEADER)
|
||||||
|
@ApiHeader(OPTIONAL_API_KEY_HEADER)
|
||||||
|
export class OrganizationsEventTypesPrivateLinksController {
|
||||||
|
constructor(
|
||||||
|
private readonly privateLinksService: PrivateLinksService,
|
||||||
|
private readonly teamsEventTypesService: TeamsEventTypesService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post("/")
|
||||||
|
@Roles("TEAM_ADMIN")
|
||||||
|
@PlatformPlan("ESSENTIALS")
|
||||||
|
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
|
||||||
|
@ApiOperation({ summary: "Create a private link for a team event type" })
|
||||||
|
async createPrivateLink(
|
||||||
|
@Param("teamId", ParseIntPipe) teamId: number,
|
||||||
|
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||||
|
@Body() body: CreatePrivateLinkInput
|
||||||
|
): Promise<CreatePrivateLinkOutput> {
|
||||||
|
await this.teamsEventTypesService.validateEventTypeExists(teamId, eventTypeId);
|
||||||
|
// Use teamId as the seed for link generation in org/team context
|
||||||
|
const privateLink = await this.privateLinksService.createPrivateLink(eventTypeId, teamId, body);
|
||||||
|
return {
|
||||||
|
status: SUCCESS_STATUS,
|
||||||
|
data: privateLink,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("/")
|
||||||
|
@Roles("TEAM_ADMIN")
|
||||||
|
@PlatformPlan("ESSENTIALS")
|
||||||
|
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
|
||||||
|
@ApiOperation({ summary: "Get all private links for a team event type" })
|
||||||
|
async getPrivateLinks(
|
||||||
|
@Param("teamId", ParseIntPipe) teamId: number,
|
||||||
|
@Param("eventTypeId", ParseIntPipe) eventTypeId: number
|
||||||
|
): Promise<GetPrivateLinksOutput> {
|
||||||
|
await this.teamsEventTypesService.validateEventTypeExists(teamId, eventTypeId);
|
||||||
|
const privateLinks = await this.privateLinksService.getPrivateLinks(eventTypeId);
|
||||||
|
return {
|
||||||
|
status: SUCCESS_STATUS,
|
||||||
|
data: privateLinks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch("/:linkId")
|
||||||
|
@Roles("TEAM_ADMIN")
|
||||||
|
@PlatformPlan("ESSENTIALS")
|
||||||
|
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
|
||||||
|
@ApiOperation({ summary: "Update a private link for a team event type" })
|
||||||
|
async updatePrivateLink(
|
||||||
|
@Param("teamId", ParseIntPipe) teamId: number,
|
||||||
|
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||||
|
@Param("linkId") linkId: string,
|
||||||
|
@Body() body: Omit<UpdatePrivateLinkInput, "linkId">
|
||||||
|
): Promise<UpdatePrivateLinkOutput> {
|
||||||
|
await this.teamsEventTypesService.validateEventTypeExists(teamId, eventTypeId);
|
||||||
|
const updateInput: UpdatePrivateLinkInput = { ...body, linkId };
|
||||||
|
const privateLink = await this.privateLinksService.updatePrivateLink(eventTypeId, updateInput);
|
||||||
|
return {
|
||||||
|
status: SUCCESS_STATUS,
|
||||||
|
data: privateLink,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("/:linkId")
|
||||||
|
@Roles("TEAM_ADMIN")
|
||||||
|
@PlatformPlan("ESSENTIALS")
|
||||||
|
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
|
||||||
|
@ApiOperation({ summary: "Delete a private link for a team event type" })
|
||||||
|
async deletePrivateLink(
|
||||||
|
@Param("teamId", ParseIntPipe) teamId: number,
|
||||||
|
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||||
|
@Param("linkId") linkId: string
|
||||||
|
): Promise<DeletePrivateLinkOutput> {
|
||||||
|
await this.teamsEventTypesService.validateEventTypeExists(teamId, eventTypeId);
|
||||||
|
await this.privateLinksService.deletePrivateLink(eventTypeId, linkId);
|
||||||
|
return {
|
||||||
|
status: SUCCESS_STATUS,
|
||||||
|
data: {
|
||||||
|
linkId,
|
||||||
|
message: "Private link deleted successfully",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import { PrivateLinksOutputService } from "./services/private-links-output.servi
|
|||||||
import { PrivateLinksService } from "./services/private-links.service";
|
import { PrivateLinksService } from "./services/private-links.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TokensModule, OAuthClientModule, PrismaModule, EventTypesModule_2024_06_14],
|
imports: [TokensModule, PrismaModule, EventTypesModule_2024_06_14],
|
||||||
controllers: [EventTypesPrivateLinksController],
|
controllers: [EventTypesPrivateLinksController],
|
||||||
providers: [
|
providers: [
|
||||||
PrivateLinksService,
|
PrivateLinksService,
|
||||||
@@ -21,5 +21,6 @@ import { PrivateLinksService } from "./services/private-links.service";
|
|||||||
PrivateLinksRepository,
|
PrivateLinksRepository,
|
||||||
EventTypeOwnershipGuard,
|
EventTypeOwnershipGuard,
|
||||||
],
|
],
|
||||||
|
exports: [PrivateLinksService],
|
||||||
})
|
})
|
||||||
export class EventTypesPrivateLinksModule {}
|
export class EventTypesPrivateLinksModule {}
|
||||||
|
|||||||
@@ -68,11 +68,7 @@ export class PrivateLinksService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePrivateLink(
|
async updatePrivateLink(eventTypeId: number, input: UpdatePrivateLinkInput): Promise<PrivateLinkOutput> {
|
||||||
eventTypeId: number,
|
|
||||||
userId: number,
|
|
||||||
input: UpdatePrivateLinkInput
|
|
||||||
): Promise<PrivateLinkOutput> {
|
|
||||||
try {
|
try {
|
||||||
const transformedInput = this.inputService.transformUpdateInput(input);
|
const transformedInput = this.inputService.transformUpdateInput(input);
|
||||||
const updatedResult = await this.repo.update(eventTypeId, {
|
const updatedResult = await this.repo.update(eventTypeId, {
|
||||||
@@ -106,7 +102,7 @@ export class PrivateLinksService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deletePrivateLink(eventTypeId: number, userId: number, linkId: string): Promise<void> {
|
async deletePrivateLink(eventTypeId: number, linkId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { count } = await this.repo.delete(eventTypeId, linkId);
|
const { count } = await this.repo.delete(eventTypeId, linkId);
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
|
|||||||
+551
@@ -0,0 +1,551 @@
|
|||||||
|
import { bootstrap } from "@/app";
|
||||||
|
import { AppModule } from "@/app.module";
|
||||||
|
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||||
|
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||||
|
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
|
||||||
|
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||||
|
import { OrganizationsTeamsBookingsModule } from "@/modules/organizations/teams/bookings/organizations-teams-bookings.module";
|
||||||
|
import { INestApplication } from "@nestjs/common";
|
||||||
|
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||||
|
import { Test } from "@nestjs/testing";
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
import * as request from "supertest";
|
||||||
|
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||||
|
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||||
|
import { HostsRepositoryFixture } from "test/fixtures/repository/hosts.repository.fixture";
|
||||||
|
import { ManagedOrganizationsRepositoryFixture } from "test/fixtures/repository/managed-organizations.repository.fixture";
|
||||||
|
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||||
|
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
|
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||||
|
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||||
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
|
import { randomString } from "@calcom/lib/random";
|
||||||
|
import {
|
||||||
|
CAL_API_VERSION_HEADER,
|
||||||
|
SUCCESS_STATUS,
|
||||||
|
VERSION_2024_08_13,
|
||||||
|
X_CAL_CLIENT_ID,
|
||||||
|
X_CAL_SECRET_KEY,
|
||||||
|
} from "@calcom/platform-constants";
|
||||||
|
import {
|
||||||
|
BookingOutput_2024_08_13,
|
||||||
|
RecurringBookingOutput_2024_08_13,
|
||||||
|
GetBookingsOutput_2024_08_13,
|
||||||
|
GetSeatedBookingOutput_2024_08_13,
|
||||||
|
} from "@calcom/platform-types";
|
||||||
|
import { PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||||
|
|
||||||
|
describe("Organizations Bookings Endpoints 2024-08-13", () => {
|
||||||
|
describe("Manager and managed organizations bookings", () => {
|
||||||
|
let app: INestApplication;
|
||||||
|
let managerOrganization: Team;
|
||||||
|
let managedOrganization: Team;
|
||||||
|
|
||||||
|
let userRepositoryFixture: UserRepositoryFixture;
|
||||||
|
let bookingsRepositoryFixture: BookingsRepositoryFixture;
|
||||||
|
let schedulesService: SchedulesService_2024_04_15;
|
||||||
|
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||||
|
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
||||||
|
let oAuthClient: PlatformOAuthClient;
|
||||||
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
|
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||||
|
let hostsRepositoryFixture: HostsRepositoryFixture;
|
||||||
|
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||||
|
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||||
|
let managedOrganizationsRepositoryFixture: ManagedOrganizationsRepositoryFixture;
|
||||||
|
|
||||||
|
const managerOrgUserEmail = "manager-org-user-1-bookings@api.com";
|
||||||
|
const managerOrgUserEmail2 = "manager-org-user-2-bookings@api.com";
|
||||||
|
const managedOrgUserEmail = "managed-org-user-1-bookings@api.com";
|
||||||
|
const nonOrgUserEmail1 = "non-org-user-1-bookings@api.com";
|
||||||
|
let managerOrgUser1: User;
|
||||||
|
let managerOrgUser2: User;
|
||||||
|
let managedOrgUser: User;
|
||||||
|
let nonOrgUser1: User;
|
||||||
|
let managerOrgTeam1: Team;
|
||||||
|
let managedOrgTeam1: Team;
|
||||||
|
|
||||||
|
let managerOrgEventTypeId: number;
|
||||||
|
let managedOrgEventTypeId: number;
|
||||||
|
let nonOrgEventTypeId: number;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const moduleRef = await withApiAuth(
|
||||||
|
managerOrgUserEmail,
|
||||||
|
Test.createTestingModule({
|
||||||
|
imports: [AppModule, OrganizationsTeamsBookingsModule],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.overrideGuard(PermissionsGuard)
|
||||||
|
.useValue({
|
||||||
|
canActivate: () => true,
|
||||||
|
})
|
||||||
|
.overrideGuard(PlatformPlanGuard)
|
||||||
|
.useValue({
|
||||||
|
canActivate: () => true,
|
||||||
|
})
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||||
|
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
|
||||||
|
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||||
|
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||||
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
|
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||||
|
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||||
|
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||||
|
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||||
|
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||||
|
managedOrganizationsRepositoryFixture = new ManagedOrganizationsRepositoryFixture(moduleRef);
|
||||||
|
|
||||||
|
await setupManagerOrganization();
|
||||||
|
await setupManagedOrganization();
|
||||||
|
|
||||||
|
app = moduleRef.createNestApplication();
|
||||||
|
bootstrap(app as NestExpressApplication);
|
||||||
|
|
||||||
|
await app.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function setupManagerOrganization() {
|
||||||
|
managerOrganization = await organizationsRepositoryFixture.create({
|
||||||
|
name: "organization bookings",
|
||||||
|
isPlatform: true,
|
||||||
|
});
|
||||||
|
managerOrgTeam1 = await teamRepositoryFixture.create({
|
||||||
|
name: "team orgs booking 1",
|
||||||
|
isOrganization: false,
|
||||||
|
parent: { connect: { id: managerOrganization.id } },
|
||||||
|
});
|
||||||
|
oAuthClient = await createOAuthClient(managerOrganization.id);
|
||||||
|
|
||||||
|
nonOrgUser1 = await userRepositoryFixture.create({
|
||||||
|
email: nonOrgUserEmail1,
|
||||||
|
locale: "it",
|
||||||
|
name: "NonOrgUser1Bookings",
|
||||||
|
platformOAuthClients: {
|
||||||
|
connect: {
|
||||||
|
id: oAuthClient.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
managerOrgUser1 = await userRepositoryFixture.create({
|
||||||
|
email: managerOrgUserEmail,
|
||||||
|
locale: "it",
|
||||||
|
name: "orgUser1Bookings",
|
||||||
|
platformOAuthClients: {
|
||||||
|
connect: {
|
||||||
|
id: oAuthClient.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
managerOrgUser2 = await userRepositoryFixture.create({
|
||||||
|
email: managerOrgUserEmail2,
|
||||||
|
locale: "it",
|
||||||
|
name: "orgUser2Bookings",
|
||||||
|
platformOAuthClients: {
|
||||||
|
connect: {
|
||||||
|
id: oAuthClient.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
|
name: "working time",
|
||||||
|
timeZone: "Europe/Rome",
|
||||||
|
isDefault: true,
|
||||||
|
};
|
||||||
|
await schedulesService.createUserSchedule(managerOrgUser1.id, userSchedule);
|
||||||
|
await schedulesService.createUserSchedule(nonOrgUser1.id, userSchedule);
|
||||||
|
await schedulesService.createUserSchedule(managerOrgUser2.id, userSchedule);
|
||||||
|
|
||||||
|
const orgEventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||||
|
schedulingType: "COLLECTIVE",
|
||||||
|
team: {
|
||||||
|
connect: { id: managerOrgTeam1.id },
|
||||||
|
},
|
||||||
|
title: "Collective Event Type",
|
||||||
|
slug: "manager-org-bookings-collective-event-type",
|
||||||
|
length: 60,
|
||||||
|
assignAllTeamMembers: true,
|
||||||
|
bookingFields: [],
|
||||||
|
locations: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await profileRepositoryFixture.create({
|
||||||
|
uid: `usr-${managerOrgUser1.id}`,
|
||||||
|
username: managerOrgUserEmail,
|
||||||
|
organization: {
|
||||||
|
connect: {
|
||||||
|
id: managerOrganization.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: managerOrgUser1.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await profileRepositoryFixture.create({
|
||||||
|
uid: `usr-${managerOrgUser2.id}`,
|
||||||
|
username: managerOrgUserEmail2,
|
||||||
|
organization: {
|
||||||
|
connect: {
|
||||||
|
id: managerOrganization.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: managerOrgUser2.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await membershipsRepositoryFixture.create({
|
||||||
|
role: "OWNER",
|
||||||
|
user: { connect: { id: managerOrgUser1.id } },
|
||||||
|
team: { connect: { id: managerOrganization.id } },
|
||||||
|
accepted: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await membershipsRepositoryFixture.create({
|
||||||
|
role: "MEMBER",
|
||||||
|
user: { connect: { id: managerOrgUser2.id } },
|
||||||
|
team: { connect: { id: managerOrganization.id } },
|
||||||
|
accepted: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const nonOrgEventType = await eventTypesRepositoryFixture.create(
|
||||||
|
{
|
||||||
|
title: "Non Org Event Type",
|
||||||
|
slug: "non-org-event-type",
|
||||||
|
length: 60,
|
||||||
|
bookingFields: [],
|
||||||
|
locations: [],
|
||||||
|
},
|
||||||
|
nonOrgUser1.id
|
||||||
|
);
|
||||||
|
|
||||||
|
managerOrgEventTypeId = orgEventType.id;
|
||||||
|
nonOrgEventTypeId = nonOrgEventType.id;
|
||||||
|
|
||||||
|
await hostsRepositoryFixture.create({
|
||||||
|
isFixed: true,
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: managerOrgUser2.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eventType: {
|
||||||
|
connect: {
|
||||||
|
id: managerOrgEventTypeId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await bookingsRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: managerOrgUser2.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
startTime: new Date(Date.UTC(2030, 0, 9, 13, 0, 0)),
|
||||||
|
endTime: new Date(Date.UTC(2030, 0, 9, 14, 0, 0)),
|
||||||
|
title: "Manager Org Collective Booking",
|
||||||
|
uid: `manager-org-collective-${randomString()}`,
|
||||||
|
eventType: {
|
||||||
|
connect: {
|
||||||
|
id: managerOrgEventTypeId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location: "https://meet.google.com/abc-def-ghi",
|
||||||
|
customInputs: {},
|
||||||
|
metadata: {},
|
||||||
|
responses: {
|
||||||
|
name: "alice",
|
||||||
|
email: "alice@gmail.com",
|
||||||
|
},
|
||||||
|
attendees: {
|
||||||
|
create: {
|
||||||
|
email: "alice@gmail.com",
|
||||||
|
name: "alice",
|
||||||
|
locale: "es",
|
||||||
|
timeZone: "Europe/Madrid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await bookingsRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: nonOrgUser1.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
startTime: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)),
|
||||||
|
endTime: new Date(Date.UTC(2030, 0, 8, 14, 0, 0)),
|
||||||
|
title: "Non-Org Booking 1",
|
||||||
|
uid: `non-org-booking-1-${randomString()}`,
|
||||||
|
eventType: {
|
||||||
|
connect: {
|
||||||
|
id: nonOrgEventTypeId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location: "https://meet.google.com/jkl-mno-pqr",
|
||||||
|
customInputs: {},
|
||||||
|
metadata: {},
|
||||||
|
responses: {
|
||||||
|
name: managerOrgUser2.name ?? "",
|
||||||
|
email: managerOrgUserEmail2,
|
||||||
|
},
|
||||||
|
attendees: {
|
||||||
|
create: {
|
||||||
|
email: managerOrgUserEmail2,
|
||||||
|
name: managerOrgUser2.name ?? "",
|
||||||
|
locale: "en",
|
||||||
|
timeZone: managerOrgUser2.timeZone ?? "Europe/Madrid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupManagedOrganization() {
|
||||||
|
managedOrganization = await organizationsRepositoryFixture.create({
|
||||||
|
name: "organization bookings",
|
||||||
|
isPlatform: true,
|
||||||
|
});
|
||||||
|
await managedOrganizationsRepositoryFixture.createManagedOrganization(
|
||||||
|
managerOrganization.id,
|
||||||
|
managedOrganization.id
|
||||||
|
);
|
||||||
|
|
||||||
|
managedOrgTeam1 = await teamRepositoryFixture.create({
|
||||||
|
name: "managed org team 1",
|
||||||
|
isOrganization: false,
|
||||||
|
parent: { connect: { id: managedOrganization.id } },
|
||||||
|
});
|
||||||
|
|
||||||
|
managedOrgUser = await userRepositoryFixture.create({
|
||||||
|
email: managedOrgUserEmail,
|
||||||
|
locale: "it",
|
||||||
|
name: "ManagedOrgUser1Bookings",
|
||||||
|
platformOAuthClients: {
|
||||||
|
connect: {
|
||||||
|
id: oAuthClient.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await profileRepositoryFixture.create({
|
||||||
|
uid: `usr-${managedOrgUser.id}`,
|
||||||
|
username: managedOrgUserEmail,
|
||||||
|
organization: {
|
||||||
|
connect: {
|
||||||
|
id: managedOrganization.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: managedOrgUser.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await profileRepositoryFixture.create({
|
||||||
|
uid: `usr-${managerOrgUser1.id}`,
|
||||||
|
username: managerOrgUserEmail,
|
||||||
|
organization: {
|
||||||
|
connect: {
|
||||||
|
id: managedOrganization.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: managerOrgUser1.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await membershipsRepositoryFixture.create({
|
||||||
|
role: "MEMBER",
|
||||||
|
user: { connect: { id: managedOrgUser.id } },
|
||||||
|
team: { connect: { id: managedOrganization.id } },
|
||||||
|
accepted: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await membershipsRepositoryFixture.create({
|
||||||
|
role: "OWNER",
|
||||||
|
user: { connect: { id: managerOrgUser1.id } },
|
||||||
|
team: { connect: { id: managedOrganization.id } },
|
||||||
|
accepted: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
|
name: "working time",
|
||||||
|
timeZone: "Europe/Rome",
|
||||||
|
isDefault: true,
|
||||||
|
};
|
||||||
|
await schedulesService.createUserSchedule(managedOrgUser.id, userSchedule);
|
||||||
|
|
||||||
|
const managedOrgEventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||||
|
schedulingType: "COLLECTIVE",
|
||||||
|
team: {
|
||||||
|
connect: { id: managedOrgTeam1.id },
|
||||||
|
},
|
||||||
|
title: "Collective Event Type",
|
||||||
|
slug: "managed-org-bookings-collective-event-type",
|
||||||
|
length: 60,
|
||||||
|
assignAllTeamMembers: true,
|
||||||
|
bookingFields: [],
|
||||||
|
locations: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
managedOrgEventTypeId = managedOrgEventType.id;
|
||||||
|
|
||||||
|
await hostsRepositoryFixture.create({
|
||||||
|
isFixed: true,
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: managedOrgUser.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eventType: {
|
||||||
|
connect: {
|
||||||
|
id: managedOrgEventType.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await bookingsRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: managedOrgUser.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
startTime: new Date(Date.UTC(2030, 0, 11, 13, 0, 0)),
|
||||||
|
endTime: new Date(Date.UTC(2030, 0, 11, 14, 0, 0)),
|
||||||
|
title: "Managed Org Booking",
|
||||||
|
uid: `managed-org-booking-${randomString()}`,
|
||||||
|
eventType: {
|
||||||
|
connect: {
|
||||||
|
id: managedOrgEventTypeId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location: "https://meet.google.com/ghi-jkl-mno",
|
||||||
|
customInputs: {},
|
||||||
|
metadata: {},
|
||||||
|
responses: {
|
||||||
|
name: "charlie",
|
||||||
|
email: "charlie@gmail.com",
|
||||||
|
},
|
||||||
|
attendees: {
|
||||||
|
create: {
|
||||||
|
email: "charlie@gmail.com",
|
||||||
|
name: "charlie",
|
||||||
|
locale: "en",
|
||||||
|
timeZone: "Europe/Madrid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("get manager organization bookings", () => {
|
||||||
|
it("should get bookings by organizationId for manager organization", async () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/organizations/${managerOrganization.id}/bookings`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set(X_CAL_CLIENT_ID, oAuthClient.id)
|
||||||
|
.set(X_CAL_SECRET_KEY, oAuthClient.secret)
|
||||||
|
.expect(200)
|
||||||
|
.then(async (response) => {
|
||||||
|
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
const data: (
|
||||||
|
| BookingOutput_2024_08_13
|
||||||
|
| RecurringBookingOutput_2024_08_13
|
||||||
|
| GetSeatedBookingOutput_2024_08_13
|
||||||
|
)[] = responseBody.data;
|
||||||
|
expect(data.length).toEqual(2);
|
||||||
|
expect(data.map((booking) => booking.eventTypeId).sort()).toEqual(
|
||||||
|
[managerOrgEventTypeId, nonOrgEventTypeId].sort()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get bookings by organizationId and userId", async () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/organizations/${managerOrganization.id}/bookings?userIds=${managerOrgUser2.id}`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.expect(200)
|
||||||
|
.then(async (response) => {
|
||||||
|
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
const data: (
|
||||||
|
| BookingOutput_2024_08_13
|
||||||
|
| RecurringBookingOutput_2024_08_13
|
||||||
|
| GetSeatedBookingOutput_2024_08_13
|
||||||
|
)[] = responseBody.data;
|
||||||
|
expect(data.length).toEqual(2);
|
||||||
|
expect(data.map((booking) => booking.eventTypeId).sort()).toEqual(
|
||||||
|
[managerOrgEventTypeId, nonOrgEventTypeId].sort()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("get managed organization bookings", () => {
|
||||||
|
it("should get bookings by organizationId for managed organization", async () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/organizations/${managedOrganization.id}/bookings`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set(X_CAL_CLIENT_ID, oAuthClient.id)
|
||||||
|
.set(X_CAL_SECRET_KEY, oAuthClient.secret)
|
||||||
|
.expect(200)
|
||||||
|
.then(async (response) => {
|
||||||
|
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
const data: (
|
||||||
|
| BookingOutput_2024_08_13
|
||||||
|
| RecurringBookingOutput_2024_08_13
|
||||||
|
| GetSeatedBookingOutput_2024_08_13
|
||||||
|
)[] = responseBody.data;
|
||||||
|
expect(data.length).toEqual(1);
|
||||||
|
expect(data[0].eventTypeId).toEqual(managedOrgEventTypeId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function createOAuthClient(organizationId: number) {
|
||||||
|
const data = {
|
||||||
|
logo: "logo-url",
|
||||||
|
name: "name",
|
||||||
|
redirectUris: ["http://localhost:5555"],
|
||||||
|
permissions: 32,
|
||||||
|
areCalendarEventsEnabled: false,
|
||||||
|
areEmailsEnabled: false,
|
||||||
|
};
|
||||||
|
const secret = "secret";
|
||||||
|
|
||||||
|
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
||||||
|
await teamRepositoryFixture.delete(managerOrganization.id);
|
||||||
|
await userRepositoryFixture.deleteByEmail(managerOrgUser1.email);
|
||||||
|
await userRepositoryFixture.deleteByEmail(nonOrgUser1.email);
|
||||||
|
await bookingsRepositoryFixture.deleteAllBookings(managerOrgUser1.id, managerOrgUser1.email);
|
||||||
|
await bookingsRepositoryFixture.deleteAllBookings(nonOrgUser1.id, nonOrgUser1.email);
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+183
@@ -0,0 +1,183 @@
|
|||||||
|
import { bootstrap } from "@/app";
|
||||||
|
import { AppModule } from "@/app.module";
|
||||||
|
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||||
|
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||||
|
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||||
|
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
|
||||||
|
import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard";
|
||||||
|
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||||
|
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||||
|
import { UsersModule } from "@/modules/users/users.module";
|
||||||
|
import { INestApplication } from "@nestjs/common";
|
||||||
|
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||||
|
import { Test } from "@nestjs/testing";
|
||||||
|
import * as request from "supertest";
|
||||||
|
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||||
|
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||||
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
|
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
|
import { CreatePrivateLinkInput } from "@calcom/platform-types";
|
||||||
|
|
||||||
|
describe("Organizations / Teams / Event Types / Private Links Endpoints", () => {
|
||||||
|
let app: INestApplication;
|
||||||
|
|
||||||
|
let orgFixture: OrganizationRepositoryFixture;
|
||||||
|
let teamFixture: TeamRepositoryFixture;
|
||||||
|
let userFixture: UserRepositoryFixture;
|
||||||
|
let eventTypesFixture: EventTypesRepositoryFixture;
|
||||||
|
|
||||||
|
let org: any;
|
||||||
|
let team: any;
|
||||||
|
let user: any;
|
||||||
|
let eventType: any;
|
||||||
|
|
||||||
|
const userEmail = `org-private-links-user-${randomString()}@api.com`;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const testingModuleBuilder = withApiAuth(
|
||||||
|
userEmail,
|
||||||
|
Test.createTestingModule({
|
||||||
|
providers: [PrismaExceptionFilter, HttpExceptionFilter],
|
||||||
|
imports: [AppModule, UsersModule, TokensModule],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
// Bypass org admin plan and admin API checks and roles in this e2e
|
||||||
|
.overrideGuard(PlatformPlanGuard)
|
||||||
|
.useValue({ canActivate: () => true })
|
||||||
|
.overrideGuard(IsAdminAPIEnabledGuard)
|
||||||
|
.useValue({ canActivate: () => true })
|
||||||
|
.overrideGuard(RolesGuard)
|
||||||
|
.useValue({ canActivate: () => true })
|
||||||
|
// Keep IsOrgGuard and IsTeamInOrg to validate org/team path integrity
|
||||||
|
.overrideGuard(ApiAuthGuard)
|
||||||
|
.useValue({ canActivate: () => true });
|
||||||
|
|
||||||
|
const moduleRef = await testingModuleBuilder.compile();
|
||||||
|
|
||||||
|
app = moduleRef.createNestApplication();
|
||||||
|
bootstrap(app as NestExpressApplication);
|
||||||
|
|
||||||
|
orgFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||||
|
teamFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
|
userFixture = new UserRepositoryFixture(moduleRef);
|
||||||
|
eventTypesFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||||
|
|
||||||
|
user = await userFixture.create({
|
||||||
|
email: userEmail,
|
||||||
|
username: `org-private-links-user-${randomString()}`,
|
||||||
|
name: "Test User",
|
||||||
|
});
|
||||||
|
|
||||||
|
org = await orgFixture.create({
|
||||||
|
name: `org-private-links-org-${randomString()}`,
|
||||||
|
slug: `org-private-links-org-${randomString()}`,
|
||||||
|
isOrganization: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
team = await teamFixture.create({
|
||||||
|
name: `org-private-links-team-${randomString()}`,
|
||||||
|
isOrganization: false,
|
||||||
|
parent: { connect: { id: org.id } },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a team-owned event type
|
||||||
|
eventType = await eventTypesFixture.createTeamEventType({
|
||||||
|
title: `org-private-links-event-type-${randomString()}`,
|
||||||
|
slug: `org-private-links-event-type-${randomString()}`,
|
||||||
|
length: 30,
|
||||||
|
locations: [],
|
||||||
|
team: { connect: { id: team.id } },
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("POST /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links - create", async () => {
|
||||||
|
const body: CreatePrivateLinkInput = { maxUsageCount: 5 };
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`)
|
||||||
|
.set("Authorization", "Bearer test")
|
||||||
|
.send(body)
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
expect(response.body.status).toBe(SUCCESS_STATUS);
|
||||||
|
expect(response.body.data.linkId).toBeDefined();
|
||||||
|
expect(response.body.data.maxUsageCount).toBe(5);
|
||||||
|
expect(response.body.data.usageCount).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("GET /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links - list", async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.get(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`)
|
||||||
|
.set("Authorization", "Bearer test")
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.status).toBe(SUCCESS_STATUS);
|
||||||
|
expect(Array.isArray(response.body.data)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("PATCH /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links/:linkId - update", async () => {
|
||||||
|
// create first
|
||||||
|
const createResp = await request(app.getHttpServer())
|
||||||
|
.post(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`)
|
||||||
|
.set("Authorization", "Bearer test")
|
||||||
|
.send({ maxUsageCount: 3 })
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
const linkId = createResp.body.data.linkId as string;
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.patch(
|
||||||
|
`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links/${linkId}`
|
||||||
|
)
|
||||||
|
.set("Authorization", "Bearer test")
|
||||||
|
.send({ maxUsageCount: 10 })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.status).toBe(SUCCESS_STATUS);
|
||||||
|
expect(response.body.data.maxUsageCount).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("DELETE /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links/:linkId - delete", async () => {
|
||||||
|
// create first
|
||||||
|
const createResp = await request(app.getHttpServer())
|
||||||
|
.post(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`)
|
||||||
|
.set("Authorization", "Bearer test")
|
||||||
|
.send({ maxUsageCount: 2 })
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
const linkId = createResp.body.data.linkId as string;
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.delete(
|
||||||
|
`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links/${linkId}`
|
||||||
|
)
|
||||||
|
.set("Authorization", "Bearer test")
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.status).toBe(SUCCESS_STATUS);
|
||||||
|
expect(response.body.data.linkId).toBe(linkId);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
try {
|
||||||
|
if (eventType?.id) {
|
||||||
|
await eventTypesFixture.delete(eventType.id);
|
||||||
|
}
|
||||||
|
if (team?.id) {
|
||||||
|
await teamFixture.delete(team.id);
|
||||||
|
}
|
||||||
|
if (org?.id) {
|
||||||
|
await orgFixture.delete(org.id);
|
||||||
|
}
|
||||||
|
if (user?.email) {
|
||||||
|
await userFixture.deleteByEmail(user.email);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -23,6 +23,8 @@ import { OrganizationsConferencingModule } from "@/modules/organizations/confere
|
|||||||
import { OrganizationsConferencingService } from "@/modules/organizations/conferencing/services/organizations-conferencing.service";
|
import { OrganizationsConferencingService } from "@/modules/organizations/conferencing/services/organizations-conferencing.service";
|
||||||
import { OrganizationsDelegationCredentialModule } from "@/modules/organizations/delegation-credentials/organizations-delegation-credential.module";
|
import { OrganizationsDelegationCredentialModule } from "@/modules/organizations/delegation-credentials/organizations-delegation-credential.module";
|
||||||
import { OrganizationsEventTypesController } from "@/modules/organizations/event-types/organizations-event-types.controller";
|
import { OrganizationsEventTypesController } from "@/modules/organizations/event-types/organizations-event-types.controller";
|
||||||
|
import { EventTypesPrivateLinksModule } from "@/ee/event-types-private-links/event-types-private-links.module";
|
||||||
|
import { OrganizationsEventTypesPrivateLinksController } from "@/ee/event-types-private-links/controllers/organizations-event-types-private-links.controller";
|
||||||
import { OrganizationsEventTypesRepository } from "@/modules/organizations/event-types/organizations-event-types.repository";
|
import { OrganizationsEventTypesRepository } from "@/modules/organizations/event-types/organizations-event-types.repository";
|
||||||
import { OutputTeamEventTypesResponsePipe } from "@/modules/organizations/event-types/pipes/team-event-types-response.transformer";
|
import { OutputTeamEventTypesResponsePipe } from "@/modules/organizations/event-types/pipes/team-event-types-response.transformer";
|
||||||
import { InputOrganizationsEventTypesService } from "@/modules/organizations/event-types/services/input.service";
|
import { InputOrganizationsEventTypesService } from "@/modules/organizations/event-types/services/input.service";
|
||||||
@@ -91,6 +93,7 @@ import { Module } from "@nestjs/common";
|
|||||||
OrganizationsStripeModule,
|
OrganizationsStripeModule,
|
||||||
OrganizationsTeamsRoutingFormsModule,
|
OrganizationsTeamsRoutingFormsModule,
|
||||||
OrganizationsConferencingModule,
|
OrganizationsConferencingModule,
|
||||||
|
EventTypesPrivateLinksModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
OrganizationsRepository,
|
OrganizationsRepository,
|
||||||
@@ -177,6 +180,7 @@ import { Module } from "@nestjs/common";
|
|||||||
OrganizationsTeamsSchedulesController,
|
OrganizationsTeamsSchedulesController,
|
||||||
OrganizationsUsersOOOController,
|
OrganizationsUsersOOOController,
|
||||||
OrganizationTeamWorkflowsController,
|
OrganizationTeamWorkflowsController,
|
||||||
|
OrganizationsEventTypesPrivateLinksController,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class OrganizationsModule {}
|
export class OrganizationsModule {}
|
||||||
|
|||||||
@@ -4384,6 +4384,292 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "OrganizationsEventTypesPrivateLinksController_createPrivateLink",
|
||||||
|
"summary": "Create a private link for a team event type",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventTypeId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreatePrivateLinkInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreatePrivateLinkOutput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Orgs / Teams / Event Types / Private Links"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"get": {
|
||||||
|
"operationId": "OrganizationsEventTypesPrivateLinksController_getPrivateLinks",
|
||||||
|
"summary": "Get all private links for a team event type",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventTypeId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/GetPrivateLinksOutput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Orgs / Teams / Event Types / Private Links"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links/{linkId}": {
|
||||||
|
"patch": {
|
||||||
|
"operationId": "OrganizationsEventTypesPrivateLinksController_updatePrivateLink",
|
||||||
|
"summary": "Update a private link for a team event type",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventTypeId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linkId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UpdatePrivateLinkOutput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Orgs / Teams / Event Types / Private Links"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"operationId": "OrganizationsEventTypesPrivateLinksController_deletePrivateLink",
|
||||||
|
"summary": "Delete a private link for a team event type",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventTypeId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linkId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DeletePrivateLinkOutput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Orgs / Teams / Event Types / Private Links"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v2/organizations/{orgId}/teams/{teamId}/memberships": {
|
"/v2/organizations/{orgId}/teams/{teamId}/memberships": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "OrganizationsTeamsMembershipsController_getAllOrgTeamMemberships",
|
"operationId": "OrganizationsTeamsMembershipsController_getAllOrgTeamMemberships",
|
||||||
@@ -23789,6 +24075,213 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CreatePrivateLinkInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expiresAt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Expiration date for time-based links",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2024-12-31T23:59:59.000Z"
|
||||||
|
},
|
||||||
|
"maxUsageCount": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Maximum number of times the link can be used. If omitted and expiresAt is not provided, defaults to 1 (one time use).",
|
||||||
|
"example": 10,
|
||||||
|
"minimum": 1,
|
||||||
|
"default": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TimeBasedPrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"linkId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The private link ID",
|
||||||
|
"example": "abc123def456"
|
||||||
|
},
|
||||||
|
"eventTypeId": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Event type ID this link belongs to",
|
||||||
|
"example": 123
|
||||||
|
},
|
||||||
|
"isExpired": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the link is currently expired",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"bookingUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Full booking URL for this private link",
|
||||||
|
"format": "uri",
|
||||||
|
"example": "https://cal.com/d/abc123def456/30min"
|
||||||
|
},
|
||||||
|
"expiresAt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Expiration date for this time-based link",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2025-12-31T23:59:59.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"linkId",
|
||||||
|
"eventTypeId",
|
||||||
|
"isExpired",
|
||||||
|
"bookingUrl",
|
||||||
|
"expiresAt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"UsageBasedPrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"linkId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The private link ID",
|
||||||
|
"example": "abc123def456"
|
||||||
|
},
|
||||||
|
"eventTypeId": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Event type ID this link belongs to",
|
||||||
|
"example": 123
|
||||||
|
},
|
||||||
|
"isExpired": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the link is currently expired",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"bookingUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Full booking URL for this private link",
|
||||||
|
"format": "uri",
|
||||||
|
"example": "https://cal.com/d/abc123def456/30min"
|
||||||
|
},
|
||||||
|
"maxUsageCount": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Maximum number of times this link can be used",
|
||||||
|
"example": 10
|
||||||
|
},
|
||||||
|
"usageCount": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Current usage count for this link",
|
||||||
|
"example": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"linkId",
|
||||||
|
"eventTypeId",
|
||||||
|
"isExpired",
|
||||||
|
"bookingUrl",
|
||||||
|
"maxUsageCount",
|
||||||
|
"usageCount"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CreatePrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Response status",
|
||||||
|
"example": "success"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"description": "Created private link data (either time-based or usage-based)",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"GetPrivateLinksOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Response status",
|
||||||
|
"example": "success"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Array of private links for the event type (mix of time-based and usage-based)",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"UpdatePrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Response status",
|
||||||
|
"example": "success"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"description": "Updated private link data (either time-based or usage-based)",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"DeletePrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Response status",
|
||||||
|
"example": "success"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Deleted link information",
|
||||||
|
"properties": {
|
||||||
|
"linkId": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "abc123def456"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Private link deleted successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
"StripConnectOutputDto": {
|
"StripConnectOutputDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -25637,6 +26130,23 @@
|
|||||||
"data"
|
"data"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"UpdatePrivateLinkBody": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expiresAt": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string",
|
||||||
|
"description": "New expiration date for time-based links",
|
||||||
|
"example": "2024-12-31T23:59:59.000Z"
|
||||||
|
},
|
||||||
|
"maxUsageCount": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "New maximum number of times the link can be used",
|
||||||
|
"example": 10,
|
||||||
|
"minimum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MeOrgOutput": {
|
"MeOrgOutput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -28282,230 +28792,6 @@
|
|||||||
"data"
|
"data"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CreatePrivateLinkInput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"expiresAt": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Expiration date for time-based links",
|
|
||||||
"format": "date-time",
|
|
||||||
"example": "2024-12-31T23:59:59.000Z"
|
|
||||||
},
|
|
||||||
"maxUsageCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Maximum number of times the link can be used. If omitted and expiresAt is not provided, defaults to 1 (one time use).",
|
|
||||||
"example": 10,
|
|
||||||
"minimum": 1,
|
|
||||||
"default": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TimeBasedPrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"linkId": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The private link ID",
|
|
||||||
"example": "abc123def456"
|
|
||||||
},
|
|
||||||
"eventTypeId": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Event type ID this link belongs to",
|
|
||||||
"example": 123
|
|
||||||
},
|
|
||||||
"isExpired": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether the link is currently expired",
|
|
||||||
"example": false
|
|
||||||
},
|
|
||||||
"bookingUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Full booking URL for this private link",
|
|
||||||
"format": "uri",
|
|
||||||
"example": "https://cal.com/d/abc123def456/30min"
|
|
||||||
},
|
|
||||||
"expiresAt": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Expiration date for this time-based link",
|
|
||||||
"format": "date-time",
|
|
||||||
"example": "2025-12-31T23:59:59.000Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"linkId",
|
|
||||||
"eventTypeId",
|
|
||||||
"isExpired",
|
|
||||||
"bookingUrl",
|
|
||||||
"expiresAt"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"UsageBasedPrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"linkId": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The private link ID",
|
|
||||||
"example": "abc123def456"
|
|
||||||
},
|
|
||||||
"eventTypeId": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Event type ID this link belongs to",
|
|
||||||
"example": 123
|
|
||||||
},
|
|
||||||
"isExpired": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether the link is currently expired",
|
|
||||||
"example": false
|
|
||||||
},
|
|
||||||
"bookingUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Full booking URL for this private link",
|
|
||||||
"format": "uri",
|
|
||||||
"example": "https://cal.com/d/abc123def456/30min"
|
|
||||||
},
|
|
||||||
"maxUsageCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Maximum number of times this link can be used",
|
|
||||||
"example": 10
|
|
||||||
},
|
|
||||||
"usageCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Current usage count for this link",
|
|
||||||
"example": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"linkId",
|
|
||||||
"eventTypeId",
|
|
||||||
"isExpired",
|
|
||||||
"bookingUrl",
|
|
||||||
"maxUsageCount",
|
|
||||||
"usageCount"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"CreatePrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Response status",
|
|
||||||
"example": "success"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"description": "Created private link data (either time-based or usage-based)",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"status",
|
|
||||||
"data"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"GetPrivateLinksOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Response status",
|
|
||||||
"example": "success"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"type": "array",
|
|
||||||
"description": "Array of private links for the event type (mix of time-based and usage-based)",
|
|
||||||
"items": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"status",
|
|
||||||
"data"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"UpdatePrivateLinkBody": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"expiresAt": {
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string",
|
|
||||||
"description": "New expiration date for time-based links",
|
|
||||||
"example": "2024-12-31T23:59:59.000Z"
|
|
||||||
},
|
|
||||||
"maxUsageCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "New maximum number of times the link can be used",
|
|
||||||
"example": 10,
|
|
||||||
"minimum": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"UpdatePrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Response status",
|
|
||||||
"example": "success"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"description": "Updated private link data (either time-based or usage-based)",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"status",
|
|
||||||
"data"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"DeletePrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Response status",
|
|
||||||
"example": "success"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Deleted link information",
|
|
||||||
"properties": {
|
|
||||||
"linkId": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "abc123def456"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "Private link deleted successfully"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"status",
|
|
||||||
"data"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"UserWebhookOutputDto": {
|
"UserWebhookOutputDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
+9
@@ -34,4 +34,13 @@ export class ManagedOrganizationsRepositoryFixture {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createManagedOrganization(managerOrganizationId: number, managedOrganizationId: number) {
|
||||||
|
return this.prismaWriteClient.managedOrganization.create({
|
||||||
|
data: {
|
||||||
|
managerOrganizationId,
|
||||||
|
managedOrganizationId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ export type PageProps = {
|
|||||||
|
|
||||||
export type LayoutProps = { params: Promise<Params>; children: React.ReactElement };
|
export type LayoutProps = { params: Promise<Params>; children: React.ReactElement };
|
||||||
|
|
||||||
export { type ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
|
export type ReadonlyHeaders = Awaited<ReturnType<typeof import("next/headers").headers>>;
|
||||||
export { type ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
|
export type ReadonlyRequestCookies = Awaited<ReturnType<typeof import("next/headers").cookies>>;
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ async function getHandler(request: NextRequest) {
|
|||||||
|
|
||||||
buffer = await optimizeImage({
|
buffer = await optimizeImage({
|
||||||
buffer,
|
buffer,
|
||||||
contentType: detectContentType(buffer) ?? "image/jpeg",
|
contentType: (await detectContentType(buffer)) ?? "image/jpeg",
|
||||||
quality: 100,
|
quality: 100,
|
||||||
width: logoDefinition.w,
|
width: logoDefinition.w,
|
||||||
height: logoDefinition.h, // optional
|
height: logoDefinition.h, // optional
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import type { SearchParams } from "app/_types";
|
import type { SearchParams } from "app/_types";
|
||||||
import { type Params } from "app/_types";
|
import { type Params } from "app/_types";
|
||||||
|
import type { ReadonlyHeaders, ReadonlyRequestCookies } from "app/_types";
|
||||||
import type { GetServerSidePropsContext, NextApiRequest } from "next";
|
import type { GetServerSidePropsContext, NextApiRequest } from "next";
|
||||||
import { type ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
|
|
||||||
import { type ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
|
|
||||||
|
|
||||||
const createProxifiedObject = (object: Record<string, string>) =>
|
const createProxifiedObject = (object: Record<string, string>) =>
|
||||||
new Proxy(object, {
|
new Proxy(object, {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"next": "^15.3.0",
|
"next": "^15.4.5",
|
||||||
"next-auth": "^4.22.1",
|
"next-auth": "^4.22.1",
|
||||||
"next-axiom": "^0.17.0",
|
"next-axiom": "^0.17.0",
|
||||||
"next-collect": "^0.2.1",
|
"next-collect": "^0.2.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { IncomingMessage } from "http";
|
import type { IncomingMessage } from "http";
|
||||||
|
import type { NextPageContext } from "next";
|
||||||
import { SessionProvider } from "next-auth/react";
|
import { SessionProvider } from "next-auth/react";
|
||||||
import type { AppContextType } from "next/dist/shared/lib/utils";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import CacheProvider from "react-inlinesvg/provider";
|
import CacheProvider from "react-inlinesvg/provider";
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MyApp.getInitialProps = async (ctx: AppContextType) => {
|
MyApp.getInitialProps = async ({ ctx }: { ctx: NextPageContext }) => {
|
||||||
const { req } = ctx.ctx;
|
const { req } = ctx;
|
||||||
|
|
||||||
let newLocale = "en";
|
let newLocale = "en";
|
||||||
|
|
||||||
|
|||||||
@@ -4193,6 +4193,292 @@
|
|||||||
"tags": ["Orgs / Teams / Event Types"]
|
"tags": ["Orgs / Teams / Event Types"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "OrganizationsEventTypesPrivateLinksController_createPrivateLink",
|
||||||
|
"summary": "Create a private link for a team event type",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventTypeId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreatePrivateLinkInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreatePrivateLinkOutput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Orgs / Teams / Event Types / Private Links"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"get": {
|
||||||
|
"operationId": "OrganizationsEventTypesPrivateLinksController_getPrivateLinks",
|
||||||
|
"summary": "Get all private links for a team event type",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventTypeId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/GetPrivateLinksOutput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Orgs / Teams / Event Types / Private Links"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links/{linkId}": {
|
||||||
|
"patch": {
|
||||||
|
"operationId": "OrganizationsEventTypesPrivateLinksController_updatePrivateLink",
|
||||||
|
"summary": "Update a private link for a team event type",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventTypeId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linkId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UpdatePrivateLinkOutput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Orgs / Teams / Event Types / Private Links"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"operationId": "OrganizationsEventTypesPrivateLinksController_deletePrivateLink",
|
||||||
|
"summary": "Delete a private link for a team event type",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventTypeId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linkId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DeletePrivateLinkOutput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Orgs / Teams / Event Types / Private Links"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v2/organizations/{orgId}/teams/{teamId}/memberships": {
|
"/v2/organizations/{orgId}/teams/{teamId}/memberships": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "OrganizationsTeamsMembershipsController_getAllOrgTeamMemberships",
|
"operationId": "OrganizationsTeamsMembershipsController_getAllOrgTeamMemberships",
|
||||||
@@ -21786,6 +22072,213 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CreatePrivateLinkInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expiresAt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Expiration date for time-based links",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2024-12-31T23:59:59.000Z"
|
||||||
|
},
|
||||||
|
"maxUsageCount": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Maximum number of times the link can be used. If omitted and expiresAt is not provided, defaults to 1 (one time use).",
|
||||||
|
"example": 10,
|
||||||
|
"minimum": 1,
|
||||||
|
"default": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TimeBasedPrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"linkId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The private link ID",
|
||||||
|
"example": "abc123def456"
|
||||||
|
},
|
||||||
|
"eventTypeId": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Event type ID this link belongs to",
|
||||||
|
"example": 123
|
||||||
|
},
|
||||||
|
"isExpired": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the link is currently expired",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"bookingUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Full booking URL for this private link",
|
||||||
|
"format": "uri",
|
||||||
|
"example": "https://cal.com/d/abc123def456/30min"
|
||||||
|
},
|
||||||
|
"expiresAt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Expiration date for this time-based link",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2025-12-31T23:59:59.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"linkId",
|
||||||
|
"eventTypeId",
|
||||||
|
"isExpired",
|
||||||
|
"bookingUrl",
|
||||||
|
"expiresAt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"UsageBasedPrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"linkId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The private link ID",
|
||||||
|
"example": "abc123def456"
|
||||||
|
},
|
||||||
|
"eventTypeId": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Event type ID this link belongs to",
|
||||||
|
"example": 123
|
||||||
|
},
|
||||||
|
"isExpired": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the link is currently expired",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"bookingUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Full booking URL for this private link",
|
||||||
|
"format": "uri",
|
||||||
|
"example": "https://cal.com/d/abc123def456/30min"
|
||||||
|
},
|
||||||
|
"maxUsageCount": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Maximum number of times this link can be used",
|
||||||
|
"example": 10
|
||||||
|
},
|
||||||
|
"usageCount": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Current usage count for this link",
|
||||||
|
"example": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"linkId",
|
||||||
|
"eventTypeId",
|
||||||
|
"isExpired",
|
||||||
|
"bookingUrl",
|
||||||
|
"maxUsageCount",
|
||||||
|
"usageCount"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CreatePrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Response status",
|
||||||
|
"example": "success"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"description": "Created private link data (either time-based or usage-based)",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"GetPrivateLinksOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Response status",
|
||||||
|
"example": "success"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Array of private links for the event type (mix of time-based and usage-based)",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"UpdatePrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Response status",
|
||||||
|
"example": "success"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"description": "Updated private link data (either time-based or usage-based)",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"DeletePrivateLinkOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Response status",
|
||||||
|
"example": "success"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Deleted link information",
|
||||||
|
"properties": {
|
||||||
|
"linkId": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "abc123def456"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Private link deleted successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status",
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
"StripConnectOutputDto": {
|
"StripConnectOutputDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -23368,6 +23861,23 @@
|
|||||||
},
|
},
|
||||||
"required": ["status", "data"]
|
"required": ["status", "data"]
|
||||||
},
|
},
|
||||||
|
"UpdatePrivateLinkBody": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expiresAt": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string",
|
||||||
|
"description": "New expiration date for time-based links",
|
||||||
|
"example": "2024-12-31T23:59:59.000Z"
|
||||||
|
},
|
||||||
|
"maxUsageCount": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "New maximum number of times the link can be used",
|
||||||
|
"example": 10,
|
||||||
|
"minimum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MeOrgOutput": {
|
"MeOrgOutput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { parse } from "accept-language-parser";
|
|||||||
import { lookup } from "bcp-47-match";
|
import { lookup } from "bcp-47-match";
|
||||||
import type { GetTokenParams } from "next-auth/jwt";
|
import type { GetTokenParams } from "next-auth/jwt";
|
||||||
import { getToken } from "next-auth/jwt";
|
import { getToken } from "next-auth/jwt";
|
||||||
import { type ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
|
|
||||||
import { type ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
|
|
||||||
|
|
||||||
import { i18n } from "@calcom/config/next-i18next.config";
|
import { i18n } from "@calcom/config/next-i18next.config";
|
||||||
|
|
||||||
|
type ReadonlyHeaders = Awaited<ReturnType<typeof import("next/headers").headers>>;
|
||||||
|
type ReadonlyRequestCookies = Awaited<ReturnType<typeof import("next/headers").cookies>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a slimmed down version of the `getServerSession` function from
|
* This is a slimmed down version of the `getServerSession` function from
|
||||||
* `next-auth`.
|
* `next-auth`.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import dayjs from "@calcom/dayjs";
|
|||||||
import PoweredBy from "@calcom/ee/components/PoweredBy";
|
import PoweredBy from "@calcom/ee/components/PoweredBy";
|
||||||
import { updateEmbedBookerState } from "@calcom/embed-core/src/embed-iframe";
|
import { updateEmbedBookerState } from "@calcom/embed-core/src/embed-iframe";
|
||||||
import TurnstileCaptcha from "@calcom/features/auth/Turnstile";
|
import TurnstileCaptcha from "@calcom/features/auth/Turnstile";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import useSkipConfirmStep from "@calcom/features/bookings/Booker/components/hooks/useSkipConfirmStep";
|
import useSkipConfirmStep from "@calcom/features/bookings/Booker/components/hooks/useSkipConfirmStep";
|
||||||
import { getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
|
import { getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
|
||||||
import { useNonEmptyScheduleDays } from "@calcom/features/schedules/lib/use-schedule/useNonEmptyScheduleDays";
|
import { useNonEmptyScheduleDays } from "@calcom/features/schedules/lib/use-schedule/useNonEmptyScheduleDays";
|
||||||
@@ -39,7 +40,6 @@ import { NotFound } from "./components/Unavailable";
|
|||||||
import { useIsQuickAvailabilityCheckFeatureEnabled } from "./components/hooks/useIsQuickAvailabilityCheckFeatureEnabled";
|
import { useIsQuickAvailabilityCheckFeatureEnabled } from "./components/hooks/useIsQuickAvailabilityCheckFeatureEnabled";
|
||||||
import { fadeInLeft, getBookerSizeClassNames, useBookerResizeAnimation } from "./config";
|
import { fadeInLeft, getBookerSizeClassNames, useBookerResizeAnimation } from "./config";
|
||||||
import framerFeatures from "./framer-features";
|
import framerFeatures from "./framer-features";
|
||||||
import { useBookerStore } from "./store";
|
|
||||||
import type { BookerProps, WrappedBookerProps } from "./types";
|
import type { BookerProps, WrappedBookerProps } from "./types";
|
||||||
import { isBookingDryRun } from "./utils/isBookingDryRun";
|
import { isBookingDryRun } from "./utils/isBookingDryRun";
|
||||||
import { isTimeSlotAvailable } from "./utils/isTimeslotAvailable";
|
import { isTimeSlotAvailable } from "./utils/isTimeslotAvailable";
|
||||||
@@ -85,9 +85,14 @@ const BookerComponent = ({
|
|||||||
}: BookerProps & WrappedBookerProps) => {
|
}: BookerProps & WrappedBookerProps) => {
|
||||||
const searchParams = useCompatSearchParams();
|
const searchParams = useCompatSearchParams();
|
||||||
const isPlatformBookerEmbed = useIsPlatformBookerEmbed();
|
const isPlatformBookerEmbed = useIsPlatformBookerEmbed();
|
||||||
const [bookerState, setBookerState] = useBookerStore((state) => [state.state, state.setState], shallow);
|
const [bookerState, setBookerState] = useBookerStoreContext(
|
||||||
|
(state) => [state.state, state.setState],
|
||||||
|
shallow
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedDate = useBookerStoreContext((state) => state.selectedDate);
|
||||||
|
const setSelectedDate = useBookerStoreContext((state) => state.setSelectedDate);
|
||||||
|
|
||||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
|
||||||
const {
|
const {
|
||||||
shouldShowFormInDialog,
|
shouldShowFormInDialog,
|
||||||
hasDarkBackground,
|
hasDarkBackground,
|
||||||
@@ -100,12 +105,15 @@ const BookerComponent = ({
|
|||||||
bookerLayouts,
|
bookerLayouts,
|
||||||
} = bookerLayout;
|
} = bookerLayout;
|
||||||
|
|
||||||
const [seatedEventData, setSeatedEventData] = useBookerStore(
|
const [seatedEventData, setSeatedEventData] = useBookerStoreContext(
|
||||||
(state) => [state.seatedEventData, state.setSeatedEventData],
|
(state) => [state.seatedEventData, state.setSeatedEventData],
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
const { selectedTimeslot, setSelectedTimeslot, allSelectedTimeslots } = slots;
|
const { selectedTimeslot, setSelectedTimeslot, allSelectedTimeslots } = slots;
|
||||||
const [dayCount, setDayCount] = useBookerStore((state) => [state.dayCount, state.setDayCount], shallow);
|
const [dayCount, setDayCount] = useBookerStoreContext(
|
||||||
|
(state) => [state.dayCount, state.setDayCount],
|
||||||
|
shallow
|
||||||
|
);
|
||||||
|
|
||||||
const nonEmptyScheduleDays = useNonEmptyScheduleDays(schedule?.data?.slots).filter(
|
const nonEmptyScheduleDays = useNonEmptyScheduleDays(schedule?.data?.slots).filter(
|
||||||
(slot) => dayjs(selectedDate).diff(slot, "day") <= 0
|
(slot) => dayjs(selectedDate).diff(slot, "day") <= 0
|
||||||
@@ -184,6 +192,10 @@ const BookerComponent = ({
|
|||||||
(bookerState === "booking" || (bookerState === "selecting_time" && skipConfirmStep))
|
(bookerState === "booking" || (bookerState === "selecting_time" && skipConfirmStep))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onAvailableTimeSlotSelect = (time: string) => {
|
||||||
|
setSelectedTimeslot(time);
|
||||||
|
};
|
||||||
|
|
||||||
updateEmbedBookerState({ bookerState, slotsQuery: schedule });
|
updateEmbedBookerState({ bookerState, slotsQuery: schedule });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -220,6 +232,7 @@ const BookerComponent = ({
|
|||||||
return bookerState === "booking" ? (
|
return bookerState === "booking" ? (
|
||||||
<BookEventForm
|
<BookEventForm
|
||||||
key={key}
|
key={key}
|
||||||
|
timeslot={selectedTimeslot}
|
||||||
shouldRenderCaptcha={shouldRenderCaptcha}
|
shouldRenderCaptcha={shouldRenderCaptcha}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setSelectedTimeslot(null);
|
setSelectedTimeslot(null);
|
||||||
@@ -312,9 +325,7 @@ const BookerComponent = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{event.data && !isPlatform ? <BookingPageTagManager eventType={event.data} /> : <></>}
|
{event.data && !isPlatform ? <BookingPageTagManager eventType={event.data} /> : <></>}
|
||||||
|
|
||||||
{(isBookingDryRunProp || isBookingDryRun(searchParams)) && <DryRunMessage isEmbed={isEmbed} />}
|
{(isBookingDryRunProp || isBookingDryRun(searchParams)) && <DryRunMessage isEmbed={isEmbed} />}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
// In a popup embed, if someone clicks outside the main(having main class or main tag), it closes the embed
|
// In a popup embed, if someone clicks outside the main(having main class or main tag), it closes the embed
|
||||||
@@ -394,6 +405,7 @@ const BookerComponent = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<EventMeta
|
<EventMeta
|
||||||
|
selectedTimeslot={selectedTimeslot}
|
||||||
classNames={{
|
classNames={{
|
||||||
eventMetaContainer: customClassNames?.eventMetaCustomClassNames?.eventMetaContainer,
|
eventMetaContainer: customClassNames?.eventMetaCustomClassNames?.eventMetaContainer,
|
||||||
eventMetaTitle: customClassNames?.eventMetaCustomClassNames?.eventMetaTitle,
|
eventMetaTitle: customClassNames?.eventMetaCustomClassNames?.eventMetaTitle,
|
||||||
@@ -480,6 +492,7 @@ const BookerComponent = ({
|
|||||||
ref={timeslotsRef}
|
ref={timeslotsRef}
|
||||||
{...fadeInLeft}>
|
{...fadeInLeft}>
|
||||||
<AvailableTimeSlots
|
<AvailableTimeSlots
|
||||||
|
onAvailableTimeSlotSelect={onAvailableTimeSlotSelect}
|
||||||
customClassNames={customClassNames?.availableTimeSlotsCustomClassNames}
|
customClassNames={customClassNames?.availableTimeSlotsCustomClassNames}
|
||||||
extraDays={extraDays}
|
extraDays={extraDays}
|
||||||
limitHeight={layout === BookerLayouts.MONTH_VIEW}
|
limitHeight={layout === BookerLayouts.MONTH_VIEW}
|
||||||
@@ -552,7 +565,6 @@ const BookerComponent = ({
|
|||||||
</m.span>
|
</m.span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<>
|
<>
|
||||||
{verifyCode && formEmail ? (
|
{verifyCode && formEmail ? (
|
||||||
<VerifyCodeDialog
|
<VerifyCodeDialog
|
||||||
@@ -571,7 +583,6 @@ const BookerComponent = ({
|
|||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
||||||
<BookFormAsModal
|
<BookFormAsModal
|
||||||
onCancel={() => setSelectedTimeslot(null)}
|
onCancel={() => setSelectedTimeslot(null)}
|
||||||
visible={bookerState === "booking" && shouldShowFormInDialog}>
|
visible={bookerState === "booking" && shouldShowFormInDialog}>
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, useContext, useRef, type ReactNode, useEffect } from "react";
|
||||||
|
import { useStore } from "zustand";
|
||||||
|
import type { StoreApi } from "zustand";
|
||||||
|
|
||||||
|
import { createBookerStore, type BookerStore, type StoreInitializeType } from "./store";
|
||||||
|
|
||||||
|
export const BookerStoreContext = createContext<StoreApi<BookerStore> | null>(null);
|
||||||
|
|
||||||
|
export interface BookerStoreProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BookerStoreProvider = ({ children }: BookerStoreProviderProps) => {
|
||||||
|
const storeRef = useRef<StoreApi<BookerStore>>();
|
||||||
|
if (!storeRef.current) {
|
||||||
|
storeRef.current = createBookerStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BookerStoreContext.Provider value={storeRef.current}>{children}</BookerStoreContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useBookerStoreContext = <T,>(
|
||||||
|
selector: (store: BookerStore) => T,
|
||||||
|
equalityFn?: (a: T, b: T) => boolean
|
||||||
|
): T => {
|
||||||
|
const bookerStoreContext = useContext(BookerStoreContext);
|
||||||
|
|
||||||
|
if (!bookerStoreContext) {
|
||||||
|
throw new Error("useBookerStoreContext must be used within BookerStoreProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
return useStore(bookerStoreContext, selector, equalityFn);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useInitializeBookerStoreContext = ({
|
||||||
|
username,
|
||||||
|
eventSlug,
|
||||||
|
month,
|
||||||
|
eventId,
|
||||||
|
rescheduleUid = null,
|
||||||
|
rescheduledBy = null,
|
||||||
|
bookingData = null,
|
||||||
|
verifiedEmail = null,
|
||||||
|
layout,
|
||||||
|
isTeamEvent,
|
||||||
|
durationConfig,
|
||||||
|
org,
|
||||||
|
isInstantMeeting,
|
||||||
|
timezone = null,
|
||||||
|
teamMemberEmail,
|
||||||
|
crmOwnerRecordType,
|
||||||
|
crmAppSlug,
|
||||||
|
crmRecordId,
|
||||||
|
isPlatform = false,
|
||||||
|
allowUpdatingUrlParams = true,
|
||||||
|
}: StoreInitializeType) => {
|
||||||
|
const bookerStoreContext = useContext(BookerStoreContext);
|
||||||
|
|
||||||
|
if (!bookerStoreContext) {
|
||||||
|
throw new Error("useInitializeBookerStoreContext must be used within BookerStoreProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeStore = useStore(bookerStoreContext, (state) => state.initialize);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initializeStore({
|
||||||
|
username,
|
||||||
|
eventSlug,
|
||||||
|
month,
|
||||||
|
eventId,
|
||||||
|
rescheduleUid,
|
||||||
|
rescheduledBy,
|
||||||
|
bookingData,
|
||||||
|
layout,
|
||||||
|
isTeamEvent,
|
||||||
|
org,
|
||||||
|
verifiedEmail,
|
||||||
|
durationConfig,
|
||||||
|
isInstantMeeting,
|
||||||
|
timezone,
|
||||||
|
teamMemberEmail,
|
||||||
|
crmOwnerRecordType,
|
||||||
|
crmAppSlug,
|
||||||
|
crmRecordId,
|
||||||
|
isPlatform,
|
||||||
|
allowUpdatingUrlParams,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
initializeStore,
|
||||||
|
org,
|
||||||
|
username,
|
||||||
|
eventSlug,
|
||||||
|
month,
|
||||||
|
eventId,
|
||||||
|
rescheduleUid,
|
||||||
|
rescheduledBy,
|
||||||
|
bookingData,
|
||||||
|
layout,
|
||||||
|
isTeamEvent,
|
||||||
|
verifiedEmail,
|
||||||
|
durationConfig,
|
||||||
|
isInstantMeeting,
|
||||||
|
timezone,
|
||||||
|
teamMemberEmail,
|
||||||
|
crmOwnerRecordType,
|
||||||
|
crmAppSlug,
|
||||||
|
crmRecordId,
|
||||||
|
isPlatform,
|
||||||
|
allowUpdatingUrlParams,
|
||||||
|
]);
|
||||||
|
};
|
||||||
@@ -10,18 +10,17 @@ import "@calcom/features/bookings/Booker/components/__mocks__/Section";
|
|||||||
import { constantsScenarios } from "@calcom/lib/__mocks__/constants";
|
import { constantsScenarios } from "@calcom/lib/__mocks__/constants";
|
||||||
import "@calcom/lib/__mocks__/logger";
|
import "@calcom/lib/__mocks__/logger";
|
||||||
|
|
||||||
import { render, screen } from "@testing-library/react";
|
import React from "react";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import "@calcom/dayjs/__mocks__";
|
import "@calcom/dayjs/__mocks__";
|
||||||
import "@calcom/features/auth/Turnstile";
|
import "@calcom/features/auth/Turnstile";
|
||||||
|
|
||||||
import { Booker } from "../Booker";
|
import { Booker } from "../Booker";
|
||||||
import { useBookerStore } from "../store";
|
import { render, screen } from "./test-utils";
|
||||||
import type { BookerState } from "../types";
|
|
||||||
|
|
||||||
vi.mock("framer-motion", async (importOriginal) => {
|
vi.mock("framer-motion", async (importOriginal) => {
|
||||||
const actual = await importOriginal();
|
const actual = (await importOriginal()) as any;
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
};
|
};
|
||||||
@@ -87,27 +86,6 @@ vi.mock("@calcom/atoms/hooks/useIsPlatform", () => ({
|
|||||||
useIsPlatform: () => false,
|
useIsPlatform: () => false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Update mockStoreState to include all required state
|
|
||||||
const mockStoreState = {
|
|
||||||
state: "booking" as BookerState,
|
|
||||||
setState: vi.fn(),
|
|
||||||
selectedDate: "2024-01-01",
|
|
||||||
seatedEventData: {},
|
|
||||||
setSeatedEventData: vi.fn(),
|
|
||||||
tentativeSelectedTimeslots: [],
|
|
||||||
setTentativeSelectedTimeslots: vi.fn(),
|
|
||||||
dayCount: 7,
|
|
||||||
setDayCount: vi.fn(),
|
|
||||||
setSelectedTimeslot: vi.fn(),
|
|
||||||
selectedTimeslot: null,
|
|
||||||
formStep: 0,
|
|
||||||
setFormStep: vi.fn(),
|
|
||||||
bookerState: "booking",
|
|
||||||
setBookerState: vi.fn(),
|
|
||||||
layout: "default",
|
|
||||||
setLayout: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update defaultProps to include missing required props
|
// Update defaultProps to include missing required props
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
username: "testuser",
|
username: "testuser",
|
||||||
@@ -188,7 +166,7 @@ const defaultProps = {
|
|||||||
describe("Booker", () => {
|
describe("Booker", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
constantsScenarios.set({
|
constantsScenarios.set({
|
||||||
PUBLIC_QUICK_AVAILABILITY_ROLLOUT: 100,
|
PUBLIC_QUICK_AVAILABILITY_ROLLOUT: "100",
|
||||||
POWERED_BY_URL: "https://go.cal.com/booking",
|
POWERED_BY_URL: "https://go.cal.com/booking",
|
||||||
APP_NAME: "Cal.com",
|
APP_NAME: "Cal.com",
|
||||||
});
|
});
|
||||||
@@ -196,24 +174,13 @@ describe("Booker", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render null when in loading state", () => {
|
it("should render null when in loading state", () => {
|
||||||
useBookerStore.setState({
|
const { container } = render(<Booker {...defaultProps} />, {
|
||||||
...mockStoreState,
|
mockStore: { state: "loading" },
|
||||||
state: "loading",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { container } = render(<Booker {...defaultProps} />);
|
|
||||||
expect(container).toBeEmptyDOMElement();
|
expect(container).toBeEmptyDOMElement();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render DryRunMessage when in dry run mode", () => {
|
it("should render DryRunMessage when in dry run mode", () => {
|
||||||
useBookerStore.setState({
|
|
||||||
...mockStoreState,
|
|
||||||
state: "selecting_time",
|
|
||||||
selectedDate: "2024-01-01",
|
|
||||||
selectedTimeslot: "2024-01-01T10:00:00Z",
|
|
||||||
tentativeSelectedTimeslots: ["2024-01-01T10:00:00Z"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const propsWithDryRun = {
|
const propsWithDryRun = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
isBookingDryRun: true,
|
isBookingDryRun: true,
|
||||||
@@ -226,7 +193,14 @@ describe("Booker", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
render(<Booker {...propsWithDryRun} />);
|
render(<Booker {...propsWithDryRun} />, {
|
||||||
|
mockStore: {
|
||||||
|
state: "selecting_time",
|
||||||
|
selectedDate: "2024-01-01",
|
||||||
|
selectedTimeslot: "2024-01-01T10:00:00Z",
|
||||||
|
tentativeSelectedTimeslots: ["2024-01-01T10:00:00Z"],
|
||||||
|
},
|
||||||
|
});
|
||||||
expect(screen.getByTestId("dry-run-message")).toBeInTheDocument();
|
expect(screen.getByTestId("dry-run-message")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -242,12 +216,10 @@ describe("Booker", () => {
|
|||||||
invalidate: mockInvalidate,
|
invalidate: mockInvalidate,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
useBookerStore.setState({
|
|
||||||
...mockStoreState,
|
|
||||||
state: "booking",
|
|
||||||
});
|
|
||||||
|
|
||||||
render(<Booker {...propsWithInvalidate} />);
|
render(<Booker {...propsWithInvalidate} />, {
|
||||||
|
mockStore: { state: "booking" },
|
||||||
|
});
|
||||||
screen.logTestingPlaygroundURL();
|
screen.logTestingPlaygroundURL();
|
||||||
// Trigger form cancel
|
// Trigger form cancel
|
||||||
const cancelButton = screen.getByRole("button", { name: /cancel/i });
|
const cancelButton = screen.getByRole("button", { name: /cancel/i });
|
||||||
@@ -267,12 +239,9 @@ describe("Booker", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
useBookerStore.setState({
|
render(<Booker {...propsWithQuickChecks} />, {
|
||||||
...mockStoreState,
|
mockStore: { state: "booking" },
|
||||||
state: "booking",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
render(<Booker {...propsWithQuickChecks} />);
|
|
||||||
const bookEventForm = screen.getByTestId("book-event-form");
|
const bookEventForm = screen.getByTestId("book-event-form");
|
||||||
await expect(bookEventForm).toHaveAttribute("data-unavailable", "true");
|
await expect(bookEventForm).toHaveAttribute("data-unavailable", "true");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import type { RenderOptions } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import type { ReactElement } from "react";
|
||||||
|
import { vi } from "vitest";
|
||||||
|
import type { StoreApi } from "zustand";
|
||||||
|
|
||||||
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
|
import { BookerStoreContext } from "../BookerStoreProvider";
|
||||||
|
import type { BookerStore } from "../store";
|
||||||
|
|
||||||
|
interface CustomRenderOptions extends Omit<RenderOptions, "wrapper"> {
|
||||||
|
mockStore?: Partial<BookerStore>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createMockStore = (initialState?: Partial<BookerStore>): StoreApi<BookerStore> => {
|
||||||
|
let state: BookerStore = {
|
||||||
|
username: null,
|
||||||
|
eventSlug: null,
|
||||||
|
eventId: null,
|
||||||
|
verifiedEmail: null,
|
||||||
|
setVerifiedEmail: vi.fn(),
|
||||||
|
month: dayjs().format("YYYY-MM"),
|
||||||
|
setMonth: vi.fn(),
|
||||||
|
state: "loading",
|
||||||
|
setState: vi.fn(),
|
||||||
|
layout: BookerLayouts.MONTH_VIEW,
|
||||||
|
setLayout: vi.fn(),
|
||||||
|
selectedDate: null,
|
||||||
|
setSelectedDate: vi.fn(),
|
||||||
|
addToSelectedDate: vi.fn(),
|
||||||
|
selectedDatesAndTimes: null,
|
||||||
|
setSelectedDatesAndTimes: vi.fn(),
|
||||||
|
durationConfig: null,
|
||||||
|
selectedDuration: null,
|
||||||
|
setSelectedDuration: vi.fn(),
|
||||||
|
selectedTimeslot: null,
|
||||||
|
setSelectedTimeslot: vi.fn(),
|
||||||
|
tentativeSelectedTimeslots: [],
|
||||||
|
setTentativeSelectedTimeslots: vi.fn(),
|
||||||
|
recurringEventCount: null,
|
||||||
|
setRecurringEventCount: vi.fn(),
|
||||||
|
occurenceCount: null,
|
||||||
|
setOccurenceCount: vi.fn(),
|
||||||
|
dayCount: null,
|
||||||
|
setDayCount: vi.fn(),
|
||||||
|
rescheduleUid: null,
|
||||||
|
rescheduledBy: null,
|
||||||
|
bookingUid: null,
|
||||||
|
bookingData: null,
|
||||||
|
setBookingData: vi.fn(),
|
||||||
|
initialize: vi.fn(),
|
||||||
|
formValues: {},
|
||||||
|
setFormValues: vi.fn(),
|
||||||
|
isTeamEvent: false,
|
||||||
|
seatedEventData: {},
|
||||||
|
setSeatedEventData: vi.fn(),
|
||||||
|
isInstantMeeting: false,
|
||||||
|
org: null,
|
||||||
|
setOrg: vi.fn(),
|
||||||
|
timezone: null,
|
||||||
|
setTimezone: vi.fn(),
|
||||||
|
teamMemberEmail: null,
|
||||||
|
crmOwnerRecordType: null,
|
||||||
|
crmAppSlug: null,
|
||||||
|
crmRecordId: null,
|
||||||
|
isPlatform: false,
|
||||||
|
allowUpdatingUrlParams: true,
|
||||||
|
...initialState,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.setMonth = vi.fn((month: string | null) => {
|
||||||
|
state.month = month;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
getState: () => state,
|
||||||
|
setState: vi.fn((updater) => {
|
||||||
|
if (typeof updater === "function") {
|
||||||
|
const newState = updater(state);
|
||||||
|
state = { ...state, ...newState };
|
||||||
|
} else {
|
||||||
|
state = { ...state, ...updater };
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
subscribe: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
} as unknown as StoreApi<BookerStore>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderWithBookerStore = (ui: ReactElement, options?: CustomRenderOptions) => {
|
||||||
|
const mockStore = createMockStore(options?.mockStore);
|
||||||
|
|
||||||
|
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<BookerStoreContext.Provider value={mockStore}>{children}</BookerStoreContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
return render(ui, { wrapper: Wrapper, ...options });
|
||||||
|
};
|
||||||
|
|
||||||
|
export * from "@testing-library/react";
|
||||||
|
|
||||||
|
export { renderWithBookerStore as render };
|
||||||
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useRef } from "react";
|
|||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { AvailableTimes, AvailableTimesSkeleton } from "@calcom/features/bookings";
|
import { AvailableTimes, AvailableTimesSkeleton } from "@calcom/features/bookings";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import type { IUseBookingLoadingStates } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
|
import type { IUseBookingLoadingStates } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
|
||||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||||
import type { Slot } from "@calcom/features/schedules/lib/use-schedule/types";
|
import type { Slot } from "@calcom/features/schedules/lib/use-schedule/types";
|
||||||
@@ -13,7 +14,6 @@ import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
|||||||
import classNames from "@calcom/ui/classNames";
|
import classNames from "@calcom/ui/classNames";
|
||||||
|
|
||||||
import { AvailableTimesHeader } from "../../components/AvailableTimesHeader";
|
import { AvailableTimesHeader } from "../../components/AvailableTimesHeader";
|
||||||
import { useBookerStore } from "../store";
|
|
||||||
import type { useScheduleForEventReturnType } from "../utils/event";
|
import type { useScheduleForEventReturnType } from "../utils/event";
|
||||||
import { getQueryParam } from "../utils/query-param";
|
import { getQueryParam } from "../utils/query-param";
|
||||||
|
|
||||||
@@ -49,6 +49,7 @@ type AvailableTimeSlotsProps = {
|
|||||||
*/
|
*/
|
||||||
unavailableTimeSlots: string[];
|
unavailableTimeSlots: string[];
|
||||||
confirmButtonDisabled?: boolean;
|
confirmButtonDisabled?: boolean;
|
||||||
|
onAvailableTimeSlotSelect: (time: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,17 +73,17 @@ export const AvailableTimeSlots = ({
|
|||||||
unavailableTimeSlots,
|
unavailableTimeSlots,
|
||||||
confirmButtonDisabled,
|
confirmButtonDisabled,
|
||||||
confirmStepClassNames,
|
confirmStepClassNames,
|
||||||
|
onAvailableTimeSlotSelect,
|
||||||
...props
|
...props
|
||||||
}: AvailableTimeSlotsProps) => {
|
}: AvailableTimeSlotsProps) => {
|
||||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
const selectedDate = useBookerStoreContext((state) => state.selectedDate);
|
||||||
|
|
||||||
const setSelectedTimeslot = useBookerStore((state) => state.setSelectedTimeslot);
|
const setSeatedEventData = useBookerStoreContext((state) => state.setSeatedEventData);
|
||||||
const setSeatedEventData = useBookerStore((state) => state.setSeatedEventData);
|
|
||||||
const date = selectedDate || dayjs().format("YYYY-MM-DD");
|
const date = selectedDate || dayjs().format("YYYY-MM-DD");
|
||||||
const [layout] = useBookerStore((state) => [state.layout]);
|
const [layout] = useBookerStoreContext((state) => [state.layout]);
|
||||||
const isColumnView = layout === BookerLayouts.COLUMN_VIEW;
|
const isColumnView = layout === BookerLayouts.COLUMN_VIEW;
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const { setTentativeSelectedTimeslots, tentativeSelectedTimeslots } = useBookerStore((state) => ({
|
const { setTentativeSelectedTimeslots, tentativeSelectedTimeslots } = useBookerStoreContext((state) => ({
|
||||||
setTentativeSelectedTimeslots: state.setTentativeSelectedTimeslots,
|
setTentativeSelectedTimeslots: state.setTentativeSelectedTimeslots,
|
||||||
tentativeSelectedTimeslots: state.tentativeSelectedTimeslots,
|
tentativeSelectedTimeslots: state.tentativeSelectedTimeslots,
|
||||||
}));
|
}));
|
||||||
@@ -146,7 +147,9 @@ export const AvailableTimeSlots = ({
|
|||||||
showAvailableSeatsCount,
|
showAvailableSeatsCount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setSelectedTimeslot(time);
|
|
||||||
|
onAvailableTimeSlotSelect(time);
|
||||||
|
|
||||||
const isTimeSlotAvailable = !unavailableTimeSlots.includes(time);
|
const isTimeSlotAvailable = !unavailableTimeSlots.includes(time);
|
||||||
if (skipConfirmStep && isTimeSlotAvailable) {
|
if (skipConfirmStep && isTimeSlotAvailable) {
|
||||||
onSubmit(time);
|
onSubmit(time);
|
||||||
@@ -156,12 +159,12 @@ export const AvailableTimeSlots = ({
|
|||||||
[
|
[
|
||||||
onSubmit,
|
onSubmit,
|
||||||
setSeatedEventData,
|
setSeatedEventData,
|
||||||
setSelectedTimeslot,
|
|
||||||
skipConfirmStep,
|
skipConfirmStep,
|
||||||
showAvailableSeatsCount,
|
showAvailableSeatsCount,
|
||||||
unavailableTimeSlots,
|
unavailableTimeSlots,
|
||||||
schedule,
|
schedule,
|
||||||
setTentativeSelectedTimeslots,
|
setTentativeSelectedTimeslots,
|
||||||
|
onAvailableTimeSlotSelect,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useMemo, useState } from "react";
|
|||||||
import type { FieldError } from "react-hook-form";
|
import type { FieldError } from "react-hook-form";
|
||||||
|
|
||||||
import { useIsPlatformBookerEmbed } from "@calcom/atoms/hooks/useIsPlatformBookerEmbed";
|
import { useIsPlatformBookerEmbed } from "@calcom/atoms/hooks/useIsPlatformBookerEmbed";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||||
import ServerTrans from "@calcom/lib/components/ServerTrans";
|
import ServerTrans from "@calcom/lib/components/ServerTrans";
|
||||||
import { WEBSITE_PRIVACY_POLICY_URL, WEBSITE_TERMS_URL } from "@calcom/lib/constants";
|
import { WEBSITE_PRIVACY_POLICY_URL, WEBSITE_TERMS_URL } from "@calcom/lib/constants";
|
||||||
@@ -16,7 +17,6 @@ import { Button } from "@calcom/ui/components/button";
|
|||||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||||
import { Form } from "@calcom/ui/components/form";
|
import { Form } from "@calcom/ui/components/form";
|
||||||
|
|
||||||
import { useBookerStore } from "../../store";
|
|
||||||
import { formatEventFromTime } from "../../utils/dates";
|
import { formatEventFromTime } from "../../utils/dates";
|
||||||
import { useBookerTime } from "../hooks/useBookerTime";
|
import { useBookerTime } from "../hooks/useBookerTime";
|
||||||
import type { UseBookingFormReturnType } from "../hooks/useBookingForm";
|
import type { UseBookingFormReturnType } from "../hooks/useBookingForm";
|
||||||
@@ -43,6 +43,7 @@ type BookEventFormProps = {
|
|||||||
confirmButton?: string;
|
confirmButton?: string;
|
||||||
backButton?: string;
|
backButton?: string;
|
||||||
};
|
};
|
||||||
|
timeslot: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BookEventForm = ({
|
export const BookEventForm = ({
|
||||||
@@ -62,6 +63,7 @@ export const BookEventForm = ({
|
|||||||
shouldRenderCaptcha,
|
shouldRenderCaptcha,
|
||||||
confirmButtonDisabled,
|
confirmButtonDisabled,
|
||||||
classNames,
|
classNames,
|
||||||
|
timeslot,
|
||||||
}: Omit<BookEventFormProps, "event"> & {
|
}: Omit<BookEventFormProps, "event"> & {
|
||||||
eventQuery: {
|
eventQuery: {
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
@@ -70,12 +72,11 @@ export const BookEventForm = ({
|
|||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
const eventType = eventQuery.data;
|
const eventType = eventQuery.data;
|
||||||
const setFormValues = useBookerStore((state) => state.setFormValues);
|
const setFormValues = useBookerStoreContext((state) => state.setFormValues);
|
||||||
const bookingData = useBookerStore((state) => state.bookingData);
|
const bookingData = useBookerStoreContext((state) => state.bookingData);
|
||||||
const rescheduleUid = useBookerStore((state) => state.rescheduleUid);
|
const rescheduleUid = useBookerStoreContext((state) => state.rescheduleUid);
|
||||||
const timeslot = useBookerStore((state) => state.selectedTimeslot);
|
const username = useBookerStoreContext((state) => state.username);
|
||||||
const username = useBookerStore((state) => state.username);
|
const isInstantMeeting = useBookerStoreContext((state) => state.isInstantMeeting);
|
||||||
const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting);
|
|
||||||
const isPlatformBookerEmbed = useIsPlatformBookerEmbed();
|
const isPlatformBookerEmbed = useIsPlatformBookerEmbed();
|
||||||
const { timeFormat, timezone } = useBookerTime();
|
const { timeFormat, timezone } = useBookerTime();
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import React from "react";
|
|||||||
|
|
||||||
import { useEventTypeById } from "@calcom/atoms/hooks/event-types/private/useEventTypeById";
|
import { useEventTypeById } from "@calcom/atoms/hooks/event-types/private/useEventTypeById";
|
||||||
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { Dialog } from "@calcom/features/components/controlled-dialog";
|
import { Dialog } from "@calcom/features/components/controlled-dialog";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Badge } from "@calcom/ui/components/badge";
|
import { Badge } from "@calcom/ui/components/badge";
|
||||||
import { DialogContent } from "@calcom/ui/components/dialog";
|
import { DialogContent } from "@calcom/ui/components/dialog";
|
||||||
|
|
||||||
import { getDurationFormatted } from "../../../components/event-meta/Duration";
|
import { getDurationFormatted } from "../../../components/event-meta/Duration";
|
||||||
import { useBookerStore } from "../../store";
|
|
||||||
import { FromTime } from "../../utils/dates";
|
import { FromTime } from "../../utils/dates";
|
||||||
import { useEvent } from "../../utils/event";
|
import { useEvent } from "../../utils/event";
|
||||||
import { useBookerTime } from "../hooks/useBookerTime";
|
import { useBookerTime } from "../hooks/useBookerTime";
|
||||||
@@ -27,7 +27,7 @@ const PlatformBookEventFormWrapper = ({
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const eventId = useBookerStore((state) => state.eventId);
|
const eventId = useBookerStoreContext((state) => state.eventId);
|
||||||
const { data } = useEventTypeById(eventId);
|
const { data } = useEventTypeById(eventId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -44,8 +44,8 @@ export const BookEventFormWrapperComponent = ({
|
|||||||
eventLength?: number;
|
eventLength?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const { i18n, t } = useLocale();
|
const { i18n, t } = useLocale();
|
||||||
const selectedTimeslot = useBookerStore((state) => state.selectedTimeslot);
|
const selectedTimeslot = useBookerStoreContext((state) => state.selectedTimeslot);
|
||||||
const selectedDuration = useBookerStore((state) => state.selectedDuration);
|
const selectedDuration = useBookerStoreContext((state) => state.selectedDuration);
|
||||||
const { timeFormat, timezone } = useBookerTime();
|
const { timeFormat, timezone } = useBookerTime();
|
||||||
if (!selectedTimeslot) {
|
if (!selectedTimeslot) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { render } from "@testing-library/react";
|
import React from "react";
|
||||||
import { vi, afterEach } from "vitest";
|
import { vi, afterEach } from "vitest";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { DatePicker as DatePickerComponent } from "@calcom/features/calendars/DatePicker";
|
import { DatePicker as DatePickerComponent } from "@calcom/features/calendars/DatePicker";
|
||||||
|
|
||||||
|
import { render } from "../__tests__/test-utils";
|
||||||
import { DatePicker } from "./DatePicker";
|
import { DatePicker } from "./DatePicker";
|
||||||
|
|
||||||
vi.mock("@calcom/features/calendars/DatePicker", () => {
|
vi.mock("@calcom/features/calendars/DatePicker", () => {
|
||||||
@@ -18,7 +19,7 @@ const noop = () => {
|
|||||||
|
|
||||||
describe("Tests for DatePicker Component", () => {
|
describe("Tests for DatePicker Component", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
DatePickerComponent.mockClear();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("It passes the loading prop to the DatePicker component", async () => {
|
test("It passes the loading prop to the DatePicker component", async () => {
|
||||||
@@ -60,7 +61,7 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
|
|
||||||
test("when there are only slots in the next month, skip the current month", async () => {
|
test("when there are only slots in the next month, skip the current month", async () => {
|
||||||
// there'll be one slot open on this day, next month.
|
// there'll be one slot open on this day, next month.
|
||||||
const slotDate = dayjs().add("1", "month");
|
const slotDate = dayjs().add(1, "month");
|
||||||
render(
|
render(
|
||||||
<DatePicker
|
<DatePicker
|
||||||
event={{}}
|
event={{}}
|
||||||
@@ -72,7 +73,12 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
/>
|
/>,
|
||||||
|
{
|
||||||
|
mockStore: {
|
||||||
|
month: slotDate.format("YYYY-MM"), // Start with next month to simulate the auto-advance
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
// slot date is next month, so it'll have skipped the current month
|
// slot date is next month, so it'll have skipped the current month
|
||||||
// by setting the browsingDate to the next month.
|
// by setting the browsingDate to the next month.
|
||||||
@@ -94,13 +100,18 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
/>
|
/>,
|
||||||
|
{
|
||||||
|
mockStore: {
|
||||||
|
month: dayjs().add(1, "month").format("YYYY-MM"), // Start with next month to simulate the auto-advance
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
// slot date is next month, so it'll have skipped the current month
|
// slot date is next month, so it'll have skipped the current month
|
||||||
// by setting the browsingDate to the next month.
|
// by setting the browsingDate to the next month.
|
||||||
expect(DatePickerComponent).toHaveBeenCalledWith(
|
expect(DatePickerComponent).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
browsingDate: dayjs().add("1", "month").startOf("month"),
|
browsingDate: dayjs().add(1, "month").startOf("month"),
|
||||||
}),
|
}),
|
||||||
expect.anything()
|
expect.anything()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { shallow } from "zustand/shallow";
|
|||||||
|
|
||||||
import type { Dayjs } from "@calcom/dayjs";
|
import type { Dayjs } from "@calcom/dayjs";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import type { DatePickerClassNames } from "@calcom/features/bookings/Booker/types";
|
import type { DatePickerClassNames } from "@calcom/features/bookings/Booker/types";
|
||||||
import { DatePicker as DatePickerComponent } from "@calcom/features/calendars/DatePicker";
|
import { DatePicker as DatePickerComponent } from "@calcom/features/calendars/DatePicker";
|
||||||
import { useNonEmptyScheduleDays } from "@calcom/features/schedules/lib/use-schedule/useNonEmptyScheduleDays";
|
import { useNonEmptyScheduleDays } from "@calcom/features/schedules/lib/use-schedule/useNonEmptyScheduleDays";
|
||||||
@@ -11,7 +12,6 @@ import type { User } from "@calcom/prisma/client";
|
|||||||
import type { PeriodData } from "@calcom/types/Event";
|
import type { PeriodData } from "@calcom/types/Event";
|
||||||
|
|
||||||
import type { Slots } from "../../types";
|
import type { Slots } from "../../types";
|
||||||
import { useBookerStore } from "../store";
|
|
||||||
|
|
||||||
const useMoveToNextMonthOnNoAvailability = ({
|
const useMoveToNextMonthOnNoAvailability = ({
|
||||||
browsingDate,
|
browsingDate,
|
||||||
@@ -76,12 +76,12 @@ export const DatePicker = ({
|
|||||||
showNoAvailabilityDialog?: boolean;
|
showNoAvailabilityDialog?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { i18n } = useLocale();
|
const { i18n } = useLocale();
|
||||||
const [month, selectedDate, layout] = useBookerStore(
|
const [month, selectedDate, layout] = useBookerStoreContext(
|
||||||
(state) => [state.month, state.selectedDate, state.layout],
|
(state) => [state.month, state.selectedDate, state.layout],
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
|
|
||||||
const [setSelectedDate, setMonth, setDayCount] = useBookerStore(
|
const [setSelectedDate, setMonth, setDayCount] = useBookerStoreContext(
|
||||||
(state) => [state.setSelectedDate, state.setMonth, state.setDayCount],
|
(state) => [state.setSelectedDate, state.setMonth, state.setDayCount],
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { shallow } from "zustand/shallow";
|
|||||||
import { Timezone as PlatformTimezoneSelect } from "@calcom/atoms/timezone";
|
import { Timezone as PlatformTimezoneSelect } from "@calcom/atoms/timezone";
|
||||||
import { useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
import { useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||||
import { EventDetails, EventMembers, EventMetaSkeleton, EventTitle } from "@calcom/features/bookings";
|
import { EventDetails, EventMembers, EventMetaSkeleton, EventTitle } from "@calcom/features/bookings";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import type { Timezone } from "@calcom/features/bookings/Booker/types";
|
import type { Timezone } from "@calcom/features/bookings/Booker/types";
|
||||||
import { SeatsAvailabilityText } from "@calcom/features/bookings/components/SeatsAvailabilityText";
|
import { SeatsAvailabilityText } from "@calcom/features/bookings/components/SeatsAvailabilityText";
|
||||||
import { EventMetaBlock } from "@calcom/features/bookings/components/event-meta/Details";
|
import { EventMetaBlock } from "@calcom/features/bookings/components/event-meta/Details";
|
||||||
@@ -19,7 +20,6 @@ import { EventTypeAutoTranslatedField } from "@calcom/prisma/enums";
|
|||||||
|
|
||||||
import i18nConfigration from "../../../../../i18n.json";
|
import i18nConfigration from "../../../../../i18n.json";
|
||||||
import { fadeInUp } from "../config";
|
import { fadeInUp } from "../config";
|
||||||
import { useBookerStore } from "../store";
|
|
||||||
import { FromToTime } from "../utils/dates";
|
import { FromToTime } from "../utils/dates";
|
||||||
import { useBookerTime } from "./hooks/useBookerTime";
|
import { useBookerTime } from "./hooks/useBookerTime";
|
||||||
|
|
||||||
@@ -54,7 +54,8 @@ export const EventMeta = ({
|
|||||||
locale,
|
locale,
|
||||||
timeZones,
|
timeZones,
|
||||||
children,
|
children,
|
||||||
roundRobinHideOrgAndTeam,
|
selectedTimeslot,
|
||||||
|
roundRobinHideOrgAndTeam
|
||||||
}: {
|
}: {
|
||||||
event?: Pick<
|
event?: Pick<
|
||||||
BookerEvent,
|
BookerEvent,
|
||||||
@@ -91,17 +92,17 @@ export const EventMeta = ({
|
|||||||
locale?: string | null;
|
locale?: string | null;
|
||||||
timeZones?: Timezone[];
|
timeZones?: Timezone[];
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
selectedTimeslot: string | null;
|
||||||
roundRobinHideOrgAndTeam?: boolean;
|
roundRobinHideOrgAndTeam?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { timeFormat, timezone } = useBookerTime();
|
const { timeFormat, timezone } = useBookerTime();
|
||||||
const [setTimezone] = useTimePreferences((state) => [state.setTimezone]);
|
const [setTimezone] = useTimePreferences((state) => [state.setTimezone]);
|
||||||
const [setBookerStoreTimezone] = useBookerStore((state) => [state.setTimezone], shallow);
|
const [setBookerStoreTimezone] = useBookerStoreContext((state) => [state.setTimezone], shallow);
|
||||||
const selectedDuration = useBookerStore((state) => state.selectedDuration);
|
const selectedDuration = useBookerStoreContext((state) => state.selectedDuration);
|
||||||
const selectedTimeslot = useBookerStore((state) => state.selectedTimeslot);
|
const bookerState = useBookerStoreContext((state) => state.state);
|
||||||
const bookerState = useBookerStore((state) => state.state);
|
const bookingData = useBookerStoreContext((state) => state.bookingData);
|
||||||
const bookingData = useBookerStore((state) => state.bookingData);
|
const rescheduleUid = useBookerStoreContext((state) => state.rescheduleUid);
|
||||||
const rescheduleUid = useBookerStore((state) => state.rescheduleUid);
|
const [seatedEventData, setSeatedEventData] = useBookerStoreContext(
|
||||||
const [seatedEventData, setSeatedEventData] = useBookerStore(
|
|
||||||
(state) => [state.seatedEventData, state.setSeatedEventData],
|
(state) => [state.seatedEventData, state.setSeatedEventData],
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { shallow } from "zustand/shallow";
|
|||||||
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||||
@@ -14,7 +15,6 @@ import { Icon } from "@calcom/ui/components/icon";
|
|||||||
import { Tooltip } from "@calcom/ui/components/tooltip";
|
import { Tooltip } from "@calcom/ui/components/tooltip";
|
||||||
|
|
||||||
import { TimeFormatToggle } from "../../components/TimeFormatToggle";
|
import { TimeFormatToggle } from "../../components/TimeFormatToggle";
|
||||||
import { useBookerStore } from "../store";
|
|
||||||
import type { BookerLayout } from "../types";
|
import type { BookerLayout } from "../types";
|
||||||
|
|
||||||
export function Header({
|
export function Header({
|
||||||
@@ -36,10 +36,10 @@ export function Header({
|
|||||||
}) {
|
}) {
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const isEmbed = useIsEmbed();
|
const isEmbed = useIsEmbed();
|
||||||
const [layout, setLayout] = useBookerStore((state) => [state.layout, state.setLayout], shallow);
|
const [layout, setLayout] = useBookerStoreContext((state) => [state.layout, state.setLayout], shallow);
|
||||||
const selectedDateString = useBookerStore((state) => state.selectedDate);
|
const selectedDateString = useBookerStoreContext((state) => state.selectedDate);
|
||||||
const setSelectedDate = useBookerStore((state) => state.setSelectedDate);
|
const setSelectedDate = useBookerStoreContext((state) => state.setSelectedDate);
|
||||||
const addToSelectedDate = useBookerStore((state) => state.addToSelectedDate);
|
const addToSelectedDate = useBookerStoreContext((state) => state.addToSelectedDate);
|
||||||
const isMonthView = layout === BookerLayouts.MONTH_VIEW;
|
const isMonthView = layout === BookerLayouts.MONTH_VIEW;
|
||||||
const today = dayjs();
|
const today = dayjs();
|
||||||
const selectedDate = selectedDateString ? dayjs(selectedDateString) : today;
|
const selectedDate = selectedDateString ? dayjs(selectedDateString) : today;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useMemo, useEffect } from "react";
|
import { useMemo, useEffect } from "react";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
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";
|
||||||
import type { CalendarAvailableTimeslots } from "@calcom/features/calendars/weeklyview/types/state";
|
import type { CalendarAvailableTimeslots } from "@calcom/features/calendars/weeklyview/types/state";
|
||||||
import { localStorage } from "@calcom/lib/webstorage";
|
import { localStorage } from "@calcom/lib/webstorage";
|
||||||
|
|
||||||
import { useBookerStore } from "../store";
|
|
||||||
import type { useScheduleForEventReturnType } from "../utils/event";
|
import type { useScheduleForEventReturnType } from "../utils/event";
|
||||||
import { getQueryParam } from "../utils/query-param";
|
import { getQueryParam } from "../utils/query-param";
|
||||||
import { useOverlayCalendarStore } from "./OverlayCalendar/store";
|
import { useOverlayCalendarStore } from "./OverlayCalendar/store";
|
||||||
@@ -25,9 +25,9 @@ export const LargeCalendar = ({
|
|||||||
data?: Pick<BookerEvent, "length"> | null;
|
data?: Pick<BookerEvent, "length"> | null;
|
||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
const selectedDate = useBookerStoreContext((state) => state.selectedDate);
|
||||||
const setSelectedTimeslot = useBookerStore((state) => state.setSelectedTimeslot);
|
const setSelectedTimeslot = useBookerStoreContext((state) => state.setSelectedTimeslot);
|
||||||
const selectedEventDuration = useBookerStore((state) => state.selectedDuration);
|
const selectedEventDuration = useBookerStoreContext((state) => state.selectedDuration);
|
||||||
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");
|
||||||
|
|||||||
+4
-4
@@ -1,11 +1,11 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Switch } from "@calcom/ui/components/form";
|
|
||||||
import { Button } from "@calcom/ui/components/button";
|
|
||||||
import classNames from "@calcom/ui/classNames";
|
import classNames from "@calcom/ui/classNames";
|
||||||
|
import { Button } from "@calcom/ui/components/button";
|
||||||
|
import { Switch } from "@calcom/ui/components/form";
|
||||||
|
|
||||||
import { useBookerStore } from "../../store";
|
|
||||||
import { useOverlayCalendarStore } from "./store";
|
import { useOverlayCalendarStore } from "./store";
|
||||||
|
|
||||||
interface OverlayCalendarSwitchProps {
|
interface OverlayCalendarSwitchProps {
|
||||||
@@ -20,7 +20,7 @@ export function OverlayCalendarSwitch({ enabled, hasSession, onStateChange }: Ov
|
|||||||
const setCalendarSettingsOverlay = useOverlayCalendarStore(
|
const setCalendarSettingsOverlay = useOverlayCalendarStore(
|
||||||
(state) => state.setCalendarSettingsOverlayModal
|
(state) => state.setCalendarSettingsOverlayModal
|
||||||
);
|
);
|
||||||
const layout = useBookerStore((state) => state.layout);
|
const layout = useBookerStoreContext((state) => state.layout);
|
||||||
const switchEnabled = enabled;
|
const switchEnabled = enabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import type { MotionProps } from "framer-motion";
|
|||||||
import { m } from "framer-motion";
|
import { m } from "framer-motion";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import classNames from "@calcom/ui/classNames";
|
import classNames from "@calcom/ui/classNames";
|
||||||
|
|
||||||
import { useBookerStore } from "../store";
|
|
||||||
import type { BookerAreas, BookerLayout } from "../types";
|
import type { BookerAreas, BookerLayout } from "../types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +44,7 @@ export const BookerSection = forwardRef<HTMLDivElement, BookerSectionProps>(func
|
|||||||
{ children, area, visible, className, ...props },
|
{ children, area, visible, className, ...props },
|
||||||
ref
|
ref
|
||||||
) {
|
) {
|
||||||
const layout = useBookerStore((state) => state.layout);
|
const layout = useBookerStoreContext((state) => state.layout);
|
||||||
let gridClassName: string;
|
let gridClassName: string;
|
||||||
|
|
||||||
if (typeof area === "string") {
|
if (typeof area === "string") {
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { useEffect, useRef } from "react";
|
|||||||
import { shallow } from "zustand/shallow";
|
import { shallow } from "zustand/shallow";
|
||||||
|
|
||||||
import { useEmbedType, useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
import { useEmbedType, useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||||
import type { BookerLayouts } from "@calcom/prisma/zod-utils";
|
import type { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||||
import { defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
|
import { defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
import { extraDaysConfig } from "../../config";
|
import { extraDaysConfig } from "../../config";
|
||||||
import { useBookerStore } from "../../store";
|
|
||||||
import type { BookerLayout } from "../../types";
|
import type { BookerLayout } from "../../types";
|
||||||
import { validateLayout } from "../../utils/layout";
|
import { validateLayout } from "../../utils/layout";
|
||||||
import { getQueryParam } from "../../utils/query-param";
|
import { getQueryParam } from "../../utils/query-param";
|
||||||
@@ -18,7 +18,7 @@ export type UseBookerLayoutType = ReturnType<typeof useBookerLayout>;
|
|||||||
export const useBookerLayout = (
|
export const useBookerLayout = (
|
||||||
profileBookerLayouts: BookerEvent["profile"]["bookerLayouts"] | undefined | null
|
profileBookerLayouts: BookerEvent["profile"]["bookerLayouts"] | undefined | null
|
||||||
) => {
|
) => {
|
||||||
const [_layout, setLayout] = useBookerStore((state) => [state.layout, state.setLayout], shallow);
|
const [_layout, setLayout] = useBookerStoreContext((state) => [state.layout, state.setLayout], shallow);
|
||||||
const isEmbed = useIsEmbed();
|
const isEmbed = useIsEmbed();
|
||||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||||
const isTablet = useMediaQuery("(max-width: 1024px)");
|
const isTablet = useMediaQuery("(max-width: 1024px)");
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { shallow } from "zustand/shallow";
|
import { shallow } from "zustand/shallow";
|
||||||
|
|
||||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
|
|
||||||
import { useTimePreferences } from "../../../lib/timePreferences";
|
import { useTimePreferences } from "../../../lib/timePreferences";
|
||||||
import { getBookerTimezone } from "../../utils/getBookerTimezone";
|
import { getBookerTimezone } from "../../utils/getBookerTimezone";
|
||||||
|
|
||||||
export const useBookerTime = () => {
|
export const useBookerTime = () => {
|
||||||
const [timezoneFromBookerStore] = useBookerStore((state) => [state.timezone], shallow);
|
const [timezoneFromBookerStore] = useBookerStoreContext((state) => [state.timezone], shallow);
|
||||||
const { timezone: timezoneFromTimePreferences, timeFormat } = useTimePreferences();
|
const { timezone: timezoneFromTimePreferences, timeFormat } = useTimePreferences();
|
||||||
const timezone = getBookerTimezone({
|
const timezone = getBookerTimezone({
|
||||||
storeTimezone: timezoneFromBookerStore,
|
storeTimezone: timezoneFromBookerStore,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
|
|||||||
import { useHandleBookEvent } from "@calcom/atoms/hooks/bookings/useHandleBookEvent";
|
import { useHandleBookEvent } from "@calcom/atoms/hooks/bookings/useHandleBookEvent";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
|
import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||||
import { updateQueryParam, getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
|
import { updateQueryParam, getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
|
||||||
import { createBooking, createRecurringBooking, createInstantBooking } from "@calcom/features/bookings/lib";
|
import { createBooking, createRecurringBooking, createInstantBooking } from "@calcom/features/bookings/lib";
|
||||||
@@ -117,21 +118,21 @@ export const useBookings = ({
|
|||||||
isBookingDryRun,
|
isBookingDryRun,
|
||||||
}: IUseBookings) => {
|
}: IUseBookings) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const eventSlug = useBookerStore((state) => state.eventSlug);
|
const eventSlug = useBookerStoreContext((state) => state.eventSlug);
|
||||||
const eventTypeId = useBookerStore((state) => state.eventId);
|
const eventTypeId = useBookerStoreContext((state) => state.eventId);
|
||||||
const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting);
|
const isInstantMeeting = useBookerStoreContext((state) => state.isInstantMeeting);
|
||||||
|
|
||||||
const rescheduleUid = useBookerStore((state) => state.rescheduleUid);
|
const rescheduleUid = useBookerStoreContext((state) => state.rescheduleUid);
|
||||||
const rescheduledBy = useBookerStore((state) => state.rescheduledBy);
|
const rescheduledBy = useBookerStoreContext((state) => state.rescheduledBy);
|
||||||
const bookingData = useBookerStore((state) => state.bookingData);
|
const bookingData = useBookerStoreContext((state) => state.bookingData);
|
||||||
const timeslot = useBookerStore((state) => state.selectedTimeslot);
|
const timeslot = useBookerStoreContext((state) => state.selectedTimeslot);
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const bookingSuccessRedirect = useBookingSuccessRedirect();
|
const bookingSuccessRedirect = useBookingSuccessRedirect();
|
||||||
const bookerFormErrorRef = useRef<HTMLDivElement>(null);
|
const bookerFormErrorRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [instantMeetingTokenExpiryTime, setExpiryTime] = useState<Date | undefined>();
|
const [instantMeetingTokenExpiryTime, setExpiryTime] = useState<Date | undefined>();
|
||||||
const [instantVideoMeetingUrl, setInstantVideoMeetingUrl] = useState<string | undefined>();
|
const [instantVideoMeetingUrl, setInstantVideoMeetingUrl] = useState<string | undefined>();
|
||||||
const duration = useBookerStore((state) => state.selectedDuration);
|
const duration = useBookerStoreContext((state) => state.selectedDuration);
|
||||||
|
|
||||||
const isRescheduling = !!rescheduleUid && !!bookingData;
|
const isRescheduling = !!rescheduleUid && !!bookingData;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect, useRef } from "react";
|
|||||||
import { shallow } from "zustand/shallow";
|
import { shallow } from "zustand/shallow";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useSlotReservationId } from "@calcom/features/bookings/Booker/useSlotReservationId";
|
import { useSlotReservationId } from "@calcom/features/bookings/Booker/useSlotReservationId";
|
||||||
import { isBookingDryRun } from "@calcom/features/bookings/Booker/utils/isBookingDryRun";
|
import { isBookingDryRun } from "@calcom/features/bookings/Booker/utils/isBookingDryRun";
|
||||||
import {
|
import {
|
||||||
@@ -78,10 +78,10 @@ const useQuickAvailabilityChecks = ({
|
|||||||
export type UseSlotsReturnType = ReturnType<typeof useSlots>;
|
export type UseSlotsReturnType = ReturnType<typeof useSlots>;
|
||||||
|
|
||||||
export const useSlots = (event: { id: number; length: number } | null) => {
|
export const useSlots = (event: { id: number; length: number } | null) => {
|
||||||
const selectedDuration = useBookerStore((state) => state.selectedDuration);
|
const selectedDuration = useBookerStoreContext((state) => state.selectedDuration);
|
||||||
const searchParams = useCompatSearchParams();
|
const searchParams = useCompatSearchParams();
|
||||||
const [selectedTimeslot, setSelectedTimeslot, tentativeSelectedTimeslots, setTentativeSelectedTimeslots] =
|
const [selectedTimeslot, setSelectedTimeslot, tentativeSelectedTimeslots, setTentativeSelectedTimeslots] =
|
||||||
useBookerStore(
|
useBookerStoreContext(
|
||||||
(state) => [
|
(state) => [
|
||||||
state.selectedTimeslot,
|
state.selectedTimeslot,
|
||||||
state.setSelectedTimeslot,
|
state.setSelectedTimeslot,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { updateQueryParam, getQueryParam, removeQueryParam } from "./utils/query
|
|||||||
* Arguments passed into store initializer, containing
|
* Arguments passed into store initializer, containing
|
||||||
* the event data.
|
* the event data.
|
||||||
*/
|
*/
|
||||||
type StoreInitializeType = {
|
export type StoreInitializeType = {
|
||||||
username: string;
|
username: string;
|
||||||
eventSlug: string;
|
eventSlug: string;
|
||||||
// Month can be undefined if it's not passed in as a prop.
|
// Month can be undefined if it's not passed in as a prop.
|
||||||
@@ -174,13 +174,10 @@ export type BookerStore = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The booker store contains the data of the component's
|
* Creates a new booker store instance
|
||||||
* current state. This data can be reused within child components
|
|
||||||
* by importing this hook.
|
|
||||||
*
|
|
||||||
* See comments in interface above for more information on it's specific values.
|
|
||||||
*/
|
*/
|
||||||
export const useBookerStore = createWithEqualityFn<BookerStore>((set, get) => ({
|
export const createBookerStore = () =>
|
||||||
|
createWithEqualityFn<BookerStore>((set, get) => ({
|
||||||
state: "loading",
|
state: "loading",
|
||||||
setState: (state: BookerState) => set({ state }),
|
setState: (state: BookerState) => set({ state }),
|
||||||
layout: BookerLayouts.MONTH_VIEW,
|
layout: BookerLayouts.MONTH_VIEW,
|
||||||
@@ -446,7 +443,12 @@ export const useBookerStore = createWithEqualityFn<BookerStore>((set, get) => ({
|
|||||||
},
|
},
|
||||||
isPlatform: false,
|
isPlatform: false,
|
||||||
allowUpdatingUrlParams: true,
|
allowUpdatingUrlParams: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default global store instance for backward compatibility
|
||||||
|
*/
|
||||||
|
export const useBookerStore = createBookerStore();
|
||||||
|
|
||||||
export const useInitializeBookerStore = ({
|
export const useInitializeBookerStore = ({
|
||||||
username,
|
username,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { shallow } from "zustand/shallow";
|
import { shallow } from "zustand/shallow";
|
||||||
|
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useSchedule } from "@calcom/features/schedules/lib/use-schedule/useSchedule";
|
import { useSchedule } from "@calcom/features/schedules/lib/use-schedule/useSchedule";
|
||||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
|
|
||||||
import { useBookerTime } from "../components/hooks/useBookerTime";
|
import { useBookerTime } from "../components/hooks/useBookerTime";
|
||||||
import { useBookerStore } from "../store";
|
|
||||||
|
|
||||||
export type useEventReturnType = ReturnType<typeof useEvent>;
|
export type useEventReturnType = ReturnType<typeof useEvent>;
|
||||||
export type useScheduleForEventReturnType = ReturnType<typeof useScheduleForEvent>;
|
export type useScheduleForEventReturnType = ReturnType<typeof useScheduleForEvent>;
|
||||||
@@ -19,7 +19,7 @@ export type useScheduleForEventReturnType = ReturnType<typeof useScheduleForEven
|
|||||||
* of combining multiple conditional hooks.
|
* of combining multiple conditional hooks.
|
||||||
*/
|
*/
|
||||||
export const useEvent = (props?: { fromRedirectOfNonOrgLink?: boolean; disabled?: boolean }) => {
|
export const useEvent = (props?: { fromRedirectOfNonOrgLink?: boolean; disabled?: boolean }) => {
|
||||||
const [username, eventSlug, isTeamEvent, org] = useBookerStore(
|
const [username, eventSlug, isTeamEvent, org] = useBookerStoreContext(
|
||||||
(state) => [state.username, state.eventSlug, state.isTeamEvent, state.org],
|
(state) => [state.username, state.eventSlug, state.isTeamEvent, state.org],
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
@@ -89,7 +89,7 @@ export const useScheduleForEvent = ({
|
|||||||
useApiV2?: boolean;
|
useApiV2?: boolean;
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
const { timezone } = useBookerTime();
|
const { timezone } = useBookerTime();
|
||||||
const [usernameFromStore, eventSlugFromStore, monthFromStore, durationFromStore] = useBookerStore(
|
const [usernameFromStore, eventSlugFromStore, monthFromStore, durationFromStore] = useBookerStoreContext(
|
||||||
(state) => [state.username, state.eventSlug, state.month, state.selectedDuration],
|
(state) => [state.username, state.eventSlug, state.month, state.selectedDuration],
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useMemo } from "react";
|
|||||||
|
|
||||||
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { OutOfOfficeInSlots } from "@calcom/features/bookings/Booker/components/OutOfOfficeInSlots";
|
import { OutOfOfficeInSlots } from "@calcom/features/bookings/Booker/components/OutOfOfficeInSlots";
|
||||||
import type { IUseBookingLoadingStates } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
|
import type { IUseBookingLoadingStates } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
|
||||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||||
@@ -19,7 +20,6 @@ import { Icon } from "@calcom/ui/components/icon";
|
|||||||
import { SkeletonText } from "@calcom/ui/components/skeleton";
|
import { SkeletonText } from "@calcom/ui/components/skeleton";
|
||||||
|
|
||||||
import { useBookerTime } from "../Booker/components/hooks/useBookerTime";
|
import { useBookerTime } from "../Booker/components/hooks/useBookerTime";
|
||||||
import { useBookerStore } from "../Booker/store";
|
|
||||||
import { getQueryParam } from "../Booker/utils/query-param";
|
import { getQueryParam } from "../Booker/utils/query-param";
|
||||||
import { useCheckOverlapWithOverlay } from "../lib/useCheckOverlapWithOverlay";
|
import { useCheckOverlapWithOverlay } from "../lib/useCheckOverlapWithOverlay";
|
||||||
import type { Slots } from "../types";
|
import type { Slots } from "../types";
|
||||||
@@ -112,8 +112,8 @@ const SlotItem = ({
|
|||||||
getQueryParam("overlayCalendar") === "true" || localStorage.getItem("overlayCalendarSwitchDefault");
|
getQueryParam("overlayCalendar") === "true" || localStorage.getItem("overlayCalendarSwitchDefault");
|
||||||
|
|
||||||
const { timeFormat, timezone } = useBookerTime();
|
const { timeFormat, timezone } = useBookerTime();
|
||||||
const bookingData = useBookerStore((state) => state.bookingData);
|
const bookingData = useBookerStoreContext((state) => state.bookingData);
|
||||||
const layout = useBookerStore((state) => state.layout);
|
const layout = useBookerStoreContext((state) => state.layout);
|
||||||
const hasTimeSlots = !!seatsPerTimeSlot;
|
const hasTimeSlots = !!seatsPerTimeSlot;
|
||||||
const computedDateWithUsersTimezone = dayjs.utc(slot.time).tz(timezone);
|
const computedDateWithUsersTimezone = dayjs.utc(slot.time).tz(timezone);
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ const SlotItem = ({
|
|||||||
|
|
||||||
const offset = (usersTimezoneDate.utcOffset() - nowDate.utcOffset()) / 60;
|
const offset = (usersTimezoneDate.utcOffset() - nowDate.utcOffset()) / 60;
|
||||||
|
|
||||||
const selectedTimeslot = useBookerStore((state) => state.selectedTimeslot);
|
const selectedTimeslot = useBookerStoreContext((state) => state.selectedTimeslot);
|
||||||
|
|
||||||
const { isOverlapping, overlappingTimeEnd, overlappingTimeStart } = useCheckOverlapWithOverlay({
|
const { isOverlapping, overlappingTimeEnd, overlappingTimeStart } = useCheckOverlapWithOverlay({
|
||||||
start: computedDateWithUsersTimezone,
|
start: computedDateWithUsersTimezone,
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { shallow } from "zustand/shallow";
|
|||||||
|
|
||||||
import type { Dayjs } from "@calcom/dayjs";
|
import type { Dayjs } from "@calcom/dayjs";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { nameOfDay } from "@calcom/lib/weekday";
|
import { nameOfDay } from "@calcom/lib/weekday";
|
||||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||||
import classNames from "@calcom/ui/classNames";
|
import classNames from "@calcom/ui/classNames";
|
||||||
|
|
||||||
import { useBookerStore } from "../Booker/store";
|
|
||||||
import { TimeFormatToggle } from "./TimeFormatToggle";
|
import { TimeFormatToggle } from "./TimeFormatToggle";
|
||||||
|
|
||||||
type AvailableTimesHeaderProps = {
|
type AvailableTimesHeaderProps = {
|
||||||
@@ -28,7 +28,7 @@ export const AvailableTimesHeader = ({
|
|||||||
customClassNames,
|
customClassNames,
|
||||||
}: AvailableTimesHeaderProps) => {
|
}: AvailableTimesHeaderProps) => {
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const [layout] = useBookerStore((state) => [state.layout], shallow);
|
const [layout] = useBookerStoreContext((state) => [state.layout], shallow);
|
||||||
const isColumnView = layout === BookerLayouts.COLUMN_VIEW;
|
const isColumnView = layout === BookerLayouts.COLUMN_VIEW;
|
||||||
const isMonthView = layout === BookerLayouts.MONTH_VIEW;
|
const isMonthView = layout === BookerLayouts.MONTH_VIEW;
|
||||||
const isToday = dayjs().isSame(date, "day");
|
const isToday = dayjs().isSame(date, "day");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { shallow } from "zustand/shallow";
|
|||||||
import type { Dayjs } from "@calcom/dayjs";
|
import type { Dayjs } from "@calcom/dayjs";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { useEmbedStyles } from "@calcom/embed-core/embed-iframe";
|
import { useEmbedStyles } from "@calcom/embed-core/embed-iframe";
|
||||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { getAvailableDatesInMonth } from "@calcom/features/calendars/lib/getAvailableDatesInMonth";
|
import { getAvailableDatesInMonth } from "@calcom/features/calendars/lib/getAvailableDatesInMonth";
|
||||||
import { daysInMonth, yyyymmdd } from "@calcom/lib/dayjs";
|
import { daysInMonth, yyyymmdd } from "@calcom/lib/dayjs";
|
||||||
import type { IFromUser, IToUser } from "@calcom/lib/getUserAvailability";
|
import type { IFromUser, IToUser } from "@calcom/lib/getUserAvailability";
|
||||||
@@ -227,7 +227,7 @@ const Days = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [selectedDatesAndTimes] = useBookerStore((state) => [state.selectedDatesAndTimes], shallow);
|
const [selectedDatesAndTimes] = useBookerStoreContext((state) => [state.selectedDatesAndTimes], shallow);
|
||||||
|
|
||||||
const isActive = (day: dayjs.Dayjs) => {
|
const isActive = (day: dayjs.Dayjs) => {
|
||||||
// for selecting a range of dates
|
// for selecting a range of dates
|
||||||
@@ -400,7 +400,7 @@ const DatePicker = ({
|
|||||||
minDate && rawBrowsingDate.valueOf() < minDate.valueOf() ? dayjs(minDate) : rawBrowsingDate;
|
minDate && rawBrowsingDate.valueOf() < minDate.valueOf() ? dayjs(minDate) : rawBrowsingDate;
|
||||||
|
|
||||||
const { i18n, t } = useLocale();
|
const { i18n, t } = useLocale();
|
||||||
const bookingData = useBookerStore((state) => state.bookingData);
|
const bookingData = useBookerStoreContext((state) => state.bookingData);
|
||||||
const isBookingInPast = bookingData ? new Date(bookingData.endTime) < new Date() : false;
|
const isBookingInPast = bookingData ? new Date(bookingData.endTime) < new Date() : false;
|
||||||
const changeMonth = (newMonth: number) => {
|
const changeMonth = (newMonth: number) => {
|
||||||
if (onMonthChange) {
|
if (onMonthChange) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import React from "react";
|
|||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { BookerStoreProvider } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { PeriodType } from "@calcom/prisma/enums";
|
import { PeriodType } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { DatePicker } from "../DatePicker";
|
import { DatePicker } from "../DatePicker";
|
||||||
@@ -16,6 +17,7 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
test("Should render correctly with default date", async () => {
|
test("Should render correctly with default date", async () => {
|
||||||
const testDate = dayjs("2024-02-20");
|
const testDate = dayjs("2024-02-20");
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
|
<BookerStoreProvider>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
onChange={noop}
|
onChange={noop}
|
||||||
@@ -30,6 +32,7 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
</BookerStoreProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedMonthLabel = getByTestId("selected-month-label");
|
const selectedMonthLabel = getByTestId("selected-month-label");
|
||||||
@@ -40,9 +43,11 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
const testDate = dayjs("2024-02-20");
|
const testDate = dayjs("2024-02-20");
|
||||||
const minDate = dayjs("2025-02-10");
|
const minDate = dayjs("2025-02-10");
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
|
<BookerStoreProvider>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<DatePicker onChange={noop} browsingDate={testDate} minDate={minDate.toDate()} locale="en" />
|
<DatePicker onChange={noop} browsingDate={testDate} minDate={minDate.toDate()} locale="en" />
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
</BookerStoreProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedMonthLabel = getByTestId("selected-month-label");
|
const selectedMonthLabel = getByTestId("selected-month-label");
|
||||||
@@ -53,9 +58,11 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
const testDate = dayjs("2025-03-20");
|
const testDate = dayjs("2025-03-20");
|
||||||
const minDate = dayjs("2025-02-10");
|
const minDate = dayjs("2025-02-10");
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
|
<BookerStoreProvider>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<DatePicker onChange={noop} browsingDate={testDate} minDate={minDate.toDate()} locale="en" />
|
<DatePicker onChange={noop} browsingDate={testDate} minDate={minDate.toDate()} locale="en" />
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
</BookerStoreProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedMonthLabel = getByTestId("selected-month-label");
|
const selectedMonthLabel = getByTestId("selected-month-label");
|
||||||
@@ -85,6 +92,7 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const { getAllByTestId } = render(
|
const { getAllByTestId } = render(
|
||||||
|
<BookerStoreProvider>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
onChange={noop}
|
onChange={noop}
|
||||||
@@ -101,6 +109,7 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
</BookerStoreProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const dayElements = getAllByTestId("day");
|
const dayElements = getAllByTestId("day");
|
||||||
@@ -127,6 +136,7 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const { getAllByTestId, queryByText } = render(
|
const { getAllByTestId, queryByText } = render(
|
||||||
|
<BookerStoreProvider>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
onChange={noop}
|
onChange={noop}
|
||||||
@@ -143,6 +153,7 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
</BookerStoreProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const dayElements = getAllByTestId("day");
|
const dayElements = getAllByTestId("day");
|
||||||
@@ -165,6 +176,7 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
const slots = createMockSlots(["2024-01-25", "2024-02-01"]);
|
const slots = createMockSlots(["2024-01-25", "2024-02-01"]);
|
||||||
|
|
||||||
const { getAllByTestId } = render(
|
const { getAllByTestId } = render(
|
||||||
|
<BookerStoreProvider>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
onChange={noop}
|
onChange={noop}
|
||||||
@@ -181,6 +193,7 @@ describe("Tests for DatePicker Component", () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
</BookerStoreProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const dayElements = getAllByTestId("day");
|
const dayElements = getAllByTestId("day");
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ import { shallow } from "zustand/shallow";
|
|||||||
import type { Dayjs } from "@calcom/dayjs";
|
import type { Dayjs } from "@calcom/dayjs";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { AvailableTimes, AvailableTimesHeader } from "@calcom/features/bookings";
|
import { AvailableTimes, AvailableTimesHeader } from "@calcom/features/bookings";
|
||||||
import { useBookerStore, useInitializeBookerStore } from "@calcom/features/bookings/Booker/store";
|
import {
|
||||||
|
BookerStoreProvider,
|
||||||
|
useInitializeBookerStoreContext,
|
||||||
|
useBookerStoreContext,
|
||||||
|
} from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
|
import { useInitializeBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||||
import { useEvent, useScheduleForEvent } from "@calcom/features/bookings/Booker/utils/event";
|
import { useEvent, useScheduleForEvent } from "@calcom/features/bookings/Booker/utils/event";
|
||||||
import DatePicker from "@calcom/features/calendars/DatePicker";
|
import DatePicker from "@calcom/features/calendars/DatePicker";
|
||||||
import { Dialog } from "@calcom/features/components/controlled-dialog";
|
import { Dialog } from "@calcom/features/components/controlled-dialog";
|
||||||
@@ -260,13 +265,21 @@ const EmailEmbed = ({
|
|||||||
org: orgSlug,
|
org: orgSlug,
|
||||||
isTeamEvent,
|
isTeamEvent,
|
||||||
});
|
});
|
||||||
|
useInitializeBookerStoreContext({
|
||||||
|
username,
|
||||||
|
eventSlug: eventType?.slug ?? "",
|
||||||
|
eventId: eventType?.id,
|
||||||
|
layout: BookerLayouts.MONTH_VIEW,
|
||||||
|
org: orgSlug,
|
||||||
|
isTeamEvent,
|
||||||
|
});
|
||||||
|
|
||||||
const [month, selectedDate, selectedDatesAndTimes] = useBookerStore(
|
const [month, selectedDate, selectedDatesAndTimes] = useBookerStoreContext(
|
||||||
(state) => [state.month, state.selectedDate, state.selectedDatesAndTimes],
|
(state) => [state.month, state.selectedDate, state.selectedDatesAndTimes],
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
const [setSelectedDate, setMonth, setSelectedDatesAndTimes, setSelectedTimeslot, setTimezone] =
|
const [setSelectedDate, setMonth, setSelectedDatesAndTimes, setSelectedTimeslot, setTimezone] =
|
||||||
useBookerStore(
|
useBookerStoreContext(
|
||||||
(state) => [
|
(state) => [
|
||||||
state.setSelectedDate,
|
state.setSelectedDate,
|
||||||
state.setMonth,
|
state.setMonth,
|
||||||
@@ -686,7 +699,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
const emailContentRef = useRef<HTMLDivElement>(null);
|
const emailContentRef = useRef<HTMLDivElement>(null);
|
||||||
const { data } = useSession();
|
const { data } = useSession();
|
||||||
|
|
||||||
const [month, selectedDatesAndTimes] = useBookerStore(
|
const [month, selectedDatesAndTimes] = useBookerStoreContext(
|
||||||
(state) => [state.month, state.selectedDatesAndTimes],
|
(state) => [state.month, state.selectedDatesAndTimes],
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
@@ -1369,6 +1382,7 @@ export const EmbedDialog = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<BookerStoreProvider>
|
||||||
<Dialog
|
<Dialog
|
||||||
{...(noQueryParamMode
|
{...(noQueryParamMode
|
||||||
? {
|
? {
|
||||||
@@ -1395,6 +1409,7 @@ export const EmbedDialog = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
</BookerStoreProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { BookerStoreProvider } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||||
import classNames from "@calcom/ui/classNames";
|
import classNames from "@calcom/ui/classNames";
|
||||||
|
|
||||||
@@ -63,5 +64,9 @@ const TroubleshooterComponent = ({ month }: TroubleshooterProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Troubleshooter = ({ month }: TroubleshooterProps) => {
|
export const Troubleshooter = ({ month }: TroubleshooterProps) => {
|
||||||
return <TroubleshooterComponent month={month} />;
|
return (
|
||||||
|
<BookerStoreProvider>
|
||||||
|
<TroubleshooterComponent month={month} />
|
||||||
|
</BookerStoreProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export async function getConnectedApps({
|
|||||||
if (includeTeamInstalledApps || teamId) {
|
if (includeTeamInstalledApps || teamId) {
|
||||||
const teamsQuery = await prisma.team.findMany({
|
const teamsQuery = await prisma.team.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
...(teamId ? { id: teamId } : {}),
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
|||||||
@@ -229,4 +229,61 @@ export class CredentialRepository {
|
|||||||
static async updateWhereId({ id, data }: { id: number; data: { key: Prisma.InputJsonValue } }) {
|
static async updateWhereId({ id, data }: { id: number; data: { key: Prisma.InputJsonValue } }) {
|
||||||
return prisma.credential.update({ where: { id }, data });
|
return prisma.credential.update({ where: { id }, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async findPaymentCredentialByAppIdAndTeamId({
|
||||||
|
appId,
|
||||||
|
teamId,
|
||||||
|
}: {
|
||||||
|
appId: string | null;
|
||||||
|
teamId: number;
|
||||||
|
}) {
|
||||||
|
return await prisma.credential.findFirst({
|
||||||
|
where: {
|
||||||
|
teamId,
|
||||||
|
appId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
app: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findPaymentCredentialByAppIdAndUserId({
|
||||||
|
appId,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
appId: string | null;
|
||||||
|
userId: number;
|
||||||
|
}) {
|
||||||
|
return await prisma.credential.findFirst({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
appId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
app: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findPaymentCredentialByAppIdAndUserIdOrTeamId({
|
||||||
|
appId,
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
}: {
|
||||||
|
appId: string | null;
|
||||||
|
userId: number;
|
||||||
|
teamId?: number | null;
|
||||||
|
}) {
|
||||||
|
const idToSearchObject = teamId ? { teamId } : { userId };
|
||||||
|
return await prisma.credential.findFirst({
|
||||||
|
where: {
|
||||||
|
...idToSearchObject,
|
||||||
|
appId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
app: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -333,4 +333,21 @@ export class TeamRepository {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findParentOrganizationByTeamId(teamId: number) {
|
||||||
|
const team = await this.prismaClient.team.findUnique({
|
||||||
|
where: {
|
||||||
|
id: teamId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
parent: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return team?.parent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
## 1.1.2
|
## 1.1.2
|
||||||
|
|
||||||
|
## 1.3.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#22925](https://github.com/calcom/cal.com/pull/22925) [`4c01f17`](https://github.com/calcom/cal.com/commit/4c01f171aebcc5a64453c27e950303902cc15309) Thanks [@sean-brydon](https://github.com/sean-brydon)! - Fixes an issues for the Booker atom wherein when multiple widgets were being placed on the same page, changes made in one widget would also get reflected in the others.
|
||||||
|
|
||||||
## 1.3.0
|
## 1.3.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import React, {
|
|||||||
import { Controller, useFieldArray, useForm, useFormContext, useWatch } from "react-hook-form";
|
import { Controller, useFieldArray, useForm, useFormContext, useWatch } from "react-hook-form";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { BookerStoreProvider } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { Dialog } from "@calcom/features/components/controlled-dialog";
|
import { Dialog } from "@calcom/features/components/controlled-dialog";
|
||||||
import { TimezoneSelect as WebTimezoneSelect } from "@calcom/features/components/timezone-select";
|
import { TimezoneSelect as WebTimezoneSelect } from "@calcom/features/components/timezone-select";
|
||||||
import type {
|
import type {
|
||||||
@@ -672,6 +673,7 @@ export const AvailabilitySettings = forwardRef<AvailabilitySettingsFormRef, Avai
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{enableOverrides && (
|
{enableOverrides && (
|
||||||
|
<BookerStoreProvider>
|
||||||
<DateOverride
|
<DateOverride
|
||||||
workingHours={schedule.workingHours}
|
workingHours={schedule.workingHours}
|
||||||
userTimeFormat={timeFormat}
|
userTimeFormat={timeFormat}
|
||||||
@@ -685,6 +687,7 @@ export const AvailabilitySettings = forwardRef<AvailabilitySettingsFormRef, Avai
|
|||||||
overridesModalClassNames={customClassNames?.overridesModalClassNames}
|
overridesModalClassNames={customClassNames?.overridesModalClassNames}
|
||||||
classNames={customClassNames?.dateOverrideClassNames}
|
classNames={customClassNames?.dateOverrideClassNames}
|
||||||
/>
|
/>
|
||||||
|
</BookerStoreProvider>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-40 col-span-3 hidden space-y-2 md:block lg:col-span-1">
|
<div className="min-w-40 col-span-3 hidden space-y-2 md:block lg:col-span-1">
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import { shallow } from "zustand/shallow";
|
|||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { Booker as BookerComponent } from "@calcom/features/bookings/Booker";
|
import { Booker as BookerComponent } from "@calcom/features/bookings/Booker";
|
||||||
|
import {
|
||||||
|
BookerStoreProvider,
|
||||||
|
useInitializeBookerStoreContext,
|
||||||
|
useBookerStoreContext,
|
||||||
|
} from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useBookerLayout } from "@calcom/features/bookings/Booker/components/hooks/useBookerLayout";
|
import { useBookerLayout } from "@calcom/features/bookings/Booker/components/hooks/useBookerLayout";
|
||||||
import { useBookingForm } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
import { useBookingForm } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
||||||
import { useLocalSet } from "@calcom/features/bookings/Booker/components/hooks/useLocalSet";
|
import { useLocalSet } from "@calcom/features/bookings/Booker/components/hooks/useLocalSet";
|
||||||
@@ -40,7 +45,7 @@ import type {
|
|||||||
BookerStoreValues,
|
BookerStoreValues,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export const BookerPlatformWrapper = (
|
const BookerPlatformWrapperComponent = (
|
||||||
props: BookerPlatformWrapperAtomPropsForIndividual | BookerPlatformWrapperAtomPropsForTeam
|
props: BookerPlatformWrapperAtomPropsForIndividual | BookerPlatformWrapperAtomPropsForTeam
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
@@ -64,15 +69,18 @@ export const BookerPlatformWrapper = (
|
|||||||
|
|
||||||
const { clientId } = useAtomsContext();
|
const { clientId } = useAtomsContext();
|
||||||
const teamId: number | undefined = props.isTeamEvent ? props.teamId : undefined;
|
const teamId: number | undefined = props.isTeamEvent ? props.teamId : undefined;
|
||||||
const [bookerState, setBookerState] = useBookerStore((state) => [state.state, state.setState], shallow);
|
const [bookerState, setBookerState] = useBookerStoreContext(
|
||||||
const setSelectedDate = useBookerStore((state) => state.setSelectedDate);
|
(state) => [state.state, state.setState],
|
||||||
const setSelectedDuration = useBookerStore((state) => state.setSelectedDuration);
|
shallow
|
||||||
const setBookingData = useBookerStore((state) => state.setBookingData);
|
);
|
||||||
const setOrg = useBookerStore((state) => state.setOrg);
|
const setSelectedDate = useBookerStoreContext((state) => state.setSelectedDate);
|
||||||
const bookingData = useBookerStore((state) => state.bookingData);
|
const setSelectedDuration = useBookerStoreContext((state) => state.setSelectedDuration);
|
||||||
const setSelectedTimeslot = useBookerStore((state) => state.setSelectedTimeslot);
|
const setBookingData = useBookerStoreContext((state) => state.setBookingData);
|
||||||
const setSelectedMonth = useBookerStore((state) => state.setMonth);
|
const setOrg = useBookerStoreContext((state) => state.setOrg);
|
||||||
const selectedDuration = useBookerStore((state) => state.selectedDuration);
|
const bookingData = useBookerStoreContext((state) => state.bookingData);
|
||||||
|
const setSelectedTimeslot = useBookerStoreContext((state) => state.setSelectedTimeslot);
|
||||||
|
const setSelectedMonth = useBookerStoreContext((state) => state.setMonth);
|
||||||
|
const selectedDuration = useBookerStoreContext((state) => state.selectedDuration);
|
||||||
|
|
||||||
const [isOverlayCalendarEnabled, setIsOverlayCalendarEnabled] = useState(
|
const [isOverlayCalendarEnabled, setIsOverlayCalendarEnabled] = useState(
|
||||||
Boolean(localStorage?.getItem?.("overlayCalendarSwitchDefault"))
|
Boolean(localStorage?.getItem?.("overlayCalendarSwitchDefault"))
|
||||||
@@ -171,11 +179,27 @@ export const BookerPlatformWrapper = (
|
|||||||
isPlatform: true,
|
isPlatform: true,
|
||||||
allowUpdatingUrlParams,
|
allowUpdatingUrlParams,
|
||||||
});
|
});
|
||||||
const [dayCount] = useBookerStore((state) => [state.dayCount, state.setDayCount], shallow);
|
useInitializeBookerStoreContext({
|
||||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
...props,
|
||||||
|
teamMemberEmail,
|
||||||
|
crmAppSlug,
|
||||||
|
crmOwnerRecordType,
|
||||||
|
crmRecordId: props.crmRecordId,
|
||||||
|
eventId: event?.data?.id,
|
||||||
|
rescheduleUid: props.rescheduleUid ?? null,
|
||||||
|
bookingUid: props.bookingUid ?? null,
|
||||||
|
layout: layout,
|
||||||
|
org: props.entity?.orgSlug,
|
||||||
|
username,
|
||||||
|
bookingData,
|
||||||
|
isPlatform: true,
|
||||||
|
allowUpdatingUrlParams,
|
||||||
|
});
|
||||||
|
const [dayCount] = useBookerStoreContext((state) => [state.dayCount, state.setDayCount], shallow);
|
||||||
|
const selectedDate = useBookerStoreContext((state) => state.selectedDate);
|
||||||
|
|
||||||
const month = useBookerStore((state) => state.month);
|
const month = useBookerStoreContext((state) => state.month);
|
||||||
const eventSlug = useBookerStore((state) => state.eventSlug);
|
const eventSlug = useBookerStoreContext((state) => state.eventSlug);
|
||||||
|
|
||||||
const { data: session } = useMe();
|
const { data: session } = useMe();
|
||||||
const hasSession = !!session;
|
const hasSession = !!session;
|
||||||
@@ -557,6 +581,16 @@ export const BookerPlatformWrapper = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BookerPlatformWrapper = (
|
||||||
|
props: BookerPlatformWrapperAtomPropsForIndividual | BookerPlatformWrapperAtomPropsForTeam
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<BookerStoreProvider>
|
||||||
|
<BookerPlatformWrapperComponent {...props} />
|
||||||
|
</BookerStoreProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function formatUsername(username: string | string[]): string {
|
function formatUsername(username: string | string[]): string {
|
||||||
if (typeof username === "string") {
|
if (typeof username === "string") {
|
||||||
return username;
|
return username;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useSession } from "next-auth/react";
|
|||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { usePathname, useRouter } from "next/navigation";
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
import { useMemo, useCallback, useEffect } from "react";
|
import { useMemo, useCallback, useEffect } from "react";
|
||||||
|
import React from "react";
|
||||||
import { shallow } from "zustand/shallow";
|
import { shallow } from "zustand/shallow";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
@@ -11,6 +12,11 @@ import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
|
|||||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||||
import type { BookerProps } from "@calcom/features/bookings/Booker";
|
import type { BookerProps } from "@calcom/features/bookings/Booker";
|
||||||
import { Booker as BookerComponent } from "@calcom/features/bookings/Booker";
|
import { Booker as BookerComponent } from "@calcom/features/bookings/Booker";
|
||||||
|
import {
|
||||||
|
BookerStoreProvider,
|
||||||
|
useInitializeBookerStoreContext,
|
||||||
|
useBookerStoreContext,
|
||||||
|
} from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useBookerLayout } from "@calcom/features/bookings/Booker/components/hooks/useBookerLayout";
|
import { useBookerLayout } from "@calcom/features/bookings/Booker/components/hooks/useBookerLayout";
|
||||||
import { useBookingForm } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
import { useBookingForm } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
||||||
import { useBookings } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
|
import { useBookings } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
|
||||||
@@ -18,19 +24,20 @@ import { useCalendars } from "@calcom/features/bookings/Booker/components/hooks/
|
|||||||
import { useSlots } from "@calcom/features/bookings/Booker/components/hooks/useSlots";
|
import { useSlots } from "@calcom/features/bookings/Booker/components/hooks/useSlots";
|
||||||
import { useVerifyCode } from "@calcom/features/bookings/Booker/components/hooks/useVerifyCode";
|
import { useVerifyCode } from "@calcom/features/bookings/Booker/components/hooks/useVerifyCode";
|
||||||
import { useVerifyEmail } from "@calcom/features/bookings/Booker/components/hooks/useVerifyEmail";
|
import { useVerifyEmail } from "@calcom/features/bookings/Booker/components/hooks/useVerifyEmail";
|
||||||
import { useBookerStore, useInitializeBookerStore } from "@calcom/features/bookings/Booker/store";
|
import { useInitializeBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||||
import { useEvent, useScheduleForEvent } from "@calcom/features/bookings/Booker/utils/event";
|
import { useEvent, useScheduleForEvent } from "@calcom/features/bookings/Booker/utils/event";
|
||||||
import { useBrandColors } from "@calcom/features/bookings/Booker/utils/use-brand-colors";
|
import { useBrandColors } from "@calcom/features/bookings/Booker/utils/use-brand-colors";
|
||||||
import type { getPublicEvent } from "@calcom/features/eventtypes/lib/getPublicEvent";
|
import type { getPublicEvent } from "@calcom/features/eventtypes/lib/getPublicEvent";
|
||||||
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR, WEBAPP_URL } from "@calcom/lib/constants";
|
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||||
|
import { localStorage } from "@calcom/lib/webstorage";
|
||||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
export type BookerWebWrapperAtomProps = BookerProps & {
|
export type BookerWebWrapperAtomProps = BookerProps & {
|
||||||
eventData?: NonNullable<Awaited<ReturnType<typeof getPublicEvent>>>;
|
eventData?: NonNullable<Awaited<ReturnType<typeof getPublicEvent>>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
|
const BookerPlatformWrapperComponent = (props: BookerWebWrapperAtomProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -74,10 +81,20 @@ export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
|
|||||||
org: props.entity.orgSlug,
|
org: props.entity.orgSlug,
|
||||||
timezone,
|
timezone,
|
||||||
});
|
});
|
||||||
|
useInitializeBookerStoreContext({
|
||||||
|
...props,
|
||||||
|
eventId: props.entity.eventTypeId ?? event?.data?.id,
|
||||||
|
rescheduleUid,
|
||||||
|
rescheduledBy,
|
||||||
|
bookingUid: bookingUid,
|
||||||
|
layout: bookerLayout.isMobile ? "mobile" : bookerLayout.defaultLayout,
|
||||||
|
org: props.entity.orgSlug,
|
||||||
|
timezone,
|
||||||
|
});
|
||||||
|
|
||||||
const [bookerState, _] = useBookerStore((state) => [state.state, state.setState], shallow);
|
const [bookerState, _] = useBookerStoreContext((state) => [state.state, state.setState], shallow);
|
||||||
const [dayCount] = useBookerStore((state) => [state.dayCount, state.setDayCount], shallow);
|
const [dayCount] = useBookerStoreContext((state) => [state.dayCount, state.setDayCount], shallow);
|
||||||
const [month] = useBookerStore((state) => [state.month, state.setMonth], shallow);
|
const [month] = useBookerStoreContext((state) => [state.month, state.setMonth], shallow);
|
||||||
|
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const routerQuery = useRouterQuery();
|
const routerQuery = useRouterQuery();
|
||||||
@@ -263,3 +280,11 @@ export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
|
||||||
|
return (
|
||||||
|
<BookerStoreProvider>
|
||||||
|
<BookerPlatformWrapperComponent {...props} />
|
||||||
|
</BookerStoreProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||||
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useBookerTime } from "@calcom/features/bookings/Booker/components/hooks/useBookerTime";
|
import { useBookerTime } from "@calcom/features/bookings/Booker/components/hooks/useBookerTime";
|
||||||
import type { UseBookingFormReturnType } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
import type { UseBookingFormReturnType } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
||||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
|
||||||
import { mapBookingToMutationInput, mapRecurringBookingToMutationInput } from "@calcom/features/bookings/lib";
|
import { mapBookingToMutationInput, mapRecurringBookingToMutationInput } from "@calcom/features/bookings/lib";
|
||||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
@@ -46,23 +46,23 @@ export const useHandleBookEvent = ({
|
|||||||
isBookingDryRun,
|
isBookingDryRun,
|
||||||
}: UseHandleBookingProps) => {
|
}: UseHandleBookingProps) => {
|
||||||
const isPlatform = useIsPlatform();
|
const isPlatform = useIsPlatform();
|
||||||
const setFormValues = useBookerStore((state) => state.setFormValues);
|
const setFormValues = useBookerStoreContext((state) => state.setFormValues);
|
||||||
const storeTimeSlot = useBookerStore((state) => state.selectedTimeslot);
|
const storeTimeSlot = useBookerStoreContext((state) => state.selectedTimeslot);
|
||||||
const duration = useBookerStore((state) => state.selectedDuration);
|
const duration = useBookerStoreContext((state) => state.selectedDuration);
|
||||||
const { timezone } = useBookerTime();
|
const { timezone } = useBookerTime();
|
||||||
const rescheduleUid = useBookerStore((state) => state.rescheduleUid);
|
const rescheduleUid = useBookerStoreContext((state) => state.rescheduleUid);
|
||||||
const rescheduledBy = useBookerStore((state) => state.rescheduledBy);
|
const rescheduledBy = useBookerStoreContext((state) => state.rescheduledBy);
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const username = useBookerStore((state) => state.username);
|
const username = useBookerStoreContext((state) => state.username);
|
||||||
const recurringEventCount = useBookerStore((state) => state.recurringEventCount);
|
const recurringEventCount = useBookerStoreContext((state) => state.recurringEventCount);
|
||||||
const bookingData = useBookerStore((state) => state.bookingData);
|
const bookingData = useBookerStoreContext((state) => state.bookingData);
|
||||||
const seatedEventData = useBookerStore((state) => state.seatedEventData);
|
const seatedEventData = useBookerStoreContext((state) => state.seatedEventData);
|
||||||
const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting);
|
const isInstantMeeting = useBookerStoreContext((state) => state.isInstantMeeting);
|
||||||
const orgSlug = useBookerStore((state) => state.org);
|
const orgSlug = useBookerStoreContext((state) => state.org);
|
||||||
const teamMemberEmail = useBookerStore((state) => state.teamMemberEmail);
|
const teamMemberEmail = useBookerStoreContext((state) => state.teamMemberEmail);
|
||||||
const crmOwnerRecordType = useBookerStore((state) => state.crmOwnerRecordType);
|
const crmOwnerRecordType = useBookerStoreContext((state) => state.crmOwnerRecordType);
|
||||||
const crmAppSlug = useBookerStore((state) => state.crmAppSlug);
|
const crmAppSlug = useBookerStoreContext((state) => state.crmAppSlug);
|
||||||
const crmRecordId = useBookerStore((state) => state.crmRecordId);
|
const crmRecordId = useBookerStoreContext((state) => state.crmRecordId);
|
||||||
const handleError = (err: any) => {
|
const handleError = (err: any) => {
|
||||||
const errorMessage = err?.message ? t(err.message) : t("can_you_try_again");
|
const errorMessage = err?.message ? t(err.message) : t("can_you_try_again");
|
||||||
showToast(errorMessage, "error");
|
showToast(errorMessage, "error");
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect } from "react";
|
|||||||
import { shallow } from "zustand/shallow";
|
import { shallow } from "zustand/shallow";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { useSlotReservationId } from "@calcom/features/bookings/Booker/useSlotReservationId";
|
import { useSlotReservationId } from "@calcom/features/bookings/Booker/useSlotReservationId";
|
||||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||||
import { MINUTES_TO_BOOK } from "@calcom/lib/constants";
|
import { MINUTES_TO_BOOK } from "@calcom/lib/constants";
|
||||||
@@ -36,8 +36,8 @@ export const useSlots = (
|
|||||||
handleSlotReservation,
|
handleSlotReservation,
|
||||||
}: UseSlotsCallbacks & { isBookingDryRun?: boolean } = {}
|
}: UseSlotsCallbacks & { isBookingDryRun?: boolean } = {}
|
||||||
) => {
|
) => {
|
||||||
const selectedDuration = useBookerStore((state) => state.selectedDuration);
|
const selectedDuration = useBookerStoreContext((state) => state.selectedDuration);
|
||||||
const [selectedTimeslot, setSelectedTimeslot] = useBookerStore(
|
const [selectedTimeslot, setSelectedTimeslot] = useBookerStoreContext(
|
||||||
(state) => [state.selectedTimeslot, state.setSelectedTimeslot],
|
(state) => [state.selectedTimeslot, state.setSelectedTimeslot],
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
@@ -52,7 +52,7 @@ export const useSlots = (
|
|||||||
onError: onReserveSlotError,
|
onError: onReserveSlotError,
|
||||||
});
|
});
|
||||||
|
|
||||||
const seatedEventData = useBookerStore((state) => state.seatedEventData);
|
const seatedEventData = useBookerStoreContext((state) => state.seatedEventData);
|
||||||
|
|
||||||
const removeSelectedSlot = useDeleteSelectedSlot({
|
const removeSelectedSlot = useDeleteSelectedSlot({
|
||||||
onSuccess: onDeleteSlotSuccess,
|
onSuccess: onDeleteSlotSuccess,
|
||||||
@@ -85,7 +85,7 @@ export const useSlots = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const timeslot = useBookerStore((state) => state.selectedTimeslot);
|
const timeslot = useBookerStoreContext((state) => state.selectedTimeslot);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleReserveSlot();
|
handleReserveSlot();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Customizable UI components to integrate scheduling into your product.",
|
"description": "Customizable UI components to integrate scheduling into your product.",
|
||||||
"authors": "Cal.com, Inc.",
|
"authors": "Cal.com, Inc.",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch",
|
"dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch",
|
||||||
"build": "NODE_OPTIONS='--max_old_space_size=12288' rm -rf dist && yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
|
"build": "NODE_OPTIONS='--max_old_space_size=12288' rm -rf dist && yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export class CreatePrivateLinkInput {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(1)
|
@Min(1)
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: "Maximum number of times the link can be used. If omitted and expiresAt is not provided, defaults to 1 (one time use).",
|
description:
|
||||||
|
"Maximum number of times the link can be used. If omitted and expiresAt is not provided, defaults to 1 (one time use).",
|
||||||
type: Number,
|
type: Number,
|
||||||
example: 10,
|
example: 10,
|
||||||
minimum: 1,
|
minimum: 1,
|
||||||
@@ -58,4 +59,25 @@ export class UpdatePrivateLinkInput {
|
|||||||
maxUsageCount?: number;
|
maxUsageCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdatePrivateLinkBody {
|
||||||
|
@IsOptional()
|
||||||
|
@IsDate()
|
||||||
|
@Type(() => Date)
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "New expiration date for time-based links",
|
||||||
|
type: Date,
|
||||||
|
example: "2024-12-31T23:59:59.000Z",
|
||||||
|
})
|
||||||
|
expiresAt?: Date;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "New maximum number of times the link can be used",
|
||||||
|
type: Number,
|
||||||
|
example: 10,
|
||||||
|
minimum: 1,
|
||||||
|
})
|
||||||
|
maxUsageCount?: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -99,6 +99,18 @@ export async function getBookings({
|
|||||||
role: {
|
role: {
|
||||||
in: ["ADMIN", "OWNER"],
|
in: ["ADMIN", "OWNER"],
|
||||||
},
|
},
|
||||||
|
...(user.orgId && {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
teamId: user.orgId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
team: {
|
||||||
|
parentId: user.orgId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { sendNoShowFeeChargedEmail } from "@calcom/emails";
|
|||||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||||
import { ErrorWithCode } from "@calcom/lib/errors";
|
import { ErrorWithCode } from "@calcom/lib/errors";
|
||||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||||
|
import { CredentialRepository } from "@calcom/lib/server/repository/credential";
|
||||||
|
import { MembershipRepository } from "@calcom/lib/server/repository/membership";
|
||||||
|
import { TeamRepository } from "@calcom/lib/server/repository/team";
|
||||||
import type { PrismaClient } from "@calcom/prisma";
|
import type { PrismaClient } from "@calcom/prisma";
|
||||||
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
|
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
@@ -20,6 +23,7 @@ interface ChargeCardHandlerOptions {
|
|||||||
}
|
}
|
||||||
export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions) => {
|
export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions) => {
|
||||||
const { prisma } = ctx;
|
const { prisma } = ctx;
|
||||||
|
const teamRepository = new TeamRepository(prisma);
|
||||||
|
|
||||||
const booking = await prisma.booking.findUnique({
|
const booking = await prisma.booking.findUnique({
|
||||||
where: {
|
where: {
|
||||||
@@ -84,18 +88,14 @@ export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const idToSearchObject = booking.eventType?.teamId
|
const userId = ctx.user.id;
|
||||||
? { teamId: booking.eventType.teamId }
|
const teamId = booking.eventType?.teamId;
|
||||||
: { userId: ctx.user.id };
|
const appId = booking.payment[0].appId;
|
||||||
|
|
||||||
if (booking.eventType?.teamId) {
|
if (teamId) {
|
||||||
const userIsInTeam = await prisma.membership.findUnique({
|
const userIsInTeam = await MembershipRepository.findUniqueByUserIdAndTeamId({
|
||||||
where: {
|
userId,
|
||||||
userId_teamId: {
|
teamId,
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId: booking.eventType?.teamId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!userIsInTeam) {
|
if (!userIsInTeam) {
|
||||||
@@ -103,16 +103,24 @@ export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const paymentCredential = await prisma.credential.findFirst({
|
let paymentCredential = await CredentialRepository.findPaymentCredentialByAppIdAndUserIdOrTeamId({
|
||||||
where: {
|
appId,
|
||||||
...idToSearchObject,
|
userId,
|
||||||
appId: booking.payment[0].appId,
|
teamId,
|
||||||
},
|
|
||||||
include: {
|
|
||||||
app: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!paymentCredential && teamId) {
|
||||||
|
// See if the team event belongs to an org
|
||||||
|
const org = await teamRepository.findParentOrganizationByTeamId(teamId);
|
||||||
|
|
||||||
|
if (org) {
|
||||||
|
paymentCredential = await CredentialRepository.findPaymentCredentialByAppIdAndTeamId({
|
||||||
|
appId,
|
||||||
|
teamId: org.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!paymentCredential?.app) {
|
if (!paymentCredential?.app) {
|
||||||
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid payment credential" });
|
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid payment credential" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2500,7 +2500,7 @@ __metadata:
|
|||||||
"@axiomhq/winston": ^1.2.0
|
"@axiomhq/winston": ^1.2.0
|
||||||
"@calcom/platform-constants": "*"
|
"@calcom/platform-constants": "*"
|
||||||
"@calcom/platform-enums": "*"
|
"@calcom/platform-enums": "*"
|
||||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.300"
|
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.306"
|
||||||
"@calcom/platform-types": "*"
|
"@calcom/platform-types": "*"
|
||||||
"@calcom/platform-utils": "*"
|
"@calcom/platform-utils": "*"
|
||||||
"@calcom/prisma": "*"
|
"@calcom/prisma": "*"
|
||||||
@@ -2590,7 +2590,7 @@ __metadata:
|
|||||||
"@sentry/nextjs": ^9.15.0
|
"@sentry/nextjs": ^9.15.0
|
||||||
bcryptjs: ^2.4.3
|
bcryptjs: ^2.4.3
|
||||||
memory-cache: ^0.2.0
|
memory-cache: ^0.2.0
|
||||||
next: ^15.3.0
|
next: ^15.4.5
|
||||||
next-api-middleware: ^1.0.1
|
next-api-middleware: ^1.0.1
|
||||||
next-axiom: ^0.17.0
|
next-axiom: ^0.17.0
|
||||||
next-swagger-doc: ^0.3.6
|
next-swagger-doc: ^0.3.6
|
||||||
@@ -3559,13 +3559,13 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.300":
|
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.306":
|
||||||
version: 0.0.300
|
version: 0.0.306
|
||||||
resolution: "@calcom/platform-libraries@npm:0.0.300"
|
resolution: "@calcom/platform-libraries@npm:0.0.306"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@calcom/features": "*"
|
"@calcom/features": "*"
|
||||||
"@calcom/lib": "*"
|
"@calcom/lib": "*"
|
||||||
checksum: bff9f08a0586169f2d5a85167f0a0e0e57ca93b637ca858a72b5d3d943ceef37d652efa3d2e6f7561548e4dfe3aabb5c0c6e0a8d38f68d3d21d5e6993e56ad7d
|
checksum: 602b8b446508429096b8e03e9edbe1ff744cf7c7af3fbe8a683aaaf0d3c96d1cd30ff356f72682449ca072beadaae95fdaa5c44b26e30cfce3bb838f519216f9
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -4115,7 +4115,7 @@ __metadata:
|
|||||||
mime-types: ^2.1.35
|
mime-types: ^2.1.35
|
||||||
module-alias: ^2.2.2
|
module-alias: ^2.2.2
|
||||||
msw: ^0.42.3
|
msw: ^0.42.3
|
||||||
next: ^15.3.0
|
next: ^15.4.5
|
||||||
next-auth: ^4.22.1
|
next-auth: ^4.22.1
|
||||||
next-axiom: ^0.17.0
|
next-axiom: ^0.17.0
|
||||||
next-collect: ^0.2.1
|
next-collect: ^0.2.1
|
||||||
@@ -4711,12 +4711,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@emnapi/runtime@npm:^1.4.0":
|
"@emnapi/runtime@npm:^1.4.4":
|
||||||
version: 1.4.3
|
version: 1.4.5
|
||||||
resolution: "@emnapi/runtime@npm:1.4.3"
|
resolution: "@emnapi/runtime@npm:1.4.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: ^2.4.0
|
tslib: ^2.4.0
|
||||||
checksum: ff2074809638ed878e476ece370c6eae7e6257bf029a581bb7a290488d8f2a08c420a65988c7f03bfc6bb689218f0cd995d2f935bd182150b357fc2341142f4f
|
checksum: 99ab25d55cf1ceeec12f83b60f48e744f8e1dfc8d52a2ed81b3b09bf15182e61ef55f25b69d51ec83044861bddaa4404e7c3285bf71dd518a7980867e41c2a10
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -6724,11 +6724,11 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-darwin-arm64@npm:0.34.1":
|
"@img/sharp-darwin-arm64@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-darwin-arm64@npm:0.34.1"
|
resolution: "@img/sharp-darwin-arm64@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-darwin-arm64": 1.1.0
|
"@img/sharp-libvips-darwin-arm64": 1.2.0
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-darwin-arm64":
|
"@img/sharp-libvips-darwin-arm64":
|
||||||
optional: true
|
optional: true
|
||||||
@@ -6748,11 +6748,11 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-darwin-x64@npm:0.34.1":
|
"@img/sharp-darwin-x64@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-darwin-x64@npm:0.34.1"
|
resolution: "@img/sharp-darwin-x64@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-darwin-x64": 1.1.0
|
"@img/sharp-libvips-darwin-x64": 1.2.0
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-darwin-x64":
|
"@img/sharp-libvips-darwin-x64":
|
||||||
optional: true
|
optional: true
|
||||||
@@ -6767,9 +6767,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-arm64@npm:1.1.0":
|
"@img/sharp-libvips-darwin-arm64@npm:1.2.0":
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.1.0"
|
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.0"
|
||||||
conditions: os=darwin & cpu=arm64
|
conditions: os=darwin & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -6781,9 +6781,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-x64@npm:1.1.0":
|
"@img/sharp-libvips-darwin-x64@npm:1.2.0":
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
resolution: "@img/sharp-libvips-darwin-x64@npm:1.1.0"
|
resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.0"
|
||||||
conditions: os=darwin & cpu=x64
|
conditions: os=darwin & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -6795,9 +6795,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm64@npm:1.1.0":
|
"@img/sharp-libvips-linux-arm64@npm:1.2.0":
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
resolution: "@img/sharp-libvips-linux-arm64@npm:1.1.0"
|
resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.0"
|
||||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -6809,16 +6809,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm@npm:1.1.0":
|
"@img/sharp-libvips-linux-arm@npm:1.2.0":
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
resolution: "@img/sharp-libvips-linux-arm@npm:1.1.0"
|
resolution: "@img/sharp-libvips-linux-arm@npm:1.2.0"
|
||||||
conditions: os=linux & cpu=arm & libc=glibc
|
conditions: os=linux & cpu=arm & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-ppc64@npm:1.1.0":
|
"@img/sharp-libvips-linux-ppc64@npm:1.2.0":
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
resolution: "@img/sharp-libvips-linux-ppc64@npm:1.1.0"
|
resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.0"
|
||||||
conditions: os=linux & cpu=ppc64 & libc=glibc
|
conditions: os=linux & cpu=ppc64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -6830,9 +6830,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-s390x@npm:1.1.0":
|
"@img/sharp-libvips-linux-s390x@npm:1.2.0":
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
resolution: "@img/sharp-libvips-linux-s390x@npm:1.1.0"
|
resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.0"
|
||||||
conditions: os=linux & cpu=s390x & libc=glibc
|
conditions: os=linux & cpu=s390x & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -6844,9 +6844,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-x64@npm:1.1.0":
|
"@img/sharp-libvips-linux-x64@npm:1.2.0":
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
resolution: "@img/sharp-libvips-linux-x64@npm:1.1.0"
|
resolution: "@img/sharp-libvips-linux-x64@npm:1.2.0"
|
||||||
conditions: os=linux & cpu=x64 & libc=glibc
|
conditions: os=linux & cpu=x64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -6858,9 +6858,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0":
|
"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0":
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0"
|
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0"
|
||||||
conditions: os=linux & cpu=arm64 & libc=musl
|
conditions: os=linux & cpu=arm64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -6872,9 +6872,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-x64@npm:1.1.0":
|
"@img/sharp-libvips-linuxmusl-x64@npm:1.2.0":
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.1.0"
|
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.0"
|
||||||
conditions: os=linux & cpu=x64 & libc=musl
|
conditions: os=linux & cpu=x64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -6891,11 +6891,11 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-arm64@npm:0.34.1":
|
"@img/sharp-linux-arm64@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-linux-arm64@npm:0.34.1"
|
resolution: "@img/sharp-linux-arm64@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linux-arm64": 1.1.0
|
"@img/sharp-libvips-linux-arm64": 1.2.0
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linux-arm64":
|
"@img/sharp-libvips-linux-arm64":
|
||||||
optional: true
|
optional: true
|
||||||
@@ -6915,11 +6915,11 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-arm@npm:0.34.1":
|
"@img/sharp-linux-arm@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-linux-arm@npm:0.34.1"
|
resolution: "@img/sharp-linux-arm@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linux-arm": 1.1.0
|
"@img/sharp-libvips-linux-arm": 1.2.0
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linux-arm":
|
"@img/sharp-libvips-linux-arm":
|
||||||
optional: true
|
optional: true
|
||||||
@@ -6927,6 +6927,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-linux-ppc64@npm:0.34.3":
|
||||||
|
version: 0.34.3
|
||||||
|
resolution: "@img/sharp-linux-ppc64@npm:0.34.3"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-linux-ppc64": 1.2.0
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-linux-ppc64":
|
||||||
|
optional: true
|
||||||
|
conditions: os=linux & cpu=ppc64 & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-s390x@npm:0.33.5":
|
"@img/sharp-linux-s390x@npm:0.33.5":
|
||||||
version: 0.33.5
|
version: 0.33.5
|
||||||
resolution: "@img/sharp-linux-s390x@npm:0.33.5"
|
resolution: "@img/sharp-linux-s390x@npm:0.33.5"
|
||||||
@@ -6939,11 +6951,11 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-s390x@npm:0.34.1":
|
"@img/sharp-linux-s390x@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-linux-s390x@npm:0.34.1"
|
resolution: "@img/sharp-linux-s390x@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linux-s390x": 1.1.0
|
"@img/sharp-libvips-linux-s390x": 1.2.0
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linux-s390x":
|
"@img/sharp-libvips-linux-s390x":
|
||||||
optional: true
|
optional: true
|
||||||
@@ -6963,11 +6975,11 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-x64@npm:0.34.1":
|
"@img/sharp-linux-x64@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-linux-x64@npm:0.34.1"
|
resolution: "@img/sharp-linux-x64@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linux-x64": 1.1.0
|
"@img/sharp-libvips-linux-x64": 1.2.0
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linux-x64":
|
"@img/sharp-libvips-linux-x64":
|
||||||
optional: true
|
optional: true
|
||||||
@@ -6987,11 +6999,11 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-arm64@npm:0.34.1":
|
"@img/sharp-linuxmusl-arm64@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.1"
|
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": 1.1.0
|
"@img/sharp-libvips-linuxmusl-arm64": 1.2.0
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linuxmusl-arm64":
|
"@img/sharp-libvips-linuxmusl-arm64":
|
||||||
optional: true
|
optional: true
|
||||||
@@ -7011,11 +7023,11 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-x64@npm:0.34.1":
|
"@img/sharp-linuxmusl-x64@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.1"
|
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linuxmusl-x64": 1.1.0
|
"@img/sharp-libvips-linuxmusl-x64": 1.2.0
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linuxmusl-x64":
|
"@img/sharp-libvips-linuxmusl-x64":
|
||||||
optional: true
|
optional: true
|
||||||
@@ -7032,15 +7044,22 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-wasm32@npm:0.34.1":
|
"@img/sharp-wasm32@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-wasm32@npm:0.34.1"
|
resolution: "@img/sharp-wasm32@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@emnapi/runtime": ^1.4.0
|
"@emnapi/runtime": ^1.4.4
|
||||||
conditions: cpu=wasm32
|
conditions: cpu=wasm32
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-win32-arm64@npm:0.34.3":
|
||||||
|
version: 0.34.3
|
||||||
|
resolution: "@img/sharp-win32-arm64@npm:0.34.3"
|
||||||
|
conditions: os=win32 & cpu=arm64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-win32-ia32@npm:0.33.5":
|
"@img/sharp-win32-ia32@npm:0.33.5":
|
||||||
version: 0.33.5
|
version: 0.33.5
|
||||||
resolution: "@img/sharp-win32-ia32@npm:0.33.5"
|
resolution: "@img/sharp-win32-ia32@npm:0.33.5"
|
||||||
@@ -7048,9 +7067,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-win32-ia32@npm:0.34.1":
|
"@img/sharp-win32-ia32@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-win32-ia32@npm:0.34.1"
|
resolution: "@img/sharp-win32-ia32@npm:0.34.3"
|
||||||
conditions: os=win32 & cpu=ia32
|
conditions: os=win32 & cpu=ia32
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -7062,9 +7081,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-win32-x64@npm:0.34.1":
|
"@img/sharp-win32-x64@npm:0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "@img/sharp-win32-x64@npm:0.34.1"
|
resolution: "@img/sharp-win32-x64@npm:0.34.3"
|
||||||
conditions: os=win32 & cpu=x64
|
conditions: os=win32 & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -8940,10 +8959,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/env@npm:15.3.3":
|
"@next/env@npm:15.4.6":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "@next/env@npm:15.3.3"
|
resolution: "@next/env@npm:15.4.6"
|
||||||
checksum: 52513b04ff1951ad094b37a89dcd37e9275c2d6c635dbe7fbdc44bd39b5511eb2c7c537a80ebbf92d197e5f1293ff16c175cd5dccc9c54bbfaac7271c1fe1bb9
|
checksum: bf1ea15f2a86c6bc968d92d4fb778d106e6e94c31893f51699f0fc51fd17416ae9e4086176164e82430c84597d509b77c2c5738e8cd98bc7f73ebc6d83b2926f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -8995,9 +9014,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-darwin-arm64@npm:15.3.3":
|
"@next/swc-darwin-arm64@npm:15.4.6":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "@next/swc-darwin-arm64@npm:15.3.3"
|
resolution: "@next/swc-darwin-arm64@npm:15.4.6"
|
||||||
conditions: os=darwin & cpu=arm64
|
conditions: os=darwin & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -9023,9 +9042,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-darwin-x64@npm:15.3.3":
|
"@next/swc-darwin-x64@npm:15.4.6":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "@next/swc-darwin-x64@npm:15.3.3"
|
resolution: "@next/swc-darwin-x64@npm:15.4.6"
|
||||||
conditions: os=darwin & cpu=x64
|
conditions: os=darwin & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -9051,9 +9070,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu@npm:15.3.3":
|
"@next/swc-linux-arm64-gnu@npm:15.4.6":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "@next/swc-linux-arm64-gnu@npm:15.3.3"
|
resolution: "@next/swc-linux-arm64-gnu@npm:15.4.6"
|
||||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -9079,9 +9098,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl@npm:15.3.3":
|
"@next/swc-linux-arm64-musl@npm:15.4.6":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "@next/swc-linux-arm64-musl@npm:15.3.3"
|
resolution: "@next/swc-linux-arm64-musl@npm:15.4.6"
|
||||||
conditions: os=linux & cpu=arm64 & libc=musl
|
conditions: os=linux & cpu=arm64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -9107,9 +9126,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu@npm:15.3.3":
|
"@next/swc-linux-x64-gnu@npm:15.4.6":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "@next/swc-linux-x64-gnu@npm:15.3.3"
|
resolution: "@next/swc-linux-x64-gnu@npm:15.4.6"
|
||||||
conditions: os=linux & cpu=x64 & libc=glibc
|
conditions: os=linux & cpu=x64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -9135,9 +9154,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl@npm:15.3.3":
|
"@next/swc-linux-x64-musl@npm:15.4.6":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "@next/swc-linux-x64-musl@npm:15.3.3"
|
resolution: "@next/swc-linux-x64-musl@npm:15.4.6"
|
||||||
conditions: os=linux & cpu=x64 & libc=musl
|
conditions: os=linux & cpu=x64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -9163,9 +9182,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc@npm:15.3.3":
|
"@next/swc-win32-arm64-msvc@npm:15.4.6":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "@next/swc-win32-arm64-msvc@npm:15.3.3"
|
resolution: "@next/swc-win32-arm64-msvc@npm:15.4.6"
|
||||||
conditions: os=win32 & cpu=arm64
|
conditions: os=win32 & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -9205,9 +9224,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc@npm:15.3.3":
|
"@next/swc-win32-x64-msvc@npm:15.4.6":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "@next/swc-win32-x64-msvc@npm:15.3.3"
|
resolution: "@next/swc-win32-x64-msvc@npm:15.4.6"
|
||||||
conditions: os=win32 & cpu=x64
|
conditions: os=win32 & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
@@ -24262,6 +24281,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"detect-libc@npm:^2.0.4":
|
||||||
|
version: 2.0.4
|
||||||
|
resolution: "detect-libc@npm:2.0.4"
|
||||||
|
checksum: 3d186b7d4e16965e10e21db596c78a4e131f9eee69c0081d13b85e6a61d7448d3ba23fe7997648022bdfa3b0eb4cc3c289a44c8188df949445a20852689abef6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"detect-newline@npm:^3.0.0":
|
"detect-newline@npm:^3.0.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "detect-newline@npm:3.1.0"
|
resolution: "detect-newline@npm:3.1.0"
|
||||||
@@ -35825,29 +35851,27 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"next@npm:^15.3.0":
|
"next@npm:^15.4.5":
|
||||||
version: 15.3.3
|
version: 15.4.6
|
||||||
resolution: "next@npm:15.3.3"
|
resolution: "next@npm:15.4.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@next/env": 15.3.3
|
"@next/env": 15.4.6
|
||||||
"@next/swc-darwin-arm64": 15.3.3
|
"@next/swc-darwin-arm64": 15.4.6
|
||||||
"@next/swc-darwin-x64": 15.3.3
|
"@next/swc-darwin-x64": 15.4.6
|
||||||
"@next/swc-linux-arm64-gnu": 15.3.3
|
"@next/swc-linux-arm64-gnu": 15.4.6
|
||||||
"@next/swc-linux-arm64-musl": 15.3.3
|
"@next/swc-linux-arm64-musl": 15.4.6
|
||||||
"@next/swc-linux-x64-gnu": 15.3.3
|
"@next/swc-linux-x64-gnu": 15.4.6
|
||||||
"@next/swc-linux-x64-musl": 15.3.3
|
"@next/swc-linux-x64-musl": 15.4.6
|
||||||
"@next/swc-win32-arm64-msvc": 15.3.3
|
"@next/swc-win32-arm64-msvc": 15.4.6
|
||||||
"@next/swc-win32-x64-msvc": 15.3.3
|
"@next/swc-win32-x64-msvc": 15.4.6
|
||||||
"@swc/counter": 0.1.3
|
|
||||||
"@swc/helpers": 0.5.15
|
"@swc/helpers": 0.5.15
|
||||||
busboy: 1.6.0
|
|
||||||
caniuse-lite: ^1.0.30001579
|
caniuse-lite: ^1.0.30001579
|
||||||
postcss: 8.4.31
|
postcss: 8.4.31
|
||||||
sharp: ^0.34.1
|
sharp: ^0.34.3
|
||||||
styled-jsx: 5.1.6
|
styled-jsx: 5.1.6
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@opentelemetry/api": ^1.1.0
|
"@opentelemetry/api": ^1.1.0
|
||||||
"@playwright/test": ^1.41.2
|
"@playwright/test": ^1.51.1
|
||||||
babel-plugin-react-compiler: "*"
|
babel-plugin-react-compiler: "*"
|
||||||
react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
|
react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
|
||||||
react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
|
react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
|
||||||
@@ -35882,7 +35906,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
next: dist/bin/next
|
next: dist/bin/next
|
||||||
checksum: 0e071a67e50b2770d27268b19dbe5a510297cf31fe3035ae169caa7e93f97bcf58c3f533e22fe300c8ad92b87e4f7d807619f7aa3b97a290de7c2b82b1451c4c
|
checksum: 996e28076e30ae1e077219e46d2555f73391ca8132016c83b6096283f638bd6c7be58e2f320d0ef45ac0c18067138d603f900df50517d20ca804f102d2150d8a
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -41705,7 +41729,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"semver@npm:^7.6.0, semver@npm:^7.6.3, semver@npm:^7.7.1":
|
"semver@npm:^7.6.0, semver@npm:^7.6.3":
|
||||||
version: 7.7.1
|
version: 7.7.1
|
||||||
resolution: "semver@npm:7.7.1"
|
resolution: "semver@npm:7.7.1"
|
||||||
bin:
|
bin:
|
||||||
@@ -41714,6 +41738,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"semver@npm:^7.7.2":
|
||||||
|
version: 7.7.2
|
||||||
|
resolution: "semver@npm:7.7.2"
|
||||||
|
bin:
|
||||||
|
semver: bin/semver.js
|
||||||
|
checksum: dd94ba8f1cbc903d8eeb4dd8bf19f46b3deb14262b6717d0de3c804b594058ae785ef2e4b46c5c3b58733c99c83339068203002f9e37cfe44f7e2cc5e3d2f621
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"semver@npm:~2.3.1":
|
"semver@npm:~2.3.1":
|
||||||
version: 2.3.2
|
version: 2.3.2
|
||||||
resolution: "semver@npm:2.3.2"
|
resolution: "semver@npm:2.3.2"
|
||||||
@@ -41989,33 +42022,35 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"sharp@npm:^0.34.1":
|
"sharp@npm:^0.34.3":
|
||||||
version: 0.34.1
|
version: 0.34.3
|
||||||
resolution: "sharp@npm:0.34.1"
|
resolution: "sharp@npm:0.34.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-darwin-arm64": 0.34.1
|
"@img/sharp-darwin-arm64": 0.34.3
|
||||||
"@img/sharp-darwin-x64": 0.34.1
|
"@img/sharp-darwin-x64": 0.34.3
|
||||||
"@img/sharp-libvips-darwin-arm64": 1.1.0
|
"@img/sharp-libvips-darwin-arm64": 1.2.0
|
||||||
"@img/sharp-libvips-darwin-x64": 1.1.0
|
"@img/sharp-libvips-darwin-x64": 1.2.0
|
||||||
"@img/sharp-libvips-linux-arm": 1.1.0
|
"@img/sharp-libvips-linux-arm": 1.2.0
|
||||||
"@img/sharp-libvips-linux-arm64": 1.1.0
|
"@img/sharp-libvips-linux-arm64": 1.2.0
|
||||||
"@img/sharp-libvips-linux-ppc64": 1.1.0
|
"@img/sharp-libvips-linux-ppc64": 1.2.0
|
||||||
"@img/sharp-libvips-linux-s390x": 1.1.0
|
"@img/sharp-libvips-linux-s390x": 1.2.0
|
||||||
"@img/sharp-libvips-linux-x64": 1.1.0
|
"@img/sharp-libvips-linux-x64": 1.2.0
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": 1.1.0
|
"@img/sharp-libvips-linuxmusl-arm64": 1.2.0
|
||||||
"@img/sharp-libvips-linuxmusl-x64": 1.1.0
|
"@img/sharp-libvips-linuxmusl-x64": 1.2.0
|
||||||
"@img/sharp-linux-arm": 0.34.1
|
"@img/sharp-linux-arm": 0.34.3
|
||||||
"@img/sharp-linux-arm64": 0.34.1
|
"@img/sharp-linux-arm64": 0.34.3
|
||||||
"@img/sharp-linux-s390x": 0.34.1
|
"@img/sharp-linux-ppc64": 0.34.3
|
||||||
"@img/sharp-linux-x64": 0.34.1
|
"@img/sharp-linux-s390x": 0.34.3
|
||||||
"@img/sharp-linuxmusl-arm64": 0.34.1
|
"@img/sharp-linux-x64": 0.34.3
|
||||||
"@img/sharp-linuxmusl-x64": 0.34.1
|
"@img/sharp-linuxmusl-arm64": 0.34.3
|
||||||
"@img/sharp-wasm32": 0.34.1
|
"@img/sharp-linuxmusl-x64": 0.34.3
|
||||||
"@img/sharp-win32-ia32": 0.34.1
|
"@img/sharp-wasm32": 0.34.3
|
||||||
"@img/sharp-win32-x64": 0.34.1
|
"@img/sharp-win32-arm64": 0.34.3
|
||||||
|
"@img/sharp-win32-ia32": 0.34.3
|
||||||
|
"@img/sharp-win32-x64": 0.34.3
|
||||||
color: ^4.2.3
|
color: ^4.2.3
|
||||||
detect-libc: ^2.0.3
|
detect-libc: ^2.0.4
|
||||||
semver: ^7.7.1
|
semver: ^7.7.2
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-darwin-arm64":
|
"@img/sharp-darwin-arm64":
|
||||||
optional: true
|
optional: true
|
||||||
@@ -42043,6 +42078,8 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-linux-arm64":
|
"@img/sharp-linux-arm64":
|
||||||
optional: true
|
optional: true
|
||||||
|
"@img/sharp-linux-ppc64":
|
||||||
|
optional: true
|
||||||
"@img/sharp-linux-s390x":
|
"@img/sharp-linux-s390x":
|
||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-linux-x64":
|
"@img/sharp-linux-x64":
|
||||||
@@ -42053,11 +42090,13 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-wasm32":
|
"@img/sharp-wasm32":
|
||||||
optional: true
|
optional: true
|
||||||
|
"@img/sharp-win32-arm64":
|
||||||
|
optional: true
|
||||||
"@img/sharp-win32-ia32":
|
"@img/sharp-win32-ia32":
|
||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-win32-x64":
|
"@img/sharp-win32-x64":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: ce2798a2d1a4e4fb9bae5642177e4681050897de8c2d6e1783c8c15d8a40cc0e22bf4ce91d1bf355afd133d16d7032fcfdfa2ed3305eab34b7f8de8f61175519
|
checksum: f1d70751ab15080957f1a5db5583d00b002e644878e3e9a6d00bdbb31255f6e9df218ef82d96d3588b83023681f35bcf03e265a44e214b11306ef3fb439ac04d
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user