Merge branch 'main' into teams-stripe-checkout-form
This commit is contained in:
commit
e59dbb9ed0
|
@ -24,14 +24,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
|
|||
alt={`${eventLocationType.label} icon`}
|
||||
/>
|
||||
<Tooltip content={locationKeyToString(location)}>
|
||||
<a
|
||||
target="_blank"
|
||||
href={locationKeyToString(location) ?? "/"}
|
||||
className="truncate"
|
||||
key={location.type}
|
||||
rel="noreferrer">
|
||||
{locationKeyToString(location)}
|
||||
</a>
|
||||
<p className="truncate">{locationKeyToString(location)}</p>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -40,6 +40,7 @@ import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
|||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Tooltip } from "@calcom/ui/Tooltip";
|
||||
import AddressInput from "@calcom/ui/form/AddressInputLazy";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
|
||||
import { EmailInput, Form } from "@calcom/ui/form/fields";
|
||||
|
@ -68,6 +69,8 @@ type BookingFormValues = {
|
|||
notes?: string;
|
||||
locationType?: EventLocationType["type"];
|
||||
guests?: string[];
|
||||
address?: string;
|
||||
attendeeAddress?: string;
|
||||
phone?: string;
|
||||
hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers
|
||||
customInputs?: {
|
||||
|
@ -269,6 +272,7 @@ const BookingPage = ({
|
|||
.refine((val) => isValidPhoneNumber(val))
|
||||
.optional()
|
||||
.nullable(),
|
||||
attendeeAddress: z.string().optional().nullable(),
|
||||
smsReminderNumber: z
|
||||
.string()
|
||||
.refine((val) => isValidPhoneNumber(val))
|
||||
|
@ -297,10 +301,10 @@ const BookingPage = ({
|
|||
|
||||
const selectedLocation = getEventLocationType(selectedLocationType);
|
||||
const AttendeeInput =
|
||||
selectedLocation?.attendeeInputType === "text"
|
||||
? "input"
|
||||
: selectedLocation?.attendeeInputType === "phone"
|
||||
selectedLocation?.attendeeInputType === "phone"
|
||||
? PhoneInput
|
||||
: selectedLocation?.attendeeInputType === "attendeeAddress"
|
||||
? AddressInput
|
||||
: null;
|
||||
|
||||
// Calculate the booking date(s)
|
||||
|
@ -356,6 +360,7 @@ const BookingPage = ({
|
|||
location: getEventLocationValue(locations, {
|
||||
type: booking.locationType ? booking.locationType : selectedLocationType || "",
|
||||
phone: booking.phone,
|
||||
attendeeAddress: booking.attendeeAddress,
|
||||
}),
|
||||
metadata,
|
||||
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
|
||||
|
@ -386,6 +391,7 @@ const BookingPage = ({
|
|||
location: getEventLocationValue(locations, {
|
||||
type: (booking.locationType ? booking.locationType : selectedLocationType) || "",
|
||||
phone: booking.phone,
|
||||
attendeeAddress: booking.attendeeAddress,
|
||||
}),
|
||||
metadata,
|
||||
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
|
||||
|
@ -648,16 +654,39 @@ const BookingPage = ({
|
|||
{AttendeeInput && (
|
||||
<div className="mb-4">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
htmlFor={
|
||||
selectedLocationType === LocationType.Phone
|
||||
? "phone"
|
||||
: selectedLocationType === LocationType.AttendeeInPerson
|
||||
? "attendeeAddress"
|
||||
: ""
|
||||
}
|
||||
className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("phone_number")}
|
||||
{selectedLocationType === LocationType.Phone
|
||||
? t("phone_number")
|
||||
: selectedLocationType === LocationType.AttendeeInPerson
|
||||
? t("Address")
|
||||
: ""}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<AttendeeInput<BookingFormValues>
|
||||
control={bookingForm.control}
|
||||
name="phone"
|
||||
bookingForm={bookingForm}
|
||||
name={
|
||||
selectedLocationType === LocationType.Phone
|
||||
? "phone"
|
||||
: selectedLocationType === LocationType.AttendeeInPerson
|
||||
? "attendeeAddress"
|
||||
: ""
|
||||
}
|
||||
placeholder={t(selectedLocation?.attendeeInputPlaceholder || "")}
|
||||
id="phone"
|
||||
id={
|
||||
selectedLocationType === LocationType.Phone
|
||||
? "phone"
|
||||
: selectedLocationType === LocationType.AttendeeInPerson
|
||||
? "attendeeAddress"
|
||||
: ""
|
||||
}
|
||||
required
|
||||
disabled={disableInput}
|
||||
/>
|
||||
|
|
|
@ -293,7 +293,10 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
|||
defaultValue={selection}
|
||||
options={
|
||||
booking
|
||||
? locationOptions.filter((location) => location.value !== "phone")
|
||||
? locationOptions.filter(
|
||||
(location) =>
|
||||
location.value !== "phone" && location.value !== "attendeeInPerson"
|
||||
)
|
||||
: locationOptions
|
||||
}
|
||||
isSearchable
|
||||
|
|
|
@ -232,6 +232,12 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
|||
const { locationType: newLocation, displayLocationPublicly } = values;
|
||||
|
||||
let details = {};
|
||||
if (newLocation === LocationType.AttendeeInPerson) {
|
||||
details = {
|
||||
address: values.locationAddress,
|
||||
};
|
||||
}
|
||||
|
||||
if (newLocation === LocationType.InPerson) {
|
||||
details = {
|
||||
address: values.locationAddress,
|
||||
|
|
|
@ -54,7 +54,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
|||
<div className="flex flex-col space-y-8">
|
||||
{/**
|
||||
* Only display calendar selector if user has connected calendars AND if it's not
|
||||
* a team event. Since we don't have logic to handle each attende calendar (for now).
|
||||
* a team event. Since we don't have logic to handle each attendee calendar (for now).
|
||||
* This will fallback to each user selected destination calendar.
|
||||
*/}
|
||||
{!!connectedCalendarsQuery.data?.connectedCalendars.length && !team && (
|
||||
|
@ -292,7 +292,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
|||
description={t("offer_seats_description")}
|
||||
checked={value}
|
||||
onCheckedChange={(e) => {
|
||||
// Enabling seats will disable guests and requiring confimation until fully supported
|
||||
// Enabling seats will disable guests and requiring confirmation until fully supported
|
||||
if (e) {
|
||||
formMethods.setValue("disableGuests", true);
|
||||
formMethods.setValue("requiresConfirmation", false);
|
||||
|
@ -316,9 +316,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
|||
label={t("number_of_seats")}
|
||||
type="number"
|
||||
defaultValue={value || 2}
|
||||
min={1}
|
||||
addOnSuffix={<>{t("seats")}</>}
|
||||
onChange={(e) => {
|
||||
onChange(Number(e.target.value));
|
||||
onChange(Math.abs(Number(e.target.value)));
|
||||
}}
|
||||
/>
|
||||
<div className="mt-2">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TooltipProvider } from "@radix-ui/react-tooltip";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { EventCollectionProvider } from "next-collect/client";
|
||||
import { appWithTranslation } from "next-i18next";
|
||||
import { appWithTranslation, SSRConfig } from "next-i18next";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import type { AppProps as NextAppProps, AppProps as NextJsAppProps } from "next/app";
|
||||
import { NextRouter } from "next/router";
|
||||
|
@ -14,9 +14,9 @@ import { MetaProvider } from "@calcom/ui/v2/core/Meta";
|
|||
|
||||
import usePublicPage from "@lib/hooks/usePublicPage";
|
||||
|
||||
const I18nextAdapter = appWithTranslation<NextJsAppProps & { children: React.ReactNode }>(({ children }) => (
|
||||
<>{children}</>
|
||||
));
|
||||
const I18nextAdapter = appWithTranslation<NextJsAppProps<SSRConfig> & { children: React.ReactNode }>(
|
||||
({ children }) => <>{children}</>
|
||||
);
|
||||
|
||||
// Workaround for https://github.com/vercel/next.js/issues/8592
|
||||
export type AppProps = Omit<NextAppProps, "Component"> & {
|
||||
|
|
|
@ -56,6 +56,7 @@ export type FormValues = {
|
|||
locations: {
|
||||
type: EventLocationType["type"];
|
||||
address?: string;
|
||||
attendeeAddress?: string;
|
||||
link?: string;
|
||||
hostPhoneNumber?: string;
|
||||
displayLocationPublicly?: boolean;
|
||||
|
|
|
@ -369,19 +369,8 @@ export default function Success(props: SuccessProps) {
|
|||
<p className="text-bookinglight">{bookingInfo.user.email}</p>
|
||||
</div>
|
||||
)}
|
||||
{!eventType.seatsShowAttendees
|
||||
? bookingInfo?.attendees
|
||||
.filter((attendee) => attendee.email === email)
|
||||
.map((attendee) => (
|
||||
<div key={attendee.name} className="mb-3">
|
||||
<p>{attendee.name}</p>
|
||||
<p className="text-bookinglight">{attendee.email}</p>
|
||||
</div>
|
||||
))
|
||||
: bookingInfo?.attendees.map((attendee, index) => (
|
||||
<div
|
||||
key={attendee.name}
|
||||
className={index === bookingInfo.attendees.length - 1 ? "" : "mb-3"}>
|
||||
{bookingInfo?.attendees.map((attendee, index) => (
|
||||
<div key={attendee.name} className="mb-3 last:mb-0">
|
||||
<p>{attendee.name}</p>
|
||||
<p className="text-bookinglight">{attendee.email}</p>
|
||||
</div>
|
||||
|
@ -786,6 +775,28 @@ const schema = z.object({
|
|||
bookingId: strToNumber,
|
||||
});
|
||||
|
||||
const handleSeatsEventTypeOnBooking = (
|
||||
eventType: {
|
||||
seatsPerTimeSlot?: boolean | null;
|
||||
seatsShowAttendees: boolean | null;
|
||||
[x: string | number | symbol]: unknown;
|
||||
},
|
||||
booking: Partial<
|
||||
Prisma.BookingGetPayload<{ include: { attendees: { select: { name: true; email: true } } } }>
|
||||
>,
|
||||
email: string
|
||||
) => {
|
||||
if (eventType?.seatsPerTimeSlot !== null) {
|
||||
// @TODO: right now bookings with seats doesn't save every description that its entered by every user
|
||||
delete booking.description;
|
||||
}
|
||||
if (!eventType.seatsShowAttendees) {
|
||||
const attendee = booking?.attendees?.find((a) => a.email === email);
|
||||
booking["attendees"] = attendee ? [attendee] : [];
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const ssr = await ssrInit(context);
|
||||
const parsedQuery = schema.safeParse(context.query);
|
||||
|
@ -884,6 +895,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
},
|
||||
},
|
||||
});
|
||||
if (bookingInfo !== null && email) {
|
||||
handleSeatsEventTypeOnBooking(eventType, bookingInfo, email);
|
||||
}
|
||||
|
||||
let recurringBookings = null;
|
||||
if (recurringEventIdQuery) {
|
||||
// We need to get the dates for the bookings to be able to show them in the UI
|
||||
|
|
|
@ -123,6 +123,13 @@ operators.between.label = "Between";
|
|||
delete operators.proximity;
|
||||
delete operators.is_null;
|
||||
delete operators.is_not_null;
|
||||
|
||||
/**
|
||||
* Not supported with JSONLogic. Implement them and add these back -> https://github.com/jwadhams/json-logic-js/issues/81
|
||||
*/
|
||||
delete operators.starts_with;
|
||||
delete operators.ends_with;
|
||||
|
||||
const config = {
|
||||
conjunctions: BasicConfig.conjunctions,
|
||||
operators,
|
||||
|
|
|
@ -160,6 +160,10 @@ export default class GoogleCalendarService implements Calendar {
|
|||
async updateEvent(uid: string, event: CalendarEvent, externalCalendarId: string): Promise<any> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const myGoogleAuth = await this.auth.getToken();
|
||||
const eventAttendees = event.attendees.map(({ id, ...rest }) => ({
|
||||
...rest,
|
||||
responseStatus: "accepted",
|
||||
}));
|
||||
const payload: calendar_v3.Schema$Event = {
|
||||
summary: event.title,
|
||||
description: getRichDescription(event),
|
||||
|
@ -179,10 +183,7 @@ export default class GoogleCalendarService implements Calendar {
|
|||
responseStatus: "accepted",
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
...event.attendees.map(({ id, ...rest }) => ({
|
||||
...rest,
|
||||
responseStatus: "accepted",
|
||||
})),
|
||||
...eventAttendees,
|
||||
],
|
||||
reminders: {
|
||||
useDefault: true,
|
||||
|
|
|
@ -16,8 +16,8 @@ export type DefaultEventLocationType = {
|
|||
iconUrl: string;
|
||||
|
||||
// HACK: `variable` and `defaultValueVariable` are required due to legacy reason where different locations were stored in different places.
|
||||
variable: "locationType" | "locationAddress" | "locationLink" | "locationPhoneNumber" | "phone";
|
||||
defaultValueVariable: "address" | "link" | "hostPhoneNumber" | "phone";
|
||||
variable: "locationType" | "locationAddress" | "address" | "locationLink" | "locationPhoneNumber" | "phone";
|
||||
defaultValueVariable: "address" | "attendeeAddress" | "link" | "hostPhoneNumber" | "phone";
|
||||
} & (
|
||||
| {
|
||||
organizerInputType: "phone" | "text" | null;
|
||||
|
@ -26,7 +26,7 @@ export type DefaultEventLocationType = {
|
|||
attendeeInputPlaceholder?: null;
|
||||
}
|
||||
| {
|
||||
attendeeInputType: "phone" | "text" | null;
|
||||
attendeeInputType: "phone" | "attendeeAddress" | null;
|
||||
attendeeInputPlaceholder: string;
|
||||
organizerInputType?: null;
|
||||
organizerInputPlaceholder?: null;
|
||||
|
@ -40,6 +40,13 @@ export type EventLocationType = DefaultEventLocationType | EventLocationTypeFrom
|
|||
export const DailyLocationType = "integrations:daily";
|
||||
|
||||
export enum DefaultEventLocationTypeEnum {
|
||||
/**
|
||||
* Booker Address
|
||||
*/
|
||||
AttendeeInPerson = "attendeeInPerson",
|
||||
/**
|
||||
* Organizer Address
|
||||
*/
|
||||
InPerson = "inPerson",
|
||||
/**
|
||||
* Booker Phone
|
||||
|
@ -53,10 +60,22 @@ export enum DefaultEventLocationTypeEnum {
|
|||
}
|
||||
|
||||
export const defaultLocations: DefaultEventLocationType[] = [
|
||||
{
|
||||
default: true,
|
||||
type: DefaultEventLocationTypeEnum.AttendeeInPerson,
|
||||
label: "In Person (Attendee Address)",
|
||||
variable: "address",
|
||||
organizerInputType: null,
|
||||
messageForOrganizer: "Cal will ask your invitee to enter an address before scheduling.",
|
||||
attendeeInputType: "attendeeAddress",
|
||||
attendeeInputPlaceholder: `Enter Address`,
|
||||
defaultValueVariable: "attendeeAddress",
|
||||
iconUrl: "/map-pin.svg",
|
||||
},
|
||||
{
|
||||
default: true,
|
||||
type: DefaultEventLocationTypeEnum.InPerson,
|
||||
label: "In Person",
|
||||
label: "In Person (Organizer Address)",
|
||||
organizerInputType: "text",
|
||||
messageForOrganizer: "Provide an Address or Place",
|
||||
// HACK:
|
||||
|
@ -103,7 +122,7 @@ export const defaultLocations: DefaultEventLocationType[] = [
|
|||
export type LocationObject = {
|
||||
type: string;
|
||||
displayLocationPublicly?: boolean;
|
||||
} & Partial<Record<"address" | "link" | "hostPhoneNumber" | "phone", string>>;
|
||||
} & Partial<Record<"address" | "attendeeAddress" | "link" | "hostPhoneNumber" | "phone", string>>;
|
||||
|
||||
// integrations:jitsi | 919999999999 | Delhi | https://manual.meeting.link | Around Video
|
||||
export type BookingLocationValue = string;
|
||||
|
|
|
@ -326,6 +326,7 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
|
|||
|
||||
const handleZoomResponse = async (response: Response, credentialId: Credential["id"]) => {
|
||||
let _response = response.clone();
|
||||
const responseClone = response.clone();
|
||||
if (_response.headers.get("content-encoding") === "gzip") {
|
||||
const responseString = await response.text();
|
||||
_response = JSON.parse(responseString);
|
||||
|
@ -339,7 +340,7 @@ const handleZoomResponse = async (response: Response, credentialId: Credential["
|
|||
throw Error(response.statusText);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
return responseClone.json();
|
||||
};
|
||||
|
||||
const invalidateCredential = async (credentialId: Credential["id"]) => {
|
||||
|
|
|
@ -7,8 +7,14 @@ import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApi
|
|||
import { getEventLocationTypeFromApp } from "@calcom/app-store/locations";
|
||||
import getApps from "@calcom/app-store/utils";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Attendee } from "@calcom/prisma/client";
|
||||
import { createdEventSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { AdditionalInformation, CalendarEvent, NewCalendarEventType } from "@calcom/types/Calendar";
|
||||
import type {
|
||||
AdditionalInformation,
|
||||
CalendarEvent,
|
||||
NewCalendarEventType,
|
||||
Person,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
|
||||
import type { Event } from "@calcom/types/Event";
|
||||
import type {
|
||||
|
@ -294,6 +300,7 @@ export default class EventManager {
|
|||
}
|
||||
|
||||
public async updateCalendarAttendees(event: CalendarEvent, booking: PartialBooking) {
|
||||
// @NOTE: This function is only used for updating attendees on a calendar event. Can we remove this?
|
||||
await this.updateAllCalendarEvents(event, booking);
|
||||
}
|
||||
|
||||
|
@ -430,11 +437,14 @@ export default class EventManager {
|
|||
try {
|
||||
// Bookings should only have one calendar reference
|
||||
calendarReference = booking.references.filter((reference) => reference.type.includes("_calendar"))[0];
|
||||
if (!calendarReference) throw new Error("bookingRef");
|
||||
|
||||
if (!calendarReference) {
|
||||
throw new Error("bookingRef");
|
||||
}
|
||||
const { uid: bookingRefUid, externalCalendarId: bookingExternalCalendarId } = calendarReference;
|
||||
|
||||
if (!bookingExternalCalendarId) throw new Error("externalCalendarId");
|
||||
if (!bookingExternalCalendarId) {
|
||||
throw new Error("externalCalendarId");
|
||||
}
|
||||
|
||||
let result = [];
|
||||
if (calendarReference.credentialId) {
|
||||
|
@ -458,6 +468,7 @@ export default class EventManager {
|
|||
.map(async (cred) => {
|
||||
const calendarReference = booking.references.find((ref) => ref.type === cred.type);
|
||||
if (!calendarReference)
|
||||
if (!calendarReference) {
|
||||
return {
|
||||
appName: cred.appName,
|
||||
type: cred.type,
|
||||
|
@ -465,6 +476,7 @@ export default class EventManager {
|
|||
uid: "",
|
||||
originalEvent: event,
|
||||
};
|
||||
}
|
||||
const { externalCalendarId: bookingExternalCalendarId, meetingId: bookingRefUid } =
|
||||
calendarReference;
|
||||
return await updateEvent(cred, event, bookingRefUid ?? null, bookingExternalCalendarId ?? null);
|
||||
|
@ -474,7 +486,9 @@ export default class EventManager {
|
|||
return Promise.all(result);
|
||||
} catch (error) {
|
||||
let message = `Tried to 'updateAllCalendarEvents' but there was no '{thing}' for '${credential?.type}', userId: '${credential?.userId}', bookingId: '${booking?.id}'`;
|
||||
if (error instanceof Error) message = message.replace("{thing}", error.message);
|
||||
if (error instanceof Error) {
|
||||
message = message.replace("{thing}", error.message);
|
||||
}
|
||||
console.error(message);
|
||||
return Promise.resolve([
|
||||
{
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.18",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.1.0",
|
||||
"vite": "^2.9.15",
|
||||
"tailwindcss": "^3.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"eslint": "^8.22.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.1.0"
|
||||
"vite": "^2.9.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"@calcom/embed-core": "*",
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
WebhookTriggerEvents,
|
||||
} from "@prisma/client";
|
||||
import async from "async";
|
||||
import { cloneDeep } from "lodash";
|
||||
import type { NextApiRequest } from "next";
|
||||
import { RRule } from "rrule";
|
||||
import short from "short-uuid";
|
||||
|
@ -482,8 +483,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
|||
|
||||
// For seats, if the booking already exists then we want to add the new attendee to the existing booking
|
||||
if (reqBody.bookingUid) {
|
||||
if (!eventType.seatsPerTimeSlot)
|
||||
if (!eventType.seatsPerTimeSlot) {
|
||||
throw new HttpError({ statusCode: 404, message: "Event type does not have seats" });
|
||||
}
|
||||
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
|
@ -505,7 +507,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
|||
},
|
||||
},
|
||||
});
|
||||
if (!booking) throw new HttpError({ statusCode: 404, message: "Booking not found" });
|
||||
if (!booking) {
|
||||
throw new HttpError({ statusCode: 404, message: "Booking not found" });
|
||||
}
|
||||
|
||||
// Need to add translation for attendees to pass type checks. Since these values are never written to the db we can just use the new attendee language
|
||||
const bookingAttendees = booking.attendees.map((attendee) => {
|
||||
|
@ -514,11 +518,13 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
|||
|
||||
evt = { ...evt, attendees: [...bookingAttendees, invitee[0]] };
|
||||
|
||||
if (eventType.seatsPerTimeSlot <= booking.attendees.length)
|
||||
if (eventType.seatsPerTimeSlot <= booking.attendees.length) {
|
||||
throw new HttpError({ statusCode: 409, message: "Booking seats are full" });
|
||||
}
|
||||
|
||||
if (booking.attendees.some((attendee) => attendee.email === invitee[0].email))
|
||||
if (booking.attendees.find((attendee) => attendee.email === invitee[0].email)) {
|
||||
throw new HttpError({ statusCode: 409, message: "Already signed up for time slot" });
|
||||
}
|
||||
|
||||
await prisma.booking.update({
|
||||
where: {
|
||||
|
@ -537,8 +543,13 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
|||
});
|
||||
|
||||
const newSeat = booking.attendees.length !== 0;
|
||||
|
||||
await sendScheduledSeatsEmails(evt, invitee[0], newSeat, !!eventType.seatsShowAttendees);
|
||||
/**
|
||||
* Remember objects are passed into functions as references
|
||||
* so if you modify it in a inner function it will be modified in the outer function
|
||||
* deep cloning evt to avoid this
|
||||
*/
|
||||
const copyEvent = cloneDeep(evt);
|
||||
await sendScheduledSeatsEmails(copyEvent, invitee[0], newSeat, !!eventType.seatsShowAttendees);
|
||||
|
||||
const credentials = await refreshCredentials(organizerUser.credentials);
|
||||
const eventManager = new EventManager({ ...organizerUser, credentials });
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
import { CAL_URL } from "./constants";
|
||||
import { CAL_URL, LOGO } from "./constants";
|
||||
|
||||
// Ensures tw prop is typed.
|
||||
declare module "react" {
|
||||
|
@ -119,7 +119,7 @@ export const Meeting = ({ title, users = [], profile }: MeetingImageProps) => {
|
|||
<Wrapper variant="dark">
|
||||
<div tw="h-full flex flex-col justify-start">
|
||||
<div tw="flex items-center justify-center" style={{ fontFamily: "cal", fontWeight: 300 }}>
|
||||
<img src={`${CAL_URL}/cal-logo-word-black.svg`} width="350" alt="Logo" />
|
||||
<img src={`${CAL_URL}/${LOGO}`} width="350" alt="Logo" />
|
||||
{avatars.length > 0 && <div tw="font-bold text-black text-[92px] mx-8 bottom-2">/</div>}
|
||||
<div tw="flex flex-row">
|
||||
{avatars.slice(0, 3).map((avatar) => (
|
||||
|
@ -189,12 +189,7 @@ const VisualBlur = ({ visualSlug }: { visualSlug: string }) => {
|
|||
|
||||
export const App = ({ name, description, slug }: AppImageProps) => (
|
||||
<Wrapper>
|
||||
<img
|
||||
src={`${CAL_URL}/cal-logo-word-black.svg`}
|
||||
width="150"
|
||||
alt="Logo"
|
||||
tw="absolute right-[48px] top-[48px]"
|
||||
/>
|
||||
<img src={`${CAL_URL}/${LOGO}`} width="150" alt="Logo" tw="absolute right-[48px] top-[48px]" />
|
||||
|
||||
<VisualBlur visualSlug={slug} />
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
locale: user.locale,
|
||||
timeFormat: user.timeFormat,
|
||||
timeZone: user.timeZone,
|
||||
avatar: user.avatar,
|
||||
avatar: `${CAL_URL}/${user.username}/avatar.png`,
|
||||
createdDate: user.createdDate,
|
||||
trialEndsAt: user.trialEndsAt,
|
||||
completedOnboarding: user.completedOnboarding,
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { UseFormReturn } from "react-hook-form";
|
||||
import { Props } from "react-phone-number-input/react-hook-form";
|
||||
|
||||
import { EventLocationType } from "@calcom/app-store/locations";
|
||||
|
||||
import { Icon } from "../Icon";
|
||||
|
||||
type BookingFormValues = {
|
||||
name: string;
|
||||
email: string;
|
||||
notes?: string;
|
||||
locationType?: EventLocationType["type"];
|
||||
guests?: string[];
|
||||
address?: string;
|
||||
attendeeAddress?: string;
|
||||
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 | boolean;
|
||||
};
|
||||
rescheduleReason?: string;
|
||||
smsReminderNumber?: string;
|
||||
};
|
||||
|
||||
export type AddressInputProps<FormValues> = Props<
|
||||
{
|
||||
value: string;
|
||||
id: string;
|
||||
placeholder: string;
|
||||
required: boolean;
|
||||
bookingForm: UseFormReturn<BookingFormValues>;
|
||||
},
|
||||
FormValues
|
||||
>;
|
||||
|
||||
function AddressInput<FormValues>({ bookingForm, name, className, ...rest }: AddressInputProps<FormValues>) {
|
||||
return (
|
||||
<div className="relative ">
|
||||
<Icon.FiMapPin color="#D2D2D2" className="absolute top-1/2 left-0.5 ml-3 h-6 -translate-y-1/2" />
|
||||
<input
|
||||
{...rest}
|
||||
{...bookingForm.register("attendeeAddress")}
|
||||
name={name}
|
||||
color="#D2D2D2"
|
||||
className={`${className} border-1 focus-within:border-brand dark:bg-darkgray-100 dark:border-darkgray-300 block h-10 w-full rounded-md border border-gray-300 py-px pl-10 text-sm outline-none ring-black focus-within:ring-1 disabled:text-gray-500 disabled:opacity-50 dark:text-white dark:placeholder-gray-500 dark:selection:bg-green-500 disabled:dark:text-gray-500`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddressInput;
|
|
@ -0,0 +1,8 @@
|
|||
import dynamic from "next/dynamic";
|
||||
|
||||
/** These are like 40kb that not every user needs */
|
||||
const AddressInput = dynamic(
|
||||
() => import("./AddressInput")
|
||||
) as unknown as typeof import("./AddressInput").default;
|
||||
|
||||
export default AddressInput;
|
Loading…
Reference in New Issue
Block a user