2021-04-11 17:12:18 +00:00
|
|
|
import {useEffect, useState} from 'react';
|
|
|
|
import Head from 'next/head';
|
|
|
|
import Link from 'next/link';
|
|
|
|
import prisma from '../../lib/prisma';
|
|
|
|
import { useRouter } from 'next/router';
|
|
|
|
const dayjs = require('dayjs');
|
|
|
|
const isSameOrBefore = require('dayjs/plugin/isSameOrBefore');
|
2021-04-11 20:51:58 +00:00
|
|
|
const isBetween = require('dayjs/plugin/isBetween');
|
2021-04-11 17:12:18 +00:00
|
|
|
dayjs.extend(isSameOrBefore);
|
2021-04-11 20:51:58 +00:00
|
|
|
dayjs.extend(isBetween);
|
2021-03-22 13:48:48 +00:00
|
|
|
|
|
|
|
export default function Type(props) {
|
|
|
|
// Initialise state
|
|
|
|
const [selectedDate, setSelectedDate] = useState('');
|
|
|
|
const [selectedMonth, setSelectedMonth] = useState(dayjs().month());
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [busy, setBusy] = useState([]);
|
|
|
|
|
2021-04-07 20:37:41 +00:00
|
|
|
// Get router variables
|
2021-04-11 17:12:18 +00:00
|
|
|
const router = useRouter();
|
|
|
|
const { user } = router.query;
|
2021-04-07 20:37:41 +00:00
|
|
|
|
2021-03-22 13:48:48 +00:00
|
|
|
// Handle month changes
|
|
|
|
const incrementMonth = () => {
|
2021-04-11 17:12:18 +00:00
|
|
|
setSelectedMonth(selectedMonth + 1);
|
2021-03-22 13:48:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const decrementMonth = () => {
|
2021-04-11 17:12:18 +00:00
|
|
|
setSelectedMonth(selectedMonth - 1);
|
2021-03-22 13:48:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set up calendar
|
2021-04-11 17:12:18 +00:00
|
|
|
var daysInMonth = dayjs().month(selectedMonth).daysInMonth();
|
|
|
|
var days = [];
|
2021-03-22 13:48:48 +00:00
|
|
|
for (let i = 1; i <= daysInMonth; i++) {
|
2021-04-11 17:12:18 +00:00
|
|
|
days.push(i);
|
2021-03-22 13:48:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const calendar = days.map((day) =>
|
2021-04-09 15:33:49 +00:00
|
|
|
<button key={day} onClick={(e) => setSelectedDate(dayjs().month(selectedMonth).date(day).format("YYYY-MM-DD"))} disabled={selectedMonth < dayjs().format('MM') && dayjs().month(selectedMonth).format("D") > day} className={"text-center w-10 h-10 rounded-full mx-auto " + (dayjs().isSameOrBefore(dayjs().date(day).month(selectedMonth)) ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-400 font-light') + (dayjs(selectedDate).month(selectedMonth).format("D") == day ? ' bg-blue-600 text-white-important' : '')}>
|
2021-03-22 13:48:48 +00:00
|
|
|
{day}
|
|
|
|
</button>
|
|
|
|
);
|
|
|
|
|
|
|
|
// Handle date change
|
|
|
|
useEffect(async () => {
|
|
|
|
setLoading(true);
|
2021-04-11 17:12:18 +00:00
|
|
|
const res = await fetch('/api/availability/' + user + '?date=' + dayjs(selectedDate).format("YYYY-MM-DD"));
|
|
|
|
const data = await res.json();
|
|
|
|
setBusy(data.primary.busy);
|
|
|
|
setLoading(false);
|
2021-03-22 13:48:48 +00:00
|
|
|
}, [selectedDate]);
|
|
|
|
|
|
|
|
// Set up timeslots
|
2021-04-11 17:12:18 +00:00
|
|
|
let times = [];
|
2021-03-22 13:48:48 +00:00
|
|
|
|
|
|
|
// If we're looking at availability throughout the current date, work out the current number of minutes elapsed throughout the day
|
|
|
|
if (selectedDate == dayjs().format("YYYY-MM-DD")) {
|
|
|
|
var i = (parseInt(dayjs().startOf('hour').format('H') * 60) + parseInt(dayjs().startOf('hour').format('m')));
|
|
|
|
} else {
|
2021-04-13 16:16:32 +00:00
|
|
|
var i = props.user.startTime;
|
2021-03-22 13:48:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Until day end, push new times every x minutes
|
2021-04-13 16:16:32 +00:00
|
|
|
for (;i < props.user.endTime; i += parseInt(props.eventType.length)) {
|
2021-04-11 17:12:18 +00:00
|
|
|
times.push(dayjs(selectedDate).hour(Math.floor(i / 60)).minute(i % 60).startOf(props.eventType.length, 'minute').add(props.eventType.length, 'minute').format("YYYY-MM-DD HH:mm:ss"));
|
2021-03-22 13:48:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check for conflicts
|
2021-04-11 20:51:58 +00:00
|
|
|
for(i = times.length - 1; i >= 0; i -= 1) {
|
2021-03-22 13:48:48 +00:00
|
|
|
busy.forEach(busyTime => {
|
2021-04-11 17:12:18 +00:00
|
|
|
let startTime = dayjs(busyTime.start);
|
|
|
|
let endTime = dayjs(busyTime.end);
|
2021-03-22 13:48:48 +00:00
|
|
|
|
2021-04-11 20:51:58 +00:00
|
|
|
// Check if time has passed
|
|
|
|
if (dayjs(times[i]).isBefore(dayjs())) {
|
|
|
|
times.splice(i, 1);
|
|
|
|
}
|
|
|
|
|
2021-03-22 13:48:48 +00:00
|
|
|
// Check if start times are the same
|
2021-04-11 20:51:58 +00:00
|
|
|
if (dayjs(times[i]).format('HH:mm') == startTime.format('HH:mm')) {
|
|
|
|
times.splice(i, 1);
|
2021-03-22 13:48:48 +00:00
|
|
|
}
|
|
|
|
|
2021-04-11 20:51:58 +00:00
|
|
|
// Check if time is between start and end times
|
|
|
|
if (dayjs(times[i]).isBetween(startTime, endTime)) {
|
|
|
|
times.splice(i, 1);
|
|
|
|
}
|
2021-03-22 13:48:48 +00:00
|
|
|
});
|
2021-04-11 20:51:58 +00:00
|
|
|
}
|
2021-03-22 13:48:48 +00:00
|
|
|
|
|
|
|
// Display available times
|
|
|
|
const availableTimes = times.map((time) =>
|
2021-04-09 15:33:49 +00:00
|
|
|
<div key={time}>
|
2021-04-14 21:17:19 +00:00
|
|
|
<Link href={"/" + props.user.username + "/book?date=" + selectedDate + "T" + dayjs(time).format("HH:mm:ss") + "&type=" + props.eventType.id}>
|
2021-03-22 13:48:48 +00:00
|
|
|
<a key={time} className="block font-medium mb-4 text-blue-600 border border-blue-600 rounded hover:text-white hover:bg-blue-600 py-4">{dayjs(time).format("hh:mma")}</a>
|
|
|
|
</Link>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<Head>
|
2021-03-31 20:10:53 +00:00
|
|
|
<title>{props.eventType.title} | {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={"mx-auto my-24 transition-max-width ease-in-out duration-500 " + (selectedDate ? 'max-w-6xl' : 'max-w-3xl')}>
|
2021-04-10 11:21:44 +00:00
|
|
|
<div className="bg-white overflow-hidden shadow rounded-lg md:max-h-96">
|
2021-03-22 13:48:48 +00:00
|
|
|
<div className="sm:flex px-4 py-5 sm:p-6">
|
|
|
|
<div className={"sm:border-r " + (selectedDate ? 'sm:w-1/3' : 'sm:w-1/2')}>
|
2021-03-31 20:10:53 +00:00
|
|
|
{props.user.avatar && <img src={props.user.avatar} alt="Avatar" 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-4">
|
|
|
|
<svg className="inline-block w-4 h-4 mr-1 -mt-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
|
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clipRule="evenodd" />
|
|
|
|
</svg>
|
|
|
|
{props.eventType.length} minutes
|
|
|
|
</p>
|
|
|
|
<p className="text-gray-600">{props.eventType.description}</p>
|
|
|
|
</div>
|
|
|
|
<div className={"mt-8 sm:mt-0 " + (selectedDate ? 'sm:w-1/3 border-r sm:px-4' : 'sm:w-1/2 sm:pl-4')}>
|
|
|
|
<div className="flex text-gray-600 font-light text-xl mb-4 ml-2">
|
|
|
|
<span className="w-1/2">{dayjs().month(selectedMonth).format("MMMM YYYY")}</span>
|
|
|
|
<div className="w-1/2 text-right">
|
|
|
|
<button onClick={decrementMonth} className={"mr-4 " + (selectedMonth < dayjs().format('MM') && 'text-gray-400')} disabled={selectedMonth < dayjs().format('MM')}>
|
|
|
|
<svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
|
|
<path fillRule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clipRule="evenodd" />
|
|
|
|
</svg>
|
|
|
|
</button>
|
|
|
|
<button onClick={incrementMonth}>
|
|
|
|
<svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
|
|
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
|
|
|
</svg>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="grid grid-cols-7 gap-y-4 text-center">
|
|
|
|
{calendar}
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-04-10 11:21:44 +00:00
|
|
|
{selectedDate && <div className="sm:pl-4 mt-8 sm:mt-0 text-center sm:w-1/3 md:max-h-96 overflow-y-scroll">
|
2021-03-22 13:48:48 +00:00
|
|
|
<div className="text-gray-600 font-light text-xl mb-4 text-left">
|
|
|
|
<span className="w-1/2">{dayjs(selectedDate).format("dddd DD MMMM YYYY")}</span>
|
|
|
|
</div>
|
|
|
|
{!loading ? availableTimes : <div className="loader"></div>}
|
2021-04-09 15:33:49 +00:00
|
|
|
</div>}
|
2021-03-22 13:48:48 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</main>
|
|
|
|
</div>
|
2021-04-11 17:12:18 +00:00
|
|
|
);
|
2021-03-22 13:48:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function getServerSideProps(context) {
|
|
|
|
const user = await prisma.user.findFirst({
|
|
|
|
where: {
|
|
|
|
username: context.query.user,
|
|
|
|
},
|
|
|
|
select: {
|
|
|
|
username: true,
|
|
|
|
name: true,
|
|
|
|
bio: true,
|
|
|
|
avatar: true,
|
2021-04-13 16:16:32 +00:00
|
|
|
eventTypes: true,
|
|
|
|
startTime: true,
|
|
|
|
endTime: true
|
2021-03-22 13:48:48 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const eventType = await prisma.eventType.findUnique({
|
|
|
|
where: {
|
|
|
|
id: parseInt(context.query.type),
|
|
|
|
},
|
|
|
|
select: {
|
|
|
|
id: true,
|
|
|
|
title: true,
|
|
|
|
description: true,
|
|
|
|
length: true
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
props: {
|
|
|
|
user,
|
|
|
|
eventType
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|