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 { PaperAirplaneIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
BanIcon,
|
||||
CheckIcon,
|
||||
ClockIcon,
|
||||
PaperAirplaneIcon,
|
||||
PencilAltIcon,
|
||||
XIcon,
|
||||
} from "@heroicons/react/outline";
|
||||
import { RefreshIcon } from "@heroicons/react/solid";
|
||||
import { BookingStatus } from "@prisma/client";
|
||||
import dayjs from "dayjs";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
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 useMeQuery from "@lib/hooks/useMeQuery";
|
||||
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 TableActions, { ActionType } from "@components/ui/TableActions";
|
||||
|
@ -37,6 +44,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
const user = query.data;
|
||||
const { t, i18n } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
const router = useRouter();
|
||||
const [rejectionReason, setRejectionReason] = useState<string>("");
|
||||
const [rejectionDialogIsOpen, setRejectionDialogIsOpen] = useState(false);
|
||||
const mutation = useMutation(
|
||||
|
@ -81,7 +89,10 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
|
||||
? t("reject_all")
|
||||
: t("reject"),
|
||||
onClick: () => setRejectionDialogIsOpen(true),
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
setRejectionDialogIsOpen(true);
|
||||
},
|
||||
icon: BanIcon,
|
||||
disabled: mutation.isLoading,
|
||||
},
|
||||
|
@ -91,7 +102,10 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
|
||||
? t("confirm_all")
|
||||
: t("confirm"),
|
||||
onClick: () => mutation.mutate(true),
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
mutation.mutate(true);
|
||||
},
|
||||
icon: CheckIcon,
|
||||
disabled: mutation.isLoading,
|
||||
color: "primary",
|
||||
|
@ -120,7 +134,10 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
id: "reschedule_request",
|
||||
icon: ClockIcon,
|
||||
label: t("send_reschedule_request"),
|
||||
onClick: () => setIsOpenRescheduleDialog(true),
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
setIsOpenRescheduleDialog(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -150,6 +167,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
i18n
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RescheduleDialog
|
||||
|
@ -191,7 +209,30 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
</DialogContent>
|
||||
</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">
|
||||
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
|
@ -264,9 +305,12 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
)}
|
||||
|
||||
{booking.attendees.length !== 0 && (
|
||||
<div className="text-sm text-gray-900 hover:text-blue-500">
|
||||
<a href={"mailto:" + booking.attendees[0].email}>{booking.attendees[0].email}</a>
|
||||
</div>
|
||||
<a
|
||||
className="text-sm text-gray-900 hover:text-blue-500"
|
||||
href={"mailto:" + booking.attendees[0].email}
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
{booking.attendees[0].email}
|
||||
</a>
|
||||
)}
|
||||
{isCancelled && booking.rescheduled && (
|
||||
<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 { EventType, PeriodType } from "@prisma/client";
|
||||
import { PeriodType } from "@prisma/client";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import dayjsBusinessTime from "dayjs-business-days2";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
|
@ -14,6 +14,7 @@ import classNames from "@lib/classNames";
|
|||
import { timeZone } from "@lib/clock";
|
||||
import { weekdayNames } from "@lib/core/i18n/weekday";
|
||||
import { doWorkAsync } from "@lib/doWorkAsync";
|
||||
import isOutOfBounds from "@lib/isOutOfBounds";
|
||||
import getSlots from "@lib/slots";
|
||||
import { WorkingHours } from "@lib/types/schedule";
|
||||
|
||||
|
@ -37,42 +38,6 @@ type DatePickerProps = {
|
|||
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({
|
||||
weekStart,
|
||||
onDatePicked,
|
||||
|
|
|
@ -77,7 +77,7 @@ type BookingFormValues = {
|
|||
phone?: string;
|
||||
hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers
|
||||
customInputs?: {
|
||||
[key: string]: string;
|
||||
[key: string]: string | boolean;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -216,7 +216,7 @@ const BookingPage = ({
|
|||
}, [router.query.guest]);
|
||||
|
||||
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
|
||||
? booking?.attendees.slice(1).map((attendee) => attendee.email)
|
||||
: [];
|
||||
|
@ -244,11 +244,22 @@ const BookingPage = ({
|
|||
if (!primaryAttendee) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const customInputType = booking.customInputs;
|
||||
return {
|
||||
name: primaryAttendee.name || "",
|
||||
email: primaryAttendee.email || "",
|
||||
guests: guestListEmails,
|
||||
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 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 (
|
||||
<div>
|
||||
|
@ -541,10 +555,7 @@ const BookingPage = ({
|
|||
name="name"
|
||||
id="name"
|
||||
required
|
||||
className={classNames(
|
||||
"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" : ""
|
||||
)}
|
||||
className={inputClassName}
|
||||
placeholder={t("example_name")}
|
||||
disabled={disableInput}
|
||||
/>
|
||||
|
@ -561,8 +572,7 @@ const BookingPage = ({
|
|||
{...bookingForm.register("email")}
|
||||
required
|
||||
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",
|
||||
disableInput ? "bg-gray-200 dark:text-gray-500" : "",
|
||||
inputClassName,
|
||||
bookingForm.formState.errors.email
|
||||
? "border-red-700 focus:ring-red-700"
|
||||
: " border-gray-300 dark:border-gray-900"
|
||||
|
@ -637,12 +647,9 @@ const BookingPage = ({
|
|||
})}
|
||||
id={"custom_" + input.id}
|
||||
rows={3}
|
||||
className={classNames(
|
||||
"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" : ""
|
||||
)}
|
||||
className={inputClassName}
|
||||
placeholder={input.placeholder}
|
||||
disabled={disableInput}
|
||||
disabled={disabledExceptForOwner}
|
||||
/>
|
||||
)}
|
||||
{input.type === EventTypeCustomInputType.TEXT && (
|
||||
|
@ -652,9 +659,9 @@ const BookingPage = ({
|
|||
required: input.required,
|
||||
})}
|
||||
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}
|
||||
disabled={disableInput}
|
||||
disabled={disabledExceptForOwner}
|
||||
/>
|
||||
)}
|
||||
{input.type === EventTypeCustomInputType.NUMBER && (
|
||||
|
@ -664,8 +671,9 @@ const BookingPage = ({
|
|||
required: input.required,
|
||||
})}
|
||||
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=""
|
||||
disabled={disabledExceptForOwner}
|
||||
/>
|
||||
)}
|
||||
{input.type === EventTypeCustomInputType.BOOL && (
|
||||
|
@ -676,8 +684,9 @@ const BookingPage = ({
|
|||
required: input.required,
|
||||
})}
|
||||
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=""
|
||||
disabled={disabledExceptForOwner}
|
||||
/>
|
||||
<label
|
||||
htmlFor={"custom_" + input.id}
|
||||
|
@ -764,12 +773,9 @@ const BookingPage = ({
|
|||
id="notes"
|
||||
name="notes"
|
||||
rows={3}
|
||||
className={classNames(
|
||||
"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" : ""
|
||||
)}
|
||||
className={inputClassName}
|
||||
placeholder={t("share_additional_notes")}
|
||||
disabled={disableInput}
|
||||
disabled={disabledExceptForOwner}
|
||||
/>
|
||||
</div>
|
||||
<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 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";
|
||||
|
||||
|
@ -12,15 +12,27 @@ export type ActionType = {
|
|||
label: string;
|
||||
disabled?: boolean;
|
||||
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 {
|
||||
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 (
|
||||
<Dropdown>
|
||||
{!actionTrigger ? (
|
||||
|
@ -40,7 +52,7 @@ const DropdownActions = ({ actions, actionTrigger }: { actions: ActionType[]; ac
|
|||
className="w-full rounded-none font-normal"
|
||||
href={action.href}
|
||||
StartIcon={action.icon}
|
||||
onClick={action.onClick}
|
||||
onClick={action.onClick || defaultAction}
|
||||
data-testid={action.id}>
|
||||
{action.label}
|
||||
</Button>
|
||||
|
@ -67,7 +79,7 @@ const TableActions: FC<Props> = ({ actions }) => {
|
|||
key={action.id}
|
||||
data-testid={action.id}
|
||||
href={action.href}
|
||||
onClick={action.onClick}
|
||||
onClick={action.onClick || defaultAction}
|
||||
StartIcon={action.icon}
|
||||
{...(action?.actions ? { EndIcon: ChevronDownIcon } : null)}
|
||||
disabled={action.disabled}
|
||||
|
|
|
@ -4,8 +4,9 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||
import Stripe from "stripe";
|
||||
|
||||
import EventManager from "@calcom/core/EventManager";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import prisma from "@calcom/prisma";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import stripe from "@calcom/stripe/server";
|
||||
import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
|
@ -42,16 +43,11 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
|||
id: payment.bookingId,
|
||||
},
|
||||
select: {
|
||||
title: true,
|
||||
description: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
...bookingMinimalSelect,
|
||||
confirmed: true,
|
||||
attendees: true,
|
||||
location: true,
|
||||
eventTypeId: true,
|
||||
userId: true,
|
||||
id: true,
|
||||
uid: true,
|
||||
paid: true,
|
||||
destinationCalendar: true,
|
||||
|
@ -113,8 +109,9 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
|||
description: booking.description || undefined,
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||
organizer: {
|
||||
email: user.email!,
|
||||
email: user.email,
|
||||
name: user.name!,
|
||||
timeZone: user.timeZone,
|
||||
language: { translate: t, locale: user.locale ?? "en" },
|
||||
|
|
|
@ -50,6 +50,7 @@ ${this.getWhen()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
|
@ -97,6 +98,7 @@ ${this.getAdditionalNotes()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -49,6 +49,7 @@ ${this.getWhen()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
@ -98,6 +99,7 @@ ${this.calEvent.cancellationReason && this.getCancellationReason()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -51,6 +51,7 @@ ${this.getWhen()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.getRejectionReason()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
@ -102,6 +103,7 @@ ${this.getRejectionReason()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.getRejectionReason()}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -62,6 +62,7 @@ ${this.getWhen()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
|
@ -119,6 +120,7 @@ ${this.getAdditionalNotes()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -105,6 +105,7 @@ ${this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
|||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
|
@ -154,6 +155,7 @@ ${getCancelLink(this.calEvent)}
|
|||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -57,6 +57,7 @@ export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.attendee.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
|
@ -69,6 +70,7 @@ ${this.getWhat()}
|
|||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
|
@ -116,6 +118,7 @@ ${this.getAdditionalNotes()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -169,6 +169,7 @@ ${getRichDescription(this.calEvent)}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</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 {
|
||||
if (!this.calEvent.rejectionReason) return "";
|
||||
return `
|
||||
|
|
|
@ -58,6 +58,7 @@ ${this.getWhen()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
@ -106,6 +107,7 @@ ${this.calEvent.cancellationReason && this.getCancellationReason()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -60,6 +60,7 @@ ${this.getWhen()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
|
@ -139,6 +140,7 @@ ${this.getAdditionalNotes()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -59,6 +59,7 @@ ${this.getWhen()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
|
||||
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
|
@ -110,6 +111,7 @@ ${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
|||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -59,6 +59,7 @@ ${this.getWhen()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
|
||||
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
|
@ -108,6 +109,7 @@ ${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -115,6 +115,7 @@ ${this.getWhat()}
|
|||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
|
@ -166,6 +167,7 @@ ${getCancelLink(this.calEvent)}
|
|||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -64,6 +64,7 @@ ${this.getWhen()}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
|
@ -113,6 +114,7 @@ ${getCancelLink(this.calEvent)}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -162,6 +162,7 @@ ${getRichDescription(this.calEvent)}
|
|||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</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 {
|
||||
if (!this.calEvent.description) return "";
|
||||
return `
|
||||
|
|
|
@ -8,6 +8,7 @@ async function getBooking(prisma: PrismaClient, uid: string) {
|
|||
select: {
|
||||
startTime: true,
|
||||
description: true,
|
||||
customInputs: true,
|
||||
attendees: {
|
||||
select: {
|
||||
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;
|
||||
user?: string | string[];
|
||||
language: string;
|
||||
customInputs: { label: string; value: string }[];
|
||||
customInputs: { label: string; value: string | boolean }[];
|
||||
metadata: {
|
||||
[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 EventManager from "@calcom/core/EventManager";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type { AdditionInformation, RecurringEvent } from "@calcom/types/Calendar";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import type { AdditionInformation, CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||
import { refund } from "@ee/lib/stripe/server";
|
||||
|
||||
import { asStringOrNull } from "@lib/asStringOrNull";
|
||||
import { getSession } from "@lib/auth";
|
||||
import { sendDeclinedEmails } from "@lib/emails/email-manager";
|
||||
import { sendScheduledEmails } from "@lib/emails/email-manager";
|
||||
import { sendDeclinedEmails, sendScheduledEmails } from "@lib/emails/email-manager";
|
||||
import prisma from "@lib/prisma";
|
||||
import { BookingConfirmBody } from "@lib/types/booking";
|
||||
|
||||
|
@ -89,6 +88,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
select: {
|
||||
title: true,
|
||||
description: true,
|
||||
customInputs: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
confirmed: true,
|
||||
|
@ -156,6 +156,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
type: booking.title,
|
||||
title: booking.title,
|
||||
description: booking.description,
|
||||
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
organizer: {
|
||||
|
|
|
@ -11,6 +11,7 @@ import short from "short-uuid";
|
|||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
import EventManager from "@calcom/core/EventManager";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
@ -28,6 +29,7 @@ import {
|
|||
import { ensureArray } from "@lib/ensureArray";
|
||||
import { getEventName } from "@lib/event";
|
||||
import getBusyTimes from "@lib/getBusyTimes";
|
||||
import isOutOfBounds from "@lib/isOutOfBounds";
|
||||
import prisma from "@lib/prisma";
|
||||
import { BookingCreateBody } from "@lib/types/booking";
|
||||
import sendPayload from "@lib/webhooks/sendPayload";
|
||||
|
@ -104,32 +106,6 @@ function isAvailable(busyTimes: BufferedBusyTimes, time: dayjs.ConfigType, lengt
|
|||
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>()({
|
||||
select: {
|
||||
id: true,
|
||||
|
@ -348,18 +324,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
t: tOrganizer,
|
||||
};
|
||||
|
||||
const additionalNotes =
|
||||
reqBody.notes +
|
||||
reqBody.customInputs.reduce(
|
||||
(str, input) => str + "<br /><br />" + input.label + ":<br />" + input.value,
|
||||
""
|
||||
);
|
||||
const additionalNotes = reqBody.notes;
|
||||
|
||||
const customInputs = {} as NonNullable<CalendarEvent["customInputs"]>;
|
||||
|
||||
if (reqBody.customInputs.length > 0) {
|
||||
reqBody.customInputs.forEach(({ label, value }) => {
|
||||
customInputs[label] = value;
|
||||
});
|
||||
}
|
||||
|
||||
const evt: CalendarEvent = {
|
||||
type: eventType.title,
|
||||
title: getEventName(eventNameObject), //this needs to be either forced in english, or fetched for each attendee and organizer separately
|
||||
description: eventType.description,
|
||||
additionalNotes,
|
||||
customInputs,
|
||||
startTime: reqBody.start,
|
||||
endTime: reqBody.end,
|
||||
organizer: {
|
||||
|
@ -456,6 +436,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
startTime: dayjs(evt.startTime).toDate(),
|
||||
endTime: dayjs(evt.endTime).toDate(),
|
||||
description: evt.additionalNotes,
|
||||
customInputs: isPrismaObjOrUndefined(evt.customInputs),
|
||||
confirmed: (!eventType.requiresConfirmation && !eventType.price) || !!rescheduleUid,
|
||||
location: evt.location,
|
||||
eventType: eventTypeRel,
|
||||
|
@ -613,7 +594,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
periodEndDate: eventType.periodEndDate,
|
||||
periodStartDate: eventType.periodStartDate,
|
||||
periodCountCalendarDays: eventType.periodCountCalendarDays,
|
||||
timeZone: currentUser.timeZone,
|
||||
});
|
||||
} catch {
|
||||
log.debug({
|
||||
|
@ -731,6 +711,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
...evt,
|
||||
additionInformation: metadata,
|
||||
additionalNotes,
|
||||
customInputs,
|
||||
},
|
||||
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 { getCalendar } from "@calcom/core/CalendarManager";
|
||||
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 { refund } from "@ee/lib/stripe/server";
|
||||
|
||||
import { asStringOrNull } from "@lib/asStringOrNull";
|
||||
import { getSession } from "@lib/auth";
|
||||
import { sendCancelledEmails } from "@lib/emails/email-manager";
|
||||
import prisma from "@lib/prisma";
|
||||
import sendPayload from "@lib/webhooks/sendPayload";
|
||||
import getWebhooks from "@lib/webhooks/subscriptions";
|
||||
|
||||
|
@ -33,7 +34,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
uid,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
...bookingMinimalSelect,
|
||||
userId: true,
|
||||
user: {
|
||||
select: {
|
||||
|
@ -45,7 +46,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
destinationCalendar: true,
|
||||
},
|
||||
},
|
||||
attendees: true,
|
||||
location: true,
|
||||
references: {
|
||||
select: {
|
||||
|
@ -56,15 +56,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
},
|
||||
payment: true,
|
||||
paid: true,
|
||||
title: true,
|
||||
eventType: {
|
||||
select: {
|
||||
title: true,
|
||||
},
|
||||
},
|
||||
description: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
uid: true,
|
||||
eventTypeId: true,
|
||||
destinationCalendar: true,
|
||||
|
@ -115,6 +111,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
title: bookingToDelete?.title,
|
||||
type: (bookingToDelete?.eventType?.title as string) || bookingToDelete?.title,
|
||||
description: bookingToDelete?.description || "",
|
||||
customInputs: isPrismaObjOrUndefined(bookingToDelete.customInputs),
|
||||
startTime: bookingToDelete?.startTime ? dayjs(bookingToDelete.startTime).format() : "",
|
||||
endTime: bookingToDelete?.endTime ? dayjs(bookingToDelete.endTime).format() : "",
|
||||
organizer: {
|
||||
|
@ -183,6 +180,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
type: bookingToDelete?.eventType?.title as string,
|
||||
title: bookingToDelete.title,
|
||||
description: bookingToDelete.description ?? "",
|
||||
customInputs: isPrismaObjOrUndefined(bookingToDelete.customInputs),
|
||||
startTime: bookingToDelete.startTime.toISOString(),
|
||||
endTime: bookingToDelete.endTime.toISOString(),
|
||||
organizer: {
|
||||
|
|
|
@ -2,10 +2,11 @@ import { ReminderType } from "@prisma/client";
|
|||
import dayjs from "dayjs";
|
||||
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 { sendOrganizerRequestReminderEmail } from "@lib/emails/email-manager";
|
||||
import prisma from "@lib/prisma";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
|
||||
|
@ -32,12 +33,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
},
|
||||
},
|
||||
select: {
|
||||
title: true,
|
||||
description: true,
|
||||
...bookingMinimalSelect,
|
||||
location: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
attendees: true,
|
||||
user: {
|
||||
select: {
|
||||
email: true,
|
||||
|
@ -48,7 +45,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
destinationCalendar: true,
|
||||
},
|
||||
},
|
||||
id: true,
|
||||
uid: true,
|
||||
destinationCalendar: true,
|
||||
},
|
||||
|
@ -94,6 +90,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
type: booking.title,
|
||||
title: booking.title,
|
||||
description: booking.description || undefined,
|
||||
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||
location: booking.location ?? "",
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
|
|
|
@ -4,13 +4,13 @@ import { GetServerSidePropsContext } from "next";
|
|||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import { Button } from "@calcom/ui/Button";
|
||||
import { TextField } from "@calcom/ui/form/fields";
|
||||
|
||||
import { asStringOrUndefined } from "@lib/asStringOrNull";
|
||||
import { getSession } from "@lib/auth";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import prisma from "@lib/prisma";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
|
||||
import { detectBrowserTimeFormat } from "@lib/timeFormat";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
@ -168,12 +168,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
uid: asStringOrUndefined(context.query.uid),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
attendees: true,
|
||||
...bookingMinimalSelect,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
|
||||
import { asStringOrUndefined } from "@lib/asStringOrNull";
|
||||
import prisma from "@lib/prisma";
|
||||
|
||||
export default function Type() {
|
||||
// Just redirect to the schedule page to reschedule it.
|
||||
|
@ -16,7 +16,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
uid: asStringOrUndefined(context.query.uid),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
...bookingMinimalSelect,
|
||||
eventType: {
|
||||
select: {
|
||||
users: {
|
||||
|
@ -35,11 +35,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
dynamicEventSlugRef: true,
|
||||
dynamicGroupSlugRef: true,
|
||||
user: true,
|
||||
title: true,
|
||||
description: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
attendees: true,
|
||||
},
|
||||
});
|
||||
const dynamicEventSlugRef = booking?.dynamicEventSlugRef || "";
|
||||
|
|
|
@ -40,6 +40,7 @@ import { isBrowserLocale24h } from "@lib/timeFormat";
|
|||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import CustomBranding from "@components/CustomBranding";
|
||||
import CancelBooking from "@components/booking/CancelBooking";
|
||||
import { HeadSeo } from "@components/seo/head-seo";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
@ -152,13 +153,13 @@ export default function Success(props: SuccessProps) {
|
|||
const { data: session } = useSession();
|
||||
|
||||
const [date, setDate] = useState(dayjs.utc(asStringOrThrow(router.query.date)));
|
||||
const { isReady, Theme } = useTheme(props.profile.theme);
|
||||
const { eventType, bookingInfo } = props;
|
||||
|
||||
const isBackgroundTransparent = useIsBackgroundTransparent();
|
||||
const isEmbed = useIsEmbed();
|
||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
||||
const [isCancellationMode, setIsCancellationMode] = useState(false);
|
||||
|
||||
const attendeeName = typeof name === "string" ? name : "Nameless";
|
||||
|
||||
|
@ -247,15 +248,26 @@ export default function Success(props: SuccessProps) {
|
|||
return t("emailed_you_and_attendees" + titleSuffix);
|
||||
}
|
||||
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(
|
||||
`booking_${needsConfirmation ? "submitted" : "confirmed"}${props.recurringBookings ? "_recurring" : ""}`
|
||||
);
|
||||
const customInputs = bookingInfo?.customInputs;
|
||||
return (
|
||||
(isReady && (
|
||||
<>
|
||||
<div
|
||||
className={isEmbed ? "" : "h-screen bg-neutral-100 dark:bg-neutral-900"}
|
||||
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 />
|
||||
<HeadSeo title={title} description={title} />
|
||||
<CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} />
|
||||
|
@ -357,21 +369,66 @@ export default function Success(props: SuccessProps) {
|
|||
)}
|
||||
{bookingInfo?.description && (
|
||||
<>
|
||||
<div className="mt-6 font-medium">{t("additional_notes")}</div>
|
||||
<div className="col-span-2 mt-6 mb-6">
|
||||
<div className="mt-9 font-medium">{t("additional_notes")}</div>
|
||||
<div className="col-span-2 mb-2 mt-9">
|
||||
<p>{bookingInfo.description}</p>
|
||||
</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>
|
||||
{!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">
|
||||
{!needsConfirmation &&
|
||||
(!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">
|
||||
{t("add_to_calendar")}
|
||||
</span>
|
||||
<div className="flex flex-grow justify-center text-center">
|
||||
<div className="-ml-16 flex flex-grow justify-center text-center">
|
||||
<Link
|
||||
href={
|
||||
`https://calendar.google.com/calendar/r/eventedit?dates=${date
|
||||
|
@ -497,15 +554,6 @@ export default function Success(props: SuccessProps) {
|
|||
</form>
|
||||
</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>
|
||||
|
@ -618,6 +666,7 @@ const getEventTypesFromDB = async (typeId: number) => {
|
|||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
hideBranding: true,
|
||||
plan: true,
|
||||
theme: true,
|
||||
|
@ -629,6 +678,7 @@ const getEventTypesFromDB = async (typeId: number) => {
|
|||
},
|
||||
team: {
|
||||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
|
@ -683,6 +733,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
hideBranding: true,
|
||||
plan: true,
|
||||
theme: true,
|
||||
|
@ -714,6 +765,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
theme: (!eventType.team?.name && eventType.users[0]?.theme) || null,
|
||||
brandColor: eventType.team ? null : eventType.users[0].brandColor || 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({
|
||||
|
@ -721,7 +773,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
id: bookingId,
|
||||
},
|
||||
select: {
|
||||
title: true,
|
||||
uid: true,
|
||||
description: true,
|
||||
customInputs: true,
|
||||
user: {
|
||||
select: {
|
||||
name: true,
|
||||
|
|
|
@ -6,9 +6,9 @@ import Link from "next/link";
|
|||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import prisma from "@lib/prisma";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
|
||||
export type JoinCallPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
|
@ -150,19 +150,14 @@ export async function getServerSideProps(context: NextPageContext) {
|
|||
uid: context.query.uid as string,
|
||||
},
|
||||
select: {
|
||||
...bookingMinimalSelect,
|
||||
uid: true,
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
credentials: true,
|
||||
},
|
||||
},
|
||||
attendees: true,
|
||||
dailyRef: {
|
||||
select: {
|
||||
dailyurl: true,
|
||||
|
|
|
@ -6,9 +6,9 @@ import { getSession } from "next-auth/react";
|
|||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import Button from "@calcom/ui/Button";
|
||||
|
||||
import prisma from "@lib/prisma";
|
||||
import { detectBrowserTimeFormat } from "@lib/timeFormat";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
|
@ -84,18 +84,13 @@ export async function getServerSideProps(context: NextPageContext) {
|
|||
uid: context.query.uid as string,
|
||||
},
|
||||
select: {
|
||||
...bookingMinimalSelect,
|
||||
uid: true,
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
user: {
|
||||
select: {
|
||||
credentials: true,
|
||||
},
|
||||
},
|
||||
attendees: true,
|
||||
dailyRef: {
|
||||
select: {
|
||||
dailyurl: true,
|
||||
|
|
|
@ -6,11 +6,11 @@ import { getSession } from "next-auth/react";
|
|||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import Button from "@calcom/ui/Button";
|
||||
|
||||
import prisma from "@lib/prisma";
|
||||
import { detectBrowserTimeFormat } from "@lib/timeFormat";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import { HeadSeo } from "@components/seo/head-seo";
|
||||
|
||||
|
@ -90,18 +90,13 @@ export async function getServerSideProps(context: NextPageContext) {
|
|||
uid: context.query.uid as string,
|
||||
},
|
||||
select: {
|
||||
...bookingMinimalSelect,
|
||||
uid: true,
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
user: {
|
||||
select: {
|
||||
credentials: true,
|
||||
},
|
||||
},
|
||||
attendees: true,
|
||||
dailyRef: {
|
||||
select: {
|
||||
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",
|
||||
"cancel": "Cancel",
|
||||
"apply": "Apply",
|
||||
"cancel_event": "Cancel this event",
|
||||
"cancel_event": "Cancel event",
|
||||
"continue": "Continue",
|
||||
"confirm": "Confirm",
|
||||
"confirm_all": "Confirm all",
|
||||
|
@ -823,11 +823,14 @@
|
|||
"generate_api_key": "Generate 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.",
|
||||
"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.",
|
||||
"go_to_app_store": "Go to App Store",
|
||||
"calendar_error": "Something went wrong, try reconnecting your calendar with all necessary permissions",
|
||||
"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: ",
|
||||
"zapier_invite_link": "Zapier Invite Link"
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { z } from "zod";
|
|||
import getApps from "@calcom/app-store/utils";
|
||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
||||
import { checkPremiumUsername } from "@calcom/ee/lib/core/checkPremiumUsername";
|
||||
import { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import { RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
|
||||
|
@ -388,18 +389,17 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
AND: passedBookingsFilter,
|
||||
},
|
||||
select: {
|
||||
...bookingMinimalSelect,
|
||||
uid: true,
|
||||
title: true,
|
||||
description: true,
|
||||
attendees: true,
|
||||
confirmed: true,
|
||||
rejected: true,
|
||||
id: true,
|
||||
startTime: true,
|
||||
recurringEventId: true,
|
||||
endTime: true,
|
||||
location: true,
|
||||
eventType: {
|
||||
select: {
|
||||
slug: true,
|
||||
id: true,
|
||||
eventName: true,
|
||||
price: true,
|
||||
recurringEvent: true,
|
||||
team: {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
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) {
|
||||
const apiKey = req.query.apiKey as string;
|
||||
|
@ -24,10 +24,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
userId: validKey.userId,
|
||||
},
|
||||
select: {
|
||||
description: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
title: true,
|
||||
...bookingMinimalSelect,
|
||||
location: true,
|
||||
attendees: {
|
||||
select: {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { v5 as uuidv5 } from "uuid";
|
|||
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { BASE_URL } from "./constants";
|
||||
import { WEBAPP_URL } from "./constants";
|
||||
|
||||
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) => {
|
||||
if (!calEvent.description) {
|
||||
return "";
|
||||
|
@ -94,7 +113,7 @@ export const getUid = (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) => {
|
||||
|
@ -110,6 +129,7 @@ ${calEvent.organizer.language.translate("where")}:
|
|||
${getLocation(calEvent)}
|
||||
${getDescription(calEvent)}
|
||||
${getAdditionalNotes(calEvent)}
|
||||
${getCustomInputs(calEvent)}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
|
@ -121,6 +141,7 @@ ${calEvent.organizer.language.translate("where")}:
|
|||
${getLocation(calEvent)}
|
||||
${getDescription(calEvent)}
|
||||
${getAdditionalNotes(calEvent)}
|
||||
${getCustomInputs(calEvent)}
|
||||
${getManageLink(calEvent)}
|
||||
`.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";
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var prisma: PrismaClient | undefined;
|
||||
}
|
||||
|
||||
|
@ -19,3 +20,5 @@ if (process.env.NODE_ENV !== "production") {
|
|||
bookingReferenceMiddleware(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?
|
||||
title String
|
||||
description String?
|
||||
customInputs Json?
|
||||
startTime DateTime
|
||||
endTime DateTime
|
||||
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 { calendar_v3 } from "googleapis";
|
||||
import type { Time } from "ical.js";
|
||||
|
@ -97,6 +97,7 @@ export interface CalendarEvent {
|
|||
organizer: Person;
|
||||
attendees: Person[];
|
||||
additionalNotes?: string | null;
|
||||
customInputs?: Prisma.JsonObject | null;
|
||||
description?: string | null;
|
||||
team?: {
|
||||
name: string;
|
||||
|
|
Loading…
Reference in New Issue
Block a user