calcom / cal.com

Scheduling infrastructure for absolutely everyone.
https://cal.com
Other
29.79k stars 6.99k forks source link

[CAL-3833] Invalid Input Phone Number Recognition, Attendee Phone Number Location Events #15182

Open Baloun1 opened 1 month ago

Baloun1 commented 1 month ago

Currently with event type 'phone call', location 'attendee phone number'. A USA formatted phone number is returning a "invalid Input" error. Country Code dropdown has USA +1 selected, data input field auto formats the raw phone number input to +1 (XXX) XXX-XXXX.

image

May relate to closed issue: https://github.com/calcom/cal.com/pull/15046

CAL-3833

dosubot[bot] commented 1 month ago

To resolve the "Invalid Input" error for a USA-formatted phone number in the 'phone call' event type with the location set to 'attendee phone number', ensure the phone number is correctly formatted and validated using the isValidPhoneNumber function from libphonenumber-js. The phone number should be in the format +1 (XXX) XXX-XXXX.

Here are the key steps:

  1. Phone Number Validation: Ensure the phone number is validated using the isValidPhoneNumber function:

    const locationFormSchema = z.object({
     locationPhoneNumber: z
       .string()
       .nullable()
       .refine((val) => val !== null && isValidPhoneNumber(val))
       .optional(),
    });
  2. Phone Input Component: Ensure the phone number input is correctly passed to the PhoneInput component:

    <Controller
     name={eventLocationType.variable}
     control={control}
     defaultValue={defaultValue}
     render={({ field: { onChange, value } }) => (
       <PhoneInput onChange={onChange} value={value} {...rest} />
     )}
    />
  3. Integration in Components: Ensure the PhoneInput component is correctly integrated within the AIEventController and AISettings components:

    <Controller
     name="aiPhoneCallConfig.yourPhoneNumber"
     render={({ field: { onChange, value } }) => (
       <PhoneInput
         required
         placeholder={t("your_phone_number")}
         id="aiPhoneCallConfig.yourPhoneNumber"
         name="aiPhoneCallConfig.yourPhoneNumber"
         value={value}
         onChange={(val) => onChange(val)}
       />
     )}
    />
  4. Default Event Location Type: Ensure the DefaultEventLocationTypeEnum and defaultLocations are correctly set up:

    export const defaultLocations: DefaultEventLocationType[] = [
     {
       default: true,
       type: DefaultEventLocationTypeEnum.Phone,
       label: "attendee_phone_number",
       variable: "phone",
       organizerInputType: null,
       attendeeInputType: "phone",
       attendeeInputPlaceholder: `enter_phone_number`,
       defaultValueVariable: "phone",
       messageForOrganizer: "Cal will ask your invitee to enter a phone number before scheduling.",
       iconUrl: "/phone.svg",
       category: "phone",
     },
    ];
  5. Phone Input Configuration: Ensure the PhoneInput component is correctly configured to handle the default country and formatting:

    import { isSupportedCountry } from "libphonenumber-js";
    import { useState, useEffect } from "react";
    import PhoneInput from "react-phone-input-2";
    import "react-phone-input-2/lib/style.css";
    
    const useDefaultCountry = () => {
     const [defaultCountry, setDefaultCountry] = useState("us");
     const query = trpc.viewer.public.countryCode.useQuery(undefined, {
       refetchOnWindowFocus: false,
       refetchOnReconnect: false,
       retry: false,
     });
    
     useEffect(() => {
       const data = query.data;
       if (!data?.countryCode) return;
    
       isSupportedCountry(data?.countryCode)
         ? setDefaultCountry(data.countryCode.toLowerCase())
         : setDefaultCountry(navigator.language.split("-")[1]?.toLowerCase() || "us");
     }, [query.data]);
    
     return defaultCountry;
    };
    
    function BasePhoneInput({ name, className = "", onChange, value, ...rest }) {
     const defaultCountry = useDefaultCountry();
    
     return (
       <PhoneInput
         {...rest}
         value={value ? value.trim().replace(/^\+?/, "+") : undefined}
         country={value ? undefined : defaultCountry}
         enableSearch
         disableSearchIcon
         inputProps={{
           name: name,
           required: rest.required,
           placeholder: rest.placeholder,
         }}
         onChange={(value) => onChange(`+${value}`)}
         containerClass="hover:border-emphasis dark:focus:border-emphasis border-default !bg-default rounded-md border focus-within:outline-none focus-within:ring-2 focus-within:ring-brand-default disabled:cursor-not-allowed"
         inputClass="text-sm focus:ring-0 !bg-default text-default"
         buttonClass="text-emphasis !bg-default hover:!bg-emphasis"
         searchClass="!text-default !bg-default hover:!bg-emphasis"
         dropdownClass="!text-default !bg-default"
         inputStyle={{ width: "inherit", border: 0 }}
         searchStyle={{
           display: "flex",
           flexDirection: "row",
           alignItems: "center",
           padding: "6px 12px",
           gap: "8px",
           width: "296px",
           height: "28px",
           marginLeft: "-4px",
         }}
         dropdownStyle={{ width: "max-content" }}
       />
     );
    }
    
    export default BasePhoneInput;
  6. Test Cases: Ensure the phone number does not contain leading spaces or other invalid characters:

    describe("validate phone type field", () => {
     test(`should fail parsing if invalid phone provided`, async ({}) => {
       const schema = getBookingResponsesSchema({
         bookingFields: [
           { name: "name", type: "name", required: true },
           { name: "email", type: "email", required: true },
           { name: "testPhone", type: "phone", required: true },
         ],
         view: "ALL_VIEWS",
       });
       const parsedResponses = await schema.safeParseAsync({
         email: "test@test.com",
         name: "test",
         testPhone: "1234567890",
       });
       expect(parsedResponses.success).toBe(false);
       if (parsedResponses.success) throw new Error("Should not reach here");
       expect(parsedResponses.error.issues[0]).toEqual(
         expect.objectContaining({
           code: "custom",
           message: `{testPhone}${CUSTOM_PHONE_VALIDATION_ERROR_MSG}`,
         })
       );
     });
    
     test(`should successfully give responses if phone type field value is valid`, async ({}) => {
       const schema = getBookingResponsesSchema({
         bookingFields: [
           { name: "name", type: "name", required: true },
           { name: "email", type: "email", required: true },
           { name: "testPhone", type: "phone", required: true },
         ],
         view: "ALL_VIEWS",
       });
       const parsedResponses = await schema.safeParseAsync({
         email: "test@test.com",
         name: "test",
         testPhone: "+919999999999",
       });
       expect(parsedResponses.success).toBe(true);
       if (!parsedResponses.success) throw new Error("Should not reach here");
       expect(parsedResponses.data).toEqual({
         email: "test@test.com",
         name: "test",
         testPhone: "+919999999999",
       });
     });
    
     test(`should give parsed response if phone type field value starts with a space`, async ({}) => {
       const schema = getBookingResponsesSchema({
         bookingFields: [
           { name: "name", type: "name", required: true },
           { name: "email", type: "email", required: true },
           { name: "testPhone", type: "phone", required: true },
         ],
         view: "ALL_VIEWS",
       });
       const parsedResponses = await schema.safeParseAsync({
         email: "test@test.com",
         name: "test",
         testPhone: " 919999999999",
       });
       expect(parsedResponses.success).toBe(true);
       if (!parsedResponses.success) throw new Error("Should not reach here");
       expect(parsedResponses.data).toEqual({
         email: "test@test.com",
         name: "test",
         testPhone: "+919999999999",
       });
    
       const parsedResponses2 = await schema.safeParseAsync({
         email: "test@test.com",
         name: "test",
         testPhone: "     919999999999",
       });
       expect(parsedResponses2.success).toBe true;
       if (!parsedResponses2.success) throw new Error("Should not reach here");
       expect(parsedResponses2.data).toEqual({
         email: "test@test.com",
         name: "test",
         testPhone: "+919999999999",
       });
     });
    });

