Skip to content

Commit

Permalink
feat: booking with phone number (calcom#14461)
Browse files Browse the repository at this point in the history
Co-authored-by: Omar López <[email protected]>
Co-authored-by: Joe Au-Yeung <[email protected]>
Co-authored-by: Joe Au-Yeung <[email protected]>
  • Loading branch information
4 people authored Sep 12, 2024
1 parent 74a9f3c commit 4a59841
Show file tree
Hide file tree
Showing 85 changed files with 2,167 additions and 363 deletions.
42 changes: 25 additions & 17 deletions apps/web/components/booking/BookingListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import getPaymentAppData from "@calcom/lib/getPaymentAppData";
import { useCopy } from "@calcom/lib/hooks/useCopy";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useGetTheme } from "@calcom/lib/hooks/useTheme";
import isSmsCalEmail from "@calcom/lib/isSmsCalEmail";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
Expand Down Expand Up @@ -322,7 +323,7 @@ function BookingListItem(booking: BookingItemProps) {
const urlSearchParams = new URLSearchParams({
allRemainingBookings: isTabRecurring.toString(),
});
if (booking.attendees[0]) urlSearchParams.set("email", booking.attendees[0].email);
if (booking.attendees[0].email) urlSearchParams.set("email", booking.attendees[0].email);
return `/booking/${booking.uid}?${urlSearchParams.toString()}`;
};

