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>
This commit is contained in:
Udit Takkar
2025-05-01 18:18:52 +00:00
committed by GitHub
co-authored by Syed Ali Shahbaz
parent 99dc5837f5
commit d9b3f8657f
9 changed files with 88 additions and 10 deletions
@@ -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}}",
@@ -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 (
<>
<SettingsToggle
toggleSwitchAtTheEnd={true}
title={t("organization_disable_phone_only_sms_notifications_switch_title")}
disabled={mutation?.isPending}
description={t("organization_disable_phone_only_sms_notifications_switch_description")}
checked={disablePhoneOnlySMSNotificationsActive}
onCheckedChange={(checked) => {
mutation.mutate({
disablePhoneOnlySMSNotifications: checked,
});
setDisablePhoneOnlySMSNotificationsActive(checked);
}}
switchContainerClassName="mt-6"
/>
</>
);
};
@@ -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 = () => {
<LockEventTypeSwitch currentOrg={currentOrg} isAdminOrOwner={!!isAdminOrOwner} />
<NoSlotsNotificationSwitch currentOrg={currentOrg} isAdminOrOwner={!!isAdminOrOwner} />
<DisablePhoneOnlySMSNotificationsSwitch currentOrg={currentOrg} isAdminOrOwner={!!isAdminOrOwner} />
</LicenseRequired>
);
};
@@ -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,
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "OrganizationSettings" ADD COLUMN "disablePhoneOnlySMSNotifications" BOOLEAN NOT NULL DEFAULT false;
+11 -9
View File
@@ -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 {
+10 -1
View File
@@ -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);
@@ -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;
@@ -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<typeof ZUpdateInputSchema>;