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-hot-toast": "^2.1.0",
|
||||||
"react-multi-email": "^0.5.3",
|
"react-multi-email": "^0.5.3",
|
||||||
"react-phone-number-input": "^3.1.25",
|
"react-phone-number-input": "^3.1.25",
|
||||||
|
"react-query": "^3.21.0",
|
||||||
"react-select": "^4.3.1",
|
"react-select": "^4.3.1",
|
||||||
"react-timezone-select": "^1.0.7",
|
"react-timezone-select": "^1.0.7",
|
||||||
"short-uuid": "^4.2.0",
|
"short-uuid": "^4.2.0",
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
import { createTelemetryClient, TelemetryProvider } from "@lib/telemetry";
|
import AppProviders from "@lib/app-providers";
|
||||||
import { Provider } from "next-auth/client";
|
import type { AppProps as NextAppProps } from "next/app";
|
||||||
import type { AppProps } from "next/app";
|
|
||||||
import Head from "next/head";
|
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 (
|
return (
|
||||||
<TelemetryProvider value={createTelemetryClient()}>
|
<AppProviders>
|
||||||
<Provider session={pageProps.session}>
|
|
||||||
<Head>
|
<Head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
</Head>
|
</Head>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} err={err} />
|
||||||
</Provider>
|
</AppProviders>
|
||||||
</TelemetryProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getSession } from "next-auth/client";
|
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) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const session = await getSession({ req: req });
|
const session = await getSession({ req: req });
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
res.status(401).json({ message: "Not authenticated" });
|
res.status(401).json({ message: "Not authenticated" });
|
||||||
return;
|
return;
|
||||||
|
@ -61,13 +62,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (req.method == "POST") {
|
if (req.method == "POST") {
|
||||||
await prisma.eventType.create({
|
const eventType = await prisma.eventType.create({
|
||||||
data: {
|
data: {
|
||||||
userId: session.user.id,
|
userId: session.user.id,
|
||||||
...data,
|
...data,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
res.status(200).json({ message: "Event created successfully" });
|
res.status(201).json({ eventType });
|
||||||
} else if (req.method == "PATCH") {
|
} else if (req.method == "PATCH") {
|
||||||
if (req.body.timeZone) {
|
if (req.body.timeZone) {
|
||||||
data.timeZone = 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: {
|
where: {
|
||||||
id: req.body.id,
|
id: req.body.id,
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
res.status(200).json({ message: "Event updated successfully" });
|
res.status(200).json({ eventType });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method == "DELETE") {
|
if (req.method == "DELETE") {
|
||||||
// Delete associations first
|
|
||||||
await prisma.eventTypeCustomInput.deleteMany({
|
await prisma.eventTypeCustomInput.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
eventTypeId: req.body.id,
|
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 Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
@ -37,53 +36,16 @@ import { DateRangePicker, OrientationShape, toMomentObject } from "react-dates";
|
||||||
import Switch from "@components/ui/Switch";
|
import Switch from "@components/ui/Switch";
|
||||||
import { Dialog, DialogTrigger } from "@components/Dialog";
|
import { Dialog, DialogTrigger } from "@components/Dialog";
|
||||||
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
|
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";
|
import showToast from "@lib/notification";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
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 = [
|
const PERIOD_TYPES = [
|
||||||
{
|
{
|
||||||
type: "rolling",
|
type: "rolling",
|
||||||
|
@ -99,12 +61,8 @@ const PERIOD_TYPES = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function EventTypePage({
|
const EventTypePage = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => {
|
||||||
user,
|
const { user, eventType, locationOptions, availability } = props;
|
||||||
eventType,
|
|
||||||
locationOptions,
|
|
||||||
availability,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [successModalOpen, setSuccessModalOpen] = useState(false);
|
const [successModalOpen, setSuccessModalOpen] = useState(false);
|
||||||
|
|
||||||
|
@ -118,6 +76,26 @@ export default function EventTypePage({
|
||||||
const [DATE_PICKER_ORIENTATION, setDatePickerOrientation] = useState<OrientationShape>("horizontal");
|
const [DATE_PICKER_ORIENTATION, setDatePickerOrientation] = useState<OrientationShape>("horizontal");
|
||||||
const [contentSize, setContentSize] = useState({ width: 0, height: 0 });
|
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 handleResizeEvent = () => {
|
||||||
const elementWidth = parseFloat(getComputedStyle(document.body).width);
|
const elementWidth = parseFloat(getComputedStyle(document.body).width);
|
||||||
const elementHeight = parseFloat(getComputedStyle(document.body).height);
|
const elementHeight = parseFloat(getComputedStyle(document.body).height);
|
||||||
|
@ -230,31 +208,14 @@ export default function EventTypePage({
|
||||||
...advancedOptionsPayload,
|
...advancedOptionsPayload,
|
||||||
};
|
};
|
||||||
|
|
||||||
await fetch("/api/availability/eventtype", {
|
updateMutation.mutate(payload);
|
||||||
method: "PATCH",
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
router.push("/event-types");
|
|
||||||
showToast("Event Type updated", "success");
|
|
||||||
setSuccessModalOpen(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteEventTypeHandler(event) {
|
async function deleteEventTypeHandler(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
await fetch("/api/availability/eventtype", {
|
const payload = { id: eventType.id };
|
||||||
method: "DELETE",
|
deleteMutation.mutate(payload);
|
||||||
body: JSON.stringify({ id: eventType.id }),
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
showToast("Event Type deleted", "success");
|
|
||||||
router.push("/event-types");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const openLocationModal = (type: LocationType) => {
|
const openLocationModal = (type: LocationType) => {
|
||||||
|
@ -1070,9 +1031,10 @@ export default function EventTypePage({
|
||||||
</Shell>
|
</Shell>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps<Props> = async ({ req, query }) => {
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
const { req, query } = context;
|
||||||
const session = await getSession({ req });
|
const session = await getSession({ req });
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return {
|
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 Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { Fragment, useRef } from "react";
|
import React, { Fragment, useRef } from "react";
|
||||||
import Shell from "../../components/Shell";
|
import Shell from "@components/Shell";
|
||||||
import prisma from "../../lib/prisma";
|
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 [session, loading] = useSession();
|
||||||
const router = useRouter();
|
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 titleRef = useRef<HTMLInputElement>();
|
||||||
const slugRef = useRef<HTMLInputElement>();
|
const slugRef = useRef<HTMLInputElement>();
|
||||||
|
@ -39,26 +52,16 @@ export default function Availability({ user, types }) {
|
||||||
const enteredTitle = titleRef.current.value;
|
const enteredTitle = titleRef.current.value;
|
||||||
const enteredSlug = slugRef.current.value;
|
const enteredSlug = slugRef.current.value;
|
||||||
const enteredDescription = descriptionRef.current.value;
|
const enteredDescription = descriptionRef.current.value;
|
||||||
const enteredLength = lengthRef.current.value;
|
const enteredLength = parseInt(lengthRef.current.value);
|
||||||
|
|
||||||
// TODO: Add validation
|
const body = {
|
||||||
await fetch("/api/availability/eventtype", {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
title: enteredTitle,
|
title: enteredTitle,
|
||||||
slug: enteredSlug,
|
slug: enteredSlug,
|
||||||
description: enteredDescription,
|
description: enteredDescription,
|
||||||
length: enteredLength,
|
length: enteredLength,
|
||||||
}),
|
};
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (enteredTitle && enteredLength) {
|
createMutation.mutate(body);
|
||||||
await router.replace(router.asPath);
|
|
||||||
}
|
|
||||||
showToast("Event Type created", "success");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoPopulateSlug() {
|
function autoPopulateSlug() {
|
||||||
|
@ -637,10 +640,12 @@ export default function Availability({ user, types }) {
|
||||||
</Shell>
|
</Shell>
|
||||||
</div>
|
</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) {
|
if (!session) {
|
||||||
return { redirect: { permanent: false, destination: "/auth/login" } };
|
return { redirect: { permanent: false, destination: "/auth/login" } };
|
||||||
}
|
}
|
||||||
|
@ -671,7 +676,13 @@ export async function getServerSideProps(context) {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
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