From d9b3f8657fc267a56bafb0923f44f3348128c2f3 Mon Sep 17 00:00:00 2001 From: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Date: Thu, 1 May 2025 23:48:52 +0530 Subject: [PATCH] feat: add setting to disable phone only sms notification (#21038) * feat: add setting to disable phone only sms notification * Update apps/web/public/static/locales/en/common.json Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> --------- Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> --- apps/web/public/static/locales/en/common.json | 2 + ...DisablePhoneOnlySMSNotificationsSwitch.tsx | 54 +++++++++++++++++++ .../organizations/pages/settings/general.tsx | 2 + .../lib/server/repository/organization.ts | 2 + .../migration.sql | 2 + packages/prisma/schema.prisma | 20 +++---- packages/sms/sms-manager.ts | 11 +++- .../viewer/organizations/update.handler.ts | 4 ++ .../viewer/organizations/update.schema.ts | 1 + 9 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 packages/features/ee/organizations/pages/components/DisablePhoneOnlySMSNotificationsSwitch.tsx create mode 100644 packages/prisma/migrations/20250430084050_disable_org_phone_only_sms/migration.sql diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 8ab599882f..0bda8e5f52 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2570,6 +2570,8 @@ "org_admin_no_slots|cta": "Open users availability", "organization_no_slots_notification_switch_title": "Get notifications when your team has no availability", "organization_no_slots_notification_switch_description": "Admins will get email notifications when a user tries to book a team member and is faced with 'No availability'. We trigger this email after two occurrences and remind you every 7 days per user. ", + "organization_disable_phone_only_sms_notifications_switch_title": "Disable SMS notifications for phone only event types", + "organization_disable_phone_only_sms_notifications_switch_description": "Disable automated SMS notifications for event types that only have phone number(attendeePhoneNumber) as required question with email as optional or not required", "email_team_invite|subject|added_to_org": "{{user}} added you to the organization {{team}} on {{appName}}", "email_team_invite|subject|invited_to_org": "{{user}} invited you to join the organization {{team}} on {{appName}}", "email_team_invite|subject|added_to_subteam": "{{user}} added you to the team {{team}} of organization {{parentTeamName}} on {{appName}}", diff --git a/packages/features/ee/organizations/pages/components/DisablePhoneOnlySMSNotificationsSwitch.tsx b/packages/features/ee/organizations/pages/components/DisablePhoneOnlySMSNotificationsSwitch.tsx new file mode 100644 index 0000000000..04f17f3d4e --- /dev/null +++ b/packages/features/ee/organizations/pages/components/DisablePhoneOnlySMSNotificationsSwitch.tsx @@ -0,0 +1,54 @@ +"use client" +import { useState } from "react"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import type { RouterOutputs } from "@calcom/trpc"; +import { trpc } from "@calcom/trpc"; +import { SettingsToggle } from "@calcom/ui/components/form"; +import { showToast } from "@calcom/ui/components/toast"; + +interface GeneralViewProps { + currentOrg: RouterOutputs["viewer"]["organizations"]["listCurrent"]; + isAdminOrOwner: boolean; +} + +export const DisablePhoneOnlySMSNotificationsSwitch = ({ currentOrg, isAdminOrOwner }: GeneralViewProps) => { + const { t } = useLocale(); + const utils = trpc.useUtils(); + const [disablePhoneOnlySMSNotificationsActive, setDisablePhoneOnlySMSNotificationsActive] = useState( + !!currentOrg.organizationSettings.disablePhoneOnlySMSNotifications + ); + + const mutation = trpc.viewer.organizations.update.useMutation({ + onSuccess: async () => { + showToast(t("settings_updated_successfully"), "success"); + }, + onError: () => { + showToast(t("error_updating_settings"), "error"); + }, + onSettled: () => { + utils.viewer.organizations.listCurrent.invalidate(); + }, + }); + + if (!isAdminOrOwner) return null; + + return ( + <> + { + mutation.mutate({ + disablePhoneOnlySMSNotifications: checked, + }); + setDisablePhoneOnlySMSNotificationsActive(checked); + }} + switchContainerClassName="mt-6" + /> + + ); +}; diff --git a/packages/features/ee/organizations/pages/settings/general.tsx b/packages/features/ee/organizations/pages/settings/general.tsx index 5ffca1841e..b4862548c6 100644 --- a/packages/features/ee/organizations/pages/settings/general.tsx +++ b/packages/features/ee/organizations/pages/settings/general.tsx @@ -21,6 +21,7 @@ import { Select } from "@calcom/ui/components/form"; import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton"; import { showToast } from "@calcom/ui/components/toast"; +import { DisablePhoneOnlySMSNotificationsSwitch } from "../components/DisablePhoneOnlySMSNotificationsSwitch"; import { LockEventTypeSwitch } from "../components/LockEventTypeSwitch"; import { NoSlotsNotificationSwitch } from "../components/NoSlotsNotificationSwitch"; @@ -82,6 +83,7 @@ const OrgGeneralView = () => { + ); }; diff --git a/packages/lib/server/repository/organization.ts b/packages/lib/server/repository/organization.ts index db97e700a3..709aeb3fcf 100644 --- a/packages/lib/server/repository/organization.ts +++ b/packages/lib/server/repository/organization.ts @@ -268,6 +268,7 @@ export class OrganizationRepository { allowSEOIndexing: true, orgProfileRedirectsToVerifiedDomain: true, orgAutoAcceptEmail: true, + disablePhoneOnlySMSNotifications: true, }, }); @@ -285,6 +286,7 @@ export class OrganizationRepository { allowSEOIndexing: organizationSettings?.allowSEOIndexing, orgProfileRedirectsToVerifiedDomain: organizationSettings?.orgProfileRedirectsToVerifiedDomain, orgAutoAcceptEmail: organizationSettings?.orgAutoAcceptEmail, + disablePhoneOnlySMSNotifications: organizationSettings?.disablePhoneOnlySMSNotifications, }, user: { role: membership?.role, diff --git a/packages/prisma/migrations/20250430084050_disable_org_phone_only_sms/migration.sql b/packages/prisma/migrations/20250430084050_disable_org_phone_only_sms/migration.sql new file mode 100644 index 0000000000..50b2c65dd8 --- /dev/null +++ b/packages/prisma/migrations/20250430084050_disable_org_phone_only_sms/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "OrganizationSettings" ADD COLUMN "disablePhoneOnlySMSNotifications" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 20588024f2..e4399dfddf 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -175,7 +175,7 @@ model EventType { rescheduleWithSameRoundRobinHost Boolean @default(false) secondaryEmailId Int? - secondaryEmail SecondaryEmail? @relation(fields: [secondaryEmailId], references: [id], onDelete: Cascade) + secondaryEmail SecondaryEmail? @relation(fields: [secondaryEmailId], references: [id], onDelete: Cascade) @@unique([userId, slug]) @@unique([teamId, slug]) @@ -206,12 +206,12 @@ model Credential { paymentStatus String? billingCycleStart Int? - destinationCalendars DestinationCalendar[] - selectedCalendars SelectedCalendar[] - invalid Boolean? @default(false) - CalendarCache CalendarCache[] - references BookingReference[] - delegationCredentialId String? + destinationCalendars DestinationCalendar[] + selectedCalendars SelectedCalendar[] + invalid Boolean? @default(false) + CalendarCache CalendarCache[] + references BookingReference[] + delegationCredentialId String? delegationCredential DelegationCredential? @relation(fields: [delegationCredentialId], references: [id], onDelete: Cascade) @@index([userId]) @@ -531,6 +531,7 @@ model OrganizationSettings { isAdminAPIEnabled Boolean @default(false) allowSEOIndexing Boolean @default(false) orgProfileRedirectsToVerifiedDomain Boolean @default(false) + disablePhoneOnlySMSNotifications Boolean @default(false) } enum MembershipRole { @@ -1467,7 +1468,7 @@ view BookingTimeStatus { model CalendarCache { // To be made required in a followup id String? @default(uuid()) - + // The key would be the unique URL that is requested by the user key String value Json @@ -1475,9 +1476,10 @@ model CalendarCache { credentialId Int userId Int? credential Credential? @relation(fields: [credentialId], references: [id], onDelete: Cascade) + @@id([credentialId, key]) - @@index([userId, key]) @@unique([credentialId, key]) + @@index([userId, key]) } enum RedirectType { diff --git a/packages/sms/sms-manager.ts b/packages/sms/sms-manager.ts index 3ce4a74f43..7dcc6642ed 100644 --- a/packages/sms/sms-manager.ts +++ b/packages/sms/sms-manager.ts @@ -29,12 +29,21 @@ const handleSendingSMS = ({ parent: { select: { isOrganization: true, + organizationSettings: { + select: { + disablePhoneOnlySMSNotifications: true, + }, + }, }, }, }, }); - if (!team?.parent?.isOrganization) return; + if ( + !team?.parent?.isOrganization || + team?.parent?.organizationSettings?.disablePhoneOnlySMSNotifications + ) + return; await checkSMSRateLimit({ identifier: `handleSendingSMS:team:${teamId}`, rateLimitingType: "sms" }); const sms = twilio.sendSMS(reminderPhone, smsMessage, senderID, teamId); diff --git a/packages/trpc/server/routers/viewer/organizations/update.handler.ts b/packages/trpc/server/routers/viewer/organizations/update.handler.ts index e3bcf6a529..2b3fadaad8 100644 --- a/packages/trpc/server/routers/viewer/organizations/update.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/update.handler.ts @@ -49,6 +49,10 @@ const updateOrganizationSettings = async ({ data.orgProfileRedirectsToVerifiedDomain = input.orgProfileRedirectsToVerifiedDomain; } + if (input.hasOwnProperty("disablePhoneOnlySMSNotifications")) { + data.disablePhoneOnlySMSNotifications = input.disablePhoneOnlySMSNotifications; + } + // If no settings values have changed lets skip this update if (Object.keys(data).length === 0) return; diff --git a/packages/trpc/server/routers/viewer/organizations/update.schema.ts b/packages/trpc/server/routers/viewer/organizations/update.schema.ts index b541771bd7..48bb92153a 100644 --- a/packages/trpc/server/routers/viewer/organizations/update.schema.ts +++ b/packages/trpc/server/routers/viewer/organizations/update.schema.ts @@ -34,6 +34,7 @@ export const ZUpdateInputSchema = z.object({ adminGetsNoSlotsNotification: z.boolean().optional(), allowSEOIndexing: z.boolean().optional(), orgProfileRedirectsToVerifiedDomain: z.boolean().optional(), + disablePhoneOnlySMSNotifications: z.boolean().optional(), }); export type TUpdateInputSchema = z.infer;