feat: added ability to assign event to all members (#13219)
* feat: added ability to assign event to all members * fix: types * chore: incorporate assignment for collective and round robin event sas well * fix: update event types query * feat: round robin hosts * fix: update * fix: update * fix: assignAllTeamMembers issue * design fixes --------- Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
co-authored by
Peer Richelsen
CarinaWolli
parent
7c7ca79d48
commit
f9359aaa7b
@@ -62,6 +62,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
|
||||
bookingLimits: true,
|
||||
onlyShowFirstAvailableSlot: true,
|
||||
durationLimits: true,
|
||||
assignAllTeamMembers: true,
|
||||
})
|
||||
.merge(
|
||||
z.object({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans } from "next-i18next";
|
||||
import Link from "next/link";
|
||||
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||
import { useEffect, useRef } from "react";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { ComponentProps, Dispatch, SetStateAction } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import type { Options } from "react-select";
|
||||
|
||||
@@ -11,7 +11,7 @@ import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTea
|
||||
import ChildrenEventTypeSelect from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { Label, Select } from "@calcom/ui";
|
||||
import { Label, Select, SettingsToggle } from "@calcom/ui";
|
||||
|
||||
interface IUserToValue {
|
||||
id: number | null;
|
||||
@@ -97,6 +97,44 @@ const ChildrenEventTypesList = ({
|
||||
);
|
||||
};
|
||||
|
||||
const AssignAllTeamMembers = ({
|
||||
assignAllTeamMembers,
|
||||
setAssignAllTeamMembers,
|
||||
onActive,
|
||||
onInactive,
|
||||
}: {
|
||||
assignAllTeamMembers: boolean;
|
||||
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||
onActive: () => void;
|
||||
onInactive?: () => void;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
|
||||
return (
|
||||
<Controller<FormValues>
|
||||
name="assignAllTeamMembers"
|
||||
render={() => (
|
||||
<SettingsToggle
|
||||
title={t("automatically_add_all_team_members")}
|
||||
description={t("including_people_who_join_in_future")}
|
||||
checked={assignAllTeamMembers}
|
||||
onCheckedChange={(active) => {
|
||||
formMethods.setValue("assignAllTeamMembers", active);
|
||||
setAssignAllTeamMembers(active);
|
||||
if (active) {
|
||||
onActive();
|
||||
} else if (!!onInactive) {
|
||||
onInactive();
|
||||
}
|
||||
}}
|
||||
toggleSwitchAtTheEnd
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CheckedHostField = ({
|
||||
labelText,
|
||||
placeholder,
|
||||
@@ -107,7 +145,7 @@ const CheckedHostField = ({
|
||||
helperText,
|
||||
...rest
|
||||
}: {
|
||||
labelText: string;
|
||||
labelText?: string;
|
||||
placeholder: string;
|
||||
isFixed: boolean;
|
||||
value: { isFixed: boolean; userId: number }[];
|
||||
@@ -118,7 +156,7 @@ const CheckedHostField = ({
|
||||
return (
|
||||
<div className="bg-muted flex flex-col space-y-5 p-4">
|
||||
<div>
|
||||
<Label>{labelText}</Label>
|
||||
{labelText ? <Label>{labelText}</Label> : <></>}
|
||||
<CheckedTeamSelect
|
||||
isOptionDisabled={(option) => !!value.find((host) => host.userId.toString() === option.value)}
|
||||
onChange={(options) => {
|
||||
@@ -165,6 +203,8 @@ const RoundRobinHosts = ({
|
||||
teamMembers,
|
||||
value,
|
||||
onChange,
|
||||
assignAllTeamMembers,
|
||||
setAssignAllTeamMembers,
|
||||
}: {
|
||||
value: { isFixed: boolean; userId: number }[];
|
||||
onChange: (hosts: { isFixed: boolean; userId: number }[]) => void;
|
||||
@@ -174,51 +214,102 @@ const RoundRobinHosts = ({
|
||||
avatar: string;
|
||||
email: string;
|
||||
}[];
|
||||
assignAllTeamMembers: boolean;
|
||||
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<CheckedHostField
|
||||
options={teamMembers.sort(sortByLabel)}
|
||||
isFixed={true}
|
||||
onChange={(changeValue) => {
|
||||
onChange([...value.filter(({ isFixed }) => !isFixed), ...changeValue]);
|
||||
}}
|
||||
value={value}
|
||||
placeholder={t("add_fixed_hosts")}
|
||||
labelText={t("fixed_hosts")}
|
||||
helperText={FixedHostHelper}
|
||||
/>
|
||||
<CheckedHostField
|
||||
options={teamMembers.sort(sortByLabel)}
|
||||
onChange={(changeValue) => onChange([...value.filter(({ isFixed }) => isFixed), ...changeValue])}
|
||||
value={value}
|
||||
isFixed={false}
|
||||
placeholder={t("add_attendees")}
|
||||
labelText={t("round_robin_hosts")}
|
||||
helperText={t("round_robin_helper")}
|
||||
/>
|
||||
</>
|
||||
<div className="border-subtle mt-6 space-y-5 rounded-lg border px-4 py-6 sm:px-6">
|
||||
<div className="flex flex-col gap-8">
|
||||
<CheckedHostField
|
||||
options={teamMembers.sort(sortByLabel)}
|
||||
isFixed={true}
|
||||
onChange={(changeValue) => {
|
||||
onChange([...value.filter(({ isFixed }) => !isFixed), ...changeValue]);
|
||||
}}
|
||||
value={value}
|
||||
placeholder={t("add_fixed_hosts")}
|
||||
labelText={t("fixed_hosts")}
|
||||
helperText={FixedHostHelper}
|
||||
/>
|
||||
|
||||
<div className="bg-muted flex flex-col">
|
||||
<Label className="p-4">{t("round_robin_hosts")}</Label>
|
||||
<div className="px-4 pb-2">
|
||||
<AssignAllTeamMembers
|
||||
assignAllTeamMembers={assignAllTeamMembers}
|
||||
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||
onActive={() =>
|
||||
formMethods.setValue(
|
||||
"hosts",
|
||||
teamMembers.map((teamMember) => ({
|
||||
isFixed: false,
|
||||
userId: parseInt(teamMember.value, 10),
|
||||
}))
|
||||
)
|
||||
}
|
||||
onInactive={() => formMethods.setValue("hosts", [])}
|
||||
/>
|
||||
</div>
|
||||
{assignAllTeamMembers ? (
|
||||
<div className="mb-4" />
|
||||
) : (
|
||||
<CheckedHostField
|
||||
options={teamMembers.sort(sortByLabel)}
|
||||
onChange={(changeValue) =>
|
||||
onChange([...value.filter(({ isFixed }) => isFixed), ...changeValue])
|
||||
}
|
||||
value={value}
|
||||
isFixed={false}
|
||||
placeholder={t("add_attendees")}
|
||||
helperText={t("round_robin_helper")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ChildrenEventTypes = ({
|
||||
childrenEventTypeOptions,
|
||||
assignAllTeamMembers,
|
||||
setAssignAllTeamMembers,
|
||||
}: {
|
||||
childrenEventTypeOptions: ReturnType<typeof mapMemberToChildrenOption>[];
|
||||
assignAllTeamMembers: boolean;
|
||||
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
return (
|
||||
<Controller<FormValues>
|
||||
name="children"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ChildrenEventTypesList value={value} options={childrenEventTypeOptions} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
<div className="border-subtle mt-6 space-y-5 rounded-lg border px-4 py-6 sm:px-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<AssignAllTeamMembers
|
||||
assignAllTeamMembers={assignAllTeamMembers}
|
||||
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||
onActive={() => formMethods.setValue("children", childrenEventTypeOptions)}
|
||||
/>
|
||||
{!assignAllTeamMembers ? (
|
||||
<Controller<FormValues>
|
||||
name="children"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ChildrenEventTypesList value={value} options={childrenEventTypeOptions} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Hosts = ({
|
||||
teamMembers,
|
||||
assignAllTeamMembers,
|
||||
setAssignAllTeamMembers,
|
||||
}: {
|
||||
teamMembers: {
|
||||
value: string;
|
||||
@@ -226,6 +317,8 @@ const Hosts = ({
|
||||
avatar: string;
|
||||
email: string;
|
||||
}[];
|
||||
assignAllTeamMembers: boolean;
|
||||
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const {
|
||||
@@ -255,24 +348,53 @@ const Hosts = ({
|
||||
});
|
||||
}, [schedulingType, resetField, getValues, submitCount]);
|
||||
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
return (
|
||||
<Controller<FormValues>
|
||||
name="hosts"
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const schedulingTypeRender = {
|
||||
COLLECTIVE: (
|
||||
<CheckedHostField
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isFixed={true}
|
||||
options={teamMembers.sort(sortByLabel)}
|
||||
placeholder={t("add_attendees")}
|
||||
labelText={t("team")}
|
||||
/>
|
||||
<div className="border-subtle mt-6 space-y-5 rounded-lg border px-4 py-6 sm:px-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<AssignAllTeamMembers
|
||||
assignAllTeamMembers={assignAllTeamMembers}
|
||||
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||
onActive={() =>
|
||||
formMethods.setValue(
|
||||
"hosts",
|
||||
teamMembers.map((teamMember) => ({
|
||||
isFixed: true,
|
||||
userId: parseInt(teamMember.value, 10),
|
||||
}))
|
||||
)
|
||||
}
|
||||
onInactive={() => formMethods.setValue("hosts", [])}
|
||||
/>
|
||||
{assignAllTeamMembers ? (
|
||||
<></>
|
||||
) : (
|
||||
<CheckedHostField
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isFixed={true}
|
||||
options={teamMembers.sort(sortByLabel)}
|
||||
placeholder={t("add_attendees")}
|
||||
labelText={t("team")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
ROUND_ROBIN: (
|
||||
<>
|
||||
<RoundRobinHosts teamMembers={teamMembers} onChange={onChange} value={value} />
|
||||
<RoundRobinHosts
|
||||
assignAllTeamMembers={assignAllTeamMembers}
|
||||
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||
teamMembers={teamMembers}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
/>
|
||||
{/*<TextField
|
||||
required
|
||||
type="number"
|
||||
@@ -320,9 +442,18 @@ export const EventTeamTab = ({
|
||||
.filter(pendingMembers)
|
||||
.map((member) => mapUserToValue(member, t("pending")));
|
||||
const childrenEventTypeOptions = teamMembers.filter(pendingMembers).map((member) => {
|
||||
return mapMemberToChildrenOption(member, eventType.slug, t("pending"));
|
||||
return mapMemberToChildrenOption(
|
||||
{ ...member, eventTypes: member.eventTypes.filter((et) => et !== eventType.slug) },
|
||||
eventType.slug,
|
||||
t("pending")
|
||||
);
|
||||
});
|
||||
const isManagedEventType = eventType.schedulingType === SchedulingType.MANAGED;
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const [assignAllTeamMembers, setAssignAllTeamMembers] = useState<boolean>(
|
||||
formMethods.getValues("assignAllTeamMembers") ?? false
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{team && !isManagedEventType && (
|
||||
@@ -338,16 +469,26 @@ export const EventTeamTab = ({
|
||||
className="w-full"
|
||||
onChange={(val) => {
|
||||
onChange(val?.value);
|
||||
formMethods.setValue("assignAllTeamMembers", false);
|
||||
setAssignAllTeamMembers(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Hosts teamMembers={teamMembersOptions} />
|
||||
<Hosts
|
||||
assignAllTeamMembers={assignAllTeamMembers}
|
||||
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||
teamMembers={teamMembersOptions}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{team && isManagedEventType && (
|
||||
<ChildrenEventTypes childrenEventTypeOptions={childrenEventTypeOptions} />
|
||||
<ChildrenEventTypes
|
||||
assignAllTeamMembers={assignAllTeamMembers}
|
||||
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||
childrenEventTypeOptions={childrenEventTypeOptions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -145,6 +145,7 @@ export type FormValues = {
|
||||
bookerLayouts: BookerLayoutSettings;
|
||||
multipleDurationEnabled: boolean;
|
||||
users: EventTypeSetup["users"];
|
||||
assignAllTeamMembers: boolean;
|
||||
};
|
||||
|
||||
export type CustomInputParsed = typeof customInputSchema._output;
|
||||
@@ -195,6 +196,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
created: true,
|
||||
}))
|
||||
);
|
||||
formMethods.setValue("assignAllTeamMembers", formMethods.getValues("assignAllTeamMembers") || false);
|
||||
showToast(t("event_type_updated_successfully", { eventTypeTitle: eventType.title }), "success");
|
||||
},
|
||||
async onSettled() {
|
||||
@@ -305,6 +307,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
},
|
||||
})),
|
||||
seatsPerTimeSlotEnabled: eventType.seatsPerTimeSlot,
|
||||
assignAllTeamMembers: eventType.assignAllTeamMembers,
|
||||
};
|
||||
}, [eventType, periodDates, metadata]);
|
||||
|
||||
@@ -463,6 +466,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
metadata,
|
||||
customInputs,
|
||||
children,
|
||||
assignAllTeamMembers,
|
||||
// We don't need to send send these values to the backend
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
seatsPerTimeSlotEnabled,
|
||||
@@ -532,6 +536,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
metadata,
|
||||
customInputs,
|
||||
children,
|
||||
assignAllTeamMembers,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -2235,6 +2235,8 @@
|
||||
"add_a_redirect": "Add a redirect",
|
||||
"create_entry": "Create entry",
|
||||
"time_range": "Time range",
|
||||
"automatically_add_all_team_members": "Automatically add all team members",
|
||||
"including_people_who_join_in_future": "Including people who join the team in the future",
|
||||
"redirect_to": "Redirect to",
|
||||
"having_trouble_finding_time": "Having trouble finding a time?",
|
||||
"show_more": "Show more",
|
||||
|
||||
@@ -57,6 +57,7 @@ const _getEventType = async (id: number) => {
|
||||
seatsPerTimeSlot: true,
|
||||
bookingLimits: true,
|
||||
durationLimits: true,
|
||||
assignAllTeamMembers: true,
|
||||
timeZone: true,
|
||||
length: true,
|
||||
metadata: true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type z from "zod";
|
||||
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
@@ -56,6 +57,7 @@ export const createOrUpdateMemberships = async ({
|
||||
},
|
||||
});
|
||||
}
|
||||
await updateNewTeamMemberEventTypes(user.id, team.id);
|
||||
return { membership, orgMembership };
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export async function joinOrganization({
|
||||
@@ -52,4 +53,6 @@ export async function joinAnyChildTeamOnOrgInvite({ userId, orgId }: { userId: n
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
await updateNewTeamMemberEventTypes(userId, orgId);
|
||||
}
|
||||
|
||||
@@ -333,6 +333,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => {
|
||||
seatsShowAvailabilityCount: true,
|
||||
bookingLimits: true,
|
||||
durationLimits: true,
|
||||
assignAllTeamMembers: true,
|
||||
parentId: true,
|
||||
owner: {
|
||||
select: {
|
||||
|
||||
@@ -112,6 +112,7 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
||||
},
|
||||
},
|
||||
hidden: true,
|
||||
assignAllTeamMembers: true,
|
||||
});
|
||||
|
||||
export const getPublicEvent = async (
|
||||
@@ -264,6 +265,7 @@ export const getPublicEvent = async (
|
||||
},
|
||||
isDynamic: false,
|
||||
isInstantEvent: event.isInstantEvent,
|
||||
assignAllTeamMembers: event.assignAllTeamMembers,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ const commons = {
|
||||
hosts: [],
|
||||
metadata: EventTypeMetaDataSchema.parse({}),
|
||||
bookingFields: [],
|
||||
assignAllTeamMembers: false,
|
||||
};
|
||||
|
||||
const dynamicEvent = {
|
||||
|
||||
@@ -105,6 +105,7 @@ export default async function getEventTypeById({
|
||||
bookingLimits: true,
|
||||
onlyShowFirstAvailableSlot: true,
|
||||
durationLimits: true,
|
||||
assignAllTeamMembers: true,
|
||||
successRedirectUrl: true,
|
||||
currency: true,
|
||||
bookingFields: true,
|
||||
|
||||
@@ -3,7 +3,12 @@ import { Prisma } from "@prisma/client";
|
||||
import { getAppFromSlug } from "@calcom/app-store/utils";
|
||||
import prisma, { baseEventTypeSelect } from "@calcom/prisma";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { _EventTypeModel } from "@calcom/prisma/zod";
|
||||
import {
|
||||
EventTypeMetaDataSchema,
|
||||
allManagedEventTypeProps,
|
||||
unlockedManagedEventTypeProps,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { WEBAPP_URL } from "../../../constants";
|
||||
import { getBookerBaseUrlSync } from "../../../getBookerUrl/client";
|
||||
@@ -247,3 +252,67 @@ export async function isTeamMember(userId: number, teamId: number) {
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
export async function updateNewTeamMemberEventTypes(userId: number, teamId: number) {
|
||||
const eventTypesToAdd = await prisma.eventType.findMany({
|
||||
where: {
|
||||
team: { id: teamId },
|
||||
assignAllTeamMembers: true,
|
||||
},
|
||||
select: {
|
||||
...allManagedEventTypeProps,
|
||||
id: true,
|
||||
schedulingType: true,
|
||||
},
|
||||
});
|
||||
|
||||
const allManagedEventTypePropsZod = _EventTypeModel.pick(allManagedEventTypeProps);
|
||||
|
||||
eventTypesToAdd.length > 0 &&
|
||||
(await prisma.$transaction(
|
||||
eventTypesToAdd.map((eventType) => {
|
||||
if (eventType.schedulingType === "MANAGED") {
|
||||
const managedEventTypeValues = allManagedEventTypePropsZod
|
||||
.omit(unlockedManagedEventTypeProps)
|
||||
.parse(eventType);
|
||||
|
||||
// Define the values for unlocked properties to use on creation, not updation
|
||||
const unlockedEventTypeValues = allManagedEventTypePropsZod
|
||||
.pick(unlockedManagedEventTypeProps)
|
||||
.parse(eventType);
|
||||
|
||||
// Calculate if there are new workflows for which assigned members will get too
|
||||
const currentWorkflowIds = eventType.workflows?.map((wf) => wf.workflowId);
|
||||
|
||||
return prisma.eventType.create({
|
||||
data: {
|
||||
...managedEventTypeValues,
|
||||
...unlockedEventTypeValues,
|
||||
bookingLimits:
|
||||
(managedEventTypeValues.bookingLimits as unknown as Prisma.InputJsonObject) ?? undefined,
|
||||
recurringEvent:
|
||||
(managedEventTypeValues.recurringEvent as unknown as Prisma.InputJsonValue) ?? undefined,
|
||||
metadata: (managedEventTypeValues.metadata as Prisma.InputJsonValue) ?? undefined,
|
||||
bookingFields: (managedEventTypeValues.bookingFields as Prisma.InputJsonValue) ?? undefined,
|
||||
durationLimits: (managedEventTypeValues.durationLimits as Prisma.InputJsonValue) ?? undefined,
|
||||
onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false,
|
||||
userId,
|
||||
users: {
|
||||
connect: [{ id: userId }],
|
||||
},
|
||||
parentId: eventType.parentId,
|
||||
hidden: false,
|
||||
workflows: currentWorkflowIds && {
|
||||
create: currentWorkflowIds.map((wfId) => ({ workflowId: wfId })),
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return prisma.eventType.update({
|
||||
where: { id: eventType.id },
|
||||
data: { hosts: { create: [{ userId, isFixed: eventType.schedulingType === "COLLECTIVE" }] } },
|
||||
});
|
||||
}
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
|
||||
scheduleId: null,
|
||||
bookingLimits: null,
|
||||
durationLimits: null,
|
||||
assignAllTeamMembers: false,
|
||||
price: 0,
|
||||
currency: "usd",
|
||||
slotInterval: null,
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "EventType" ADD COLUMN "assignAllTeamMembers" BOOLEAN NOT NULL DEFAULT false;
|
||||
@@ -119,6 +119,7 @@ model EventType {
|
||||
/// @zod.custom(imports.intervalLimitsType)
|
||||
durationLimits Json?
|
||||
isInstantEvent Boolean @default(false)
|
||||
assignAllTeamMembers Boolean @default(false)
|
||||
|
||||
@@unique([userId, slug])
|
||||
@@unique([teamId, slug])
|
||||
|
||||
@@ -611,6 +611,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
|
||||
workflows: true,
|
||||
bookingFields: true,
|
||||
durationLimits: true,
|
||||
assignAllTeamMembers: true,
|
||||
};
|
||||
|
||||
// All properties that are defined as unlocked based on all managed props
|
||||
|
||||
@@ -37,6 +37,7 @@ export const EventTypeUpdateInput = _EventTypeModel
|
||||
.optional(),
|
||||
schedule: z.number().nullable().optional(),
|
||||
hashedLink: z.string(),
|
||||
assignAllTeamMembers: z.boolean().optional(),
|
||||
})
|
||||
.partial()
|
||||
.extend({
|
||||
|
||||
@@ -39,6 +39,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
recurringEvent,
|
||||
users,
|
||||
children,
|
||||
assignAllTeamMembers,
|
||||
hosts,
|
||||
id,
|
||||
hashedLink,
|
||||
@@ -65,9 +66,33 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
name: true,
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
parentId: true,
|
||||
parent: {
|
||||
select: {
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
members: {
|
||||
select: {
|
||||
role: true,
|
||||
accepted: true,
|
||||
user: {
|
||||
select: {
|
||||
name: true,
|
||||
id: true,
|
||||
email: true,
|
||||
eventTypes: {
|
||||
select: {
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -290,6 +315,8 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
}
|
||||
}
|
||||
|
||||
data.assignAllTeamMembers = assignAllTeamMembers ?? false;
|
||||
|
||||
const updatedEventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
||||
slug: true,
|
||||
schedulingType: true,
|
||||
@@ -322,6 +349,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
children,
|
||||
prisma: ctx.prisma,
|
||||
});
|
||||
|
||||
const res = ctx.res as NextApiResponse;
|
||||
if (typeof res?.revalidate !== "undefined") {
|
||||
try {
|
||||
|
||||
@@ -153,6 +153,7 @@ export async function getEventType(
|
||||
afterEventBuffer: true,
|
||||
bookingLimits: true,
|
||||
durationLimits: true,
|
||||
assignAllTeamMembers: true,
|
||||
schedulingType: true,
|
||||
periodType: true,
|
||||
periodStartDate: true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries";
|
||||
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
@@ -26,6 +27,8 @@ export const acceptOrLeaveHandler = async ({ ctx, input }: AcceptOrLeaveOptions)
|
||||
},
|
||||
});
|
||||
|
||||
await updateNewTeamMemberEventTypes(ctx.user.id, input.teamId);
|
||||
|
||||
closeComUpsertTeamUser(membership.team, ctx.user, membership.role);
|
||||
} else {
|
||||
try {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { updateQuantitySubscriptionFromStripe } from "@calcom/features/ee/teams/
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries";
|
||||
import { isOrganisationOwner } from "@calcom/lib/server/queries/organisations";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
@@ -131,6 +132,12 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) =
|
||||
}),
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
autoJoinUsers.map(async (userToAutoJoin) => {
|
||||
await updateNewTeamMemberEventTypes(userToAutoJoin.id, team.id);
|
||||
})
|
||||
);
|
||||
|
||||
await sendTeamInviteEmails({
|
||||
currentUserName: ctx?.user?.name,
|
||||
currentUserTeamName: team?.name,
|
||||
|
||||
Reference in New Issue
Block a user