fix: remove kyc and make text messages to attendee org only (#11389)
* add orgs upgrade badge to select * don't allow updating to if no teams or orgs plan * make sure only paying users can request verification codes * add new action helper function * remove kyc code * code clean up * fix verify button UI * fix type errors * add eventTypeRequiresConfirmation everywhere * address feedback --------- Co-authored-by: CarinaWolli <wollencarina@gmail.com>pull/11473/head
parent
96263b0cf7
commit
3b50fe075d
|
@ -1,4 +0,0 @@
|
|||
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||
import { kycVerificationRouter } from "@calcom/trpc/server/routers/viewer/kycVerification/_router";
|
||||
|
||||
export default createNextApiHandler(kycVerificationRouter);
|
|
@ -1,11 +0,0 @@
|
|||
import PageWrapper from "@components/PageWrapper";
|
||||
import { getLayout } from "@components/auth/layouts/AdminLayout";
|
||||
|
||||
import KYCVerificationView from "./kycVerificationView";
|
||||
|
||||
const KYCVerificationPage = () => <KYCVerificationView />;
|
||||
|
||||
KYCVerificationPage.getLayout = getLayout;
|
||||
KYCVerificationPage.PageWrapper = PageWrapper;
|
||||
|
||||
export default KYCVerificationPage;
|
|
@ -1,80 +0,0 @@
|
|||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { Meta, Form, Button, TextField, showToast } from "@calcom/ui";
|
||||
|
||||
type FormValues = {
|
||||
name?: string | null;
|
||||
};
|
||||
|
||||
export default function KYCVerificationView() {
|
||||
const teamForm = useForm<FormValues>();
|
||||
const userForm = useForm<FormValues>();
|
||||
|
||||
const mutation = trpc.viewer.kycVerification.verify.useMutation({
|
||||
onSuccess: async (data) => {
|
||||
showToast(`Successfully verified ${data.isTeam ? "team" : "user"} ${data.name}`, "success");
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(`Verification failed: ${error.message}`, "error");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Meta
|
||||
title="KYC Verification"
|
||||
description="Here you can verify users and teams. This verification is needed for sending sms/whatsapp messages to attendees."
|
||||
/>
|
||||
<div>
|
||||
<div className="mb-2 font-medium">Verify Team</div>
|
||||
|
||||
<Form
|
||||
form={teamForm}
|
||||
handleSubmit={(values) => {
|
||||
mutation.mutate({
|
||||
name: values.name || "",
|
||||
isTeam: true,
|
||||
});
|
||||
}}>
|
||||
<div className="flex space-x-2">
|
||||
<TextField
|
||||
{...teamForm.register("name")}
|
||||
label=""
|
||||
type="text"
|
||||
id="name"
|
||||
placeholder="team slug"
|
||||
className="-mt-2 "
|
||||
required
|
||||
/>
|
||||
<Button type="submit">Verify</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-2 mt-6 font-medium">Verify User</div>
|
||||
<Form
|
||||
form={userForm}
|
||||
handleSubmit={(values) => {
|
||||
mutation.mutate({
|
||||
name: values.name || "",
|
||||
isTeam: false,
|
||||
});
|
||||
}}>
|
||||
<div className="flex space-x-2">
|
||||
<TextField
|
||||
{...userForm.register("name")}
|
||||
label=""
|
||||
type="text"
|
||||
id="name"
|
||||
placeholder="user name"
|
||||
className="-mt-2"
|
||||
required
|
||||
/>
|
||||
<Button type="submit">Verify</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1639,6 +1639,7 @@
|
|||
"minimum_round_robin_hosts_count": "Number of hosts required to attend",
|
||||
"hosts": "Hosts",
|
||||
"upgrade_to_enable_feature": "You need to create a team to enable this feature. Click to create a team.",
|
||||
"orgs_upgrade_to_enable_feature" : "You need to upgrade to our enterprise plan to enable this feature.",
|
||||
"new_attendee": "New Attendee",
|
||||
"awaiting_approval": "Awaiting Approval",
|
||||
"requires_google_calendar": "This app requires a Google Calendar connection",
|
||||
|
@ -2003,11 +2004,6 @@
|
|||
"requires_booker_email_verification": "Requires booker email verification",
|
||||
"description_requires_booker_email_verification": "To ensure booker's email verification before scheduling events",
|
||||
"requires_confirmation_mandatory": "Text messages can only be sent to attendees when event type requires confirmation.",
|
||||
"kyc_verification_information": "To ensure security, you have to verify your {{teamOrAccount}} before sending text messages to attendees. Please contact us at <a>{{supportEmail}}</a> and provide the following information:",
|
||||
"kyc_verification_documents": "<ul><li>Your {{teamOrUser}}</li><li>For businesses: Attach your Business Verification Documentation</li><li>For individuals: Attach a government-issued ID</li></ul>",
|
||||
"verify_team_or_account": "Verify {{teamOrAccount}}",
|
||||
"verify_account": "Verify Account",
|
||||
"kyc_verification": "KYC Verification",
|
||||
"organizations": "Organizations",
|
||||
"org_admin_other_teams": "Other teams",
|
||||
"org_admin_other_teams_description": "Here you can see teams inside your organization that you are not part of. You can add yourself to them if needed.",
|
||||
|
|
|
@ -9,7 +9,6 @@ import { deleteMeeting, updateMeeting } from "@calcom/core/videoClient";
|
|||
import dayjs from "@calcom/dayjs";
|
||||
import { sendCancelledEmails, sendCancelledSeatEmails } from "@calcom/emails";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { isEventTypeOwnerKYCVerified } from "@calcom/features/ee/workflows/lib/isEventTypeOwnerKYCVerified";
|
||||
import { deleteScheduledEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager";
|
||||
import { sendCancelledReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import { deleteScheduledSMSReminder } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
|
||||
|
@ -71,17 +70,6 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine
|
|||
select: {
|
||||
id: true,
|
||||
hideBranding: true,
|
||||
metadata: true,
|
||||
teams: {
|
||||
select: {
|
||||
accepted: true,
|
||||
team: {
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
teamId: true,
|
||||
|
@ -299,8 +287,6 @@ async function handler(req: CustomRequest) {
|
|||
);
|
||||
await Promise.all(promises);
|
||||
|
||||
const isKYCVerified = isEventTypeOwnerKYCVerified(bookingToDelete.eventType);
|
||||
|
||||
//Workflows - schedule reminders
|
||||
if (bookingToDelete.eventType?.workflows) {
|
||||
await sendCancelledReminders({
|
||||
|
@ -311,7 +297,7 @@ async function handler(req: CustomRequest) {
|
|||
...{ eventType: { slug: bookingToDelete.eventType.slug } },
|
||||
},
|
||||
hideBranding: !!bookingToDelete.eventType.owner?.hideBranding,
|
||||
isKYCVerified,
|
||||
eventTypeRequiresConfirmation: bookingToDelete.eventType.requiresConfirmation,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import type { Prisma, Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@pri
|
|||
import type { EventManagerUser } from "@calcom/core/EventManager";
|
||||
import EventManager from "@calcom/core/EventManager";
|
||||
import { sendScheduledEmails } from "@calcom/emails";
|
||||
import { isEventTypeOwnerKYCVerified } from "@calcom/features/ee/workflows/lib/isEventTypeOwnerKYCVerified";
|
||||
import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { scheduleTrigger } from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||
|
@ -87,18 +86,8 @@ export async function handleConfirmation(args: {
|
|||
eventType: {
|
||||
bookingFields: Prisma.JsonValue | null;
|
||||
slug: string;
|
||||
team: {
|
||||
metadata: Prisma.JsonValue;
|
||||
} | null;
|
||||
owner: {
|
||||
hideBranding?: boolean | null;
|
||||
metadata: Prisma.JsonValue;
|
||||
teams: {
|
||||
accepted: boolean;
|
||||
team: {
|
||||
metadata: Prisma.JsonValue;
|
||||
};
|
||||
}[];
|
||||
} | null;
|
||||
workflows: (WorkflowsOnEventTypes & {
|
||||
workflow: Workflow & {
|
||||
|
@ -135,25 +124,9 @@ export async function handleConfirmation(args: {
|
|||
select: {
|
||||
slug: true,
|
||||
bookingFields: true,
|
||||
team: {
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
owner: {
|
||||
select: {
|
||||
hideBranding: true,
|
||||
metadata: true,
|
||||
teams: {
|
||||
select: {
|
||||
accepted: true,
|
||||
team: {
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
workflows: {
|
||||
|
@ -202,25 +175,9 @@ export async function handleConfirmation(args: {
|
|||
select: {
|
||||
slug: true,
|
||||
bookingFields: true,
|
||||
team: {
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
owner: {
|
||||
select: {
|
||||
hideBranding: true,
|
||||
metadata: true,
|
||||
teams: {
|
||||
select: {
|
||||
accepted: true,
|
||||
team: {
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
workflows: {
|
||||
|
@ -250,8 +207,6 @@ export async function handleConfirmation(args: {
|
|||
updatedBookings.push(updatedBooking);
|
||||
}
|
||||
|
||||
const isKYCVerified = isEventTypeOwnerKYCVerified(updatedBookings[0].eventType);
|
||||
|
||||
//Workflows - set reminders for confirmed events
|
||||
try {
|
||||
for (let index = 0; index < updatedBookings.length; index++) {
|
||||
|
@ -276,7 +231,6 @@ export async function handleConfirmation(args: {
|
|||
isFirstRecurringEvent: isFirstBooking,
|
||||
hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding,
|
||||
eventTypeRequiresConfirmation: true,
|
||||
isKYCVerified,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ import {
|
|||
allowDisablingAttendeeConfirmationEmails,
|
||||
allowDisablingHostConfirmationEmails,
|
||||
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
|
||||
import { isEventTypeOwnerKYCVerified } from "@calcom/features/ee/workflows/lib/isEventTypeOwnerKYCVerified";
|
||||
import {
|
||||
cancelWorkflowReminders,
|
||||
scheduleWorkflowReminders,
|
||||
|
@ -260,7 +259,6 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
|
|||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
bookingFields: true,
|
||||
|
@ -292,17 +290,6 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
|
|||
owner: {
|
||||
select: {
|
||||
hideBranding: true,
|
||||
metadata: true,
|
||||
teams: {
|
||||
select: {
|
||||
accepted: true,
|
||||
team: {
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
workflows: {
|
||||
|
@ -379,7 +366,7 @@ async function ensureAvailableUsers(
|
|||
}
|
||||
) {
|
||||
const availableUsers: IsFixedAwareUser[] = [];
|
||||
const duration = dayjs(input.dateTo).diff(input.dateFrom, 'minute');
|
||||
const duration = dayjs(input.dateTo).diff(input.dateFrom, "minute");
|
||||
|
||||
const originalBookingDuration = input.originalRescheduledBooking
|
||||
? dayjs(input.originalRescheduledBooking.endTime).diff(
|
||||
|
@ -1199,8 +1186,6 @@ async function handler(
|
|||
|
||||
const subscribersMeetingEnded = await getWebhooks(subscriberOptionsMeetingEnded);
|
||||
|
||||
const isKYCVerified = isEventTypeOwnerKYCVerified(eventType);
|
||||
|
||||
const handleSeats = async () => {
|
||||
let resultBooking:
|
||||
| (Partial<Booking> & {
|
||||
|
@ -1769,7 +1754,7 @@ async function handler(
|
|||
isFirstRecurringEvent: true,
|
||||
emailAttendeeSendToOverride: bookerEmail,
|
||||
seatReferenceUid: evt.attendeeSeatId,
|
||||
isKYCVerified,
|
||||
eventTypeRequiresConfirmation: eventType.requiresConfirmation,
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("Error while scheduling workflow reminders", error);
|
||||
|
@ -2416,7 +2401,7 @@ async function handler(
|
|||
isFirstRecurringEvent: true,
|
||||
hideBranding: !!eventType.owner?.hideBranding,
|
||||
seatReferenceUid: evt.attendeeSeatId,
|
||||
isKYCVerified,
|
||||
eventTypeRequiresConfirmation: eventType.requiresConfirmation,
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("Error while scheduling workflow reminders", error);
|
||||
|
|
|
@ -39,7 +39,6 @@ interface IAddActionDialog {
|
|||
senderId?: string,
|
||||
senderName?: string
|
||||
) => void;
|
||||
setKYCVerificationDialogOpen: () => void;
|
||||
}
|
||||
|
||||
interface ISelectActionOption {
|
||||
|
@ -57,7 +56,7 @@ type AddActionFormValues = {
|
|||
|
||||
export const AddActionDialog = (props: IAddActionDialog) => {
|
||||
const { t } = useLocale();
|
||||
const { isOpenDialog, setIsOpenDialog, addAction, setKYCVerificationDialogOpen } = props;
|
||||
const { isOpenDialog, setIsOpenDialog, addAction } = props;
|
||||
const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState(false);
|
||||
const [isSenderIdNeeded, setIsSenderIdNeeded] = useState(false);
|
||||
const [isEmailAddressNeeded, setIsEmailAddressNeeded] = useState(false);
|
||||
|
@ -171,14 +170,13 @@ export const AddActionDialog = (props: IAddActionDialog) => {
|
|||
onChange={handleSelectAction}
|
||||
options={actionOptions.map((option) => ({
|
||||
...option,
|
||||
verificationAction: () => setKYCVerificationDialogOpen(),
|
||||
}))}
|
||||
isOptionDisabled={(option: {
|
||||
label: string;
|
||||
value: WorkflowActions;
|
||||
needsUpgrade: boolean;
|
||||
needsVerification: boolean;
|
||||
}) => option.needsUpgrade || option.needsVerification}
|
||||
needsTeamsUpgrade: boolean;
|
||||
needsOrgsUpgrade: boolean;
|
||||
}) => option.needsTeamsUpgrade || option.needsOrgsUpgrade}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
import { Trans } from "next-i18next";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
import { SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Dialog, DialogContent, DialogClose, DialogFooter } from "@calcom/ui";
|
||||
|
||||
export const KYCVerificationDialog = (props: {
|
||||
isOpenDialog: boolean;
|
||||
setIsOpenDialog: Dispatch<SetStateAction<boolean>>;
|
||||
isPartOfTeam: boolean;
|
||||
}) => {
|
||||
const { isOpenDialog, setIsOpenDialog, isPartOfTeam } = props;
|
||||
const { t } = useLocale();
|
||||
|
||||
const isTeamString = isPartOfTeam ? "team" : "";
|
||||
|
||||
return (
|
||||
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
|
||||
<DialogContent title={t("verify_team_or_account", { teamOrAccount: isTeamString || "account" })}>
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<Trans
|
||||
i18nKey="kyc_verification_information"
|
||||
components={{
|
||||
a: (
|
||||
<a
|
||||
href={`mailto:support@cal.com?subject=${
|
||||
isPartOfTeam ? "Team%20Verification" : "Account%20Verification"
|
||||
}`}
|
||||
style={{ color: "#3E3E3E" }}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
values={{
|
||||
supportEmail:
|
||||
SUPPORT_MAIL_ADDRESS === "help@cal.com" ? "support@cal.com" : SUPPORT_MAIL_ADDRESS,
|
||||
teamOrAccount: isTeamString || "account",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Trans
|
||||
i18nKey="kyc_verification_documents"
|
||||
components={{ li: <li />, ul: <ul className="ml-8 list-disc" /> }}
|
||||
values={{ teamOrUser: isPartOfTeam ? "team URL" : "user name" }}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose />
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
|
@ -5,7 +5,6 @@ import type { UseFormReturn } from "react-hook-form";
|
|||
import { Controller } from "react-hook-form";
|
||||
|
||||
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
|
||||
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { WorkflowTemplates } from "@calcom/prisma/enums";
|
||||
import type { WorkflowActions } from "@calcom/prisma/enums";
|
||||
|
@ -19,7 +18,6 @@ import { isSMSAction, isWhatsappAction } from "../lib/actionHelperFunctions";
|
|||
import type { FormValues } from "../pages/workflow";
|
||||
import { AddActionDialog } from "./AddActionDialog";
|
||||
import { DeleteDialog } from "./DeleteDialog";
|
||||
import { KYCVerificationDialog } from "./KYCVerificationDialog";
|
||||
import WorkflowStepContainer from "./WorkflowStepContainer";
|
||||
|
||||
type User = RouterOutputs["viewer"]["me"];
|
||||
|
@ -41,15 +39,12 @@ export default function WorkflowDetailsPage(props: Props) {
|
|||
const router = useRouter();
|
||||
|
||||
const [isAddActionDialogOpen, setIsAddActionDialogOpen] = useState(false);
|
||||
const [isKYCVerificationDialogOpen, setKYCVerificationDialogOpen] = useState(false);
|
||||
|
||||
const [reload, setReload] = useState(false);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
|
||||
const { data, isLoading } = trpc.viewer.eventTypes.getByViewer.useQuery();
|
||||
|
||||
const isPartOfTeam = useHasTeamPlan();
|
||||
|
||||
const eventTypeOptions = useMemo(
|
||||
() =>
|
||||
data?.eventTypeGroups.reduce((options, group) => {
|
||||
|
@ -177,7 +172,6 @@ export default function WorkflowDetailsPage(props: Props) {
|
|||
user={props.user}
|
||||
teamId={teamId}
|
||||
readOnly={props.readOnly}
|
||||
setKYCVerificationDialogOpen={setKYCVerificationDialogOpen}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -194,7 +188,6 @@ export default function WorkflowDetailsPage(props: Props) {
|
|||
setReload={setReload}
|
||||
teamId={teamId}
|
||||
readOnly={props.readOnly}
|
||||
setKYCVerificationDialogOpen={setKYCVerificationDialogOpen}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -222,12 +215,6 @@ export default function WorkflowDetailsPage(props: Props) {
|
|||
isOpenDialog={isAddActionDialogOpen}
|
||||
setIsOpenDialog={setIsAddActionDialogOpen}
|
||||
addAction={addAction}
|
||||
setKYCVerificationDialogOpen={() => setKYCVerificationDialogOpen(true)}
|
||||
/>
|
||||
<KYCVerificationDialog
|
||||
isOpenDialog={isKYCVerificationDialogOpen}
|
||||
setIsOpenDialog={setKYCVerificationDialogOpen}
|
||||
isPartOfTeam={!!isPartOfTeam.hasTeamPlan}
|
||||
/>
|
||||
<DeleteDialog
|
||||
isOpenDialog={deleteDialogOpen}
|
||||
|
|
|
@ -67,7 +67,6 @@ type WorkflowStepProps = {
|
|||
setReload?: Dispatch<SetStateAction<boolean>>;
|
||||
teamId?: number;
|
||||
readOnly: boolean;
|
||||
setKYCVerificationDialogOpen: Dispatch<SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export default function WorkflowStepContainer(props: WorkflowStepProps) {
|
||||
|
@ -331,15 +330,13 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
|
|||
}
|
||||
|
||||
if (step && step.action) {
|
||||
const templateValue = form.watch(`steps.${step.stepNumber - 1}.template`);
|
||||
const actionString = t(`${step.action.toLowerCase()}_action`);
|
||||
|
||||
const selectedAction = {
|
||||
label: actionString.charAt(0).toUpperCase() + actionString.slice(1),
|
||||
value: step.action,
|
||||
needsUpgrade: false,
|
||||
needsVerification: false,
|
||||
verificationAction: () => props.setKYCVerificationDialogOpen(true),
|
||||
needsTeamsUpgrade: false,
|
||||
needsOrgsUpgrade: false,
|
||||
};
|
||||
|
||||
const selectedTemplate = { label: t(`${step.template.toLowerCase()}`), value: step.template };
|
||||
|
@ -530,14 +527,13 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
|
|||
defaultValue={selectedAction}
|
||||
options={actionOptions?.map((option) => ({
|
||||
...option,
|
||||
verificationAction: () => props.setKYCVerificationDialogOpen(true),
|
||||
}))}
|
||||
isOptionDisabled={(option: {
|
||||
label: string;
|
||||
value: WorkflowActions;
|
||||
needsUpgrade: boolean;
|
||||
needsVerification: boolean;
|
||||
}) => option.needsUpgrade || option.needsVerification}
|
||||
needsTeamsUpgrade: boolean;
|
||||
needsOrgsUpgrade: boolean;
|
||||
}) => option.needsTeamsUpgrade || option.needsOrgsUpgrade}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
@ -617,7 +613,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
|
|||
/>
|
||||
<Button
|
||||
color="secondary"
|
||||
className="-ml-[3px] h-[38px] min-w-fit sm:block sm:rounded-bl-none sm:rounded-tl-none "
|
||||
className="-ml-[3px] h-[36px] min-w-fit py-0 sm:block sm:rounded-bl-none sm:rounded-tl-none "
|
||||
disabled={verifyPhoneNumberMutation.isLoading || props.readOnly}
|
||||
onClick={() => {
|
||||
verifyPhoneNumberMutation.mutate({
|
||||
|
|
|
@ -41,6 +41,9 @@ export function isAttendeeAction(action: WorkflowActions) {
|
|||
export function isTextMessageToAttendeeAction(action?: WorkflowActions) {
|
||||
return action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.WHATSAPP_ATTENDEE;
|
||||
}
|
||||
export function isTextMessageToSpecificNumber(action?: WorkflowActions) {
|
||||
return action === WorkflowActions.SMS_NUMBER || action === WorkflowActions.WHATSAPP_NUMBER;
|
||||
}
|
||||
|
||||
export function getWhatsappTemplateForTrigger(trigger: WorkflowTriggerEvents): WorkflowTemplates {
|
||||
switch (trigger) {
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
WORKFLOW_TRIGGER_EVENTS,
|
||||
} from "./constants";
|
||||
|
||||
export function getWorkflowActionOptions(t: TFunction, isTeamsPlan?: boolean, isKYCVerified?: boolean) {
|
||||
export function getWorkflowActionOptions(t: TFunction, isTeamsPlan?: boolean, isOrgsPlan?: boolean) {
|
||||
return WORKFLOW_ACTIONS.filter((action) => action !== WorkflowActions.EMAIL_ADDRESS) //removing EMAIL_ADDRESS for now due to abuse episode
|
||||
.map((action) => {
|
||||
const actionString = t(`${action.toLowerCase()}_action`);
|
||||
|
@ -23,8 +23,9 @@ export function getWorkflowActionOptions(t: TFunction, isTeamsPlan?: boolean, is
|
|||
return {
|
||||
label: actionString.charAt(0).toUpperCase() + actionString.slice(1),
|
||||
value: action,
|
||||
needsUpgrade: isSMSOrWhatsappAction(action) && !isTeamsPlan,
|
||||
needsVerification: isTextMessageToAttendeeAction(action) && !isKYCVerified,
|
||||
needsTeamsUpgrade:
|
||||
isSMSOrWhatsappAction(action) && !isTextMessageToAttendeeAction(action) && !isTeamsPlan,
|
||||
needsOrgsUpgrade: isTextMessageToAttendeeAction(action) && !isOrgsPlan,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
type EventTypeOwnerType = {
|
||||
team?: {
|
||||
metadata: Prisma.JsonValue;
|
||||
} | null;
|
||||
owner?: {
|
||||
metadata: Prisma.JsonValue;
|
||||
teams: {
|
||||
accepted: boolean;
|
||||
team: {
|
||||
metadata: Prisma.JsonValue;
|
||||
};
|
||||
}[];
|
||||
} | null;
|
||||
};
|
||||
|
||||
export function isEventTypeOwnerKYCVerified(eventType?: EventTypeOwnerType | null) {
|
||||
if (!eventType) return false;
|
||||
|
||||
if (eventType.team) {
|
||||
const isKYCVerified =
|
||||
eventType.team &&
|
||||
hasKeyInMetadata(eventType.team, "kycVerified") &&
|
||||
!!eventType.team.metadata.kycVerified;
|
||||
return isKYCVerified;
|
||||
}
|
||||
|
||||
if (eventType.owner) {
|
||||
const isKYCVerified =
|
||||
eventType.owner &&
|
||||
hasKeyInMetadata(eventType.owner, "kycVerified") &&
|
||||
!!eventType.owner.metadata.kycVerified;
|
||||
if (isKYCVerified) return isKYCVerified;
|
||||
|
||||
const isPartOfVerifiedTeam = eventType.owner.teams.find(
|
||||
(team) =>
|
||||
team.accepted && hasKeyInMetadata(team.team, "kycVerified") && !!team.team.metadata.kycVerified
|
||||
);
|
||||
return !!isPartOfVerifiedTeam;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -23,7 +23,6 @@ type ProcessWorkflowStepParams = {
|
|||
emailAttendeeSendToOverride?: string;
|
||||
hideBranding?: boolean;
|
||||
seatReferenceUid?: string;
|
||||
isKYCVerified: boolean;
|
||||
eventTypeRequiresConfirmation?: boolean;
|
||||
};
|
||||
|
||||
|
@ -48,11 +47,9 @@ const processWorkflowStep = async (
|
|||
hideBranding,
|
||||
seatReferenceUid,
|
||||
eventTypeRequiresConfirmation,
|
||||
isKYCVerified,
|
||||
}: ProcessWorkflowStepParams
|
||||
) => {
|
||||
if (isTextMessageToAttendeeAction(step.action) && (!isKYCVerified || !eventTypeRequiresConfirmation))
|
||||
return;
|
||||
if (isTextMessageToAttendeeAction(step.action) && !eventTypeRequiresConfirmation) return;
|
||||
|
||||
if (step.action === WorkflowActions.SMS_ATTENDEE || step.action === WorkflowActions.SMS_NUMBER) {
|
||||
const sendTo = step.action === WorkflowActions.SMS_ATTENDEE ? smsReminderNumber : step.sendTo;
|
||||
|
@ -143,7 +140,6 @@ export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersA
|
|||
hideBranding,
|
||||
seatReferenceUid,
|
||||
eventTypeRequiresConfirmation = false,
|
||||
isKYCVerified,
|
||||
} = args;
|
||||
if (isNotConfirmed || !workflows.length) return;
|
||||
|
||||
|
@ -177,7 +173,6 @@ export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersA
|
|||
hideBranding,
|
||||
seatReferenceUid,
|
||||
eventTypeRequiresConfirmation,
|
||||
isKYCVerified,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -208,13 +203,11 @@ export interface SendCancelledRemindersArgs {
|
|||
smsReminderNumber: string | null;
|
||||
evt: ExtendedCalendarEvent;
|
||||
hideBranding?: boolean;
|
||||
isKYCVerified: boolean;
|
||||
eventTypeRequiresConfirmation?: boolean;
|
||||
}
|
||||
|
||||
export const sendCancelledReminders = async (args: SendCancelledRemindersArgs) => {
|
||||
const { workflows, smsReminderNumber, evt, hideBranding, isKYCVerified, eventTypeRequiresConfirmation } =
|
||||
args;
|
||||
const { workflows, smsReminderNumber, evt, hideBranding, eventTypeRequiresConfirmation } = args;
|
||||
if (!workflows.length) return;
|
||||
|
||||
for (const workflowRef of workflows) {
|
||||
|
@ -228,7 +221,6 @@ export const sendCancelledReminders = async (args: SendCancelledRemindersArgs) =
|
|||
hideBranding,
|
||||
calendarEvent: evt,
|
||||
eventTypeRequiresConfirmation,
|
||||
isKYCVerified,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,6 @@ const tabs: VerticalTabItemProps[] = [
|
|||
{ name: "apps", href: "/settings/admin/apps/calendar" },
|
||||
{ name: "users", href: "/settings/admin/users" },
|
||||
{ name: "organizations", href: "/settings/admin/organizations" },
|
||||
{ name: "kyc_verification", href: "/settings/admin/kycVerification" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -41,7 +41,6 @@ const ENDPOINTS = [
|
|||
"workflows",
|
||||
"appsRouter",
|
||||
"googleWorkspace",
|
||||
"kycVerification",
|
||||
] as const;
|
||||
export type Endpoint = (typeof ENDPOINTS)[number];
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import { bookingsRouter } from "./bookings/_router";
|
|||
import { deploymentSetupRouter } from "./deploymentSetup/_router";
|
||||
import { eventTypesRouter } from "./eventTypes/_router";
|
||||
import { googleWorkspaceRouter } from "./googleWorkspace/_router";
|
||||
import { kycVerificationRouter } from "./kycVerification/_router";
|
||||
import { viewerOrganizationsRouter } from "./organizations/_router";
|
||||
import { paymentsRouter } from "./payments/_router";
|
||||
import { slotsRouter } from "./slots/_router";
|
||||
|
@ -53,6 +52,5 @@ export const viewerRouter = mergeRouters(
|
|||
users: userAdminRouter,
|
||||
googleWorkspace: googleWorkspaceRouter,
|
||||
admin: adminRouter,
|
||||
kycVerification: kycVerificationRouter,
|
||||
})
|
||||
);
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import { authedAdminProcedure } from "../../../procedures/authedProcedure";
|
||||
import { router } from "../../../trpc";
|
||||
import { ZVerifyInputSchema } from "./verify.schema";
|
||||
|
||||
type KYCVerificationRouterHandlerCache = {
|
||||
isVerified?: typeof import("./isVerified.handler").isVerifiedHandler;
|
||||
verify?: typeof import("./verify.handler").verifyHandler;
|
||||
};
|
||||
|
||||
const UNSTABLE_HANDLER_CACHE: KYCVerificationRouterHandlerCache = {};
|
||||
|
||||
export const kycVerificationRouter = router({
|
||||
isVerified: authedAdminProcedure.query(async ({ ctx }) => {
|
||||
if (!UNSTABLE_HANDLER_CACHE.isVerified) {
|
||||
UNSTABLE_HANDLER_CACHE.isVerified = await import("./isVerified.handler").then(
|
||||
(mod) => mod.isVerifiedHandler
|
||||
);
|
||||
}
|
||||
// Unreachable code but required for type safety
|
||||
if (!UNSTABLE_HANDLER_CACHE.isVerified) {
|
||||
throw new Error("Failed to load handler");
|
||||
}
|
||||
|
||||
return UNSTABLE_HANDLER_CACHE.isVerified({
|
||||
ctx,
|
||||
});
|
||||
}),
|
||||
verify: authedAdminProcedure.input(ZVerifyInputSchema).mutation(async ({ ctx, input }) => {
|
||||
if (!UNSTABLE_HANDLER_CACHE.verify) {
|
||||
UNSTABLE_HANDLER_CACHE.verify = await import("./verify.handler").then((mod) => mod.verifyHandler);
|
||||
}
|
||||
// Unreachable code but required for type safety
|
||||
if (!UNSTABLE_HANDLER_CACHE.verify) {
|
||||
throw new Error("Failed to load handler");
|
||||
}
|
||||
|
||||
return UNSTABLE_HANDLER_CACHE.verify({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
}),
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
type IsKYCVerifiedOptions = {
|
||||
ctx: {
|
||||
user: NonNullable<TrpcSessionUser>;
|
||||
};
|
||||
};
|
||||
|
||||
export const isVerifiedHandler = async ({ ctx }: IsKYCVerifiedOptions) => {
|
||||
const user = ctx.user;
|
||||
|
||||
const memberships = await prisma.membership.findMany({
|
||||
where: {
|
||||
accepted: true,
|
||||
userId: user.id,
|
||||
team: {
|
||||
slug: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let isKYCVerified = user && hasKeyInMetadata(user, "kycVerified") ? !!user.metadata.kycVerified : false;
|
||||
|
||||
if (!isKYCVerified) {
|
||||
//check if user is part of a team that is KYC verified
|
||||
isKYCVerified = !!memberships.find(
|
||||
(membership) =>
|
||||
hasKeyInMetadata(membership.team, "kycVerified") && !!membership.team.metadata.kycVerified
|
||||
);
|
||||
}
|
||||
|
||||
return { isKYCVerified };
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
export {};
|
|
@ -1,76 +0,0 @@
|
|||
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import type { TVerifyInputSchema } from "./verify.schema";
|
||||
|
||||
type VerifyOptions = {
|
||||
ctx: {
|
||||
user: NonNullable<TrpcSessionUser>;
|
||||
};
|
||||
input: TVerifyInputSchema;
|
||||
};
|
||||
|
||||
export const verifyHandler = async ({ ctx, input }: VerifyOptions) => {
|
||||
const { name, isTeam } = input;
|
||||
if (isTeam) {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
slug: name,
|
||||
},
|
||||
});
|
||||
if (!team) {
|
||||
throw new Error("Team not found");
|
||||
}
|
||||
|
||||
if (hasKeyInMetadata(team, "kycVerified") && !!team.metadata.kycVerified) {
|
||||
throw new Error("Team already verified");
|
||||
}
|
||||
|
||||
const metadata = typeof team.metadata === "object" ? team.metadata : {};
|
||||
const updatedMetadata = { ...metadata, kycVerified: true };
|
||||
|
||||
const updatedTeam = await prisma.team.update({
|
||||
where: {
|
||||
id: team.id,
|
||||
},
|
||||
data: {
|
||||
metadata: updatedMetadata,
|
||||
},
|
||||
});
|
||||
return {
|
||||
name: updatedTeam.slug,
|
||||
isTeam: true,
|
||||
};
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username: name,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found");
|
||||
}
|
||||
|
||||
if (hasKeyInMetadata(user, "kycVerified") && !!user.metadata.kycVerified) {
|
||||
throw new Error("User already verified");
|
||||
}
|
||||
|
||||
const metadata = typeof user.metadata === "object" ? user.metadata : {};
|
||||
const updatedMetadata = { ...metadata, kycVerified: true };
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
metadata: updatedMetadata,
|
||||
},
|
||||
});
|
||||
return {
|
||||
name: updatedUser.username,
|
||||
isTeam: false,
|
||||
};
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const ZVerifyInputSchema = z.object({
|
||||
name: z.string(),
|
||||
isTeam: z.boolean(),
|
||||
});
|
||||
|
||||
export type TVerifyInputSchema = z.infer<typeof ZVerifyInputSchema>;
|
|
@ -4,7 +4,6 @@ import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
|
|||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { isVerifiedHandler } from "../kycVerification/isVerified.handler";
|
||||
import { hasTeamPlanHandler } from "../teams/hasTeamPlan.handler";
|
||||
|
||||
type GetWorkflowActionOptionsOptions = {
|
||||
|
@ -27,12 +26,12 @@ export const getWorkflowActionOptionsHandler = async ({ ctx }: GetWorkflowAction
|
|||
isTeamsPlan = !!hasTeamPlan;
|
||||
}
|
||||
|
||||
const { isKYCVerified } = await isVerifiedHandler({ ctx });
|
||||
const hasOrgsPlan = !!user.organizationId;
|
||||
|
||||
const t = await getTranslation(ctx.user.locale, "common");
|
||||
return getWorkflowActionOptions(
|
||||
t,
|
||||
IS_SELF_HOSTED || isCurrentUsernamePremium || isTeamsPlan,
|
||||
isKYCVerified
|
||||
IS_SELF_HOSTED || hasOrgsPlan
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { sendVerificationCode } from "@calcom/features/ee/workflows/lib/reminders/verifyPhoneNumber";
|
||||
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { hasTeamPlanHandler } from "../teams/hasTeamPlan.handler";
|
||||
import type { TSendVerificationCodeInputSchema } from "./sendVerificationCode.schema";
|
||||
|
||||
type SendVerificationCodeOptions = {
|
||||
|
@ -10,7 +14,22 @@ type SendVerificationCodeOptions = {
|
|||
input: TSendVerificationCodeInputSchema;
|
||||
};
|
||||
|
||||
export const sendVerificationCodeHandler = async ({ ctx: _ctx, input }: SendVerificationCodeOptions) => {
|
||||
export const sendVerificationCodeHandler = async ({ ctx, input }: SendVerificationCodeOptions) => {
|
||||
const { user } = ctx;
|
||||
|
||||
const isCurrentUsernamePremium =
|
||||
user && hasKeyInMetadata(user, "isPremium") ? !!user.metadata.isPremium : false;
|
||||
|
||||
let isTeamsPlan = false;
|
||||
if (!isCurrentUsernamePremium) {
|
||||
const { hasTeamPlan } = await hasTeamPlanHandler({ ctx });
|
||||
isTeamsPlan = !!hasTeamPlan;
|
||||
}
|
||||
|
||||
if (!isCurrentUsernamePremium && !isTeamsPlan) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
const { phoneNumber } = input;
|
||||
return sendVerificationCode(phoneNumber);
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ import type { Prisma } from "@prisma/client";
|
|||
import {
|
||||
isSMSOrWhatsappAction,
|
||||
isTextMessageToAttendeeAction,
|
||||
isTextMessageToSpecificNumber,
|
||||
} from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
|
||||
import {
|
||||
deleteScheduledEmailReminder,
|
||||
|
@ -25,7 +26,6 @@ import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
|||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { isVerifiedHandler } from "../kycVerification/isVerified.handler";
|
||||
import { hasTeamPlanHandler } from "../teams/hasTeamPlan.handler";
|
||||
import type { TUpdateInputSchema } from "./update.schema";
|
||||
import {
|
||||
|
@ -84,8 +84,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
|||
}
|
||||
const hasPaidPlan = IS_SELF_HOSTED || isCurrentUsernamePremium || isTeamsPlan;
|
||||
|
||||
const kycVerified = await isVerifiedHandler({ ctx });
|
||||
const isKYCVerified = kycVerified.isKYCVerified;
|
||||
const hasOrgsPlan = IS_SELF_HOSTED || ctx.user.organizationId;
|
||||
|
||||
const activeOnEventTypes = await ctx.prisma.eventType.findMany({
|
||||
where: {
|
||||
|
@ -425,11 +424,21 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
|||
|
||||
//step was edited
|
||||
} else if (JSON.stringify(oldStep) !== JSON.stringify(newStep)) {
|
||||
if (!hasPaidPlan && !isSMSOrWhatsappAction(oldStep.action) && isSMSOrWhatsappAction(newStep.action)) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
// check if step that require team plan already existed before
|
||||
if (
|
||||
!hasPaidPlan &&
|
||||
!isTextMessageToSpecificNumber(oldStep.action) &&
|
||||
isTextMessageToSpecificNumber(newStep.action)
|
||||
) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Not available on free plan" });
|
||||
}
|
||||
if (!isKYCVerified && isTextMessageToAttendeeAction(newStep.action)) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Account needs to be verified" });
|
||||
// check if step that require org already existed before
|
||||
if (
|
||||
!hasOrgsPlan &&
|
||||
!isTextMessageToAttendeeAction(oldStep.action) &&
|
||||
isTextMessageToAttendeeAction(newStep.action)
|
||||
) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Enterprise plan required" });
|
||||
}
|
||||
const requiresSender =
|
||||
newStep.action === WorkflowActions.SMS_NUMBER || newStep.action === WorkflowActions.WHATSAPP_NUMBER;
|
||||
|
@ -605,11 +614,11 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
|||
//added steps
|
||||
const addedSteps = steps.map((s) => {
|
||||
if (s.id <= 0) {
|
||||
if (isSMSOrWhatsappAction(s.action) && !hasPaidPlan) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
if (isSMSOrWhatsappAction(s.action) && !isTextMessageToAttendeeAction(s.action) && !hasPaidPlan) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Not available on free plan" });
|
||||
}
|
||||
if (!isKYCVerified && isTextMessageToAttendeeAction(s.action)) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Account needs to be verified" });
|
||||
if (!hasOrgsPlan && isTextMessageToAttendeeAction(s.action)) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Enterprise plan require" });
|
||||
}
|
||||
const { id: _stepId, ...stepToAdd } = s;
|
||||
return stepToAdd;
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { Badge } from "./Badge";
|
||||
|
||||
export const KYCVerificationBadge = function KYCVerificationBadge(props: { verifyTeamAction?: () => void }) {
|
||||
const { verifyTeamAction } = props;
|
||||
const { t } = useLocale();
|
||||
if (!verifyTeamAction) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip content={t("verify_team_tooltip")}>
|
||||
<Badge variant="gray" onClick={() => verifyTeamAction()}>
|
||||
{t("verify_account")}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { Badge } from "./Badge";
|
||||
|
||||
export const UpgradeOrgsBadge = function UpgradeOrgsBadge() {
|
||||
const { t } = useLocale();
|
||||
|
||||
return (
|
||||
<Tooltip content={t("orgs_upgrade_to_enable_feature")}>
|
||||
<a href="https://cal.com/enterprise" target="_blank">
|
||||
<Badge variant="gray">{t("upgrade")}</Badge>
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
export { Badge } from "./Badge";
|
||||
export { UpgradeTeamsBadge } from "./UpgradeTeamsBadge";
|
||||
export { KYCVerificationBadge } from "./KYCVerificationBadge";
|
||||
export { UpgradeOrgsBadge } from "./UpgradeOrgsBadge";
|
||||
|
||||
export type { BadgeProps } from "./Badge";
|
||||
|
|
|
@ -3,7 +3,7 @@ import { components as reactSelectComponents } from "react-select";
|
|||
|
||||
import { classNames } from "@calcom/lib";
|
||||
|
||||
import { UpgradeTeamsBadge, KYCVerificationBadge } from "../../badge";
|
||||
import { UpgradeTeamsBadge, UpgradeOrgsBadge } from "../../badge";
|
||||
import { Check } from "../../icon";
|
||||
|
||||
export const InputComponent = <
|
||||
|
@ -29,9 +29,8 @@ export const InputComponent = <
|
|||
type ExtendedOption = {
|
||||
value: string | number;
|
||||
label: string;
|
||||
needsUpgrade?: boolean;
|
||||
needsVerification?: boolean;
|
||||
verificationAction?: () => void;
|
||||
needsTeamsUpgrade?: boolean;
|
||||
needsOrgsUpgrade?: boolean;
|
||||
};
|
||||
|
||||
export const OptionComponent = <
|
||||
|
@ -48,12 +47,10 @@ export const OptionComponent = <
|
|||
<span className="mr-auto" data-testid={`select-option-${(props as unknown as ExtendedOption).value}`}>
|
||||
{props.label || <> </>}
|
||||
</span>
|
||||
{(props.data as unknown as ExtendedOption).needsUpgrade ? (
|
||||
{(props.data as unknown as ExtendedOption).needsTeamsUpgrade ? (
|
||||
<UpgradeTeamsBadge />
|
||||
) : (props.data as unknown as ExtendedOption).needsVerification ? (
|
||||
<KYCVerificationBadge
|
||||
verifyTeamAction={(props.data as unknown as ExtendedOption).verificationAction}
|
||||
/>
|
||||
) : (props.data as unknown as ExtendedOption).needsOrgsUpgrade ? (
|
||||
<UpgradeOrgsBadge />
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
|
Loading…
Reference in New Issue