* feature to enable optimized slots * type check (was not caught running locally) * update for typechecks * update for type checks * initialize form * update slots test for showOptimizedSlots * added some more tests * handled edge case * added test for edge case * generalized condition * refactored instead of ternary and added comments * update condition * Update apps/web/public/static/locales/en/common.json Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> * moved showOptimizedSlots setting to advanced tab * updated to always move to next 15min if not possible to next interval * correct after merge * chore * fix typecheck after latest merges * remove workinghours when dateRanges input * refactor: extract to fn * chore * remove unrelated changes * remove unrealted change * updated tests * unrelated format changes no-verify * unrelated format changes no-verify * handle current day booking cases, to show roundedoff minutes * remove unrelated format changes in openapi.json * add test for new condition * remove unrelated auto-format changes * remove unrelated auto-format changes * update after merge * For current day bookings, normalizing the seconds to zero to avoid issues with time calculations * Update packages/platform/atoms/event-types/hooks/useEventTypeForm.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * update for typecheck due to latest changes * resolve merge conflicts * Smaller description * Minor style change to prefer early return --------- Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: Devanshu Sharma <devanshusharma658@gmail.com> Co-authored-by: Volnei Munhoz <volnei.munhoz@gmail.com> Co-authored-by: Keith Williams <keithwillcode@gmail.com> Co-authored-by: Alex van Andel <me@alexvanandel.com>
217 lines
6.3 KiB
TypeScript
217 lines
6.3 KiB
TypeScript
import type { Prisma, SelectedCalendar } from "@prisma/client";
|
|
|
|
import { DailyLocationType } from "@calcom/app-store/locations";
|
|
import slugify from "@calcom/lib/slugify";
|
|
import { PeriodType, SchedulingType } from "@calcom/prisma/enums";
|
|
import type { userSelect } from "@calcom/prisma/selects";
|
|
import type { CustomInputSchema } from "@calcom/prisma/zod-utils";
|
|
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/prisma/zod-utils";
|
|
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
|
import type { CredentialPayload } from "@calcom/types/Credential";
|
|
|
|
type User = Omit<Prisma.UserGetPayload<typeof userSelect>, "selectedCalendars"> & {
|
|
allSelectedCalendars: SelectedCalendar[];
|
|
userLevelSelectedCalendars: SelectedCalendar[];
|
|
};
|
|
|
|
type UsernameSlugLinkProps = {
|
|
users: {
|
|
id?: number;
|
|
username: string | null;
|
|
email?: string;
|
|
name?: string | null;
|
|
bio?: string | null;
|
|
avatar?: string | null;
|
|
theme?: string | null;
|
|
away?: boolean;
|
|
verified?: boolean | null;
|
|
allowDynamicBooking?: boolean | null;
|
|
}[];
|
|
slug: string;
|
|
};
|
|
|
|
const user: User & { credentials: CredentialPayload[] } = {
|
|
metadata: null,
|
|
theme: null,
|
|
credentials: [],
|
|
username: "john.doe",
|
|
timeZone: "",
|
|
bufferTime: 0,
|
|
availability: [],
|
|
id: 0,
|
|
startTime: 0,
|
|
endTime: 0,
|
|
allSelectedCalendars: [],
|
|
userLevelSelectedCalendars: [],
|
|
schedules: [],
|
|
defaultScheduleId: null,
|
|
locale: "en",
|
|
email: "john.doe@example.com",
|
|
name: "John doe",
|
|
destinationCalendar: null,
|
|
hideBranding: true,
|
|
brandColor: "#797979",
|
|
darkBrandColor: "#efefef",
|
|
allowDynamicBooking: true,
|
|
timeFormat: 12,
|
|
travelSchedules: [],
|
|
locked: false,
|
|
isPlatformManaged: false,
|
|
};
|
|
|
|
const customInputs: CustomInputSchema[] = [];
|
|
|
|
const commons = {
|
|
isDynamic: true,
|
|
periodCountCalendarDays: true,
|
|
periodStartDate: null,
|
|
periodEndDate: null,
|
|
beforeEventBuffer: 0,
|
|
afterEventBuffer: 0,
|
|
periodType: PeriodType.UNLIMITED,
|
|
periodDays: null,
|
|
slotInterval: null,
|
|
offsetStart: 0,
|
|
locations: [{ type: DailyLocationType }],
|
|
customInputs,
|
|
disableGuests: true,
|
|
minimumBookingNotice: 120,
|
|
schedule: null,
|
|
timeZone: null,
|
|
successRedirectUrl: "",
|
|
forwardParamsSuccessRedirect: true,
|
|
teamId: null,
|
|
scheduleId: null,
|
|
availability: [],
|
|
price: 0,
|
|
currency: "usd",
|
|
schedulingType: SchedulingType.COLLECTIVE,
|
|
seatsPerTimeSlot: null,
|
|
seatsShowAttendees: null,
|
|
seatsShowAvailabilityCount: null,
|
|
disableCancelling: false,
|
|
disableRescheduling: false,
|
|
onlyShowFirstAvailableSlot: false,
|
|
allowReschedulingPastBookings: false,
|
|
hideOrganizerEmail: false,
|
|
showOptimizedSlots: false,
|
|
id: 0,
|
|
hideCalendarNotes: false,
|
|
hideCalendarEventDetails: false,
|
|
recurringEvent: null,
|
|
destinationCalendar: null,
|
|
team: null,
|
|
lockTimeZoneToggleOnBookingPage: false,
|
|
lockedTimeZone: null,
|
|
requiresConfirmation: false,
|
|
requiresConfirmationForFreeEmail: false,
|
|
requiresBookerEmailVerification: false,
|
|
bookingLimits: null,
|
|
maxActiveBookingsPerBooker: null,
|
|
maxActiveBookingPerBookerOfferReschedule: false,
|
|
durationLimits: null,
|
|
hidden: false,
|
|
userId: 0,
|
|
parentId: null,
|
|
parent: null,
|
|
owner: null,
|
|
workflows: [],
|
|
users: [user],
|
|
hosts: [],
|
|
subsetOfHosts: [],
|
|
metadata: EventTypeMetaDataSchema.parse({}),
|
|
bookingFields: [],
|
|
assignAllTeamMembers: false,
|
|
assignRRMembersUsingSegment: false,
|
|
rrSegmentQueryValue: null,
|
|
isRRWeightsEnabled: false,
|
|
rescheduleWithSameRoundRobinHost: false,
|
|
useEventTypeDestinationCalendarEmail: false,
|
|
secondaryEmailId: null,
|
|
secondaryEmail: null,
|
|
autoTranslateDescriptionEnabled: false,
|
|
fieldTranslations: [],
|
|
maxLeadThreshold: null,
|
|
includeNoShowInRRCalculation: false,
|
|
useEventLevelSelectedCalendars: false,
|
|
rrResetInterval: null,
|
|
rrTimestampBasis: null,
|
|
interfaceLanguage: null,
|
|
customReplyToEmail: null,
|
|
restrictionScheduleId: null,
|
|
useBookerTimezone: false,
|
|
profileId: null,
|
|
requiresConfirmationWillBlockSlot: false,
|
|
canSendCalVideoTranscriptionEmails: false,
|
|
instantMeetingExpiryTimeOffsetInSeconds: 0,
|
|
instantMeetingScheduleId: null,
|
|
instantMeetingParameters: [],
|
|
eventTypeColor: null,
|
|
hostGroups: [],
|
|
bookingRequiresAuthentication: false,
|
|
};
|
|
|
|
export const dynamicEvent = {
|
|
length: 30,
|
|
slug: "dynamic",
|
|
title: "Group Meeting",
|
|
eventName: "Group Meeting",
|
|
description: "Join us for a meeting with multiple people",
|
|
descriptionAsSafeHTML: "",
|
|
position: 0,
|
|
...commons,
|
|
metadata: eventTypeMetaDataSchemaWithTypedApps.parse({ multipleDuration: [15, 30, 45, 60, 90] }),
|
|
};
|
|
|
|
export const defaultEvents = [dynamicEvent];
|
|
|
|
export const getDynamicEventDescription = (dynamicUsernames: string[], slug: string): string => {
|
|
return `Book a ${slug} min event with ${dynamicUsernames.join(", ")}`;
|
|
};
|
|
|
|
export const getDynamicEventName = (dynamicNames: string[], slug: string): string => {
|
|
const lastUser = dynamicNames.pop();
|
|
return `Dynamic Collective ${slug} min event with ${dynamicNames.join(", ")} & ${lastUser}`;
|
|
};
|
|
|
|
export const getDefaultEvent = (slug: string) => {
|
|
const event = defaultEvents.find((obj) => {
|
|
return obj.slug === slug;
|
|
});
|
|
return event || dynamicEvent;
|
|
};
|
|
|
|
export const getGroupName = (usernameList: string[]): string => {
|
|
return usernameList.join(", ");
|
|
};
|
|
|
|
export const getUsernameSlugLink = ({ users, slug }: UsernameSlugLinkProps): string => {
|
|
let slugLink = ``;
|
|
if (users.length > 1) {
|
|
const combinedUsername = users.map((user) => user.username).join("+");
|
|
slugLink = `/${combinedUsername}/${slug}`;
|
|
} else {
|
|
slugLink = `/${users[0].username}/${slug}`;
|
|
}
|
|
return slugLink;
|
|
};
|
|
|
|
const arrayCast = (value: unknown | unknown[]) => {
|
|
return Array.isArray(value) ? value : value ? [value] : [];
|
|
};
|
|
|
|
export const getUsernameList = (users: string | string[] | undefined): string[] => {
|
|
// Multiple users can come in case of a team round-robin booking and in that case dynamic link won't be a user.
|
|
// So, even though this code handles even if individual user is dynamic link, that isn't a possibility right now.
|
|
users = arrayCast(users);
|
|
const allUsers = users
|
|
.map((user) => user.replace(/( |%20|%2b)/gi, "+").split("+"))
|
|
.flat()
|
|
.filter(Boolean);
|
|
return Array.prototype.concat(...allUsers.map((userSlug) => slugify(userSlug)));
|
|
};
|
|
|
|
export default defaultEvents;
|
|
|
|
export type DefaultEvent = Awaited<ReturnType<typeof getDefaultEvent>>;
|