diff --git a/backend/generate-types.js b/backend/mysql.js similarity index 100% rename from backend/generate-types.js rename to backend/mysql.js diff --git a/backend/src/controllers/volunteerController.ts b/backend/src/controllers/volunteerController.ts index 86feb2f..be474ac 100644 --- a/backend/src/controllers/volunteerController.ts +++ b/backend/src/controllers/volunteerController.ts @@ -139,10 +139,9 @@ async function getProfilePicture(req: Request, res: Response) { try { const profilePic = await volunteerModel.getProfilePicture(volunteer_id); - res.writeHead(200, { 'Content-Type': 'image/jpeg' }); - res.end(profilePic, 'binary'); + res.type('image/jpeg').send(profilePic.profile_pic); } catch (error: any) { - return res.status(error.status).json({ + return res.status(error.status ?? 500).json({ error: error.message, }); } diff --git a/backend/src/models/volunteerModel.ts b/backend/src/models/volunteerModel.ts index 6882193..600c2bf 100644 --- a/backend/src/models/volunteerModel.ts +++ b/backend/src/models/volunteerModel.ts @@ -212,7 +212,7 @@ export default class VolunteerModel { message: `No profile picture found under the given volunteer ID`, }); } - resolve(results[0].profile_pic); + resolve(results[0]); }); }); } diff --git a/frontend/src/App.js b/frontend/src/App.js index 9c84483..985fdc6 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,16 +1,18 @@ -import { BrowserRouter, Routes, Route } from "react-router-dom"; +// src/App.js +import "notyf/notyf.min.css"; import { useEffect, useState } from "react"; +import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import { isAuthenticated } from "./api/authService"; -import VolunteerDash from "./pages/volunteerDash"; -import VolunteerSignup from "./pages/VolunteerSignup"; -import VolunteerLogin from "./pages/VolunteerLogin"; +import VolunteerLayout from "./components/volunteerLayout"; +import AdminVerify from "./pages/AdminVerify"; +import Classes from "./pages/Classes"; +import VolunteerSchedule from "./pages/Schedule"; +import VolunteerDash from "./pages/VolunteerDash"; import VolunteerForgotPassword from "./pages/VolunteerForgotPassword"; +import VolunteerLogin from "./pages/VolunteerLogin"; +import VolunteerProfile from "./pages/VolunteerProfile"; import VolunteerResetPassword from "./pages/VolunteerResetPassword"; -import Classes from "./pages/classes"; -import VolunteerSchedule from "./pages/schedule"; -import VolunteerProfile from "./pages/volunteerProfile"; -import AdminVerify from "./pages/AdminVerify"; -import "notyf/notyf.min.css"; +import VolunteerSignup from "./pages/VolunteerSignup"; function App() { const [isVolunteer, setIsVolunteer] = useState(false); @@ -41,19 +43,35 @@ function App() {
+ {/* Public Routes */} } /> } /> - }/> - }/> - : } /> - : } /> - : }/> - : } /> + } /> + } /> + + {/* Volunteer Routes */} + : } + > + {/* Nested Routes within VolunteerLayout */} + } /> + + } /> + } /> + } /> + + + + {/* Admin Route (Assuming it's accessible without authentication or has its own protection) */} } /> + + {/* Catch-all Route for Undefined Paths */} + } />
); } -export default App; \ No newline at end of file +export default App; diff --git a/frontend/src/api/volunteerService.js b/frontend/src/api/volunteerService.js index 862baa9..e1b176b 100644 --- a/frontend/src/api/volunteerService.js +++ b/frontend/src/api/volunteerService.js @@ -1,3 +1,4 @@ +import { backend } from '../data/constants'; import api from './api'; export const fetchVolunteerData = async (volunteer_id) => { @@ -33,14 +34,7 @@ export const insertProfilePicture = async (profilePicData) => { } export const getProfilePicture = async (volunteer_id) => { - try { - const response = await api.get(`/volunteer/profile-picture/${volunteer_id}`, { - responseType: 'blob' - }); - return URL.createObjectURL(response.data); - } catch (error) { - console.error('Error getting profile picture:', error); - } + return `${backend}/volunteer/profile-picture/${volunteer_id}` } export const updateProfilePicture = async (volunteer_id, profilePicData) => { diff --git a/frontend/src/components/ImgFallback/index.css b/frontend/src/components/ImgFallback/index.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/components/ImgFallback/index.js b/frontend/src/components/ImgFallback/index.js new file mode 100644 index 0000000..53e2196 --- /dev/null +++ b/frontend/src/components/ImgFallback/index.js @@ -0,0 +1,29 @@ +import React from "react"; +import "./index.css"; + + +export default function ProfileImg({ + src, + name, + className, +}) { + const fallbackUrl = `https://api.dicebear.com/9.x/initials/svg?seed=${encodeURIComponent(name)}`; + + const handleImageError = (e) => { + if (e.target.src !== fallbackUrl) { + e.target.src = fallbackUrl; + } + }; + + if (name) + return {name} + else + return null +} diff --git a/frontend/src/components/NavProfileCard/index.css b/frontend/src/components/NavProfileCard/index.css index 1a1b804..52dd564 100644 --- a/frontend/src/components/NavProfileCard/index.css +++ b/frontend/src/components/NavProfileCard/index.css @@ -20,6 +20,7 @@ flex-direction: row; gap: 0.5rem; align-items: center; + min-width: 0; } .nav-profile-card__avatar, .nav-profile-card__avatar-collapse { @@ -43,12 +44,15 @@ .nav-profile-card__info { display: flex; flex-direction: column; - gap:0.2rem; + gap: 0.2rem; + min-width: 0; } .nav-profile-card__name, .nav-profile-card__email { margin: 0; - + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .nav-profile-card__name { diff --git a/frontend/src/components/NavProfileCard/index.js b/frontend/src/components/NavProfileCard/index.js index e816ba5..82d716a 100644 --- a/frontend/src/components/NavProfileCard/index.js +++ b/frontend/src/components/NavProfileCard/index.js @@ -1,5 +1,6 @@ import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; import React from "react"; +import ProfileImg from "../ImgFallback"; import "./index.css"; import { useNavigate } from "react-router-dom"; @@ -18,31 +19,19 @@ export default function NavProfileCard({ return collapse ? (
- {avatar ? ( - {name} - ) : ( - {name} - )} +
) : (
- {avatar ? ( - {name} - ) : ( - {name} - )} +

{name}

diff --git a/frontend/src/components/volunteerLayout/index.js b/frontend/src/components/volunteerLayout/index.js index 77ba014..c6bd6d3 100644 --- a/frontend/src/components/volunteerLayout/index.js +++ b/frontend/src/components/volunteerLayout/index.js @@ -1,16 +1,16 @@ -import "./index.css"; -import { NavLink } from "react-router-dom"; -import { useState, useEffect } from "react"; +import { React, useEffect, useState } from "react"; +import { NavLink, Outlet } from "react-router-dom"; import BC_brain from "../../assets/bwp-logo-text.png"; -import sidebar_toggle from "../../assets/sidebar-toggle.png"; +import nav_item_classes from "../../assets/nav-item-classes.png"; import nav_item_dash from "../../assets/nav-item-dash.png"; import nav_item_schedule from "../../assets/nav-item-sched.png"; -import nav_item_classes from "../../assets/nav-item-classes.png"; import nav_item_settings from "../../assets/nav-item-settings.png"; +import sidebar_toggle from "../../assets/sidebar-toggle.png"; +import "./index.css"; -import NavProfileCard from "../NavProfileCard"; import { isAuthenticated } from "../../api/authService"; import { getProfilePicture } from "../../api/volunteerService"; +import NavProfileCard from "../NavProfileCard"; function VolunteerLayout({ pageTitle, children, pageStyle }) { const [collapsed, setCollapsed] = useState(window.innerWidth <= 800); @@ -118,23 +118,15 @@ function VolunteerLayout({ pageTitle, children, pageStyle }) { name={volunteer?.f_name} email={volunteer?.email} collapse={collapsed} - link={"/volunteer/my-profile"} + link="/volunteer/my-profile" />

{pageTitle}

- {pageTitle === "My Profile" && ( - - )}
- {children} {/* Render page content here */} + {/* Render page content here */}
); diff --git a/frontend/src/components/volunteerProfile/volunteerDetailsCard/index.js b/frontend/src/components/volunteerProfile/volunteerDetailsCard/index.js index 9500217..fc981e4 100644 --- a/frontend/src/components/volunteerProfile/volunteerDetailsCard/index.js +++ b/frontend/src/components/volunteerProfile/volunteerDetailsCard/index.js @@ -5,6 +5,7 @@ import camera_icon from "../../../assets/camera.png"; import cancel_icon from "../../../assets/cancel-icon.png"; import check_icon from "../../../assets/check-icon.png"; import edit_icon from "../../../assets/edit-icon.png"; +import ProfileImg from "../../ImgFallback"; import { CgSelect } from "react-icons/cg"; import { insertProfilePicture, updateProfilePicture, updateVolunteerData } from "../../../api/volunteerService"; @@ -160,7 +161,11 @@ function VolunteerDetailsCard({ volunteer }) { document.getElementById('fileInput').click() }} > - Profile + {isEditing &&
Edit Profile

Edit

@@ -189,7 +194,7 @@ function VolunteerDetailsCard({ volunteer }) { 'font-style': 'italic' }} hidden={isEditing}> - {mutableData.preferredName ? mutableData.preferredName : "not yet set"} + {mutableData.preferredName ?? "not yet set"} @@ -204,7 +209,7 @@ function VolunteerDetailsCard({ volunteer }) { 'font-style': 'italic' }} hidden={isEditing}> - {mutableData.pronouns ? mutableData.pronouns : "not yet set"} + {mutableData.pronouns ?? "not yet set"} {isEditing && ( Location - {volunteer.city}, {volunteer.province} + {volunteer.city && volunteer.province ? `${volunteer.city}, ${volunteer.province}` : 'not yet set'} diff --git a/frontend/src/pages/classes/index.js b/frontend/src/pages/classes/index.js index 42720fb..68b9de2 100644 --- a/frontend/src/pages/classes/index.js +++ b/frontend/src/pages/classes/index.js @@ -1,13 +1,12 @@ -import "./index.css"; -import React, { useEffect, useState, useRef } from "react"; -import VolunteerLayout from "../../components/volunteerLayout"; -import DetailsPanel from "../../components/DetailsPanel"; +import React, { useEffect, useRef, useState } from "react"; import { getAllClasses, getAllClassImages, getAllClassSchedules, } from "../../api/classesPageService"; import ClassCategoryContainer from "../../components/ClassCategoryContainer"; +import DetailsPanel from "../../components/DetailsPanel"; +import "./index.css"; function Classes() { const [completeClassData, setCompleteClassData] = useState(null); @@ -139,7 +138,10 @@ function Classes() { }; return ( - +
+
+

Classes

+
- + ); } diff --git a/frontend/src/pages/schedule/index.js b/frontend/src/pages/schedule/index.js index 81c92c2..44cda38 100644 --- a/frontend/src/pages/schedule/index.js +++ b/frontend/src/pages/schedule/index.js @@ -1,12 +1,11 @@ -import "./index.css"; -import { useState, useEffect, useRef, useCallback } from 'react'; -import VolunteerLayout from "../../components/volunteerLayout"; import dayjs from 'dayjs'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { getVolunteerShiftsForMonth } from "../../api/shiftService"; import DateToolbar from "../../components/DateToolbar"; +import DetailsPanel from "../../components/DetailsPanel"; import ShiftCard from "../../components/ShiftCard"; import ShiftStatusToolbar from "../../components/ShiftStatusToolbar"; -import { getVolunteerShiftsForMonth } from "../../api/shiftService"; -import DetailsPanel from "../../components/DetailsPanel"; +import "./index.css"; function VolunteerSchedule() { const volunteerID = localStorage.getItem('volunteerID'); @@ -99,8 +98,10 @@ function VolunteerSchedule() { }; return ( - +
+
+

Schedule

+
- + ); } diff --git a/frontend/src/pages/volunteerDash/index.js b/frontend/src/pages/volunteerDash/index.js index 986ff95..5fc9edb 100644 --- a/frontend/src/pages/volunteerDash/index.js +++ b/frontend/src/pages/volunteerDash/index.js @@ -1,33 +1,26 @@ // home/ is the landing page of the application. -import "./index.css"; -import React from "react"; -import { useEffect, useState, useRef } from "react"; -import { useNavigate } from "react-router-dom"; -import VolunteerLayout from "../../components/volunteerLayout"; -import { isAuthenticated } from "../../api/authService"; import ArrowForwardIcon from "@mui/icons-material/ArrowForwardIos"; import HelpOutlineIcon from "@mui/icons-material/HelpOutline"; -import DashShifts from "../../components/DashShifts"; -import DashCoverShifts from "../../components/DashCoverShifts"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; -import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import dayjs from "dayjs"; -import DashboardCoverage from "../../components/DashboardCoverage"; +import React, { useEffect, useState } from "react"; import { getVolunteerShiftsForMonth } from "../../api/shiftService"; +import DashboardCoverage from "../../components/DashboardCoverage"; +import DashCoverShifts from "../../components/DashCoverShifts"; +import DashShifts from "../../components/DashShifts"; import { SHIFT_TYPES } from "../../data/constants"; +import "./index.css"; function VolunteerDash() { const volunteerID = localStorage.getItem("volunteerID"); - const [loading, setLoading] = useState(true); const [checkIn, setCheckIn] = useState(false); const [shifts, setShifts] = useState([]); const monthDate = dayjs().date(1).hour(0).minute(0); const [selectedDate, setSelectedDate] = useState(dayjs()); const [future, setFuture] = useState(false); - const navigate = useNavigate(); - const checkInItem = () => { return checkIn ? (
@@ -51,19 +44,6 @@ function VolunteerDash() { ); }; - useEffect(() => { - isAuthenticated() - .then((response) => { - console.log(response); - if (!response.isAuthenticated) { - navigate("/auth/login"); - } - }) - .catch((error) => { - console.error(error); - }); - }, []); - useEffect(() => { const fetchShifts = async () => { const body = { @@ -133,7 +113,10 @@ function VolunteerDash() { }, [selectedDate, shifts]); return ( - +
+
+

Dashboard

+
- +
); } diff --git a/frontend/src/pages/volunteerProfile/index.js b/frontend/src/pages/volunteerProfile/index.js index eb7c0d6..0423f81 100644 --- a/frontend/src/pages/volunteerProfile/index.js +++ b/frontend/src/pages/volunteerProfile/index.js @@ -1,13 +1,10 @@ // volunteer profile page -import "./index.css"; import React, { useState } from 'react'; -import VolunteerLayout from "../../components/volunteerLayout"; import { fetchVolunteerData, getProfilePicture } from "../../api/volunteerService"; -import VolunteerDetailsCard from "../../components/volunteerProfile/volunteerDetailsCard"; -import ChangePasswordCard from "../../components/volunteerProfile/changePasswordCard"; import AvailabilityGrid from "../../components/volunteerProfile/availabilityGrid"; - -const pageTitle = "My Profile"; +import ChangePasswordCard from "../../components/volunteerProfile/changePasswordCard"; +import VolunteerDetailsCard from "../../components/volunteerProfile/volunteerDetailsCard"; +import "./index.css"; function VolunteerProfile() { const [availability, setAvailability] = useState([]); @@ -31,12 +28,18 @@ function VolunteerProfile() { }, []); return ( - +
+
+

My Profile

+ +
{ volunteer ?
@@ -59,7 +62,7 @@ function VolunteerProfile() {
:
Loading...
} - +
); };