save additional inputs as json + view details of booking (#2796)
* move custom inputs from description to own json object * show custom inputs on success page * fix type error * add custom inputs to email and webhook * add custom inputs to all emails * add values for custom inputs when rescheduling * add custom input everywhere description is shown * fix bug with boolean value * fix issues with null values * disable custom inputs and add notes for organizer * don't show custom input with empty string * don't show custom inputs with empty string in calender event and email * add link to booking details page * redirect to success page to see booking details * add functionality to cancel and reschedule booking * fix bookings that require confirmation * clean code * fix infinite lopp in useEffect of success page * show web conference details message when integration as location * improve design of cancelling event * clean code * disable darkmode for organizer on booking details page * fix dark mode for cancelling booking * fix build error * Fixes infinite loop * Fixes infinite loop * Fixes infinite loop * Update all Yarn dependencies (2022-05-16) (#2769) * Update all Yarn dependencies (2022-05-16) * Upgrade dependencies * Removes deprecated packages * Upgrades deps * Updates submodules * Update yarn.lock * Linting * Linting * Update website * Build fixes * TODO: fix this * Module resolving * Type fixes * Intercom fixes on SSG * Fixes infinite loop * Upgrades to React 18 * Type fixes * Locks node version to 14 * Upgrades daily-js * Readds missing types * Upgrades playwright * Noop when intercom is not installed * Update website * Removed yarn.lock in favor of monorepo Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Co-authored-by: zomars <zomars@me.com> * Create ci.yml * Update ci.yml * Reintroduces typescript-eslint Buckle up! * Type fixes * Update ci.yml * Update api * Update admin * Reusable inferSSRProps * Linting * Linting * Prisma fixes * Update ci.yml * Cache testing * Update e2e.yml * Update DatePicker.tsx * Update e2e.yml * Revert "Linting" This reverts commitadf817766e
. * Revert "Linting" This reverts commit1b59dacd64
. * Linting * Update e2e.yml * Ci updates * Add team Id to hash url (#2803) * Fix missing tabs - Embed (#2804) * Fix missing tabs * Fix Eslint error * Fix Eslint errors * Add import statement (#2812) * Add import statement * Update apps/docs/next.config.js Co-authored-by: Omar López <zomars@me.com> * Show success page if booking was deleted on calendar (#2808) * Add exception to 410 * Fix type error * Add GoogelCalError type * only show invite link for app.cal.dev (#2807) Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: Omar López <zomars@me.com> * fix: update eslint config to test .ts and .js separately (#2805) * fix: update eslint config * fix: update ts ignore * fix: update eslint config * Update TeamAvailabilityScreen.tsx * Type fixes * Update useIntercom.ts Co-authored-by: Omar López <zomars@me.com> * fix: sync api to latest commit (#2810) Co-authored-by: Agusti Fernandez Pardo <git@agusti.me> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> * Embed React improvements (#2782) * Add off support. Add getApi export. * Add publish command * Add embed-snippet in prod deps * Update README * Update package.json Co-authored-by: Bailey Pumfleet <pumfleet@hey.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> * Consolidates test-results * Type fixes * Abstracts minimal booking select * Type fixes * Update listBookings.ts * Update common.json * Update bookingReminder.ts * Consolidates isOutOfBounds * Update webhookResponse-chromium.txt * Update TableActions.tsx * Type fixes * Update BookingPage.tsx * Update webhookResponse-chromium.txt Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: Bailey Pumfleet <pumfleet@hey.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: iamkun <kunhello@outlook.com> Co-authored-by: Agusti Fernandez Pardo <me@agusti.me> Co-authored-by: Agusti Fernandez Pardo <git@agusti.me> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
8455945761
commit
e7f1a829fd
|
@ -1,8 +1,15 @@
|
||||||
import { BanIcon, CheckIcon, ClockIcon, XIcon, PencilAltIcon } from "@heroicons/react/outline";
|
import {
|
||||||
import { PaperAirplaneIcon } from "@heroicons/react/outline";
|
BanIcon,
|
||||||
|
CheckIcon,
|
||||||
|
ClockIcon,
|
||||||
|
PaperAirplaneIcon,
|
||||||
|
PencilAltIcon,
|
||||||
|
XIcon,
|
||||||
|
} from "@heroicons/react/outline";
|
||||||
import { RefreshIcon } from "@heroicons/react/solid";
|
import { RefreshIcon } from "@heroicons/react/solid";
|
||||||
import { BookingStatus } from "@prisma/client";
|
import { BookingStatus } from "@prisma/client";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useMutation } from "react-query";
|
import { useMutation } from "react-query";
|
||||||
import { Frequency as RRuleFrequency } from "rrule";
|
import { Frequency as RRuleFrequency } from "rrule";
|
||||||
|
@ -17,7 +24,7 @@ import { TextArea } from "@calcom/ui/form/fields";
|
||||||
import { HttpError } from "@lib/core/http/error";
|
import { HttpError } from "@lib/core/http/error";
|
||||||
import useMeQuery from "@lib/hooks/useMeQuery";
|
import useMeQuery from "@lib/hooks/useMeQuery";
|
||||||
import { parseRecurringDates } from "@lib/parseDate";
|
import { parseRecurringDates } from "@lib/parseDate";
|
||||||
import { inferQueryOutput, trpc, inferQueryInput } from "@lib/trpc";
|
import { inferQueryInput, inferQueryOutput, trpc } from "@lib/trpc";
|
||||||
|
|
||||||
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
|
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
|
||||||
import TableActions, { ActionType } from "@components/ui/TableActions";
|
import TableActions, { ActionType } from "@components/ui/TableActions";
|
||||||
|
@ -37,6 +44,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
const user = query.data;
|
const user = query.data;
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
|
const router = useRouter();
|
||||||
const [rejectionReason, setRejectionReason] = useState<string>("");
|
const [rejectionReason, setRejectionReason] = useState<string>("");
|
||||||
const [rejectionDialogIsOpen, setRejectionDialogIsOpen] = useState(false);
|
const [rejectionDialogIsOpen, setRejectionDialogIsOpen] = useState(false);
|
||||||
const mutation = useMutation(
|
const mutation = useMutation(
|
||||||
|
@ -81,7 +89,10 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
|
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
|
||||||
? t("reject_all")
|
? t("reject_all")
|
||||||
: t("reject"),
|
: t("reject"),
|
||||||
onClick: () => setRejectionDialogIsOpen(true),
|
onClick: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setRejectionDialogIsOpen(true);
|
||||||
|
},
|
||||||
icon: BanIcon,
|
icon: BanIcon,
|
||||||
disabled: mutation.isLoading,
|
disabled: mutation.isLoading,
|
||||||
},
|
},
|
||||||
|
@ -91,7 +102,10 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
|
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
|
||||||
? t("confirm_all")
|
? t("confirm_all")
|
||||||
: t("confirm"),
|
: t("confirm"),
|
||||||
onClick: () => mutation.mutate(true),
|
onClick: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
mutation.mutate(true);
|
||||||
|
},
|
||||||
icon: CheckIcon,
|
icon: CheckIcon,
|
||||||
disabled: mutation.isLoading,
|
disabled: mutation.isLoading,
|
||||||
color: "primary",
|
color: "primary",
|
||||||
|
@ -120,7 +134,10 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
id: "reschedule_request",
|
id: "reschedule_request",
|
||||||
icon: ClockIcon,
|
icon: ClockIcon,
|
||||||
label: t("send_reschedule_request"),
|
label: t("send_reschedule_request"),
|
||||||
onClick: () => setIsOpenRescheduleDialog(true),
|
onClick: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsOpenRescheduleDialog(true);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -150,6 +167,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
i18n
|
i18n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RescheduleDialog
|
<RescheduleDialog
|
||||||
|
@ -191,7 +209,30 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<tr className="flex">
|
<tr
|
||||||
|
className="flex cursor-pointer hover:bg-neutral-50"
|
||||||
|
onClick={() =>
|
||||||
|
router.push({
|
||||||
|
pathname: "/success",
|
||||||
|
query: {
|
||||||
|
date: booking.startTime,
|
||||||
|
type: booking.eventType.id,
|
||||||
|
eventSlug: booking.eventType.slug,
|
||||||
|
user: user?.username || "",
|
||||||
|
name: booking.attendees[0].name,
|
||||||
|
email: booking.attendees[0].email,
|
||||||
|
location: booking.location
|
||||||
|
? booking.location.includes("integration")
|
||||||
|
? (t("web_conferencing_details_to_follow") as string)
|
||||||
|
: booking.location
|
||||||
|
: "",
|
||||||
|
eventName: booking.eventType.eventName || "",
|
||||||
|
bookingId: booking.id,
|
||||||
|
recur: booking.recurringEventId,
|
||||||
|
reschedule: booking.confirmed,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}>
|
||||||
<td className="hidden whitespace-nowrap py-4 align-top ltr:pl-6 rtl:pr-6 sm:table-cell sm:w-56">
|
<td className="hidden whitespace-nowrap py-4 align-top ltr:pl-6 rtl:pr-6 sm:table-cell sm:w-56">
|
||||||
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
|
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
|
@ -264,9 +305,12 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{booking.attendees.length !== 0 && (
|
{booking.attendees.length !== 0 && (
|
||||||
<div className="text-sm text-gray-900 hover:text-blue-500">
|
<a
|
||||||
<a href={"mailto:" + booking.attendees[0].email}>{booking.attendees[0].email}</a>
|
className="text-sm text-gray-900 hover:text-blue-500"
|
||||||
</div>
|
href={"mailto:" + booking.attendees[0].email}
|
||||||
|
onClick={(e) => e.stopPropagation()}>
|
||||||
|
{booking.attendees[0].email}
|
||||||
|
</a>
|
||||||
)}
|
)}
|
||||||
{isCancelled && booking.rescheduled && (
|
{isCancelled && booking.rescheduled && (
|
||||||
<div className="mt-2 inline-block text-left text-sm md:hidden">
|
<div className="mt-2 inline-block text-left text-sm md:hidden">
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { XIcon } from "@heroicons/react/solid";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { Button } from "@calcom/ui/Button";
|
||||||
|
|
||||||
|
import useTheme from "@lib/hooks/useTheme";
|
||||||
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
booking: {
|
||||||
|
title?: string;
|
||||||
|
uid?: string;
|
||||||
|
};
|
||||||
|
profile: {
|
||||||
|
name: string | null;
|
||||||
|
slug: string | null;
|
||||||
|
};
|
||||||
|
team?: string | null;
|
||||||
|
setIsCancellationMode: (value: boolean) => void;
|
||||||
|
theme: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CancelBooking(props: Props) {
|
||||||
|
const [cancellationReason, setCancellationReason] = useState<string>("");
|
||||||
|
const { t } = useLocale();
|
||||||
|
const router = useRouter();
|
||||||
|
const { booking, profile, team } = props;
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const telemetry = useTelemetry();
|
||||||
|
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
|
||||||
|
const { isReady, Theme } = useTheme(props.theme);
|
||||||
|
|
||||||
|
if (isReady) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Theme />
|
||||||
|
{error && (
|
||||||
|
<div>
|
||||||
|
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
|
||||||
|
<XIcon className="h-6 w-6 text-red-600" />
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 text-center sm:mt-5">
|
||||||
|
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
|
||||||
|
{error}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!error && (
|
||||||
|
<div className="mt-5 sm:mt-6">
|
||||||
|
<label className="text-bookingdark font-medium dark:text-white">{t("cancellation_reason")}</label>
|
||||||
|
<textarea
|
||||||
|
placeholder={t("cancellation_reason_placeholder")}
|
||||||
|
value={cancellationReason}
|
||||||
|
onChange={(e) => setCancellationReason(e.target.value)}
|
||||||
|
className="mt-2 mb-3 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white sm:mb-3 "
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
<div className="flex rtl:space-x-reverse">
|
||||||
|
<div className="w-full">
|
||||||
|
<Button color="secondary" onClick={() => router.push("/reschedule/" + booking?.uid)}>
|
||||||
|
{t("reschedule_this")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="w-full space-x-2 text-right">
|
||||||
|
<Button color="secondary" onClick={() => props.setIsCancellationMode(false)}>
|
||||||
|
{t("nevermind")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
data-testid="cancel"
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
uid: booking?.uid,
|
||||||
|
reason: cancellationReason,
|
||||||
|
};
|
||||||
|
|
||||||
|
telemetry.withJitsu((jitsu) =>
|
||||||
|
jitsu.track(telemetryEventTypes.bookingCancelled, collectPageParameters())
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = await fetch("/api/cancel", {
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status >= 200 && res.status < 300) {
|
||||||
|
await router.push(
|
||||||
|
`/cancel/success?name=${props.profile.name}&title=${booking?.title}&eventPage=${
|
||||||
|
profile.slug
|
||||||
|
}&team=${team ? 1 : 0}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
setError(
|
||||||
|
`${t("error_with_status_code_occured", { status: res.status })} ${t(
|
||||||
|
"please_try_again"
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
loading={loading}>
|
||||||
|
{t("cancel_event")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <></>;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
|
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
|
||||||
import { EventType, PeriodType } from "@prisma/client";
|
import { PeriodType } from "@prisma/client";
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import dayjsBusinessTime from "dayjs-business-days2";
|
import dayjsBusinessTime from "dayjs-business-days2";
|
||||||
import timezone from "dayjs/plugin/timezone";
|
import timezone from "dayjs/plugin/timezone";
|
||||||
|
@ -14,6 +14,7 @@ import classNames from "@lib/classNames";
|
||||||
import { timeZone } from "@lib/clock";
|
import { timeZone } from "@lib/clock";
|
||||||
import { weekdayNames } from "@lib/core/i18n/weekday";
|
import { weekdayNames } from "@lib/core/i18n/weekday";
|
||||||
import { doWorkAsync } from "@lib/doWorkAsync";
|
import { doWorkAsync } from "@lib/doWorkAsync";
|
||||||
|
import isOutOfBounds from "@lib/isOutOfBounds";
|
||||||
import getSlots from "@lib/slots";
|
import getSlots from "@lib/slots";
|
||||||
import { WorkingHours } from "@lib/types/schedule";
|
import { WorkingHours } from "@lib/types/schedule";
|
||||||
|
|
||||||
|
@ -37,42 +38,6 @@ type DatePickerProps = {
|
||||||
minimumBookingNotice: number;
|
minimumBookingNotice: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isOutOfBounds(
|
|
||||||
time: dayjs.ConfigType,
|
|
||||||
{
|
|
||||||
periodType,
|
|
||||||
periodDays,
|
|
||||||
periodCountCalendarDays,
|
|
||||||
periodStartDate,
|
|
||||||
periodEndDate,
|
|
||||||
}: Pick<
|
|
||||||
EventType,
|
|
||||||
"periodType" | "periodDays" | "periodCountCalendarDays" | "periodStartDate" | "periodEndDate"
|
|
||||||
>
|
|
||||||
) {
|
|
||||||
const date = dayjs(time);
|
|
||||||
if (!periodDays) return false;
|
|
||||||
|
|
||||||
switch (periodType) {
|
|
||||||
case PeriodType.ROLLING: {
|
|
||||||
const periodRollingEndDay = periodCountCalendarDays
|
|
||||||
? dayjs().utcOffset(date.utcOffset()).add(periodDays, "days").endOf("day")
|
|
||||||
: dayjs().utcOffset(date.utcOffset()).businessDaysAdd(periodDays).endOf("day");
|
|
||||||
return date.endOf("day").isAfter(periodRollingEndDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
case PeriodType.RANGE: {
|
|
||||||
const periodRangeStartDay = dayjs(periodStartDate).utcOffset(date.utcOffset()).endOf("day");
|
|
||||||
const periodRangeEndDay = dayjs(periodEndDate).utcOffset(date.utcOffset()).endOf("day");
|
|
||||||
return date.endOf("day").isBefore(periodRangeStartDay) || date.endOf("day").isAfter(periodRangeEndDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
case PeriodType.UNLIMITED:
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function DatePicker({
|
function DatePicker({
|
||||||
weekStart,
|
weekStart,
|
||||||
onDatePicked,
|
onDatePicked,
|
||||||
|
|
|
@ -77,7 +77,7 @@ type BookingFormValues = {
|
||||||
phone?: string;
|
phone?: string;
|
||||||
hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers
|
hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers
|
||||||
customInputs?: {
|
customInputs?: {
|
||||||
[key: string]: string;
|
[key: string]: string | boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ const BookingPage = ({
|
||||||
}, [router.query.guest]);
|
}, [router.query.guest]);
|
||||||
|
|
||||||
const locationInfo = (type: LocationType) => locations.find((location) => location.type === type);
|
const locationInfo = (type: LocationType) => locations.find((location) => location.type === type);
|
||||||
const loggedInIsOwner = eventType?.users[0]?.name === session?.user?.name;
|
const loggedInIsOwner = eventType?.users[0]?.id === session?.user?.id;
|
||||||
const guestListEmails = !isDynamicGroupBooking
|
const guestListEmails = !isDynamicGroupBooking
|
||||||
? booking?.attendees.slice(1).map((attendee) => attendee.email)
|
? booking?.attendees.slice(1).map((attendee) => attendee.email)
|
||||||
: [];
|
: [];
|
||||||
|
@ -244,11 +244,22 @@ const BookingPage = ({
|
||||||
if (!primaryAttendee) {
|
if (!primaryAttendee) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const customInputType = booking.customInputs;
|
||||||
return {
|
return {
|
||||||
name: primaryAttendee.name || "",
|
name: primaryAttendee.name || "",
|
||||||
email: primaryAttendee.email || "",
|
email: primaryAttendee.email || "",
|
||||||
guests: guestListEmails,
|
guests: guestListEmails,
|
||||||
notes: booking.description || "",
|
notes: booking.description || "",
|
||||||
|
customInputs: eventType.customInputs.reduce(
|
||||||
|
(customInputs, input) => ({
|
||||||
|
...customInputs,
|
||||||
|
[input.id]: booking.customInputs
|
||||||
|
? booking.customInputs[input.label as keyof typeof customInputType]
|
||||||
|
: "",
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -400,6 +411,9 @@ const BookingPage = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const disableInput = !!rescheduleUid;
|
const disableInput = !!rescheduleUid;
|
||||||
|
const disabledExceptForOwner = disableInput && !loggedInIsOwner;
|
||||||
|
const inputClassName =
|
||||||
|
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black disabled:bg-gray-200 disabled:hover:cursor-not-allowed dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 disabled:dark:text-gray-500 sm:text-sm";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -541,10 +555,7 @@ const BookingPage = ({
|
||||||
name="name"
|
name="name"
|
||||||
id="name"
|
id="name"
|
||||||
required
|
required
|
||||||
className={classNames(
|
className={inputClassName}
|
||||||
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
|
|
||||||
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
|
|
||||||
)}
|
|
||||||
placeholder={t("example_name")}
|
placeholder={t("example_name")}
|
||||||
disabled={disableInput}
|
disabled={disableInput}
|
||||||
/>
|
/>
|
||||||
|
@ -561,8 +572,7 @@ const BookingPage = ({
|
||||||
{...bookingForm.register("email")}
|
{...bookingForm.register("email")}
|
||||||
required
|
required
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"focus:border-brand block w-full rounded-sm shadow-sm focus:ring-black dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
|
inputClassName,
|
||||||
disableInput ? "bg-gray-200 dark:text-gray-500" : "",
|
|
||||||
bookingForm.formState.errors.email
|
bookingForm.formState.errors.email
|
||||||
? "border-red-700 focus:ring-red-700"
|
? "border-red-700 focus:ring-red-700"
|
||||||
: " border-gray-300 dark:border-gray-900"
|
: " border-gray-300 dark:border-gray-900"
|
||||||
|
@ -637,12 +647,9 @@ const BookingPage = ({
|
||||||
})}
|
})}
|
||||||
id={"custom_" + input.id}
|
id={"custom_" + input.id}
|
||||||
rows={3}
|
rows={3}
|
||||||
className={classNames(
|
className={inputClassName}
|
||||||
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
|
|
||||||
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
|
|
||||||
)}
|
|
||||||
placeholder={input.placeholder}
|
placeholder={input.placeholder}
|
||||||
disabled={disableInput}
|
disabled={disabledExceptForOwner}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{input.type === EventTypeCustomInputType.TEXT && (
|
{input.type === EventTypeCustomInputType.TEXT && (
|
||||||
|
@ -652,9 +659,9 @@ const BookingPage = ({
|
||||||
required: input.required,
|
required: input.required,
|
||||||
})}
|
})}
|
||||||
id={"custom_" + input.id}
|
id={"custom_" + input.id}
|
||||||
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm"
|
className={inputClassName}
|
||||||
placeholder={input.placeholder}
|
placeholder={input.placeholder}
|
||||||
disabled={disableInput}
|
disabled={disabledExceptForOwner}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{input.type === EventTypeCustomInputType.NUMBER && (
|
{input.type === EventTypeCustomInputType.NUMBER && (
|
||||||
|
@ -664,8 +671,9 @@ const BookingPage = ({
|
||||||
required: input.required,
|
required: input.required,
|
||||||
})}
|
})}
|
||||||
id={"custom_" + input.id}
|
id={"custom_" + input.id}
|
||||||
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm"
|
className={inputClassName}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
|
disabled={disabledExceptForOwner}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{input.type === EventTypeCustomInputType.BOOL && (
|
{input.type === EventTypeCustomInputType.BOOL && (
|
||||||
|
@ -676,8 +684,9 @@ const BookingPage = ({
|
||||||
required: input.required,
|
required: input.required,
|
||||||
})}
|
})}
|
||||||
id={"custom_" + input.id}
|
id={"custom_" + input.id}
|
||||||
className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black ltr:mr-2 rtl:ml-2"
|
className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black disabled:bg-gray-200 ltr:mr-2 rtl:ml-2 disabled:dark:text-gray-500"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
|
disabled={disabledExceptForOwner}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={"custom_" + input.id}
|
htmlFor={"custom_" + input.id}
|
||||||
|
@ -764,12 +773,9 @@ const BookingPage = ({
|
||||||
id="notes"
|
id="notes"
|
||||||
name="notes"
|
name="notes"
|
||||||
rows={3}
|
rows={3}
|
||||||
className={classNames(
|
className={inputClassName}
|
||||||
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
|
|
||||||
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
|
|
||||||
)}
|
|
||||||
placeholder={t("share_additional_notes")}
|
placeholder={t("share_additional_notes")}
|
||||||
disabled={disableInput}
|
disabled={disabledExceptForOwner}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start space-x-2 rtl:space-x-reverse">
|
<div className="flex items-start space-x-2 rtl:space-x-reverse">
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { ChevronDownIcon, DotsHorizontalIcon } from "@heroicons/react/solid";
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
|
|
||||||
import Button from "@calcom/ui/Button";
|
import Button from "@calcom/ui/Button";
|
||||||
import Dropdown, { DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "@calcom/ui/Dropdown";
|
import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@calcom/ui/Dropdown";
|
||||||
|
|
||||||
import { SVGComponent } from "@lib/types/SVGComponent";
|
import { SVGComponent } from "@lib/types/SVGComponent";
|
||||||
|
|
||||||
|
@ -12,15 +12,27 @@ export type ActionType = {
|
||||||
label: string;
|
label: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
color?: "primary" | "secondary";
|
color?: "primary" | "secondary";
|
||||||
} & ({ href?: never; onClick: () => any } | { href?: string; onClick?: never }) & {
|
} & (
|
||||||
actions?: ActionType[];
|
| { href: string; onClick?: never; actions?: never }
|
||||||
};
|
| { href?: never; onClick: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void; actions?: never }
|
||||||
|
| { actions?: ActionType[]; href?: never; onClick?: never }
|
||||||
|
);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
actions: ActionType[];
|
actions: ActionType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownActions = ({ actions, actionTrigger }: { actions: ActionType[]; actionTrigger?: any }) => {
|
const defaultAction = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const DropdownActions = ({
|
||||||
|
actions,
|
||||||
|
actionTrigger,
|
||||||
|
}: {
|
||||||
|
actions: ActionType[];
|
||||||
|
actionTrigger?: React.ReactNode;
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
{!actionTrigger ? (
|
{!actionTrigger ? (
|
||||||
|
@ -40,7 +52,7 @@ const DropdownActions = ({ actions, actionTrigger }: { actions: ActionType[]; ac
|
||||||
className="w-full rounded-none font-normal"
|
className="w-full rounded-none font-normal"
|
||||||
href={action.href}
|
href={action.href}
|
||||||
StartIcon={action.icon}
|
StartIcon={action.icon}
|
||||||
onClick={action.onClick}
|
onClick={action.onClick || defaultAction}
|
||||||
data-testid={action.id}>
|
data-testid={action.id}>
|
||||||
{action.label}
|
{action.label}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -67,7 +79,7 @@ const TableActions: FC<Props> = ({ actions }) => {
|
||||||
key={action.id}
|
key={action.id}
|
||||||
data-testid={action.id}
|
data-testid={action.id}
|
||||||
href={action.href}
|
href={action.href}
|
||||||
onClick={action.onClick}
|
onClick={action.onClick || defaultAction}
|
||||||
StartIcon={action.icon}
|
StartIcon={action.icon}
|
||||||
{...(action?.actions ? { EndIcon: ChevronDownIcon } : null)}
|
{...(action?.actions ? { EndIcon: ChevronDownIcon } : null)}
|
||||||
disabled={action.disabled}
|
disabled={action.disabled}
|
||||||
|
|
|
@ -4,8 +4,9 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
|
|
||||||
import EventManager from "@calcom/core/EventManager";
|
import EventManager from "@calcom/core/EventManager";
|
||||||
|
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import stripe from "@calcom/stripe/server";
|
import stripe from "@calcom/stripe/server";
|
||||||
import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
|
@ -42,16 +43,11 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||||
id: payment.bookingId,
|
id: payment.bookingId,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
title: true,
|
...bookingMinimalSelect,
|
||||||
description: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
attendees: true,
|
|
||||||
location: true,
|
location: true,
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
id: true,
|
|
||||||
uid: true,
|
uid: true,
|
||||||
paid: true,
|
paid: true,
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
|
@ -113,8 +109,9 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||||
description: booking.description || undefined,
|
description: booking.description || undefined,
|
||||||
startTime: booking.startTime.toISOString(),
|
startTime: booking.startTime.toISOString(),
|
||||||
endTime: booking.endTime.toISOString(),
|
endTime: booking.endTime.toISOString(),
|
||||||
|
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||||
organizer: {
|
organizer: {
|
||||||
email: user.email!,
|
email: user.email,
|
||||||
name: user.name!,
|
name: user.name!,
|
||||||
timeZone: user.timeZone,
|
timeZone: user.timeZone,
|
||||||
language: { translate: t, locale: user.locale ?? "en" },
|
language: { translate: t, locale: user.locale ?? "en" },
|
||||||
|
|
|
@ -50,6 +50,7 @@ ${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ ${this.getAdditionalNotes()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -49,6 +49,7 @@ ${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
}
|
}
|
||||||
|
@ -98,6 +99,7 @@ ${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -51,6 +51,7 @@ ${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.getRejectionReason()}
|
${this.getRejectionReason()}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
}
|
}
|
||||||
|
@ -102,6 +103,7 @@ ${this.getRejectionReason()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.getRejectionReason()}
|
${this.getRejectionReason()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -62,6 +62,7 @@ ${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +120,7 @@ ${this.getAdditionalNotes()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -105,6 +105,7 @@ ${this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||||
${this.getWhat()}
|
${this.getWhat()}
|
||||||
${this.getWhen()}
|
${this.getWhen()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||||
${getCancelLink(this.calEvent)}
|
${getCancelLink(this.calEvent)}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
|
@ -154,6 +155,7 @@ ${getCancelLink(this.calEvent)}
|
||||||
${this.getWhen()}
|
${this.getWhen()}
|
||||||
${this.getWho()}
|
${this.getWho()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -57,6 +57,7 @@ export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.attendee.language.translate("need_to_reschedule_or_cancel")}
|
${this.attendee.language.translate("need_to_reschedule_or_cancel")}
|
||||||
${getCancelLink(this.calEvent)}
|
${getCancelLink(this.calEvent)}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
|
@ -69,6 +70,7 @@ ${this.getWhat()}
|
||||||
${this.getWhen()}
|
${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +118,7 @@ ${this.getAdditionalNotes()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -169,6 +169,7 @@ ${getRichDescription(this.calEvent)}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -316,6 +317,28 @@ ${getRichDescription(this.calEvent)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getCustomInputs(): string {
|
||||||
|
const { customInputs } = this.calEvent;
|
||||||
|
if (!customInputs) return "";
|
||||||
|
const customInputsString = Object.keys(customInputs)
|
||||||
|
.map((key) => {
|
||||||
|
if (customInputs[key] !== "") {
|
||||||
|
return `
|
||||||
|
<p style="height: 6px"></p>
|
||||||
|
<div style="line-height: 6px;">
|
||||||
|
<p style="color: #494949;">${key}</p>
|
||||||
|
<p style="color: #494949; font-weight: 400;">
|
||||||
|
${customInputs[key]}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return customInputsString;
|
||||||
|
}
|
||||||
|
|
||||||
protected getRejectionReason(): string {
|
protected getRejectionReason(): string {
|
||||||
if (!this.calEvent.rejectionReason) return "";
|
if (!this.calEvent.rejectionReason) return "";
|
||||||
return `
|
return `
|
||||||
|
|
|
@ -58,6 +58,7 @@ ${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
}
|
}
|
||||||
|
@ -106,6 +107,7 @@ ${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -60,6 +60,7 @@ ${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +140,7 @@ ${this.getAdditionalNotes()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -59,6 +59,7 @@ ${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
|
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
|
||||||
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
|
@ -110,6 +111,7 @@ ${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
||||||
${this.getWho()}
|
${this.getWho()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -59,6 +59,7 @@ ${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
|
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
|
||||||
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
|
@ -108,6 +109,7 @@ ${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -115,6 +115,7 @@ ${this.getWhat()}
|
||||||
${this.getWhen()}
|
${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||||
${getCancelLink(this.calEvent)}
|
${getCancelLink(this.calEvent)}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
|
@ -166,6 +167,7 @@ ${getCancelLink(this.calEvent)}
|
||||||
${this.getWhen()}
|
${this.getWhen()}
|
||||||
${this.getWho()}
|
${this.getWho()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -64,6 +64,7 @@ ${this.getWhen()}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||||
${getCancelLink(this.calEvent)}
|
${getCancelLink(this.calEvent)}
|
||||||
`.replace(/(<([^>]+)>)/gi, "");
|
`.replace(/(<([^>]+)>)/gi, "");
|
||||||
|
@ -113,6 +114,7 @@ ${getCancelLink(this.calEvent)}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -162,6 +162,7 @@ ${getRichDescription(this.calEvent)}
|
||||||
${this.getLocation()}
|
${this.getLocation()}
|
||||||
${this.getDescription()}
|
${this.getDescription()}
|
||||||
${this.getAdditionalNotes()}
|
${this.getAdditionalNotes()}
|
||||||
|
${this.getCustomInputs()}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -303,6 +304,28 @@ ${getRichDescription(this.calEvent)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getCustomInputs(): string {
|
||||||
|
const { customInputs } = this.calEvent;
|
||||||
|
if (!customInputs) return "";
|
||||||
|
const customInputsString = Object.keys(customInputs)
|
||||||
|
.map((key) => {
|
||||||
|
if (customInputs[key] !== "") {
|
||||||
|
return `
|
||||||
|
<p style="height: 6px"></p>
|
||||||
|
<div style="line-height: 6px;">
|
||||||
|
<p style="color: #494949;">${key}</p>
|
||||||
|
<p style="color: #494949; font-weight: 400;">
|
||||||
|
${customInputs[key]}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return customInputsString;
|
||||||
|
}
|
||||||
|
|
||||||
protected getDescription(): string {
|
protected getDescription(): string {
|
||||||
if (!this.calEvent.description) return "";
|
if (!this.calEvent.description) return "";
|
||||||
return `
|
return `
|
||||||
|
|
|
@ -8,6 +8,7 @@ async function getBooking(prisma: PrismaClient, uid: string) {
|
||||||
select: {
|
select: {
|
||||||
startTime: true,
|
startTime: true,
|
||||||
description: true,
|
description: true,
|
||||||
|
customInputs: true,
|
||||||
attendees: {
|
attendees: {
|
||||||
select: {
|
select: {
|
||||||
email: true,
|
email: true,
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { EventType, PeriodType } from "@prisma/client";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
function isOutOfBounds(
|
||||||
|
time: dayjs.ConfigType,
|
||||||
|
{
|
||||||
|
periodType,
|
||||||
|
periodDays,
|
||||||
|
periodCountCalendarDays,
|
||||||
|
periodStartDate,
|
||||||
|
periodEndDate,
|
||||||
|
}: Pick<
|
||||||
|
EventType,
|
||||||
|
"periodType" | "periodDays" | "periodCountCalendarDays" | "periodStartDate" | "periodEndDate"
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
const date = dayjs(time);
|
||||||
|
periodDays = periodDays || 0;
|
||||||
|
|
||||||
|
switch (periodType) {
|
||||||
|
case PeriodType.ROLLING: {
|
||||||
|
const periodRollingEndDay = periodCountCalendarDays
|
||||||
|
? dayjs().utcOffset(date.utcOffset()).add(periodDays, "days").endOf("day")
|
||||||
|
: dayjs().utcOffset(date.utcOffset()).businessDaysAdd(periodDays).endOf("day");
|
||||||
|
return date.endOf("day").isAfter(periodRollingEndDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
case PeriodType.RANGE: {
|
||||||
|
const periodRangeStartDay = dayjs(periodStartDate).utcOffset(date.utcOffset()).endOf("day");
|
||||||
|
const periodRangeEndDay = dayjs(periodEndDate).utcOffset(date.utcOffset()).endOf("day");
|
||||||
|
return date.endOf("day").isBefore(periodRangeStartDay) || date.endOf("day").isAfter(periodRangeEndDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
case PeriodType.UNLIMITED:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default isOutOfBounds;
|
|
@ -24,7 +24,7 @@ export type BookingCreateBody = {
|
||||||
timeZone: string;
|
timeZone: string;
|
||||||
user?: string | string[];
|
user?: string | string[];
|
||||||
language: string;
|
language: string;
|
||||||
customInputs: { label: string; value: string }[];
|
customInputs: { label: string; value: string | boolean }[];
|
||||||
metadata: {
|
metadata: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import { Prisma, User, Booking, SchedulingType, BookingStatus } from "@prisma/client";
|
import { Booking, BookingStatus, Prisma, SchedulingType, User } from "@prisma/client";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import EventManager from "@calcom/core/EventManager";
|
import EventManager from "@calcom/core/EventManager";
|
||||||
|
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import type { AdditionInformation, RecurringEvent } from "@calcom/types/Calendar";
|
import type { AdditionInformation, CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
|
||||||
import { refund } from "@ee/lib/stripe/server";
|
import { refund } from "@ee/lib/stripe/server";
|
||||||
|
|
||||||
import { asStringOrNull } from "@lib/asStringOrNull";
|
import { asStringOrNull } from "@lib/asStringOrNull";
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
import { sendDeclinedEmails } from "@lib/emails/email-manager";
|
import { sendDeclinedEmails, sendScheduledEmails } from "@lib/emails/email-manager";
|
||||||
import { sendScheduledEmails } from "@lib/emails/email-manager";
|
|
||||||
import prisma from "@lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
import { BookingConfirmBody } from "@lib/types/booking";
|
import { BookingConfirmBody } from "@lib/types/booking";
|
||||||
|
|
||||||
|
@ -89,6 +88,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
select: {
|
select: {
|
||||||
title: true,
|
title: true,
|
||||||
description: true,
|
description: true,
|
||||||
|
customInputs: true,
|
||||||
startTime: true,
|
startTime: true,
|
||||||
endTime: true,
|
endTime: true,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
|
@ -156,6 +156,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
type: booking.title,
|
type: booking.title,
|
||||||
title: booking.title,
|
title: booking.title,
|
||||||
description: booking.description,
|
description: booking.description,
|
||||||
|
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||||
startTime: booking.startTime.toISOString(),
|
startTime: booking.startTime.toISOString(),
|
||||||
endTime: booking.endTime.toISOString(),
|
endTime: booking.endTime.toISOString(),
|
||||||
organizer: {
|
organizer: {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import short from "short-uuid";
|
||||||
import { v5 as uuidv5 } from "uuid";
|
import { v5 as uuidv5 } from "uuid";
|
||||||
|
|
||||||
import EventManager from "@calcom/core/EventManager";
|
import EventManager from "@calcom/core/EventManager";
|
||||||
|
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||||
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
|
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
|
||||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
|
@ -28,6 +29,7 @@ import {
|
||||||
import { ensureArray } from "@lib/ensureArray";
|
import { ensureArray } from "@lib/ensureArray";
|
||||||
import { getEventName } from "@lib/event";
|
import { getEventName } from "@lib/event";
|
||||||
import getBusyTimes from "@lib/getBusyTimes";
|
import getBusyTimes from "@lib/getBusyTimes";
|
||||||
|
import isOutOfBounds from "@lib/isOutOfBounds";
|
||||||
import prisma from "@lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
import { BookingCreateBody } from "@lib/types/booking";
|
import { BookingCreateBody } from "@lib/types/booking";
|
||||||
import sendPayload from "@lib/webhooks/sendPayload";
|
import sendPayload from "@lib/webhooks/sendPayload";
|
||||||
|
@ -104,32 +106,6 @@ function isAvailable(busyTimes: BufferedBusyTimes, time: dayjs.ConfigType, lengt
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOutOfBounds(
|
|
||||||
time: dayjs.ConfigType,
|
|
||||||
{ periodType, periodDays, periodCountCalendarDays, periodStartDate, periodEndDate, timeZone }: any // FIXME types
|
|
||||||
): boolean {
|
|
||||||
const date = dayjs(time);
|
|
||||||
|
|
||||||
switch (periodType) {
|
|
||||||
case "rolling": {
|
|
||||||
const periodRollingEndDay = periodCountCalendarDays
|
|
||||||
? dayjs().tz(timeZone).add(periodDays, "days").endOf("day")
|
|
||||||
: dayjs().tz(timeZone).businessDaysAdd(periodDays).endOf("day");
|
|
||||||
return date.endOf("day").isAfter(periodRollingEndDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "range": {
|
|
||||||
const periodRangeStartDay = dayjs(periodStartDate).tz(timeZone).endOf("day");
|
|
||||||
const periodRangeEndDay = dayjs(periodEndDate).tz(timeZone).endOf("day");
|
|
||||||
return date.endOf("day").isBefore(periodRangeStartDay) || date.endOf("day").isAfter(periodRangeEndDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "unlimited":
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const userSelect = Prisma.validator<Prisma.UserArgs>()({
|
const userSelect = Prisma.validator<Prisma.UserArgs>()({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
@ -348,18 +324,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
t: tOrganizer,
|
t: tOrganizer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const additionalNotes =
|
const additionalNotes = reqBody.notes;
|
||||||
reqBody.notes +
|
|
||||||
reqBody.customInputs.reduce(
|
const customInputs = {} as NonNullable<CalendarEvent["customInputs"]>;
|
||||||
(str, input) => str + "<br /><br />" + input.label + ":<br />" + input.value,
|
|
||||||
""
|
if (reqBody.customInputs.length > 0) {
|
||||||
);
|
reqBody.customInputs.forEach(({ label, value }) => {
|
||||||
|
customInputs[label] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const evt: CalendarEvent = {
|
const evt: CalendarEvent = {
|
||||||
type: eventType.title,
|
type: eventType.title,
|
||||||
title: getEventName(eventNameObject), //this needs to be either forced in english, or fetched for each attendee and organizer separately
|
title: getEventName(eventNameObject), //this needs to be either forced in english, or fetched for each attendee and organizer separately
|
||||||
description: eventType.description,
|
description: eventType.description,
|
||||||
additionalNotes,
|
additionalNotes,
|
||||||
|
customInputs,
|
||||||
startTime: reqBody.start,
|
startTime: reqBody.start,
|
||||||
endTime: reqBody.end,
|
endTime: reqBody.end,
|
||||||
organizer: {
|
organizer: {
|
||||||
|
@ -456,6 +436,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
startTime: dayjs(evt.startTime).toDate(),
|
startTime: dayjs(evt.startTime).toDate(),
|
||||||
endTime: dayjs(evt.endTime).toDate(),
|
endTime: dayjs(evt.endTime).toDate(),
|
||||||
description: evt.additionalNotes,
|
description: evt.additionalNotes,
|
||||||
|
customInputs: isPrismaObjOrUndefined(evt.customInputs),
|
||||||
confirmed: (!eventType.requiresConfirmation && !eventType.price) || !!rescheduleUid,
|
confirmed: (!eventType.requiresConfirmation && !eventType.price) || !!rescheduleUid,
|
||||||
location: evt.location,
|
location: evt.location,
|
||||||
eventType: eventTypeRel,
|
eventType: eventTypeRel,
|
||||||
|
@ -613,7 +594,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
periodEndDate: eventType.periodEndDate,
|
periodEndDate: eventType.periodEndDate,
|
||||||
periodStartDate: eventType.periodStartDate,
|
periodStartDate: eventType.periodStartDate,
|
||||||
periodCountCalendarDays: eventType.periodCountCalendarDays,
|
periodCountCalendarDays: eventType.periodCountCalendarDays,
|
||||||
timeZone: currentUser.timeZone,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
log.debug({
|
log.debug({
|
||||||
|
@ -731,6 +711,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
...evt,
|
...evt,
|
||||||
additionInformation: metadata,
|
additionInformation: metadata,
|
||||||
additionalNotes,
|
additionalNotes,
|
||||||
|
customInputs,
|
||||||
},
|
},
|
||||||
reqBody.recurringEventId ? (eventType.recurringEvent as RecurringEvent) : {}
|
reqBody.recurringEventId ? (eventType.recurringEvent as RecurringEvent) : {}
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,13 +6,14 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
|
import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
|
||||||
import { getCalendar } from "@calcom/core/CalendarManager";
|
import { getCalendar } from "@calcom/core/CalendarManager";
|
||||||
import { deleteMeeting } from "@calcom/core/videoClient";
|
import { deleteMeeting } from "@calcom/core/videoClient";
|
||||||
|
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
import { refund } from "@ee/lib/stripe/server";
|
import { refund } from "@ee/lib/stripe/server";
|
||||||
|
|
||||||
import { asStringOrNull } from "@lib/asStringOrNull";
|
import { asStringOrNull } from "@lib/asStringOrNull";
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
import { sendCancelledEmails } from "@lib/emails/email-manager";
|
import { sendCancelledEmails } from "@lib/emails/email-manager";
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
import sendPayload from "@lib/webhooks/sendPayload";
|
import sendPayload from "@lib/webhooks/sendPayload";
|
||||||
import getWebhooks from "@lib/webhooks/subscriptions";
|
import getWebhooks from "@lib/webhooks/subscriptions";
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
uid,
|
uid,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
...bookingMinimalSelect,
|
||||||
userId: true,
|
userId: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
|
@ -45,7 +46,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
attendees: true,
|
|
||||||
location: true,
|
location: true,
|
||||||
references: {
|
references: {
|
||||||
select: {
|
select: {
|
||||||
|
@ -56,15 +56,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
},
|
},
|
||||||
payment: true,
|
payment: true,
|
||||||
paid: true,
|
paid: true,
|
||||||
title: true,
|
|
||||||
eventType: {
|
eventType: {
|
||||||
select: {
|
select: {
|
||||||
title: true,
|
title: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
uid: true,
|
uid: true,
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
|
@ -115,6 +111,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
title: bookingToDelete?.title,
|
title: bookingToDelete?.title,
|
||||||
type: (bookingToDelete?.eventType?.title as string) || bookingToDelete?.title,
|
type: (bookingToDelete?.eventType?.title as string) || bookingToDelete?.title,
|
||||||
description: bookingToDelete?.description || "",
|
description: bookingToDelete?.description || "",
|
||||||
|
customInputs: isPrismaObjOrUndefined(bookingToDelete.customInputs),
|
||||||
startTime: bookingToDelete?.startTime ? dayjs(bookingToDelete.startTime).format() : "",
|
startTime: bookingToDelete?.startTime ? dayjs(bookingToDelete.startTime).format() : "",
|
||||||
endTime: bookingToDelete?.endTime ? dayjs(bookingToDelete.endTime).format() : "",
|
endTime: bookingToDelete?.endTime ? dayjs(bookingToDelete.endTime).format() : "",
|
||||||
organizer: {
|
organizer: {
|
||||||
|
@ -183,6 +180,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
type: bookingToDelete?.eventType?.title as string,
|
type: bookingToDelete?.eventType?.title as string,
|
||||||
title: bookingToDelete.title,
|
title: bookingToDelete.title,
|
||||||
description: bookingToDelete.description ?? "",
|
description: bookingToDelete.description ?? "",
|
||||||
|
customInputs: isPrismaObjOrUndefined(bookingToDelete.customInputs),
|
||||||
startTime: bookingToDelete.startTime.toISOString(),
|
startTime: bookingToDelete.startTime.toISOString(),
|
||||||
endTime: bookingToDelete.endTime.toISOString(),
|
endTime: bookingToDelete.endTime.toISOString(),
|
||||||
organizer: {
|
organizer: {
|
||||||
|
|
|
@ -2,10 +2,11 @@ import { ReminderType } from "@prisma/client";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { sendOrganizerRequestReminderEmail } from "@lib/emails/email-manager";
|
import { sendOrganizerRequestReminderEmail } from "@lib/emails/email-manager";
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
|
|
||||||
import { getTranslation } from "@server/lib/i18n";
|
import { getTranslation } from "@server/lib/i18n";
|
||||||
|
|
||||||
|
@ -32,12 +33,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
title: true,
|
...bookingMinimalSelect,
|
||||||
description: true,
|
|
||||||
location: true,
|
location: true,
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
attendees: true,
|
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
email: true,
|
email: true,
|
||||||
|
@ -48,7 +45,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
id: true,
|
|
||||||
uid: true,
|
uid: true,
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
},
|
},
|
||||||
|
@ -94,6 +90,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
type: booking.title,
|
type: booking.title,
|
||||||
title: booking.title,
|
title: booking.title,
|
||||||
description: booking.description || undefined,
|
description: booking.description || undefined,
|
||||||
|
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||||
location: booking.location ?? "",
|
location: booking.location ?? "",
|
||||||
startTime: booking.startTime.toISOString(),
|
startTime: booking.startTime.toISOString(),
|
||||||
endTime: booking.endTime.toISOString(),
|
endTime: booking.endTime.toISOString(),
|
||||||
|
|
|
@ -4,13 +4,13 @@ import { GetServerSidePropsContext } from "next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import { Button } from "@calcom/ui/Button";
|
import { Button } from "@calcom/ui/Button";
|
||||||
import { TextField } from "@calcom/ui/form/fields";
|
import { TextField } from "@calcom/ui/form/fields";
|
||||||
|
|
||||||
import { asStringOrUndefined } from "@lib/asStringOrNull";
|
import { asStringOrUndefined } from "@lib/asStringOrNull";
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@lib/hooks/useLocale";
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
|
||||||
import { detectBrowserTimeFormat } from "@lib/timeFormat";
|
import { detectBrowserTimeFormat } from "@lib/timeFormat";
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
@ -168,12 +168,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
uid: asStringOrUndefined(context.query.uid),
|
uid: asStringOrUndefined(context.query.uid),
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
...bookingMinimalSelect,
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
attendees: true,
|
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { GetServerSidePropsContext } from "next";
|
import { GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
|
||||||
import { asStringOrUndefined } from "@lib/asStringOrNull";
|
import { asStringOrUndefined } from "@lib/asStringOrNull";
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
|
|
||||||
export default function Type() {
|
export default function Type() {
|
||||||
// Just redirect to the schedule page to reschedule it.
|
// Just redirect to the schedule page to reschedule it.
|
||||||
|
@ -16,7 +16,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
uid: asStringOrUndefined(context.query.uid),
|
uid: asStringOrUndefined(context.query.uid),
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
...bookingMinimalSelect,
|
||||||
eventType: {
|
eventType: {
|
||||||
select: {
|
select: {
|
||||||
users: {
|
users: {
|
||||||
|
@ -35,11 +35,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
dynamicEventSlugRef: true,
|
dynamicEventSlugRef: true,
|
||||||
dynamicGroupSlugRef: true,
|
dynamicGroupSlugRef: true,
|
||||||
user: true,
|
user: true,
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
attendees: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const dynamicEventSlugRef = booking?.dynamicEventSlugRef || "";
|
const dynamicEventSlugRef = booking?.dynamicEventSlugRef || "";
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { isBrowserLocale24h } from "@lib/timeFormat";
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
import CustomBranding from "@components/CustomBranding";
|
import CustomBranding from "@components/CustomBranding";
|
||||||
|
import CancelBooking from "@components/booking/CancelBooking";
|
||||||
import { HeadSeo } from "@components/seo/head-seo";
|
import { HeadSeo } from "@components/seo/head-seo";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
@ -152,13 +153,13 @@ export default function Success(props: SuccessProps) {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
const [date, setDate] = useState(dayjs.utc(asStringOrThrow(router.query.date)));
|
const [date, setDate] = useState(dayjs.utc(asStringOrThrow(router.query.date)));
|
||||||
const { isReady, Theme } = useTheme(props.profile.theme);
|
|
||||||
const { eventType, bookingInfo } = props;
|
const { eventType, bookingInfo } = props;
|
||||||
|
|
||||||
const isBackgroundTransparent = useIsBackgroundTransparent();
|
const isBackgroundTransparent = useIsBackgroundTransparent();
|
||||||
const isEmbed = useIsEmbed();
|
const isEmbed = useIsEmbed();
|
||||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||||
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
||||||
|
const [isCancellationMode, setIsCancellationMode] = useState(false);
|
||||||
|
|
||||||
const attendeeName = typeof name === "string" ? name : "Nameless";
|
const attendeeName = typeof name === "string" ? name : "Nameless";
|
||||||
|
|
||||||
|
@ -247,15 +248,26 @@ export default function Success(props: SuccessProps) {
|
||||||
return t("emailed_you_and_attendees" + titleSuffix);
|
return t("emailed_you_and_attendees" + titleSuffix);
|
||||||
}
|
}
|
||||||
const userIsOwner = !!(session?.user?.id && eventType.users.find((user) => (user.id = session.user.id)));
|
const userIsOwner = !!(session?.user?.id && eventType.users.find((user) => (user.id = session.user.id)));
|
||||||
|
const { isReady, Theme } = useTheme(userIsOwner ? "light" : props.profile.theme);
|
||||||
const title = t(
|
const title = t(
|
||||||
`booking_${needsConfirmation ? "submitted" : "confirmed"}${props.recurringBookings ? "_recurring" : ""}`
|
`booking_${needsConfirmation ? "submitted" : "confirmed"}${props.recurringBookings ? "_recurring" : ""}`
|
||||||
);
|
);
|
||||||
|
const customInputs = bookingInfo?.customInputs;
|
||||||
return (
|
return (
|
||||||
(isReady && (
|
(isReady && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={isEmbed ? "" : "h-screen bg-neutral-100 dark:bg-neutral-900"}
|
className={isEmbed ? "" : "h-screen bg-neutral-100 dark:bg-neutral-900"}
|
||||||
data-testid="success-page">
|
data-testid="success-page">
|
||||||
|
{userIsOwner && !isEmbed && (
|
||||||
|
<div className="-mb-7 ml-9 mt-7">
|
||||||
|
<Link href="/bookings">
|
||||||
|
<a className="flex items-center text-black dark:text-white">
|
||||||
|
<ArrowLeftIcon className="mr-1 h-4 w-4" /> {t("back_to_bookings")}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Theme />
|
<Theme />
|
||||||
<HeadSeo title={title} description={title} />
|
<HeadSeo title={title} description={title} />
|
||||||
<CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} />
|
<CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} />
|
||||||
|
@ -357,21 +369,66 @@ export default function Success(props: SuccessProps) {
|
||||||
)}
|
)}
|
||||||
{bookingInfo?.description && (
|
{bookingInfo?.description && (
|
||||||
<>
|
<>
|
||||||
<div className="mt-6 font-medium">{t("additional_notes")}</div>
|
<div className="mt-9 font-medium">{t("additional_notes")}</div>
|
||||||
<div className="col-span-2 mt-6 mb-6">
|
<div className="col-span-2 mb-2 mt-9">
|
||||||
<p>{bookingInfo.description}</p>
|
<p>{bookingInfo.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{customInputs &&
|
||||||
|
Object.keys(customInputs).map((key) => {
|
||||||
|
const customInput = customInputs[key as keyof typeof customInputs];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{customInput !== "" && (
|
||||||
|
<>
|
||||||
|
<div className="mt-2 pr-3 font-medium">{key}</div>
|
||||||
|
<div className="col-span-2 mt-2 mb-2">
|
||||||
|
{typeof customInput === "boolean" ? (
|
||||||
|
<p>{customInput ? "true" : "false"}</p>
|
||||||
|
) : (
|
||||||
|
<p>{customInput}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!needsConfirmation && (
|
{!needsConfirmation &&
|
||||||
<div className="border-bookinglightest mt-5 flex border-b pt-2 pb-4 text-center dark:border-gray-900 sm:mt-0 sm:pt-4">
|
(!isCancellationMode ? (
|
||||||
|
<div className="border-bookinglightest text-bookingdark mt-2 grid grid-cols-3 border-b py-4 text-left dark:border-gray-900">
|
||||||
|
<span className="flex self-center font-medium text-gray-700 ltr:mr-2 rtl:ml-2 dark:text-gray-50">
|
||||||
|
{t("need_to_make_a_change")}
|
||||||
|
</span>
|
||||||
|
<div className="ml-7 flex items-center justify-center self-center ltr:mr-2 rtl:ml-2 dark:text-gray-50">
|
||||||
|
<button className="underline" onClick={() => setIsCancellationMode(true)}>
|
||||||
|
{t("cancel")}
|
||||||
|
</button>
|
||||||
|
<div className="mx-2">{t("or_lowercase")}</div>
|
||||||
|
<div className="underline">
|
||||||
|
<Link href={"/reschedule/" + bookingInfo?.uid}>{t("Reschedule")}</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<CancelBooking
|
||||||
|
booking={{ uid: bookingInfo?.uid, title: bookingInfo?.title }}
|
||||||
|
profile={{ name: props.profile.name, slug: props.profile.slug }}
|
||||||
|
team={eventType?.team?.name}
|
||||||
|
setIsCancellationMode={setIsCancellationMode}
|
||||||
|
theme={userIsOwner ? "light" : props.profile.theme}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{userIsOwner && !needsConfirmation && !isCancellationMode && (
|
||||||
|
<div className="border-bookinglightest mt-9 flex border-b pt-2 pb-4 text-center dark:border-gray-900 sm:mt-0 sm:pt-4">
|
||||||
<span className="flex self-center font-medium text-gray-700 ltr:mr-2 rtl:ml-2 dark:text-gray-50">
|
<span className="flex self-center font-medium text-gray-700 ltr:mr-2 rtl:ml-2 dark:text-gray-50">
|
||||||
{t("add_to_calendar")}
|
{t("add_to_calendar")}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex flex-grow justify-center text-center">
|
<div className="-ml-16 flex flex-grow justify-center text-center">
|
||||||
<Link
|
<Link
|
||||||
href={
|
href={
|
||||||
`https://calendar.google.com/calendar/r/eventedit?dates=${date
|
`https://calendar.google.com/calendar/r/eventedit?dates=${date
|
||||||
|
@ -497,15 +554,6 @@ export default function Success(props: SuccessProps) {
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{userIsOwner && !isEmbed && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<Link href="/bookings">
|
|
||||||
<a className="flex items-center text-black dark:text-white">
|
|
||||||
<ArrowLeftIcon className="mr-1 h-4 w-4" /> {t("back_to_bookings")}
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -618,6 +666,7 @@ const getEventTypesFromDB = async (typeId: number) => {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
username: true,
|
||||||
hideBranding: true,
|
hideBranding: true,
|
||||||
plan: true,
|
plan: true,
|
||||||
theme: true,
|
theme: true,
|
||||||
|
@ -629,6 +678,7 @@ const getEventTypesFromDB = async (typeId: number) => {
|
||||||
},
|
},
|
||||||
team: {
|
team: {
|
||||||
select: {
|
select: {
|
||||||
|
slug: true,
|
||||||
name: true,
|
name: true,
|
||||||
hideBranding: true,
|
hideBranding: true,
|
||||||
},
|
},
|
||||||
|
@ -683,6 +733,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
username: true,
|
||||||
hideBranding: true,
|
hideBranding: true,
|
||||||
plan: true,
|
plan: true,
|
||||||
theme: true,
|
theme: true,
|
||||||
|
@ -714,6 +765,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
theme: (!eventType.team?.name && eventType.users[0]?.theme) || null,
|
theme: (!eventType.team?.name && eventType.users[0]?.theme) || null,
|
||||||
brandColor: eventType.team ? null : eventType.users[0].brandColor || null,
|
brandColor: eventType.team ? null : eventType.users[0].brandColor || null,
|
||||||
darkBrandColor: eventType.team ? null : eventType.users[0].darkBrandColor || null,
|
darkBrandColor: eventType.team ? null : eventType.users[0].darkBrandColor || null,
|
||||||
|
slug: eventType.team?.slug || eventType.users[0]?.username || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const bookingInfo = await prisma.booking.findUnique({
|
const bookingInfo = await prisma.booking.findUnique({
|
||||||
|
@ -721,7 +773,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
id: bookingId,
|
id: bookingId,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
|
title: true,
|
||||||
|
uid: true,
|
||||||
description: true,
|
description: true,
|
||||||
|
customInputs: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
name: true,
|
name: true,
|
||||||
|
|
|
@ -6,9 +6,9 @@ import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import prisma from "@lib/prisma";
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||||
|
|
||||||
export type JoinCallPageProps = inferSSRProps<typeof getServerSideProps>;
|
export type JoinCallPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||||
|
|
||||||
|
@ -150,19 +150,14 @@ export async function getServerSideProps(context: NextPageContext) {
|
||||||
uid: context.query.uid as string,
|
uid: context.query.uid as string,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
|
...bookingMinimalSelect,
|
||||||
uid: true,
|
uid: true,
|
||||||
id: true,
|
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
credentials: true,
|
credentials: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
attendees: true,
|
|
||||||
dailyRef: {
|
dailyRef: {
|
||||||
select: {
|
select: {
|
||||||
dailyurl: true,
|
dailyurl: true,
|
||||||
|
|
|
@ -6,9 +6,9 @@ import { getSession } from "next-auth/react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import Button from "@calcom/ui/Button";
|
import Button from "@calcom/ui/Button";
|
||||||
|
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
import { detectBrowserTimeFormat } from "@lib/timeFormat";
|
import { detectBrowserTimeFormat } from "@lib/timeFormat";
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
@ -84,18 +84,13 @@ export async function getServerSideProps(context: NextPageContext) {
|
||||||
uid: context.query.uid as string,
|
uid: context.query.uid as string,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
|
...bookingMinimalSelect,
|
||||||
uid: true,
|
uid: true,
|
||||||
id: true,
|
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
credentials: true,
|
credentials: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
attendees: true,
|
|
||||||
dailyRef: {
|
dailyRef: {
|
||||||
select: {
|
select: {
|
||||||
dailyurl: true,
|
dailyurl: true,
|
||||||
|
|
|
@ -6,11 +6,11 @@ import { getSession } from "next-auth/react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||||
import Button from "@calcom/ui/Button";
|
import Button from "@calcom/ui/Button";
|
||||||
|
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
import { detectBrowserTimeFormat } from "@lib/timeFormat";
|
import { detectBrowserTimeFormat } from "@lib/timeFormat";
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
|
||||||
|
|
||||||
import { HeadSeo } from "@components/seo/head-seo";
|
import { HeadSeo } from "@components/seo/head-seo";
|
||||||
|
|
||||||
|
@ -90,18 +90,13 @@ export async function getServerSideProps(context: NextPageContext) {
|
||||||
uid: context.query.uid as string,
|
uid: context.query.uid as string,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
|
...bookingMinimalSelect,
|
||||||
uid: true,
|
uid: true,
|
||||||
id: true,
|
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
credentials: true,
|
credentials: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
attendees: true,
|
|
||||||
dailyRef: {
|
dailyRef: {
|
||||||
select: {
|
select: {
|
||||||
dailyurl: true,
|
dailyurl: true,
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"uid":"[redacted/dynamic]","metadata":{},"additionInformation":"[redacted/dynamic]"}}
|
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"uid":"[redacted/dynamic]","metadata":{},"additionInformation":"[redacted/dynamic]"}}
|
|
@ -476,7 +476,7 @@
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
"cancel_event": "Cancel this event",
|
"cancel_event": "Cancel event",
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"confirm_all": "Confirm all",
|
"confirm_all": "Confirm all",
|
||||||
|
@ -823,11 +823,14 @@
|
||||||
"generate_api_key": "Generate Api Key",
|
"generate_api_key": "Generate Api Key",
|
||||||
"your_unique_api_key": "Your unique API key",
|
"your_unique_api_key": "Your unique API key",
|
||||||
"copy_safe_api_key": "Copy this API key and save it somewhere safe. If you lose this key you have to generate a new one.",
|
"copy_safe_api_key": "Copy this API key and save it somewhere safe. If you lose this key you have to generate a new one.",
|
||||||
|
"zapier_setup_instructions": "<0>Log into your Zapier account and create a new Zap.</0><1>Select Cal.com as your Trigger app. Also choose a Trigger event.</1><2>Choose your account and then enter your Unique API Key.</2><3>Test your Trigger.</3><4>You're set!</4>",
|
||||||
"install_zapier_app": "Please first install the Zapier App in the app store.",
|
"install_zapier_app": "Please first install the Zapier App in the app store.",
|
||||||
"go_to_app_store": "Go to App Store",
|
"go_to_app_store": "Go to App Store",
|
||||||
"calendar_error": "Something went wrong, try reconnecting your calendar with all necessary permissions",
|
"calendar_error": "Something went wrong, try reconnecting your calendar with all necessary permissions",
|
||||||
"calendar_no_busy_slots": "There are no busy slots",
|
"calendar_no_busy_slots": "There are no busy slots",
|
||||||
"zapier_setup_instructions": "<0>Log into your Zapier account and create a new Zap.</0><1>Select Cal.com as your Trigger app. Also choose a Trigger event.</1><2>Choose your account and then enter your Unique API Key.</2><3>Test your Trigger.</3><4>You're set!</4>",
|
"booking_details": "Booking details",
|
||||||
|
"or_lowercase": "or",
|
||||||
|
"nevermind": "Nevermind",
|
||||||
"go_to": "Go to: ",
|
"go_to": "Go to: ",
|
||||||
"zapier_invite_link": "Zapier Invite Link"
|
"zapier_invite_link": "Zapier Invite Link"
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { z } from "zod";
|
||||||
import getApps from "@calcom/app-store/utils";
|
import getApps from "@calcom/app-store/utils";
|
||||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
||||||
import { checkPremiumUsername } from "@calcom/ee/lib/core/checkPremiumUsername";
|
import { checkPremiumUsername } from "@calcom/ee/lib/core/checkPremiumUsername";
|
||||||
|
import { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import { RecurringEvent } from "@calcom/types/Calendar";
|
import { RecurringEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
|
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
|
||||||
|
@ -388,18 +389,17 @@ const loggedInViewerRouter = createProtectedRouter()
|
||||||
AND: passedBookingsFilter,
|
AND: passedBookingsFilter,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
|
...bookingMinimalSelect,
|
||||||
uid: true,
|
uid: true,
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
attendees: true,
|
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
rejected: true,
|
rejected: true,
|
||||||
id: true,
|
|
||||||
startTime: true,
|
|
||||||
recurringEventId: true,
|
recurringEventId: true,
|
||||||
endTime: true,
|
location: true,
|
||||||
eventType: {
|
eventType: {
|
||||||
select: {
|
select: {
|
||||||
|
slug: true,
|
||||||
|
id: true,
|
||||||
|
eventName: true,
|
||||||
price: true,
|
price: true,
|
||||||
recurringEvent: true,
|
recurringEvent: true,
|
||||||
team: {
|
team: {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import findValidApiKey from "@calcom/ee/lib/api/findValidApiKey";
|
import findValidApiKey from "@calcom/ee/lib/api/findValidApiKey";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const apiKey = req.query.apiKey as string;
|
const apiKey = req.query.apiKey as string;
|
||||||
|
@ -24,10 +24,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
userId: validKey.userId,
|
userId: validKey.userId,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
description: true,
|
...bookingMinimalSelect,
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
title: true,
|
|
||||||
location: true,
|
location: true,
|
||||||
attendees: {
|
attendees: {
|
||||||
select: {
|
select: {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { v5 as uuidv5 } from "uuid";
|
||||||
|
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { BASE_URL } from "./constants";
|
import { WEBAPP_URL } from "./constants";
|
||||||
|
|
||||||
const translator = short();
|
const translator = short();
|
||||||
|
|
||||||
|
@ -55,6 +55,25 @@ ${calEvent.additionalNotes}
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCustomInputs = (calEvent: CalendarEvent) => {
|
||||||
|
if (!calEvent.customInputs) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const customInputsString = Object.keys(calEvent.customInputs)
|
||||||
|
.map((key) => {
|
||||||
|
if (!calEvent.customInputs) return "";
|
||||||
|
if (calEvent.customInputs[key] !== "") {
|
||||||
|
return `
|
||||||
|
${key}:
|
||||||
|
${calEvent.customInputs[key]}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return customInputsString;
|
||||||
|
};
|
||||||
|
|
||||||
export const getDescription = (calEvent: CalendarEvent) => {
|
export const getDescription = (calEvent: CalendarEvent) => {
|
||||||
if (!calEvent.description) {
|
if (!calEvent.description) {
|
||||||
return "";
|
return "";
|
||||||
|
@ -94,7 +113,7 @@ export const getUid = (calEvent: CalendarEvent): string => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCancelLink = (calEvent: CalendarEvent): string => {
|
export const getCancelLink = (calEvent: CalendarEvent): string => {
|
||||||
return BASE_URL + "/cancel/" + getUid(calEvent);
|
return WEBAPP_URL + "/cancel/" + getUid(calEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRichDescription = (calEvent: CalendarEvent, attendee?: Person) => {
|
export const getRichDescription = (calEvent: CalendarEvent, attendee?: Person) => {
|
||||||
|
@ -110,6 +129,7 @@ ${calEvent.organizer.language.translate("where")}:
|
||||||
${getLocation(calEvent)}
|
${getLocation(calEvent)}
|
||||||
${getDescription(calEvent)}
|
${getDescription(calEvent)}
|
||||||
${getAdditionalNotes(calEvent)}
|
${getAdditionalNotes(calEvent)}
|
||||||
|
${getCustomInputs(calEvent)}
|
||||||
`.trim();
|
`.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +141,7 @@ ${calEvent.organizer.language.translate("where")}:
|
||||||
${getLocation(calEvent)}
|
${getLocation(calEvent)}
|
||||||
${getDescription(calEvent)}
|
${getDescription(calEvent)}
|
||||||
${getAdditionalNotes(calEvent)}
|
${getAdditionalNotes(calEvent)}
|
||||||
|
${getCustomInputs(calEvent)}
|
||||||
${getManageLink(calEvent)}
|
${getManageLink(calEvent)}
|
||||||
`.trim();
|
`.trim();
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as isPrismaObj, isPrismaObjOrUndefined } from "./isPrismaObj";
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
function isPrismaObj(obj: unknown): obj is Prisma.JsonObject {
|
||||||
|
return typeof obj === "object" && !Array.isArray(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPrismaObjOrUndefined(obj: unknown) {
|
||||||
|
return isPrismaObj(obj) ? obj : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default isPrismaObj;
|
|
@ -3,6 +3,7 @@ import { PrismaClient } from "@prisma/client";
|
||||||
import { bookingReferenceMiddleware } from "./middleware";
|
import { bookingReferenceMiddleware } from "./middleware";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
var prisma: PrismaClient | undefined;
|
var prisma: PrismaClient | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,3 +20,5 @@ if (process.env.NODE_ENV !== "production") {
|
||||||
bookingReferenceMiddleware(prisma);
|
bookingReferenceMiddleware(prisma);
|
||||||
|
|
||||||
export default prisma;
|
export default prisma;
|
||||||
|
|
||||||
|
export * from "./selects";
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Booking" ADD COLUMN "customInputs" JSONB;
|
|
@ -263,6 +263,7 @@ model Booking {
|
||||||
eventTypeId Int?
|
eventTypeId Int?
|
||||||
title String
|
title String
|
||||||
description String?
|
description String?
|
||||||
|
customInputs Json?
|
||||||
startTime DateTime
|
startTime DateTime
|
||||||
endTime DateTime
|
endTime DateTime
|
||||||
attendees Attendee[]
|
attendees Attendee[]
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
export const bookingMinimalSelect = Prisma.validator<Prisma.BookingSelect>()({
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
description: true,
|
||||||
|
customInputs: true,
|
||||||
|
startTime: true,
|
||||||
|
endTime: true,
|
||||||
|
attendees: true,
|
||||||
|
});
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./booking";
|
|
@ -1,4 +1,4 @@
|
||||||
import type { DestinationCalendar, SelectedCalendar } from "@prisma/client";
|
import type { Prisma, DestinationCalendar, SelectedCalendar } from "@prisma/client";
|
||||||
import type { Dayjs } from "dayjs";
|
import type { Dayjs } from "dayjs";
|
||||||
import type { calendar_v3 } from "googleapis";
|
import type { calendar_v3 } from "googleapis";
|
||||||
import type { Time } from "ical.js";
|
import type { Time } from "ical.js";
|
||||||
|
@ -97,6 +97,7 @@ export interface CalendarEvent {
|
||||||
organizer: Person;
|
organizer: Person;
|
||||||
attendees: Person[];
|
attendees: Person[];
|
||||||
additionalNotes?: string | null;
|
additionalNotes?: string | null;
|
||||||
|
customInputs?: Prisma.JsonObject | null;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
team?: {
|
team?: {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user