diff --git a/client/src/Assets/Images/secretCloset/.DS_Store b/.DS_Store
similarity index 87%
rename from client/src/Assets/Images/secretCloset/.DS_Store
rename to .DS_Store
index 5008ddf..7a73315 100644
Binary files a/client/src/Assets/Images/secretCloset/.DS_Store and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index 382f778..d296706 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
.env
-node_modules
-.env
node_modules/
+package-lock.json
\ No newline at end of file
diff --git a/Client/Auth/AuthContext.js b/Client/Auth/AuthContext.js
new file mode 100644
index 0000000..86e754e
--- /dev/null
+++ b/Client/Auth/AuthContext.js
@@ -0,0 +1,93 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import axios from 'axios';
+
+/**
+ * Context object created
+ */
+const AuthContext = createContext();
+
+/**
+ * Functional component that will use the Provider functionality in the AuthContext to
+ * share the state and functions with child components.
+ * AuthContext : { ..., Provider: {the auth state}}
+ * @param {*} children deconstructed prop - refers to any nested components wrapped by AuthProvider
+ * in the virtual DOM (see client/index.js)
+ * @returns
+ */
+export const AuthProvider = ({ children }) => {
+
+ /**
+ * State variables and setters for holding top level authentication information
+ */
+ const [user, setUser] = useState(null);
+ const [session, setSession] = useState(null);
+ const [avatarUrl, setAvatarUrl] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ /**
+ * sessionStorage does not persist beyond the current session,
+ * only accessible in the same window/tab and cleared when either closed.
+ * Stored in memory on web browser, not written to disk like cookies
+ */
+ useEffect(() => {
+ const storedUser = JSON.parse(sessionStorage.getItem('user'));
+ const storedSession = JSON.parse(sessionStorage.getItem('session'));
+ const storedAvatarUrl = sessionStorage.getItem('avatarUrl');
+ if (storedUser) {
+ setUser(storedUser);
+ }
+ if (storedSession) {
+ setSession(storedSession);
+ }
+ if (storedAvatarUrl) {
+ setAvatarUrl(storedAvatarUrl);
+ }
+ setLoading(false); // Indicates that the data fetching has completed
+ }, []);
+
+ /**
+ * Async function that makes a request
+ * @param {*} user
+ * @param {*} session
+ */
+ const login = async (user, session) => {
+ setUser(user);
+ setSession(session);
+ fetchAvatarUrl(user.id);
+ sessionStorage.setItem('user', JSON.stringify(user));
+ sessionStorage.setItem('session', JSON.stringify(session));
+ };
+
+ const fetchAvatarUrl = async (userId) => {
+ try {
+ const response = await axios.get(`/api/user-profile/${userId}`);
+ const avatarUrl = response.data.avatar_url;
+ setAvatarUrl(avatarUrl);
+ sessionStorage.setItem('avatarUrl', avatarUrl);
+ } catch (error) {
+ console.error('Error fetching avatar URL:', error);
+ }
+ };
+
+ const logout = () => {
+ setUser(null);
+ setSession(null);
+ setAvatarUrl(null);
+ sessionStorage.removeItem('user');
+ sessionStorage.removeItem('session');
+ sessionStorage.removeItem('avatarUrl');
+ };
+
+ /**
+ * Using React
+ */
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAuth = () => {
+ return useContext(AuthContext);
+};
diff --git a/Client/Auth/AuthProviderDebugger.jsx b/Client/Auth/AuthProviderDebugger.jsx
new file mode 100644
index 0000000..e174172
--- /dev/null
+++ b/Client/Auth/AuthProviderDebugger.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import { useAuth } from './AuthContext'; // Adjust the path as needed
+
+const style = {
+ position: 'fixed',
+ bottom: '10px',
+ right: '10px',
+ width: 300,
+ bgcolor: 'background.paper',
+ border: '2px solid #000',
+ boxShadow: 24,
+ p: 2,
+ zIndex: 1000,
+ overflow: 'auto',
+ maxHeight: '50vh',
+};
+
+const AuthProviderDebugger = () => {
+ const { user, session, avatarUrl } = useAuth();
+
+ return (
+
+
+ AuthContext Debug Info
+
+
+ {JSON.stringify({ user, session, avatarUrl }, null, 2)}
+
+
+ );
+};
+
+export default AuthProviderDebugger;
diff --git a/Client/Auth/Login.jsx b/Client/Auth/Login.jsx
new file mode 100644
index 0000000..dd272e0
--- /dev/null
+++ b/Client/Auth/Login.jsx
@@ -0,0 +1,79 @@
+import React, { useState } from 'react';
+import axios from 'axios';
+import { useHistory } from 'react-router-dom';
+import { Typography, CircularProgress, Snackbar, Alert } from '@mui/material';
+import { useAuth } from './AuthContext';
+import AuthNavigationButton from '../components/AuthNavigationButton';
+import '../styles/AuthForm.css';
+
+/**
+ * User-side functionality for existing user at login and route path
+ * @returns
+ */
+const Login = () => {
+ /**
+ * local state for email, password, loading
+ */
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: '' });
+ const history = useHistory();
+ const { login } = useAuth();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setLoading(true);
+ try {
+ const response = await axios.post('/api/login', { email, password });
+ const { user, session } = response.data;
+ login(user, session);
+ setSnackbar({ open: true, message: 'Login successful', severity: 'success' });
+ setTimeout(() => {
+ history.push('/search');
+ }, 1200);
+ } catch (error) {
+ setSnackbar({ open: true, message: 'Login failed', severity: 'error' });
+ console.error('AxiosError:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleCloseSnackbar = () => {
+ setSnackbar({ ...snackbar, open: false });
+ };
+
+ return (
+
+ {user ? `Logged in as: ${user.user_metadata.first_name}` : 'Not logged in'}
+
+ );
+};
+
+export default LoginStatus;
diff --git a/Client/Auth/Signup.jsx b/Client/Auth/Signup.jsx
new file mode 100644
index 0000000..0a8db8a
--- /dev/null
+++ b/Client/Auth/Signup.jsx
@@ -0,0 +1,84 @@
+import React, { useState } from 'react';
+import axios from 'axios';
+import { useHistory } from 'react-router-dom';
+import { Typography, CircularProgress, Snackbar, Alert } from '@mui/material';
+import { useAuth } from './AuthContext';
+import AuthNavigationButton from '../components/AuthNavigationButton';
+import '../styles/AuthForm.css';
+
+const Signup = () => {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [firstName, setFirstName] = useState('');
+ const [lastName, setLastName] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: '' });
+ const history = useHistory();
+ const { login } = useAuth();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setLoading(true);
+ try {
+ const response = await axios.post('/api/signup', { email, password, firstName, lastName });
+ const { user, session } = response.data;
+ login(user, session);
+ setTimeout(() => {
+ setSnackbar({ open: true, message: 'Signup successful', severity: 'success' });
+ setTimeout(() => {
+ history.push('/search');
+ }, 2000); // Redirect after 2 seconds to ensure the user sees the Snackbar
+ }, 1000); // Ensure spinner shows for at least 1 second
+ } catch (error) {
+ setTimeout(() => {
+ setSnackbar({ open: true, message: 'Signup failed', severity: 'error' });
+ }, 1000); // Ensure spinner shows for at least 1 second
+ console.error('AxiosError:', error);
+ } finally {
+ setTimeout(() => {
+ setLoading(false);
+ }, 1000); // Ensure spinner shows for at least 1 second
+ }
+ };
+
+ const handleCloseSnackbar = () => {
+ setSnackbar({ ...snackbar, open: false });
+ };
+
+ return (
+
+
My Account
+
+
+ First Name: {first_name}
+
+
+ {editField === 'first_name' && (
+
handleFieldUpdate('first_name', first_name)}
+ />
+ )}
+
+
+
+ Last Name: {last_name}
+
+
+ {editField === 'last_name' && (
+
handleFieldUpdate('last_name', last_name)}
+ />
+ )}
+
+
+
+ Email: {email}
+
+
+ {editField === 'email' && (
+
handleFieldUpdate('email', email)}
+ />
+ )}
+
+
+
+ Password: ******
+
+
+ {editField === 'password' && (
+
handleFieldUpdate('password', password)}
+ />
+ )}
+
+
+
+ Avatar:
+
+
+
+
+
+ );
+};
+
+export default MyAccount;
diff --git a/Client/Routes.js b/Client/Routes.js
new file mode 100644
index 0000000..ba464a1
--- /dev/null
+++ b/Client/Routes.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
+import Login from './Auth/Login';
+import Signup from './Auth/Signup';
+import LoginStatus from './Auth/LoginStatus';
+import ImageSearch from './components/StyleImageSearchPage';
+import ResponsiveAppBar from './components/ResponsiveAppBar';
+import HomePage from './Pages/HomePage';
+import About from './Pages/About';
+import MyAccount from './Pages/MyAccount';
+
+const Routes = () => {
+ return (
+