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:
parent
3c37c2e774
commit
7fd149fd2e
|
@ -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 || "";
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export enum DefaultLocationType {
|
||||
InPerson = "inPerson",
|
||||
Phone = "phone",
|
||||
UserPhone = "userPhone",
|
||||
Link = "link",
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user