Skip to content

Commit

Permalink
Resolved conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
Nico-J committed Jul 7, 2021
2 parents 313ee03 + ad00e5f commit 3c09837
Show file tree
Hide file tree
Showing 37 changed files with 4,008 additions and 1,220 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["next/babel"]
}
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# See http://EditorConfig.org for more information

# top-most EditorConfig file
root = true

# Every File
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"env": {
"browser": true,
"node": true,
"es6": true
"es6": true,
"jest": true
},
"settings": {
"react": {
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ Let's face it: Calendly and other scheduling tools are awesome. It made our live
### Product of the Month: April
#### Support us on [Product Hunt](https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso)

<a href="https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=291910&theme=light&period=monthly" alt="Calendso - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/calendso?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=291910&theme=light" alt="Calendso - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/stories/how-this-open-source-calendly-alternative-rocketed-to-product-of-the-day" target="_blank"><img src="https://calendso.com/maker-grant.svg" alt="Calendso - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>


<a href="https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=291910&theme=light&period=monthly" alt="Calendso - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>

### Built With

Expand Down Expand Up @@ -107,7 +108,7 @@ You will also need Google API credentials. You can get this from the [Google API

5. Set up the database using the Prisma schema (found in `prisma/schema.prisma`)
```sh
npx prisma db push --preview-feature
npx prisma db push
```
6. Run (in development mode)
```sh
Expand Down Expand Up @@ -157,7 +158,11 @@ You will also need Google API credentials. You can get this from the [Google API
5. Enjoy the new version.
<!-- DEPLOYMENT -->
## Deployment

### Docker
The Docker configuration for Calendso is an effort powered by people within the community. Calendso does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk.

The Docker configuration can be found [in our docker repository](https://github.com/calendso/docker).
### Railway
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fcalendso%2Fcalendso&plugins=postgresql&envs=GOOGLE_API_CREDENTIALS%2CBASE_URL%2CNEXTAUTH_URL%2CPORT&BASE_URLDefault=http%3A%2F%2Flocalhost%3A3000&NEXTAUTH_URLDefault=http%3A%2F%2Flocalhost%3A3000&PORTDefault=3000)

You can deploy Calendso on [Railway](https://railway.app/) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Calendso on their platform.
Expand Down
110 changes: 19 additions & 91 deletions components/booking/AvailableTimes.tsx
Original file line number Diff line number Diff line change
@@ -1,112 +1,40 @@
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
dayjs.extend(isBetween);
import { useEffect, useState, useMemo } from "react";
import getSlots from "../../lib/slots";
import Link from "next/link";
import { timeZone } from "../../lib/clock";
import { useRouter } from "next/router";
import Slots from "./Slots";
import { ExclamationIcon } from "@heroicons/react/solid";

const AvailableTimes = (props) => {
const AvailableTimes = ({ date, eventLength, eventTypeId, workingHours, timeFormat, user }) => {
const router = useRouter();
const { user, rescheduleUid } = router.query;
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(false);

const times = useMemo(() => {
const slots = getSlots({
calendarTimeZone: props.user.timeZone,
selectedTimeZone: timeZone(),
eventLength: props.eventType.length,
selectedDate: props.date,
dayStartTime: props.user.startTime,
dayEndTime: props.user.endTime,
});

return slots;
}, [props.date]);

const handleAvailableSlots = (busyTimes: []) => {
// Check for conflicts
for (let i = times.length - 1; i >= 0; i -= 1) {
busyTimes.forEach((busyTime) => {
const startTime = dayjs(busyTime.start);
const endTime = dayjs(busyTime.end);

// Check if start times are the same
if (dayjs(times[i]).format("HH:mm") == startTime.format("HH:mm")) {
times.splice(i, 1);
}

// Check if time is between start and end times
if (dayjs(times[i]).isBetween(startTime, endTime)) {
times.splice(i, 1);
}

// Check if slot end time is between start and end time
if (dayjs(times[i]).add(props.eventType.length, "minutes").isBetween(startTime, endTime)) {
times.splice(i, 1);
}

// Check if startTime is between slot
if (startTime.isBetween(dayjs(times[i]), dayjs(times[i]).add(props.eventType.length, "minutes"))) {
times.splice(i, 1);
}
});
}
// Display available times
setLoaded(true);
};

// Re-render only when invitee changes date
useEffect(() => {
setLoaded(false);
setError(false);
fetch(
`/api/availability/${user}?dateFrom=${props.date.startOf("day").utc().format()}&dateTo=${props.date
.endOf("day")
.utc()
.format()}`
)
.then((res) => res.json())
.then(handleAvailableSlots)
.catch((e) => {
console.error(e);
setError(true);
});
}, [props.date]);

const { rescheduleUid } = router.query;
const { slots, isFullyBooked, hasErrors } = Slots({ date, eventLength, workingHours });
return (
<div className="sm:pl-4 mt-8 sm:mt-0 text-center sm:w-1/3 md:max-h-97 overflow-y-auto">
<div className="text-gray-600 font-light text-xl mb-4 text-left">
<span className="w-1/2">{props.date.format("dddd DD MMMM YYYY")}</span>
<span className="w-1/2">{date.format("dddd DD MMMM YYYY")}</span>
</div>
{!error &&
loaded &&
times.length > 0 &&
times.map((time) => (
<div key={dayjs(time).utc().format()}>
{slots.length > 0 &&
slots.map((slot) => (
<div key={slot.format()}>
<Link
href={
`/${props.user.username}/book?date=${dayjs(time).utc().format()}&type=${props.eventType.id}` +
`/${user.username}/book?date=${slot.utc().format()}&type=${eventTypeId}` +
(rescheduleUid ? "&rescheduleUid=" + rescheduleUid : "")
}>
<a
key={dayjs(time).format("hh:mma")}
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).tz(timeZone()).format(props.timeFormat)}
<a className="block font-medium mb-4 text-blue-600 border border-blue-600 rounded hover:text-white hover:bg-blue-600 py-4">
{slot.format(timeFormat)}
</a>
</Link>
</div>
))}
{!error && loaded && times.length == 0 && (
{isFullyBooked && (
<div className="w-full h-full flex flex-col justify-center content-center items-center -mt-4">
<h1 className="text-xl font">{props.user.name} is all booked today.</h1>
<h1 className="text-xl font">{user.name} is all booked today.</h1>
</div>
)}
{!error && !loaded && <div className="loader" />}
{error && (

{!isFullyBooked && slots.length === 0 && !hasErrors && <div className="loader" />}

{hasErrors && (
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4">
<div className="flex">
<div className="flex-shrink-0">
Expand All @@ -116,9 +44,9 @@ const AvailableTimes = (props) => {
<p className="text-sm text-yellow-700">
Could not load the available time slots.{" "}
<a
href={"mailto:" + props.user.email}
href={"mailto:" + user.email}
className="font-medium underline text-yellow-700 hover:text-yellow-600">
Contact {props.user.name} via e-mail
Contact {user.name} via e-mail
</a>
</p>
</div>
Expand Down
135 changes: 135 additions & 0 deletions components/booking/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
import { useEffect, useState } from "react";
import dayjs, { Dayjs } from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import getSlots from "@lib/slots";

dayjs.extend(utc);
dayjs.extend(timezone);

const DatePicker = ({
weekStart,
onDatePicked,
workingHours,
organizerTimeZone,
inviteeTimeZone,
eventLength,
}) => {
const [calendar, setCalendar] = useState([]);
const [selectedMonth, setSelectedMonth]: number = useState();
const [selectedDate, setSelectedDate]: Dayjs = useState();

useEffect(() => {
setSelectedMonth(dayjs().tz(inviteeTimeZone).month());
}, []);

useEffect(() => {
if (selectedDate) onDatePicked(selectedDate);
}, [selectedDate]);

// Handle month changes
const incrementMonth = () => {
setSelectedMonth(selectedMonth + 1);
};

const decrementMonth = () => {
setSelectedMonth(selectedMonth - 1);
};

useEffect(() => {
if (!selectedMonth) {
// wish next had a way of dealing with this magically;
return;
}

const inviteeDate = dayjs().tz(inviteeTimeZone).month(selectedMonth);

const isDisabled = (day: number) => {
const date: Dayjs = inviteeDate.date(day);
return (
date.endOf("day").isBefore(dayjs().tz(inviteeTimeZone)) ||
!getSlots({
inviteeDate: date,
frequency: eventLength,
workingHours,
organizerTimeZone,
}).length
);
};

// Set up calendar
const daysInMonth = inviteeDate.daysInMonth();
const days = [];
for (let i = 1; i <= daysInMonth; i++) {
days.push(i);
}

// Create placeholder elements for empty days in first week
let weekdayOfFirst = inviteeDate.date(1).day();
if (weekStart === "Monday") {
weekdayOfFirst -= 1;
if (weekdayOfFirst < 0) weekdayOfFirst = 6;
}
const emptyDays = Array(weekdayOfFirst)
.fill(null)
.map((day, i) => (
<div key={`e-${i}`} className={"text-center w-10 h-10 rounded-full mx-auto"}>
{null}
</div>
));

// Combine placeholder days with actual days
setCalendar([
...emptyDays,
...days.map((day) => (
<button
key={day}
onClick={() => setSelectedDate(inviteeDate.date(day))}
disabled={isDisabled(day)}
className={
"text-center w-10 h-10 rounded-full mx-auto" +
(isDisabled(day) ? " text-gray-400 font-light" : " text-blue-600 font-medium") +
(selectedDate && selectedDate.isSame(inviteeDate.date(day), "day")
? " bg-blue-600 text-white-important"
: !isDisabled(day)
? " bg-blue-50"
: "")
}>
{day}
</button>
)),
]);
}, [selectedMonth, inviteeTimeZone, selectedDate]);

return selectedMonth ? (
<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().tz(inviteeTimeZone).month() && "text-gray-400")}
disabled={selectedMonth <= dayjs().tz(inviteeTimeZone).month()}>
<ChevronLeftIcon className="w-5 h-5" />
</button>
<button onClick={incrementMonth}>
<ChevronRightIcon className="w-5 h-5" />
</button>
</div>
</div>
<div className="grid grid-cols-7 gap-y-4 text-center">
{["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
.sort((a, b) => (weekStart.startsWith(a) ? -1 : weekStart.startsWith(b) ? 1 : 0))
.map((weekDay) => (
<div key={weekDay} className="uppercase text-gray-400 text-xs tracking-widest">
{weekDay}
</div>
))}
{calendar}
</div>
</div>
) : null;
};

export default DatePicker;
Loading

0 comments on commit 3c09837

Please sign in to comment.