From 038d6f4544d631bdc4b42b23399316e13e0e4e53 Mon Sep 17 00:00:00 2001 From: Aditya raj Date: Thu, 27 Jun 2024 12:03:06 +0530 Subject: [PATCH] add calendar server actions, UI, home page --- src/app/actions/EventActions.js | 167 +++++++++++++++++ src/app/actions/EventData.js | 42 +++++ src/app/dashboard/events/page.jsx | 35 +++- src/app/page.jsx | 4 +- src/components/Calendar.jsx | 90 ---------- src/components/Events/Calendar.jsx | 85 +++++++++ src/components/Events/create-modal.jsx | 157 ++++++++++++++++ src/components/Events/view-update-modal.jsx | 187 ++++++++++++++++++++ src/components/Home/OurProjects.jsx | 29 --- src/components/Home/OurSchedule.jsx | 34 ++++ src/components/ui/DashboardHome.jsx | 11 +- src/components/ui/EventDetailDialog.jsx | 35 ++++ src/components/ui/MonthCalendar.jsx | 78 ++++++++ src/models/events.js | 20 +++ 14 files changed, 845 insertions(+), 129 deletions(-) create mode 100644 src/app/actions/EventActions.js create mode 100644 src/app/actions/EventData.js delete mode 100644 src/components/Calendar.jsx create mode 100644 src/components/Events/Calendar.jsx create mode 100644 src/components/Events/create-modal.jsx create mode 100644 src/components/Events/view-update-modal.jsx create mode 100644 src/components/Home/OurSchedule.jsx create mode 100644 src/components/ui/EventDetailDialog.jsx create mode 100644 src/components/ui/MonthCalendar.jsx create mode 100644 src/models/events.js diff --git a/src/app/actions/EventActions.js b/src/app/actions/EventActions.js new file mode 100644 index 0000000..cea9187 --- /dev/null +++ b/src/app/actions/EventActions.js @@ -0,0 +1,167 @@ +"use server"; +import { z } from "zod"; +import connectMongoDB from "@/lib/db"; +import Event from "@/models/events"; +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; +import { auth } from "@/auth"; + +// Define the schema for event validation +const EventSchema = z.object({ + date: z.number().min(1, "Date is required."), + title: z.string().min(1, "Title is required."), + venue: z.string().min(1, "Venue is required."), + time: z.string().min(1, "Time is required."), + about: z.string().min(1, "About is required."), +}); + +// Server action to add an event +export async function addEvent(prevState, formData) { + const session = await auth(); + const club = session?.user.email.split("@")[0]; + + const validatedFields = EventSchema.safeParse({ + date: parseInt(formData.get("date")), + title: formData.get("title"), + venue: formData.get("venue"), + time: formData.get("time"), // Updated from timing to time + about: formData.get("about"), + }); + + // If form validation fails, return errors early. Otherwise, continue. + if (!validatedFields.success) { + return { + errors: validatedFields.error.flatten().fieldErrors, + message: "Missing or invalid fields. Failed to add event.", + }; + } + + // Extract validated data + const { date, title, venue, time, about } = validatedFields.data; + const eventObject = { + date, + title, + venue, + time, + about, + }; + + // Insert data into the database + try { + await connectMongoDB(); + const event = await Event.findOne({ club }); + + if (event) { + event.events.push(eventObject); + await event.save(); + } else { + await Event.create({ + events: [eventObject], + club, + }); + } + } catch (error) { + // If a database error occurs, return a more specific error. + return { + message: "Database Error: Failed to add event.", + }; + } + + // Revalidate the cache for the events page and redirect the user. + revalidatePath("/dashboard/events"); +} + +export async function updateEvent(prevState, formData) { + // Get the user's session and club + const session = await auth(); + const club = session?.user.email.split("@")[0]; + + // Extract the event index and updated fields from form data + + const validatedFields = EventSchema.safeParse({ + date: parseInt(formData.get("date")), + title: formData.get("title"), + venue: formData.get("venue"), + time: formData.get("time"), + about: formData.get("about"), + }); + if (!validatedFields.success) { + return { + errors: validatedFields.error.flatten().fieldErrors, + message: "Missing or invalid fields. Failed to add event.", + }; + } + + // Extract validated data + const { title, venue, time, about, date } = validatedFields.data; + const updatedEventObj = { + date, + title, + about, + venue, + time, + }; + + try { + await connectMongoDB(); // Connect to the database + // Find the event for the specified club + const event = await Event.findOne({ club }); + + const foundEventObjectIndex = event.events.findIndex( + (e) => e.date === date + ); + + event.events[foundEventObjectIndex] = updatedEventObj; + + // Save the updated event + await event.save(); + } catch (error) { + return { + message: "Database Error: Failed to update event.", + status: 500, + }; + } + + revalidatePath("/dashboard/events"); +} + +export async function deleteEventByDate(date) { + const session = await auth(); + const club = session?.user.email.split("@")[0]; + + try { + await connectMongoDB(); + const event = await Event.findOne({ club }); + + if (!event) { + return { + message: "Event not found for the club", + status: 404, + }; + } + + // Filter out the event at the specified index + const updatedEvents = event.events.filter((event) => event.date !== date); + + // If the event to delete was not found + if (updatedEvents.length === event.events.length) { + return { + message: "Event not found", + status: 404, + }; + } + + event.events = updatedEvents; + await event.save(); + } catch (error) { + console.error("Error deleting event:", error); + return { + message: "Database Error: Failed to delete event.", + status: 500, + }; + } + + // Revalidate the cache for the events page + revalidatePath("/dashboard/events"); + redirect("/dashboard/events"); +} diff --git a/src/app/actions/EventData.js b/src/app/actions/EventData.js new file mode 100644 index 0000000..c91c6f8 --- /dev/null +++ b/src/app/actions/EventData.js @@ -0,0 +1,42 @@ +import Event from "@/models/events"; +import connectMongoDB from "@/lib/db"; +import { unstable_noStore as noStore } from "next/cache"; + +export async function getEventsForClub(club) { + noStore(); // Ensure no caching is done + try { + await connectMongoDB(); + const eventRecord = await Event.findOne({ club }); + + if (!eventRecord) { + return []; + } + return eventRecord.events; + } catch (error) { + return { + message: "Database Error: Failed to retrieve events.", + }; + } +} + +// Server action to get events for a club on a specific date +export async function getEventsForClubAndDate(club, date) { + noStore(); // Ensure no caching is done + try { + await connectMongoDB(); + const eventRecord = await Event.findOne({ club }); + + if (!eventRecord) { + return {}; + } + + const foundEventObject = eventRecord.events.findOne((e) => e.date === date); + console.log("founded", foundEventObject); + + return foundEventObject; + } catch (error) { + return { + message: "Database Error: Failed to retrieve events.", + }; + } +} diff --git a/src/app/dashboard/events/page.jsx b/src/app/dashboard/events/page.jsx index bef1171..b092152 100644 --- a/src/app/dashboard/events/page.jsx +++ b/src/app/dashboard/events/page.jsx @@ -1,19 +1,38 @@ -import React from "react"; -import { MonthCalendar } from "@/components/Calendar"; +import { MonthCalendar } from "@/components/Events/Calendar"; +import { auth } from "@/auth"; +import { getEventsForClub } from "@/app/actions/EventData"; -const Page = () => { +const Page = async () => { const monthNames = [ - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", ]; const month = new Date().getMonth(); // Call the function to get the month - const year= new Date().getFullYear() + const year = new Date().getFullYear(); const monthName = monthNames[month]; + const session = await auth(); + const club = session?.user.email.split("@")[0]; + const events = await getEventsForClub(club); + // console.log(events) return ( <> -

