Skip to content

Commit

Permalink
load profile pics directly, abstract VolunteerLayout so not to be rel…
Browse files Browse the repository at this point in the history
…oaded each link change
  • Loading branch information
theosiemensrhodes committed Jan 13, 2025
1 parent 532c6ae commit 93cfd7e
Show file tree
Hide file tree
Showing 15 changed files with 144 additions and 125 deletions.
File renamed without changes.
5 changes: 2 additions & 3 deletions backend/src/controllers/volunteerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/models/volunteerModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
});
});
}
Expand Down
50 changes: 34 additions & 16 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -41,19 +43,35 @@ function App() {
<div className="App">
<BrowserRouter>
<Routes>
{/* Public Routes */}
<Route path="/auth/signup" element={<VolunteerSignup />} />
<Route path="/auth/login" element={<VolunteerLogin />} />
<Route path="/auth/forgot-password" element={<VolunteerForgotPassword />}/>
<Route path="/auth/reset-password" element={<VolunteerResetPassword />}/>
<Route path="/" element={isVolunteer ? <VolunteerDash /> : <VolunteerLogin />} />
<Route path="/volunteer/classes" element={isVolunteer ? <Classes /> : <VolunteerLogin />} />
<Route path="/volunteer/my-profile" element={isVolunteer ? <VolunteerProfile /> : <VolunteerLogin />}/>
<Route path="/volunteer/schedule" element={isVolunteer ? <VolunteerSchedule /> : <VolunteerLogin />} />
<Route path="/auth/forgot-password" element={<VolunteerForgotPassword />} />
<Route path="/auth/reset-password" element={<VolunteerResetPassword />} />

{/* Volunteer Routes */}
<Route
path="/"
element={isVolunteer ? <VolunteerLayout /> : <Navigate to="/auth/login" replace />}
>
{/* Nested Routes within VolunteerLayout */}
<Route index element={<VolunteerDash />} />
<Route path="volunteer">
<Route path="classes" element={<Classes />} />
<Route path="schedule" element={<VolunteerSchedule />} />
<Route path="my-profile" element={<VolunteerProfile />} />
</Route>
</Route>

{/* Admin Route (Assuming it's accessible without authentication or has its own protection) */}
<Route path="/admin/verify-volunteers" element={<AdminVerify />} />

{/* Catch-all Route for Undefined Paths */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
</div>
);
}

export default App;
export default App;
10 changes: 2 additions & 8 deletions frontend/src/api/volunteerService.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { backend } from '../data/constants';
import api from './api';

