From 65b0f59a3dd2b835e2e6f7ea9e137f8cb121f31d Mon Sep 17 00:00:00 2001 From: Harini312821 Date: Fri, 19 Jul 2024 10:53:31 +0800 Subject: [PATCH] events page, manage events page, create events page --- client/src/App.tsx | 4 + client/src/icons.tsx | 19 +++ client/src/pages/CreateEventsPage.tsx | 170 ++++++++++++++++++++++++++ client/src/pages/EventsPage.tsx | 69 +++++++---- client/src/pages/ManageEventsPage.tsx | 101 +++++++++++++++ server/models/Events.js | 12 +- server/routes/events.js | 78 +++--------- 7 files changed, 358 insertions(+), 95 deletions(-) create mode 100644 client/src/pages/CreateEventsPage.tsx create mode 100644 client/src/pages/ManageEventsPage.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 6865441..4294c97 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -10,6 +10,8 @@ import CreatePostPage from "./pages/CreatePostPage"; import EditPostPage from "./pages/EditPostPage"; import SchedulePage from "./pages/SchedulePage"; import EventsPage from "./pages/EventsPage"; +import CreateEventsPage from "./pages/CreateEventsPage"; +import ManageEventsPage from "./pages/ManageEventsPage"; import AdministratorSpringboard from "./pages/AdministratorSpringboard"; function App() { @@ -27,6 +29,8 @@ function App() { } path="/editPost/:id" /> } path="/schedule" /> } path="/events" /> + } path="/createEvent" /> + } path="/manageEvent" /> ); } diff --git a/client/src/icons.tsx b/client/src/icons.tsx index 0579efb..47dd10c 100644 --- a/client/src/icons.tsx +++ b/client/src/icons.tsx @@ -425,3 +425,22 @@ export const BarsThreeIcon = () => { ); }; + +export const TrashIcon = () => { + return ( + + + + ); +}; \ No newline at end of file diff --git a/client/src/pages/CreateEventsPage.tsx b/client/src/pages/CreateEventsPage.tsx new file mode 100644 index 0000000..088a798 --- /dev/null +++ b/client/src/pages/CreateEventsPage.tsx @@ -0,0 +1,170 @@ +import DefaultLayout from "../layouts/default"; +import { Button } from "@nextui-org/react"; +import { Formik, Form } from "formik"; +import * as Yup from "yup"; +import axios from "axios"; +import { useNavigate } from "react-router-dom"; +import NextUIFormikInput from "../components/NextUIFormikInput"; +import NextUIFormikTextarea from "../components/NextUIFormikTextarea"; +import config from "../config"; +import { ArrowUTurnLeftIcon } from "../icons"; + +const validationSchema = Yup.object({ + title: Yup.string() + .trim() + .min(3, "Title must be at least 3 characters") + .max(200, "Title must be at most 200 characters") + .matches( + /^[a-zA-Z0-9\s]+$/, + "Title can only contain letters, numbers, and spaces" + ) + .required("Title is required"), + description: Yup.string() + .trim() + .min(3, "Description must be at least 3 characters") + .max(500, "Description must be at most 500 characters") + .matches( + /^[a-zA-Z0-9,\s!"'-]*$/, + "Only letters, numbers, commas, spaces, exclamation marks, quotations, and common symbols are allowed" + ) + .required("Description is required"), + date: Yup.date().required("Date is required"), + time: Yup.string().required("Time is required"), + location: Yup.string().required("Location is required"), + category: Yup.string().required("Category is required"), + slotsAvailable: Yup.number().integer().required("Slots Available is required"), + imageUrl: Yup.string().url("Invalid URL format").required("Image URL is required") +}); + +const CreateEventsPage = () => { + const navigate = useNavigate(); + + const initialValues = { + title: "", + description: "", + date: "", + time: "", + location: "", + category: "", + slotsAvailable: "", + imageUrl: "" + }; + + const handleSubmit = async ( + values: any, + { setSubmitting, resetForm, setFieldError }: any + ) => { + console.log("Submitting form with values:", values); // Debug log + try { + const response = await axios.post(config.serverAddress + "/events", values); + console.log("Server response:", response); // Debug log + if (response.status === 200 || response.status === 201) { + console.log("Event created successfully:", response.data); + resetForm(); // Clear form after successful submit + navigate("/manageEvent"); + } else { + console.error("Error creating event:", response.statusText); + } + } catch (error: any) { + if (error.response && error.response.data && error.response.data.field) { + setFieldError(error.response.data.field, error.response.data.error); + } else { + console.error("Unexpected error:", error); + } + } finally { + setSubmitting(false); + } + }; + + return ( + +
+ +
+
+ + {({ isValid, dirty, isSubmitting }) => ( +
+ + + + + + + + +
+ +
+ + )} +
+
+
+ ); +}; + +export default CreateEventsPage; diff --git a/client/src/pages/EventsPage.tsx b/client/src/pages/EventsPage.tsx index a0ec497..5a6b0c2 100644 --- a/client/src/pages/EventsPage.tsx +++ b/client/src/pages/EventsPage.tsx @@ -1,17 +1,26 @@ -import { useState, useEffect } from "react"; +import React, { useState, useEffect } from 'react'; import DefaultLayout from "../layouts/default"; import { useNavigate } from "react-router-dom"; import instance from "../security/http"; import config from "../config"; +import { Card, CardHeader, CardBody, CardFooter, Image, Button } from "@nextui-org/react"; const EventsPage = () => { const [events, setEvents] = useState([]); const navigate = useNavigate(); useEffect(() => { - instance.get(config.serverAddress + "/events").then((res) => { - setEvents(res.data); - }); + const fetchEvents = async () => { + try { + const res = await instance.get(config.serverAddress + "/events"); + console.log("Fetched events data:", res.data); // Log the fetched data + setEvents(res.data); + } catch (error) { + console.error("Failed to fetch events:", error); + } + }; + + fetchEvents(); }, []); return ( @@ -21,28 +30,36 @@ const EventsPage = () => {

Events

- {events.map((event) => ( -
- {event.title} -
-

{event.title}

-

{event.description}

- -
-
- ))} + {events.length === 0 ? ( +

No events available.

+ ) : ( + events.map((event) => ( + + +

{event.title}

+
+ + {event.title} + + +

{event.description}

+ +
+
+ )) + )}
diff --git a/client/src/pages/ManageEventsPage.tsx b/client/src/pages/ManageEventsPage.tsx new file mode 100644 index 0000000..09ecd3a --- /dev/null +++ b/client/src/pages/ManageEventsPage.tsx @@ -0,0 +1,101 @@ +import DefaultLayout from "../layouts/default"; +import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Avatar, Button } from "@nextui-org/react"; +import { useNavigate } from "react-router-dom"; +import { PencilSquareIcon, TrashIcon } from "../icons"; +import { useEffect, useState } from "react"; +import axios from "axios"; +import config from "../config"; + +const ManageEventsPage = () => { + const [events, setEvents] = useState([]); + const navigate = useNavigate(); + + const fetchEvents = async () => { + try { + const res = await axios.get(config.serverAddress + "/events"); + console.log("Fetched events data:", res.data); // Debug log + setEvents(res.data); + } catch (error) { + console.error("Failed to fetch events:", error); + } + }; + + useEffect(() => { + fetchEvents(); + }, []); + + const handleEdit = (id: string) => { + navigate(`/edit-event/${id}`); + }; + + const handleDelete = (id: string) => { + // Add delete functionality here + axios.delete(`${config.serverAddress}/events/${id}`).then((res) => { + setEvents(events.filter((event) => event.id !== id)); + }); + }; + + return ( + +
+

Manage Events

+
+ + + Event Name and Picture + Date + Time + Location + Description + Event Category + Actions + + + {events.map((event) => ( + + +
+ + {event.title} +
+
+ {new Date(event.date).toLocaleDateString()} + {event.time} + {event.location} + {event.description} + {event.category} + +
+ + +
+
+
+ ))} +
+
+ +
+ ); +}; + +export default ManageEventsPage; diff --git a/server/models/Events.js b/server/models/Events.js index c604912..1b4690f 100644 --- a/server/models/Events.js +++ b/server/models/Events.js @@ -1,4 +1,7 @@ -module.exports = (sequelize, DataTypes) => { +const { DataTypes } = require('sequelize'); + + +module.exports = (sequelize) => { const Events = sequelize.define("Events", { title: { type: DataTypes.STRING, @@ -25,8 +28,8 @@ module.exports = (sequelize, DataTypes) => { allowNull: false, }, Events: { - type: DataTypes.BLOB("long"), - allowNull: true, + type: DataTypes.BLOB("long"), + allowNull: true, }, slotsAvailable: { type: DataTypes.INTEGER, @@ -36,6 +39,5 @@ module.exports = (sequelize, DataTypes) => { tableName: "events", timestamps: true, }); - return Events; -}; \ No newline at end of file +} diff --git a/server/routes/events.js b/server/routes/events.js index 58ba6a0..c9e343a 100644 --- a/server/routes/events.js +++ b/server/routes/events.js @@ -15,8 +15,8 @@ router.post("/", async (req, res) => { time: yup.string().required(), location: yup.string().required(), category: yup.string().required(), - imageUrl: yup.string().matches(/(\/uploads\/.+)/, 'Image URL must be a valid path').required(), - slotsAvailable: yup.number().integer().required() + imageUrl: yup.string(), + slotsAvailable: yup.number().integer().required(), }); try { @@ -32,68 +32,18 @@ router.post("/", async (req, res) => { }); const upload = multer({ storage: multer.memoryStorage() }); - -router.put( - "/:id", - upload.single("image"), - async (req, res) => { - const id = req.params.id; - // Check if file is uploaded - if (!req.file) { - return res.status(400).json({ message: "No file uploaded" }); - } - - try { - const { buffer, mimetype, size } = req.file; - - // Validate file type and size (example: max 5MB, only images) - const allowedTypes = ["image/jpeg", "image/png", "image/gif"]; - const maxSize = 5 * 1024 * 1024; // 5MB - - if (!allowedTypes.includes(mimetype)) { - return res.status(400).json({ - message: - "Invalid file type\nSupported: jpeg, png, gif\nUploaded: " + - mimetype.substring(6), - }); - } - - if (size > maxSize) { - return res.status(400).json({ - message: - "File too large!\nMaximum: 5MB, Uploaded: " + - (size / 1000000).toFixed(2) + - "MB", - }); - } - - // Crop the image to a square - const croppedBuffer = await sharp(buffer) - .resize({ width: 512, height: 512, fit: sharp.fit.cover }) // Adjust size as necessary - .toBuffer(); - - await User.update( - { Events: croppedBuffer }, - { where: { id: id } } - ); - - res.json({ message: "Event image successfully uploaded." }); - } catch (err) { - res - .status(500) - .json({ message: "Internal server error", errors: err.errors }); - } - } - ); - - -router.get("/", async (req, res) => { - const list = await Events.findAll({ + router.get("/", async (req, res) => { + try { + const list = await Events.findAll({ order: [['createdAt', 'DESC']], - }); - res.json(list); -}); + }); + res.json(list); + } catch (error) { + console.error("Error fetching events:", error); + res.status(500).json({ message: "Internal Server Error" }); + } + }); router.get("/:id", async (req, res) => { const id = req.params.id; @@ -115,8 +65,8 @@ router.put("/:id", async (req, res) => { time: yup.string(), location: yup.string(), category: yup.string(), - imageUrl: yup.string().matches(/(\/uploads\/.+)/, 'Image URL must be a valid path'), - slotsAvailable: yup.number().integer() + imageUrl: yup.string(), + slotsAvailable: yup.number().integer(), }); try {