diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..27b85a5 Binary files /dev/null and b/.DS_Store differ diff --git a/backend/app.js b/backend/app.js index daf90fd..ab21aa1 100644 --- a/backend/app.js +++ b/backend/app.js @@ -3,9 +3,12 @@ import cookieParser from "cookie-parser"; import dotenv from "dotenv"; import authRouter from "./routes/Auth.router.js"; import teamRouter from "./routes/Team.router.js"; +import adminRouter from "./routes/admin.router.js"; import inviteRouter from "./routes/Invite.router.js"; import userRouter from "./routes/User.router.js"; import memberRouter from "./routes/Member.router.js"; +import teamPageRouter from "./routes/Team.page.router.js"; + import joinRouter from "./routes/Join.router.js"; import eventRouter from "./routes/Event.router.js"; import projectRouter from "./routes/Project.router.js"; @@ -22,7 +25,7 @@ app.use(cookieParser()); app.use(methodOverride("_method")); // CORS Configuration -const allowedOrigins = process.env.FRONTEND_DOMAIN_PROD || process.env.FRONTEND_DOMAIN_DEV; +const allowedOrigins = process.env.FRONTEND_DOMAIN_PROD || process.env.FRONTEND_DOMAIN_DEV || "http://localhost:5173"; console.log(allowedOrigins); const corsOptions = { origin: allowedOrigins, @@ -51,6 +54,8 @@ app.use('/api/teams', joinRouter); app.use('/api/teams', memberRouter); app.use('/api/events', eventRouter); app.use('/api/projects', projectRouter); +app.use('/api/teampage', teamPageRouter); +app.use('/api/admin', adminRouter); // Error handling middleware app.use((err, req, res, next) => { diff --git a/backend/controllers/Team.Member.controller.js b/backend/controllers/Team.Member.controller.js new file mode 100644 index 0000000..54561fb --- /dev/null +++ b/backend/controllers/Team.Member.controller.js @@ -0,0 +1,53 @@ +import TeamMember from "../models/TeamMember.model.js"; + +export const getTeamByYears = async (req, res) => { + try { + const years = await TeamMember.distinct("year"); + years.sort((a, b) => b - a); + res.json(years); + } catch (err) { + res.status(500).json({ message: "Failed to fetch years" }); + } +}; + +export const getCurrentTeam = async (req, res) => { + try { + const year = parseInt(req.query.year); + if (!year) return res.status(400).json({ message: "Year is required" }); + const team = await TeamMember.find({ year }).sort({ order: 1 }); + res.json(team); + } catch (err) { + res.status(500).json({ message: "Failed to fetch team" }); + } +}; + +export const createTeamMember = async (req, res) => { + try { + const member = await TeamMember.create(req.body); + res.status(201).json(member); + } catch (err) { + res.status(400).json({ message: "Failed to create team member", error: err.message }); + } +}; + +export const updateTeamMember = async (req, res) => { + try { + const { id } = req.params; + const member = await TeamMember.findByIdAndUpdate(id, req.body, { new: true }); + if (!member) return res.status(404).json({ message: "Team member not found" }); + res.json(member); + } catch (err) { + res.status(400).json({ message: "Failed to update team member", error: err.message }); + } +}; + +export const deleteTeamMember = async (req, res) => { + try { + const { id } = req.params; + const member = await TeamMember.findByIdAndDelete(id); + if (!member) return res.status(404).json({ message: "Team member not found" }); + res.json({ success: true }); + } catch (err) { + res.status(400).json({ message: "Failed to delete team member", error: err.message }); + } +}; diff --git a/backend/index.js b/backend/index.js index 19b67f5..4104b58 100644 --- a/backend/index.js +++ b/backend/index.js @@ -4,6 +4,7 @@ dotenv.config({path:'./.env'}) import connectDB from "./db/db.js" import app from "./app.js" +// app.use(express.json()) connectDB() .then(()=>{ const port=process.env.PORT || 3000; diff --git a/backend/models/TeamMember.model.js b/backend/models/TeamMember.model.js new file mode 100644 index 0000000..64e7d33 --- /dev/null +++ b/backend/models/TeamMember.model.js @@ -0,0 +1,20 @@ +import mongoose from "mongoose"; + +const teamMemberSchema = new mongoose.Schema( + { + name: { type: String, required: true }, + role: { type: String, required: true }, + image: { type: String }, + desc: { type: String }, + facebook: { type: String }, + twitter: { type: String }, + instagram: { type: String }, + year: { type: Number, required: true }, // e.g. 2025 + order: { type: Number, default: 0 }, // for sorting + }, + { timestamps: true } +); + +const TeamMember = mongoose.model("TeamMember", teamMemberSchema); + +export default TeamMember; \ No newline at end of file diff --git a/backend/routes/Team.member.router.js b/backend/routes/Team.member.router.js new file mode 100644 index 0000000..f8ba418 --- /dev/null +++ b/backend/routes/Team.member.router.js @@ -0,0 +1,15 @@ +import express from "express"; +import { getCurrentTeam, getTeamByYear, createTeamMember, updateTeamMember, deleteTeamMember } from "../controllers/Team..Member.controller.js"; + +const router = express.Router(); + +// Public endpoints +router.get("/years", getTeamByYears); +// router.get("/", getCurrentTeam); + +// Admin endpoints +router.post("/create-team-member", createTeamMember); +router.put("/team/:id", updateTeamMember); +router.delete("/team/:id", deleteTeamMember); + +export default router; diff --git a/backend/routes/Team.page.router.js b/backend/routes/Team.page.router.js new file mode 100644 index 0000000..6221d53 --- /dev/null +++ b/backend/routes/Team.page.router.js @@ -0,0 +1,8 @@ +import express from "express"; +import { getTeamYears, getTeamByYear, createTeamMember, updateTeamMember, deleteTeamMember } from "../controllers/Team..Member.controller.js"; + +const router = express.Router(); + +router.get("/years", getTeamYears); +router.get("/", getTeamByYear); +export default router; diff --git a/backend/routes/admin.router.js b/backend/routes/admin.router.js new file mode 100644 index 0000000..1c5ca1d --- /dev/null +++ b/backend/routes/admin.router.js @@ -0,0 +1,10 @@ +import express from "express"; +// import { getTeamYears, getTeamByYear, createTeamMember, updateTeamMember, deleteTeamMember } from "../controllers/Team..Member.controller.js"; +import teamRouter from "./Team.member.router.js"; + +const router = express.Router(); + +// Public endpoints +router.use("/team", teamRouter); + +export default router; \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6ba8b31..ae11311 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "3dfolio", + "name": "EmR frontend", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "3dfolio", + "name": "EmR frontend", "version": "0.0.0", "dependencies": { "@emailjs/browser": "^4.4.1", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2431810..cc3b7c9 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -22,11 +22,13 @@ import TransactionVerifyPage from "./pages/TransactionVerifyPage"; import WorkshopInfo from "./pages/WorkshopInfo"; import TeamDetailsPage from "./pages/TeamDetailsPage"; import SynapseEventPage from "./pages/SynapseEventPage"; +import AdminDashBoard from "./admin/AdminDashBoard"; import UserDashboardPage from "./pages/UserDashboardPage"; import EventDetailsPage from "./pages/EventDetailsPage.jsx"; import CreateEventPage from "./pages/CreateEventPage.jsx"; import ManageEventsPage from "./pages/ManageEventsPage.jsx"; import { Toaster } from "./components/ui/toaster"; +import TeamPage from "./pages/TeamPage"; const App = () => { return ( @@ -45,6 +47,8 @@ const App = () => { } /> } /> } /> + } /> + } /> + +
+ {activeSection === "Team Management" && } + {activeSection !== "Team Management" && ( +
+ {activeSection} (Coming Soon) +
+ )} +
+ + ); +} + diff --git a/frontend/src/admin/admin-comp/AdminSidebar.jsx b/frontend/src/admin/admin-comp/AdminSidebar.jsx new file mode 100644 index 0000000..4d01eaa --- /dev/null +++ b/frontend/src/admin/admin-comp/AdminSidebar.jsx @@ -0,0 +1,27 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +export default function AdminSidebar({ options, activeSection, setActiveSection, className }) { + return ( + + ); +} + diff --git a/frontend/src/admin/admin-comp/TeamManagement.jsx b/frontend/src/admin/admin-comp/TeamManagement.jsx new file mode 100644 index 0000000..0f409c2 --- /dev/null +++ b/frontend/src/admin/admin-comp/TeamManagement.jsx @@ -0,0 +1,173 @@ +import React, { useEffect, useState } from "react"; +import YearSelector from "./YearSelector"; +import TeamMemberCard from "./TeamMemberCard"; +import TeamMemberSkeleton from "./TeamMemberSkeleton"; +import TeamMemberModal from "./TeamMemberModal"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "http://localhost:3000"; +const initialForm = { + name: "", + role: "", + image: "", + desc: "", + facebook: "", + twitter: "", + instagram: "", + year: "", + order: 0, +}; + +export default function TeamManagement() { + const [years, setYears] = useState([]); + const [selectedYear, setSelectedYear] = useState(""); + const [teamMembers, setTeamMembers] = useState([]); + const [loading, setLoading] = useState(false); + const [formOpen, setFormOpen] = useState(false); + const [form, setForm] = useState(initialForm); + const [editId, setEditId] = useState(null); + const [formLoading, setFormLoading] = useState(false); + const [orderUpdating, setOrderUpdating] = useState(null); + + useEffect(() => { + fetch(API_BASE_URL+"/api/admin/team/years") + .then((res) => res.json()) + .then((data) => { + if (Array.isArray(data)) { + setYears(data); + setSelectedYear(data[0] || ""); + } else { + setYears([]); + } + }) + .catch(() => setYears([])); + }, []); + + useEffect(() => { + if (!selectedYear) return; + setLoading(true); + fetch(`${API_BASE_URL}/api/admin/team?year=${selectedYear}`) + .then((res) => res.json()) + .then((data) => { + // Sort by order ascending + setTeamMembers(data.sort((a, b) => a.order - b.order)); + setLoading(false); + }) + .catch(() => setLoading(false)); + }, [selectedYear]); + + const openAddForm = () => { + setForm({ ...initialForm, year: selectedYear }); + setEditId(null); + setFormOpen(true); + }; + + const openEditForm = (member) => { + setForm({ ...member }); + setEditId(member._id); + setFormOpen(true); + }; + + const handleDelete = async (id) => { + if (!window.confirm("Delete this team member?")) return; + setLoading(true); + await fetch(`${API_BASE_URL}/api/admin/team/team/${id}`, { method: "DELETE" }); + setTeamMembers((prev) => prev.filter((m) => m._id !== id)); + setLoading(false); + }; + + const handleFormSubmit = async (e) => { + e.preventDefault(); + setFormLoading(true); + const method = editId ? "PUT" : "POST"; + const url = editId ? `${API_BASE_URL}/api/admin/team/team/${editId}` : `${API_BASE_URL}/api/admin/team/create-team-member`; + const res = await fetch(url, { + method, + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(form), + }); + const data = await res.json(); + if (res.ok) { + setFormOpen(false); + setEditId(null); + setForm(initialForm); + // Refresh list + fetch(`${API_BASE_URL}/api/team?year=${selectedYear}`) + .then((res) => res.json()) + .then((data) => setTeamMembers(data.sort((a, b) => a.order - b.order))); + } + setFormLoading(false); + }; + + const handleOrderUpdate = async (id, newOrder) => { + setOrderUpdating(id); + await fetch(`${API_BASE_URL}/api/admin/team/team/${id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ order: newOrder }), + }); + // Refresh list + fetch(`${API_BASE_URL}/api/admin/team?year=${selectedYear}`) + .then((res) => res.json()) + .then((data) => setTeamMembers(data.sort((a, b) => a.order - b.order))); + setOrderUpdating(null); + }; + + return ( +
+
+
+

Team Management

+

Manage team members for each year

+
+ +
+ +
+ + + + + + + + + + + + + + {loading + ? Array.from({ length: 6 }).map((_, idx) => ( + + )) + : teamMembers.map((member) => ( + + ))} + +
NameRoleImageDescriptionSocialOrderActions
+
+ +
+ ); +} + diff --git a/frontend/src/admin/admin-comp/TeamMemberCard.jsx b/frontend/src/admin/admin-comp/TeamMemberCard.jsx new file mode 100644 index 0000000..0cd5124 --- /dev/null +++ b/frontend/src/admin/admin-comp/TeamMemberCard.jsx @@ -0,0 +1,70 @@ +import React, { useState } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faEdit, faTrash, faSave } from "@fortawesome/free-solid-svg-icons"; +import { faFacebookF, faTwitter, faInstagram } from "@fortawesome/free-brands-svg-icons"; + +export default function TeamMemberCard({ member, onEdit, onDelete, onOrderChange, onOrderUpdate }) { + const [order, setOrder] = useState(member.order); + const [orderChanged, setOrderChanged] = useState(false); + + const handleOrderInput = (e) => { + setOrder(Number(e.target.value)); + setOrderChanged(true); + }; + + const handleOrderUpdate = () => { + onOrderUpdate(member._id, order); + setOrderChanged(false); + }; + + return ( + + {member.name} + {member.role} + + {member.name} + + {member.desc} + + + + + + {orderChanged && ( + + )} + + + + + + + ); +} + diff --git a/frontend/src/admin/admin-comp/TeamMemberModal.jsx b/frontend/src/admin/admin-comp/TeamMemberModal.jsx new file mode 100644 index 0000000..2388fc4 --- /dev/null +++ b/frontend/src/admin/admin-comp/TeamMemberModal.jsx @@ -0,0 +1,32 @@ +import React from "react"; + +export default function TeamMemberModal({ form, setForm, formOpen, setFormOpen, handleFormSubmit, formLoading, editId }) { + if (!formOpen) return null; + return ( +
+
+

{editId ? "Edit" : "Add"} Team Member

+
+ setForm({ ...form, name: e.target.value })} /> + setForm({ ...form, role: e.target.value })} /> + setForm({ ...form, image: e.target.value })} /> + setForm({ ...form, facebook: e.target.value })} /> + setForm({ ...form, twitter: e.target.value })} /> + setForm({ ...form, instagram: e.target.value })} /> + setForm({ ...form, year: e.target.value })} /> + setForm({ ...form, order: e.target.value })} /> +
+