Skip to content

Commit

Permalink
feat: Add US OpenClassrooms-Student-Center#5 & 6 (Update Profile & Re…
Browse files Browse the repository at this point in the history
…dux) Profile components can update user + NavBar display user name + some comments
  • Loading branch information
reffinger committed Feb 16, 2024
1 parent 33f1a7d commit 38047a7
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 13 deletions.
14 changes: 11 additions & 3 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./src/assets/img/argentBankLogo.png" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="icon"
type="image/svg+xml"
href="./src/assets/img/argentBankLogo.png"
/>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"

/>
<title>ArgentBank</title>
</head>
<body>
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,19 @@ body {
font-size: 1.2rem;
}

.edit-input {
display: flex;
justify-content: center;
gap: 1rem;
font-size: 1.2rem;
margin-bottom: 1rem;
}

.edit-input input {
padding: 5px;
font-size: 1.2rem;
}

.footer {
display: flex;
justify-content: center;
Expand Down Expand Up @@ -260,6 +273,13 @@ body {
color: #fff;
font-weight: bold;
padding: 10px;
width: 110px;
}

.edit-button-wrapper {
display: flex;
justify-content: center;
gap: 1rem;
}

.header {
Expand Down
17 changes: 15 additions & 2 deletions frontend/src/layout/NavBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import { authenticate } from '../service/user/userApi';

const Navbar = () => {
const dispatch = useDispatch();
// Get the isAuthenticated and user state from the store
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
const user = useSelector((state) => state.user.user);

// Handle the sign in and sign out functionality
const handleAuth = () => {
if (isAuthenticated) {
localStorage.removeItem('token'); // Remove the token from local storage
Expand All @@ -25,13 +28,23 @@ const Navbar = () => {
<h1 className='sr-only'>Argent Bank</h1>
</NavLink>
<div>
<NavLink className='main-nav-item' to='/'>
<i className='fa fa-user-circle'></i>
{user && isAuthenticated ? `${user.body.firstName}` : null}
</NavLink>
<NavLink
className='main-nav-item'
to={isAuthenticated ? '/' : '/login'}
onClick={handleAuth}
>
<i className='fa fa-user-circle'></i>
{isAuthenticated ? 'Sign Out' : 'Sign In'}
{isAuthenticated ? (
<>
<i className='fa fa-sign-out'></i>
Sign Out
</>
) : (
'Sign In'
)}
</NavLink>
</div>
</nav>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Login = () => {
dispatch(authenticate({ email, password }));
};

// If the user is authenticated, redirect to the profile page
useEffect(() => {
if (isAuthenticated) {
navigate('/profile');
Expand Down
72 changes: 65 additions & 7 deletions frontend/src/pages/Profile.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,83 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUserProfile } from '../service/user/userApi';
import { fetchUserProfile, updateUserProfile } from '../service/user/userApi';

const Profile = () => {
const user = useSelector((state) => state.user.user);
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [isEditing, setIsEditing] = useState(false);
const dispatch = useDispatch();
const profile = useSelector((state) => state.user);

// Fetch the user profile on component mount
useEffect(() => {
dispatch(fetchUserProfile());
}, [dispatch]);

console.log(profile);
return profile.user ? (
// Set the first and last name from the user object
useEffect(() => {
if (user) {
const { firstName: userFirstName, lastName: userLastName } = user.body;
setFirstName(userFirstName);
setLastName(userLastName);
}
}, [user]);

// Update the user profile
const handleUpdate = (e) => {
e.preventDefault();
dispatch(updateUserProfile({ firstName, lastName }));
setIsEditing(false);
};

return user ? (
<main className='main bg-dark'>
<div className='header'>
<h1>
Welcome back
<br />
{profile.user.body.firstName + ' ' + profile.user.body.lastName}!
{`${firstName} ${lastName}`}!
</h1>
<button className='edit-button'>Edit Name</button>
{isEditing ? (
<form>
<div className='edit-input'>
<input
type='text'
id='firstName'
name='firstName'
placeholder={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input
type='text'
id='lastName'
name='lastName'
placeholder={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</div>
<div className='edit-button-wrapper'>
<button
className='edit-button'
type='submit'
onClick={handleUpdate}
>
Save
</button>
<button
className='edit-button'
type='submit'
onClick={() => setIsEditing(false)}
>
Cancel
</button>
</div>
</form>
) : (
<button className='edit-button' onClick={() => setIsEditing(true)}>
Edit Name
</button>
)}
</div>
<h2 className='sr-only'>Accounts</h2>
<section className='account'>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/service/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const preloadedState = {
},
};

// The store is created with the auth and user reducers.
export const store = configureStore({
reducer: {
auth: authReducer,
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/service/user/userApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,27 @@ export const fetchUserProfile = createAsyncThunk(
}
}
);

// Async thunk to update user profile
export const updateUserProfile = createAsyncThunk(
'user/updateProfile',
async ({ firstName, lastName }) => {
const token = localStorage.getItem('token');
const response = await fetch('http://localhost:3001/api/v1/user/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ firstName, lastName }),
});
const data = await response.json();
if (response.ok) {
console.log(data.message);
return data;
} else {
window.alert(data.message);
throw new Error(data.message);
}
}
);
12 changes: 11 additions & 1 deletion frontend/src/service/user/userSlice.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createSlice } from '@reduxjs/toolkit';
import { authenticate, fetchUserProfile } from './userApi';
import { authenticate, fetchUserProfile, updateUserProfile } from './userApi';

// The authSlice reducer manages the state of the user's authentication status.
const authSlice = createSlice({
Expand All @@ -18,6 +18,7 @@ const authSlice = createSlice({
},
});

// The userSlice reducer manages the state of the user's profile and loading status.
const userSlice = createSlice({
name: 'user',
initialState: { user: null, loading: false },
Expand All @@ -33,6 +34,15 @@ const userSlice = createSlice({
})
.addCase(fetchUserProfile.rejected, (state) => {
state.loading = false;
})
.addCase(updateUserProfile.pending, (state) => {
state.loading = true;
})
.addCase(updateUserProfile.fulfilled, (state, action) => {
state.user = action.payload;
state.loading = false;
}).addCase(updateUserProfile.rejected, (state) => {
state.loading = false;
});
},
});
Expand Down

0 comments on commit 38047a7

Please sign in to comment.