Feat/cal 95/date range on event types (#353)

* add edit links to events on dashboard

* fit elements on screen for mobile

* initialize components for consistent text styles

* add more fine grained width/height settings

* add higher level setting for when an event is available

- db: add supporting values to period allow setting an amount of days,
 a range, or unlimited number days an event is available

* fix issue where periodDates are null

* return minimal required data, handle date parsing

* [ui] limit booking days based on user period settings

* api: validate user period settings

* [db] migration for event type period settings
This commit is contained in:
Femi Odugbesan 2021-07-15 09:10:26 -05:00 committed by GitHub
parent ecd0360d9a
commit 45689059c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1455 additions and 380 deletions

View File

@ -4,7 +4,9 @@ import dayjs, { Dayjs } from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import getSlots from "@lib/slots";
import dayjsBusinessDays from "dayjs-business-days";
dayjs.extend(dayjsBusinessDays);
dayjs.extend(utc);
dayjs.extend(timezone);
@ -16,6 +18,11 @@ const DatePicker = ({
inviteeTimeZone,
eventLength,
date,
periodType = "unlimited",
periodStartDate,
periodEndDate,
periodDays,
periodCountCalendarDays,
}) => {
const [calendar, setCalendar] = useState([]);
const [selectedMonth, setSelectedMonth] = useState<number>();
@ -28,7 +35,11 @@ const DatePicker = ({
return;
}
setSelectedMonth(dayjs().tz(inviteeTimeZone).month());
if (periodType === "range") {
setSelectedMonth(dayjs(periodStartDate).tz(inviteeTimeZone).month());
} else {
setSelectedMonth(dayjs().tz(inviteeTimeZone).month());
}
}, []);
useEffect(() => {
@ -54,15 +65,52 @@ const DatePicker = ({
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
);
switch (periodType) {
case "rolling": {
const periodRollingEndDay = periodCountCalendarDays
? dayjs().tz(organizerTimeZone).add(periodDays, "days").endOf("day")
: dayjs().tz(organizerTimeZone).businessDaysAdd(periodDays, "days").endOf("day");
return (
date.endOf("day").isBefore(dayjs().tz(inviteeTimeZone)) ||
date.endOf("day").isAfter(periodRollingEndDay) ||
!getSlots({
inviteeDate: date,
frequency: eventLength,
workingHours,
organizerTimeZone,
}).length
);
}
case "range": {
const periodRangeStartDay = dayjs(periodStartDate).tz(organizerTimeZone).endOf("day");
const periodRangeEndDay = dayjs(periodEndDate).tz(organizerTimeZone).endOf("day");
return (
date.endOf("day").isBefore(dayjs().tz(inviteeTimeZone)) ||
date.endOf("day").isBefore(periodRangeStartDay) ||
date.endOf("day").isAfter(periodRangeEndDay) ||
!getSlots({
inviteeDate: date,
frequency: eventLength,
workingHours,
organizerTimeZone,
}).length
);
}
case "unlimited":
default:
return (
date.endOf("day").isBefore(dayjs().tz(inviteeTimeZone)) ||
!getSlots({
inviteeDate: date,
frequency: eventLength,
workingHours,
organizerTimeZone,
}).length
);
}
};
// Set up calendar

View File

@ -70,7 +70,7 @@ export const Scheduler = ({
const OpeningHours = ({ idx, item }) => (
<li className="py-2 flex justify-between border-t">
<div className="inline-flex ml-2">
<div className="flex flex-col space-y-4 lg:inline-flex ml-2 ">
<WeekdaySelect defaultValue={item.days} onSelect={(selected: number[]) => (item.days = selected)} />
<button className="ml-2 text-sm px-2" type="button" onClick={() => setEditSchedule(idx)}>
{dayjs()
@ -96,8 +96,8 @@ export const Scheduler = ({
return (
<div>
<div className="rounded border flex">
<div className="w-3/5">
<div className="w-3/4 p-2">
<div className="w-full">
<div className=" p-2">
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
Timezone
</label>
@ -120,13 +120,6 @@ export const Scheduler = ({
Add another
</button>
</div>
<div className="border-l p-2 w-2/5 text-sm bg-gray-50">
{/*<p className="font-bold mb-2">Add date overrides</p>
<p className="mb-2">
Add dates when your availability changes from your weekly hours
</p>
<button className="btn-sm btn-white">Add a date override</button>*/}
</div>
</div>
{editSchedule >= 0 && (
<SetTimesModal
@ -136,9 +129,6 @@ export const Scheduler = ({
onExit={() => setEditSchedule(-1)}
/>
)}
{/*{showDateOverrideModal &&
<DateOverrideModal />
}*/}
</div>
);
};

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Body: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--body"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Body;

View File

@ -0,0 +1,2 @@
import Body from "./Body";
export default Body;

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Caption: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--caption"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Caption;

View File

@ -0,0 +1,2 @@
import Caption from "./Caption";
export default Caption;

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Caption2: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--caption2"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Caption2;

View File

@ -0,0 +1,2 @@
import Caption2 from "./Caption2";
export default Caption2;

View File

@ -0,0 +1,11 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
const Footnote: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames("text--footnote", props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Footnote;

View File

@ -0,0 +1,2 @@
import Footnote from "./Footnote";
export default Footnote;

View File

@ -0,0 +1,11 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
const Headline: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames("text--headline", props?.className, props?.color);
return <h1 className={classes}>{props.children}</h1>;
};
export default Headline;

View File

@ -0,0 +1,2 @@
import Headline from "./Headline";
export default Headline;

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Largetitle: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--largetitle"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Largetitle;

View File

@ -0,0 +1,2 @@
import Largetitle from "./Largetitle";
export default Largetitle;

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Overline: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--overline"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Overline;

View File

@ -0,0 +1,2 @@
import Overline from "./Overline";
export default Overline;

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Subheadline: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--subheadline"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Subheadline;

View File

@ -0,0 +1,2 @@
import Subheadline from "./Subheadline";
export default Subheadline;

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Subtitle: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--subtitle"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Subtitle;

View File

@ -0,0 +1,2 @@
import Subtitle from "./Subtitle";
export default Subtitle;

View File

@ -0,0 +1,55 @@
/* strong {
@apply font-medium;
} */
.text {
}
.text--body {
@apply text-lg leading-relaxed;
}
.text--overline {
@apply text-sm uppercase font-semibold leading-snug tracking-wide;
}
.text--caption {
@apply text-sm text-gray-500 leading-tight;
}
.text--caption2 {
@apply text-xs italic text-gray-500 leading-tight;
}
.text--footnote {
@apply text-base font-normal;
}
.text--headline {
/* @apply text-base font-normal; */
@apply text-3xl leading-8 font-semibold tracking-tight text-gray-900 sm:text-4xl;
}
.text--subheadline {
@apply text-xl text-gray-500 leading-relaxed;
}
.text--largetitle {
@apply text-2xl font-normal;
}
.text--subtitle {
@apply text-base font-normal;
}
.text--title {
@apply text-base font-normal;
}
.text--title2 {
@apply text-base font-normal;
}
.text--title3 {
@apply text-xs font-semibold leading-tight;
}

166
components/ui/Text/Text.tsx Normal file
View File

@ -0,0 +1,166 @@
import React from "react";
import Body from "./Body";
import Caption from "./Caption";
import Caption2 from "./Caption2";
import Footnote from "./Footnote";
import Headline from "./Headline";
import Largetitle from "./Largetitle";
import Overline from "./Overline";
import Subheadline from "./Subheadline";
import Subtitle from "./Subtitle";
import Title from "./Title";
import Title2 from "./Title2";
import Title3 from "./Title3";
import classnames from "classnames";
type Props = {
variant?:
| "overline"
| "caption"
| "body"
| "caption2"
| "footnote"
| "headline"
| "largetitle"
| "subheadline"
| "subtitle"
| "title"
| "title2"
| "title3";
children: any;
text?: string;
tx?: string;
className?: string;
color?: string;
};
export type TextProps = {
children: any;
text?: string;
tx?: string;
color?: string;
className?: string;
};
/**
* static let largeTitle: Font
* A font with the large title text style.
*
* static let title: Font
* A font with the title text style.
*
* static let title2: Font
* Create a font for second level hierarchical headings.
*
* static let title3: Font
* Create a font for third level hierarchical headings.
*
* static let headline: Font
* A font with the headline text style.
*
* static let subheadline: Font
* A font with the subheadline text style.
*
* static let body: Font
* A font with the body text style.
*
* static let callout: Font
* A font with the callout text style.
*
* static let caption: Font
* A font with the caption text style.
*
* static let caption2: Font
* Create a font with the alternate caption text style.
*
* static let footnote: Font
* A font with the footnote text style.
*/
const Text: React.FunctionComponent<Props> = (props: Props) => {
const classes = classnames(props?.className, props?.color);
switch (props?.variant) {
case "overline":
return (
<Overline className={classes} {...props}>
{props.children}
</Overline>
);
case "body":
return (
<Body className={classes} {...props}>
{props.children}
</Body>
);
case "caption":
return (
<Caption className={classes} {...props}>
{props.children}
</Caption>
);
case "caption2":
return (
<Caption2 className={classes} {...props}>
{props.children}
</Caption2>
);
case "footnote":
return (
<Footnote className={classes} {...props}>
{props.children}
</Footnote>
);
case "headline":
return (
<Headline className={classes} {...props}>
{props.children}
</Headline>
);
case "largetitle":
return (
<Largetitle className={classes} {...props}>
{props.children}
</Largetitle>
);
case "subheadline":
return (
<Subheadline className={classes} {...props}>
{props.children}
</Subheadline>
);
case "subtitle":
return (
<Subtitle className={classes} {...props}>
{props.children}
</Subtitle>
);
case "title":
return (
<Title className={classes} {...props}>
{props.children}
</Title>
);
case "title2":
return (
<Title2 className={classes} {...props}>
{props.children}
</Title2>
);
case "title3":
return (
<Title3 className={classes} {...props}>
{props.children}
</Title3>
);
default:
return (
<Body className={classes} {...props}>
{props.children}
</Body>
);
}
};
export default Text;

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Title: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--title"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Title;

View File

@ -0,0 +1,2 @@
import Title from "./Title";
export default Title;

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Title2: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--title2"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Title2;

View File

@ -0,0 +1,2 @@
import Title2 from "./Title2";
export default Title2;

View File

@ -0,0 +1,12 @@
import React from "react";
import classnames from "classnames";
import { TextProps } from "../Text";
import Styles from "../Text.module.css";
const Title3: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames(Styles["text--title3"], props?.className, props?.color);
return <p className={classes}>{props.children}</p>;
};
export default Title3;

View File

@ -0,0 +1,2 @@
import Title3 from "./Title3";
export default Title3;

View File

@ -0,0 +1,39 @@
import Text from "./Text";
export { Text };
export default Text;
import Title from "./Title";
export { Title };
import Title2 from "./Title2";
export { Title2 };
import Title3 from "./Title3";
export { Title3 };
import Largetitle from "./Largetitle";
export { Largetitle };
import Subtitle from "./Subtitle";
export { Subtitle };
import Headline from "./Headline";
export { Headline };
import Subheadline from "./Subheadline";
export { Subheadline };
import Caption from "./Caption";
export { Caption };
import Caption2 from "./Caption2";
export { Caption2 };
import Footnote from "./Footnote";
export { Footnote };
import Overline from "./Overline";
export { Overline };
import Body from "./Body";
export { Body };

View File

@ -20,17 +20,21 @@
"@tailwindcss/forms": "^0.2.1",
"async": "^3.2.0",
"bcryptjs": "^2.4.3",
"classnames": "^2.3.1",
"dayjs": "^1.10.4",
"dayjs-business-days": "^1.0.4",
"googleapis": "^67.1.1",
"handlebars": "^4.7.7",
"ics": "^2.27.0",
"lodash.debounce": "^4.0.8",
"lodash.merge": "^4.6.2",
"lodash.throttle": "^4.1.1",
"next": "^10.2.0",
"next-auth": "^3.13.2",
"next-transpile-modules": "^7.0.0",
"nodemailer": "^6.6.1",
"react": "17.0.1",
"react-dates": "^21.8.0",
"react-dom": "17.0.1",
"react-phone-number-input": "^3.1.21",
"react-select": "^4.3.0",
@ -44,6 +48,7 @@
"@types/node": "^14.14.33",
"@types/nodemailer": "^6.4.2",
"@types/react": "^17.0.3",
"@types/react-dates": "^21.8.3",
"@typescript-eslint/eslint-plugin": "^4.27.0",
"@typescript-eslint/parser": "^4.27.0",
"autoprefixer": "^10.2.5",

View File

@ -60,7 +60,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
{
username: context.query.user.toLowerCase(),
},
["id", "username", "email", "name", "bio", "avatar", "eventTypes", "theme"]
["id", "username", "email", "name", "bio", "avatar", "theme"]
);
if (!user) {
return {
@ -73,6 +73,11 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
userId: user.id,
hidden: false,
},
select: {
slug: true,
title: true,
description: true,
},
});
return {

View File

@ -156,6 +156,11 @@ export default function Type(props): Type {
</div>
<DatePicker
date={selectedDate}
periodType={props.eventType?.periodType}
periodStartDate={props.eventType?.periodStartDate}
periodEndDate={props.eventType?.periodEndDate}
periodDays={props.eventType?.periodDays}
periodCountCalendarDays={props.eventType?.periodCountCalendarDays}
weekStart={props.user.weekStart}
onDatePicked={changeDate}
workingHours={props.workingHours}
@ -199,7 +204,6 @@ export const getServerSideProps: GetServerSideProps = async (context: GetServerS
"email",
"bio",
"avatar",
"eventTypes",
"startTime",
"endTime",
"timeZone",
@ -222,7 +226,19 @@ export const getServerSideProps: GetServerSideProps = async (context: GetServerS
userId: user.id,
slug: context.query.type,
},
["id", "title", "description", "length", "availability", "timeZone"]
[
"id",
"title",
"description",
"length",
"availability",
"timeZone",
"periodType",
"periodDays",
"periodStartDate",
"periodEndDate",
"periodCountCalendarDays",
]
);
if (!eventType) {
@ -249,11 +265,16 @@ export const getServerSideProps: GetServerSideProps = async (context: GetServerS
workingHours.sort((a, b) => a.startTime - b.startTime);
const eventTypeObject = Object.assign({}, eventType, {
periodStartDate: eventType.periodStartDate?.toString() ?? null,
periodEndDate: eventType.periodEndDate?.toString() ?? null,
});
return {
props: {
user,
date,
eventType,
eventType: eventTypeObject,
workingHours,
},
};

View File

@ -381,7 +381,7 @@ export async function getServerSideProps(context) {
{
username: context.query.user,
},
["username", "name", "email", "bio", "avatar", "eventTypes", "theme"]
["username", "name", "email", "bio", "avatar", "theme"]
);
const eventType = await prisma.eventType.findUnique({
@ -396,9 +396,19 @@ export async function getServerSideProps(context) {
length: true,
locations: true,
customInputs: true,
periodType: true,
periodDays: true,
periodStartDate: true,
periodEndDate: true,
periodCountCalendarDays: true,
},
});
const eventTypeObject = Object.assign({}, eventType, {
periodStartDate: eventType.periodStartDate?.toString() ?? null,
periodEndDate: eventType.periodEndDate?.toString() ?? null,
});
let booking = null;
if (context.query.rescheduleUid) {
@ -421,7 +431,7 @@ export async function getServerSideProps(context) {
return {
props: {
user,
eventType,
eventType: eventTypeObject,
booking,
},
};

View File

@ -49,6 +49,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
})),
},
periodType: req.body.periodType,
periodDays: req.body.periodDays,
periodStartDate: req.body.periodStartDate,
periodEndDate: req.body.periodEndDate,
periodCountCalendarDays: req.body.periodCountCalendarDays,
};
if (req.method == "POST") {

View File

@ -12,6 +12,14 @@ import merge from "lodash.merge";
import dayjs from "dayjs";
import logger from "../../../lib/logger";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import dayjsBusinessDays from "dayjs-business-days";
dayjs.extend(dayjsBusinessDays);
dayjs.extend(utc);
dayjs.extend(timezone);
const translator = short();
const log = logger.getChildLogger({ prefix: ["[api] book:user"] });
@ -49,6 +57,32 @@ function isAvailable(busyTimes, time, length) {
return t;
}
function isOutOfBounds(
time: dayjs.ConfigType,
{ periodType, periodDays, periodCountCalendarDays, periodStartDate, periodEndDate, timeZone }
): boolean {
const date = dayjs(time);
switch (periodType) {
case "rolling": {
const periodRollingEndDay = periodCountCalendarDays
? dayjs().tz(timeZone).add(periodDays, "days").endOf("day")
: dayjs().tz(timeZone).businessDaysAdd(periodDays, "days").endOf("day");
return date.endOf("day").isAfter(periodRollingEndDay);
}
case "range": {
const periodRangeStartDay = dayjs(periodStartDate).tz(timeZone).endOf("day");
const periodRangeEndDay = dayjs(periodEndDate).tz(timeZone).endOf("day");
return date.endOf("day").isBefore(periodRangeStartDay) || date.endOf("day").isAfter(periodRangeEndDay);
}
case "unlimited":
default:
return false;
}
}
interface GetLocationRequestFromIntegrationRequest {
location: string;
}
@ -166,6 +200,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
eventName: true,
title: true,
length: true,
periodType: true,
periodDays: true,
periodStartDate: true,
periodEndDate: true,
periodCountCalendarDays: true,
},
});
@ -228,6 +267,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(400).json(error);
}
let timeOutOfBounds = false;
try {
timeOutOfBounds = isOutOfBounds(req.body.start, {
periodType: selectedEventType.periodType,
periodDays: selectedEventType.periodDays,
periodEndDate: selectedEventType.periodEndDate,
periodStartDate: selectedEventType.periodStartDate,
periodCountCalendarDays: selectedEventType.periodCountCalendarDays,
timeZone: currentUser.timeZone,
});
} catch {
log.debug({
message: "Unable set timeOutOfBounds. Using false. ",
});
}
if (timeOutOfBounds) {
const error = {
errorCode: "BookingUserUnAvailable",
message: `${currentUser.name} is unavailable at this time.`,
};
log.debug(`Booking ${user} failed`, error);
return res.status(400).json(error);
}
let results = [];
let referencesToCreate = [];

View File

@ -19,6 +19,13 @@ import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import { Availability, EventType, User } from "@prisma/client";
import { validJson } from "@lib/jsonUtils";
import Text from "@components/ui/Text";
import { RadioGroup } from "@headlessui/react";
import classnames from "classnames";
import throttle from "lodash.throttle";
import "react-dates/initialize";
import "react-dates/lib/css/_datepicker.css";
import { DateRangePicker, OrientationShape, toMomentObject } from "react-dates";
dayjs.extend(utc);
dayjs.extend(timezone);
@ -54,8 +61,28 @@ type EventTypeInput = {
customInputs: EventTypeCustomInput[];
timeZone: string;
availability?: { openingHours: OpeningHours[]; dateOverrides: DateOverride[] };
periodType?: string;
periodDays?: number;
periodStartDate?: Date | string;
periodEndDate?: Date | string;
periodCountCalendarDays?: boolean;
};
const PERIOD_TYPES = [
{
type: "rolling",
suffix: "into the future",
},
{
type: "range",
prefix: "Within a date range",
},
{
type: "unlimited",
prefix: "Indefinitely into the future",
},
];
export default function EventTypePage({
user,
eventType,
@ -64,6 +91,7 @@ export default function EventTypePage({
}: Props): JSX.Element {
const router = useRouter();
console.log(eventType);
const inputOptions: OptionBase[] = [
{ value: EventTypeCustomInputType.Text, label: "Text" },
{ value: EventTypeCustomInputType.TextLong, label: "Multiline Text" },
@ -71,6 +99,39 @@ export default function EventTypePage({
{ value: EventTypeCustomInputType.Bool, label: "Checkbox" },
];
const [DATE_PICKER_ORIENTATION, setDatePickerOrientation] = useState<OrientationShape>("horizontal");
const [contentSize, setContentSize] = useState({ width: 0, height: 0 });
const handleResizeEvent = () => {
const elementWidth = parseFloat(getComputedStyle(document.body).width);
const elementHeight = parseFloat(getComputedStyle(document.body).height);
setContentSize({
width: elementWidth,
height: elementHeight,
});
};
const throttledHandleResizeEvent = throttle(handleResizeEvent, 100);
useEffect(() => {
handleResizeEvent();
window.addEventListener("resize", throttledHandleResizeEvent);
return () => {
window.removeEventListener("resize", throttledHandleResizeEvent);
};
}, []);
useEffect(() => {
if (contentSize.width < 500) {
setDatePickerOrientation("vertical");
} else {
setDatePickerOrientation("horizontal");
}
}, [contentSize]);
const [enteredAvailability, setEnteredAvailability] = useState();
const [showLocationModal, setShowLocationModal] = useState(false);
const [showAddCustomModal, setShowAddCustomModal] = useState(false);
@ -83,12 +144,37 @@ export default function EventTypePage({
eventType.customInputs.sort((a, b) => a.id - b.id) || []
);
const [periodStartDate, setPeriodStartDate] = useState(() => {
if (eventType.periodType === "range" && eventType?.periodStartDate) {
return toMomentObject(new Date(eventType.periodStartDate));
}
return null;
});
const [periodEndDate, setPeriodEndDate] = useState(() => {
if (eventType.periodType === "range" && eventType.periodEndDate) {
return toMomentObject(new Date(eventType?.periodEndDate));
}
return null;
});
const [focusedInput, setFocusedInput] = useState(null);
const [periodType, setPeriodType] = useState(() => {
return (
PERIOD_TYPES.find((s) => s.type === eventType.periodType) ||
PERIOD_TYPES.find((s) => s.type === "unlimited")
);
});
const titleRef = useRef<HTMLInputElement>();
const slugRef = useRef<HTMLInputElement>();
const descriptionRef = useRef<HTMLTextAreaElement>();
const lengthRef = useRef<HTMLInputElement>();
const isHiddenRef = useRef<HTMLInputElement>();
const eventNameRef = useRef<HTMLInputElement>();
const periodDaysRef = useRef<HTMLInputElement>();
const periodDaysTypeRef = useRef<HTMLSelectElement>();
useEffect(() => {
setSelectedTimeZone(eventType.timeZone || user.timeZone);
@ -103,6 +189,22 @@ export default function EventTypePage({
const enteredLength: number = parseInt(lengthRef.current.value);
const enteredIsHidden: boolean = isHiddenRef.current.checked;
const enteredEventName: string = eventNameRef.current.value;
const type = periodType.type;
const enteredPeriodDays = parseInt(periodDaysRef?.current?.value);
const enteredPeriodDaysType = Boolean(parseInt(periodDaysTypeRef?.current.value));
const enteredPeriodStartDate = periodStartDate ? periodStartDate.toDate() : null;
const enteredPeriodEndDate = periodEndDate ? periodEndDate.toDate() : null;
console.log("values", {
type,
periodDaysTypeRef,
enteredPeriodDays,
enteredPeriodDaysType,
enteredPeriodStartDate,
enteredPeriodEndDate,
});
// TODO: Add validation
const payload: EventTypeInput = {
@ -116,6 +218,11 @@ export default function EventTypePage({
eventName: enteredEventName,
customInputs,
timeZone: selectedTimeZone,
periodType: type,
periodDays: enteredPeriodDays,
periodStartDate: enteredPeriodStartDate,
periodEndDate: enteredPeriodEndDate,
periodCountCalendarDays: enteredPeriodDaysType,
};
if (enteredAvailability) {
@ -268,8 +375,8 @@ export default function EventTypePage({
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell heading={"Event Type - " + eventType.title}>
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3 sm:col-span-2">
<div className="max-w-5xl mx-auto">
<div className="">
<div className="bg-white overflow-hidden shadow rounded-lg mb-4">
<div className="px-4 py-5 sm:p-6">
<form onSubmit={updateEventTypeHandler}>
@ -330,7 +437,7 @@ export default function EventTypePage({
</div>
)}
{locations.length > 0 && (
<ul className="w-96 mt-1">
<ul className="mt-1">
{locations.map((location) => (
<li key={location.type} className="bg-blue-50 mb-2 p-2 border">
<div className="flex justify-between">
@ -450,26 +557,6 @@ export default function EventTypePage({
defaultValue={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"
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"
defaultValue={eventType.length}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm">
minutes
</div>
</div>
</div>
<div className="mb-4">
<label htmlFor="eventName" className="block text-sm font-medium text-gray-700">
Calendar entry name
@ -554,29 +641,151 @@ export default function EventTypePage({
</div>
</div>
</div>
<hr className="my-4" />
<div>
<h3 className="mb-2">How do you want to offer your availability for this event type?</h3>
<Scheduler
setAvailability={setEnteredAvailability}
setTimeZone={setSelectedTimeZone}
timeZone={selectedTimeZone}
availability={availability}
/>
<div className="py-4 flex justify-end">
<Link href="/availability">
<a className="mr-2 btn btn-white">Cancel</a>
</Link>
<button type="submit" className="btn btn-primary">
Update
</button>
</div>
</div>
<fieldset className="my-8">
<Text variant="largetitle">When can people book this event?</Text>
<hr className="my-8" />
<section className="space-y-12">
<div className="mb-4">
{/* <label htmlFor="period" className=""> */}
<Text variant="subtitle">Date Range</Text>
{/* </label> */}
<Text variant="title3">Invitees can schedule...</Text>
<div className="mt-1 relative ">
<RadioGroup value={periodType} onChange={setPeriodType}>
<RadioGroup.Label className="sr-only">Date Range</RadioGroup.Label>
<div className="bg-white rounded-md -space-y-px">
{PERIOD_TYPES.map((period) => (
<RadioGroup.Option
key={period.type}
value={period}
className={({ checked }) =>
classnames(
checked ? "bg-indigo-50 border-indigo-200 z-10" : "border-gray-200",
"relative py-4 px-2 lg:p-4 min-h-20 lg:flex items-center cursor-pointer focus:outline-none"
)
}>
{({ active, checked }) => (
<>
<span
className={classnames(
checked
? "bg-indigo-600 border-transparent"
: "bg-white border-gray-300",
active ? "ring-2 ring-offset-2 ring-indigo-500" : "",
"h-4 w-4 mt-0.5 cursor-pointer rounded-full border flex items-center justify-center"
)}
aria-hidden="true">
<span className="rounded-full bg-white w-1.5 h-1.5" />
</span>
<div className="lg:ml-3 flex flex-col">
<RadioGroup.Label
as="span"
className={classnames(
checked ? "text-indigo-900" : "text-gray-900",
"block text-sm font-light space-y-2 lg:space-y-0 lg:space-x-2"
)}>
<span>{period.prefix}</span>
{period.type === "rolling" && (
<div className="inline-flex">
<input
ref={periodDaysRef}
type="text"
name="periodDays"
id=""
className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-12 sm:text-sm border-gray-300 rounded-md"
placeholder="30"
defaultValue={eventType.periodDays || 30}
/>
<select
ref={periodDaysTypeRef}
id=""
name="periodDaysType"
className=" block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
defaultValue={eventType.periodCountCalendarDays ? "1" : "0"}>
<option value="1">calendar days</option>
<option value="0">business days</option>
</select>
</div>
)}
{checked && period.type === "range" && (
<div className="inline-flex space-x-2">
<DateRangePicker
orientation={DATE_PICKER_ORIENTATION}
startDate={periodStartDate}
startDateId="your_unique_start_date_id"
endDate={periodEndDate}
endDateId="your_unique_end_date_id"
onDatesChange={({ startDate, endDate }) => {
setPeriodStartDate(startDate);
setPeriodEndDate(endDate);
}}
focusedInput={focusedInput}
onFocusChange={(focusedInput) => {
setFocusedInput(focusedInput);
}}
/>
</div>
)}
<span>{period.suffix}</span>
</RadioGroup.Label>
</div>
</>
)}
</RadioGroup.Option>
))}
</div>
</RadioGroup>
</div>
</div>
<hr className="my-8" />
<div className="mb-4">
<label htmlFor="length" className="block text-sm font-medium text-gray-700">
<Text variant="caption">Duration</Text>
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<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"
defaultValue={eventType.length}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm">
minutes
</div>
</div>
</div>
<hr className="my-8" />
<div>
<h3 className="mb-2">
How do you want to offer your availability for this event type?
</h3>
<Scheduler
setAvailability={setEnteredAvailability}
setTimeZone={setSelectedTimeZone}
timeZone={selectedTimeZone}
availability={availability}
/>
<div className="py-4 flex justify-end">
<Link href="/availability">
<a className="mr-2 btn btn-white">Cancel</a>
</Link>
<button type="submit" className="btn btn-primary">
Update
</button>
</div>
</div>
</section>
</fieldset>
</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 mb-2 leading-6 font-medium text-gray-900">Delete this event type</h3>
@ -777,6 +986,11 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ req, query
availability: true,
customInputs: true,
timeZone: true,
periodType: true,
periodDays: true,
periodStartDate: true,
periodEndDate: true,
periodCountCalendarDays: true,
},
});
@ -853,10 +1067,15 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ req, query
availability.sort((a, b) => a.startTime - b.startTime);
const eventTypeObject = Object.assign({}, eventType, {
periodStartDate: eventType.periodStartDate?.toString() ?? null,
periodEndDate: eventType.periodEndDate?.toString() ?? null,
});
return {
props: {
user,
eventType,
eventType: eventTypeObject,
locationOptions,
availability,
},

View File

@ -1,94 +1,84 @@
import Head from 'next/head';
import Link from 'next/link';
import prisma from '../lib/prisma';
import Shell from '../components/Shell';
import {getSession, useSession} from 'next-auth/client';
import {CheckIcon, ClockIcon, InformationCircleIcon} from '@heroicons/react/outline';
import DonateBanner from '../components/DonateBanner';
import Head from "next/head";
import Link from "next/link";
import prisma from "../lib/prisma";
import Shell from "../components/Shell";
import { getSession, useSession } from "next-auth/client";
import { CheckIcon, ClockIcon, InformationCircleIcon } from "@heroicons/react/outline";
import DonateBanner from "../components/DonateBanner";
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
return classes.filter(Boolean).join(" ");
}
export default function Home(props) {
const [session, loading] = useSession();
if (loading) {
return <div className="loader"></div>;
}
const [session, loading] = useSession();
if (loading) {
return <div className="loader"></div>;
}
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}`;
}
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}`;
}
const stats = [
{ name: 'Event Types', stat: props.eventTypeCount },
{ name: 'Integrations', stat: props.integrationCount },
{ name: 'Available Hours', stat: Math.round(((props.user.endTime - props.user.startTime) / 60) * 100) / 100 + ' hours' },
const stats = [
{ name: "Event Types", stat: props.eventTypeCount },
{ name: "Integrations", stat: props.integrationCount },
{
name: "Available Hours",
stat: Math.round(((props.user.endTime - props.user.startTime) / 60) * 100) / 100 + " hours",
},
];
let timeline = [];
if (session) {
timeline = [
{
id: 1,
content: "Add your first",
target: "integration",
href: "/integrations",
icon: props.integrationCount != 0 ? CheckIcon : InformationCircleIcon,
iconBackground: props.integrationCount != 0 ? "bg-green-400" : "bg-gray-400",
},
{
id: 2,
content: "Add one or more",
target: "event types",
href: "/availability",
icon: props.eventTypeCount != 0 ? CheckIcon : InformationCircleIcon,
iconBackground: props.eventTypeCount != 0 ? "bg-green-400" : "bg-gray-400",
},
{
id: 3,
content: "Complete your",
target: "profile",
href: "/settings/profile",
icon: session.user.image ? CheckIcon : InformationCircleIcon,
iconBackground: session.user.image ? "bg-green-400" : "bg-gray-400",
},
];
} else {
timeline = [];
}
let timeline = [];
return (
<div>
<Head>
<title>Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
if (session) {
timeline = [
{
id: 1,
content: 'Add your first',
target: 'integration',
href: '/integrations',
icon: props.integrationCount != 0 ? CheckIcon : InformationCircleIcon,
iconBackground: props.integrationCount != 0 ? 'bg-green-400' : 'bg-gray-400',
},
{
id: 2,
content: 'Add one or more',
target: 'event types',
href: '/availability',
icon: props.eventTypeCount != 0 ? CheckIcon : InformationCircleIcon,
iconBackground: props.eventTypeCount != 0 ? 'bg-green-400' : 'bg-gray-400',
},
{
id: 3,
content: 'Complete your',
target: 'profile',
href: '/settings/profile',
icon: session.user.image ? CheckIcon : InformationCircleIcon,
iconBackground: session.user.image ? 'bg-green-400' : 'bg-gray-400',
},
];
} else {
timeline = [];
}
return (
<div>
<Head>
<title>Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell heading="Dashboard">
<div className="md:grid grid-cols-3 gap-4">
<div className="col-span-2">
<div className="rounded-lg bg-white shadow dark:bg-gray-800">
<div className="pt-5 pb-2 px-6 sm:flex sm:items-center sm:justify-between">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Your stats</h3>
</div>
<dl className="grid grid-cols-1 overflow-hidden divide-y divide-gray-200 dark:divide-gray-900 md:grid-cols-3 md:divide-y-0 md:divide-x">
{stats.map((item) => (
<div key={item.name} className="px-4 py-5 sm:p-6">
<dt className="text-base font-normal dark:text-white text-gray-900">{item.name}</dt>
<dd className="mt-1 flex justify-between items-baseline md:block lg:flex">
<div className="flex items-baseline text-2xl font-semibold text-blue-600">
{item.stat}
</div>
</dd>
</div>
))}
</dl>
<Shell heading="Dashboard">
<div className="md:grid grid-cols-3 gap-4">
<div className="col-span-2">
<div className="rounded-lg bg-white shadow dark:bg-gray-800">
<div className="pt-5 pb-2 px-6 sm:flex sm:items-center sm:justify-between">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Your stats</h3>
</div>
<div className="mt-8 bg-white shadow dark:bg-gray-800 overflow-hidden rounded-md">
<div className="pt-5 pb-2 px-6 sm:flex sm:items-center sm:justify-between">
@ -119,235 +109,251 @@ export default function Home(props) {
</div>
</div>
</div>
<div className="ml-5 flex-shrink-0">
<Link href={"/" + session.user.username + "/" + type.slug}>
<a target="_blank" className="text-blue-600 hover:text-blue-900 mr-2 font-medium">
View
</a>
</Link>
</div>
</div>
</li>
))}
{props.eventTypes.length == 0 && (
<div className="text-center text-gray-400 py-12">
<p>You haven't created any event types.</p>
<div className="ml-5 flex-shrink-0">
<Link href={"/availability/event/" + type.id}>
<a className="text-blue-600 hover:text-blue-900 mr-2 font-medium">Edit</a>
</Link>
<Link href={"/" + session.user.username + "/" + type.slug}>
<a target="_blank" className="text-blue-600 hover:text-blue-900 mr-2 font-medium">
View
</a>
</Link>
</div>
</div>
)}
</ul>
</div>
<div className="mt-8 bg-white dark:bg-gray-800 shadow overflow-hidden rounded-md p-6 mb-8 md:mb-0">
<div className="md:flex">
<div className="md:w-1/2 self-center mb-8 md:mb-0">
<h2 className="text-2xl dark:text-white font-semibold">Getting started</h2>
<p className="text-gray-600 dark:text-gray-200 text-sm">
Steps you should take to get started with Calendso.
</p>
</li>
))}
{props.eventTypes.length == 0 && (
<div className="text-center text-gray-400 py-12">
<p>You haven't created any event types.</p>
</div>
<div className="md:w-1/2">
<div className="flow-root">
<ul className="-mb-8">
{timeline.map((event, eventIdx) => (
<li key={event.id}>
<div className="relative pb-8">
{eventIdx !== timeline.length - 1 ? (
)}
</ul>
</div>
<div className="mt-8 bg-white dark:bg-gray-800 shadow overflow-hidden rounded-md p-6 mb-8 md:mb-0">
<div className="md:flex">
<div className="md:w-1/2 self-center mb-8 md:mb-0">
<h2 className="text-2xl dark:text-white font-semibold">Getting started</h2>
<p className="text-gray-600 dark:text-gray-200 text-sm">
Steps you should take to get started with Calendso.
</p>
</div>
<div className="md:w-1/2">
<div className="flow-root">
<ul className="-mb-8">
{timeline.map((event, eventIdx) => (
<li key={event.id}>
<div className="relative pb-8">
{eventIdx !== timeline.length - 1 ? (
<span
className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200 dark:bg-gray-900"
aria-hidden="true"
/>
) : null}
<div className="relative flex space-x-3">
<div>
<span
className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200 dark:bg-gray-900"
aria-hidden="true"
/>
) : null}
<div className="relative flex space-x-3">
className={classNames(
event.iconBackground,
"h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white dark:ring-gray-800"
)}>
<event.icon className="h-5 w-5 text-white" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4">
<div>
<span
className={classNames(
event.iconBackground,
"h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white dark:ring-gray-800"
)}>
<event.icon className="h-5 w-5 text-white" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4">
<div>
<p className="text-sm text-gray-500 dark:text-gray-200">
{event.content}{" "}
<Link href={event.href}>
<a className="font-medium dark:text-white text-gray-900">
{event.target}
</a>
</Link>
</p>
</div>
<p className="text-sm text-gray-500 dark:text-gray-200">
{event.content}{" "}
<Link href={event.href}>
<a className="font-medium dark:text-white text-gray-900">
{event.target}
</a>
</Link>
</p>
</div>
</div>
</div>
</li>
))}
</ul>
</div>
</div>
</li>
))}
</ul>
</div>
</div>
</div>
</div>
<div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
<div className="mb-4 sm:flex sm:items-center sm:justify-between">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Your day</h3>
<div className="mt-3 sm:mt-0 sm:ml-4">
<Link href="/availability">
<a className="text-sm text-gray-400">Configure</a>
</Link>
</div>
</div>
<div>
<p className="text-2xl font-semibold text-gray-600 dark:text-white">
Offering time slots between{" "}
<span className="text-blue-600">{convertMinsToHrsMins(props.user.startTime)}</span> and{" "}
<span className="text-blue-600">{convertMinsToHrsMins(props.user.endTime)}</span>
</p>
</div>
</div>
<div className="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
<div className="mb-8 sm:flex sm:items-center sm:justify-between">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Your integrations
</h3>
<div className="mt-3 sm:mt-0 sm:ml-4">
<Link href="/integrations">
<a className="text-sm text-gray-400">View more</a>
</Link>
</div>
</div>
<ul className="divide-y divide-gray-200">
{props.credentials.map((integration) => (
<li className="pb-4 flex">
{integration.type == "google_calendar" && (
<img
className="h-10 w-10 mr-2"
src="integrations/google-calendar.png"
alt="Google Calendar"
/>
)}
{integration.type == "office365_calendar" && (
<img
className="h-10 w-10 mr-2"
src="integrations/office-365.png"
alt="Office 365 / Outlook.com Calendar"
/>
)}
{integration.type == "zoom_video" && (
<img className="h-10 w-10 mr-2" src="integrations/zoom.png" alt="Zoom" />
)}
<div className="ml-3">
{integration.type == "office365_calendar" && (
<p className="text-sm font-medium text-gray-900">
Office 365 / Outlook.com Calendar
</p>
)}
{integration.type == "google_calendar" && (
<p className="text-sm font-medium text-gray-900">Google Calendar</p>
)}
{integration.type == "zoom_video" && (
<p className="text-sm font-medium text-gray-900">Zoom</p>
)}
{integration.type.endsWith("_calendar") && (
<p className="text-sm text-gray-500">Calendar Integration</p>
)}
{integration.type.endsWith("_video") && (
<p className="text-sm text-gray-500">Video Conferencing</p>
)}
</div>
</li>
))}
{props.credentials.length == 0 && (
<div className="text-center text-gray-400 py-2">
<p>You haven't added any integrations.</p>
</div>
)}
</ul>
</div>
<div className="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
<div className="mb-4 sm:flex sm:items-center sm:justify-between">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Your event types
</h3>
<div className="mt-3 sm:mt-0 sm:ml-4">
<Link href="/availability">
<a className="text-sm text-gray-400">View more</a>
</Link>
</div>
</div>
<ul className="divide-y divide-gray-200">
{props.eventTypes.map((type) => (
<li
key={type.id}
className="relative py-5 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
<div className="flex justify-between space-x-3">
<div className="min-w-0 flex-1">
<a href="#" className="block focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
<p className="text-sm font-medium text-gray-900 dark:text-white truncate">
{type.title}
</p>
<p className="text-sm text-gray-500 truncate">{type.description}</p>
</a>
</div>
<span className="flex-shrink-0 whitespace-nowrap text-sm text-gray-500">
{type.length} minutes
</span>
</div>
</li>
))}
{props.eventTypes.length == 0 && (
<div className="text-center text-gray-400 py-2">
<p>You haven't created any event types.</p>
</div>
)}
</ul>
</div>
</div>
</div>
<div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
<div className="mb-4 sm:flex sm:items-center sm:justify-between">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Your day</h3>
<div className="mt-3 sm:mt-0 sm:ml-4">
<Link href="/availability">
<a className="text-sm text-gray-400">Configure</a>
</Link>
</div>
</div>
<div>
<p className="text-2xl font-semibold text-gray-600 dark:text-white">
Offering time slots between{" "}
<span className="text-blue-600">{convertMinsToHrsMins(props.user.startTime)}</span> and{" "}
<span className="text-blue-600">{convertMinsToHrsMins(props.user.endTime)}</span>
</p>
</div>
</div>
<div className="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
<div className="mb-8 sm:flex sm:items-center sm:justify-between">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Your integrations
</h3>
<div className="mt-3 sm:mt-0 sm:ml-4">
<Link href="/integrations">
<a className="text-sm text-gray-400">View more</a>
</Link>
</div>
</div>
<ul className="divide-y divide-gray-200">
{props.credentials.map((integration) => (
<li className="pb-4 flex">
{integration.type == "google_calendar" && (
<img
className="h-10 w-10 mr-2"
src="integrations/google-calendar.png"
alt="Google Calendar"
/>
)}
{integration.type == "office365_calendar" && (
<img
className="h-10 w-10 mr-2"
src="integrations/office-365.png"
alt="Office 365 / Outlook.com Calendar"
/>
)}
{integration.type == "zoom_video" && (
<img className="h-10 w-10 mr-2" src="integrations/zoom.png" alt="Zoom" />
)}
<div className="ml-3">
{integration.type == "office365_calendar" && (
<p className="text-sm font-medium text-gray-900">Office 365 / Outlook.com Calendar</p>
)}
{integration.type == "google_calendar" && (
<p className="text-sm font-medium text-gray-900">Google Calendar</p>
)}
{integration.type == "zoom_video" && (
<p className="text-sm font-medium text-gray-900">Zoom</p>
)}
{integration.type.endsWith("_calendar") && (
<p className="text-sm text-gray-500">Calendar Integration</p>
)}
{integration.type.endsWith("_video") && (
<p className="text-sm text-gray-500">Video Conferencing</p>
)}
</div>
</li>
))}
{props.credentials.length == 0 && (
<div className="text-center text-gray-400 py-2">
<p>You haven't added any integrations.</p>
</div>
)}
</ul>
</div>
<div className="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
<div className="mb-4 sm:flex sm:items-center sm:justify-between">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Your event types
</h3>
<div className="mt-3 sm:mt-0 sm:ml-4">
<Link href="/availability">
<a className="text-sm text-gray-400">View more</a>
</Link>
</div>
</div>
<ul className="divide-y divide-gray-200">
{props.eventTypes.map((type) => (
<li
key={type.id}
className="relative py-5 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
<div className="flex justify-between space-x-3">
<div className="min-w-0 flex-1">
<a href="#" className="block focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
<p className="text-sm font-medium text-gray-900 dark:text-white truncate">
{type.title}
</p>
<p className="text-sm text-gray-500 truncate">{type.description}</p>
</a>
</div>
<span className="flex-shrink-0 whitespace-nowrap text-sm text-gray-500">
{type.length} minutes
</span>
</div>
</li>
))}
{props.eventTypes.length == 0 && (
<div className="text-center text-gray-400 py-2">
<p>You haven't created any event types.</p>
</div>
)}
</ul>
</div>
</div>
</div>
<DonateBanner />
</Shell>
</div>
);
<DonateBanner />
</Shell>
</div>
);
}
export async function getServerSideProps(context) {
const session = await getSession(context);
const session = await getSession(context);
let user = [];
let credentials = [];
let eventTypes = [];
let user = [];
let credentials = [];
let eventTypes = [];
if (session) {
user = await prisma.user.findFirst({
where: {
email: session.user.email,
},
select: {
id: true,
startTime: true,
endTime: true
}
});
if (session) {
user = await prisma.user.findFirst({
where: {
email: session.user.email,
},
select: {
id: true,
startTime: true,
endTime: true,
},
});
credentials = await prisma.credential.findMany({
where: {
userId: session.user.id,
},
select: {
type: true
}
});
credentials = await prisma.credential.findMany({
where: {
userId: session.user.id,
},
select: {
type: true,
},
});
eventTypes = await prisma.eventType.findMany({
where: {
userId: session.user.id,
}
});
}
return {
props: { user, credentials, eventTypes, eventTypeCount: eventTypes.length, integrationCount: credentials.length }, // will be passed to the page component as props
}
}
eventTypes = (
await prisma.eventType.findMany({
where: {
userId: session.user.id,
},
})
).map((eventType) => {
return {
...eventType,
periodStartDate: eventType.periodStartDate?.toString() ?? null,
periodEndDate: eventType.periodEndDate?.toString() ?? null,
};
});
}
return {
props: {
user,
credentials,
eventTypes,
eventTypeCount: eventTypes.length,
integrationCount: credentials.length,
}, // will be passed to the page component as props
};
}

View File

@ -222,7 +222,7 @@ export async function getServerSideProps(context) {
{
username: context.query.user,
},
["username", "name", "bio", "avatar", "eventTypes", "hideBranding", "theme"]
["username", "name", "bio", "avatar", "hideBranding", "theme"]
)
: null;

View File

@ -0,0 +1,6 @@
-- AlterTable
ALTER TABLE "EventType" ADD COLUMN "periodCountCalendarDays" BOOLEAN,
ADD COLUMN "periodDays" INTEGER,
ADD COLUMN "periodEndDate" TIMESTAMP(3),
ADD COLUMN "periodStartDate" TIMESTAMP(3),
ADD COLUMN "periodType" TEXT DEFAULT E'unlimited';

View File

@ -25,6 +25,11 @@ model EventType {
eventName String?
customInputs EventTypeCustomInput[]
timeZone String?
periodType String? @default("unlimited") // unlimited | rolling | range
periodStartDate DateTime?
periodEndDate DateTime?
periodDays Int?
periodCountCalendarDays Boolean?
}
model Credential {

View File

@ -28,9 +28,32 @@ module.exports = {
900: "#01579b",
},
},
maxHeight: {
maxHeight: (theme) => ({
0: "0",
97: "25rem",
},
...theme("spacing"),
full: "100%",
screen: "100vh",
}),
minHeight: (theme) => ({
0: "0",
...theme("spacing"),
full: "100%",
screen: "100vh",
}),
minWidth: (theme) => ({
0: "0",
...theme("spacing"),
full: "100%",
screen: "100vw",
}),
maxWidth: (theme, { breakpoints }) => ({
0: "0",
...theme("spacing"),
...breakpoints(theme("screens")),
full: "100%",
screen: "100vw",
}),
},
},
variants: {

264
yarn.lock
View File

@ -893,6 +893,31 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/react-dates@^21.8.3":
version "21.8.3"
resolved "https://registry.yarnpkg.com/@types/react-dates/-/react-dates-21.8.3.tgz#dc4e71f83d09979b1c4f355c267e52a850d0fe2c"
integrity sha512-MSG/A5UCXepPw5a9BtdOXfCCSMcQ5+oQIkm0K2u39sf4EJbsgngUg1zcoY3amxa6Hz0EWZkZOiExK/92J6hxUw==
dependencies:
"@types/react" "*"
"@types/react-outside-click-handler" "*"
moment "^2.26.0"
"@types/react-outside-click-handler@*":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@types/react-outside-click-handler/-/react-outside-click-handler-1.3.0.tgz#ccf0014032fc6ec286210f8a05d26a5c1f94cc96"
integrity sha512-BxQpd5GsbA9rjqLcM4lYp70VnvahgjMUeJ4OKi0A7QOsDLD2yUPswOVixtDpmvCu0PkRTfvMoStIR3gKC/L3XQ==
dependencies:
"@types/react" "*"
"@types/react@*":
version "17.0.14"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.14.tgz#f0629761ca02945c4e8fea99b8177f4c5c61fb0f"
integrity sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^17.0.3":
version "17.0.11"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
@ -1069,6 +1094,21 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
airbnb-prop-types@^2.10.0, airbnb-prop-types@^2.14.0, airbnb-prop-types@^2.15.0:
version "2.16.0"
resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2"
integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==
dependencies:
array.prototype.find "^2.1.1"
function.prototype.name "^1.1.2"
is-regex "^1.1.0"
object-is "^1.1.2"
object.assign "^4.1.0"
object.entries "^1.1.2"
prop-types "^15.7.2"
prop-types-exact "^1.2.0"
react-is "^16.13.1"
ajv@^6.10.0, ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@ -1196,6 +1236,23 @@ array-union@^2.1.0:
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
array.prototype.find@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c"
integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.4"
array.prototype.flat@^1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123"
integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==
dependencies:
call-bind "^1.0.0"
define-properties "^1.1.3"
es-abstract "^1.18.0-next.1"
array.prototype.flatmap@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9"
@ -1397,6 +1454,11 @@ braces@^3.0.1, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
brcast@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/brcast/-/brcast-2.0.2.tgz#2db16de44140e418dc37fab10beec0369e78dcef"
integrity sha512-Tfn5JSE7hrUlFcOoaLzVvkbgIemIorMIyoMr3TgvszWW7jFt2C9PdeMLtysYD9RU0MmU17b69+XJG1eRY2OBRg==
brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@ -1663,7 +1725,7 @@ classnames@2.2.6:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
classnames@^2.2.5:
classnames@^2.2.5, classnames@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
@ -1796,6 +1858,11 @@ console-browserify@^1.1.0:
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==
"consolidated-events@^1.1.1 || ^2.0.0":
version "2.0.2"
resolved "https://registry.yarnpkg.com/consolidated-events/-/consolidated-events-2.0.2.tgz#da8d8f8c2b232831413d9e190dc11669c79f4a91"
integrity sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ==
constants-browserify@1.0.0, constants-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
@ -1958,6 +2025,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
dayjs-business-days@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/dayjs-business-days/-/dayjs-business-days-1.0.4.tgz#36e93e7566149e175c1541d92ce16e12145412bf"
integrity sha512-kmb8Hvlhmv7INc2YXWew4SaEBJprqJ9C48CdlO1NTsqQa31ZEcO48ziFa3YFr5W9rdwB1nUmd5iIkLwgCkJ8nA==
dayjs@^1.10.4:
version "1.10.5"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.5.tgz#5600df4548fc2453b3f163ebb2abbe965ccfb986"
@ -1992,12 +2064,17 @@ deep-is@^0.1.3, deep-is@~0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
deepmerge@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
deepmerge@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
define-properties@^1.1.3:
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
@ -2072,6 +2149,11 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
direction@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/direction/-/direction-1.0.4.tgz#2b86fb686967e987088caf8b89059370d4837442"
integrity sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==
dlv@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
@ -2091,6 +2173,13 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
document.contains@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/document.contains/-/document.contains-1.0.2.tgz#4260abad67a6ae9e135c1be83d68da0db169d5f0"
integrity sha512-YcvYFs15mX8m3AO1QNQy3BlIpSMfNRj3Ujk2BEJxsZG+HZf7/hZ6jr7mDpXrF8q+ff95Vef5yjhiZxm8CGJr6Q==
dependencies:
define-properties "^1.1.3"
dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
@ -2188,6 +2277,14 @@ enquirer@^2.3.5, enquirer@^2.3.6:
dependencies:
ansi-colors "^4.1.1"
enzyme-shallow-equal@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e"
integrity sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==
dependencies:
has "^1.0.3"
object-is "^1.1.2"
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@ -2195,7 +2292,7 @@ error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2:
es-abstract@^1.17.4, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2:
version "1.18.3"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0"
integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==
@ -2620,11 +2717,26 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
function.prototype.name@^1.1.2:
version "1.1.4"
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.4.tgz#e4ea839b9d3672ae99d0efd9f38d9191c5eaac83"
integrity sha512-iqy1pIotY/RmhdFZygSSlW0wko2yxkSCKqsuv4pr8QESohpYyG/Z7B/XXvPRKTJS//960rgguE5mSRUsDdaJrQ==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.3"
es-abstract "^1.18.0-next.2"
functions-have-names "^1.2.2"
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
functions-have-names@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21"
integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==
futoin-hkdf@^1.3.2:
version "1.3.3"
resolved "https://registry.yarnpkg.com/futoin-hkdf/-/futoin-hkdf-1.3.3.tgz#6ee1c9c105dfa0995ba4f80633cf1c0c32defcb2"
@ -2721,6 +2833,14 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
once "^1.3.0"
path-is-absolute "^1.0.0"
global-cache@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/global-cache/-/global-cache-1.2.1.tgz#39ca020d3dd7b3f0934c52b75363f8d53312c16d"
integrity sha512-EOeUaup5DgWKlCMhA9YFqNRIlZwoxt731jCh47WBV9fQqHgXhr3Fa55hfgIUqilIcPsfdNKN7LHjrNY+Km40KA==
dependencies:
define-properties "^1.1.2"
is-symbol "^1.0.1"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@ -2883,7 +3003,7 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.3.1:
hoist-non-react-statics@^3.2.1, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -3186,7 +3306,7 @@ is-potential-custom-element-name@^1.0.1:
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
is-regex@^1.1.3:
is-regex@^1.1.0, is-regex@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==
@ -3209,13 +3329,18 @@ is-string@^1.0.5, is-string@^1.0.6:
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f"
integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==
is-symbol@^1.0.2, is-symbol@^1.0.3:
is-symbol@^1.0.1, is-symbol@^1.0.2, is-symbol@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
dependencies:
has-symbols "^1.0.2"
is-touch-device@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-touch-device/-/is-touch-device-1.0.1.tgz#9a2fd59f689e9a9bf6ae9a86924c4ba805a42eab"
integrity sha512-LAYzo9kMT1b2p19L/1ATGt2XcSilnzNlyvq6c0pbPRVisLbAPpLqr53tIJS00kvrTkj0HtR8U7+u8X0yR8lPSw==
is-typed-array@^1.1.3:
version "1.1.5"
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e"
@ -4045,6 +4170,11 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
lodash.toarray@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
@ -4060,7 +4190,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
lodash@^4.17.13, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
lodash@^4.1.1, lodash@^4.17.13, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -4210,6 +4340,11 @@ modern-normalize@^1.1.0:
resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7"
integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==
moment@>=1.6.0, moment@^2.26.0:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -4458,7 +4593,7 @@ object-inspect@^1.10.3, object-inspect@^1.9.0:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==
object-is@^1.0.1:
object-is@^1.0.1, object-is@^1.1.2:
version "1.1.5"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
@ -4471,7 +4606,7 @@ object-keys@^1.0.12, object-keys@^1.1.1:
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object.assign@^4.1.2:
object.assign@^4.1.0, object.assign@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
@ -4481,7 +4616,7 @@ object.assign@^4.1.2:
has-symbols "^1.0.1"
object-keys "^1.1.1"
object.entries@^1.1.4:
object.entries@^1.1.2, object.entries@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.4.tgz#43ccf9a50bc5fd5b649d45ab1a579f24e088cafd"
integrity sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==
@ -4500,7 +4635,7 @@ object.fromentries@^2.0.4:
es-abstract "^1.18.0-next.2"
has "^1.0.3"
object.values@^1.1.4:
object.values@^1.0.4, object.values@^1.1.0, object.values@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30"
integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==
@ -4691,6 +4826,11 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
@ -4888,6 +5028,15 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types-exact@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869"
integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==
dependencies:
has "^1.0.3"
object.assign "^4.1.0"
reflect.ownkeys "^0.2.0"
prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
@ -4976,6 +5125,13 @@ quick-lru@^5.1.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
raf@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
dependencies:
performance-now "^2.1.0"
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@ -5001,6 +5157,27 @@ raw-body@2.4.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
react-dates@^21.8.0:
version "21.8.0"
resolved "https://registry.yarnpkg.com/react-dates/-/react-dates-21.8.0.tgz#355c3c7a243a7c29568fe00aca96231e171a5e94"
integrity sha512-PPriGqi30CtzZmoHiGdhlA++YPYPYGCZrhydYmXXQ6RAvAsaONcPtYgXRTLozIOrsQ5mSo40+DiA5eOFHnZ6xw==
dependencies:
airbnb-prop-types "^2.15.0"
consolidated-events "^1.1.1 || ^2.0.0"
enzyme-shallow-equal "^1.0.0"
is-touch-device "^1.0.1"
lodash "^4.1.1"
object.assign "^4.1.0"
object.values "^1.1.0"
prop-types "^15.7.2"
raf "^3.4.1"
react-moment-proptypes "^1.6.0"
react-outside-click-handler "^1.2.4"
react-portal "^4.2.0"
react-with-direction "^1.3.1"
react-with-styles "^4.1.0"
react-with-styles-interface-css "^6.0.0"
react-dom@17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
@ -5017,7 +5194,7 @@ react-input-autosize@^3.0.0:
dependencies:
prop-types "^15.5.8"
react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1:
react-is@16.13.1, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -5027,6 +5204,24 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-moment-proptypes@^1.6.0:
version "1.8.1"
resolved "https://registry.yarnpkg.com/react-moment-proptypes/-/react-moment-proptypes-1.8.1.tgz#7ba4076147f6b5998f0d4f51d302d6d8c62049fd"
integrity sha512-Er940DxWoObfIqPrZNfwXKugjxMIuk1LAuEzn23gytzV6hKS/sw108wibi9QubfMN4h+nrlje8eUCSbQRJo2fQ==
dependencies:
moment ">=1.6.0"
react-outside-click-handler@^1.2.4:
version "1.3.0"
resolved "https://registry.yarnpkg.com/react-outside-click-handler/-/react-outside-click-handler-1.3.0.tgz#3831d541ac059deecd38ec5423f81e80ad60e115"
integrity sha512-Te/7zFU0oHpAnctl//pP3hEAeobfeHMyygHB8MnjP6sX5OR8KHT1G3jmLsV3U9RnIYo+Yn+peJYWu+D5tUS8qQ==
dependencies:
airbnb-prop-types "^2.15.0"
consolidated-events "^1.1.1 || ^2.0.0"
document.contains "^1.0.1"
object.values "^1.1.0"
prop-types "^15.7.2"
react-phone-number-input@^3.1.21:
version "3.1.23"
resolved "https://registry.yarnpkg.com/react-phone-number-input/-/react-phone-number-input-3.1.23.tgz#8e8d2b7fb98d94514721d05ca5c58bd31f8d06e1"
@ -5038,6 +5233,13 @@ react-phone-number-input@^3.1.21:
libphonenumber-js "^1.9.19"
prop-types "^15.7.2"
react-portal@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.2.1.tgz#12c1599238c06fb08a9800f3070bea2a3f78b1a6"
integrity sha512-fE9kOBagwmTXZ3YGRYb4gcMy+kSA+yLO0xnPankjRlfBv4uCpFXqKPfkpsGQQR15wkZ9EssnvTOl1yMzbkxhPQ==
dependencies:
prop-types "^15.5.8"
react-refresh@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
@ -5075,6 +5277,39 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-with-direction@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/react-with-direction/-/react-with-direction-1.3.1.tgz#9fd414564f0ffe6947e5ff176f6132dd83f8b8df"
integrity sha512-aGcM21ZzhqeXFvDCfPj0rVNYuaVXfTz5D3Rbn0QMz/unZe+CCiLHthrjQWO7s6qdfXORgYFtmS7OVsRgSk5LXQ==
dependencies:
airbnb-prop-types "^2.10.0"
brcast "^2.0.2"
deepmerge "^1.5.2"
direction "^1.0.2"
hoist-non-react-statics "^3.3.0"
object.assign "^4.1.0"
object.values "^1.0.4"
prop-types "^15.6.2"
react-with-styles-interface-css@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/react-with-styles-interface-css/-/react-with-styles-interface-css-6.0.0.tgz#b53da7fa8359d452cb934cface8738acaef7b5fe"
integrity sha512-6khSG1Trf4L/uXOge/ZAlBnq2O2PEXlQEqAhCRbvzaQU4sksIkdwpCPEl6d+DtP3+IdhyffTWuHDO9lhe1iYvA==
dependencies:
array.prototype.flat "^1.2.1"
global-cache "^1.2.1"
react-with-styles@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/react-with-styles/-/react-with-styles-4.2.0.tgz#0b8a8e5d94d082518b9f564f6fcf6103e28096c5"
integrity sha512-tZCTY27KriRNhwHIbg1NkSdTTOSfXDg6Z7s+Q37mtz0Ym7Sc7IOr3PzVt4qJhJMW6Nkvfi3g34FuhtiGAJCBQA==
dependencies:
airbnb-prop-types "^2.14.0"
hoist-non-react-statics "^3.2.1"
object.assign "^4.1.0"
prop-types "^15.7.2"
react-with-direction "^1.3.1"
react@17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
@ -5132,6 +5367,11 @@ reflect-metadata@^0.1.13:
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
reflect.ownkeys@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"