{monthName} {year} Calendar

- +
+

+ {monthName} {year} +

+ +
); }; diff --git a/src/app/page.jsx b/src/app/page.jsx index c86bb0f..4cb85d6 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -1,8 +1,9 @@ import Achievements from "@/components/Home/Achievements"; +import OurSchedule from "@/components/Home/OurSchedule"; import Gallery from "@/components/Home/Gallery"; import OurTeam from "@/components/Home/OurTeam"; import OurProjects from "@/components/Home/OurProjects"; -import GalleryCarousel from "@/components/Home/Carousel"; +// import GalleryCarousel from "@/components/Home/Carousel"; import Hero from "@/components/Home/Hero"; import OurBlogs from "@/components/Home/OurBlogs"; @@ -11,6 +12,7 @@ export default function Home() { return ( <> + diff --git a/src/components/Calendar.jsx b/src/components/Calendar.jsx deleted file mode 100644 index 77c0205..0000000 --- a/src/components/Calendar.jsx +++ /dev/null @@ -1,90 +0,0 @@ -'use client'; -import React, { useState } from 'react'; -import { DayPicker } from 'react-day-picker'; -import 'react-day-picker/dist/style.css'; -import { - AlertDialog, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui/alert-dialog'; - -const events = [ - { date: 19, title: 'Title 19', about: 'About the event on 19th', venue: 'Venue 19' }, - { date: 25, title: 'Title 25', about: 'About the event on 25th', venue: 'Venue 25' }, - // Add more events as needed -]; - -/** Replace the 19th with an emoji */ -function CustomDayContent(props) { - const event = events.find((event) => event.date === props.date.getDate()); - return ( - - {event ? ( -
- {event.date} -
- ) : ( - props.date.getDate() - )} -
- ); -} -function CustomCaptionComponent(props) { - return null; -} - -export function MonthCalendar() { - const [selectedEvent, setSelectedEvent] = useState(null); - const [open, setOpen] = useState(false); - - const handleDayClick = (day) => { - const dayNumber = day.getDate(); - const event = events.find((event) => event.date === dayNumber); - if (event) { - setSelectedEvent(event); - setOpen(true); - } else { - setSelectedEvent(null); - setOpen(false); - } - }; - - return ( - <> - - {selectedEvent && ( - - )} - - ); -} - -export function AlertDialogDemo({ event, open, onOpenChange }) { - return ( - - - - {event.title} - -

About: {event.about}

-

Venue: {event.venue}

-
-
- - Close - -
-
- ); -} diff --git a/src/components/Events/Calendar.jsx b/src/components/Events/Calendar.jsx new file mode 100644 index 0000000..2fe9253 --- /dev/null +++ b/src/components/Events/Calendar.jsx @@ -0,0 +1,85 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { DayPicker } from "react-day-picker"; +import "react-day-picker/dist/style.css"; + +import { CreateEventModal } from "./create-modal"; +import { ViewUpdateEventModal } from "./view-update-modal"; + +function CustomDayContent({ date, eventDates }) { + const isEventDay = eventDates.includes(date.getDate()); + return ( + + {isEventDay ? ( +
+ {date.getDate()} +
+ ) : ( + date.getDate() + )} +
+ ); +} + +function CustomCaptionComponent(props) { + return null; +} + +export function MonthCalendar({ serializedEvents, club }) { + const [open, setOpen] = useState(false); + const [eventDaySelected, setEventDaySelected] = useState(false); + const [selectedDate, setSelectedDate] = useState(null); + const [selectedEvent, setSelectedEvent] = useState(null); + const events = JSON.parse(serializedEvents); + const eventDates = events.map((e) => e.date); + useEffect(() => { + setOpen(false); + }, [serializedEvents]); + + const handleDayClick = (day) => { + const dayNumber = day.getDate(); + setSelectedDate(dayNumber); + const isEventDay = eventDates.includes(dayNumber); + + if (isEventDay) { + setEventDaySelected(true); + const eventObj = events.find((e) => e.date === dayNumber); + setSelectedEvent(eventObj); + } else { + setEventDaySelected(false); + setSelectedEvent(null); + } + setOpen(true); + }; + + return ( + <> + ( + + ), + Caption: CustomCaptionComponent, + }} + className="text-xl" + /> + {eventDaySelected && open && ( + + )} + {!eventDaySelected && open && ( + + )} + + ); +} diff --git a/src/components/Events/create-modal.jsx b/src/components/Events/create-modal.jsx new file mode 100644 index 0000000..43befb6 --- /dev/null +++ b/src/components/Events/create-modal.jsx @@ -0,0 +1,157 @@ +"use client" +import React from "react"; +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { useFormState } from "react-dom"; +import { addEvent } from "@/app/actions/EventActions"; // Assume you have an action to create an event + +export function CreateEventModal({ Date, open, onOpenChange }) { + const initialState = { message: null, errors: {} }; + const [state, dispatch] = useFormState(addEvent, initialState); + + const handleSubmit = (event) => { + event.preventDefault(); + const formData = new FormData(event.target); + formData.set("date", Date); // Set the date in the form data + dispatch(formData); + }; + + return ( + + + + Create Event on {Date} + +
+ {/* Title */} +
+ + +
+ {state?.errors?.title && + state.errors.title.map((error) => ( +

+ {error} +

+ ))} +
+
+ + {/* About */} +
+ + +
+ {state?.errors?.about && + state.errors.about.map((error) => ( +

+ {error} +

+ ))} +
+
+ + {/* Venue */} +
+ + +
+ {state?.errors?.venue && + state.errors.venue.map((error) => ( +

+ {error} +

+ ))} +
+
+ + {/* Time */} +
+ + +
+ {state?.errors?.time && + state.errors.time.map((error) => ( +

+ {error} +

+ ))} +
+
+ + + Close + + +
+
+
+
+ ); +} diff --git a/src/components/Events/view-update-modal.jsx b/src/components/Events/view-update-modal.jsx new file mode 100644 index 0000000..04ffa36 --- /dev/null +++ b/src/components/Events/view-update-modal.jsx @@ -0,0 +1,187 @@ +"use client"; +import React, { useState } from "react"; +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { useFormState } from "react-dom"; +import { updateEvent, deleteEventByDate } from "@/app/actions/EventActions"; // Assume you have an action to create an event + +export function ViewUpdateEventModal({ + Date, + open, + onOpenChange, + event, + club, +}) { + const initialState = { message: null, errors: {} }; + const [eventDetails, setEventDetails] = useState(event); + const [state, dispatch] = useFormState(updateEvent, initialState); + + const handleSubmit = (event) => { + event.preventDefault(); + const formData = new FormData(event.target); + formData.set("date", Date); // Set the date in the form data + console.log("about to dispatch"); + dispatch(formData); + }; + + const handleDelete = async () => { + try { + await deleteEventByDate(Date); + onOpenChange(false); // Close the modal after deletion + } catch (error) { + console.error("Failed to delete event:", error); + } + }; + + return ( + + + + Update Event on {Date} + +
+ {/* Title */} +
+ + +
+ {state?.errors?.title && + state.errors.title.map((error) => ( +

+ {error} +

+ ))} +
+
+ + {/* About */} +
+ + +
+ {state?.errors?.about && + state.errors.about.map((error) => ( +

+ {error} +

+ ))} +
+
+ + {/* Venue */} +
+ + +
+ {state?.errors?.venue && + state.errors.venue.map((error) => ( +

+ {error} +

+ ))} +
+
+ + {/* Time */} +
+ + +
+ {state?.errors?.time && + state.errors.time.map((error) => ( +

+ {error} +

+ ))} +
+
+ + + Close + + + +
+
+
+
+ ); +} diff --git a/src/components/Home/OurProjects.jsx b/src/components/Home/OurProjects.jsx index 9354e59..8a50028 100644 --- a/src/components/Home/OurProjects.jsx +++ b/src/components/Home/OurProjects.jsx @@ -2,35 +2,6 @@ import React from "react"; import ProjectCard from "../ui/ProjectCard"; import { getAllProjects } from "@/app/actions/ProjectData"; -// const projects = { -// projects: [ -// { -// title: "Project 1", -// description: -// "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc felis ligula.", -// image: "public/Home/person.jpg", -// }, -// { -// title: "Project 2", -// description: -// "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc felis ligula.", -// image: "public/Home/person.jpg", -// }, -// { -// title: "Project 3", -// description: -// "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc felis ligula yayyy.", -// image: "public/Home/person.jpg", -// }, -// { -// title: "Project 4", -// description: -// "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc felis ligula.", -// image: "public/Home/person.jpg", -// }, -// ], -// }; - const OurProjects = async () => { const projects=await getAllProjects(process.env.SUPER_ADMIN) return ( diff --git a/src/components/Home/OurSchedule.jsx b/src/components/Home/OurSchedule.jsx new file mode 100644 index 0000000..34fe701 --- /dev/null +++ b/src/components/Home/OurSchedule.jsx @@ -0,0 +1,34 @@ +import React from "react"; +import { getEventsForClub } from "@/app/actions/EventData"; +import Calendar from "../ui/MonthCalendar"; + +const OurSchedule = async () => { + const fetchedEvents = await getEventsForClub(process.env.SUPER_ADMIN); + const monthNames = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ]; + const month = new Date().getMonth(); // Call the function to get the month + const year = new Date().getFullYear(); + const monthName = monthNames[month]; + return ( +
+

