Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const register = async (req, res) => {
// Send verification email for students
if(userRole == 'student'){
try {
const backendUrl = process.env.BACKEND_URL || 'http://localhost:3000';
const backendUrl = `http://localhost:${process.env.PORT}` || 'http://localhost:5000';
const verificationUrl = `${backendUrl}/api/users/verify-email?token=${emailVerificationToken}`;

await sendViaGmailApi({
Expand Down
4 changes: 3 additions & 1 deletion backend/controllers/eventController.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ export const createWorkshop = async (req, res) => {
registrationDeadline,
} = req.body;

const creatorId = req.user?.userId || req.body.createdBy || null;

const workshop = new Workshop({
name,
description,
Expand All @@ -441,7 +443,7 @@ export const createWorkshop = async (req, res) => {
extraRequiredResources,
capacity,
registrationDeadline,
createdBy: req.user?.userId || null // from auth middleware
createdBy: creatorId // from auth middleware or request body
});

await workshop.save();
Expand Down
4 changes: 2 additions & 2 deletions frontend/my-app/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function AppRoutes() {
return (
<Routes>
{/* Auth Routes */}
<Route path="/" element={<Users/>} />
<Route path="*" element={<Users/>} />
<Route path="/signup" element={<SignUp />} />
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/VendorSignup" element={<VendorSignup />} />
Expand Down Expand Up @@ -144,7 +144,7 @@ function App() {
<AuthProvider>
<Routes>
{/*Auth Routes*/ }
<Route path="/" element={<Users/>} />
<Route path="*" element={<Users/>} />
<Route path="/signup" element={<SignUp />} />
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/VendorSignup" element={<VendorSignup />} />
Expand Down
18 changes: 15 additions & 3 deletions frontend/my-app/src/components/Shared/EventCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ import useArchiveEvent from "../../features/Events/hooks/useArchiveEvent";
import useExportRegistrations from "../../features/Events/hooks/useExportRegistrations";
import useRestrictEventRoles from "../../features/Events/hooks/useRestrictEventRoles";

export default function EventCard({ event, index = 0, onUpdate, isFavorite: propIsFavorite, onFavoriteChange, onRegister }) {
export default function EventCard({
event,
index = 0,
onUpdate,
isFavorite: propIsFavorite,
onFavoriteChange,
onRegister,
detailUrl,
detailState,
}) {
const navigate = useNavigate();
const [isFavorited, setIsFavorited] = useState(propIsFavorite || false);
const [favoriteLoading, setFavoriteLoading] = useState(false);
Expand Down Expand Up @@ -46,7 +55,9 @@ export default function EventCard({ event, index = 0, onUpdate, isFavorite: prop
}, [event.isArchived]);

const handleCardClick = () => {
navigate(`/events/${event._id}`);
const targetId = event._id || event.id;
const targetUrl = detailUrl || (targetId ? `/events/${targetId}` : "/events");
navigate(targetUrl, detailState ? { state: detailState } : undefined);
};

const handleFavoriteClick = async (e) => {
Expand Down Expand Up @@ -386,7 +397,8 @@ export default function EventCard({ event, index = 0, onUpdate, isFavorite: prop
const canRestrictRoles = user?.role === "eventsOffice";
const isWorkshop = eventType === "workshop";
const isTrip = eventType === "trip";
const canRegister = (isWorkshop || isTrip) && onRegister && user && ['student', 'staff', 'ta', 'professor'].includes(user.role);
// For public registration, show button whenever a handler is provided for workshops/trips
const canRegister = (isWorkshop || isTrip) && typeof onRegister === "function";

const handleArchiveClick = async (e) => {
e.stopPropagation();
Expand Down
20 changes: 20 additions & 0 deletions frontend/my-app/src/components/Shared/EventsListing.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ export default function EventsListing() {
resetFilters,
showArchivedOnly,
setShowArchivedOnly,
showFavoritesOnly,
setShowFavoritesOnly,
favoriteEventIds,
favoritesLoading,
refreshFavorites,
registrationForm,
setRegistrationForm,
openRegistrationForm,
closeRegistrationForm,
handleRegistration,
} = useEventsListing();

return (
Expand All @@ -43,6 +53,16 @@ export default function EventsListing() {
resetFilters={resetFilters}
showArchivedOnly={showArchivedOnly}
setShowArchivedOnly={setShowArchivedOnly}
showFavoritesOnly={showFavoritesOnly}
setShowFavoritesOnly={setShowFavoritesOnly}
favoriteEventIds={favoriteEventIds}
favoritesLoading={favoritesLoading}
refreshFavorites={refreshFavorites}
registrationForm={registrationForm}
setRegistrationForm={setRegistrationForm}
openRegistrationForm={openRegistrationForm}
closeRegistrationForm={closeRegistrationForm}
handleRegistration={handleRegistration}
/>
);
}
Expand Down
144 changes: 140 additions & 4 deletions frontend/my-app/src/features/EventsListing/hooks/EventListingHook.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { useState, useEffect, useMemo, useContext } from "react";
import { useState, useEffect, useMemo, useContext, useCallback } from "react";
import AuthContext from "../../../context/AuthContext";
import { fetchUpcomingEvents, fetchArchivedEventsUseCase } from "../usecases/EventListingUseCase.js";
import { assignEventImage } from"../../../utils/eventImages";
import {
fetchUpcomingEvents,
fetchArchivedEventsUseCase,
fetchFavoriteEventIds,
} from "../usecases/EventListingUseCase.js";
import { registerForEvent } from "../../Workshops/usecases/WorkshopsUsersUsecases";
import { assignEventImage } from "../../../utils/eventImages";
import Swal from "sweetalert2";

export default function useEventsListing() {
const [searchQuery, setSearchQuery] = useState("");
Expand All @@ -19,8 +25,18 @@ export default function useEventsListing() {
const [favoriteEventIds, setFavoriteEventIds] = useState([]);
const [favoritesLoading, setFavoritesLoading] = useState(false);
const [showArchivedOnly, setShowArchivedOnly] = useState(false);
const [registrationForm, setRegistrationForm] = useState({
open: false,
event: null,
name: "",
email: "",
studentStaffId: "",
});

const { token, user } = useContext(AuthContext);
const favoriteRoles = ["student", "staff", "ta", "professor"];
const normalizedRole = user?.role?.toLowerCase();
const canFavorite = normalizedRole && favoriteRoles.includes(normalizedRole);

const apiUrl =
import.meta.env.VITE_API_BASE_URL ||
Expand Down Expand Up @@ -90,6 +106,29 @@ export default function useEventsListing() {
loadArchived();
}, [showArchivedOnly, archivedEvents.length, archivedLoading, apiUrl, token]);

const refreshFavorites = useCallback(async () => {
if (!canFavorite || !token) {
setFavoritesLoading(false);
setFavoriteEventIds([]);
return;
}

setFavoritesLoading(true);
const result = await fetchFavoriteEventIds(apiUrl, token);

if (result.data && Array.isArray(result.data)) {
setFavoriteEventIds(result.data);
} else if (result.error) {
console.error("Failed to fetch favorites:", result.error);
}

setFavoritesLoading(false);
}, [apiUrl, token, canFavorite]);

useEffect(() => {
refreshFavorites();
}, [refreshFavorites]);

// Event types
const sourceEvents = showArchivedOnly ? archivedEvents : events;

Expand Down Expand Up @@ -172,7 +211,16 @@ export default function useEventsListing() {
});

return out;
}, [sourceEvents, searchQuery, selectedType, selectedLocation, sortBy, showArchivedOnly]);
}, [
sourceEvents,
searchQuery,
selectedType,
selectedLocation,
sortBy,
showArchivedOnly,
showFavoritesOnly,
favoriteEventIds,
]);

const sortOptions = [
{ label: "Date (Ascending)", value: "date-asc" },
Expand All @@ -187,6 +235,84 @@ export default function useEventsListing() {
setSelectedLocation(null);
setSortBy("date-asc");
setShowArchivedOnly(false);
setShowFavoritesOnly(false);
};

const openRegistrationForm = (event) => {
setRegistrationForm({
open: true,
event,
name: "",
email: "",
studentStaffId: "",
});
};

const closeRegistrationForm = () => {
setRegistrationForm({
open: false,
event: null,
name: "",
email: "",
studentStaffId: "",
});
};

const handleRegistration = async () => {
const { name, email, studentStaffId, event } = registrationForm;

if (!name || !email || !studentStaffId) {
Swal.fire({
title: "Missing Information",
text: "Please fill in name, email, and student/staff ID.",
icon: "warning",
});
return;
}

if (!email.includes("@")) {
Swal.fire({
title: "Invalid Email",
text: "Please enter a valid email address.",
icon: "warning",
});
return;
}

const eventId = event?._id || event?.id;
if (!eventId) {
Swal.fire({
title: "Error",
text: "Invalid event selection.",
icon: "error",
});
return;
}

const result = await registerForEvent(
eventId,
user?._id,
{ name, email, studentStaffId },
token
);

if (result.error) {
Swal.fire({
title: "Registration Failed",
text: result.error,
icon: "error",
});
} else {
const eventName = event?.title || event?.name || "this event";
Swal.fire({
title: "Registration Successful!",
text: `You have been registered for "${eventName}"`,
icon: "success",
timer: 2000,
showConfirmButton: false,
});
closeRegistrationForm();
}
};

const loading = showArchivedOnly ? archivedLoading : activeLoading;
Expand All @@ -210,5 +336,15 @@ export default function useEventsListing() {
resetFilters,
showArchivedOnly,
setShowArchivedOnly,
showFavoritesOnly,
setShowFavoritesOnly,
favoriteEventIds,
favoritesLoading,
refreshFavorites,
registrationForm,
setRegistrationForm,
openRegistrationForm,
closeRegistrationForm,
handleRegistration,
};
}
Loading