feat: event team Assignment tab atom (#17145)
* feat: event team Assignment tab atom * Update event-types-atom.service.ts * Update event-types-atom.service.ts
This commit is contained in:
@@ -3,13 +3,14 @@ import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06
|
||||
import { AtomsController } from "@/modules/atoms/controllers/atoms.controller";
|
||||
import { EventTypesAtomService } from "@/modules/atoms/services/event-types-atom.service";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { OrganizationsModule } from "@/modules/organizations/organizations.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, EventTypesModule_2024_06_14],
|
||||
imports: [PrismaModule, EventTypesModule_2024_06_14, OrganizationsModule],
|
||||
providers: [
|
||||
EventTypesAtomService,
|
||||
MembershipsRepository,
|
||||
|
||||
@@ -64,4 +64,20 @@ export class AtomsController {
|
||||
data: eventType,
|
||||
};
|
||||
}
|
||||
|
||||
@Patch("/organizations/:organizationId/teams/:teamId/event-types/:eventTypeId")
|
||||
@Version(VERSION_NEUTRAL)
|
||||
@UseGuards(ApiAuthGuard)
|
||||
async updateAtomTeamEventType(
|
||||
@GetUser() user: UserWithProfile,
|
||||
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||
@Param("teamId", ParseIntPipe) teamId: number,
|
||||
@Body() body: UpdateEventTypeReturn
|
||||
): Promise<ApiResponse<UpdateEventTypeReturn>> {
|
||||
const eventType = await this.eventTypesService.updateTeamEventType(eventTypeId, body, user, teamId);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { OrganizationsEventTypesService } from "@/modules/organizations/services/event-types/organizations-event-types.service";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Injectable, NotFoundException, ForbiddenException } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
updateEventType,
|
||||
@@ -21,11 +22,11 @@ export class EventTypesAtomService {
|
||||
private readonly usersService: UsersService,
|
||||
private readonly dbWrite: PrismaWriteService,
|
||||
private readonly dbRead: PrismaReadService,
|
||||
private readonly eventTypeService: EventTypesService_2024_06_14
|
||||
private readonly eventTypeService: EventTypesService_2024_06_14,
|
||||
private readonly teamEventTypeService: OrganizationsEventTypesService
|
||||
) {}
|
||||
|
||||
async getUserEventType(user: UserWithProfile, eventTypeId: number) {
|
||||
this.eventTypeService.checkUserOwnsEventType(user.id, { id: eventTypeId, userId: user.id });
|
||||
const organizationId = this.usersService.getUserMainOrgId(user);
|
||||
|
||||
const isUserOrganizationAdmin = organizationId
|
||||
@@ -45,12 +46,22 @@ export class EventTypesAtomService {
|
||||
throw new NotFoundException(`Event type with id ${eventTypeId} not found`);
|
||||
}
|
||||
|
||||
this.eventTypeService.checkUserOwnsEventType(user.id, eventType.eventType);
|
||||
if (eventType?.team?.id) {
|
||||
await this.checkTeamOwnsEventType(user.id, eventType.eventType.id, eventType.team.id);
|
||||
} else {
|
||||
this.eventTypeService.checkUserOwnsEventType(user.id, eventType.eventType);
|
||||
}
|
||||
|
||||
return eventType;
|
||||
}
|
||||
|
||||
async updateEventType(eventTypeId: number, body: TUpdateEventTypeInputSchema, user: UserWithProfile) {
|
||||
this.eventTypeService.checkCanUpdateEventType(user.id, eventTypeId, body.scheduleId);
|
||||
async updateTeamEventType(
|
||||
eventTypeId: number,
|
||||
body: TUpdateEventTypeInputSchema,
|
||||
user: UserWithProfile,
|
||||
teamId: number
|
||||
) {
|
||||
await this.checkCanUpdateTeamEventType(user.id, eventTypeId, teamId, body.scheduleId);
|
||||
const eventTypeUser = await this.eventTypeService.getUserToUpdateEvent(user);
|
||||
const bookingFields = [...(body.bookingFields || [])];
|
||||
|
||||
@@ -77,4 +88,62 @@ export class EventTypesAtomService {
|
||||
|
||||
return eventType.eventType;
|
||||
}
|
||||
|
||||
async updateEventType(eventTypeId: number, body: TUpdateEventTypeInputSchema, user: UserWithProfile) {
|
||||
await this.eventTypeService.checkCanUpdateEventType(user.id, eventTypeId, body.scheduleId);
|
||||
const eventTypeUser = await this.eventTypeService.getUserToUpdateEvent(user);
|
||||
const bookingFields = [...(body.bookingFields || [])];
|
||||
|
||||
if (
|
||||
!bookingFields.find((field) => field.type === "email") &&
|
||||
!bookingFields.find((field) => field.type === "phone")
|
||||
) {
|
||||
bookingFields.push(systemBeforeFieldEmail);
|
||||
}
|
||||
|
||||
const eventType = await updateEventType({
|
||||
input: { id: eventTypeId, ...body, bookingFields },
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
prisma: this.dbWrite.prisma,
|
||||
},
|
||||
});
|
||||
|
||||
if (!eventType) {
|
||||
throw new NotFoundException(`Event type with id ${eventTypeId} not found`);
|
||||
}
|
||||
|
||||
return eventType.eventType;
|
||||
}
|
||||
|
||||
async checkCanUpdateTeamEventType(userId: number, eventTypeId: number, teamId: number, scheduleId: number) {
|
||||
await this.checkTeamOwnsEventType(userId, eventTypeId, teamId);
|
||||
await this.teamEventTypeService.validateEventTypeExists(teamId, eventTypeId);
|
||||
await this.eventTypeService.checkUserOwnsSchedule(userId, scheduleId);
|
||||
}
|
||||
|
||||
async checkTeamOwnsEventType(userId: number, eventTypeId: number, teamId: number) {
|
||||
const membership = await this.dbRead.prisma.membership.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
teamId,
|
||||
accepted: true,
|
||||
OR: [{ role: "ADMIN" }, { role: "OWNER" }],
|
||||
},
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
eventTypes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!membership?.team?.eventTypes?.some((item) => item.id === eventTypeId)) {
|
||||
throw new ForbiddenException(
|
||||
`Access denied. Either the team with ID=${teamId} does not own the event type with ID=${eventTypeId}, or your MEMBER role does not have permission to access this resource.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ import { Module } from "@nestjs/common";
|
||||
OrganizationsWebhooksService,
|
||||
WebhooksRepository,
|
||||
WebhooksService,
|
||||
OrganizationsEventTypesService,
|
||||
],
|
||||
controllers: [
|
||||
OrganizationsTeamsController,
|
||||
|
||||
@@ -21,6 +21,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { Label, Select, SettingsToggle } from "@calcom/ui";
|
||||
|
||||
export type EventTeamAssignmentTabBaseProps = Pick<EventTypeSetupProps, "teamMembers" | "team" | "eventType">;
|
||||
|
||||
export const mapMemberToChildrenOption = (
|
||||
member: EventTypeSetupProps["teamMembers"][number],
|
||||
slug: string,
|
||||
@@ -427,11 +429,7 @@ const Hosts = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const EventTeamAssignmentTab = ({
|
||||
team,
|
||||
teamMembers,
|
||||
eventType,
|
||||
}: Pick<EventTypeSetupProps, "teamMembers" | "team" | "eventType">) => {
|
||||
export const EventTeamAssignmentTab = ({ team, teamMembers, eventType }: EventTeamAssignmentTabBaseProps) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
const schedulingTypeOptions: {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
|
||||
import type { EventType } from "@calcom/prisma/client";
|
||||
|
||||
import { useAtomsContext } from "../../hooks/useAtomsContext";
|
||||
import http from "../../lib/http";
|
||||
|
||||
export const QUERY_KEY = "use-event-by-id";
|
||||
@@ -12,6 +13,7 @@ export type UseAtomUpdateEventTypeProps = {
|
||||
onSuccess?: () => void;
|
||||
onError?: (err: Error) => void;
|
||||
onSettled?: () => void;
|
||||
teamId?: number;
|
||||
};
|
||||
export const useAtomUpdateEventType = ({
|
||||
onSuccess = () => {
|
||||
@@ -23,14 +25,20 @@ export const useAtomUpdateEventType = ({
|
||||
onSettled = () => {
|
||||
return;
|
||||
},
|
||||
teamId,
|
||||
}: UseAtomUpdateEventTypeProps) => {
|
||||
const { organizationId } = useAtomsContext();
|
||||
return useMutation({
|
||||
onSuccess,
|
||||
onError,
|
||||
onSettled,
|
||||
mutationFn: (data: EventTypeUpdateInput) => {
|
||||
if (!data.id) throw new Error("Event type id is required");
|
||||
const pathname = `/atoms/${V2_ENDPOINTS.eventTypes}/${data.id}`;
|
||||
let pathname = `/atoms/${V2_ENDPOINTS.eventTypes}/${data.id}`;
|
||||
|
||||
if (teamId) {
|
||||
pathname = `/atoms/organizations/${organizationId}/teams/${teamId}/${V2_ENDPOINTS.eventTypes}/${data.id}`;
|
||||
}
|
||||
return http?.patch<ApiResponse<EventType>>(pathname, data).then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return (res.data as ApiSuccessResponse<EventType>).data;
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import {
|
||||
EventTeamAssignmentTab,
|
||||
type EventTeamAssignmentTabBaseProps,
|
||||
} from "@calcom/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab";
|
||||
|
||||
const EventTeamAssignmentTabPlatformWrapper = (props: EventTeamAssignmentTabBaseProps) => {
|
||||
return <EventTeamAssignmentTab {...props} />;
|
||||
};
|
||||
|
||||
export default EventTeamAssignmentTabPlatformWrapper;
|
||||
@@ -0,0 +1,10 @@
|
||||
import {
|
||||
EventTeamAssignmentTab,
|
||||
type EventTeamAssignmentTabBaseProps,
|
||||
} from "@calcom/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab";
|
||||
|
||||
const EventTeamAssignmentTabWebWrapper = (props: EventTeamAssignmentTabBaseProps) => {
|
||||
return <EventTeamAssignmentTab {...props} />;
|
||||
};
|
||||
|
||||
export default EventTeamAssignmentTabWebWrapper;
|
||||
@@ -24,6 +24,7 @@ import EventAvailabilityTabPlatformWrapper from "./EventAvailabilityTabPlatformW
|
||||
import EventLimitsTabPlatformWrapper from "./EventLimitsTabPlatformWrapper";
|
||||
import EventRecurringTabPlatformWrapper from "./EventRecurringTabPlatformWrapper";
|
||||
import SetupTab from "./EventSetupTabPlatformWrapper";
|
||||
import EventTeamAssignmentTabPlatformWrapper from "./EventTeamAssignmentTabPlatformWrapper";
|
||||
|
||||
export type PlatformTabs = keyof Omit<TabMap, "workflows" | "webhooks" | "instant" | "ai" | "apps">;
|
||||
|
||||
@@ -99,9 +100,13 @@ const EventType = ({
|
||||
toast({ description: message ? t(message) : t(err.message) });
|
||||
onError?.(currentValues, err);
|
||||
},
|
||||
teamId: team?.id,
|
||||
});
|
||||
|
||||
const { form, handleSubmit } = useEventTypeForm({ eventType, onSubmit: updateMutation.mutate });
|
||||
const { form, handleSubmit } = useEventTypeForm({
|
||||
eventType,
|
||||
onSubmit: (data) => updateMutation.mutate(data),
|
||||
});
|
||||
const slug = form.watch("slug") ?? eventType.slug;
|
||||
|
||||
const showToast = (message: string, variant: "success" | "warning" | "error") => {
|
||||
@@ -125,7 +130,11 @@ const EventType = ({
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
team: <></>,
|
||||
team: tabs.includes("team") ? (
|
||||
<EventTeamAssignmentTabPlatformWrapper team={team} eventType={eventType} teamMembers={teamMembers} />
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
advanced: tabs.includes("advanced") ? (
|
||||
<EventAdvancedPlatformWrapper
|
||||
eventType={eventType}
|
||||
|
||||
@@ -45,11 +45,7 @@ const EventAvailabilityTab = dynamic(() =>
|
||||
import("./EventAvailabilityTabWebWrapper").then((mod) => mod)
|
||||
);
|
||||
|
||||
const EventTeamAssignmentTab = dynamic(() =>
|
||||
import("@calcom/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab").then(
|
||||
(mod) => mod.EventTeamAssignmentTab
|
||||
)
|
||||
);
|
||||
const EventTeamAssignmentTab = dynamic(() => import("./EventTeamAssignmentTabWebWrapper").then((mod) => mod));
|
||||
|
||||
const EventLimitsTab = dynamic(() =>
|
||||
// import web wrapper when it's ready
|
||||
|
||||
@@ -98,9 +98,8 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
customClassNames={{ atomsWrapper: "!w-[50vw] !m-auto" }}
|
||||
allowDelete={true}
|
||||
id={eventTypeId}
|
||||
tabs={["setup", "limits", "recurring", "advanced", "availability"]}
|
||||
tabs={["setup", "limits", "recurring", "advanced", "availability", "team"]}
|
||||
onSuccess={(eventType) => {
|
||||
console.log(eventType);
|
||||
setEventTypeId(null);
|
||||
refetch();
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user