export const fetchVolunteerData = async (volunteer_id) => {
Expand Down Expand Up @@ -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) => {
Expand Down
Empty file.
29 changes: 29 additions & 0 deletions frontend/src/components/ImgFallback/index.js
Original file line number Diff line number Diff line change
@@ -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 <img
className={className}
src={src}
alt={name}
width={40}
height={40}
onError={handleImageError}
/>
else
return null
}
8 changes: 6 additions & 2 deletions frontend/src/components/NavProfileCard/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
29 changes: 9 additions & 20 deletions frontend/src/components/NavProfileCard/index.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -18,31 +19,19 @@ export default function NavProfileCard({

return collapse ? (
<div className="nav-profile-card__avatar-collapse">
{avatar ? (
<img src={avatar} alt={name} width={32} height={32} />
) : (
<img
src={`https://api.dicebear.com/9.x/initials/svg?seed=${name}`}
alt={name}
width={32}
height={32}
/>
)}
<ProfileImg
src={avatar}
name={name}
></ProfileImg>
</div>
) : (
<div className="nav-profile-card" onClick={toProfile}>
<div className="nav-profile-card__main">
<div className="nav-profile-card__avatar">
{avatar ? (
<img src={avatar} alt={name} width={40} height={40} />
) : (
<img
src={`https://api.dicebear.com/9.x/initials/svg?seed=${name}`}
alt={name}
width={40}
height={40}
/>
)}
<ProfileImg
src={avatar}
name={name}
></ProfileImg>
</div>
<div className="nav-profile-card__info">
<p className="nav-profile-card__name">{name}</p>
Expand Down
24 changes: 8 additions & 16 deletions frontend/src/components/volunteerLayout/index.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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"
/>
</div>
</aside>
<main className="content-container" style={pageStyle}>
<div className="content-heading">
<h2 className="content-title">{pageTitle}</h2>
{pageTitle === "My Profile" && (
<button className="logout-button" onClick={() => {
localStorage.removeItem("neuronAuthToken");
window.location.href = "/auth/login";
}}>
<i className="fa-solid fa-arrow-right-from-bracket"></i>&nbsp;&nbsp;Log Out
</button>
)}
</div>
{children} {/* Render page content here */}
<Outlet /> {/* Render page content here */}
</main>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -160,7 +161,11 @@ function VolunteerDetailsCard({ volunteer }) {
document.getElementById('fileInput').click()
}}
>
<img src={tempImage ? tempImage : mutableData.profilePicture ? mutableData.profilePicture : `https://api.dicebear.com/9.x/initials/svg?seed=${mutableData.preferredName}`} alt="Profile" className="profile-image"/>
<ProfileImg
className="profile-image"
src={tempImage ? tempImage : mutableData.profilePicture}
name={mutableData.preferredName}
></ProfileImg>
{isEditing && <div className="overlay">
<img src={camera_icon} alt="Edit Profile" className="camera-icon" />
<p className="edit-text">Edit</p>
Expand Down Expand Up @@ -189,7 +194,7 @@ function VolunteerDetailsCard({ volunteer }) {
'font-style': 'italic'
}}
hidden={isEditing}>
{mutableData.preferredName ? mutableData.preferredName : "not yet set"}
{mutableData.preferredName ?? "not yet set"}
</td>
<td hidden={!isEditing}>
<input type="text" className="text-input" placeholder="Enter your preferred name" name="preferredName" value={mutableData.preferredName} onChange={handleInputChange}></input>
Expand All @@ -204,7 +209,7 @@ function VolunteerDetailsCard({ volunteer }) {
'font-style': 'italic'
}}
hidden={isEditing}>
{mutableData.pronouns ? mutableData.pronouns : "not yet set"}
{mutableData.pronouns ?? "not yet set"}
</td>
{isEditing && (
<td
Expand Down Expand Up @@ -267,7 +272,7 @@ function VolunteerDetailsCard({ volunteer }) {
</tr>
<tr className="view volunteer-location" hidden={isEditing}>
<td>Location</td>
<td>{volunteer.city}, {volunteer.province}</td>
<td>{volunteer.city && volunteer.province ? `${volunteer.city}, ${volunteer.province}` : 'not yet set'}</td>
</tr>
</tbody>
</table>
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/pages/classes/index.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -139,7 +138,10 @@ function Classes() {
};

return (
<VolunteerLayout pageTitle="Classes">
<main className="content-container">
<div className="content-heading">
<h2 className="content-title">Classes</h2>
</div>
<DetailsPanel
classId={selectedClassId}
classList={completeClassData}
Expand Down Expand Up @@ -181,7 +183,7 @@ function Classes() {
</div>
</div>
</DetailsPanel>
</VolunteerLayout>
</main>
);
}

Expand Down
17 changes: 9 additions & 8 deletions frontend/src/pages/schedule/index.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -99,8 +98,10 @@ function VolunteerSchedule() {
};

return (
<VolunteerLayout
pageTitle="Schedule">
<main className="content-container">
<div className="content-heading">
<h2 className="content-title">Schedule</h2>
</div>
<DetailsPanel
classId={selectedClassId}
classList={shifts}
Expand Down Expand Up @@ -148,7 +149,7 @@ function VolunteerSchedule() {
</div>
</div>
</DetailsPanel>
</VolunteerLayout>
</main>
);
}

Expand Down
Loading

0 comments on commit 93cfd7e

Please sign in to comment.