diff --git a/packages/emails/lib/types/booking-redirect-types.ts b/packages/emails/lib/types/booking-redirect-types.ts new file mode 100644 index 0000000000..ffa136d0cf --- /dev/null +++ b/packages/emails/lib/types/booking-redirect-types.ts @@ -0,0 +1,12 @@ +import type { TFunction } from "i18next"; + +export interface IBookingRedirect { + language: TFunction; + fromEmail: string; + eventOwner: string; + toEmail: string; + toName: string; + oldDates?: string; + dates: string; + action: "add" | "update" | "cancel"; +} diff --git a/packages/emails/lib/types/email-types.ts b/packages/emails/lib/types/email-types.ts new file mode 100644 index 0000000000..f8e48dffaa --- /dev/null +++ b/packages/emails/lib/types/email-types.ts @@ -0,0 +1,23 @@ +import type { TFunction } from "i18next"; + +export type OrganizationCreation = { + language: TFunction; + from: string; + to: string; + ownerNewUsername: string; + ownerOldUsername: string | null; + orgDomain: string; + orgName: string; + prevLink: string | null; + newLink: string; +}; + +export type EmailVerifyCode = { + language: TFunction; + user: { + name?: string | null; + email: string; + }; + verificationEmailCode: string; + isVerifyingEmail?: boolean; +}; diff --git a/packages/emails/lib/utils/date-formatting.ts b/packages/emails/lib/utils/date-formatting.ts new file mode 100644 index 0000000000..3e6f1888cc --- /dev/null +++ b/packages/emails/lib/utils/date-formatting.ts @@ -0,0 +1,26 @@ +import dayjs from "@calcom/dayjs"; +import { TimeFormat } from "@calcom/lib/timeFormat"; +import type { CalendarEvent, Person } from "@calcom/types/Calendar"; + +export function getFormattedDate(calEvent: CalendarEvent, attendee: Person): string { + const inviteeTimeFormat = calEvent.organizer.timeFormat || TimeFormat.TWELVE_HOUR; + const timezone = attendee.timeZone; + const locale = attendee.language.locale; + const t = attendee.language.translate; + + const getFormattedRecipientTime = (time: string, format: string) => { + return dayjs(time).tz(timezone).locale(locale).format(format); + }; + + const getInviteeStart = (format: string) => { + return getFormattedRecipientTime(calEvent.startTime, format); + }; + + const getInviteeEnd = (format: string) => { + return getFormattedRecipientTime(calEvent.endTime, format); + }; + + return `${getInviteeStart(inviteeTimeFormat)} - ${getInviteeEnd(inviteeTimeFormat)}, ${t( + getInviteeStart("dddd").toLowerCase() + )}, ${t(getInviteeStart("MMMM").toLowerCase())} ${getInviteeStart("D, YYYY")}`; +} diff --git a/packages/emails/lib/utils/team-invite-utils.ts b/packages/emails/lib/utils/team-invite-utils.ts new file mode 100644 index 0000000000..d9f8562cab --- /dev/null +++ b/packages/emails/lib/utils/team-invite-utils.ts @@ -0,0 +1,59 @@ +import type { TFunction } from "i18next"; + +import { APP_NAME } from "@calcom/lib/constants"; + +export type TeamInvite = { + language: TFunction; + from: string; + to: string; + teamName: string; + joinLink: string; + isCalcomMember: boolean; + /** + * We ideally should have a separate email for auto-join(when a user is automatically accepted into a team/org), but we don't have one yet. + */ + isAutoJoin: boolean; + isOrg: boolean; + parentTeamName: string | undefined; + isExistingUserMovedToOrg: boolean; + prevLink: string | null; + newLink: string | null; +}; + +export function getTypeOfInvite(teamInviteEvent: TeamInvite) { + if (teamInviteEvent.isOrg) { + return "TO_ORG"; + } + + if (teamInviteEvent.parentTeamName) { + return "TO_SUBTEAM"; + } + + if (teamInviteEvent.isAutoJoin) { + throw new Error("Auto-join is not supported for regular teams"); + } + + return "TO_REGULAR_TEAM"; +} + +export const getSubject = (teamInviteEvent: TeamInvite) => { + const typeOfInvite = getTypeOfInvite(teamInviteEvent); + const type = teamInviteEvent.isAutoJoin ? "added" : "invited"; + const variables = { + user: teamInviteEvent.from, + team: teamInviteEvent.teamName, + appName: APP_NAME, + parentTeamName: teamInviteEvent.parentTeamName, + entity: teamInviteEvent.language(teamInviteEvent.isOrg ? "organization" : "team").toLowerCase(), + }; + + if (typeOfInvite === "TO_ORG") { + return teamInviteEvent.language(`email_team_invite|subject|${type}_to_org`, variables); + } + + if (typeOfInvite === "TO_SUBTEAM") { + return teamInviteEvent.language(`email_team_invite|subject|${type}_to_subteam`, variables); + } + + return teamInviteEvent.language(`email_team_invite|subject|${type}_to_regular_team`, variables); +}; diff --git a/packages/emails/src/templates/AttendeeRequestEmail.tsx b/packages/emails/src/templates/AttendeeRequestEmail.tsx index 233a38c9bb..bf6071c81f 100644 --- a/packages/emails/src/templates/AttendeeRequestEmail.tsx +++ b/packages/emails/src/templates/AttendeeRequestEmail.tsx @@ -1,8 +1,8 @@ -import AttendeeScheduledEmailClass from "../../templates/attendee-rescheduled-email"; +import { getFormattedDate } from "../../lib/utils/date-formatting"; import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail"; export const AttendeeRequestEmail = (props: React.ComponentProps) => { - const date = new AttendeeScheduledEmailClass(props.calEvent, props.attendee).getFormattedDate(); + const date = getFormattedDate(props.calEvent, props.attendee); return ( { - const typeOfInvite = getTypeOfInvite(teamInviteEvent); - const type = teamInviteEvent.isAutoJoin ? "added" : "invited"; - const variables = { - user: teamInviteEvent.from, - team: teamInviteEvent.teamName, - appName: APP_NAME, - parentTeamName: teamInviteEvent.parentTeamName, - entity: teamInviteEvent.language(teamInviteEvent.isOrg ? "organization" : "team").toLowerCase(), - }; - - if (typeOfInvite === "TO_ORG") { - return teamInviteEvent.language(`email_team_invite|subject|${type}_to_org`, variables); - } - - if (typeOfInvite === "TO_SUBTEAM") { - return teamInviteEvent.language(`email_team_invite|subject|${type}_to_subteam`, variables); - } - - return teamInviteEvent.language(`email_team_invite|subject|${type}_to_regular_team`, variables); -}; +export { getSubject, getTypeOfInvite, type TeamInvite } from "../lib/utils/team-invite-utils"; export default class TeamInviteEmail extends BaseEmail { teamInviteEvent: TeamInvite; diff --git a/packages/trpc/server/routers/apps/routing-forms/_router.ts b/packages/trpc/server/routers/apps/routing-forms/_router.ts index 9f8e219ae3..9e4731bed4 100644 --- a/packages/trpc/server/routers/apps/routing-forms/_router.ts +++ b/packages/trpc/server/routers/apps/routing-forms/_router.ts @@ -8,10 +8,11 @@ import { ZFormMutationInputSchema } from "./formMutation.schema"; import { ZFormQueryInputSchema } from "./formQuery.schema"; import { ZGetAttributesForTeamInputSchema } from "./getAttributesForTeam.schema"; import { ZGetIncompleteBookingSettingsInputSchema } from "./getIncompleteBookingSettings.schema"; +import { ZFormByResponseIdInputSchema } from "./getResponseWithFormFields.schema"; import { forms } from "./procedures/forms"; import { ZSaveIncompleteBookingSettingsInputSchema } from "./saveIncompleteBookingSettings.schema"; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type const UNSTABLE_HANDLER_CACHE: Record = {}; // TODO: Move getHandler and UNSTABLE_HANDLER_CACHE to a common utils file making sure that there is no name collision across routes @@ -24,7 +25,7 @@ const UNSTABLE_HANDLER_CACHE: Record = {}; */ const getHandler = async < T extends { - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type default: Function; } >( @@ -45,10 +46,6 @@ const getHandler = async < return UNSTABLE_HANDLER_CACHE[nameInCache] as unknown as T["default"]; }; -export const ZFormByResponseIdInputSchema = z.object({ - formResponseId: z.number(), -}); - export type TFormQueryInputSchema = z.infer; const appRoutingForms = router({ diff --git a/packages/trpc/server/routers/apps/routing-forms/getResponseWithFormFields.handler.ts b/packages/trpc/server/routers/apps/routing-forms/getResponseWithFormFields.handler.ts index 0359b2a2e5..d5e9e6ebfd 100644 --- a/packages/trpc/server/routers/apps/routing-forms/getResponseWithFormFields.handler.ts +++ b/packages/trpc/server/routers/apps/routing-forms/getResponseWithFormFields.handler.ts @@ -10,7 +10,7 @@ import type { TrpcSessionUser } from "@calcom/trpc/server/types"; import { TRPCError } from "@trpc/server"; -import type { ZFormByResponseIdInputSchema } from "./_router"; +import type { ZFormByResponseIdInputSchema } from "./getResponseWithFormFields.schema"; type GetResponseWithFormFieldsOptions = { ctx: { diff --git a/packages/trpc/server/routers/apps/routing-forms/getResponseWithFormFields.schema.ts b/packages/trpc/server/routers/apps/routing-forms/getResponseWithFormFields.schema.ts new file mode 100644 index 0000000000..0dd9b05ebb --- /dev/null +++ b/packages/trpc/server/routers/apps/routing-forms/getResponseWithFormFields.schema.ts @@ -0,0 +1,5 @@ +import { z } from "zod"; + +export const ZFormByResponseIdInputSchema = z.object({ + formResponseId: z.number(), +}); diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts index f4344dae23..2b7d9eda65 100644 --- a/packages/types/Calendar.d.ts +++ b/packages/types/Calendar.d.ts @@ -6,7 +6,6 @@ import type { Frequency } from "rrule"; import type z from "zod"; import type { bookingResponse } from "@calcom/features/bookings/lib/getBookingResponsesSchema"; -import type { Calendar } from "@calcom/features/calendars/weeklyview"; import type { TimeFormat } from "@calcom/lib/timeFormat"; import type { BookingSeat, @@ -66,7 +65,6 @@ export type EventBusyDetails = EventBusyDate & { userId?: number | null; }; -export type CalendarServiceType = typeof Calendar; export type AdditionalInfo = Record & { calWarnings?: string[] }; export type NewCalendarEventType = { @@ -104,9 +102,9 @@ export type CalendarEventType = { isNegative: boolean; }; organizer: string; - attendees: any[][]; + attendees: unknown[][]; recurrenceId: Time; - timezone: any; + timezone: string | object; }; export type BatchResponse = { @@ -314,7 +312,7 @@ export interface Calendar { /** * @see [How to inference class type that implements an interface](https://stackoverflow.com/a/64765554/6297100) */ -type Class = new (...args: Args) => I; +type Class = new (...args: Args) => I; export type CalendarClass = Class;