Fixes eventype form (#777)
* Type fixes * Uses all integrations and session fixes on getting started page * eventtype form fixes * Update pages/event-types/[type].tsx Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
b23c032a4c
commit
7ab49acebe
6 changed files with 88 additions and 103 deletions
|
@ -2,6 +2,18 @@ export function asStringOrNull(str: unknown) {
|
|||
return typeof str === "string" ? str : null;
|
||||
}
|
||||
|
||||
export function asStringOrUndefined(str: unknown) {
|
||||
return typeof str === "string" ? str : undefined;
|
||||
}
|
||||
|
||||
export function asNumberOrUndefined(str: unknown) {
|
||||
return typeof str === "string" ? parseInt(str) : undefined;
|
||||
}
|
||||
|
||||
export function asNumberOrThrow(str: unknown) {
|
||||
return parseInt(asStringOrThrow(str));
|
||||
}
|
||||
|
||||
export function asStringOrThrow(str: unknown): string {
|
||||
const type = typeof str;
|
||||
if (type !== "string") {
|
||||
|
|
|
@ -3,8 +3,12 @@ import { EventType } from "@prisma/client";
|
|||
import * as fetch from "@lib/core/http/fetch-wrapper";
|
||||
import { EventTypeInput } from "@lib/types/event-type";
|
||||
|
||||
type EventTypeResponse = {
|
||||
eventType: EventType;
|
||||
};
|
||||
|
||||
const updateEventType = async (data: EventTypeInput) => {
|
||||
const response = await fetch.patch<EventTypeInput, EventType>("/api/availability/eventtype", data);
|
||||
const response = await fetch.patch<EventTypeInput, EventTypeResponse>("/api/availability/eventtype", data);
|
||||
return response;
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,16 @@ export type AdvancedOptions = {
|
|||
periodEndDate?: Date | string;
|
||||
periodCountCalendarDays?: boolean;
|
||||
requiresConfirmation?: boolean;
|
||||
disableGuests?: boolean;
|
||||
minimumBookingNotice?: number;
|
||||
price?: number;
|
||||
currency?: string;
|
||||
schedulingType?: SchedulingType;
|
||||
users?: {
|
||||
value: number;
|
||||
label: string;
|
||||
avatar: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type EventTypeCustomInput = {
|
||||
|
|
|
@ -89,7 +89,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
periodStartDate: req.body.periodStartDate,
|
||||
periodEndDate: req.body.periodEndDate,
|
||||
periodCountCalendarDays: req.body.periodCountCalendarDays,
|
||||
minimumBookingNotice: req.body.minimumBookingNotice,
|
||||
minimumBookingNotice: req.body.minimumBookingNotice
|
||||
? parseInt(req.body.minimumBookingNotice)
|
||||
: undefined,
|
||||
price: req.body.price,
|
||||
currency: req.body.currency,
|
||||
};
|
||||
|
|
|
@ -2,18 +2,18 @@
|
|||
import { Disclosure, RadioGroup } from "@headlessui/react";
|
||||
import { PhoneIcon, XIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
LocationMarkerIcon,
|
||||
LinkIcon,
|
||||
PlusIcon,
|
||||
DocumentIcon,
|
||||
ChevronRightIcon,
|
||||
ClockIcon,
|
||||
TrashIcon,
|
||||
DocumentIcon,
|
||||
ExternalLinkIcon,
|
||||
UsersIcon,
|
||||
LinkIcon,
|
||||
LocationMarkerIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
UserAddIcon,
|
||||
UsersIcon,
|
||||
} from "@heroicons/react/solid";
|
||||
import { EventTypeCustomInput, EventTypeCustomInputType, SchedulingType } from "@prisma/client";
|
||||
import { EventTypeCustomInput, EventTypeCustomInputType, Prisma, SchedulingType } from "@prisma/client";
|
||||
import dayjs from "dayjs";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
@ -28,9 +28,10 @@ import "react-dates/lib/css/_datepicker.css";
|
|||
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||
import { useMutation } from "react-query";
|
||||
import Select, { OptionTypeBase } from "react-select";
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { asStringOrThrow } from "@lib/asStringOrNull";
|
||||
import { StripeData } from "@ee/lib/stripe/server";
|
||||
|
||||
import { asNumberOrThrow, asNumberOrUndefined, asStringOrThrow } from "@lib/asStringOrNull";
|
||||
import { getSession } from "@lib/auth";
|
||||
import classNames from "@lib/classNames";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
|
@ -144,7 +145,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
}
|
||||
}, [contentSize]);
|
||||
|
||||
const [users, setUsers] = useState([]);
|
||||
const [users, setUsers] = useState<AdvancedOptions["users"]>([]);
|
||||
const [enteredAvailability, setEnteredAvailability] = useState();
|
||||
const [showLocationModal, setShowLocationModal] = useState(false);
|
||||
const [showAddCustomModal, setShowAddCustomModal] = useState(false);
|
||||
|
@ -184,12 +185,8 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
const [hidden, setHidden] = useState<boolean>(eventType.hidden);
|
||||
|
||||
const titleRef = useRef<HTMLInputElement>(null);
|
||||
const slugRef = useRef<HTMLInputElement>(null);
|
||||
const requiresConfirmationRef = useRef<HTMLInputElement>(null);
|
||||
const eventNameRef = useRef<HTMLInputElement>(null);
|
||||
const periodDaysRef = useRef<HTMLInputElement>(null);
|
||||
const periodDaysTypeRef = useRef<HTMLSelectElement>(null);
|
||||
const priceRef = useRef<HTMLInputElement>(null);
|
||||
const isAdvancedSettingsVisible = !!eventNameRef.current;
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedTimeZone(eventType.timeZone);
|
||||
|
@ -200,45 +197,47 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
|
||||
const formData = Object.fromEntries(new FormData(event.target).entries());
|
||||
|
||||
const enteredTitle: string = titleRef.current.value;
|
||||
const enteredSlug: string = slugRef.current.value;
|
||||
const enteredPrice = requirePayment ? Math.round(parseFloat(priceRef.current.value) * 100) : 0;
|
||||
const enteredTitle: string = titleRef.current!.value;
|
||||
|
||||
const advancedOptionsPayload: AdvancedOptions = {};
|
||||
if (requiresConfirmationRef.current) {
|
||||
advancedOptionsPayload.eventName = eventNameRef.current.value;
|
||||
advancedOptionsPayload.periodType = periodType.type;
|
||||
advancedOptionsPayload.periodDays = parseInt(periodDaysRef?.current?.value);
|
||||
advancedOptionsPayload.periodCountCalendarDays = Boolean(parseInt(periodDaysTypeRef?.current.value));
|
||||
advancedOptionsPayload.periodStartDate = periodStartDate ? periodStartDate.toDate() : null;
|
||||
advancedOptionsPayload.periodEndDate = periodEndDate ? periodEndDate.toDate() : null;
|
||||
const advancedPayload: AdvancedOptions = {};
|
||||
if (isAdvancedSettingsVisible) {
|
||||
advancedPayload.eventName = eventNameRef.current.value;
|
||||
advancedPayload.periodType = periodType?.type;
|
||||
advancedPayload.periodDays = asNumberOrUndefined(formData.periodDays);
|
||||
advancedPayload.periodCountCalendarDays = Boolean(
|
||||
asNumberOrUndefined(formData.periodCountCalendarDays)
|
||||
);
|
||||
advancedPayload.periodStartDate = periodStartDate ? periodStartDate.toDate() : undefined;
|
||||
advancedPayload.periodEndDate = periodEndDate ? periodEndDate.toDate() : undefined;
|
||||
advancedPayload.minimumBookingNotice = asNumberOrUndefined(formData.minimumBookingNotice);
|
||||
// prettier-ignore
|
||||
advancedPayload.price =
|
||||
!requirePayment ? undefined :
|
||||
formData.price ? Math.round(parseFloat(asStringOrThrow(formData.price)) * 100) :
|
||||
/* otherwise */ 0;
|
||||
advancedPayload.currency = currency;
|
||||
}
|
||||
|
||||
const payload: EventTypeInput = {
|
||||
id: eventType.id,
|
||||
title: enteredTitle,
|
||||
slug: enteredSlug,
|
||||
description: formData.description as string,
|
||||
// note(zomars) Why does this field doesnt need to be parsed...
|
||||
length: formData.length as unknown as number,
|
||||
// note(zomars) ...But this does? (Is being sent as string, despite it's a number field)
|
||||
minimumBookingNotice: parseInt(formData.minimumBookingNotice as unknown as string),
|
||||
slug: asStringOrThrow(formData.slug),
|
||||
description: asStringOrThrow(formData.description),
|
||||
length: asNumberOrThrow(formData.length),
|
||||
requiresConfirmation: formData.requiresConfirmation === "on",
|
||||
disableGuests: formData.disableGuests === "on",
|
||||
hidden,
|
||||
locations,
|
||||
customInputs,
|
||||
timeZone: selectedTimeZone,
|
||||
availability: enteredAvailability || null,
|
||||
...advancedOptionsPayload,
|
||||
availability: enteredAvailability || undefined,
|
||||
...advancedPayload,
|
||||
...(team
|
||||
? {
|
||||
schedulingType: formData.schedulingType as string,
|
||||
schedulingType: formData.schedulingType as SchedulingType,
|
||||
users,
|
||||
}
|
||||
: {}),
|
||||
price: enteredPrice,
|
||||
currency: currency,
|
||||
};
|
||||
|
||||
updateMutation.mutate(payload);
|
||||
|
@ -411,7 +410,6 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
{team ? "team/" + team.slug : eventType.users[0].username}/
|
||||
</span>
|
||||
<input
|
||||
ref={slugRef}
|
||||
type="text"
|
||||
name="slug"
|
||||
id="slug"
|
||||
|
@ -727,7 +725,6 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
</div>
|
||||
|
||||
<CheckboxField
|
||||
ref={requiresConfirmationRef}
|
||||
id="requiresConfirmation"
|
||||
name="requiresConfirmation"
|
||||
label="Opt-in booking"
|
||||
|
@ -800,7 +797,6 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
{period.type === "rolling" && (
|
||||
<div className="inline-flex">
|
||||
<input
|
||||
ref={periodDaysRef}
|
||||
type="text"
|
||||
name="periodDays"
|
||||
id=""
|
||||
|
@ -809,7 +805,6 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
defaultValue={eventType.periodDays || 30}
|
||||
/>
|
||||
<select
|
||||
ref={periodDaysTypeRef}
|
||||
id=""
|
||||
name="periodDaysType"
|
||||
className="block w-full py-2 pl-3 pr-10 text-base border-gray-300 rounded-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm"
|
||||
|
@ -924,7 +919,6 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
<div className="w-full">
|
||||
<div className="mt-1 relative rounded-sm shadow-sm">
|
||||
<input
|
||||
ref={priceRef}
|
||||
type="number"
|
||||
name="price"
|
||||
id="price"
|
||||
|
@ -1200,6 +1194,13 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
};
|
||||
}
|
||||
|
||||
const userSelect = Prisma.validator<Prisma.UserSelect>()({
|
||||
name: true,
|
||||
id: true,
|
||||
avatar: true,
|
||||
email: true,
|
||||
});
|
||||
|
||||
const eventType = await prisma.eventType.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
|
@ -1251,24 +1252,14 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
},
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
name: true,
|
||||
id: true,
|
||||
avatar: true,
|
||||
email: true,
|
||||
},
|
||||
select: userSelect,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
users: {
|
||||
select: {
|
||||
name: true,
|
||||
id: true,
|
||||
avatar: true,
|
||||
username: true,
|
||||
},
|
||||
select: userSelect,
|
||||
},
|
||||
schedulingType: true,
|
||||
userId: true,
|
||||
|
@ -1284,17 +1275,15 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
}
|
||||
|
||||
// backwards compat
|
||||
if (eventType.users.length === 0) {
|
||||
eventType.users.push(
|
||||
await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
select: {
|
||||
username: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
if (eventType.users.length === 0 && !eventType.team) {
|
||||
const fallbackUser = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
select: userSelect,
|
||||
});
|
||||
if (!fallbackUser) throw Error("The event type doesn't have user and no fallback user was found");
|
||||
eventType.users.push(fallbackUser);
|
||||
}
|
||||
|
||||
const credentials = await prisma.credential.findMany({
|
||||
|
@ -1321,7 +1310,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
locationOptions.push({ value: LocationType.GoogleMeet, label: "Google Meet" });
|
||||
}
|
||||
const currency =
|
||||
(credentials.find((integration) => integration.type === "stripe_payment")?.key as Stripe.OAuthToken)
|
||||
(credentials.find((integration) => integration.type === "stripe_payment")?.key as unknown as StripeData)
|
||||
?.default_currency || "usd";
|
||||
|
||||
if (hasIntegration(integrations, "office365_calendar")) {
|
||||
|
|
|
@ -24,7 +24,7 @@ import { getSession } from "@lib/auth";
|
|||
import AddCalDavIntegration, {
|
||||
ADD_CALDAV_INTEGRATION_FORM_TITLE,
|
||||
} from "@lib/integrations/CalDav/components/AddCalDavIntegration";
|
||||
import { validJson } from "@lib/jsonUtils";
|
||||
import getIntegrations from "@lib/integrations/getIntegrations";
|
||||
import prisma from "@lib/prisma";
|
||||
|
||||
import { Dialog, DialogClose, DialogContent, DialogHeader } from "@components/Dialog";
|
||||
|
@ -688,40 +688,7 @@ export async function getServerSideProps(context: NextPageContext) {
|
|||
},
|
||||
});
|
||||
|
||||
integrations = [
|
||||
{
|
||||
installed: !!(process.env.GOOGLE_API_CREDENTIALS && validJson(process.env.GOOGLE_API_CREDENTIALS)),
|
||||
credential: credentials.find((integration) => integration.type === "google_calendar") || null,
|
||||
type: "google_calendar",
|
||||
title: "Google Calendar",
|
||||
imageSrc: "integrations/google-calendar.svg",
|
||||
description: "Gmail, G Suite",
|
||||
},
|
||||
{
|
||||
installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET),
|
||||
credential: credentials.find((integration) => integration.type === "office365_calendar") || null,
|
||||
type: "office365_calendar",
|
||||
title: "Office 365 Calendar",
|
||||
imageSrc: "integrations/outlook.svg",
|
||||
description: "Office 365, Outlook.com, live.com, or hotmail calendar",
|
||||
},
|
||||
{
|
||||
installed: !!(process.env.ZOOM_CLIENT_ID && process.env.ZOOM_CLIENT_SECRET),
|
||||
credential: credentials.find((integration) => integration.type === "zoom_video") || null,
|
||||
type: "zoom_video",
|
||||
title: "Zoom",
|
||||
imageSrc: "integrations/zoom.svg",
|
||||
description: "Video Conferencing",
|
||||
},
|
||||
{
|
||||
installed: true,
|
||||
credential: credentials.find((integration) => integration.type === "caldav_calendar") || null,
|
||||
type: "caldav_calendar",
|
||||
title: "Caldav",
|
||||
imageSrc: "integrations/caldav.svg",
|
||||
description: "CalDav Server",
|
||||
},
|
||||
];
|
||||
integrations = getIntegrations(credentials);
|
||||
|
||||
eventTypes = await prisma.eventType.findMany({
|
||||
where: {
|
||||
|
@ -748,6 +715,7 @@ export async function getServerSideProps(context: NextPageContext) {
|
|||
|
||||
return {
|
||||
props: {
|
||||
session,
|
||||
user,
|
||||
integrations,
|
||||
eventTypes,
|
||||
|
|
Loading…
Reference in a new issue