Create, edit and delete event types
This commit is contained in:
		
							parent
							
								
									d209d3dd04
								
							
						
					
					
						commit
						2c4b5c2846
					
				
					 4 changed files with 445 additions and 3 deletions
				
			
		|  | @ -28,10 +28,10 @@ export default function Shell(props) { | |||
|                                             <Link href="/"> | ||||
|                                                 <a className={router.pathname == "/" ? "bg-gray-700 text-white px-3 py-2 rounded-md text-sm font-medium" : "text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Dashboard</a> | ||||
|                                             </Link> | ||||
|                                             <Link href="/"> | ||||
|                                             {/* <Link href="/"> | ||||
|                                                 <a className={router.pathname.startsWith("/bookings") ? "bg-gray-700 text-white px-3 py-2 rounded-md text-sm font-medium" : "text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Bookings</a> | ||||
|                                             </Link> | ||||
|                                             <Link href="/"> | ||||
|                                             </Link> */} | ||||
|                                             <Link href="/availability"> | ||||
|                                                 <a className={router.pathname.startsWith("/availability") ? "bg-gray-700 text-white px-3 py-2 rounded-md text-sm font-medium" : "text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Availability</a> | ||||
|                                             </Link> | ||||
|                                             <Link href="/integrations"> | ||||
|  |  | |||
							
								
								
									
										97
									
								
								pages/api/availability/eventtype.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								pages/api/availability/eventtype.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,97 @@ | |||
| import type { NextApiRequest, NextApiResponse } from 'next'; | ||||
| import { getSession } from 'next-auth/client'; | ||||
| 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; | ||||
|     } | ||||
| 
 | ||||
|     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 description = req.body.description; | ||||
|         const length = parseInt(req.body.length); | ||||
| 
 | ||||
|         const createEventType = await prisma.eventType.create({ | ||||
|             data: { | ||||
|                 title: title, | ||||
|                 description: description, | ||||
|                 length: length, | ||||
|                 userId: user.id, | ||||
|             }, | ||||
|         }); | ||||
| 
 | ||||
|         res.status(200).json({message: 'Event created successfully'}); | ||||
|     } | ||||
| 
 | ||||
|     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 (!user) { res.status(404).json({message: 'User not found'}); return; } | ||||
| 
 | ||||
|         const id = req.body.id; | ||||
|         const title = req.body.title; | ||||
|         const description = req.body.description; | ||||
|         const length = parseInt(req.body.length); | ||||
| 
 | ||||
|         const updateEventType = await prisma.eventType.update({ | ||||
|             where: { | ||||
|                 id: id, | ||||
|             }, | ||||
|             data: { | ||||
|                 title: title, | ||||
|                 description: description, | ||||
|                 length: length | ||||
|             }, | ||||
|         }); | ||||
| 
 | ||||
|         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, | ||||
|             }, | ||||
|         }); | ||||
| 
 | ||||
|         res.status(200).json({message: 'Event deleted successfully'}); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										141
									
								
								pages/availability/event/[type].tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								pages/availability/event/[type].tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,141 @@ | |||
| import Head from 'next/head'; | ||||
| import Link from 'next/link'; | ||||
| import { useRouter } from 'next/router'; | ||||
| import { useRef } from 'react'; | ||||
| import prisma from '../../../lib/prisma'; | ||||
| import Shell from '../../../components/Shell'; | ||||
| import { useSession, getSession } from 'next-auth/client'; | ||||
| 
 | ||||
