Users Phone Number Option (#2669)

* Users Phone Number Option

* Implemented improvments

* Add validation to form

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
sean-brydon 2022-05-16 16:50:12 +01:00 committed by GitHub
parent 3c37c2e774
commit 7fd149fd2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 72 additions and 6 deletions

View File

@ -66,6 +66,7 @@ type BookingFormValues = {
locationType?: LocationType;
guests?: string[];
phone?: string;
hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers
customInputs?: {
[key: string]: string;
};
@ -193,7 +194,7 @@ const BookingPage = ({
const eventTypeDetail = { isWeb3Active: false, ...eventType };
type Location = { type: LocationType; address?: string; link?: string };
type Location = { type: LocationType; address?: string; link?: string; hostPhoneNumber?: string };
// it would be nice if Prisma at some point in the future allowed for Json<Location>; as of now this is not the case.
const locations: Location[] = useMemo(
() => (eventType.locations as Location[]) || [],
@ -268,7 +269,9 @@ const BookingPage = ({
})(),
});
const getLocationValue = (booking: Pick<BookingFormValues, "locationType" | "phone">) => {
const getLocationValue = (
booking: Pick<BookingFormValues, "locationType" | "phone" | "hostPhoneNumber">
) => {
const { locationType } = booking;
switch (locationType) {
case LocationType.Phone: {
@ -280,6 +283,9 @@ const BookingPage = ({
case LocationType.Link: {
return locationInfo(locationType)?.link || "";
}
case LocationType.UserPhone: {
return locationInfo(locationType)?.hostPhoneNumber || "";
}
// Catches all other location types, such as Google Meet, Zoom etc.
default:
return selectedLocation || "";

View File

@ -74,6 +74,7 @@
"ical.js": "^1.4.0",
"ics": "^2.31.0",
"jimp": "^0.16.1",
"libphonenumber-js": "^1.9.53",
"lodash": "^4.17.21",
"micro": "^9.3.4",
"mime-types": "^2.1.35",

View File

@ -21,6 +21,7 @@ import classNames from "classnames";
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import { isValidPhoneNumber, parsePhoneNumber } from "libphonenumber-js";
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
@ -70,6 +71,7 @@ import CheckboxField from "@components/ui/form/CheckboxField";
import CheckedSelect from "@components/ui/form/CheckedSelect";
import { DateRangePicker } from "@components/ui/form/DateRangePicker";
import MinutesField from "@components/ui/form/MinutesField";
import PhoneInput from "@components/ui/form/PhoneInput";
import Select from "@components/ui/form/Select";
import * as RadioArea from "@components/ui/form/radio-area";
import WebhookListContainer from "@components/webhook/WebhookListContainer";
@ -422,6 +424,34 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
</div>
</div>
);
case LocationType.UserPhone:
return (
<div>
<label htmlFor="phonenumber" className="block text-sm font-medium text-gray-700">
{t("set_your_phone_number")}
</label>
<div className="mt-1">
<PhoneInput
control={locationFormMethods.control}
name="locationPhoneNumber"
required
id="locationPhoneNumber"
placeholder={t("host_phone_number")}
rules={{}}
defaultValue={
formMethods
.getValues("locations")
.find((location) => location.type === LocationType.UserPhone)?.hostPhoneNumber
}
/>
{locationFormMethods.formState.errors.locationPhoneNumber && (
<p className="mt-1 text-red-500">
{locationFormMethods.formState.errors.locationPhoneNumber.message}
</p>
)}
</div>
</div>
);
case LocationType.Phone:
return <p className="text-sm">{t("cal_invitee_phone_number_scheduling")}</p>;
/* TODO: Render this dynamically from App Store */
@ -509,7 +539,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
hidden: boolean;
hideCalendarNotes: boolean;
hashedLink: string | undefined;
locations: { type: LocationType; address?: string; link?: string }[];
locations: { type: LocationType; address?: string; link?: string; hostPhoneNumber?: string }[];
customInputs: EventTypeCustomInput[];
users: string[];
schedule: number;
@ -542,11 +572,16 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
const locationFormSchema = z.object({
locationType: z.string(),
locationAddress: z.string().optional(),
locationPhoneNumber: z
.string()
.refine((val) => isValidPhoneNumber(val))
.optional(),
locationLink: z.string().url().optional(), // URL validates as new URL() - which requires HTTPS:// In the input field
});
const locationFormMethods = useForm<{
locationType: LocationType;
locationPhoneNumber?: string;
locationAddress?: string; // TODO: We should validate address or fetch the address from googles api to see if its valid?
locationLink?: string; // Currently this only accepts links that are HTTPS://
}>({
@ -565,7 +600,11 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
if (e?.value) {
const newLocationType: LocationType = e.value;
locationFormMethods.setValue("locationType", newLocationType);
if (newLocationType === LocationType.InPerson || newLocationType === LocationType.Link) {
if (
newLocationType === LocationType.InPerson ||
newLocationType === LocationType.Link ||
newLocationType === LocationType.UserPhone
) {
openLocationModal(newLocationType);
} else {
addLocation(newLocationType);
@ -602,6 +641,16 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
/>
</div>
)}
{location.type === LocationType.UserPhone && (
<div className="flex flex-grow items-center">
<PhoneIcon className="h-6 w-6" />
<input
disabled
className="w-full border-0 bg-transparent text-sm ltr:ml-2 rtl:mr-2"
value={location.hostPhoneNumber}
/>
</div>
)}
{location.type === LocationType.Phone && (
<div className="flex flex-grow items-center">
<PhoneIcon className="h-6 w-6" />
@ -869,6 +918,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
locationFormMethods.setValue("locationType", location.type);
locationFormMethods.unregister("locationLink");
locationFormMethods.unregister("locationAddress");
locationFormMethods.unregister("locationPhoneNumber");
openLocationModal(location.type);
}}
aria-label={t("edit")}
@ -1925,7 +1975,9 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
if (newLocation === LocationType.Link) {
details = { link: values.locationLink };
}
if (newLocation === LocationType.UserPhone) {
details = { hostPhoneNumber: values.locationPhoneNumber };
}
addLocation(newLocation, details);
setShowLocationModal(false);
}}>
@ -1946,6 +1998,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister("locationLink");
locationFormMethods.unregister("locationAddress");
locationFormMethods.unregister("locationPhoneNumber");
setSelectedLocation(val);
}
}}

View File

@ -434,6 +434,8 @@
"link_meeting": "Link meeting",
"phone_call": "Phone call",
"phone_number": "Phone Number",
"attendee_phone_number": "Attendee Phone Number",
"host_phone_number": "Your Phone Number",
"enter_phone_number": "Enter phone number",
"reschedule": "Reschedule",
"reschedule_this": "Reschedule instead",
@ -624,6 +626,7 @@
"calendar_days": "calendar days",
"business_days": "business days",
"set_address_place": "Set an address or place",
"set_your_phone_number": "Set a phone number for the meeting",
"set_link_meeting": "Set a link to the meeting",
"cal_invitee_phone_number_scheduling": "Cal will ask your invitee to enter a phone number before scheduling.",
"cal_provide_google_meet_location": "Cal will provide a Google Meet location.",

View File

@ -1,6 +1,7 @@
export enum DefaultLocationType {
InPerson = "inPerson",
Phone = "phone",
UserPhone = "userPhone",
Link = "link",
}

View File

@ -36,7 +36,8 @@ function translateLocations(locations: OptionTypeBase[], t: TFunction) {
const defaultLocations: OptionTypeBase[] = [
{ value: LocationType.InPerson, label: "in_person_meeting" },
{ value: LocationType.Link, label: "link_meeting" },
{ value: LocationType.Phone, label: "phone_call" },
{ value: LocationType.Phone, label: "attendee_phone_number" },
{ value: LocationType.UserPhone, label: "host_phone_number" },
];
export function getLocationOptions(integrations: AppMeta, t: TFunction) {

View File

@ -9,6 +9,7 @@ export const eventTypeLocations = z.array(
type: z.nativeEnum(LocationType),
address: z.string().optional(),
link: z.string().url().optional(),
hostPhoneNumber: z.string().optional(),
})
);