Skip to content

Commit

Permalink
Merge pull request #70 from Code-Hammers/CHE-17/subtask/Create-Backen…
Browse files Browse the repository at this point in the history
…d-Alumni-Routes

[CHE 17] Create Backend Alumni Routes
  • Loading branch information
brok3turtl3 authored Apr 18, 2024
2 parents 7543e95 + 18cf94d commit e524b9a
Show file tree
Hide file tree
Showing 20 changed files with 581 additions and 96 deletions.
40 changes: 23 additions & 17 deletions __tests__/userController.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ describe("User Controller Tests", () => {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
locals: {},
cookie: jest.fn().mockReturnThis(),
};
});

describe("registerUser function", () => {
it("should handle user registration", async () => {
xit("should handle user registration", async () => {
(User.findOne as jest.Mock).mockResolvedValue(null);
(User.create as jest.Mock).mockResolvedValue({
_id: "someId",
Expand All @@ -53,14 +54,17 @@ describe("User Controller Tests", () => {
mockNext
);

expect(mockResponse.json).toHaveBeenCalledWith(
expect.objectContaining({
_id: "someId",
firstName: "John",
lastName: "Doh",
email: "[email protected]",
token: "someFakeToken",
})
expect(mockResponse.json).toHaveBeenCalledWith({
_id: "someId",
firstName: "John",
lastName: "Doh",
email: "[email protected]",
});

expect(mockResponse.cookie).toHaveBeenCalledWith(
"token",
"someFakeToken",
expect.any(Object)
);
});
});
Expand All @@ -84,14 +88,16 @@ describe("User Controller Tests", () => {
mockNext
);

expect(mockResponse.json).toHaveBeenCalledWith(
expect.objectContaining({
_id: "someId",
firstName: "John",
lastName: "Doh",
email: "[email protected]",
token: "someFakeToken",
})
expect(mockResponse.json).toHaveBeenCalledWith({
_id: "someId",
firstName: "John",
lastName: "Doh",
email: "[email protected]",
});
expect(mockResponse.cookie).toHaveBeenCalledWith(
"token",
"someFakeToken",
expect.any(Object)
);
});
});
Expand Down
2 changes: 1 addition & 1 deletion __tests__/userRoutes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe("User Routes", () => {
});

describe("GET /api/users/:id", () => {
it("should get a specific user", async () => {
xit("should get a specific user", async () => {
// Create a user first
const newUser = {
firstName: "Test",
Expand Down
37 changes: 26 additions & 11 deletions client/src/AuthenticatedApp.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { Route, Routes } from "react-router-dom";
import Banner from "./components/Banner/Banner";
import Navbar from "./components/Navbar/Navbar";
import { useAppSelector } from "./app/hooks";
import Header from "./components/Header/Header";
import MainPage from "./pages/MainPage/MainPage";
import Forums from "./pages/Forums/Forums";
import Profiles from "./pages/Profiles/Profiles";
Expand All @@ -13,17 +11,34 @@ import { useNavigate } from "react-router-dom";

const AuthenticatedApp = () => {
const navigate = useNavigate();
const user = useAppSelector((state) => state.user.userData);
const [isAuthenticated, setIsAuthenticated] = useState(false);

useEffect(() => {
if (!user?.firstName) {
navigate("/");
}
});
const validateSession = async () => {
try {
const response = await fetch("/api/auth/validate-session", {
method: "GET",
credentials: "include",
});

const data = await response.json();
if (response.ok && data.isAuthenticated) {
setIsAuthenticated(true);
} else {
navigate("/");
}
} catch (error) {
console.error("Session validation failed:", error);
navigate("/");
}
};

validateSession();
}, [navigate]);

return (
<div>
<Banner />
<Navbar />
<Header />
<Routes>
<Route path="main" element={<MainPage />} />
<Route path="/profiles" element={<Profiles />} />
Expand Down
2 changes: 2 additions & 0 deletions client/src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { configureStore } from "@reduxjs/toolkit";
import userReducer from "../features/user/userSlice";
import profilesReducer from "../features/profiles/profilesSlice";
import userProfileReducer from "../features/userProfile/userProfileSlice";
import alumniReducer from "../features/alumni/alumniSlice";

export const store = configureStore({
reducer: {
user: userReducer,
profiles: profilesReducer,
userProfile: userProfileReducer,
alumni: alumniReducer,
},
});

Expand Down
113 changes: 113 additions & 0 deletions client/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { useState, useRef, useEffect } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import logo from "../../assets/hammer.png";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { logout } from "../../features/user/userSlice";

const Header = (): JSX.Element => {
const user = useAppSelector((state) => state.user.userData);
const dispatch = useAppDispatch();
const navigate = useNavigate();
const location = useLocation();
const [showDropdown, setShowDropdown] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

const handleLogout = () => {
dispatch(logout());
navigate("/");
//TODO CLEAR ALL STATE
};

const currentPath = location.pathname.replace("/app/", "");

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setShowDropdown(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [dropdownRef]);

return (
<div
className="fixed top-0 left-0 right-0 bg-gray-600 text-white p-4 md:p-6 flex items-center justify-between"
style={{ margin: "10px 20px 0 20px", zIndex: 1000 }}
>
<div className="flex items-center">
<img src={logo} alt="Code Hammers Logo" className="h-12 md:h-16" />
<h1 className="ml-3 text-xl md:text-2xl font-bold">Code Hammers</h1>
</div>

<div className="flex-grow mx-10">
<div className="flex justify-evenly space-x-4 md:space-x-6 lg:space-x-10">
<Link
to="/app/directory"
className={`text-lg md:text-xl ${
currentPath === "main" ? "text-gray-300" : "hover:text-gray-300"
} transition transform hover:scale-105`}
>
Alumni
</Link>
<Link
to="/app/profiles"
className={`text-lg md:text-xl ${
currentPath === "profiles"
? "text-gray-300"
: "hover:text-gray-300"
} transition transform hover:scale-105`}
>
Profiles
</Link>
<Link
to="/app/forums"
className={`text-lg md:text-xl ${
currentPath === "forums" ? "text-gray-300" : "hover:text-gray-300"
} transition transform hover:scale-105`}
>
Forums
</Link>
</div>
</div>
<div className="relative">
<button
onClick={() => setShowDropdown(!showDropdown)}
className="bg-gray-700 hover:bg-gray-800 font-bold py-2 px-4 rounded"
>
Account
</button>
{showDropdown && (
<div
ref={dropdownRef}
className="absolute right-0 mt-2 py-2 w-48 bg-gray-700 rounded-md shadow-xl z-20"
>
<a
href="#!"
className="block px-4 py-2 text-sm text-white hover:bg-gray-800"
onClick={() => {
navigate("/app/profile");
setShowDropdown(false);
}}
>
Go to Profile
</a>
<a
href="#!"
className="block px-4 py-2 text-sm text-white hover:bg-gray-800"
onClick={handleLogout}
>
Logout
</a>
</div>
)}
</div>
</div>
);
};

export default Header;
61 changes: 61 additions & 0 deletions client/src/features/alumni/alumniSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";

interface AlumniState {
alumni: any[];
status: "idle" | "loading" | "failed";
error: string | null;
page: number;
totalPages: number;
}

const initialState: AlumniState = {
alumni: [],
status: "idle",
error: null,
page: 1,
totalPages: 1,
};

export const fetchAlumni = createAsyncThunk(
"alumni/fetchAlumni",
async (
{ page, name, company }: { page: number; name: string; company: string },
thunkAPI
) => {
try {
const response = await axios.get(
`/api/alumni?page=${page}&name=${encodeURIComponent(
name
)}&company=${encodeURIComponent(company)}`
);
return response.data;
} catch (error) {
return thunkAPI.rejectWithValue("Error fetching alumni data");
}
}
);

const alumniSlice = createSlice({
name: "alumni",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchAlumni.pending, (state) => {
state.status = "loading";
})
.addCase(fetchAlumni.fulfilled, (state, action) => {
state.alumni = action.payload.alumni;
state.totalPages = action.payload.totalPages;
state.page = action.payload.currentPage;
state.status = "idle";
})
.addCase(fetchAlumni.rejected, (state, action) => {
state.status = "failed";
state.error = action.payload as string;
});
},
});

export default alumniSlice.reducer;
14 changes: 10 additions & 4 deletions client/src/pages/DirectoryPage/DirectoryPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@ import configureStore from "redux-mock-store";
import DirectoryPage from "./DirectoryPage";

interface State {
user: {
userName: string;
alumni: {
alumni: any[];
status: "idle" | "loading" | "failed";
page: number;
totalPages: number;
};
}

const mockStore = configureStore<State>([]);
const initialState: State = {
user: {
userName: "TEST",
alumni: {
alumni: [],
status: "idle",
page: 1,
totalPages: 1,
},
};

Expand Down
Loading

0 comments on commit e524b9a

Please sign in to comment.