diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index ef3e9c57e217ec..4a5516ec0c2594 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -38,12 +38,12 @@ interface GoogleCalError extends Error { const MS_PER_DAY = 24 * 60 * 60 * 1000; const ONE_MONTH_IN_MS = 30 * MS_PER_DAY; -// eslint-disable-next-line turbo/no-undeclared-env-vars -- GOOGLE_WEBHOOK_URL only for local testing + const GOOGLE_WEBHOOK_URL_BASE = process.env.GOOGLE_WEBHOOK_URL || process.env.NEXT_PUBLIC_WEBAPP_URL; const GOOGLE_WEBHOOK_URL = `${GOOGLE_WEBHOOK_URL_BASE}/api/integrations/googlecalendar/webhook`; const isGaxiosResponse = (error: unknown): error is GaxiosResponse => - typeof error === "object" && !!error && error.hasOwnProperty("config"); + typeof error === "object" && !!error && Object.prototype.hasOwnProperty.call(error, "config"); type GoogleChannelProps = { kind?: string | null; @@ -181,6 +181,15 @@ export default class GoogleCalendarService implements Calendar { ): Promise { this.log.debug("Creating event"); + // Determine optional custom reminder minutes from event payload or destination calendar metadata + // We intentionally access via `any` to avoid widening Calendar types and to keep this change isolated. + const customReminderMinutes: number | undefined = + (calEvent as any)?.customReminderMinutes ?? + (calEvent.destinationCalendar?.find((cal) => cal.credentialId === credentialId) + ? (calEvent.destinationCalendar.find((cal) => cal.credentialId === credentialId) as any) + ?.customReminderMinutes + : undefined); + const payload: calendar_v3.Schema$Event = { summary: calEvent.title, description: calEvent.calendarDescription, @@ -193,10 +202,21 @@ export default class GoogleCalendarService implements Calendar { timeZone: calEvent.organizer.timeZone, }, attendees: this.getAttendees({ event: calEvent, hostExternalCalendarId: externalCalendarId }), - reminders: { - useDefault: true, - }, - guestsCanSeeOtherGuests: !!calEvent.seatsPerTimeSlot ? calEvent.seatsShowAttendees : true, + reminders: + customReminderMinutes !== undefined && Number.isFinite(customReminderMinutes) + ? { + useDefault: false, + overrides: [ + { + method: "popup", + minutes: Math.max(0, Math.floor(customReminderMinutes)), + }, + ], + } + : { + useDefault: true, + }, + guestsCanSeeOtherGuests: calEvent.seatsPerTimeSlot ? calEvent.seatsShowAttendees : true, iCalUID: calEvent.iCalUID, }; if (calEvent.hideCalendarEventDetails) { @@ -340,6 +360,14 @@ export default class GoogleCalendarService implements Calendar { } async updateEvent(uid: string, event: CalendarServiceEvent, externalCalendarId: string): Promise { + // Determine optional custom reminder minutes from event payload or destination calendar metadata + const updateCustomReminderMinutes: number | undefined = + (event as any)?.customReminderMinutes ?? + (event.destinationCalendar && externalCalendarId + ? ((event.destinationCalendar.find((cal) => cal.externalId === externalCalendarId) as any) + ?.customReminderMinutes as number | undefined) + : undefined); + const payload: calendar_v3.Schema$Event = { summary: event.title, description: event.calendarDescription, @@ -352,10 +380,21 @@ export default class GoogleCalendarService implements Calendar { timeZone: event.organizer.timeZone, }, attendees: this.getAttendees({ event, hostExternalCalendarId: externalCalendarId }), - reminders: { - useDefault: true, - }, - guestsCanSeeOtherGuests: !!event.seatsPerTimeSlot ? event.seatsShowAttendees : true, + reminders: + updateCustomReminderMinutes !== undefined && Number.isFinite(updateCustomReminderMinutes) + ? { + useDefault: false, + overrides: [ + { + method: "popup", + minutes: Math.max(0, Math.floor(updateCustomReminderMinutes)), + }, + ], + } + : { + useDefault: true, + }, + guestsCanSeeOtherGuests: event.seatsPerTimeSlot ? event.seatsShowAttendees : true, }; if (event.location) { diff --git a/packages/features/ee/teams/components/MemberList.tsx b/packages/features/ee/teams/components/MemberList.tsx index 929eec88af8557..d8007d5220ff0f 100644 --- a/packages/features/ee/teams/components/MemberList.tsx +++ b/packages/features/ee/teams/components/MemberList.tsx @@ -433,7 +433,8 @@ function MemberListContent(props: Props) { const canImpersonate = props.permissions?.canImpersonate ?? false; const canResendInvitation = props.permissions?.canInvite ?? false; const editMode = - [canChangeRole, canRemove, canImpersonate, canResendInvitation].some(Boolean) && !isSelf; + [canChangeRole, canRemove, canImpersonate, canResendInvitation].some(Boolean) && + (!isSelf || props.isOrgAdminOrOwner); const impersonationMode = canImpersonate && @@ -668,7 +669,7 @@ function MemberListContent(props: Props) { getFacetedUniqueValues: (_, columnId) => () => { if (facetedTeamValues) { switch (columnId) { - case "role": + case "role": { // Include both traditional roles and PBAC custom roles const allRoles = facetedTeamValues.roles.map((role) => ({ label: role.name, @@ -676,6 +677,7 @@ function MemberListContent(props: Props) { })); return convertFacetedValuesToMap(allRoles); + } default: return new Map(); } @@ -685,7 +687,7 @@ function MemberListContent(props: Props) { getRowId: (row) => `${row.id}`, }); - const fetchMoreOnBottomReached = useFetchMoreOnBottomReached({ + const _fetchMoreOnBottomReached = useFetchMoreOnBottomReached({ tableContainerRef, hasNextPage, fetchNextPage, diff --git a/packages/features/users/components/UserTable/UserListTable.tsx b/packages/features/users/components/UserTable/UserListTable.tsx index 4460d1c7c4a81e..8f123abd2d921a 100644 --- a/packages/features/users/components/UserTable/UserListTable.tsx +++ b/packages/features/users/components/UserTable/UserListTable.tsx @@ -169,7 +169,7 @@ function UserListTableContent({ ); // TODO (SEAN): Make Column filters a trpc query param so we can fetch serverside even if the data is not loaded - const totalRowCount = data?.meta?.totalRowCount ?? 0; + const _totalRowCount = data?.meta?.totalRowCount ?? 0; const adminOrOwner = checkAdminOrOwner(org?.user?.role); //we must flatten the array of arrays from the useInfiniteQuery hook @@ -448,7 +448,7 @@ function UserListTableContent({ const isSelf = user.id === session?.user.id; const permissionsForUser = { - canEdit: permissionsRaw.canEdit && user.accepted && !isSelf, + canEdit: permissionsRaw.canEdit && user.accepted && (!isSelf || adminOrOwner), canRemove: permissionsRaw.canRemove && !isSelf, canImpersonate: user.accepted && @@ -514,7 +514,7 @@ function UserListTableContent({ value: team.name, })) ); - default: + default: { const attribute = facetedTeamValues.attributes.find((attr) => attr.id === columnId); if (attribute) { return convertFacetedValuesToMap( @@ -525,6 +525,7 @@ function UserListTableContent({ ); } return new Map(); + } } } return new Map(); diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts index f4344dae23997f..09661b6bae6562 100644 --- a/packages/types/Calendar.d.ts +++ b/packages/types/Calendar.d.ts @@ -5,6 +5,7 @@ import type { Time } from "ical.js"; import type { Frequency } from "rrule"; import type z from "zod"; +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { bookingResponse } from "@calcom/features/bookings/lib/getBookingResponsesSchema"; import type { Calendar } from "@calcom/features/calendars/weeklyview"; import type { TimeFormat } from "@calcom/lib/timeFormat"; @@ -210,6 +211,9 @@ export interface CalendarEvent { disableCancelling?: boolean; disableRescheduling?: boolean; + // Optional per-calendar reminder override in minutes for integrations that support it + customReminderMinutes?: number; + // It has responses to all the fields(system + user) responses?: CalEventResponses | null; @@ -260,6 +264,8 @@ export interface IntegrationCalendar extends Ensure, export type SelectedCalendarEventTypeIds = (number | null)[]; export interface CalendarServiceEvent extends CalendarEvent { + // Optional per-calendar reminder override in minutes for integrations that support it + customReminderMinutes?: number; calendarDescription: string; }