+ {monthName} {year} Schedule +

+ +
+ ); +}; + +export default OurSchedule; diff --git a/src/components/ui/DashboardHome.jsx b/src/components/ui/DashboardHome.jsx index c12a8cb..3bb51b6 100644 --- a/src/components/ui/DashboardHome.jsx +++ b/src/components/ui/DashboardHome.jsx @@ -13,7 +13,8 @@ import { IconArticle, IconBrandGithub, IconList, - IconLogout + IconLogout, + IconCalendarEvent } from "@tabler/icons-react"; export const DashboardHome = ({ isSuperAdmin }) => { @@ -101,6 +102,14 @@ const AboutBlock = ({ isSuperAdmin }) => (

+ +

+ /Events{" "} + + : Create, Update, delete your Events + +

+ {isSuperAdmin && (

diff --git a/src/components/ui/EventDetailDialog.jsx b/src/components/ui/EventDetailDialog.jsx new file mode 100644 index 0000000..0461eab --- /dev/null +++ b/src/components/ui/EventDetailDialog.jsx @@ -0,0 +1,35 @@ +"use client"; +import React, { useState } from "react"; +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; + +export function EventDetailDialog({ Date, open, onOpenChange, event }) { + const [eventDetails, setEventDetails] = useState(event); + + return ( + + + + {event.title} +

+

About: {event.about}

+

Venue: {event.venue}

+

Time: {event.time}

+ +
+ + + + Close + + + + + ); +} diff --git a/src/components/ui/MonthCalendar.jsx b/src/components/ui/MonthCalendar.jsx new file mode 100644 index 0000000..1006003 --- /dev/null +++ b/src/components/ui/MonthCalendar.jsx @@ -0,0 +1,78 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { DayPicker } from "react-day-picker"; +import "react-day-picker/dist/style.css"; + +// import { CreateEventModal } from "./create-modal"; +import { EventDetailDialog } from "./EventDetailDialog"; + +function CustomDayContent({ date, eventDates }) { + const isEventDay = eventDates.includes(date.getDate()); + return ( + + {isEventDay ? ( +
+ {date.getDate()} +
+ ) : ( + date.getDate() + )} +
+ ); +} + +function CustomCaptionComponent(props) { + return null; +} + +export default function Calendar({ serializedEvents }) { + const [open, setOpen] = useState(false); + const [eventDaySelected, setEventDaySelected] = useState(false); + const [selectedDate, setSelectedDate] = useState(null); + const [selectedEvent, setSelectedEvent] = useState(null); + const events = JSON.parse(serializedEvents); + const eventDates = events.map((e) => e.date); + useEffect(() => { + setOpen(false); + }, [serializedEvents]); + + const handleDayClick = (day) => { + const dayNumber = day.getDate(); + setSelectedDate(dayNumber); + const isEventDay = eventDates.includes(dayNumber); + + if (isEventDay) { + setEventDaySelected(true); + const eventObj = events.find((e) => e.date === dayNumber); + setSelectedEvent(eventObj); + } else { + setEventDaySelected(false); + setSelectedEvent(null); + } + setOpen(true); + }; + + return ( + <> + ( + + ), + Caption: CustomCaptionComponent, + }} + className="text-lg sm:text-xl" + /> + {eventDaySelected && open && ( + + )} + + + ); +} diff --git a/src/models/events.js b/src/models/events.js new file mode 100644 index 0000000..7ae2872 --- /dev/null +++ b/src/models/events.js @@ -0,0 +1,20 @@ +import mongoose, { Schema } from "mongoose"; + +const eventSchema = new Schema( + { + events: [ + { + date: Number, + title: String, + about: String, + venue: String, + time: String, + }, + ], + club: String, + } +); + +const Event = mongoose.models?.Event || mongoose.model("Event", eventSchema); + +export default Event;