diff --git a/components/Theme.tsx b/components/Theme.tsx new file mode 100644 index 00000000..2c0dc1ca --- /dev/null +++ b/components/Theme.tsx @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; + +export default function Theme(theme?: string) { + const [isReady, setIsReady] = useState(false); + useEffect(() => { + if (!theme && window.matchMedia("(prefers-color-scheme: dark)").matches) { + document.documentElement.classList.add("dark"); + } else { + document.documentElement.classList.add(theme); + } + setIsReady(true); + }, []); + + return { + isReady, + }; +} diff --git a/components/booking/AvailableTimes.tsx b/components/booking/AvailableTimes.tsx index 0f074293..64cb4dd0 100644 --- a/components/booking/AvailableTimes.tsx +++ b/components/booking/AvailableTimes.tsx @@ -43,7 +43,7 @@ const AvailableTimes = ({ ))} {isFullyBooked && (
-

{user.name} is all booked today.

+

{user.name} is all booked today.

)} diff --git a/components/booking/DatePicker.tsx b/components/booking/DatePicker.tsx index 62907e16..b49b887e 100644 --- a/components/booking/DatePicker.tsx +++ b/components/booking/DatePicker.tsx @@ -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); @@ -15,13 +17,29 @@ const DatePicker = ({ organizerTimeZone, inviteeTimeZone, eventLength, + date, + periodType = "unlimited", + periodStartDate, + periodEndDate, + periodDays, + periodCountCalendarDays, }) => { const [calendar, setCalendar] = useState([]); - const [selectedMonth, setSelectedMonth]: number = useState(); - const [selectedDate, setSelectedDate]: Dayjs = useState(); + const [selectedMonth, setSelectedMonth] = useState(); + const [selectedDate, setSelectedDate] = useState(); useEffect(() => { - setSelectedMonth(dayjs().tz(inviteeTimeZone).month()); + if (date) { + setSelectedDate(dayjs(date).tz(inviteeTimeZone)); + setSelectedMonth(dayjs(date).tz(inviteeTimeZone).month()); + return; + } + + if (periodType === "range") { + setSelectedMonth(dayjs(periodStartDate).tz(inviteeTimeZone).month()); + } else { + setSelectedMonth(dayjs().tz(inviteeTimeZone).month()); + } }, []); useEffect(() => { @@ -47,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 diff --git a/components/ui/Scheduler.tsx b/components/ui/Scheduler.tsx index edec1319..fcb4688c 100644 --- a/components/ui/Scheduler.tsx +++ b/components/ui/Scheduler.tsx @@ -70,7 +70,7 @@ export const Scheduler = ({ const OpeningHours = ({ idx, item }) => (
  • -
    +
    (item.days = selected)} />
    -
    - {/*

    Add date overrides

    -

    - Add dates when your availability changes from your weekly hours -

    - */} -
    {editSchedule >= 0 && ( setEditSchedule(-1)} /> )} - {/*{showDateOverrideModal && - - }*/} ); }; diff --git a/components/ui/Text/Body/Body.tsx b/components/ui/Text/Body/Body.tsx new file mode 100644 index 00000000..f8332092 --- /dev/null +++ b/components/ui/Text/Body/Body.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--body"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Body; diff --git a/components/ui/Text/Body/index.ts b/components/ui/Text/Body/index.ts new file mode 100644 index 00000000..e77f776a --- /dev/null +++ b/components/ui/Text/Body/index.ts @@ -0,0 +1,2 @@ +import Body from "./Body"; +export default Body; diff --git a/components/ui/Text/Caption/Caption.tsx b/components/ui/Text/Caption/Caption.tsx new file mode 100644 index 00000000..4dadc57c --- /dev/null +++ b/components/ui/Text/Caption/Caption.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--caption"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Caption; diff --git a/components/ui/Text/Caption/index.ts b/components/ui/Text/Caption/index.ts new file mode 100644 index 00000000..a3c3d984 --- /dev/null +++ b/components/ui/Text/Caption/index.ts @@ -0,0 +1,2 @@ +import Caption from "./Caption"; +export default Caption; diff --git a/components/ui/Text/Caption2/Caption2.tsx b/components/ui/Text/Caption2/Caption2.tsx new file mode 100644 index 00000000..ca02e615 --- /dev/null +++ b/components/ui/Text/Caption2/Caption2.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--caption2"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Caption2; diff --git a/components/ui/Text/Caption2/index.ts b/components/ui/Text/Caption2/index.ts new file mode 100644 index 00000000..b93a9079 --- /dev/null +++ b/components/ui/Text/Caption2/index.ts @@ -0,0 +1,2 @@ +import Caption2 from "./Caption2"; +export default Caption2; diff --git a/components/ui/Text/Footnote/Footnote.tsx b/components/ui/Text/Footnote/Footnote.tsx new file mode 100644 index 00000000..3beda4fa --- /dev/null +++ b/components/ui/Text/Footnote/Footnote.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import classnames from "classnames"; +import { TextProps } from "../Text"; + +const Footnote: React.FunctionComponent = (props: TextProps) => { + const classes = classnames("text--footnote", props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Footnote; diff --git a/components/ui/Text/Footnote/index.ts b/components/ui/Text/Footnote/index.ts new file mode 100644 index 00000000..4fa2ec93 --- /dev/null +++ b/components/ui/Text/Footnote/index.ts @@ -0,0 +1,2 @@ +import Footnote from "./Footnote"; +export default Footnote; diff --git a/components/ui/Text/Headline/Headline.tsx b/components/ui/Text/Headline/Headline.tsx new file mode 100644 index 00000000..a7b531a9 --- /dev/null +++ b/components/ui/Text/Headline/Headline.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import classnames from "classnames"; +import { TextProps } from "../Text"; +import Styles from "../Text.module.css"; + +const Headline: React.FunctionComponent = (props: TextProps) => { + const classes = classnames(Styles["text--headline"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Headline; diff --git a/components/ui/Text/Headline/index.ts b/components/ui/Text/Headline/index.ts new file mode 100644 index 00000000..42ad4d66 --- /dev/null +++ b/components/ui/Text/Headline/index.ts @@ -0,0 +1,2 @@ +import Headline from "./Headline"; +export default Headline; diff --git a/components/ui/Text/Largetitle/Largetitle.tsx b/components/ui/Text/Largetitle/Largetitle.tsx new file mode 100644 index 00000000..8b0c3271 --- /dev/null +++ b/components/ui/Text/Largetitle/Largetitle.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--largetitle"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Largetitle; diff --git a/components/ui/Text/Largetitle/index.ts b/components/ui/Text/Largetitle/index.ts new file mode 100644 index 00000000..4c16d0f1 --- /dev/null +++ b/components/ui/Text/Largetitle/index.ts @@ -0,0 +1,2 @@ +import Largetitle from "./Largetitle"; +export default Largetitle; diff --git a/components/ui/Text/Overline/Overline.tsx b/components/ui/Text/Overline/Overline.tsx new file mode 100644 index 00000000..82003351 --- /dev/null +++ b/components/ui/Text/Overline/Overline.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--overline"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Overline; diff --git a/components/ui/Text/Overline/index.ts b/components/ui/Text/Overline/index.ts new file mode 100644 index 00000000..8d40c8c9 --- /dev/null +++ b/components/ui/Text/Overline/index.ts @@ -0,0 +1,2 @@ +import Overline from "./Overline"; +export default Overline; diff --git a/components/ui/Text/Subheadline/Subheadline.tsx b/components/ui/Text/Subheadline/Subheadline.tsx new file mode 100644 index 00000000..550a955f --- /dev/null +++ b/components/ui/Text/Subheadline/Subheadline.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--subheadline"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Subheadline; diff --git a/components/ui/Text/Subheadline/index.ts b/components/ui/Text/Subheadline/index.ts new file mode 100644 index 00000000..86c9def1 --- /dev/null +++ b/components/ui/Text/Subheadline/index.ts @@ -0,0 +1,2 @@ +import Subheadline from "./Subheadline"; +export default Subheadline; diff --git a/components/ui/Text/Subtitle/Subtitle.tsx b/components/ui/Text/Subtitle/Subtitle.tsx new file mode 100644 index 00000000..11065651 --- /dev/null +++ b/components/ui/Text/Subtitle/Subtitle.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--subtitle"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Subtitle; diff --git a/components/ui/Text/Subtitle/index.ts b/components/ui/Text/Subtitle/index.ts new file mode 100644 index 00000000..5091f41b --- /dev/null +++ b/components/ui/Text/Subtitle/index.ts @@ -0,0 +1,2 @@ +import Subtitle from "./Subtitle"; +export default Subtitle; diff --git a/components/ui/Text/Text.module.css b/components/ui/Text/Text.module.css new file mode 100644 index 00000000..0afe4759 --- /dev/null +++ b/components/ui/Text/Text.module.css @@ -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; +} diff --git a/components/ui/Text/Text.tsx b/components/ui/Text/Text.tsx new file mode 100644 index 00000000..eb3d1897 --- /dev/null +++ b/components/ui/Text/Text.tsx @@ -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) => { + const classes = classnames(props?.className, props?.color); + + switch (props?.variant) { + case "overline": + return ( + + {props.children} + + ); + case "body": + return ( + + {props.children} + + ); + case "caption": + return ( + + {props.children} + + ); + case "caption2": + return ( + + {props.children} + + ); + case "footnote": + return ( + + {props.children} + + ); + case "headline": + return ( + + {props.children} + + ); + case "largetitle": + return ( + + {props.children} + + ); + case "subheadline": + return ( + + {props.children} + + ); + case "subtitle": + return ( + + {props.children} + + ); + case "title": + return ( + + {props.children} + + ); + case "title2": + return ( + + {props.children} + + ); + case "title3": + return ( + + {props.children} + + ); + default: + return ( + + {props.children} + + ); + } +}; + +export default Text; diff --git a/components/ui/Text/Title/Title.tsx b/components/ui/Text/Title/Title.tsx new file mode 100644 index 00000000..7ebb409b --- /dev/null +++ b/components/ui/Text/Title/Title.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--title"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Title; diff --git a/components/ui/Text/Title/index.ts b/components/ui/Text/Title/index.ts new file mode 100644 index 00000000..6036b4be --- /dev/null +++ b/components/ui/Text/Title/index.ts @@ -0,0 +1,2 @@ +import Title from "./Title"; +export default Title; diff --git a/components/ui/Text/Title2/Title2.tsx b/components/ui/Text/Title2/Title2.tsx new file mode 100644 index 00000000..3534c02e --- /dev/null +++ b/components/ui/Text/Title2/Title2.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--title2"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Title2; diff --git a/components/ui/Text/Title2/index.ts b/components/ui/Text/Title2/index.ts new file mode 100644 index 00000000..73c63d9e --- /dev/null +++ b/components/ui/Text/Title2/index.ts @@ -0,0 +1,2 @@ +import Title2 from "./Title2"; +export default Title2; diff --git a/components/ui/Text/Title3/Title3.tsx b/components/ui/Text/Title3/Title3.tsx new file mode 100644 index 00000000..6f60baed --- /dev/null +++ b/components/ui/Text/Title3/Title3.tsx @@ -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 = (props: TextProps) => { + const classes = classnames(Styles["text--title3"], props?.className, props?.color); + + return

    {props.children}

    ; +}; + +export default Title3; diff --git a/components/ui/Text/Title3/index.ts b/components/ui/Text/Title3/index.ts new file mode 100644 index 00000000..0c673c02 --- /dev/null +++ b/components/ui/Text/Title3/index.ts @@ -0,0 +1,2 @@ +import Title3 from "./Title3"; +export default Title3; diff --git a/components/ui/Text/index.ts b/components/ui/Text/index.ts new file mode 100644 index 00000000..b426e062 --- /dev/null +++ b/components/ui/Text/index.ts @@ -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 }; diff --git a/lib/calendarClient.ts b/lib/calendarClient.ts index f9c4dfd5..0b3f9102 100644 --- a/lib/calendarClient.ts +++ b/lib/calendarClient.ts @@ -157,29 +157,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => { optional.location = { displayName: event.location }; } - return { - subject: event.title, - body: { - contentType: "HTML", - content: event.description, - }, - start: { - dateTime: event.startTime, - timeZone: event.organizer.timeZone, - }, - end: { - dateTime: event.endTime, - timeZone: event.organizer.timeZone, - }, - attendees: event.attendees.map((attendee) => ({ - emailAddress: { - address: attendee.email, - name: attendee.name, - }, - type: "required", - })), - ...optional, - }; + return toRet; }; const integrationType = "office365_calendar"; diff --git a/lib/emails/EventOrganizerMail.ts b/lib/emails/EventOrganizerMail.ts index 48b5f078..9c60383c 100644 --- a/lib/emails/EventOrganizerMail.ts +++ b/lib/emails/EventOrganizerMail.ts @@ -66,13 +66,13 @@ export default class EventOrganizerMail extends EventMail { ${this.calEvent.attendees[0].email}

    ` + this.getAdditionalBody() + - "
    " + `Invitee Time Zone:
    ${this.calEvent.attendees[0].timeZone}

    Additional notes:
    ${this.calEvent.description} ` + + "
    " + this.getAdditionalFooter() + ` diff --git a/lib/slots.ts b/lib/slots.ts index 862e8b14..3c0d45a1 100644 --- a/lib/slots.ts +++ b/lib/slots.ts @@ -131,7 +131,7 @@ const getSlots = ({ ) .reduce((slots, boundary: Boundary) => [...slots, ...getSlotsBetweenBoundary(frequency, boundary)], []) .map((slot) => - slot.month(inviteeDate.month()).date(inviteeDate.date()).utcOffset(inviteeDate.utcOffset()) + slot.utcOffset(inviteeDate.utcOffset()).month(inviteeDate.month()).date(inviteeDate.date()) ); }; diff --git a/package.json b/package.json index 99e3462e..2875cf53 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pages/[user].tsx b/pages/[user].tsx index bdb78725..58e28c30 100644 --- a/pages/[user].tsx +++ b/pages/[user].tsx @@ -1,10 +1,13 @@ import { GetServerSideProps } from "next"; import Head from "next/head"; import Link from "next/link"; -import prisma from "../lib/prisma"; +import prisma, { whereAndSelect } from "@lib/prisma"; import Avatar from "../components/Avatar"; +import Theme from "@components/Theme"; export default function User(props): User { + const { isReady } = Theme(props.user.theme); + const eventTypes = props.eventTypes.map((type) => (
  • )); return ( -
    - - {props.user.name || props.user.username} | Calendso - - + isReady && ( +
    + + {props.user.name || props.user.username} | Calendso + + -
    -
    - -

    - {props.user.name || props.user.username} -

    -

    {props.user.bio}

    -
    -
    -
      {eventTypes}
    - {eventTypes.length == 0 && ( -
    -

    Uh oh!

    -

    This user hasn't set up any event types yet.

    -
    - )} -
    -
    -
    +
    +
    + +

    + {props.user.name || props.user.username} +

    +

    {props.user.bio}

    +
    +
    +
      {eventTypes}
    + {eventTypes.length == 0 && ( +
    +

    Uh oh!

    +

    This user hasn't set up any event types yet.

    +
    + )} +
    +
    +
    + ) ); } export const getServerSideProps: GetServerSideProps = async (context) => { - const user = await prisma.user.findFirst({ - where: { + const user = await whereAndSelect( + prisma.user.findFirst, + { username: context.query.user.toLowerCase(), }, - select: { - id: true, - username: true, - email: true, - name: true, - bio: true, - avatar: true, - eventTypes: true, - }, - }); - + ["id", "username", "email", "name", "bio", "avatar", "theme"] + ); if (!user) { return { notFound: true, @@ -76,6 +73,11 @@ export const getServerSideProps: GetServerSideProps = async (context) => { userId: user.id, hidden: false, }, + select: { + slug: true, + title: true, + description: true, + }, }); return { diff --git a/pages/[user]/[type].tsx b/pages/[user]/[type].tsx index 52e0773b..04cdbd76 100644 --- a/pages/[user]/[type].tsx +++ b/pages/[user]/[type].tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; -import { GetServerSideProps } from "next"; +import { GetServerSideProps, GetServerSidePropsContext } from "next"; import Head from "next/head"; import { ChevronDownIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid"; import { useRouter } from "next/router"; -import { Dayjs } from "dayjs"; +import dayjs, { Dayjs } from "dayjs"; import prisma, { whereAndSelect } from "@lib/prisma"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry"; @@ -13,18 +13,24 @@ import Avatar from "../../components/Avatar"; import { timeZone } from "../../lib/clock"; import DatePicker from "../../components/booking/DatePicker"; import PoweredByCalendso from "../../components/ui/PoweredByCalendso"; +import Theme from "@components/Theme"; export default function Type(props): Type { // Get router variables const router = useRouter(); const { rescheduleUid } = router.query; - const [selectedDate, setSelectedDate] = useState(); + const { isReady } = Theme(props.user.theme); + + const [selectedDate, setSelectedDate] = useState(() => { + return props.date && dayjs(props.date).isValid() ? dayjs(props.date) : null; + }); const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false); const [timeFormat, setTimeFormat] = useState("h:mma"); const telemetry = useTelemetry(); useEffect(() => { + handleToggle24hClock(localStorage.getItem("timeOption.is24hClock") === "true"); telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters())); }, [telemetry]); @@ -33,6 +39,26 @@ export default function Type(props): Type { setSelectedDate(date); }; + useEffect(() => { + if (!selectedDate) { + return; + } + + const formattedDate = selectedDate.utc().format("YYYY-MM-DD"); + + router.replace( + { + query: { + date: formattedDate, + }, + }, + undefined, + { + shallow: true, + } + ); + }, [selectedDate]); + const handleSelectTimeZone = (selectedTimeZone: string): void => { if (selectedDate) { setSelectedDate(selectedDate.tz(selectedTimeZone)); @@ -45,116 +71,127 @@ export default function Type(props): Type { }; return ( -
    - - - {rescheduleUid && "Reschedule"} {props.eventType.title} | {props.user.name || props.user.username} | - Calendso - - - + isReady && ( +
    + + + {rescheduleUid && "Reschedule"} {props.eventType.title} | {props.user.name || props.user.username}{" "} + | Calendso + + + - - - - - " + props.eventType.description - ).replace(/'/g, "%27") + - ".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" + - encodeURIComponent(props.user.avatar) - } - /> + + + + + " + props.eventType.description + ).replace(/'/g, "%27") + + ".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" + + encodeURIComponent(props.user.avatar) + } + /> - - - - - " + props.eventType.description - ).replace(/'/g, "%27") + - ".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" + - encodeURIComponent(props.user.avatar) - } - /> - -
    -
    -
    -
    - -

    {props.user.name}

    -

    - {props.eventType.title} -

    -

    - - {props.eventType.length} minutes -

    - - {isTimeOptionsOpen && ( - + + + + " + props.eventType.description + ).replace(/'/g, "%27") + + ".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" + + encodeURIComponent(props.user.avatar) + } + /> + +
    +
    +
    +
    + +

    {props.user.name}

    +

    + {props.eventType.title} +

    +

    + + {props.eventType.length} minutes +

    + + {isTimeOptionsOpen && ( + + )} +

    {props.eventType.description}

    +
    + + {selectedDate && ( + )} -

    {props.eventType.description}

    - - {selectedDate && ( - - )}
    -
    - {!props.user.hideBranding && } -
    -
    + {!props.user.hideBranding && } + +
    + ) ); } -export const getServerSideProps: GetServerSideProps = async (context) => { +export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => { + const dateQuery = context.query?.date ?? null; + const date = Array.isArray(dateQuery) && dateQuery.length > 0 ? dateQuery.pop() : dateQuery; + const user = await whereAndSelect( prisma.user.findFirst, { @@ -167,13 +204,13 @@ export const getServerSideProps: GetServerSideProps = async (context) => { "email", "bio", "avatar", - "eventTypes", "startTime", "endTime", "timeZone", "weekStart", "availability", "hideBranding", + "theme", ] ); @@ -189,7 +226,19 @@ export const getServerSideProps: GetServerSideProps = async (context) => { 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) { @@ -216,10 +265,16 @@ export const getServerSideProps: GetServerSideProps = async (context) => { 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, - eventType, + date, + eventType: eventTypeObject, workingHours, }, }; diff --git a/pages/[user]/book.tsx b/pages/[user]/book.tsx index ec348b69..34430e30 100644 --- a/pages/[user]/book.tsx +++ b/pages/[user]/book.tsx @@ -2,7 +2,7 @@ import Head from "next/head"; import Link from "next/link"; import { useRouter } from "next/router"; import { CalendarIcon, ClockIcon, ExclamationIcon, LocationMarkerIcon } from "@heroicons/react/solid"; -import prisma from "../../lib/prisma"; +import prisma, { whereAndSelect } from "../../lib/prisma"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry"; import { useEffect, useState } from "react"; import dayjs from "dayjs"; @@ -14,6 +14,7 @@ import { LocationType } from "../../lib/location"; import Avatar from "../../components/Avatar"; import Button from "../../components/ui/Button"; import { EventTypeCustomInputType } from "../../lib/eventTypeInput"; +import Theme from "@components/Theme"; dayjs.extend(utc); dayjs.extend(timezone); @@ -32,7 +33,10 @@ export default function Book(props: any): JSX.Element { const [selectedLocation, setSelectedLocation] = useState( locations.length === 1 ? locations[0].type : "" ); + + const { isReady } = Theme(props.user.theme); const telemetry = useTelemetry(); + useEffect(() => { setPreferredTimeZone(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()); setIs24h(!!localStorage.getItem("timeOption.is24hClock")); @@ -138,247 +142,247 @@ export default function Book(props: any): JSX.Element { }; return ( -
    - - - {rescheduleUid ? "Reschedule" : "Confirm"} your {props.eventType.title} with{" "} - {props.user.name || props.user.username} | Calendso - - - + isReady && ( +
    + + + {rescheduleUid ? "Reschedule" : "Confirm"} your {props.eventType.title} with{" "} + {props.user.name || props.user.username} | Calendso + + + -
    -
    -
    -
    - -

    {props.user.name}

    -

    - {props.eventType.title} -

    -

    - - {props.eventType.length} minutes -

    - {selectedLocation === LocationType.InPerson && ( +
    +
    +
    +
    + +

    {props.user.name}

    +

    + {props.eventType.title} +

    - - {locationInfo(selectedLocation).address} + + {props.eventType.length} minutes

    - )} -

    - - {preferredTimeZone && - dayjs(date) - .tz(preferredTimeZone) - .format((is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY")} -

    -

    {props.eventType.description}

    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    - {locations.length > 1 && ( -
    - Location - {locations.map((location) => ( - - ))} -
    + {selectedLocation === LocationType.InPerson && ( +

    + + {locationInfo(selectedLocation).address} +

    )} - {selectedLocation === LocationType.Phone && ( +

    + + {preferredTimeZone && + dayjs(date) + .tz(preferredTimeZone) + .format((is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY")} +

    +

    {props.eventType.description}

    +
    +
    +
    -
    - )} - {props.eventType.customInputs && - props.eventType.customInputs - .sort((a, b) => a.id - b.id) - .map((input) => ( -
    - {input.type !== EventTypeCustomInputType.Bool && ( - - )} - {input.type === EventTypeCustomInputType.TextLong && ( -
    -
    - -
    - -
    - minutes -
    -
    -
    -
    -
    -

    How do you want to offer your availability for this event type?

    - -
    - - Cancel - - -
    -
    + +
    + When can people book this event? +
    +
    +
    + {/* */} + Invitees can schedule... +
    + + Date Range +
    + {PERIOD_TYPES.map((period) => ( + + 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 }) => ( + <> + + ))} +
    +
    +
    +
    +
    +
    + +
    + +
    + minutes +
    +
    +
    +
    +
    +

    + How do you want to offer your availability for this event type? +

    + +
    + + Cancel + + +
    +
    +
    +
    -
    -

    Delete this event type

    @@ -777,6 +986,11 @@ export const getServerSideProps: GetServerSideProps = 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 = 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, }, diff --git a/pages/availability/index.tsx b/pages/availability/index.tsx index ad18b232..11b4e670 100644 --- a/pages/availability/index.tsx +++ b/pages/availability/index.tsx @@ -1,424 +1,550 @@ -import Head from 'next/head'; -import Link from 'next/link'; -import prisma from '../../lib/prisma'; -import Modal from '../../components/Modal'; -import Shell from '../../components/Shell'; -import {useRouter} from 'next/router'; -import {useRef, useState} from 'react'; -import {getSession, useSession} from 'next-auth/client'; -import {ClockIcon, PlusIcon} from '@heroicons/react/outline'; +import Head from "next/head"; +import Link from "next/link"; +import prisma from "../../lib/prisma"; +import Modal from "../../components/Modal"; +import Shell from "../../components/Shell"; +import { useRouter } from "next/router"; +import { useRef, useState } from "react"; +import { getSession, useSession } from "next-auth/client"; +import { ClockIcon, PlusIcon } from "@heroicons/react/outline"; export default function Availability(props) { - const [ session, loading ] = useSession(); - const router = useRouter(); - const [showAddModal, setShowAddModal] = useState(false); - const [successModalOpen, setSuccessModalOpen] = useState(false); - const [showChangeTimesModal, setShowChangeTimesModal] = useState(false); - const titleRef = useRef(); - const slugRef = useRef(); - const descriptionRef = useRef(); - const lengthRef = useRef(); - const isHiddenRef = useRef(); + const [, loading] = useSession(); + const router = useRouter(); + const [showAddModal, setShowAddModal] = useState(false); + const [successModalOpen, setSuccessModalOpen] = useState(false); + const [showChangeTimesModal, setShowChangeTimesModal] = useState(false); + const titleRef = useRef(); + const slugRef = useRef(); + const descriptionRef = useRef(); + const lengthRef = useRef(); + const isHiddenRef = useRef(); - const startHoursRef = useRef(); - const startMinsRef = useRef(); - const endHoursRef = useRef(); - const endMinsRef = useRef(); - const bufferHoursRef = useRef(); - const bufferMinsRef = useRef(); + const startHoursRef = useRef(); + const startMinsRef = useRef(); + const endHoursRef = useRef(); + const endMinsRef = useRef(); + const bufferHoursRef = useRef(); + const bufferMinsRef = useRef(); - if (loading) { - return
    ; + if (loading) { + return
    ; + } + + function toggleAddModal() { + setShowAddModal(!showAddModal); + } + + function toggleChangeTimesModal() { + setShowChangeTimesModal(!showChangeTimesModal); + } + + const closeSuccessModal = () => { + setSuccessModalOpen(false); + router.replace(router.asPath); + }; + + 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}`; + } + + async function createEventTypeHandler(event) { + event.preventDefault(); + + const enteredTitle = titleRef.current.value; + const enteredSlug = slugRef.current.value; + const enteredDescription = descriptionRef.current.value; + const enteredLength = lengthRef.current.value; + const enteredIsHidden = isHiddenRef.current.checked; + + // TODO: Add validation + + await fetch("/api/availability/eventtype", { + method: "POST", + body: JSON.stringify({ + title: enteredTitle, + slug: enteredSlug, + description: enteredDescription, + length: enteredLength, + hidden: enteredIsHidden, + }), + headers: { + "Content-Type": "application/json", + }, + }); + + if (enteredTitle && enteredLength) { + router.replace(router.asPath); + toggleAddModal(); } + } - function toggleAddModal() { - setShowAddModal(!showAddModal); - } + async function updateStartEndTimesHandler(event) { + event.preventDefault(); - function toggleChangeTimesModal() { - setShowChangeTimesModal(!showChangeTimesModal); - } + const enteredStartHours = parseInt(startHoursRef.current.value); + const enteredStartMins = parseInt(startMinsRef.current.value); + const enteredEndHours = parseInt(endHoursRef.current.value); + const enteredEndMins = parseInt(endMinsRef.current.value); + const enteredBufferHours = parseInt(bufferHoursRef.current.value); + const enteredBufferMins = parseInt(bufferMinsRef.current.value); - const closeSuccessModal = () => { setSuccessModalOpen(false); router.replace(router.asPath); } + const startMins = enteredStartHours * 60 + enteredStartMins; + const endMins = enteredEndHours * 60 + enteredEndMins; + const bufferMins = enteredBufferHours * 60 + enteredBufferMins; - 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}`; - } + // TODO: Add validation - async function createEventTypeHandler(event) { - event.preventDefault(); + await fetch("/api/availability/day", { + method: "PATCH", + body: JSON.stringify({ start: startMins, end: endMins, buffer: bufferMins }), + headers: { + "Content-Type": "application/json", + }, + }); - const enteredTitle = titleRef.current.value; - const enteredSlug = slugRef.current.value; - const enteredDescription = descriptionRef.current.value; - const enteredLength = lengthRef.current.value; - const enteredIsHidden = isHiddenRef.current.checked; + setShowChangeTimesModal(false); + setSuccessModalOpen(true); + } - // TODO: Add validation - - const response = await fetch('/api/availability/eventtype', { - method: 'POST', - body: JSON.stringify({title: enteredTitle, slug: enteredSlug, description: enteredDescription, length: enteredLength, hidden: enteredIsHidden}), - headers: { - 'Content-Type': 'application/json' - } - }); - - if (enteredTitle && enteredLength) { - router.replace(router.asPath); - toggleAddModal(); - } - } - - async function updateStartEndTimesHandler(event) { - event.preventDefault(); - - const enteredStartHours = parseInt(startHoursRef.current.value); - const enteredStartMins = parseInt(startMinsRef.current.value); - const enteredEndHours = parseInt(endHoursRef.current.value); - const enteredEndMins = parseInt(endMinsRef.current.value); - const enteredBufferHours = parseInt(bufferHoursRef.current.value); - const enteredBufferMins = parseInt(bufferMinsRef.current.value); - - const startMins = enteredStartHours * 60 + enteredStartMins; - const endMins = enteredEndHours * 60 + enteredEndMins; - const bufferMins = enteredBufferHours * 60 + enteredBufferMins; - - // TODO: Add validation - - const response = await fetch('/api/availability/day', { - method: 'PATCH', - body: JSON.stringify({start: startMins, end: endMins, buffer: bufferMins}), - headers: { - 'Content-Type': 'application/json' - } - }); - - setShowChangeTimesModal(false); - setSuccessModalOpen(true); - } - - return( -
    - - Availability | Calendso - - - -
    -

    - Event Types -

    -
    - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - {props.types.map((eventType) => - - - - - - - )} - -
    - Name - - Description - - Length - - Edit -
    - {eventType.title} - {eventType.hidden && - - Hidden - - } - - {eventType.description} - - {eventType.length} minutes - - View - Edit -
    -
    -
    -
    -
    - -
    -
    -
    -

    - Change the start and end times of your day -

    -
    -

    - Currently, your day is set to start at {convertMinsToHrsMins(props.user.startTime)} and end at {convertMinsToHrsMins(props.user.endTime)}. -

    -
    -
    - -
    -
    -
    - -
    -
    -

    - Something doesn't look right? -

    -
    -

    - Troubleshoot your availability to explore why your times are showing as they are. -

    -
    - -
    -
    -
    - {showAddModal && -
    -
    - - - - -
    -
    -
    - -
    -
    - -
    -

    - Create a new event type for people to book times with. -

    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - {location.hostname}/{props.user.username}/ - - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    - minutes -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -

    Hide the event type from your page, so it can only be booked through it's URL.

    -
    -
    -
    - {/* TODO: Add an error message when required input fields empty*/} -
    - - -
    -
    -
    -
    -
    - } - {showChangeTimesModal && -
    -
    - - - - -
    -
    -
    - -
    -
    - -
    -

    - Set the start and end time of your day and a minimum buffer between your meetings. -

    -
    -
    -
    -
    -
    - -
    - - -
    - : -
    - - -
    -
    -
    - -
    - - -
    - : -
    - - -
    -
    -
    - -
    - - -
    - : -
    - - -
    -
    -
    - - -
    -
    -
    -
    -
    - } - -
    + return ( +
    + + Availability | Calendso + + + +
    +

    Event Types

    +
    + +
    - ); +
    +
    +
    +
    + + + + + + + + + + + {props.types.map((eventType) => ( + + + + + + + ))} + +
    + Name + + Description + + Length + + Edit +
    + {eventType.title} + {eventType.hidden && ( + + Hidden + + )} + {eventType.description} + {eventType.length} minutes + + + + View + + + + Edit + +
    +
    +
    +
    +
    + +
    +
    +
    +

    + Change the start and end times of your day +

    +
    +

    + Currently, your day is set to start at {convertMinsToHrsMins(props.user.startTime)} and end + at {convertMinsToHrsMins(props.user.endTime)}. +

    +
    +
    + +
    +
    +
    + +
    +
    +

    + Something doesn't look right? +

    +
    +

    Troubleshoot your availability to explore why your times are showing as they are.

    +
    + +
    +
    +
    + {showAddModal && ( +
    +
    + + + + +
    +
    +
    + +
    +
    + +
    +

    + Create a new event type for people to book times with. +

    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + {location.hostname}/{props.user.username}/ + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + minutes +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +

    + Hide the event type from your page, so it can only be booked through its URL. +

    +
    +
    +
    + {/* TODO: Add an error message when required input fields empty*/} +
    + + +
    +
    +
    +
    +
    + )} + {showChangeTimesModal && ( +
    +
    + + + + +
    +
    +
    + +
    +
    + +
    +

    + Set the start and end time of your day and a minimum buffer between your meetings. +

    +
    +
    +
    +
    +
    + +
    + + +
    + : +
    + + +
    +
    +
    + +
    + + +
    + : +
    + + +
    +
    +
    + +
    + + +
    + : +
    + + +
    +
    +
    + + +
    +
    +
    +
    +
    + )} + +
    +
    + ); } export async function getServerSideProps(context) { - const session = await getSession(context); - if (!session) { - return { redirect: { permanent: false, destination: '/auth/login' } }; - } + const session = await getSession(context); + if (!session) { + return { redirect: { permanent: false, destination: "/auth/login" } }; + } - const user = await prisma.user.findFirst({ - where: { - email: session.user.email, - }, - select: { - id: true, - username: true, - startTime: true, - endTime: true, - bufferTime: true - } - }); + const user = await prisma.user.findFirst({ + where: { + email: session.user.email, + }, + select: { + id: true, + username: true, + startTime: true, + endTime: true, + bufferTime: true, + }, + }); - const types = await prisma.eventType.findMany({ - where: { - userId: user.id, - }, - select: { - id: true, - title: true, - slug: true, - description: true, - length: true, - hidden: true - } - }); - return { - props: {user, types}, // will be passed to the page component as props - } + const types = await prisma.eventType.findMany({ + where: { + userId: user.id, + }, + select: { + id: true, + title: true, + slug: true, + description: true, + length: true, + hidden: true, + }, + }); + return { + props: { user, types }, // will be passed to the page component as props + }; } diff --git a/pages/index.tsx b/pages/index.tsx index 4cf03b68..765842f7 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,353 +1,368 @@ -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
    ; - } + const [session, loading] = useSession(); + if (loading) { + return
    ; + } - 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 ( +
    + + Calendso + + - 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 ( -
    - - Calendso - - - - -
    -
    -
    -
    -

    Your stats

    -
    -
    - {stats.map((item) => ( -
    -
    {item.name}
    -
    -
    - {item.stat} -
    -
    -
    - ))} -
    + +
    +
    +
    +
    +

    Your stats

    -
    -
    -

    - Your event types -

    -
    -
      - {props.eventTypes.map((type) => ( -
    • -
      -
      -
      -
      -

      {type.title}

      -

      - in {type.description} -

      -
      -
      -
      -
      +
      + {stats.map((item) => ( +
      +
      {item.name}
      +
      +
      + {item.stat} +
      +
      +
      + ))} +
      +
      +
      +
      +

      + Your event types +

      +
      +
        + {props.eventTypes.map((type) => ( +
      • +
        +
        +
        +
        +

        {type.title}

        +

        in {type.description}

        +
        +
        +
        +
        -
        - - - View - - -
        -
      • - ))} - {props.eventTypes.length == 0 && ( -
        -

        You haven't created any event types.

        - )} -
      -
      -
      -
      -
      -

      Getting started

      -

      - Steps you should take to get started with Calendso. -

      +
      + + Edit + + + + View + + +
      +
    • + ))} + {props.eventTypes.length == 0 && ( +
      +

      You haven't created any event types.

      -
      -
      -
        - {timeline.map((event, eventIdx) => ( -
      • -
        - {eventIdx !== timeline.length - 1 ? ( + )} +
      +
      +
      +
      +
      +

      Getting started

      +

      + Steps you should take to get started with Calendso. +

      +
      +
      +
      +
        + {timeline.map((event, eventIdx) => ( +
      • +
        + {eventIdx !== timeline.length - 1 ? ( +
      • - ))} -
      -
      +
      + + ))} +
    -
    -
    -
    -

    Your day

    -
    - - Configure - -
    -
    -
    -

    - Offering time slots between{" "} - {convertMinsToHrsMins(props.user.startTime)} and{" "} - {convertMinsToHrsMins(props.user.endTime)} -

    -
    -
    -
    -
    -

    - Your integrations -

    -
    - - View more - -
    -
    -
      - {props.credentials.map((integration) => ( -
    • - {integration.type == "google_calendar" && ( - Google Calendar - )} - {integration.type == "office365_calendar" && ( - Office 365 / Outlook.com Calendar - )} - {integration.type == "zoom_video" && ( - Zoom - )} -
      - {integration.type == "office365_calendar" && ( -

      - Office 365 / Outlook.com Calendar -

      - )} - {integration.type == "google_calendar" && ( -

      Google Calendar

      - )} - {integration.type == "zoom_video" && ( -

      Zoom

      - )} - {integration.type.endsWith("_calendar") && ( -

      Calendar Integration

      - )} - {integration.type.endsWith("_video") && ( -

      Video Conferencing

      - )} -
      -
    • - ))} - {props.credentials.length == 0 && ( -
      -

      You haven't added any integrations.

      -
      - )} -
    -
    -
    -
    -

    - Your event types -

    -
    - - View more - -
    -
    - -
    -
    - - - -
    - ); +
    +
    +
    +

    Your day

    +
    + + Configure + +
    +
    +
    +

    + Offering time slots between{" "} + {convertMinsToHrsMins(props.user.startTime)} and{" "} + {convertMinsToHrsMins(props.user.endTime)} +

    +
    +
    +
    +
    +

    + Your integrations +

    +
    + + View more + +
    +
    +
      + {props.credentials.map((integration) => ( +
    • + {integration.type == "google_calendar" && ( + Google Calendar + )} + {integration.type == "office365_calendar" && ( + Office 365 / Outlook.com Calendar + )} + {integration.type == "zoom_video" && ( + Zoom + )} +
      + {integration.type == "office365_calendar" && ( +

      Office 365 / Outlook.com Calendar

      + )} + {integration.type == "google_calendar" && ( +

      Google Calendar

      + )} + {integration.type == "zoom_video" && ( +

      Zoom

      + )} + {integration.type.endsWith("_calendar") && ( +

      Calendar Integration

      + )} + {integration.type.endsWith("_video") && ( +

      Video Conferencing

      + )} +
      +
    • + ))} + {props.credentials.length == 0 && ( +
      +

      You haven't added any integrations.

      +
      + )} +
    +
    +
    +
    +

    + Your event types +

    +
    + + View more + +
    +
    + +
    +
    +
    + +
    +
    + ); } 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 - } -} \ No newline at end of file + 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 + }; +} diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx index 8278a55c..6f7836eb 100644 --- a/pages/settings/profile.tsx +++ b/pages/settings/profile.tsx @@ -1,12 +1,13 @@ import { GetServerSideProps } from "next"; import Head from "next/head"; -import { useRef, useState } from "react"; -import prisma from "../../lib/prisma"; +import { useEffect, useRef, useState } from "react"; +import prisma, { whereAndSelect } from "@lib/prisma"; import Modal from "../../components/Modal"; import Shell from "../../components/Shell"; import SettingsShell from "../../components/Settings"; import Avatar from "../../components/Avatar"; import { getSession } from "next-auth/client"; +import Select from "react-select"; import TimezoneSelect from "react-timezone-select"; import { UsernameInput } from "../../components/ui/UsernameInput"; import ErrorAlert from "../../components/ui/alerts/Error"; @@ -18,12 +19,25 @@ export default function Settings(props) { const descriptionRef = useRef(); const avatarRef = useRef(); const hideBrandingRef = useRef(); + const [selectedTheme, setSelectedTheme] = useState({ value: "" }); const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone }); - const [selectedWeekStartDay, setSelectedWeekStartDay] = useState(props.user.weekStart || "Sunday"); + const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({ value: "" }); const [hasErrors, setHasErrors] = useState(false); const [errorMessage, setErrorMessage] = useState(""); + const themeOptions = [ + { value: "light", label: "Light" }, + { value: "dark", label: "Dark" }, + ]; + + useEffect(() => { + setSelectedTheme( + props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : null + ); + setSelectedWeekStartDay({ value: props.user.weekStart, label: props.user.weekStart }); + }, []); + const closeSuccessModal = () => { setSuccessModalOpen(false); }; @@ -43,7 +57,7 @@ export default function Settings(props) { const enteredDescription = descriptionRef.current.value; const enteredAvatar = avatarRef.current.value; const enteredTimeZone = selectedTimeZone.value; - const enteredWeekStartDay = selectedWeekStartDay; + const enteredWeekStartDay = selectedWeekStartDay.value; const enteredHideBranding = hideBrandingRef.current.checked; // TODO: Add validation @@ -58,6 +72,7 @@ export default function Settings(props) { timeZone: enteredTimeZone, weekStart: enteredWeekStartDay, hideBranding: enteredHideBranding, + theme: selectedTheme ? selectedTheme.value : null, }), headers: { "Content-Type": "application/json", @@ -124,9 +139,8 @@ export default function Settings(props) { name="about" placeholder="A little something about yourself." rows={3} - className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"> - {props.user.bio} - + defaultValue={props.user.bio} + className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md">
    @@ -147,14 +161,48 @@ export default function Settings(props) { First Day of Week
    - + onChange={setSelectedWeekStartDay} + className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md" + options={[ + { value: "Sunday", label: "Sunday" }, + { value: "Monday", label: "Monday" }, + ]} + /> +
    +
    +
    + +
    + setSelectedTheme(e.target.checked ? null : themeOptions[0])} + defaultChecked={!selectedTheme} + className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded" + /> +
    +
    + +
    @@ -257,22 +305,13 @@ export const getServerSideProps: GetServerSideProps = async (context) => { return { redirect: { permanent: false, destination: "/auth/login" } }; } - const user = await prisma.user.findFirst({ - where: { - email: session.user.email, + const user = await whereAndSelect( + prisma.user.findFirst, + { + id: session.user.id, }, - select: { - id: true, - username: true, - name: true, - email: true, - bio: true, - avatar: true, - timeZone: true, - weekStart: true, - hideBranding: true, - }, - }); + ["id", "username", "name", "email", "bio", "avatar", "timeZone", "weekStart", "hideBranding", "theme"] + ); return { props: { user }, // will be passed to the page component as props diff --git a/pages/success.tsx b/pages/success.tsx index 286897b9..d36ea8d9 100644 --- a/pages/success.tsx +++ b/pages/success.tsx @@ -1,6 +1,6 @@ import Head from "next/head"; import Link from "next/link"; -import prisma from "../lib/prisma"; +import prisma, { whereAndSelect } from "../lib/prisma"; import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { CheckIcon } from "@heroicons/react/outline"; @@ -11,6 +11,7 @@ import toArray from "dayjs/plugin/toArray"; import timezone from "dayjs/plugin/timezone"; import { createEvent } from "ics"; import { getEventName } from "../lib/event"; +import Theme from "@components/Theme"; dayjs.extend(utc); dayjs.extend(toArray); @@ -22,6 +23,7 @@ export default function Success(props) { const [is24h, setIs24h] = useState(false); const [date, setDate] = useState(dayjs.utc(router.query.date)); + const { isReady } = Theme(props.user.theme); useEffect(() => { setDate(date.tz(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess())); @@ -31,7 +33,7 @@ export default function Success(props) { const eventName = getEventName(name, props.eventType.title, props.eventType.eventName); function eventLink(): string { - let optional = {}; + const optional = {}; if (location) { optional["location"] = location; } @@ -57,187 +59,186 @@ export default function Success(props) { } return ( -
    - - Booking Confirmed | {eventName} | Calendso - - -
    -
    -
    -
    +
    + ) ); } 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, - eventTypes: true, - hideBranding: true, - }, - }); + const user = context.query.user + ? await whereAndSelect( + prisma.user.findFirst, + { + username: context.query.user, + }, + ["username", "name", "bio", "avatar", "hideBranding", "theme"] + ) + : null; - const eventType = await prisma.eventType.findUnique({ - where: { + if (!user) { + return { + notFound: true, + }; + } + + const eventType = await whereAndSelect( + prisma.eventType.findUnique, + { id: parseInt(context.query.type), }, - select: { - id: true, - title: true, - description: true, - length: true, - eventName: true, - }, - }); + ["id", "title", "description", "length", "eventName"] + ); return { props: { diff --git a/prisma/migrations/20210709231256_add_user_theme/migration.sql b/prisma/migrations/20210709231256_add_user_theme/migration.sql new file mode 100644 index 00000000..75875267 --- /dev/null +++ b/prisma/migrations/20210709231256_add_user_theme/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "users" ADD COLUMN "theme" TEXT; diff --git a/prisma/migrations/20210714151216_event_type_period_settings/migration.sql b/prisma/migrations/20210714151216_event_type_period_settings/migration.sql new file mode 100644 index 00000000..0493981b --- /dev/null +++ b/prisma/migrations/20210714151216_event_type_period_settings/migration.sql @@ -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'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ddaf7d81..7ea3cafe 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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 { @@ -50,6 +55,7 @@ model User { endTime Int @default(1440) bufferTime Int @default(0) hideBranding Boolean @default(false) + theme String? createdDate DateTime @default(now()) @map(name: "created") eventTypes EventType[] credentials Credential[] diff --git a/tailwind.config.js b/tailwind.config.js index 24bfaf0f..a6063b5d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,7 +1,7 @@ module.exports = { mode: "jit", purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"], - darkMode: "media", + darkMode: "class", theme: { extend: { colors: { @@ -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: { diff --git a/yarn.lock b/yarn.lock index 478c781d..0fea0ba0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"