No confirmation shall be needed when rescheduling events that need confirmation (#440)
* No reconfirmation needed when rescheduling * adapted success page * Parse query as string Co-authored-by: nicolas <privat@nicolasjessen.de> Co-authored-by: Bailey Pumfleet <pumfleet@hey.com> Co-authored-by: Peer_Rich <peeroke@gmail.com> Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
parent
4c6bf96213
commit
961f297ba8
14 changed files with 41 additions and 39 deletions
|
@ -1,5 +1,5 @@
|
||||||
import Cropper from "react-easy-crop";
|
import Cropper from "react-easy-crop";
|
||||||
import { useState, useCallback, useRef } from "react";
|
import { useCallback, useRef, useState } from "react";
|
||||||
import Slider from "./Slider";
|
import Slider from "./Slider";
|
||||||
|
|
||||||
export default function ImageUploader({ target, id, buttonMsg, handleAvatarChange, imageRef }) {
|
export default function ImageUploader({ target, id, buttonMsg, handleAvatarChange, imageRef }) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState, useRef } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { ArrowLeftIcon, PlusIcon, TrashIcon } from "@heroicons/react/outline";
|
import { ArrowLeftIcon, PlusIcon, TrashIcon } from "@heroicons/react/outline";
|
||||||
import ErrorAlert from "@components/ui/alerts/Error";
|
import ErrorAlert from "@components/ui/alerts/Error";
|
||||||
import { UsernameInput } from "@components/ui/UsernameInput";
|
import { UsernameInput } from "@components/ui/UsernameInput";
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {
|
import {
|
||||||
TrashIcon,
|
|
||||||
DotsHorizontalIcon,
|
DotsHorizontalIcon,
|
||||||
|
ExternalLinkIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
PencilAltIcon,
|
PencilAltIcon,
|
||||||
ExternalLinkIcon,
|
TrashIcon,
|
||||||
} from "@heroicons/react/outline";
|
} from "@heroicons/react/outline";
|
||||||
import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/Dropdown";
|
import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/Dropdown";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { XCircleIcon, InformationCircleIcon, CheckCircleIcon } from "@heroicons/react/solid";
|
import { CheckCircleIcon, InformationCircleIcon, XCircleIcon } from "@heroicons/react/solid";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Text from "@components/ui/Text";
|
||||||
import { PlusIcon, TrashIcon } from "@heroicons/react/outline";
|
import { PlusIcon, TrashIcon } from "@heroicons/react/outline";
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
|
|
||||||
export const SCHEDULE_FORM_ID = "SCHEDULE_FORM_ID";
|
export const SCHEDULE_FORM_ID = "SCHEDULE_FORM_ID";
|
||||||
export const toCalendsoAvailabilityFormat = (schedule: Schedule) => {
|
export const toCalendsoAvailabilityFormat = (schedule: Schedule) => {
|
||||||
return schedule;
|
return schedule;
|
||||||
|
|
|
@ -5,11 +5,11 @@ import { Credential } from "@prisma/client";
|
||||||
import CalEventParser from "./CalEventParser";
|
import CalEventParser from "./CalEventParser";
|
||||||
import { EventResult } from "@lib/events/EventManager";
|
import { EventResult } from "@lib/events/EventManager";
|
||||||
import logger from "@lib/logger";
|
import logger from "@lib/logger";
|
||||||
|
|
||||||
const log = logger.getChildLogger({ prefix: ["[lib] calendarClient"] });
|
|
||||||
import { CalDavCalendar } from "./integrations/CalDav/CalDavCalendarAdapter";
|
import { CalDavCalendar } from "./integrations/CalDav/CalDavCalendarAdapter";
|
||||||
import { AppleCalendar } from "./integrations/Apple/AppleCalendarAdapter";
|
import { AppleCalendar } from "./integrations/Apple/AppleCalendarAdapter";
|
||||||
|
|
||||||
|
const log = logger.getChildLogger({ prefix: ["[lib] calendarClient"] });
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { google } = require("googleapis");
|
const { google } = require("googleapis");
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { IntegrationCalendar, CalendarApiAdapter, CalendarEvent } from "../../calendarClient";
|
import { CalendarApiAdapter, CalendarEvent, IntegrationCalendar } from "../../calendarClient";
|
||||||
import { symmetricDecrypt } from "@lib/crypto";
|
import { symmetricDecrypt } from "@lib/crypto";
|
||||||
import {
|
import {
|
||||||
createAccount,
|
createAccount,
|
||||||
fetchCalendars,
|
|
||||||
fetchCalendarObjects,
|
|
||||||
getBasicAuthHeaders,
|
|
||||||
createCalendarObject,
|
createCalendarObject,
|
||||||
updateCalendarObject,
|
|
||||||
deleteCalendarObject,
|
deleteCalendarObject,
|
||||||
|
fetchCalendarObjects,
|
||||||
|
fetchCalendars,
|
||||||
|
getBasicAuthHeaders,
|
||||||
|
updateCalendarObject,
|
||||||
} from "tsdav";
|
} from "tsdav";
|
||||||
import { Credential } from "@prisma/client";
|
import { Credential } from "@prisma/client";
|
||||||
import ICAL from "ical.js";
|
import ICAL from "ical.js";
|
||||||
import { createEvent, DurationObject, Attendee, Person } from "ics";
|
import { Attendee, createEvent, DurationObject, Person } from "ics";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { stripHtml } from "../../emails/helpers";
|
import { stripHtml } from "../../emails/helpers";
|
||||||
|
|
|
@ -242,13 +242,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
}; // used for invitee emails
|
}; // used for invitee emails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize EventManager with credentials
|
||||||
|
const rescheduleUid = req.body.rescheduleUid;
|
||||||
|
|
||||||
const bookingCreateInput: Prisma.BookingCreateInput = {
|
const bookingCreateInput: Prisma.BookingCreateInput = {
|
||||||
uid,
|
uid,
|
||||||
title: evt.title,
|
title: evt.title,
|
||||||
startTime: dayjs(evt.startTime).toDate(),
|
startTime: dayjs(evt.startTime).toDate(),
|
||||||
endTime: dayjs(evt.endTime).toDate(),
|
endTime: dayjs(evt.endTime).toDate(),
|
||||||
description: evt.description,
|
description: evt.description,
|
||||||
confirmed: !eventType.requiresConfirmation,
|
confirmed: !eventType.requiresConfirmation || !!rescheduleUid,
|
||||||
location: evt.location,
|
location: evt.location,
|
||||||
eventType: {
|
eventType: {
|
||||||
connect: {
|
connect: {
|
||||||
|
@ -375,9 +378,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize EventManager with credentials
|
|
||||||
const eventManager = new EventManager(user.credentials);
|
const eventManager = new EventManager(user.credentials);
|
||||||
const rescheduleUid = req.body.rescheduleUid;
|
|
||||||
if (rescheduleUid) {
|
if (rescheduleUid) {
|
||||||
// Use EventManager to conditionally use all needed integrations.
|
// Use EventManager to conditionally use all needed integrations.
|
||||||
const updateResults: CreateUpdateResult = await eventManager.update(evt, rescheduleUid);
|
const updateResults: CreateUpdateResult = await eventManager.update(evt, rescheduleUid);
|
||||||
|
@ -410,7 +412,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType.requiresConfirmation) {
|
if (eventType.requiresConfirmation && !rescheduleUid) {
|
||||||
await new EventOrganizerRequestMail(evt, uid).sendEmail();
|
await new EventOrganizerRequestMail(evt, uid).sendEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,6 +431,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// booking succesfull
|
// booking successful
|
||||||
return res.status(201).json(booking);
|
return res.status(201).json(booking);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
import prisma from "../../../../lib/prisma";
|
import prisma from "../../../../lib/prisma";
|
||||||
|
|
||||||
const scopes = ["offline_access", "Calendars.Read", "Calendars.ReadWrite"];
|
const scopes = ["offline_access", "Calendars.Read", "Calendars.ReadWrite"];
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Modal from "@components/Modal";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import Select, { OptionTypeBase } from "react-select";
|
import Select, { OptionTypeBase } from "react-select";
|
||||||
import prisma from "@lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
import { EventTypeCustomInput, EventTypeCustomInputType, SchedulingType } from "@prisma/client";
|
import { Availability, EventTypeCustomInput, EventTypeCustomInputType, SchedulingType } from "@prisma/client";
|
||||||
import { LocationType } from "@lib/location";
|
import { LocationType } from "@lib/location";
|
||||||
import Shell from "@components/Shell";
|
import Shell from "@components/Shell";
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
|
@ -28,7 +28,6 @@ import {
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import timezone from "dayjs/plugin/timezone";
|
import timezone from "dayjs/plugin/timezone";
|
||||||
import { Availability } from "@prisma/client";
|
|
||||||
import { validJson } from "@lib/jsonUtils";
|
import { validJson } from "@lib/jsonUtils";
|
||||||
import throttle from "lodash.throttle";
|
import throttle from "lodash.throttle";
|
||||||
import "react-dates/initialize";
|
import "react-dates/initialize";
|
||||||
|
|
|
@ -2,15 +2,15 @@ import Head from "next/head";
|
||||||
import prisma from "@lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
import { useSession } from "next-auth/client";
|
import { useSession } from "next-auth/client";
|
||||||
import {
|
import {
|
||||||
|
EventType,
|
||||||
EventTypeCreateInput,
|
EventTypeCreateInput,
|
||||||
|
Schedule,
|
||||||
ScheduleCreateInput,
|
ScheduleCreateInput,
|
||||||
User,
|
User,
|
||||||
UserUpdateInput,
|
UserUpdateInput,
|
||||||
EventType,
|
|
||||||
Schedule,
|
|
||||||
} from "@prisma/client";
|
} from "@prisma/client";
|
||||||
import { NextPageContext } from "next";
|
import { NextPageContext } from "next";
|
||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { validJson } from "@lib/jsonUtils";
|
import { validJson } from "@lib/jsonUtils";
|
||||||
import TimezoneSelect from "react-timezone-select";
|
import TimezoneSelect from "react-timezone-select";
|
||||||
import Text from "@components/ui/Text";
|
import Text from "@components/ui/Text";
|
||||||
|
@ -18,8 +18,6 @@ import ErrorAlert from "@components/ui/alerts/Error";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import timezone from "dayjs/plugin/timezone";
|
import timezone from "dayjs/plugin/timezone";
|
||||||
dayjs.extend(utc);
|
|
||||||
dayjs.extend(timezone);
|
|
||||||
import AddCalDavIntegration, {
|
import AddCalDavIntegration, {
|
||||||
ADD_CALDAV_INTEGRATION_FORM_TITLE,
|
ADD_CALDAV_INTEGRATION_FORM_TITLE,
|
||||||
} from "@lib/integrations/CalDav/components/AddCalDavIntegration";
|
} from "@lib/integrations/CalDav/components/AddCalDavIntegration";
|
||||||
|
@ -33,6 +31,9 @@ import { ArrowRightIcon } from "@heroicons/react/outline";
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
import Button from "@components/ui/Button";
|
import Button from "@components/ui/Button";
|
||||||
|
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
const DEFAULT_EVENT_TYPES = [
|
const DEFAULT_EVENT_TYPES = [
|
||||||
{
|
{
|
||||||
title: "15 Min Meeting",
|
title: "15 Min Meeting",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import prisma from "@lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
import Shell from "@components/Shell";
|
import Shell from "@components/Shell";
|
||||||
import { useEffect, useState, useRef, useCallback } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useSession } from "next-auth/client";
|
import { useSession } from "next-auth/client";
|
||||||
import { CheckCircleIcon, ChevronRightIcon, PlusIcon, XCircleIcon } from "@heroicons/react/solid";
|
import { CheckCircleIcon, ChevronRightIcon, PlusIcon, XCircleIcon } from "@heroicons/react/solid";
|
||||||
import { InformationCircleIcon } from "@heroicons/react/outline";
|
import { InformationCircleIcon } from "@heroicons/react/outline";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { GetServerSideProps } from "next";
|
import { GetServerSideProps } from "next";
|
||||||
import Shell from "@components/Shell";
|
import Shell from "@components/Shell";
|
||||||
import SettingsShell from "@components/Settings";
|
import SettingsShell from "@components/Settings";
|
||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import type { Session } from "next-auth";
|
import type { Session } from "next-auth";
|
||||||
import { useSession } from "next-auth/client";
|
import { useSession } from "next-auth/client";
|
||||||
import { UsersIcon } from "@heroicons/react/outline";
|
import { UsersIcon } from "@heroicons/react/outline";
|
||||||
|
|
|
@ -23,7 +23,7 @@ dayjs.extend(timezone);
|
||||||
|
|
||||||
export default function Success(props: inferSSRProps<typeof getServerSideProps>) {
|
export default function Success(props: inferSSRProps<typeof getServerSideProps>) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { location, name } = router.query;
|
const { location, name, reschedule } = router.query;
|
||||||
|
|
||||||
const [is24h, setIs24h] = useState(false);
|
const [is24h, setIs24h] = useState(false);
|
||||||
const [date, setDate] = useState(dayjs.utc(asStringOrNull(router.query.date)));
|
const [date, setDate] = useState(dayjs.utc(asStringOrNull(router.query.date)));
|
||||||
|
@ -62,12 +62,14 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
||||||
return encodeURIComponent(event.value);
|
return encodeURIComponent(event.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const needsConfirmation = props.eventType.requiresConfirmation && reschedule != "true";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isReady && (
|
isReady && (
|
||||||
<div className="bg-neutral-50 dark:bg-neutral-900 h-screen">
|
<div className="bg-neutral-50 dark:bg-neutral-900 h-screen">
|
||||||
<HeadSeo
|
<HeadSeo
|
||||||
title={`Booking ${props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"}`}
|
title={`Booking ${needsConfirmation ? "Submitted" : "Confirmed"}`}
|
||||||
description={`Booking ${props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"}`}
|
description={`Booking ${needsConfirmation ? "Submitted" : "Confirmed"}`}
|
||||||
/>
|
/>
|
||||||
<main className="max-w-3xl mx-auto py-24">
|
<main className="max-w-3xl mx-auto py-24">
|
||||||
<div className="fixed z-50 inset-0 overflow-y-auto">
|
<div className="fixed z-50 inset-0 overflow-y-auto">
|
||||||
|
@ -83,22 +85,18 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
||||||
aria-labelledby="modal-headline">
|
aria-labelledby="modal-headline">
|
||||||
<div>
|
<div>
|
||||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||||
{!props.eventType.requiresConfirmation && (
|
{!needsConfirmation && <CheckIcon className="h-8 w-8 text-green-600" />}
|
||||||
<CheckIcon className="h-8 w-8 text-green-600" />
|
{needsConfirmation && <ClockIcon className="h-8 w-8 text-green-600" />}
|
||||||
)}
|
|
||||||
{props.eventType.requiresConfirmation && (
|
|
||||||
<ClockIcon className="h-8 w-8 text-green-600" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-center sm:mt-5">
|
<div className="mt-3 text-center sm:mt-5">
|
||||||
<h3
|
<h3
|
||||||
className="text-2xl leading-6 font-semibold dark:text-white text-neutral-900"
|
className="text-2xl leading-6 font-semibold dark:text-white text-neutral-900"
|
||||||
id="modal-headline">
|
id="modal-headline">
|
||||||
{props.eventType.requiresConfirmation ? "Submitted" : "This meeting is scheduled"}
|
{needsConfirmation ? "Submitted" : "This meeting is scheduled"}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<p className="text-sm text-neutral-600 dark:text-gray-300">
|
<p className="text-sm text-neutral-600 dark:text-gray-300">
|
||||||
{props.eventType.requiresConfirmation
|
{needsConfirmation
|
||||||
? props.profile.name !== null
|
? props.profile.name !== null
|
||||||
? `${props.profile.name} still needs to confirm or reject the booking.`
|
? `${props.profile.name} still needs to confirm or reject the booking.`
|
||||||
: "Your booking still needs to be confirmed or rejected."
|
: "Your booking still needs to be confirmed or rejected."
|
||||||
|
@ -126,7 +124,7 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!props.eventType.requiresConfirmation && (
|
{!needsConfirmation && (
|
||||||
<div className="mt-5 sm:mt-0 sm:pt-4 pt-2 text-center flex">
|
<div className="mt-5 sm:mt-0 sm:pt-4 pt-2 text-center flex">
|
||||||
<span className="font-medium text-gray-700 dark:text-gray-50 flex self-center mr-6">
|
<span className="font-medium text-gray-700 dark:text-gray-50 flex self-center mr-6">
|
||||||
Add to calendar
|
Add to calendar
|
||||||
|
|
Loading…
Reference in a new issue