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:
Somay Chauhan
2024-10-17 17:33:59 +00:00
committed by GitHub
parent 497b0804d3
commit 9a5efd39ce
11 changed files with 139 additions and 22 deletions
@@ -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;
@@ -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();
}}