From a6c3c7fbb3d25e5445094acdfe447a7e6c55b69e Mon Sep 17 00:00:00 2001
From: Alex van Andel 
Date: Sat, 8 May 2021 19:03:47 +0000
Subject: [PATCH] Implemented configurable eventType phone or physical
 locations.
---
 lib/calendarClient.ts               |  58 ++++++----
 lib/location.ts                     |   6 +
 package.json                        |   2 +
 pages/[user]/book.tsx               |  53 ++++++++-
 pages/api/availability/eventtype.ts | 118 +++++++------------
 pages/api/book/[user].ts            |   1 +
 pages/availability/event/[type].tsx | 168 +++++++++++++++++++++++++++-
 pages/success.tsx                   |  24 ++--
 prisma/schema.prisma                |   1 +
 yarn.lock                           |  37 +++++-
 10 files changed, 349 insertions(+), 119 deletions(-)
 create mode 100644 lib/location.ts
diff --git a/lib/calendarClient.ts b/lib/calendarClient.ts
index dae3ccb0..dcfaefc9 100644
--- a/lib/calendarClient.ts
+++ b/lib/calendarClient.ts
@@ -49,6 +49,7 @@ interface CalendarEvent {
     timeZone: string;
     endTime: string;
     description?: string;
+    location?: string;
     organizer: { name?: string, email: string };
     attendees: { name?: string, email: string }[];
 };
@@ -57,28 +58,37 @@ const MicrosoftOffice365Calendar = (credential) => {
 
     const auth = o365Auth(credential);
 
-    const translateEvent = (event: CalendarEvent) => ({
-        subject: event.title,
-        body: {
-            contentType: 'HTML',
-            content: event.description,
-        },
-        start: {
-            dateTime: event.startTime,
-            timeZone: event.timeZone,
-        },
-        end: {
-            dateTime: event.endTime,
-            timeZone: event.timeZone,
-        },
-        attendees: event.attendees.map(attendee => ({
-            emailAddress: {
-                address: attendee.email,
-                name: attendee.name
+    const translateEvent = (event: CalendarEvent) => {
+
+        let optional = {};
+        if (event.location) {
+            optional.location = { displayName: event.location };
+        }
+
+        return {
+            subject: event.title,
+            body: {
+                contentType: 'HTML',
+                content: event.description,
             },
-            type: "required"
-        }))
-    });
+            start: {
+                dateTime: event.startTime,
+                timeZone: event.timeZone,
+            },
+            end: {
+                dateTime: event.endTime,
+                timeZone: event.timeZone,
+            },
+            attendees: event.attendees.map(attendee => ({
+                emailAddress: {
+                    address: attendee.email,
+                    name: attendee.name
+                },
+                type: "required"
+            })),
+            ...optional
+        }
+    };
 
     return {
         getAvailability: (dateFrom, dateTo) => {
@@ -119,7 +129,7 @@ const MicrosoftOffice365Calendar = (credential) => {
                 'Content-Type': 'application/json',
             },
             body: JSON.stringify(translateEvent(event))
-        }))
+        }).then(handleErrors))
     }
 };
 
@@ -165,6 +175,10 @@ const GoogleCalendar = (credential) => {
                 },
             };
 
