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:
Omar López 2021-09-26 15:49:16 -06:00 committed by GitHub
parent b23c032a4c
commit 7ab49acebe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 103 deletions

View file

@ -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") {

View file

@ -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;
};

View file

@ -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 = {

View file

@ -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,
};

View file

@ -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")) {

View file

@ -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,