| export default function EventType(props) { | ||||
|     const router = useRouter(); | ||||
|     const [ session, loading ] = useSession(); | ||||
|     const titleRef = useRef(); | ||||
|     const descriptionRef = useRef(); | ||||
|     const lengthRef = useRef(); | ||||
| 
 | ||||
|     if (loading) { | ||||
|         return <p className="text-gray-400">Loading...</p>; | ||||
|     } else { | ||||
|         if (!session) { | ||||
|             window.location.href = "/auth/login"; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async function updateEventTypeHandler(event) { | ||||
|         event.preventDefault(); | ||||
| 
 | ||||
|         const enteredTitle = titleRef.current.value; | ||||
|         const enteredDescription = descriptionRef.current.value; | ||||
|         const enteredLength = lengthRef.current.value; | ||||
| 
 | ||||
|         // TODO: Add validation
 | ||||
| 
 | ||||
|         const response = await fetch('/api/availability/eventtype', { | ||||
|             method: 'PATCH', | ||||
|             body: JSON.stringify({id: props.eventType.id, title: enteredTitle, description: enteredDescription, length: enteredLength}), | ||||
|             headers: { | ||||
|                 'Content-Type': 'application/json' | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         console.log(response); | ||||
|     } | ||||
| 
 | ||||
|     async function deleteEventTypeHandler(event) { | ||||
|         event.preventDefault(); | ||||
| 
 | ||||
|         const response = await fetch('/api/availability/eventtype', { | ||||
|             method: 'DELETE', | ||||
|             body: JSON.stringify({id: props.eventType.id}), | ||||
|             headers: { | ||||
|                 'Content-Type': 'application/json' | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         router.push('/availability'); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <Head> | ||||
|                 <title>{props.eventType.title} | Event Type | Calendso</title> | ||||
|                 <link rel="icon" href="/favicon.ico" /> | ||||
|             </Head> | ||||
|             <Shell heading={'Event Type - ' + props.eventType.title}> | ||||
|                 <div className="grid grid-cols-3 gap-4"> | ||||
|                     <div className="col-span-2"> | ||||
|                         <div className="bg-white overflow-hidden shadow rounded-lg"> | ||||
|                             <div className="px-4 py-5 sm:p-6"> | ||||
|                                 <form onSubmit={updateEventTypeHandler}> | ||||
|                                     <div className="mb-4"> | ||||
|                                         <label htmlFor="title" className="block text-sm font-medium text-gray-700">Title</label> | ||||
|                                         <div className="mt-1"> | ||||
|                                             <input ref={titleRef} type="text" name="title" id="title" 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" defaultValue={props.eventType.title} /> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                     <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." defaultValue={props.eventType.description}></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"> | ||||
|                                             <input ref={lengthRef} type="number" name="length" id="length" className="focus:ring-blue-500 focus:border-blue-500 block w-full pr-20 sm:text-sm border-gray-300 rounded-md" placeholder="15" defaultValue={props.eventType.length} /> | ||||
|                                             <div className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm"> | ||||
|                                                 minutes | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                     <button type="submit" className="btn btn-primary">Update</button> | ||||
|                                     <Link href="/availability"><a className="ml-2 btn btn-white">Cancel</a></Link> | ||||
|                                 </form> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <div className="bg-white shadow sm:rounded-lg"> | ||||
|                             <div className="px-4 py-5 sm:p-6"> | ||||
|                                 <h3 className="text-lg leading-6 font-medium text-gray-900"> | ||||
|                                     Delete this event type | ||||
|                                 </h3> | ||||
|                                 <div className="mt-2 max-w-xl text-sm text-gray-500"> | ||||
|                                     <p> | ||||
|                                         Once you delete this event type, it will be permanently removed. | ||||
|                                     </p> | ||||
|                                 </div> | ||||
|                                 <div className="mt-5"> | ||||
|                                     <button onClick={deleteEventTypeHandler} type="button" className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"> | ||||
|                                         Delete event type | ||||
|                                     </button> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </Shell> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| export async function getServerSideProps(context) { | ||||
| 
 | ||||
|     const eventType = await prisma.eventType.findUnique({ | ||||
|         where: { | ||||
|           id: parseInt(context.query.type), | ||||
|         }, | ||||
|         select: { | ||||
|             id: true, | ||||
|             title: true, | ||||
|             description: true, | ||||
|             length: true | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     return { | ||||
|         props: { | ||||
|             eventType | ||||
|         }, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										204
									
								
								pages/availability/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								pages/availability/index.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,204 @@ | |||
| import Head from 'next/head'; | ||||
| import Link from 'next/link'; | ||||
| import prisma from '../../lib/prisma'; | ||||
| import Shell from '../../components/Shell'; | ||||
| import Router from 'next/router'; | ||||
| import { useRef } from 'react'; | ||||
| import { useState } from 'react'; | ||||
| import { useSession, getSession } from 'next-auth/client'; | ||||
| 
 | ||||
| export default function Availability(props) { | ||||
|     const [ session, loading ] = useSession(); | ||||
|     const [showAddModal, setShowAddModal] = useState(false); | ||||
|     const titleRef = useRef(); | ||||
|     const descriptionRef = useRef(); | ||||
|     const lengthRef = useRef(); | ||||
| 
 | ||||
|     if (loading) { | ||||
|         return <p className="text-gray-400">Loading...</p>; | ||||
|     } else { | ||||
|         if (!session) { | ||||
|             window.location.href = "/auth/login"; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function toggleAddModal() { | ||||
|         setShowAddModal(!showAddModal); | ||||
|     } | ||||
| 
 | ||||
|     async function createEventTypeHandler(event) { | ||||
|         event.preventDefault(); | ||||
| 
 | ||||
|         const enteredTitle = titleRef.current.value; | ||||
|         const enteredDescription = descriptionRef.current.value; | ||||
|         const enteredLength = lengthRef.current.value; | ||||
| 
 | ||||
|         // TODO: Add validation
 | ||||
| 
 | ||||
|         const response = await fetch('/api/availability/eventtype', { | ||||
|             method: 'POST', | ||||
|             body: JSON.stringify({title: enteredTitle, description: enteredDescription, length: enteredLength}), | ||||
|             headers: { | ||||
|                 'Content-Type': 'application/json' | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         console.log(response); | ||||
|         Router.reload(); | ||||
|     } | ||||
| 
 | ||||
|     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"> | ||||
|                         <button onClick={toggleAddModal} type="button" className="btn-sm btn-primary"> | ||||
|                             New event type | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div className="flex flex-col"> | ||||
|                     <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"> | ||||
|                             <div className="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"> | ||||
|                                 <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"> | ||||
|                                         {props.types.map((eventType) =>  | ||||
|                                             <tr> | ||||
|                                                 <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"> | ||||
|                                                     {eventType.title} | ||||
|                                                 </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"> | ||||
|                                                     <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> | ||||
|                 {showAddModal &&  | ||||
|                     <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">​</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"> | ||||
|                                         <svg className="h-6 w-6 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||||
|                                             <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" /> | ||||
|                                         </svg> | ||||
|                                     </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"> | ||||
|                                                 <input ref={titleRef} type="text" name="title" id="title" 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" /> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                         <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"> | ||||
|                                                 <input ref={lengthRef} type="number" name="length" id="length" className="focus:ring-blue-500 focus:border-blue-500 block w-full pr-20 sm:text-sm border-gray-300 rounded-md" placeholder="15" /> | ||||
|                                                 <div className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm"> | ||||
|                                                     minutes | ||||
|                                                 </div> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                     <div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> | ||||
|                                         <button type="submit" className="btn btn-primary"> | ||||
|                                             Create | ||||
|                                         </button> | ||||
|                                         <button onClick={toggleAddModal} type="button" className="btn btn-white mr-2"> | ||||
|                                             Cancel | ||||
|                                         </button> | ||||
|                                     </div> | ||||
|                                 </form> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 } | ||||
|             </Shell> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| export async function getServerSideProps(context) { | ||||
|     const session = await getSession(context); | ||||
| 
 | ||||
|     const user = await prisma.user.findFirst({ | ||||
|         where: { | ||||
|             email: session.user.email, | ||||
|         }, | ||||
|         select: { | ||||
|             id: true | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     const types = await prisma.eventType.findMany({ | ||||
|         where: { | ||||
|             userId: user.id, | ||||
|         }, | ||||
|         select: { | ||||
|             id: true, | ||||
|             title: true, | ||||
|             description: true, | ||||
|             length: true | ||||
|         } | ||||
|     }); | ||||
|     return { | ||||
|       props: {types}, // will be passed to the page component as props
 | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in a new issue
	
	 Bailey Pumfleet
						Bailey Pumfleet