+            if (event.location) {
+                payload['location'] = event.location;
+            }
+
             const calendar = google.calendar({version: 'v3', auth: myGoogleAuth });
             calendar.events.insert({
                 auth: myGoogleAuth,
diff --git a/lib/location.ts b/lib/location.ts
new file mode 100644
index 00000000..b1ec56af
--- /dev/null
+++ b/lib/location.ts
@@ -0,0 +1,6 @@
+
+export enum LocationType {
+    InPerson = 'inPerson',
+    Phone = 'phone',
+}
+
diff --git a/package.json b/package.json
index 7deef52d..4eeb16ce 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,8 @@
     "next-transpile-modules": "^7.0.0",
     "react": "17.0.1",
     "react-dom": "17.0.1",
+    "react-phone-number-input": "^3.1.21",
+    "react-select": "^4.3.0",
     "react-timezone-select": "^1.0.2"
   },
   "devDependencies": {
diff --git a/pages/[user]/book.tsx b/pages/[user]/book.tsx
index ce56b0f6..ad59279e 100644
--- a/pages/[user]/book.tsx
+++ b/pages/[user]/book.tsx
@@ -1,22 +1,39 @@
 import Head from 'next/head';
 import Link from 'next/link';
 import { useRouter } from 'next/router';
-import { ClockIcon, CalendarIcon } from '@heroicons/react/solid';
+import { ClockIcon, CalendarIcon, LocationMarkerIcon } from '@heroicons/react/solid';
 import prisma from '../../lib/prisma';
 import {collectPageParameters, telemetryEventTypes, useTelemetry} from "../../lib/telemetry";
-import {useEffect} from "react";
-const dayjs = require('dayjs');
+import { useEffect, useState } from "react";
+import dayjs from 'dayjs';
+import 'react-phone-number-input/style.css';
+import PhoneInput from 'react-phone-number-input';
+import { LocationType } from '../../lib/location';
 
 export default function Book(props) {
     const router = useRouter();
     const { date, user } = router.query;
+    const [ selectedLocation, setSelectedLocation ] = useState(props.eventType.locations.length === 1 ? props.eventType.locations[0].type : '');
     const telemetry = useTelemetry();
     useEffect(() => {
         telemetry.withJitsu(jitsu => jitsu.track(telemetryEventTypes.timeSelected, collectPageParameters()));
-    })
+    });
+
+    const locationInfo = (type: LocationType) => props.eventType.locations.find(
+        (location) => location.type === type
+    );
+
+    // TODO: Move to translations
+    const locationLabels = {
+        [LocationType.InPerson]: 'In-person meeting',
+        [LocationType.Phone]: 'Phone call',
+    };
 
     const bookingHandler = event => {
         event.preventDefault();
+
+        const locationText = selectedLocation === LocationType.Phone ? event.target.phone.value : locationInfo(selectedLocation).address;
+
         telemetry.withJitsu(jitsu => jitsu.track(telemetryEventTypes.bookingConfirmed, collectPageParameters()));
         const res = fetch(
             '/api/book/' + user,
@@ -26,6 +43,7 @@ export default function Book(props) {
                     end: dayjs(date).add(props.eventType.length, 'minute').format(),
                     name: event.target.name.value,
                     email: event.target.email.value,
+                    location: locationText,
                     notes: event.target.notes.value
                   }),
                 headers: {
@@ -34,7 +52,8 @@ export default function Book(props) {
                 method: 'POST'
             }
         );
-        router.push("/success?date=" + date + "&type=" + props.eventType.id + "&user=" + props.user.username);
+
+        router.push(`/success?date=${date}&type=${props.eventType.id}&user=${props.user.username}&location=${encodeURIComponent(locationText)}`);
     }
 
     return (
@@ -55,6 +74,10 @@ export default function Book(props) {
                                 
                                 {props.eventType.length} minutes
                             
+                            {selectedLocation === LocationType.InPerson && 
+                                
+                                {locationInfo(selectedLocation).address}
+                            
}
                             
                                 
                                 {dayjs(date).format("hh:mma, dddd DD MMMM YYYY")}
@@ -75,6 +98,23 @@ export default function Book(props) {
                                         
                                     
                                 
+                                {props.eventType.locations.length > 1 && (
+                                    
+                                        Location
+                                        {props.eventType.locations.map( (location) => (
+                                            
+                                        ))}
+                                    
+                                )}
+                                {selectedLocation === LocationType.Phone && ()}
                                 
                                     
                                     
@@ -117,7 +157,8 @@ export async function getServerSideProps(context) {
             title: true,
             slug: true,
             description: true,
-            length: true
+            length: true,
+            locations: true,
         }
     });
 
diff --git a/pages/api/availability/eventtype.ts b/pages/api/availability/eventtype.ts
index 3725120b..ea37b2fc 100644
--- a/pages/api/availability/eventtype.ts
+++ b/pages/api/availability/eventtype.ts
@@ -4,99 +4,61 @@ import prisma from '../../../lib/prisma';
 
 export default async function handler(req: NextApiRequest, res: NextApiResponse) {
     const session = await getSession({req: req});
-
     if (!session) {
         res.status(401).json({message: "Not authenticated"});
         return;
     }
+    // TODO: Add user ID to user session object
+    const user = await prisma.user.findFirst({
+        where: {
+            email: session.user.email,
+        },
+        select: {
+            id: true
+        }
+    });
 
-    if (req.method == "POST") {
-        // TODO: Add user ID to user session object
-        const user = await prisma.user.findFirst({
-            where: {
-                email: session.user.email,
-            },
-            select: {
-                id: true
-            }
-        });
-
-        if (!user) { res.status(404).json({message: 'User not found'}); return; }
-
-        const title = req.body.title;
-        const slug = req.body.slug;
-        const description = req.body.description;
-        const length = parseInt(req.body.length);
-        const hidden = req.body.hidden;
-
-        const createEventType = await prisma.eventType.create({
-            data: {
-                title: title,
-                slug: slug,
-                description: description,
-                length: length,
-                hidden: hidden,
-                userId: user.id,
-            },
-        });
-
-        res.status(200).json({message: 'Event created successfully'});
+    if (!user) {
+        res.status(404).json({message: 'User not found'});
+        return;
     }
 
-    if (req.method == "PATCH") {
-        // TODO: Add user ID to user session object
-        const user = await prisma.user.findFirst({
-            where: {
-                email: session.user.email,
-            },
-            select: {
-                id: true
-            }
-        });
+    if (req.method == "PATCH" || req.method == "POST") {
 
-        if (!user) { res.status(404).json({message: 'User not found'}); return; }
+        const data = {
+            title: req.body.title,
+            slug: req.body.slug,
+            description: req.body.description,
+            length: parseInt(req.body.length),
+            hidden: req.body.hidden,
+            locations: req.body.locations,
+        };
 
-        const id = req.body.id;
-        const title = req.body.title;
-        const slug = req.body.slug;
-        const description = req.body.description;
-        const length = parseInt(req.body.length);
-        const hidden = req.body.hidden;
-
-        const updateEventType = await prisma.eventType.update({
-            where: {
-                id: id,
-            },
-            data: {
-                title: title,
-                slug: slug,
-                description: description,
-                length: length,
-                hidden: hidden
-            },
-        });
-
-        res.status(200).json({message: 'Event updated successfully'});
+        if (req.method == "POST") {
+            const createEventType = await prisma.eventType.create({
+                data: {
+                    userId: user.id,
+                    ...data,
+                },
+            });
+            res.status(200).json({message: 'Event created successfully'});
+        }
+        else if (req.method == "PATCH") {
+            const updateEventType = await prisma.eventType.update({
+                where: {
+                    id: req.body.id,
+                },
+                data,
+            });
+            res.status(200).json({message: 'Event updated successfully'});
+        }
     }
 
     if (req.method == "DELETE") {
-        // TODO: Add user ID to user session object
-        const user = await prisma.user.findFirst({
-            where: {
-                email: session.user.email,
-            },
-            select: {
-                id: true
-            }
-        });
-
-        if (!user) { res.status(404).json({message: 'User not found'}); return; }
-
-        const id = req.body.id;
 
         const deleteEventType = await prisma.eventType.delete({
             where: {
-                id: id,
+                id: req.body.id,
             },
         });
 
diff --git a/pages/api/book/[user].ts b/pages/api/book/[user].ts
index dd00c245..d832901c 100644
--- a/pages/api/book/[user].ts
+++ b/pages/api/book/[user].ts
@@ -21,6 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
         startTime: req.body.start,
         endTime: req.body.end,
         timeZone: currentUser.timeZone,
+        location: req.body.location,
         attendees: [
             { email: req.body.email, name: req.body.name }
         ]
diff --git a/pages/availability/event/[type].tsx b/pages/availability/event/[type].tsx
index 8829c333..80215c99 100644
--- a/pages/availability/event/[type].tsx
+++ b/pages/availability/event/[type].tsx
@@ -1,14 +1,22 @@
 import Head from 'next/head';
 import Link from 'next/link';
 import { useRouter } from 'next/router';
-import { useRef } from 'react';
+import { useRef, useState } from 'react';
+import Select, { OptionBase } from 'react-select';
 import prisma from '../../../lib/prisma';
+import { LocationType } from '../../../lib/location';
 import Shell from '../../../components/Shell';
 import { useSession, getSession } from 'next-auth/client';
+import { LocationMarkerIcon, PlusCircleIcon, XIcon, PhoneIcon } from '@heroicons/react/outline';
 
 export default function EventType(props) {
     const router = useRouter();
+
     const [ session, loading ] = useSession();
+    const [ showLocationModal, setShowLocationModal ] = useState(false);
+    const [ selectedLocation, setSelectedLocation ] = useState
(undefined);
+    const [ locations, setLocations ] = useState(props.eventType.locations || []);
+
     const titleRef = useRef();
     const slugRef = useRef();
     const descriptionRef = useRef();
@@ -27,12 +35,11 @@ export default function EventType(props) {
         const enteredDescription = descriptionRef.current.value;
         const enteredLength = lengthRef.current.value;
         const enteredIsHidden = isHiddenRef.current.checked;
-
         // TODO: Add validation
 
         const response = await fetch('/api/availability/eventtype', {
             method: 'PATCH',
-            body: JSON.stringify({id: props.eventType.id, title: enteredTitle, slug: enteredSlug, description: enteredDescription, length: enteredLength, hidden: enteredIsHidden}),
+            body: JSON.stringify({id: props.eventType.id, title: enteredTitle, slug: enteredSlug, description: enteredDescription, length: enteredLength, hidden: enteredIsHidden, locations }),
             headers: {
                 'Content-Type': 'application/json'
             }
@@ -55,6 +62,72 @@ export default function EventType(props) {
         router.push('/availability');
     }
 
+    // TODO: Tie into translations instead of abstracting to locations.ts
+    const locationOptions: OptionBase[] = [
+        { value: LocationType.InPerson, label: 'In-person meeting' },
+        { value: LocationType.Phone, label: 'Phone call', },
+    ];
+
+    const openLocationModal = (type: LocationType) => {
+        setSelectedLocation(locationOptions.find( (option) => option.value === type));
+        setShowLocationModal(true);
+    }
+
+    const closeLocationModal = () => {
+        setSelectedLocation(undefined);
+        setShowLocationModal(false);
+    };
+
+    const LocationOptions = () => {
+        if (!selectedLocation) {
+            return null;
+        }
+        switch (selectedLocation.value) {
+            case LocationType.InPerson:
+                const address = locations.find(
+                    (location) => location.type === LocationType.InPerson
+                )?.address;
+                return (
+                    
+                        
+                        
+                            
+                        
+                    
 
+                )
+            case LocationType.Phone:
+
+                 return (
+                    Calendso will ask your invitee to enter a phone number before scheduling.
+                )
+        }
+        return null;
+    };
+
+    const updateLocations = (e) => {
+        e.preventDefault();
+
+        let details = {};
+        if (e.target.location.value === LocationType.InPerson) {
+            details = { address: e.target.address.value };
+        }
+
+        const existingIdx = locations.findIndex( (loc) => e.target.location.value === loc.type );
+        if (existingIdx !== -1) {
+            let copy = locations;
+            copy[ existingIdx ] = { ...locations[ existingIdx ], ...details };
+            setLocations(copy);
+        } else {
+            setLocations(locations.concat({ type: e.target.location.value, ...details }));
+        }
+
+        setShowLocationModal(false);
+    };
+
+    const removeLocation = (selectedLocation) => {
+        setLocations(locations.filter( (location) => location.type !== selectedLocation.type ));
+    };
+
     return (
         
             
@@ -92,6 +165,53 @@ export default function EventType(props) {
                                             
                                          
                                     
+                                    
+                                        
+                                        {locations.length === 0 && 
+                                            
+                                                
+                                        
}
+                                        {locations.length > 0 && 
+                                            {locations.map( (location) => (
+                                                - 
+                                                    
+                                                        {location.type === LocationType.InPerson && (
+                                                            
+                                                                
+                                                                {location.address}
+                                                            
+                                                        )}
+                                                        {location.type === LocationType.Phone && (
+                                                            
+                                                        )}
+                                                        
+                                                            
+                                                            
+                                                        
+                                                    
 
+                                                 
+                                            ))}
+                                            {locations.length > 0 && locations.length !== locationOptions.length && - 
+                                                
+                                            
 }
+                                        
}
+                                    
 
                                     
                                         
                                         
@@ -153,6 +273,45 @@ export default function EventType(props) {
                         
                      
                 
+                {showLocationModal &&
+                    
+                        
+                            
+
+                            
+
+                            
+                                
+                                    
+                                        
+                                    
+                                    
+                                        
Edit location
+                                    
+                                
+                                
+                            
+                        
+                    
 
+                }
             
         
     );
@@ -182,7 +341,8 @@ export async function getServerSideProps(context) {
             slug: true,
             description: true,
             length: true,
-            hidden: true
+            hidden: true,
+            locations: true,
         }
     });
 
diff --git a/pages/success.tsx b/pages/success.tsx
index fd1c2407..7283d3e1 100644
--- a/pages/success.tsx
+++ b/pages/success.tsx
@@ -3,13 +3,13 @@ import Link from 'next/link';
 import prisma from '../lib/prisma';
 import { useRouter } from 'next/router';
 import { CheckIcon } from '@heroicons/react/outline';
-import { ClockIcon, CalendarIcon } from '@heroicons/react/solid';
+import { ClockIcon, CalendarIcon, LocationMarkerIcon } from '@heroicons/react/solid';
 const dayjs = require('dayjs');
 const ics = require('ics');
 
 export default function Success(props) {
     const router = useRouter();
-    const { date } = router.query;
+    const { date, location } = router.query;
 
     function eventLink(): string {
 
@@ -17,12 +17,18 @@ export default function Success(props) {
             (parts) => parts.split('-').length > 1 ? parts.split('-').map( (n) => parseInt(n, 10) ) : parts.split(':').map( (n) => parseInt(n, 10) )
         ));
 
+        let optional = {};
+        if (location) {
+            optional['location'] = location;
+        }
+
         const event = ics.createEvent({
            start,
            startInputType: 'utc',
            title: props.eventType.title + ' with ' + props.user.name,
            description: props.eventType.description,
-           duration: { minutes: props.eventType.length }
+           duration: { minutes: props.eventType.length },
+           ...optional
         });
 
         if (event.error) {
@@ -60,10 +66,14 @@ export default function Success(props) {
                                     
                                     
                                         {props.eventType.title} with {props.user.name}
-                                        
+                                        
                                             
                                             {props.eventType.length} minutes
                                         
+                                        
+                                            
+                                            {location}
+                                        
                                         
                                             
                                             {dayjs(date).format("hh:mma, dddd DD MMMM YYYY")}
@@ -74,17 +84,17 @@ export default function Success(props) {
                             
                                 Add to your calendar
                                 
-                                    
+                                    
                                         
                                             
                                         
                                     
-                                    
+                                    
                                         
                                             
                                         
                                     
-                                    
+                                    
                                         
                                             
                                         
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 288682e7..f72eaee2 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -15,6 +15,7 @@ model EventType {
   title         String
   slug          String
   description   String?
+  locations     Json?
   length        Int
   hidden        Boolean @default(false)
   user          User?   @relation(fields: [userId], references: [id])
diff --git a/yarn.lock b/yarn.lock
index b3f1af68..fc932f71 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -756,6 +756,11 @@ classnames@2.2.6:
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
   integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
 
+classnames@^2.2.5:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
+  integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
+
 cli-highlight@^2.1.10:
   version "2.1.11"
   resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf"
@@ -859,6 +864,11 @@ core-util-is@~1.0.0:
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 
+country-flag-icons@^1.0.2:
+  version "1.2.10"
+  resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.2.10.tgz#c60fdf25883abacd28fbbf3842b920890f944591"
+  integrity sha512-nG+kGe4wVU9M+EsLUhP4buSuNdBH0leTm0Fv6RToXxO9BbbxUKV9VUq+9AcztnW7nEnweK7WYdtJsfyNLmQugQ==
+
 create-ecdh@^4.0.0:
   version "4.0.4"
   resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
@@ -1564,6 +1574,13 @@ inherits@2.0.3:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
 
+input-format@^0.3.6:
+  version "0.3.6"
+  resolved "https://registry.yarnpkg.com/input-format/-/input-format-0.3.6.tgz#b9b167dbd16435eb3c0012347964b230ea0024c8"
+  integrity sha512-SbUu43CDVV5GlC8Xi6NYBUoiU+tLpN/IMYyQl0mzSXDiU1w0ql8wpcwjDOFpaCVLySLoreLUimhI82IA5y42Pw==
+  dependencies:
+    prop-types "^15.7.2"
+
 is-arguments@^1.0.4:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
@@ -1827,6 +1844,11 @@ jws@^4.0.0:
     jwa "^2.0.0"
     safe-buffer "^5.0.1"
 
+libphonenumber-js@^1.9.17:
+  version "1.9.17"
+  resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.17.tgz#fef2e6fd7a981be69ba358c24495725ee8daf331"
+  integrity sha512-ElJki901OynMg1l+evooPH1VyHrECuLqpgc12z2BkK25dFU5lUKTuMHEYV2jXxvtns/PIuJax56cBeoSK7ANow==
+
 loader-utils@1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
@@ -2483,7 +2505,7 @@ process@0.11.10, process@^0.11.10:
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
 
-prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2:
+prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
   version "15.7.2"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
   integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -2607,12 +2629,23 @@ react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1:
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
+react-phone-number-input@^3.1.21:
+  version "3.1.21"
+  resolved "https://registry.yarnpkg.com/react-phone-number-input/-/react-phone-number-input-3.1.21.tgz#7c6de442d9d2ebd6e757e93c6603698aa008e82b"
+  integrity sha512-Q1CS7RKFE+DyiZxEKrs00wf7geQ4qBJpOflCVNtTXnO0a2iXG42HFF7gtUpKQpro8THr7ejNy8H+zm2zD+EgvQ==
+  dependencies:
+    classnames "^2.2.5"
+    country-flag-icons "^1.0.2"
+    input-format "^0.3.6"
+    libphonenumber-js "^1.9.17"
+    prop-types "^15.7.2"
+
 react-refresh@0.8.3:
   version "0.8.3"
   resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
   integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
 
-react-select@^4.2.1:
+react-select@^4.2.1, react-select@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.0.tgz#6bde634ae7a378b49f3833c85c126f533483fa2e"
   integrity sha512-SBPD1a3TJqE9zoI/jfOLCAoLr/neluaeokjOixr3zZ1vHezkom8K0A9J4QG9IWDqIDE9K/Mv+0y1GjidC2PDtQ==