Expand Down Expand Up @@ -356,6 +357,7 @@ function BookingListItem(booking: BookingItemProps) {
email: attendee.email,
id: attendee.id,
noShow: attendee.noShow || false,
phoneNumber: attendee.phoneNumber,
};
});
return (
Expand Down Expand Up @@ -730,6 +732,7 @@ const FirstAttendee = ({
type AttendeeProps = {
name?: string;
email: string;
phoneNumber: string | null;
id: number;
noShow: boolean;
};
Expand All @@ -740,7 +743,7 @@ type NoShowProps = {
};

const Attendee = (attendeeProps: AttendeeProps & NoShowProps) => {
const { email, name, bookingUid, isBookingInPast, noShow: noShowAttendee } = attendeeProps;
const { email, name, bookingUid, isBookingInPast, noShow: noShowAttendee, phoneNumber } = attendeeProps;
const { t } = useLocale();

const [noShow, setNoShow] = useState(noShowAttendee);
Expand Down Expand Up @@ -784,38 +787,43 @@ const Attendee = (attendeeProps: AttendeeProps & NoShowProps) => {
</button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem className="focus:outline-none">
<DropdownItem
StartIcon="mail"
href={`mailto:${email}`}
onClick={(e) => {
setOpenDropdown(false);
e.stopPropagation();
}}>
<a href={`mailto:${email}`}>{t("email")}</a>
</DropdownItem>
</DropdownMenuItem>
{!isSmsCalEmail(email) && (
<DropdownMenuItem className="focus:outline-none">
<DropdownItem
StartIcon="mail"
href={`mailto:${email}`}
onClick={(e) => {
setOpenDropdown(false);
e.stopPropagation();
}}>
<a href={`mailto:${email}`}>{t("email")}</a>
</DropdownItem>
</DropdownMenuItem>
)}

<DropdownMenuItem className="focus:outline-none">
<DropdownItem
StartIcon={isCopied ? "clipboard-check" : "clipboard"}
onClick={(e) => {
e.preventDefault();
copyToClipboard(email);
const isEmailCopied = isSmsCalEmail(email);
copyToClipboard(isEmailCopied ? email : phoneNumber ?? "");
setOpenDropdown(false);
showToast(t("email_copied"), "success");
showToast(isEmailCopied ? t("email_copied") : t("phone_number_copied"), "success");
}}>
{!isCopied ? t("copy") : t("copied")}
</DropdownItem>
</DropdownMenuItem>

{isBookingInPast && (
<DropdownMenuItem className="focus:outline-none">
{noShow ? (
<DropdownItem
data-testid="unmark-no-show"
onClick={(e) => {
e.preventDefault();
setOpenDropdown(false);
toggleNoShow({ attendee: { noShow: false, email }, bookingUid });
e.preventDefault();
}}
StartIcon="eye">
{t("unmark_as_no_show")}
Expand All @@ -824,9 +832,9 @@ const Attendee = (attendeeProps: AttendeeProps & NoShowProps) => {
<DropdownItem
data-testid="mark-no-show"
onClick={(e) => {
e.preventDefault();
setOpenDropdown(false);
toggleNoShow({ attendee: { noShow: true, email }, bookingUid });
e.preventDefault();
}}
StartIcon="eye-off">
{t("mark_as_no_show")}
Expand Down
19 changes: 15 additions & 4 deletions apps/web/lib/booking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export const getEventTypesFromDB = async (id: number) => {
bookingFields: true,
disableGuests: true,
timeZone: true,
profile: {
select: {
organizationId: true,
},
},
teamId: true,
owner: {
select: userSelect,
Expand Down Expand Up @@ -82,11 +87,13 @@ export const getEventTypesFromDB = async (id: number) => {
}

const metadata = EventTypeMetaDataSchema.parse(eventType.metadata);
const { profile, ...restEventType } = eventType;
const isOrgTeamEvent = !!eventType?.team && !!profile?.organizationId;

return {
isDynamic: false,
...eventType,
bookingFields: getBookingFieldsWithSystemFields(eventType),
...restEventType,
bookingFields: getBookingFieldsWithSystemFields({ ...eventType, isOrgTeamEvent }),
metadata,
};
};
Expand All @@ -101,7 +108,7 @@ export const handleSeatsEventTypeOnBooking = async (
bookingInfo: Partial<
Prisma.BookingGetPayload<{
include: {
attendees: { select: { name: true; email: true } };
attendees: { select: { name: true; email: true; phoneNumber: true } };
seatsReferences: { select: { referenceUid: true } };
user: {
select: {
Expand All @@ -123,6 +130,7 @@ export const handleSeatsEventTypeOnBooking = async (
attendee: {
email: string;
name: string;
phoneNumber: string | null;
};
id: number;
data: Prisma.JsonValue;
Expand All @@ -141,6 +149,7 @@ export const handleSeatsEventTypeOnBooking = async (
select: {
name: true,
email: true,
phoneNumber: true,
},
},
},
Expand All @@ -158,7 +167,9 @@ export const handleSeatsEventTypeOnBooking = async (
if (!eventType.seatsShowAttendees && !isHost) {
if (seatAttendee) {
const attendee = bookingInfo?.attendees?.find((a) => {
return a.email === seatAttendee?.attendee?.email;
return (
a.email === seatAttendee?.attendee?.email || a.phoneNumber === seatAttendee?.attendee?.phoneNumber
);
});
bookingInfo["attendees"] = attendee ? [attendee] : [];
} else {
Expand Down
12 changes: 10 additions & 2 deletions apps/web/modules/bookings/views/bookings-single-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import useTheme from "@calcom/lib/hooks/useTheme";
import isSmsCalEmail from "@calcom/lib/isSmsCalEmail";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
import { localStorage } from "@calcom/lib/webstorage";
Expand Down Expand Up @@ -144,7 +145,7 @@ export default function Success(props: PageProps) {

const attendees = bookingInfo?.attendees;

const isGmail = !!attendees.find((attendee) => attendee.email.includes("gmail.com"));
const isGmail = !!attendees.find((attendee) => attendee?.email?.includes("gmail.com"));

const [is24h, setIs24h] = useState(
props?.userTimeFormat ? props.userTimeFormat === 24 : isBrowserLocale24h()
Expand Down Expand Up @@ -551,7 +552,14 @@ export default function Success(props: PageProps) {
{attendee.name && (
<p data-testid={`attendee-name-${attendee.name}`}>{attendee.name}</p>
)}
<p data-testid={`attendee-email-${attendee.email}`}>{attendee.email}</p>
{attendee.phoneNumber && (
<p data-testid={`attendee-phone-${attendee.phoneNumber}`}>
{attendee.phoneNumber}
</p>
)}
{!isSmsCalEmail(attendee.email) && (
<p data-testid={`attendee-email-${attendee.email}`}>{attendee.email}</p>
)}
</div>
))}
</div>
Expand Down
8 changes: 7 additions & 1 deletion apps/web/playwright/lib/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,19 @@ export async function bookFirstEvent(page: Page) {
await bookEventOnThisPage(page);
}

export const bookTimeSlot = async (page: Page, opts?: { name?: string; email?: string; title?: string }) => {
export const bookTimeSlot = async (
page: Page,
opts?: { name?: string; email?: string; title?: string; attendeePhoneNumber?: string }
) => {
// --- fill form
await page.fill('[name="name"]', opts?.name ?? testName);
await page.fill('[name="email"]', opts?.email ?? testEmail);
if (opts?.title) {
await page.fill('[name="title"]', opts.title);
}
if (opts?.attendeePhoneNumber) {
await page.fill('[name="attendeePhoneNumber"]', opts.attendeePhoneNumber ?? "+918888888888");
}
await page.press('[name="email"]', "Enter");
};

Expand Down
Loading

0 comments on commit 4a59841

Please sign in to comment.