Skip to content

Commit

Permalink
Add event creation UI
Browse files Browse the repository at this point in the history
  • Loading branch information
mjanderson1227 committed Jan 31, 2025
1 parent 3cdec01 commit 4e27d9d
Show file tree
Hide file tree
Showing 3 changed files with 349 additions and 0 deletions.
34 changes: 34 additions & 0 deletions apps/web/src/app/admin/events/edit/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { getEventById } from "db/functions";
import { redirect } from "next/navigation";
import EditEventForm from "@/components/events/admin/EditEventForm";

export default async function EditEventPage({
params,
}: {
params: { slug: string };
}) {
const eventId = parseInt(params.slug);

if (!eventId) {
return <h1>Error: invalid event id</h1>;
}

const event = await getEventById(eventId);

if (!event) {
return redirect("/admin/events");
}

return (
<div className="mx-auto max-w-3xl pt-32">
<div className="grid grid-cols-2">
<h1 className="text-3xl font-bold tracking-tight">
Edit Event
</h1>
</div>
<div className="mt-2 rounded-xl border border-muted p-5">
<EditEventForm {...event} />
</div>
</div>
);
}
306 changes: 306 additions & 0 deletions apps/web/src/components/events/admin/EditEventForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
"use client";

import { useForm } from "react-hook-form";
import {
Form,
FormField,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
} from "@/components/shadcn/ui/form";
import {
Select,
SelectTrigger,
SelectContent,
SelectGroup,
SelectItem,
SelectValue,
} from "@/components/shadcn/ui/select";
import { Input } from "@/components/shadcn/ui/input";
import { Button } from "@/components/shadcn/ui/button";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Textarea } from "@/components/shadcn/ui/textarea";
import c from "config";
import { DateTimePicker } from "@/components/shadcn/ui/date-time-picker/date-time-picker";
import { parseAbsolute, getLocalTimeZone } from "@internationalized/date";
import { useCallback, useState } from "react";
import { useRouter } from "next/navigation";
import { ONE_HOUR_IN_MILLISECONDS } from "@/lib/constants";
import { type eventEditType } from "@/lib/types/events";
import { newEventFormSchema } from "@/validators/event";
import { ThreeCircles } from "react-loader-spinner";
import { useAction } from "next-safe-action/hooks";
import { editEvent } from "@/actions/admin/event-actions";
import { toast } from "sonner";

export default function EditEventForm({
id,
title,
description,
type,
host,
endTime,
points,
startTime,
location,
}: eventEditType & { id: number }) {
const [loading, setLoading] = useState(false);
const router = useRouter();
const userLocalTimeZone = getLocalTimeZone();
const { execute } = useAction(editEvent, {
onExecute: () => setLoading(true),
onSettled: () => setLoading(false),
onSuccess: () => {
alert("Event Edited Successfully! Redirecting to event page...");
router.push("/admin/events");
},
onError: ({ error }) => {
const description =
error.serverError ||
"An error occurred while validating the form data";

toast.error("Unable to edit event", { description });
},
});

const form = useForm<z.infer<typeof newEventFormSchema>>({
resolver: zodResolver(newEventFormSchema),
defaultValues: {
id,
title,
description,
type: type as any,
host,
startTime,
points,
location,
endTime,
},
});

const onSubmit = useCallback(
(values: z.infer<typeof newEventFormSchema>) => {
if (form.formState.isDirty) {
execute(values);
} else {
toast.error("Failed to edit event", {
description:
"Please make changes to the form before submitting it!",
});
}
},
[form.formState.isDirty],
);

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
Keep title short and concise
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="location"
render={({ field }) => (
<FormItem>
<FormLabel>Location</FormLabel>
<FormControl>
<Input
{...field}
value={field.value ?? "TBD"}
/>
</FormControl>
{/* <FormDescription>
Keep title short and concise
</FormDescription> */}
<FormMessage />
</FormItem>
)}
/>
<div className="grid grid-cols-2 gap-x-2">
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Event Type</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a Event Type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectGroup>
{Object.keys(c.eventTypes).map(
(type) => (
<SelectItem
key={type}
value={type}
>
{type}
</SelectItem>
),
)}
</SelectGroup>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="host"
render={({ field }) => (
<FormItem>
<FormLabel>Host (Optional)</FormLabel>
<FormControl>
<Input
placeholder={c.hackathonName}
{...(field as any)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="grid grid-cols-2 gap-x-2">
<FormField
control={form.control}
name="startTime"
render={({ field }) => (
<FormItem>
<FormLabel>Event Start</FormLabel>
<DateTimePicker
value={
field.value
? parseAbsolute(
field.value.toISOString(),
userLocalTimeZone,
)
: null
}
onChange={(date) => {
const newDate = date
? date.toDate(userLocalTimeZone)
: null;
field.onChange(newDate);
const isEventStartBeforeEnd =
newDate &&
newDate > form.getValues("endTime");
if (isEventStartBeforeEnd) {
form.setValue(
"endTime",
new Date(
newDate.getTime() +
ONE_HOUR_IN_MILLISECONDS,
),
);
}
}}
shouldCloseOnSelect={false}
granularity={"minute"}
label="Event Start"
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="endTime"
render={({ field }) => (
<FormItem>
<FormLabel>Event End</FormLabel>
<DateTimePicker
value={
!!field.value
? parseAbsolute(
field.value.toISOString(),
userLocalTimeZone,
)
: null
}
onChange={(date) => {
const newDate = !!date
? date.toDate(userLocalTimeZone)
: null;
field.onChange(newDate);
const isEventEndBeforeStart =
newDate &&
newDate <
form.getValues("startTime");
if (isEventEndBeforeStart) {
form.setValue(
"startTime",
new Date(
newDate.getTime() -
ONE_HOUR_IN_MILLISECONDS,
),
);
}
}}
shouldCloseOnSelect={false}
granularity={"minute"}
label="Event End"
/>
<FormMessage />
</FormItem>
)}
/>
</div>
{loading ? (
<p className="flex justify-center gap-x-1">
Updating Event{" "}
<ThreeCircles
visible={true}
height="20"
width="20"
color="#4fa94d"
ariaLabel="three-circles-loading"
wrapperStyle={{}}
wrapperClass=""
/>
</p>
) : (
<Button type="submit">Edit Event</Button>
)}
</form>
</Form>
);
}
9 changes: 9 additions & 0 deletions apps/web/src/components/events/shared/EventColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,13 @@ export const columns: ColumnDef<eventTableValidatorType>[] = [
</Link>
),
},
{
accessorKey: "Edit",
header: "Edit",
cell: ({ row }) => (
<Link href={`/admin/events/edit/${row.original.id}`}>
<Button>Edit</Button>
</Link>
),
},
];

0 comments on commit 4e27d9d

Please sign in to comment.