These steps should help resolve the "Invalid Input" error by ensuring the phone number is correctly formatted and validated. If the issue persists, double-check the implementation of the PhoneInput component and the isValidPhoneNumber function.

#### Details **Sources** - [[CAL-437] Rescheduling a meeting with attendee's phone number as a location throws an error](https://github.com/calcom/cal.com/issues/5652) - [Attendee Phone Number - Input field missing when default](https://github.com/calcom/cal.com/issues/4454) - [apps/web/components/dialog/EditLocationDialog.tsx](https://github.com/calcom/cal.com/blob/main/apps/web/components/dialog/EditLocationDialog.tsx) - [apps/web/components/eventtype/AIEventController.tsx](https://github.com/calcom/cal.com/blob/main/apps/web/components/eventtype/AIEventController.tsx) - [packages/app-store/locations.ts](https://github.com/calcom/cal.com/blob/main/packages/app-store/locations.ts) - [packages/ui/form/PhoneInput.tsx](https://github.com/calcom/cal.com/blob/main/packages/ui/form/PhoneInput.tsx) - [packages/features/bookings/lib/getBookingResponsesSchema.test.ts](https://github.com/calcom/cal.com/blob/main/packages/features/bookings/lib/getBookingResponsesSchema.test.ts) - [packages/trpc/server/routers/viewer/workflows/verifyPhoneNumber.schema.ts](https://github.com/calcom/cal.com/blob/main/packages/trpc/server/routers/viewer/workflows/verifyPhoneNumber.schema.ts) - [packages/features/ee/workflows/lib/reminders/verifyPhoneNumber.ts](https://github.com/calcom/cal.com/blob/main/packages/features/ee/workflows/lib/reminders/verifyPhoneNumber.ts) - [.yarn/patches/libphonenumber-js-npm-1.10.51-4ff79b15f8.patch](https://github.com/calcom/cal.com/blob/main/.yarn/patches/libphonenumber-js-npm-1.10.51-4ff79b15f8.patch) - [package.json](https://github.com/calcom/cal.com/blob/main/package.json)
**About Dosu** This response is meant to be useful and save you time. It is **not meant to be a precise solution**, but rather a starting point for your own research. Help me be more useful! Please leave a 👍 if this is helpful and 👎 if it is irrelevant.

To continue the conversation, mention @dosu.

PeerRich commented 1 month ago

@Udit-takkar and @zomars lets fix the phone number input once and for all. too many issues

Vishalra-i commented 1 month ago

Can i work on thia ?