feat: add react-query and navigate to edit after event-type creation (#528)
* feat: add react-query and navigate to edit after event-type creation * fix: add types/toasts and add react-query mutations on event-types Co-authored-by: Mihai Colceriu <colceriumi@gmail.com>
This commit is contained in:
parent
a44bc63304
commit
fc50821282
11 changed files with 287 additions and 1053 deletions
21
lib/app-providers.tsx
Normal file
21
lib/app-providers.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from "react";
|
||||
import { createTelemetryClient, TelemetryProvider } from "@lib/telemetry";
|
||||
import { Provider } from "next-auth/client";
|
||||
import { QueryClient, QueryClientProvider } from "react-query";
|
||||
import { Hydrate } from "react-query/hydration";
|
||||
|
||||
export const queryClient = new QueryClient();
|
||||
|
||||
const AppProviders: React.FC = (props, pageProps) => {
|
||||
return (
|
||||
<TelemetryProvider value={createTelemetryClient()}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Hydrate state={pageProps.dehydratedState}>
|
||||
<Provider session={pageProps.session}>{props.children}</Provider>
|
||||
</Hydrate>
|
||||
</QueryClientProvider>
|
||||
</TelemetryProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppProviders;
|
19
lib/mutations/event-types/create-event-type.ts
Normal file
19
lib/mutations/event-types/create-event-type.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { CreateEventType } from "@lib/types/event-type";
|
||||
|
||||
const createEventType = async (data: CreateEventType) => {
|
||||
const response = await fetch("/api/availability/eventtype", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export default createEventType;
|
17
lib/mutations/event-types/delete-event-type.ts
Normal file
17
lib/mutations/event-types/delete-event-type.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
const deleteEventType = async (data: { id: number }) => {
|
||||
const response = await fetch("/api/availability/eventtype", {
|
||||
method: "DELETE",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export default deleteEventType;
|
19
lib/mutations/event-types/update-event-type.ts
Normal file
19
lib/mutations/event-types/update-event-type.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { EventTypeInput } from "@lib/types/event-type";
|
||||
|
||||
const updateEventType = async (data: EventTypeInput) => {
|
||||
const response = await fetch("/api/availability/eventtype", {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export default updateEventType;
|
49
lib/types/event-type.ts
Normal file
49
lib/types/event-type.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
export type OpeningHours = {
|
||||
days: number[];
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
};
|
||||
|
||||
export type DateOverride = {
|
||||
date: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
};
|
||||
|
||||
export type AdvancedOptions = {
|
||||
eventName?: string;
|
||||
periodType?: string;
|
||||
periodDays?: number;
|
||||
periodStartDate?: Date | string;
|
||||
periodEndDate?: Date | string;
|
||||
periodCountCalendarDays?: boolean;
|
||||
requiresConfirmation?: boolean;
|
||||
};
|
||||
|
||||
export type EventTypeCustomInput = {
|
||||
id: number;
|
||||
label: string;
|
||||
placeholder: string;
|
||||
required: boolean;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type CreateEventType = {
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
length: number;
|
||||
};
|
||||
|
||||
export type EventTypeInput = AdvancedOptions & {
|
||||
id: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
length: number;
|
||||
hidden: boolean;
|
||||
locations: unknown;
|
||||
customInputs: EventTypeCustomInput[];
|
||||
timeZone: string;
|
||||
availability?: { openingHours: OpeningHours[]; dateOverrides: DateOverride[] };
|
||||
};
|
|
@ -51,6 +51,7 @@
|
|||
"react-hot-toast": "^2.1.0",
|
||||
"react-multi-email": "^0.5.3",
|
||||
"react-phone-number-input": "^3.1.25",
|
||||
"react-query": "^3.21.0",
|
||||
"react-select": "^4.3.1",
|
||||
"react-timezone-select": "^1.0.7",
|
||||
"short-uuid": "^4.2.0",
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import "../styles/globals.css";
|
||||
import { createTelemetryClient, TelemetryProvider } from "@lib/telemetry";
|
||||
import { Provider } from "next-auth/client";
|
||||
import type { AppProps } from "next/app";
|
||||
import AppProviders from "@lib/app-providers";
|
||||
import type { AppProps as NextAppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
// Workaround for https://github.com/zeit/next.js/issues/8592
|
||||
export type AppProps = NextAppProps & {
|
||||
/** Will be defined only is there was an error */
|
||||
err?: Error;
|
||||
};
|
||||
|
||||
function MyApp({ Component, pageProps, err }: AppProps) {
|
||||
return (
|
||||
<TelemetryProvider value={createTelemetryClient()}>
|
||||
<Provider session={pageProps.session}>
|
||||
<AppProviders>
|
||||
<Head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</Head>
|
||||
<Component {...pageProps} />
|
||||
</Provider>
|
||||
</TelemetryProvider>
|
||||
<Component {...pageProps} err={err} />
|
||||
</AppProviders>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getSession } from "next-auth/client";
|
||||
import prisma from "../../../lib/prisma";
|
||||
import prisma from "@lib/prisma";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getSession({ req: req });
|
||||
|
||||
if (!session) {
|
||||
res.status(401).json({ message: "Not authenticated" });
|
||||
return;
|
||||
|
@ -61,13 +62,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
};
|
||||
|
||||
if (req.method == "POST") {
|
||||
await prisma.eventType.create({
|
||||
const eventType = await prisma.eventType.create({
|
||||
data: {
|
||||
userId: session.user.id,
|
||||
...data,
|
||||
},
|
||||
});
|
||||
res.status(200).json({ message: "Event created successfully" });
|
||||
res.status(201).json({ eventType });
|
||||
} else if (req.method == "PATCH") {
|
||||
if (req.body.timeZone) {
|
||||
data.timeZone = req.body.timeZone;
|
||||
|
@ -98,18 +99,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
});
|
||||
}
|
||||
|
||||
await prisma.eventType.update({
|
||||
const eventType = await prisma.eventType.update({
|
||||
where: {
|
||||
id: req.body.id,
|
||||
},
|
||||
data,
|
||||
});
|
||||
res.status(200).json({ message: "Event updated successfully" });
|
||||
res.status(200).json({ eventType });
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method == "DELETE") {
|
||||
// Delete associations first
|
||||
await prisma.eventTypeCustomInput.deleteMany({
|
||||
where: {
|
||||
eventTypeId: req.body.id,
|
||||
|
@ -122,6 +122,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
},
|
||||
});
|
||||
|
||||
res.status(200).json({ message: "Event deleted successfully" });
|
||||
res.status(200).json({});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { GetServerSideProps } from "next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
@ -37,53 +36,16 @@ import { DateRangePicker, OrientationShape, toMomentObject } from "react-dates";
|
|||
import Switch from "@components/ui/Switch";
|
||||
import { Dialog, DialogTrigger } from "@components/Dialog";
|
||||
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
|
||||
import { GetServerSidePropsContext, InferGetServerSidePropsType } from "next";
|
||||
import { useMutation } from "react-query";
|
||||
import { EventTypeInput } from "@lib/types/event-type";
|
||||
import updateEventType from "@lib/mutations/event-types/update-event-type";
|
||||
import deleteEventType from "@lib/mutations/event-types/delete-event-type";
|
||||
import showToast from "@lib/notification";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
eventType: EventType;
|
||||
locationOptions: OptionBase[];
|
||||
availability: Availability[];
|
||||
};
|
||||
|
||||
type OpeningHours = {
|
||||
days: number[];
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
};
|
||||
|
||||
type DateOverride = {
|
||||
date: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
};
|
||||
|
||||
type AdvancedOptions = {
|
||||
eventName?: string;
|
||||
periodType?: string;
|
||||
periodDays?: number;
|
||||
periodStartDate?: Date | string;
|
||||
periodEndDate?: Date | string;
|
||||
periodCountCalendarDays?: boolean;
|
||||
requiresConfirmation?: boolean;
|
||||
};
|
||||
|
||||
type EventTypeInput = AdvancedOptions & {
|
||||
id: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
length: number;
|
||||
hidden: boolean;
|
||||
locations: unknown;
|
||||
customInputs: EventTypeCustomInput[];
|
||||
timeZone: string;
|
||||
availability?: { openingHours: OpeningHours[]; dateOverrides: DateOverride[] };
|
||||
};
|
||||
|
||||
const PERIOD_TYPES = [
|
||||
{
|
||||
type: "rolling",
|
||||
|
@ -99,12 +61,8 @@ const PERIOD_TYPES = [
|
|||
},
|
||||
];
|
||||
|
||||
export default function EventTypePage({
|
||||
user,
|
||||
eventType,
|
||||
locationOptions,
|
||||
availability,
|
||||
}: Props): JSX.Element {
|
||||
const EventTypePage = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => {
|
||||
const { user, eventType, locationOptions, availability } = props;
|
||||
const router = useRouter();
|
||||
const [successModalOpen, setSuccessModalOpen] = useState(false);
|
||||
|
||||
|
@ -118,6 +76,26 @@ export default function EventTypePage({
|
|||
const [DATE_PICKER_ORIENTATION, setDatePickerOrientation] = useState<OrientationShape>("horizontal");
|
||||
const [contentSize, setContentSize] = useState({ width: 0, height: 0 });
|
||||
|
||||
const updateMutation = useMutation(updateEventType, {
|
||||
onSuccess: async ({ eventType }) => {
|
||||
await router.push("/event-types");
|
||||
showToast(`${eventType.title} event type updated successfully`, "success");
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
showToast(err.message, "error");
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation(deleteEventType, {
|
||||
onSuccess: async () => {
|
||||
await router.push("/event-types");
|
||||
showToast("Event type deleted successfully", "success");
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
showToast(err.message, "error");
|
||||
},
|
||||
});
|
||||
|
||||
const handleResizeEvent = () => {
|
||||
const elementWidth = parseFloat(getComputedStyle(document.body).width);
|
||||
const elementHeight = parseFloat(getComputedStyle(document.body).height);
|
||||
|
@ -230,31 +208,14 @@ export default function EventTypePage({
|
|||
...advancedOptionsPayload,
|
||||
};
|
||||
|
||||
await fetch("/api/availability/eventtype", {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(payload),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
router.push("/event-types");
|
||||
showToast("Event Type updated", "success");
|
||||
setSuccessModalOpen(true);
|
||||
updateMutation.mutate(payload);
|
||||
}
|
||||
|
||||
async function deleteEventTypeHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
await fetch("/api/availability/eventtype", {
|
||||
method: "DELETE",
|
||||
body: JSON.stringify({ id: eventType.id }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
showToast("Event Type deleted", "success");
|
||||
router.push("/event-types");
|
||||
const payload = { id: eventType.id };
|
||||
deleteMutation.mutate(payload);
|
||||
}
|
||||
|
||||
const openLocationModal = (type: LocationType) => {
|
||||
|
@ -1070,9 +1031,10 @@ export default function EventTypePage({
|
|||
</Shell>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async ({ req, query }) => {
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const { req, query } = context;
|
||||
const session = await getSession({ req });
|
||||
if (!session) {
|
||||
return {
|
||||
|
@ -1208,3 +1170,5 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ req, query
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default EventTypePage;
|
||||
|
|
|
@ -19,12 +19,25 @@ import Head from "next/head";
|
|||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { Fragment, useRef } from "react";
|
||||
import Shell from "../../components/Shell";
|
||||
import prisma from "../../lib/prisma";
|
||||
import Shell from "@components/Shell";
|
||||
import prisma from "@lib/prisma";
|
||||
import { GetServerSidePropsContext, InferGetServerSidePropsType } from "next";
|
||||
import { useMutation } from "react-query";
|
||||
import createEventType from "@lib/mutations/event-types/create-event-type";
|
||||
|
||||
export default function Availability({ user, types }) {
|
||||
const EventTypesPage = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => {
|
||||
const { user, types } = props;
|
||||
const [session, loading] = useSession();
|
||||
const router = useRouter();
|
||||
const createMutation = useMutation(createEventType, {
|
||||
onSuccess: async ({ eventType }) => {
|
||||
await router.replace("/event-types/" + eventType.id);
|
||||
showToast(`${eventType.title} event type created successfully`, "success");
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
showToast(err.message, "error");
|
||||
},
|
||||
});
|
||||
|
||||
const titleRef = useRef<HTMLInputElement>();
|
||||
const slugRef = useRef<HTMLInputElement>();
|
||||
|
@ -39,26 +52,16 @@ export default function Availability({ user, types }) {
|
|||
const enteredTitle = titleRef.current.value;
|
||||
const enteredSlug = slugRef.current.value;
|
||||
const enteredDescription = descriptionRef.current.value;
|
||||
const enteredLength = lengthRef.current.value;
|
||||
const enteredLength = parseInt(lengthRef.current.value);
|
||||
|
||||
// TODO: Add validation
|
||||
await fetch("/api/availability/eventtype", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
const body = {
|
||||
title: enteredTitle,
|
||||
slug: enteredSlug,
|
||||
description: enteredDescription,
|
||||
length: enteredLength,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (enteredTitle && enteredLength) {
|
||||
await router.replace(router.asPath);
|
||||
}
|
||||
showToast("Event Type created", "success");
|
||||
createMutation.mutate(body);
|
||||
}
|
||||
|
||||
function autoPopulateSlug() {
|
||||
|
@ -637,10 +640,12 @@ export default function Availability({ user, types }) {
|
|||
</Shell>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const { req } = context;
|
||||
const session = await getSession({ req });
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: "/auth/login" } };
|
||||
}
|
||||
|
@ -671,7 +676,13 @@ export async function getServerSideProps(context) {
|
|||
hidden: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
props: { user, types }, // will be passed to the page component as props
|
||||
props: {
|
||||
user,
|
||||
types,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default EventTypesPage;
|
||||
|
|
Loading…
Reference in a new issue