2021-04-08 14:20:38 +00:00
import Head from 'next/head' ;
import Link from 'next/link' ;
import prisma from '../../lib/prisma' ;
2021-04-20 14:09:31 +00:00
import Modal from '../../components/Modal' ;
2021-04-08 14:20:38 +00:00
import Shell from '../../components/Shell' ;
2021-06-20 14:37:51 +00:00
import { useRouter } from 'next/router' ;
import { useRef , useState } from 'react' ;
import { getSession , useSession } from 'next-auth/client' ;
import { ClockIcon , PlusIcon } from '@heroicons/react/outline' ;
2021-04-08 14:20:38 +00:00
export default function Availability ( props ) {
const [ session , loading ] = useSession ( ) ;
2021-04-29 12:05:50 +00:00
const router = useRouter ( ) ;
2021-04-08 14:20:38 +00:00
const [ showAddModal , setShowAddModal ] = useState ( false ) ;
2021-04-20 14:09:31 +00:00
const [ successModalOpen , setSuccessModalOpen ] = useState ( false ) ;
2021-04-13 16:16:32 +00:00
const [ showChangeTimesModal , setShowChangeTimesModal ] = useState ( false ) ;
2021-04-29 13:04:08 +00:00
const titleRef = useRef < HTMLInputElement > ( ) ;
const slugRef = useRef < HTMLInputElement > ( ) ;
2021-04-29 13:47:01 +00:00
const descriptionRef = useRef < HTMLTextAreaElement > ( ) ;
2021-04-29 13:04:08 +00:00
const lengthRef = useRef < HTMLInputElement > ( ) ;
const isHiddenRef = useRef < HTMLInputElement > ( ) ;
const startHoursRef = useRef < HTMLInputElement > ( ) ;
const startMinsRef = useRef < HTMLInputElement > ( ) ;
const endHoursRef = useRef < HTMLInputElement > ( ) ;
const endMinsRef = useRef < HTMLInputElement > ( ) ;
2021-06-15 16:19:00 +00:00
const bufferHoursRef = useRef < HTMLInputElement > ( ) ;
const bufferMinsRef = useRef < HTMLInputElement > ( ) ;
2021-04-13 16:16:32 +00:00
2021-04-08 14:20:38 +00:00
if ( loading ) {
2021-07-08 09:23:22 +00:00
return < div className = "loader" > < / div > ;
2021-04-08 14:20:38 +00:00
}
function toggleAddModal() {
setShowAddModal ( ! showAddModal ) ;
}
2021-04-13 16:16:32 +00:00
function toggleChangeTimesModal() {
setShowChangeTimesModal ( ! showChangeTimesModal ) ;
}
2021-05-05 22:14:07 +00:00
const closeSuccessModal = ( ) = > { setSuccessModalOpen ( false ) ; router . replace ( router . asPath ) ; }
2021-04-20 14:09:31 +00:00
2021-04-13 16:16:32 +00:00
function convertMinsToHrsMins ( mins ) {
let h = Math . floor ( mins / 60 ) ;
let m = mins % 60 ;
h = h < 10 ? '0' + h : h ;
m = m < 10 ? '0' + m : m ;
return ` ${ h } : ${ m } ` ;
}
2021-04-08 14:20:38 +00:00
async function createEventTypeHandler ( event ) {
event . preventDefault ( ) ;
const enteredTitle = titleRef . current . value ;
2021-04-28 12:24:16 +00:00
const enteredSlug = slugRef . current . value ;
2021-04-08 14:20:38 +00:00
const enteredDescription = descriptionRef . current . value ;
const enteredLength = lengthRef . current . value ;
2021-04-28 09:23:30 +00:00
const enteredIsHidden = isHiddenRef . current . checked ;
2021-04-08 14:20:38 +00:00
// TODO: Add validation
const response = await fetch ( '/api/availability/eventtype' , {
method : 'POST' ,
2021-04-28 12:24:16 +00:00
body : JSON.stringify ( { title : enteredTitle , slug : enteredSlug , description : enteredDescription , length : enteredLength , hidden : enteredIsHidden } ) ,
2021-04-08 14:20:38 +00:00
headers : {
'Content-Type' : 'application/json'
}
} ) ;
2021-04-20 00:30:38 +00:00
if ( enteredTitle && enteredLength ) {
2021-04-29 12:05:50 +00:00
router . replace ( router . asPath ) ;
toggleAddModal ( ) ;
2021-04-20 00:30:38 +00:00
}
2021-04-08 14:20:38 +00:00
}
2021-04-13 16:16:32 +00:00
async function updateStartEndTimesHandler ( event ) {
event . preventDefault ( ) ;
const enteredStartHours = parseInt ( startHoursRef . current . value ) ;
const enteredStartMins = parseInt ( startMinsRef . current . value ) ;
const enteredEndHours = parseInt ( endHoursRef . current . value ) ;
const enteredEndMins = parseInt ( endMinsRef . current . value ) ;
2021-06-15 16:19:00 +00:00
const enteredBufferHours = parseInt ( bufferHoursRef . current . value ) ;
const enteredBufferMins = parseInt ( bufferMinsRef . current . value ) ;
2021-04-13 16:16:32 +00:00
const startMins = enteredStartHours * 60 + enteredStartMins ;
const endMins = enteredEndHours * 60 + enteredEndMins ;
2021-06-15 16:19:00 +00:00
const bufferMins = enteredBufferHours * 60 + enteredBufferMins ;
2021-04-13 16:16:32 +00:00
// TODO: Add validation
const response = await fetch ( '/api/availability/day' , {
method : 'PATCH' ,
2021-06-15 16:19:00 +00:00
body : JSON.stringify ( { start : startMins , end : endMins , buffer : bufferMins } ) ,
2021-04-13 16:16:32 +00:00
headers : {
'Content-Type' : 'application/json'
}
} ) ;
2021-04-20 14:09:31 +00:00
setShowChangeTimesModal ( false ) ;
setSuccessModalOpen ( true ) ;
2021-04-13 16:16:32 +00:00
}
2021-04-08 14:20:38 +00:00
return (
< div >
< Head >
< title > Availability | Calendso < / title >
< link rel = "icon" href = "/favicon.ico" / >
< / Head >
< Shell heading = "Availability" >
< div className = "mb-4 sm:flex sm:items-center sm:justify-between" >
< h3 className = "text-lg leading-6 font-medium text-white" >
Event Types
< / h3 >
< div className = "mt-3 sm:mt-0 sm:ml-4" >
2021-05-11 13:11:17 +00:00
< button onClick = { toggleAddModal } type = "button" className = "btn-sm btn-white" >
2021-04-08 14:20:38 +00:00
New event type
< / button >
< / div >
< / div >
2021-04-13 16:16:32 +00:00
< div className = "flex flex-col mb-8" >
2021-04-08 14:20:38 +00:00
< div className = "-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8" >
< div className = "py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8" >
2021-05-26 19:47:06 +00:00
< div className = "shadow overflow-hidden border-b border-gray-200 rounded-lg" >
2021-04-08 14:20:38 +00:00
< table className = "min-w-full divide-y divide-gray-200" >
< thead className = "bg-gray-50" >
< tr >
< th scope = "col" className = "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
Name
< / th >
< th scope = "col" className = "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
Description
< / th >
< th scope = "col" className = "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
Length
< / th >
< th scope = "col" className = "relative px-6 py-3" >
< span className = "sr-only" > Edit < / span >
< / th >
< / tr >
< / thead >
< tbody className = "bg-white divide-y divide-gray-200" >
2021-05-07 16:01:29 +00:00
{ props . types . map ( ( eventType ) = >
2021-04-08 14:20:38 +00:00
< tr >
< td className = "px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900" >
{ eventType . title }
2021-05-07 16:01:29 +00:00
{ eventType . hidden &&
2021-04-28 09:23:30 +00:00
< span className = "ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800" >
Hidden
< / span >
}
2021-04-08 14:20:38 +00:00
< / td >
< td className = "px-6 py-4 whitespace-nowrap text-sm text-gray-500" >
{ eventType . description }
< / td >
< td className = "px-6 py-4 whitespace-nowrap text-sm text-gray-500" >
{ eventType . length } minutes
< / td >
< td className = "px-6 py-4 whitespace-nowrap text-right text-sm font-medium" >
2021-05-05 20:01:56 +00:00
< Link href = { "/" + props . user . username + "/" + eventType . slug } > < a target = "_blank" className = "text-blue-600 hover:text-blue-900 mr-2" > View < / a > < / Link >
2021-04-08 14:20:38 +00:00
< Link href = { "/availability/event/" + eventType . id } > < a className = "text-blue-600 hover:text-blue-900" > Edit < / a > < / Link >
< / td >
< / tr >
) }
< / tbody >
< / table >
< / div >
< / div >
< / div >
< / div >
2021-07-08 09:23:22 +00:00
< div className = "flex" >
< div className = "w-1/2 mr-2 bg-white shadow rounded-lg" >
2021-04-13 16:16:32 +00:00
< div className = "px-4 py-5 sm:p-6" >
2021-07-08 09:23:22 +00:00
< h3 className = "text-lg leading-6 font-medium text-gray-900" >
Change the start and end times of your day
< / h3 >
< div className = "mt-2 max-w-xl text-sm text-gray-500" >
< p >
Currently , your day is set to start at { convertMinsToHrsMins ( props . user . startTime ) } and end at { convertMinsToHrsMins ( props . user . endTime ) } .
< / p >
< / div >
< div className = "mt-5" >
< button onClick = { toggleChangeTimesModal } type = "button" className = "btn btn-primary" >
Change available times
< / button >
< / div >
< / div >
< / div >
< div className = "w-1/2 ml-2 bg-white shadow rounded-lg" >
< div className = "px-4 py-5 sm:p-6" >
< h3 className = "text-lg leading-6 font-medium text-gray-900" >
Something doesn ' t look right ?
< / h3 >
< div className = "mt-2 max-w-xl text-sm text-gray-500" >
< p >
Troubleshoot your availability to explore why your times are showing as they are .
< / p >
< / div >
< div className = "mt-5" >
< Link href = "/availability/troubleshoot" >
< a className = "btn btn-primary" >
Launch troubleshooter
< / a >
< / Link >
< / div >
2021-04-13 16:16:32 +00:00
< / div >
2021-07-08 09:23:22 +00:00
< / div >
2021-04-13 16:16:32 +00:00
< / div >
2021-05-07 16:01:29 +00:00
{ showAddModal &&
2021-04-08 14:20:38 +00:00
< div className = "fixed z-10 inset-0 overflow-y-auto" aria - labelledby = "modal-title" role = "dialog" aria - modal = "true" >
< div className = "flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0" >
< div className = "fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria - hidden = "true" > < / div >
< span className = "hidden sm:inline-block sm:align-middle sm:h-screen" aria - hidden = "true" > & # 8203 ; < / span >
< div className = "inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6" >
< div className = "sm:flex sm:items-start mb-4" >
< div className = "mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10" >
2021-04-21 10:10:27 +00:00
< PlusIcon className = "h-6 w-6 text-blue-600" / >
2021-04-08 14:20:38 +00:00
< / div >
< div className = "mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" >
< h3 className = "text-lg leading-6 font-medium text-gray-900" id = "modal-title" >
Add a new event type
< / h3 >
< div >
< p className = "text-sm text-gray-500" >
Create a new event type for people to book times with .
< / p >
< / div >
< / div >
< / div >
< form onSubmit = { createEventTypeHandler } >
< div >
< div className = "mb-4" >
< label htmlFor = "title" className = "block text-sm font-medium text-gray-700" > Title < / label >
< div className = "mt-1" >
2021-05-05 21:58:42 +00:00
< input ref = { titleRef } type = "text" name = "title" id = "title" required className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "Quick Chat" / >
2021-04-08 14:20:38 +00:00
< / div >
< / div >
2021-04-28 12:24:16 +00:00
< div className = "mb-4" >
< label htmlFor = "slug" className = "block text-sm font-medium text-gray-700" > URL < / label >
< div className = "mt-1" >
< div className = "flex rounded-md shadow-sm" >
< span className = "inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm" >
{ location . hostname } / { props . user . username } /
< / span >
< input
ref = { slugRef }
type = "text"
name = "slug"
id = "slug"
2021-05-05 21:58:42 +00:00
required
2021-04-28 12:24:16 +00:00
className = "flex-1 block w-full focus:ring-blue-500 focus:border-blue-500 min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
/ >
< / div >
< / div >
< / div >
2021-04-08 14:20:38 +00:00
< div className = "mb-4" >
< label htmlFor = "description" className = "block text-sm font-medium text-gray-700" > Description < / label >
< div className = "mt-1" >
< textarea ref = { descriptionRef } name = "description" id = "description" className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "A quick video meeting." > < / textarea >
< / div >
< / div >
< div className = "mb-4" >
< label htmlFor = "length" className = "block text-sm font-medium text-gray-700" > Length < / label >
< div className = "mt-1 relative rounded-md shadow-sm" >
2021-05-05 21:58:42 +00:00
< input ref = { lengthRef } type = "number" name = "length" id = "length" required className = "focus:ring-blue-500 focus:border-blue-500 block w-full pr-20 sm:text-sm border-gray-300 rounded-md" placeholder = "15" / >
2021-04-08 14:20:38 +00:00
< div className = "absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm" >
minutes
< / div >
< / div >
< / div >
< / div >
2021-04-28 09:23:30 +00:00
< div className = "my-8" >
< div className = "relative flex items-start" >
< div className = "flex items-center h-5" >
< input
ref = { isHiddenRef }
id = "ishidden"
name = "ishidden"
type = "checkbox"
className = "focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
/ >
< / div >
< div className = "ml-3 text-sm" >
< label htmlFor = "ishidden" className = "font-medium text-gray-700" >
Hide this event type
< / label >
< p className = "text-gray-500" > Hide the event type from your page , so it can only be booked through it ' s URL . < / p >
< / div >
< / div >
< / div >
2021-04-20 00:30:38 +00:00
{ /* TODO: Add an error message when required input fields empty*/ }
2021-04-08 14:20:38 +00:00
< div className = "mt-5 sm:mt-4 sm:flex sm:flex-row-reverse" >
< button type = "submit" className = "btn btn-primary" >
Create
< / button >
2021-04-19 14:04:04 +00:00
< button onClick = { toggleAddModal } type = "button" className = "btn btn-white mr-2" >
2021-04-13 16:16:32 +00:00
Cancel
< / button >
< / div >
< / form >
< / div >
< / div >
< / div >
}
2021-05-07 16:01:29 +00:00
{ showChangeTimesModal &&
2021-04-13 16:16:32 +00:00
< div className = "fixed z-10 inset-0 overflow-y-auto" aria - labelledby = "modal-title" role = "dialog" aria - modal = "true" >
< div className = "flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0" >
< div className = "fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria - hidden = "true" > < / div >
< span className = "hidden sm:inline-block sm:align-middle sm:h-screen" aria - hidden = "true" > & # 8203 ; < / span >
< div className = "inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6" >
< div className = "sm:flex sm:items-start mb-4" >
< div className = "mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10" >
2021-04-21 10:10:27 +00:00
< ClockIcon className = "h-6 w-6 text-blue-600" / >
2021-04-13 16:16:32 +00:00
< / div >
< div className = "mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" >
< h3 className = "text-lg leading-6 font-medium text-gray-900" id = "modal-title" >
Change your available times
< / h3 >
< div >
< p className = "text-sm text-gray-500" >
2021-06-15 16:19:00 +00:00
Set the start and end time of your day and a minimum buffer between your meetings .
2021-04-13 16:16:32 +00:00
< / p >
< / div >
< / div >
< / div >
< form onSubmit = { updateStartEndTimesHandler } >
< div className = "flex mb-4" >
< label className = "w-1/4 pt-2 block text-sm font-medium text-gray-700" > Start time < / label >
< div >
< label htmlFor = "hours" className = "sr-only" > Hours < / label >
< input ref = { startHoursRef } type = "number" name = "hours" id = "hours" className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "9" defaultValue = { convertMinsToHrsMins ( props . user . startTime ) . split ( ":" ) [ 0 ] } / >
< / div >
< span className = "mx-2 pt-1" > : < / span >
< div >
< label htmlFor = "minutes" className = "sr-only" > Minutes < / label >
< input ref = { startMinsRef } type = "number" name = "minutes" id = "minutes" className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "30" defaultValue = { convertMinsToHrsMins ( props . user . startTime ) . split ( ":" ) [ 1 ] } / >
< / div >
< / div >
2021-06-15 16:19:00 +00:00
< div className = "flex mb-4" >
2021-04-13 16:16:32 +00:00
< label className = "w-1/4 pt-2 block text-sm font-medium text-gray-700" > End time < / label >
< div >
< label htmlFor = "hours" className = "sr-only" > Hours < / label >
< input ref = { endHoursRef } type = "number" name = "hours" id = "hours" className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "17" defaultValue = { convertMinsToHrsMins ( props . user . endTime ) . split ( ":" ) [ 0 ] } / >
< / div >
< span className = "mx-2 pt-1" > : < / span >
< div >
< label htmlFor = "minutes" className = "sr-only" > Minutes < / label >
< input ref = { endMinsRef } type = "number" name = "minutes" id = "minutes" className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "30" defaultValue = { convertMinsToHrsMins ( props . user . endTime ) . split ( ":" ) [ 1 ] } / >
< / div >
< / div >
2021-06-15 16:19:00 +00:00
< div className = "flex mb-4" >
< label className = "w-1/4 pt-2 block text-sm font-medium text-gray-700" > Buffer < / label >
< div >
< label htmlFor = "hours" className = "sr-only" > Hours < / label >
< input ref = { bufferHoursRef } type = "number" name = "hours" id = "hours" className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "0" defaultValue = { convertMinsToHrsMins ( props . user . bufferTime ) . split ( ":" ) [ 0 ] } / >
< / div >
< span className = "mx-2 pt-1" > : < / span >
< div >
< label htmlFor = "minutes" className = "sr-only" > Minutes < / label >
< input ref = { bufferMinsRef } type = "number" name = "minutes" id = "minutes" className = "shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder = "10" defaultValue = { convertMinsToHrsMins ( props . user . bufferTime ) . split ( ":" ) [ 1 ] } / >
< / div >
< / div >
2021-04-13 16:16:32 +00:00
< div className = "mt-5 sm:mt-4 sm:flex sm:flex-row-reverse" >
< button type = "submit" className = "btn btn-primary" >
Update
< / button >
< button onClick = { toggleChangeTimesModal } type = "button" className = "btn btn-white mr-2" >
2021-04-08 14:20:38 +00:00
Cancel
< / button >
< / div >
< / form >
< / div >
< / div >
< / div >
}
2021-04-20 14:09:31 +00:00
< Modal heading = "Start and end times changed" description = "The start and end times for your day have been changed successfully." open = { successModalOpen } handleClose = { closeSuccessModal } / >
2021-04-08 14:20:38 +00:00
< / Shell >
< / div >
) ;
}
export async function getServerSideProps ( context ) {
const session = await getSession ( context ) ;
2021-05-07 16:01:29 +00:00
if ( ! session ) {
return { redirect : { permanent : false , destination : '/auth/login' } } ;
}
2021-04-08 14:20:38 +00:00
const user = await prisma . user . findFirst ( {
where : {
email : session.user.email ,
} ,
select : {
2021-04-13 16:16:32 +00:00
id : true ,
2021-04-28 09:27:45 +00:00
username : true ,
2021-04-13 16:16:32 +00:00
startTime : true ,
2021-06-15 16:19:00 +00:00
endTime : true ,
bufferTime : true
2021-04-08 14:20:38 +00:00
}
} ) ;
const types = await prisma . eventType . findMany ( {
where : {
userId : user.id ,
} ,
select : {
id : true ,
title : true ,
2021-04-28 12:24:16 +00:00
slug : true ,
2021-04-08 14:20:38 +00:00
description : true ,
2021-04-28 09:23:30 +00:00
length : true ,
hidden : true
2021-04-08 14:20:38 +00:00
}
} ) ;
return {
2021-04-13 16:16:32 +00:00
props : { user , types } , // will be passed to the page component as props
2021-04-08 14:20:38 +00:00
}
2021-06-15 16:19:00 +00:00
}