diff --git a/app/controllers/current_user_controller.rb b/app/controllers/current_user_controller.rb
index dc0b299..2fcd3c9 100644
--- a/app/controllers/current_user_controller.rb
+++ b/app/controllers/current_user_controller.rb
@@ -2,7 +2,7 @@ class CurrentUserController < ApplicationController
before_action :authenticate_user!
# If a user is authenticated, they are set as the current user.
- def index
+ def show
render json: UserSerializer.new(current_user).serializable_hash[:data][:attributes], status: :ok
end
end
diff --git a/config/routes.rb b/config/routes.rb
index 56ccb4a..d48648e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,5 +1,5 @@
Rails.application.routes.draw do
- get "/current_user", to: "current_user#index"
+ get "/current_user", to: "current_user#show"
devise_for :users, path: "", path_names: {
sign_in: "login",
sign_out: "logout",
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index c21a67f..751c5b0 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,5 +1,6 @@
import './App.css';
-import {BrowserRouter as Router, Routes, Route, Navigate} from "react-router-dom";
+import { useContext } from 'react';
+import { Routes, Route, Navigate } from "react-router-dom";
import PageWrapper from './components/PageWrapper';
import Home from './components/pages/Home';
import About from './components/pages/About';
@@ -9,11 +10,9 @@ import RequestKit from './components/pages/RequestKit'
import Registration from './components/auth/Registration';
import Login from './components/auth/Login';
import ScrollToHash from './components/ScrollToHash';
-import { useState, useEffect } from 'react';
import Confirmation from './components/pages/Confirmation';
import Donation from './components/pages/Donation';
import RequestSpeaker from './components/pages/RequestSpeaker';
-import { jwtDecode } from 'jwt-decode';
import AdminDashboard from './components/pages/AdminDashboard';
import NewForms from './components/NewForms';
import NewKit from './components/NewKit';
@@ -21,6 +20,7 @@ import NewUser from './components/NewUser';
import AddNew from './components/pages/AddNew';
import NewKitItem from './components/NewKitItem';
import AddItemToKit from './components/AddItemToKit';
+import { AuthContext } from './components/auth/AuthContext';
import UserProfile from './components/pages/UserProfile';
@@ -28,89 +28,26 @@ import UserProfile from './components/pages/UserProfile';
function App() {
- const [loggedIn, setLoggedIn] = useState();
- const [user, setUser] = useState(null);
- const [tokenExpiration, setTokenExpiration] = useState(null);
-
- // Method handles login state and token, checking for existence or expiration
- useEffect(() => {
- const token = localStorage.getItem('jwt');
-
- if (token) {
- try {
- const decoded = jwtDecode(token);
- const now = Date.now() / 1000; // Current time in seconds
-
- if (decoded.exp > now) {
- setLoggedIn(true); // Token is valid
- setUser(decoded.user ? decoded.user : null); // Set user data
-
- // Calculate remaining time until token expiration
- const timeUntilExpiration = (decoded.exp - now) * 1000;
-
- // Notify 5 minutes before expiration
- if (timeUntilExpiration < 300000) {
- alert("Your session is about to expire. Please save your work.");
- }
-
- // Set token expiration time in state
- setTokenExpiration(timeUntilExpiration);
-
- } else {
- // Token is expired, clear it
- console.log("Token has expired, clearing JWT.");
- localStorage.removeItem('jwt');
- setLoggedIn(false);
- setUser(null);
- alert("Your session has expired. Please log in again.");
- }
- } catch (error) {
- console.error('Token decoding failed:', error);
- localStorage.removeItem('jwt');
- setLoggedIn(false);
- setUser(null);
- }
- } else {
- // No token, set to logged out state
- setLoggedIn(false);
- setUser(null);
- }
- }, []);
-
- // Logs out the user when the token expires
- useEffect(() => {
- if (tokenExpiration) {
- const timer = setTimeout(() => {
- console.log("Token has expired, logging out.");
- localStorage.removeItem('jwt');
- setLoggedIn(false);
- setUser(null);
- alert("Your session has expired. Please log in again.");
- }, tokenExpiration);
-
- return () => clearTimeout(timer);
- }
- }, [tokenExpiration]);
+ const { user } = useContext(AuthContext);
return (
// Sets routes for app navigation and passes props to the necessary components
<>
-
-
+
} />
} />
- } />
- } />
- } />
+ } />
+ } />
+ } />
} />
- }/>
- } />
- } />
+ }/>
+ } />
+ } />
}/>
- : } />
+ : } />
} >
} />
} />
@@ -120,7 +57,6 @@ function App() {
}/>
-
>
);
diff --git a/frontend/src/components/DashCardSet.jsx b/frontend/src/components/DashCardSet.jsx
index aa0faf7..96b62ab 100644
--- a/frontend/src/components/DashCardSet.jsx
+++ b/frontend/src/components/DashCardSet.jsx
@@ -29,11 +29,12 @@ const DashCardSet = () => {
}
} catch (error) {
console.error('Error fetching dashboard data:', error);
+ alert("An error occurred.")
}
};
fetchDashboardData();
- }, []);
+ }, [dashUrl]);
return (
// Displays data using the dashboard card component
diff --git a/frontend/src/components/DashTable.jsx b/frontend/src/components/DashTable.jsx
index 74023b2..1c29e0a 100644
--- a/frontend/src/components/DashTable.jsx
+++ b/frontend/src/components/DashTable.jsx
@@ -28,6 +28,7 @@ const DashTable = ({ apiEndpoint, headers, handleShow }) => {
} catch (err) {
setError(err.message);
console.error("Error fetching data:", err);
+ alert("A network error occurred.")
} finally {
setLoading(false);
}
diff --git a/frontend/src/components/EditModal.jsx b/frontend/src/components/EditModal.jsx
index e60c09a..76b4abb 100644
--- a/frontend/src/components/EditModal.jsx
+++ b/frontend/src/components/EditModal.jsx
@@ -103,6 +103,8 @@ if (recordType === 'user') {
console.log(`${recordType} updated successfully!`);
alert(`${recordType} updated successfully!`);
handleClose(); // Close the modal after success
+ } else {
+ alert("An error occurred with the update.")
}
};
diff --git a/frontend/src/components/Navigation.jsx b/frontend/src/components/Navigation.jsx
index ebc64a7..1a7b79e 100644
--- a/frontend/src/components/Navigation.jsx
+++ b/frontend/src/components/Navigation.jsx
@@ -1,10 +1,12 @@
-import React from "react"
+import React, { useContext } from "react"
import { Link, useNavigate } from "react-router-dom";
import CurrentUser from "./auth/CurrentUser";
+import { AuthContext } from "./auth/AuthContext";
import Logout from "./auth/Logout";
// Passed in logged in and user state
-function Navigation({ loggedIn, setLoggedIn, setUser, user }) {
+function Navigation() {
+ const { loggedIn, user } = useContext(AuthContext);
const navigate = useNavigate();
const handleDonateClick = (e) => {
e.preventDefault();
@@ -13,7 +15,7 @@ function Navigation({ loggedIn, setLoggedIn, setUser, user }) {
if (!user) {
// If user not logged in, navigate to login page
alert("You must be logged in to make a donation. Please log in or register if you haven't already.");
- navigate("/login")
+ navigate("/login");
} else {
// If user is logged in, navigate to the donation page
navigate("/donation");
@@ -32,7 +34,7 @@ function Navigation({ loggedIn, setLoggedIn, setUser, user }) {
{loggedIn && (
<>
-
+
>
)}
@@ -48,7 +50,7 @@ function Navigation({ loggedIn, setLoggedIn, setUser, user }) {
{loggedIn ? (
<>
-
+
>
) : (
<>
diff --git a/frontend/src/components/NewKit.jsx b/frontend/src/components/NewKit.jsx
index a814716..9d39fa6 100644
--- a/frontend/src/components/NewKit.jsx
+++ b/frontend/src/components/NewKit.jsx
@@ -50,12 +50,6 @@ const NewKit = () => {
console.log("New Kit added successfully!");
alert("New Kit added successfully!");
-
- // Clear input fields
- setName("");
- setDescription("");
- setGradeLevel("");
- setImage('');
// Redirect to admin page
navigate("/admin");
} else {
diff --git a/frontend/src/components/NewKitItem.jsx b/frontend/src/components/NewKitItem.jsx
index 5f10f24..b8713d0 100644
--- a/frontend/src/components/NewKitItem.jsx
+++ b/frontend/src/components/NewKitItem.jsx
@@ -4,7 +4,6 @@ import { useNavigate } from 'react-router-dom';
import { API_URL } from '../constants';
const NewKitItem = () => {
-
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [image, setImage] = useState(null);
diff --git a/frontend/src/components/PageWrapper.jsx b/frontend/src/components/PageWrapper.jsx
index 566a649..8745b0b 100644
--- a/frontend/src/components/PageWrapper.jsx
+++ b/frontend/src/components/PageWrapper.jsx
@@ -2,16 +2,17 @@ import React from 'react'
import Navigation from './Navigation';
import Footer from './Footer';
-// Passed in the content for the middle of the page as children and the logged in and user state. Navbar and footer displayed on every page.
-const PageWrapper = ({children, loggedIn, setLoggedIn, setUser, user }) => (
+// Passed in the content for the middle of the page as children. Navbar and footer displayed on every page.
+const PageWrapper = ({children }) => {
-
+ return (
-
+
{children}
+ );
-);
+};
export default PageWrapper;
diff --git a/frontend/src/components/auth/AuthContext.jsx b/frontend/src/components/auth/AuthContext.jsx
new file mode 100644
index 0000000..1099870
--- /dev/null
+++ b/frontend/src/components/auth/AuthContext.jsx
@@ -0,0 +1,72 @@
+import React, { createContext, useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { jwtDecode } from 'jwt-decode';
+import { API_URL2 } from '../../constants';
+
+export const AuthContext = createContext();
+
+export const AuthProvider = ({ children }) => {
+ const [loggedIn, setLoggedIn] = useState(false);
+ const [user, setUser] = useState(null);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const checkAuth = async () => {
+ const token = localStorage.getItem('jwt');
+ if (token) {
+ try {
+ const decoded = jwtDecode(token);
+ const now = Date.now() / 1000;
+
+ if (decoded.exp > now) {
+ const response = await fetch(`${API_URL2}/current_user`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ if (response.ok) {
+ const contentType = response.headers.get('content-type');
+ if (contentType && contentType.includes('application/json')) {
+ const data = await response.json();
+ setUser(data);
+ console.log("Current user data: ", data)
+ setLoggedIn(true);
+ } else {
+ throw new Error('Invalid response format');
+ }
+ } else {
+ handleUnauthorized();
+ }
+ } else {
+ handleUnauthorized();
+ }
+ } catch (error) {
+ console.error('Token decoding failed:', error);
+ handleUnauthorized();
+ }
+ } else {
+ handleUnauthorized();
+ }
+ };
+
+ const handleUnauthorized = () => {
+ logout();
+ };
+
+ checkAuth();
+ }, [navigate]);
+
+ const logout = () => {
+ setLoggedIn(false);
+ setUser(null);
+ localStorage.removeItem('jwt');
+ navigate("/login");
+ };
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/auth/CurrentUser.jsx b/frontend/src/components/auth/CurrentUser.jsx
index 3ca3690..b49d9cf 100644
--- a/frontend/src/components/auth/CurrentUser.jsx
+++ b/frontend/src/components/auth/CurrentUser.jsx
@@ -1,46 +1,19 @@
-import React, { useEffect, useState } from 'react';
-import { API_URL2 } from '../../constants';
+import React, { useContext } from 'react';
import { Link } from 'react-router-dom';
+import { AuthContext } from './AuthContext';
-const CurrentUser = ({ setLoggedIn, setUser, user }) => {
+const CurrentUser = () => {
+ const { user } = useContext(AuthContext);
- const userUrl = `${API_URL2}/current_user`
- // Fetches the current user whenever someone logs in
- useEffect(() => {
- const fetchUser = async () => {
- if(!user) {
- try {
- const response = await fetch(userUrl, {
- headers: {
- Authorization: `Bearer ${localStorage.getItem('jwt')}`,
- },
- });
- if (response.ok) {
- const data = await response.json();
- setUser(data);
- } else {
- console.log("No user logged in.")
- setUser(null);
- const error = await response.json()
- console.log(error)
- }
- } catch (error) {
- console.error("Error fetching current user");
- }
- }};
-
- fetchUser();
- }, [setLoggedIn, user, setUser, userUrl]);
- // Stretch Goal: Add admin dashboard
if (!user) return null;
return (
// Displays a welcome message and if admin, a link to access admin dashboard.
- Welcome, {user.first_name}!
+ Welcome, {user.name ? user.name.split(" ")[0] : "Guest"}!
- {user.role === 'admin' &&
+ {user && user.role === 'admin' &&
}
{user.role != 'admin' &&
}
diff --git a/frontend/src/components/auth/Login.jsx b/frontend/src/components/auth/Login.jsx
index 4d63c8f..0eb6c74 100644
--- a/frontend/src/components/auth/Login.jsx
+++ b/frontend/src/components/auth/Login.jsx
@@ -1,14 +1,15 @@
-import React, { useState } from "react";
+import React, { useContext, useState } from "react";
import { useNavigate } from "react-router-dom";
import { API_URL2 } from "../../constants";
import { Link } from "react-router-dom";
import CurrentUser from "./CurrentUser";
+import { AuthContext } from "./AuthContext";
-
-export default function Login({setLoggedIn}) {
+export default function Login() {
// Handles login, setting the data for the user
+ const { setLoggedIn } = useContext(AuthContext);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@@ -52,24 +53,17 @@ export default function Login({setLoggedIn}) {
if (jwt) {
// Store the JWT in local storage
localStorage.setItem('jwt', jwt.split(' ')[1]);
- console.log(localStorage.getItem('jwt'));
}
console.log("Login successful!");
setLoggedIn(true);
-
-
- // Clear input fields
- setEmail("");
- setPassword("");
-
navigate("/")
} else {
// Handle registration error
const errorData = await response.json();
- setLoginMessages(errorData.status.message || "Login failed");
+ setLoginMessages('Login failed. Please check your credentials and try again.');
// Access the status and message in the JSON response
@@ -79,7 +73,7 @@ export default function Login({setLoggedIn}) {
} catch (error) {
// Handle other errors
console.log("An error occurred:", error)
- setLoginMessages(error)
+ setLoginMessages("An error occurred, please try again.");
}
};
diff --git a/frontend/src/components/auth/Logout.jsx b/frontend/src/components/auth/Logout.jsx
index 739312a..015957c 100644
--- a/frontend/src/components/auth/Logout.jsx
+++ b/frontend/src/components/auth/Logout.jsx
@@ -1,9 +1,11 @@
-import React, { useState } from "react";
+import React, { useContext, useState } from "react";
import { API_URL2 } from "../../constants";
import { jwtDecode } from 'jwt-decode';
import { useNavigate } from "react-router-dom";
+import { AuthContext } from "./AuthContext";
-export default function Logout({ setLoggedIn, setUser }) {
+export default function Logout() {
+ const { logout } = useContext(AuthContext);
const [errorMessage, setErrorMessage] = useState("");
const navigate = useNavigate();
@@ -12,13 +14,10 @@ export default function Logout({ setLoggedIn, setUser }) {
const handleLogout = async () => {
const logoutUrl = `${API_URL2}/logout`
const jwt = localStorage.getItem('jwt');
- console.log(jwt)
if (!jwt) {
alert('You are already logged out or your session has expired.');
- setLoggedIn(false);
- setUser(null);
- navigate('/login')
+ logout();
return;
}
@@ -30,9 +29,7 @@ export default function Logout({ setLoggedIn, setUser }) {
if (decoded.exp < now) {
alert('Your session has expired. Please log in again.');
- localStorage.removeItem('jwt');
- setLoggedIn(false);
- navigate('/login');
+ logout();
return;
}
const response = await fetch(logoutUrl, {
@@ -45,26 +42,21 @@ export default function Logout({ setLoggedIn, setUser }) {
if (response.ok) {
alert('Logged out successfully.');
- localStorage.removeItem('jwt');
- setLoggedIn(false);
- setUser(null);
- navigate("/");
+ logout();
console.log("Logout successful.")
} else {
const message = response.json();
- setErrorMessage(message.error.message);
- console.error(errorMessage);
+ console.error(message.error.message);
+ logout();
alert("Your session has expired, please sign in again.");
- setLoggedIn(false);
- setUser(null);
- navigate("/login")
}
} catch (error) {
console.error("An error occurred:", error.message);
+ logout();
alert(error.message)
}
diff --git a/frontend/src/components/auth/Registration.jsx b/frontend/src/components/auth/Registration.jsx
index 153538b..baaeab6 100644
--- a/frontend/src/components/auth/Registration.jsx
+++ b/frontend/src/components/auth/Registration.jsx
@@ -57,16 +57,7 @@ export default function Registration() {
// Handle successful registration (e.g., redirect to another page)
console.log("Registration successful!");
setRegistrationMessages("Registration successful!");
-
-
- // Clear input fields
- setEmail("");
- setPassword("");
- // Removed name field and added firstName and lastName field
- setFirstName("");
- setLastName("");
-
-
+
navigate("/login");
} else {
// Handle registration error
diff --git a/frontend/src/components/pages/AdminDashboard.jsx b/frontend/src/components/pages/AdminDashboard.jsx
index 7fabe22..cae8ec9 100644
--- a/frontend/src/components/pages/AdminDashboard.jsx
+++ b/frontend/src/components/pages/AdminDashboard.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useRef } from "react";
+import React, { useState, useEffect, useRef, useContext } from "react";
import "bootstrap-icons/font/bootstrap-icons.css";
import SampleChart from "../SampleChart";
import SampleChartThree from "../SampleChartThree";
@@ -7,8 +7,10 @@ import { API_URL, API_URL2,ADMIN_URL } from "../../constants";
import DataEndpoint from "../DataEndpoint";
import DashTable from "../DashTable";
import EditModal from "../EditModal";
+import { AuthContext } from "../auth/AuthContext";
-const AdminDashboard = ({ user }) => {
+const AdminDashboard = () => {
+ const { user, setLoggedIn, setUser } = useContext(AuthContext);
// Necessary api endpoints
const userUrl = `${API_URL}/users`;
const kitsUrl = `${API_URL}/kits`;
@@ -209,7 +211,7 @@ const AdminDashboard = ({ user }) => {
}, [selectedEndpoint]);
- if (user.role !== "admin") {
+ if (!user || user.role !== "admin") {
return (
@@ -218,7 +220,6 @@ const AdminDashboard = ({ user }) => {
);
}
- console.log("selectedEndpoint:", selectedEndpoint);
return (
// Displays cards, graph data, and Data tables
@@ -233,7 +234,7 @@ const AdminDashboard = ({ user }) => {
>
- Admin Dashboard
+ Admin Dashboard
@@ -312,7 +313,7 @@ const AdminDashboard = ({ user }) => {
headers={headers.userUrl}
apiEndpoint={userUrl}
handleShow={(item) => handleShow(item, "user")}
- />
+ />
)}
{selectedEndpoint === kitsUrl && (
{error}}
- {kits.map(
- (
- kit // Map through the list of kits
- ) => (
- <>
-
-
+ {kits.map(kit => (
+
- >
)
)}
diff --git a/frontend/src/components/pages/RequestKit.jsx b/frontend/src/components/pages/RequestKit.jsx
index 2f8d5aa..1b329c2 100644
--- a/frontend/src/components/pages/RequestKit.jsx
+++ b/frontend/src/components/pages/RequestKit.jsx
@@ -1,11 +1,12 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useState, useContext } from "react";
import { API_URL } from "../../constants";
import { useLocation, useNavigate } from "react-router-dom";
+import { AuthContext } from "../auth/AuthContext";
import * as Yup from "yup";
-function RequestKit({ user }) {
-
+function RequestKit() {
+ const { user } = useContext(AuthContext);
const validationSchema = Yup.object().shape({
phone: Yup.string().required('Phone is required'),
schoolAddress: Yup.string().required('School address is required'),
@@ -55,7 +56,6 @@ function RequestKit({ user }) {
},
};
-
try {
// Send POST request to registration endpoint
await validationSchema.validate(orderForm, { abortEarly: false });
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index 89f91e5..1585f52 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -1,10 +1,16 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
+import { BrowserRouter as Router } from 'react-router-dom'
import App from './App.jsx'
+import { AuthProvider } from './components/auth/AuthContext.jsx'
import './index.css'
createRoot(document.getElementById('root')).render(
-
+
+
+
+
+
,
-)
+);