diff --git a/src/app/admin/one-time-event-creation/page.tsx b/src/app/admin/one-time-event-creation/page.tsx index 8c940ad..7fbada9 100644 --- a/src/app/admin/one-time-event-creation/page.tsx +++ b/src/app/admin/one-time-event-creation/page.tsx @@ -1,10 +1,75 @@ "use client"; -import { Box, Card, CardContent, TextField, Typography } from "@mui/material"; +import { + Box, + Card, + CardContent, + FormControl, + FormHelperText, + InputLabel, + MenuItem, + Select, + SelectChangeEvent, + TextField, + Typography, +} from "@mui/material"; import * as React from "react"; import SubmitFormButton from "@/components/Button/SubmitFormButton"; +const US_STATES = [ + { value: "AL", label: "AL" }, + { value: "AK", label: "AK" }, + { value: "AZ", label: "AZ" }, + { value: "AR", label: "AR" }, + { value: "CA", label: "CA" }, + { value: "CO", label: "CO" }, + { value: "CT", label: "CT" }, + { value: "DE", label: "DE" }, + { value: "FL", label: "FL" }, + { value: "GA", label: "GA" }, + { value: "HI", label: "HI" }, + { value: "ID", label: "ID" }, + { value: "IL", label: "IL" }, + { value: "IN", label: "IN" }, + { value: "IA", label: "IA" }, + { value: "KS", label: "KS" }, + { value: "KY", label: "KY" }, + { value: "LA", label: "LA" }, + { value: "ME", label: "ME" }, + { value: "MD", label: "MD" }, + { value: "MA", label: "MA" }, + { value: "MI", label: "MI" }, + { value: "MN", label: "MN" }, + { value: "MS", label: "MS" }, + { value: "MO", label: "MO" }, + { value: "MT", label: "MT" }, + { value: "NE", label: "NE" }, + { value: "NV", label: "NV" }, + { value: "NH", label: "NH" }, + { value: "NJ", label: "NJ" }, + { value: "NM", label: "NM" }, + { value: "NY", label: "NY" }, + { value: "NC", label: "NC" }, + { value: "ND", label: "ND" }, + { value: "OH", label: "OH" }, + { value: "OK", label: "OK" }, + { value: "OR", label: "OR" }, + { value: "PA", label: "PA" }, + { value: "RI", label: "RI" }, + { value: "SC", label: "SC" }, + { value: "SD", label: "SD" }, + { value: "TN", label: "TN" }, + { value: "TX", label: "TX" }, + { value: "UT", label: "UT" }, + { value: "VT", label: "VT" }, + { value: "VA", label: "VA" }, + { value: "WA", label: "WA" }, + { value: "WV", label: "WV" }, + { value: "WI", label: "WI" }, + { value: "WY", label: "WY" }, +]; + type CreateEventFormState = { title: string; eventDate: string; @@ -34,11 +99,22 @@ export default function CreateEventForm(): React.ReactElement { description: "", }); + const [errors, setErrors] = React.useState< + Partial> + >({}); + function handleChange( - e: React.ChangeEvent, + e: + | React.ChangeEvent + | SelectChangeEvent, ): void { const { name, value } = e.target; setForm((prev) => ({ ...prev, [name]: value })); + + setErrors((prev) => ({ + ...prev, + [name]: undefined, + })); } async function handleSubmit( @@ -46,6 +122,45 @@ export default function CreateEventForm(): React.ReactElement { ): Promise { e.preventDefault(); + const newErrors: Partial> = {}; + + // Required fields + if (!form.title.trim()) newErrors.title = "Title is required."; + if (!form.eventDate) newErrors.eventDate = "Event date is required."; + if (!form.startTime) newErrors.startTime = "Start time is required."; + if (!form.endTime) newErrors.endTime = "End time is required."; + if (!form.streetLine.trim()) + newErrors.streetLine = "Street address is required."; + if (!form.city.trim()) newErrors.city = "City is required."; + if (!form.state.trim()) newErrors.state = "State is required."; + if (!form.postalCode.trim()) newErrors.postalCode = "Zip is required."; + if (!form.description.trim()) + newErrors.description = "Description is required."; + + // Value-based validations + if ( + form.eventDate && + form.eventDate < new Date().toISOString().split("T")[0] + ) { + newErrors.eventDate = "Event date cannot be in the past."; + } + + if (form.startTime && form.endTime && form.startTime >= form.endTime) { + newErrors.startTime = "Start time must be before end time."; + newErrors.endTime = "End time must be after start time."; + } + + if (form.capacity && Number(form.capacity) <= 0) { + newErrors.capacity = "Capacity must be a positive number."; + } + + if (Object.keys(newErrors).length > 0) { + setErrors(newErrors); + return; + } + + setErrors({}); + const res = await fetch("/api/events", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -105,6 +220,7 @@ export default function CreateEventForm(): React.ReactElement { display: "flex", flexDirection: "column", gap: 2, + height: "2000px", }} > Create Event @@ -121,6 +237,8 @@ export default function CreateEventForm(): React.ReactElement { fullWidth value={form.title} onChange={handleChange} + error={Boolean(errors.title)} + helperText={errors.title} /> @@ -147,6 +267,8 @@ export default function CreateEventForm(): React.ReactElement { inputLabel: { shrink: true }, htmlInput: { step: 900 }, }} + error={Boolean(errors.startTime)} + helperText={errors.startTime} /> @@ -171,6 +295,8 @@ export default function CreateEventForm(): React.ReactElement { fullWidth value={form.capacity} onChange={handleChange} + error={Boolean(errors.capacity)} + helperText={errors.capacity} /> {/* Location */} @@ -185,6 +311,8 @@ export default function CreateEventForm(): React.ReactElement { fullWidth value={form.streetLine} onChange={handleChange} + error={Boolean(errors.streetLine)} + helperText={errors.streetLine} /> @@ -195,27 +323,55 @@ export default function CreateEventForm(): React.ReactElement { fullWidth value={form.city} onChange={handleChange} + sx={{ flex: 1, minWidth: 0 }} + error={Boolean(errors.city)} + helperText={errors.city} /> - - - + + State * + + {errors.state} + - {/* Description */} Description @@ -229,6 +385,8 @@ export default function CreateEventForm(): React.ReactElement { fullWidth value={form.description} onChange={handleChange} + error={Boolean(errors.description)} + helperText={errors.description} /> {/* Submit */} diff --git a/src/app/map-test/page.tsx b/src/app/map-test/page.tsx index 2356f3f..ee153fc 100644 --- a/src/app/map-test/page.tsx +++ b/src/app/map-test/page.tsx @@ -2,7 +2,9 @@ import dynamic from "next/dynamic"; -const MapComponent = dynamic(() => import("../../components/MapComp"), { ssr: false }); +const MapComponent = dynamic(() => import("../../components/MapComp"), { + ssr: false, +}); // Making it fit 30 percent of the page export default function MapTestPage(): React.ReactElement { diff --git a/src/components/MapComp.tsx b/src/components/MapComp.tsx index 3214223..9747947 100644 --- a/src/components/MapComp.tsx +++ b/src/components/MapComp.tsx @@ -1,19 +1,18 @@ "use client"; -import { useEffect, useState } from "react"; -import { MapContainer, Marker, TileLayer } from "react-leaflet"; import "leaflet/dist/leaflet.css"; import L from "leaflet"; +import { useEffect, useState } from "react"; +import { MapContainer, Marker, TileLayer } from "react-leaflet"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any delete (L.Icon.Default.prototype as any)._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png", - iconUrl: - "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png", - shadowUrl: - "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png", + iconUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png", + shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png", }); type LatitudeLongitudeStore = { lat: number; lng: number };