2021-04-11 17:12:18 +00:00
import Head from 'next/head' ;
import Link from 'next/link' ;
2021-06-09 19:46:41 +00:00
import { useRouter } from 'next/router' ;
2021-06-20 18:32:30 +00:00
import { CalendarIcon , ClockIcon , ExclamationIcon , LocationMarkerIcon } from '@heroicons/react/solid' ;
2021-04-11 17:12:18 +00:00
import prisma from '../../lib/prisma' ;
2021-05-07 17:05:33 +00:00
import { collectPageParameters , telemetryEventTypes , useTelemetry } from "../../lib/telemetry" ;
2021-06-09 19:46:41 +00:00
import { useEffect , useState } from "react" ;
2021-05-08 19:03:47 +00:00
import dayjs from 'dayjs' ;
2021-05-26 18:40:22 +00:00
import utc from 'dayjs/plugin/utc' ;
import timezone from 'dayjs/plugin/timezone' ;
2021-05-08 19:03:47 +00:00
import 'react-phone-number-input/style.css' ;
import PhoneInput from 'react-phone-number-input' ;
2021-06-09 19:46:41 +00:00
import { LocationType } from '../../lib/location' ;
2021-05-08 20:26:19 +00:00
import Avatar from '../../components/Avatar' ;
2021-05-27 20:34:02 +00:00
import Button from '../../components/ui/Button' ;
2021-06-19 19:44:36 +00:00
import { EventTypeCustomInputType } from "../../lib/eventTypeInput" ;
2021-03-22 13:48:48 +00:00
2021-05-26 18:40:22 +00:00
dayjs . extend ( utc ) ;
dayjs . extend ( timezone ) ;
2021-03-22 13:48:48 +00:00
export default function Book ( props ) {
2021-04-11 17:12:18 +00:00
const router = useRouter ( ) ;
2021-06-09 18:28:39 +00:00
const { date , user , rescheduleUid } = router . query ;
2021-05-08 21:53:10 +00:00
2021-05-26 18:40:22 +00:00
const [ is24h , setIs24h ] = useState ( false ) ;
const [ preferredTimeZone , setPreferredTimeZone ] = useState ( '' ) ;
2021-06-20 18:32:30 +00:00
const [ loading , setLoading ] = useState ( false ) ;
const [ error , setError ] = useState ( false ) ;
2021-05-26 18:40:22 +00:00
2021-05-08 21:53:10 +00:00
const locations = props . eventType . locations || [ ] ;
const [ selectedLocation , setSelectedLocation ] = useState < LocationType > ( locations . length === 1 ? locations [ 0 ] . type : '' ) ;
2021-04-27 14:19:12 +00:00
const telemetry = useTelemetry ( ) ;
useEffect ( ( ) = > {
2021-05-26 18:40:22 +00:00
setPreferredTimeZone ( localStorage . getItem ( 'timeOption.preferredTimeZone' ) || dayjs . tz . guess ( ) ) ;
setIs24h ( ! ! localStorage . getItem ( 'timeOption.is24hClock' ) ) ;
2021-05-07 17:05:33 +00:00
telemetry . withJitsu ( jitsu = > jitsu . track ( telemetryEventTypes . timeSelected , collectPageParameters ( ) ) ) ;
2021-05-08 19:03:47 +00:00
} ) ;
2021-05-08 21:53:10 +00:00
const locationInfo = ( type : LocationType ) = > locations . find (
2021-05-08 19:03:47 +00:00
( location ) = > location . type === type
) ;
// TODO: Move to translations
const locationLabels = {
[ LocationType . InPerson ] : 'In-person meeting' ,
[ LocationType . Phone ] : 'Phone call' ,
2021-06-21 23:15:29 +00:00
[ LocationType . GoogleMeet ] : 'Google Meet' ,
2021-05-08 19:03:47 +00:00
} ;
2021-03-22 13:48:48 +00:00
const bookingHandler = event = > {
2021-06-20 18:32:30 +00:00
const book = async ( ) = > {
setLoading ( true ) ;
setError ( false ) ;
2021-06-21 15:56:14 +00:00
let notes = "" ;
if ( props . eventType . customInputs ) {
notes = props . eventType . customInputs . map ( input = > {
const data = event . target [ "custom_" + input . id ] ;
if ( ! ! data ) {
if ( input . type === EventTypeCustomInputType . Bool ) {
return input . label + "\n" + ( data . value ? "Yes" : "No" )
} else {
return input . label + "\n" + data . value
}
2021-06-19 19:44:36 +00:00
}
2021-06-21 15:56:14 +00:00
} ) . join ( "\n\n" )
}
if ( ! ! notes && ! ! event . target . notes . value ) {
notes += "\n\nAdditional notes:\n" + event . target . notes . value ;
} else {
notes += event . target . notes . value ;
}
2021-05-08 19:03:47 +00:00
2021-06-20 18:32:30 +00:00
let payload = {
start : dayjs ( date ) . format ( ) ,
end : dayjs ( date ) . add ( props . eventType . length , 'minute' ) . format ( ) ,
name : event.target.name.value ,
email : event.target.email.value ,
2021-06-21 15:56:14 +00:00
notes : notes ,
2021-06-20 18:32:30 +00:00
timeZone : preferredTimeZone ,
eventTypeId : props.eventType.id ,
rescheduleUid : rescheduleUid
} ;
2021-05-08 21:53:10 +00:00
if ( selectedLocation ) {
2021-06-21 23:15:29 +00:00
switch ( selectedLocation ) {
case LocationType . Phone :
payload [ 'location' ] = event . target . phone . value
break
2021-06-24 15:49:15 +00:00
2021-06-21 23:15:29 +00:00
case LocationType . InPerson :
payload [ 'location' ] = locationInfo ( selectedLocation ) . address
break
2021-06-24 15:49:15 +00:00
2021-06-21 23:15:29 +00:00
case LocationType . GoogleMeet :
payload [ 'location' ] = LocationType . GoogleMeet
break
2021-03-22 13:48:48 +00:00
}
2021-05-08 21:53:10 +00:00
}
2021-05-08 19:03:47 +00:00
2021-06-20 18:32:30 +00:00
telemetry . withJitsu ( jitsu = > jitsu . track ( telemetryEventTypes . bookingConfirmed , collectPageParameters ( ) ) ) ;
2021-06-24 18:41:26 +00:00
/*const res = await */ fetch (
2021-06-20 18:32:30 +00:00
'/api/book/' + user ,
{
body : JSON.stringify ( payload ) ,
headers : {
'Content-Type' : 'application/json'
} ,
method : 'POST'
}
) ;
2021-06-24 18:41:26 +00:00
// TODO When the endpoint is fixed, change this to await the result again
//if (res.ok) {
2021-06-21 15:56:14 +00:00
let successUrl = ` /success?date= ${ date } &type= ${ props . eventType . id } &user= ${ props . user . username } &reschedule= ${ ! ! rescheduleUid } &name= ${ payload . name } ` ;
2021-06-20 18:32:30 +00:00
if ( payload [ 'location' ] ) {
2021-06-24 15:49:15 +00:00
if ( payload [ 'location' ] . includes ( 'integration' ) ) {
successUrl += "&location=" + encodeURIComponent ( "Web conferencing details to follow." ) ;
}
else {
successUrl += "&location=" + encodeURIComponent ( payload [ 'location' ] ) ;
}
2021-06-20 18:32:30 +00:00
}
await router . push ( successUrl ) ;
2021-06-24 18:41:26 +00:00
/ * } e l s e {
2021-06-20 18:32:30 +00:00
setLoading ( false ) ;
setError ( true ) ;
2021-06-24 18:41:26 +00:00
} * /
2021-05-08 21:53:10 +00:00
}
2021-06-20 18:32:30 +00:00
event . preventDefault ( ) ;
book ( ) ;
2021-03-22 13:48:48 +00:00
}
return (
< div >
< Head >
2021-06-09 18:28:39 +00:00
< title > { rescheduleUid ? 'Reschedule' : 'Confirm' } your { props . eventType . title } with { props . user . name || props . user . username } | Calendso < / title >
2021-03-22 13:48:48 +00:00
< link rel = "icon" href = "/favicon.ico" / >
< / Head >
< main className = "max-w-3xl mx-auto my-24" >
< div className = "bg-white overflow-hidden shadow rounded-lg" >
< div className = "sm:flex px-4 py-5 sm:p-6" >
< div className = "sm:w-1/2 sm:border-r" >
2021-05-08 20:26:19 +00:00
< Avatar user = { props . user } className = "w-16 h-16 rounded-full mb-4" / >
2021-03-22 13:48:48 +00:00
< h2 className = "font-medium text-gray-500" > { props . user . name } < / h2 >
< h1 className = "text-3xl font-semibold text-gray-800 mb-4" > { props . eventType . title } < / h1 >
< p className = "text-gray-500 mb-2" >
2021-04-21 10:10:27 +00:00
< ClockIcon className = "inline-block w-4 h-4 mr-1 -mt-1" / >
2021-03-22 13:48:48 +00:00
{ props . eventType . length } minutes
< / p >
2021-05-08 19:03:47 +00:00
{ selectedLocation === LocationType . InPerson && < p className = "text-gray-500 mb-2" >
< LocationMarkerIcon className = "inline-block w-4 h-4 mr-1 -mt-1" / >
{ locationInfo ( selectedLocation ) . address }
< / p > }
2021-03-22 13:48:48 +00:00
< p className = "text-blue-600 mb-4" >
2021-04-21 10:10:27 +00:00
< CalendarIcon className = "inline-block w-4 h-4 mr-1 -mt-1" / >
2021-05-26 18:40:22 +00:00
{ preferredTimeZone && dayjs ( date ) . tz ( preferredTimeZone ) . format ( ( is24h ? "H:mm" : "h:mma" ) + ", dddd DD MMMM YYYY" ) }
2021-03-22 13:48:48 +00:00
< / p >
< p className = "text-gray-600" > { props . eventType . description } < / p >
< / div >
< div className = "sm:w-1/2 pl-8 pr-4" >
< form onSubmit = { bookingHandler } >
< div className = "mb-4" >
< label htmlFor = "name" className = "block text-sm font-medium text-gray-700" > Your name < / label >
< div className = "mt-1" >
2021-06-09 22:50:45 +00:00
< input type = "text" name = "name" id = "name" required className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "John Doe" defaultValue = { props . booking ? props . booking . attendees [ 0 ] . name : '' } / >
2021-03-22 13:48:48 +00:00
< / div >
< / div >
< div className = "mb-4" >
< label htmlFor = "email" className = "block text-sm font-medium text-gray-700" > Email address < / label >
< div className = "mt-1" >
2021-06-09 22:50:45 +00:00
< input type = "email" name = "email" id = "email" required className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "you@example.com" defaultValue = { props . booking ? props . booking . attendees [ 0 ] . email : '' } / >
2021-03-22 13:48:48 +00:00
< / div >
< / div >
2021-05-08 21:53:10 +00:00
{ locations . length > 1 && (
2021-05-08 19:03:47 +00:00
< div className = "mb-4" >
< span className = "block text-sm font-medium text-gray-700" > Location < / span >
2021-05-08 21:53:10 +00:00
{ locations . map ( ( location ) = > (
2021-05-08 19:03:47 +00:00
< label key = { location . type } className = "block" >
< input type = "radio" required onChange = { ( e ) = > setSelectedLocation ( e . target . value ) } className = "location" name = "location" value = { location . type } checked = { selectedLocation === location . type } / >
< span className = "text-sm ml-2" > { locationLabels [ location . type ] } < / span >
< / label >
) ) }
< / div >
) }
{ selectedLocation === LocationType . Phone && ( < div className = "mb-4" >
< label htmlFor = "phone" className = "block text-sm font-medium text-gray-700" > Phone Number < / label >
< div className = "mt-1" >
< PhoneInput name = "phone" placeholder = "Enter phone number" id = "phone" required className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" onChange = { ( ) = > { } } / >
< / div >
< / div > ) }
2021-06-19 19:44:36 +00:00
{ props . eventType . customInputs && props . eventType . customInputs . sort ( ( a , b ) = > a . id - b . id ) . map ( input = > (
< div className = "mb-4" >
{ input . type !== EventTypeCustomInputType . Bool &&
< label htmlFor = { input . label } className = "block text-sm font-medium text-gray-700 mb-1" > { input . label } < / label > }
{ input . type === EventTypeCustomInputType . TextLong &&
< textarea name = { "custom_" + input . id } id = { "custom_" + input . id }
required = { input . required }
rows = { 3 }
className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
placeholder = "" / > }
{ input . type === EventTypeCustomInputType . Text &&
< input type = "text" name = { "custom_" + input . id } id = { "custom_" + input . id }
required = { input . required }
className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
placeholder = "" / > }
{ input . type === EventTypeCustomInputType . Number &&
< input type = "number" name = { "custom_" + input . id } id = { "custom_" + input . id }
required = { input . required }
className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
placeholder = "" / > }
{ input . type === EventTypeCustomInputType . Bool &&
< div className = "flex items-center h-5" >
< input type = "checkbox" name = { "custom_" + input . id } id = { "custom_" + input . id }
className = "focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded mr-2"
placeholder = "" / >
< label htmlFor = { input . label } className = "block text-sm font-medium text-gray-700" > { input . label } < / label >
< / div > }
< / div >
) ) }
2021-03-22 13:48:48 +00:00
< div className = "mb-4" >
< label htmlFor = "notes" className = "block text-sm font-medium text-gray-700 mb-1" > Additional notes < / label >
2021-06-19 19:44:36 +00:00
< textarea name = "notes" id = "notes" rows = { 3 } className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "Please share anything that will help prepare for our meeting." defaultValue = { props . booking ? props . booking . description : '' } / >
2021-03-22 13:48:48 +00:00
< / div >
2021-06-01 18:03:13 +00:00
< div className = "flex items-start" >
2021-06-20 18:32:30 +00:00
< Button type = "submit" loading = { loading } className = "btn btn-primary" > { rescheduleUid ? 'Reschedule' : 'Confirm' } < / Button >
2021-06-09 18:28:39 +00:00
< Link href = { "/" + props . user . username + "/" + props . eventType . slug + ( rescheduleUid ? "?rescheduleUid=" + rescheduleUid : "" ) } >
2021-03-22 13:48:48 +00:00
< a className = "ml-2 btn btn-white" > Cancel < / a >
< / Link >
< / div >
< / form >
2021-06-20 18:32:30 +00:00
{ error && < div className = "bg-yellow-50 border-l-4 border-yellow-400 p-4 mt-2" >
< div className = "flex" >
< div className = "flex-shrink-0" >
< ExclamationIcon className = "h-5 w-5 text-yellow-400" aria-hidden = "true" / >
< / div >
< div className = "ml-3" >
< p className = "text-sm text-yellow-700" >
Could not { rescheduleUid ? 'reschedule' : 'book' } the meeting . Please try again or { ' ' }
< a href = { "mailto:" + props . user . email } className = "font-medium underline text-yellow-700 hover:text-yellow-600" >
Contact { props . user . name } via e - mail
< / a >
< / p >
< / div >
< / div >
< / div > }
2021-03-22 13:48:48 +00:00
< / div >
< / div >
< / div >
< / main >
< / div >
)
}
export async function getServerSideProps ( context ) {
const user = await prisma . user . findFirst ( {
where : {
username : context.query.user ,
} ,
select : {
username : true ,
name : true ,
2021-05-08 20:26:19 +00:00
email :true ,
2021-03-22 13:48:48 +00:00
bio : true ,
avatar : true ,
eventTypes : true
}
} ) ;
const eventType = await prisma . eventType . findUnique ( {
where : {
id : parseInt ( context . query . type ) ,
} ,
select : {
id : true ,
title : true ,
2021-04-30 12:06:04 +00:00
slug : true ,
2021-03-22 13:48:48 +00:00
description : true ,
2021-05-08 19:03:47 +00:00
length : true ,
locations : true ,
2021-06-19 19:44:36 +00:00
customInputs : true ,
2021-03-22 13:48:48 +00:00
}
} ) ;
2021-06-09 19:46:41 +00:00
let booking = null ;
2021-06-09 18:28:39 +00:00
if ( context . query . rescheduleUid ) {
booking = await prisma . booking . findFirst ( {
where : {
uid : context.query.rescheduleUid
} ,
select : {
description : true ,
attendees : {
select : {
email : true ,
name : true
}
}
}
} ) ;
}
2021-03-22 13:48:48 +00:00
return {
props : {
user ,
2021-06-09 18:28:39 +00:00
eventType ,
booking
2021-03-22 13:48:48 +00:00
} ,
}
2021-06-15 15:26:16 +00:00
}