@@ -8,8 +8,11 @@ import {
88 ChevronDownIcon ,
99 CheckIcon ,
1010 Clock3Icon ,
11+ LinkIcon ,
1112 Loader2Icon ,
13+ MapPinIcon ,
1214 SparklesIcon ,
15+ VideoIcon ,
1316} from "lucide-react" ;
1417import { useRouter } from "next/navigation" ;
1518import { useMemo , useState , useTransition } from "react" ;
@@ -52,6 +55,9 @@ type CreateEventFormProps = {
5255const eventFieldOrder = [
5356 "eventType" ,
5457 "title" ,
58+ "location" ,
59+ "isOnlineMeeting" ,
60+ "meetingLink" ,
5561 "notificationEmail" ,
5662 "timezone" ,
5763 "dates" ,
@@ -68,6 +74,9 @@ type EventFormErrors = Partial<Record<EventField, string>>;
6874const eventFieldIds : Record < EventField , string > = {
6975 eventType : "event-type" ,
7076 title : "title" ,
77+ location : "location" ,
78+ isOnlineMeeting : "event-format" ,
79+ meetingLink : "meeting-link" ,
7180 notificationEmail : "notification-email" ,
7281 timezone : "timezone-trigger" ,
7382 dates : "date-range-trigger" ,
@@ -190,6 +199,9 @@ export function CreateEventForm({
190199 const [ initialDateRange ] = useState ( ( ) => getDefaultDateRange ( "time_grid" , startOfToday ( ) ) ) ;
191200 const [ eventType , setEventType ] = useState < EventType > ( "time_grid" ) ;
192201 const [ title , setTitle ] = useState ( "" ) ;
202+ const [ location , setLocation ] = useState ( "" ) ;
203+ const [ isOnlineMeeting , setIsOnlineMeeting ] = useState ( false ) ;
204+ const [ meetingLink , setMeetingLink ] = useState ( "" ) ;
193205 const [ notificationEmail , setNotificationEmail ] = useState ( "" ) ;
194206 const [ dateRange , setDateRange ] = useState < DateRange | undefined > ( initialDateRange ) ;
195207 const [ draftDateRange , setDraftDateRange ] = useState < DateRange | undefined > ( initialDateRange ) ;
@@ -367,6 +379,9 @@ export function CreateEventForm({
367379 const parsed = createEventCreateSchema ( messages ) . safeParse ( {
368380 eventType,
369381 title,
382+ location : isOnlineMeeting ? undefined : location ,
383+ isOnlineMeeting,
384+ meetingLink : isOnlineMeeting ? meetingLink : undefined ,
370385 notificationEmail : notificationsConfigured ? notificationEmail : undefined ,
371386 timezone,
372387 dates : selectedDates ,
@@ -448,6 +463,120 @@ export function CreateEventForm({
448463 ) : null }
449464 </ div >
450465
466+ < div className = "space-y-4" >
467+ < div className = "space-y-2" >
468+ < Label id = { eventFieldIds . isOnlineMeeting } >
469+ { messages . createEvent . eventFormatLabel }
470+ </ Label >
471+ < div
472+ role = "radiogroup"
473+ aria-labelledby = { eventFieldIds . isOnlineMeeting }
474+ className = "grid grid-cols-1 gap-2 sm:grid-cols-2"
475+ >
476+ < button
477+ type = "button"
478+ role = "radio"
479+ aria-checked = { ! isOnlineMeeting }
480+ className = { cn (
481+ "flex h-10 items-center justify-center gap-2 rounded-md border px-3 text-sm font-medium transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" ,
482+ ! isOnlineMeeting
483+ ? "border-primary bg-primary/10 text-foreground"
484+ : "border-input bg-background text-muted-foreground" ,
485+ ) }
486+ onClick = { ( ) => {
487+ setIsOnlineMeeting ( false ) ;
488+ setMeetingLink ( "" ) ;
489+ clearErrors ( "isOnlineMeeting" , "meetingLink" ) ;
490+ } }
491+ >
492+ < MapPinIcon className = "size-4" aria-hidden = "true" />
493+ { messages . createEvent . inPersonLabel }
494+ </ button >
495+ < button
496+ type = "button"
497+ role = "radio"
498+ aria-checked = { isOnlineMeeting }
499+ className = { cn (
500+ "flex h-10 items-center justify-center gap-2 rounded-md border px-3 text-sm font-medium transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" ,
501+ isOnlineMeeting
502+ ? "border-primary bg-primary/10 text-foreground"
503+ : "border-input bg-background text-muted-foreground" ,
504+ ) }
505+ onClick = { ( ) => {
506+ setIsOnlineMeeting ( true ) ;
507+ setLocation ( "" ) ;
508+ clearErrors ( "isOnlineMeeting" , "location" ) ;
509+ } }
510+ >
511+ < VideoIcon className = "size-4" aria-hidden = "true" />
512+ { messages . createEvent . onlineMeetingLabel }
513+ </ button >
514+ </ div >
515+ </ div >
516+
517+ { isOnlineMeeting ? (
518+ < div className = "space-y-2" >
519+ < Label htmlFor = { eventFieldIds . meetingLink } >
520+ { messages . createEvent . meetingLinkLabel }
521+ </ Label >
522+ < div className = "relative" >
523+ < LinkIcon className = "pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
524+ < Input
525+ id = { eventFieldIds . meetingLink }
526+ type = "url"
527+ inputMode = "url"
528+ value = { meetingLink }
529+ placeholder = { messages . createEvent . meetingLinkPlaceholder }
530+ aria-invalid = { fieldErrors . meetingLink ? true : undefined }
531+ aria-describedby = { fieldErrors . meetingLink ? "meeting-link-error" : undefined }
532+ className = { cn (
533+ "pl-9" ,
534+ fieldErrors . meetingLink &&
535+ "border-destructive focus-visible:ring-destructive/20" ,
536+ ) }
537+ onChange = { ( event ) => {
538+ setMeetingLink ( event . target . value ) ;
539+ clearErrors ( "meetingLink" ) ;
540+ } }
541+ />
542+ </ div >
543+ { fieldErrors . meetingLink ? (
544+ < p id = "meeting-link-error" className = "text-sm text-destructive" >
545+ { fieldErrors . meetingLink }
546+ </ p >
547+ ) : null }
548+ </ div >
549+ ) : (
550+ < div className = "space-y-2" >
551+ < Label htmlFor = { eventFieldIds . location } > { messages . createEvent . locationLabel } </ Label >
552+ < div className = "relative" >
553+ < MapPinIcon className = "pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
554+ < Input
555+ id = { eventFieldIds . location }
556+ value = { location }
557+ placeholder = { messages . createEvent . locationPlaceholder }
558+ aria-invalid = { fieldErrors . location ? true : undefined }
559+ aria-describedby = { fieldErrors . location ? "location-error" : undefined }
560+ className = { cn (
561+ "pl-9" ,
562+ fieldErrors . location &&
563+ "border-destructive focus-visible:ring-destructive/20" ,
564+ ) }
565+ onChange = { ( event ) => {
566+ setLocation ( event . target . value ) ;
567+ clearErrors ( "location" ) ;
568+ } }
569+ />
570+ </ div >
571+ { fieldErrors . location ? (
572+ < p id = "location-error" className = "text-sm text-destructive" >
573+ { fieldErrors . location }
574+ </ p >
575+ ) : null }
576+ </ div >
577+ ) }
578+ </ div >
579+
451580 < div className = "space-y-2" >
452581 < Label id = { eventFieldIds . eventType } > { messages . createEvent . eventTypeLabel } </ Label >
453582 < div
0 commit comments