add task solution#300
Conversation
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am requesting changes because there are still a few critical blockers against the checklist that break required functionality. Most importantly, your backend authMiddleware validates the access token but never assigns the decoded user to req.user, yet updateProfile relies on req.user.id and req.user.email; this means all profile updates (name, password, email) will fail at runtime and violates [CHECKLIST ITEM #19–#21]. On the frontend, after successful activation you redirect to /?activated=true instead of /profile, and after successful login you don’t navigate to /profile at all, which both conflict with the requirements to redirect to the profile page after activation and login ([CHECKLIST ITEM #7] and [CHECKLIST ITEM #10]). Your login UI also never displays state.auth.error, so inactive users don’t see the backend message asking them to activate their email, which is required by [CHECKLIST ITEM #9].
The critical issues you must fix are:
- In
user.middleware.js(and any equivalent middleware files), after validating the access token, setreq.user = userData;beforenext()soupdateProfilecan access the current user. - In
ActivationPage, change the post-success navigation target fromnavigate('/?activated=true')tonavigate('/profile')(keeping or removing the delay as you prefer). - In
LoginModal, either awaitloginUser(...).unwrap()and then callnavigate('/profile'), or observeisAuthviauseSelectorand redirect to/profilewhen it becomestrue. - Also in
LoginModal, selectstate.auth.errorfrom Redux and render it in the modal so backend errors, especially “Акаунт не активовано. Перевірте пошту”, are visible to users.
Minor but recommended improvements (not blockers) include adding route guards that redirect authenticated users away from public auth pages (activation, forgot/reset, home) to strictly enforce “only non authenticated”, and using the updateProfile thunk in ProfilePage to keep Redux user data in sync and show success/error messages. Overall you’ve implemented the majority of the flows correctly; once you address the middleware bug and the activation/login redirect and error-handling issues, your solution will be in good shape to meet the full specification.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
| box-sizing: border-box; | ||
| } | ||
|
|
There was a problem hiding this comment.
The requirements state that if a user is not active, you should ask them to activate their email (checklist item #9). Currently, you dispatch loginUser but never read state.auth.error or show any feedback, so an inactive-account error from the backend will be invisible. Consider selecting the error from the store and rendering it in the modal so users know they must activate via the email link.
| color: var(--text-primary); | ||
| font-family: 'Inter', system-ui, sans-serif; | ||
| line-height: 1.6; | ||
| transition: background-color 0.3s, color 0.3s; | ||
| } | ||
|
|
||
| /* Фікс накладання: додаємо gap для контейнерів */ | ||
| .page-container { | ||
| min-height: 100vh; | ||
| padding: 2rem; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 1.5rem; /* Запобігає прилипанню елементів */ | ||
| } | ||
|
|
||
| .centered { | ||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| flex-direction: column; | ||
| } | ||
|
|
||
| .auth-card { | ||
| background: var(--card-bg); | ||
| padding: 2.5rem; | ||
| border-radius: 12px; | ||
| border: 1px solid var(--border); | ||
| width: 90%; | ||
| max-width: 400px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 1rem; /* Відступи всередині картки */ | ||
| } | ||
|
|
||
| /* Навігація: додаємо flex-wrap, щоб меню не наїжджало на лого при вузькому екрані */ | ||
| .navbar { |
There was a problem hiding this comment.
Checklist item #10 requires redirecting to the profile page after a successful login. This component does not navigate anywhere on successful authentication, so users remain on the home page. You could observe isAuth from the Redux state with useSelector and call navigate('/profile') when it becomes true, or handle this in a higher-level route component.
| const handleSubmit = async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| dispatch(loginUser({ email, password })); |
There was a problem hiding this comment.
According to checklist item #10, after a successful login the user should be redirected to the profile page, but this handler only dispatches loginUser without navigating. Consider awaiting the thunk and, on success, calling navigate('/profile') here.
| <form className="auth-form" onSubmit={handleSubmit}> | ||
| <div className="form-group"> | ||
| <label>Електронна пошта</label> | ||
| <input | ||
| type="email" | ||
| value={email} | ||
| onChange={(e) => setEmail(e.target.value)} | ||
| placeholder="email@example.com" | ||
| required | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="form-group"> | ||
| <label>Пароль</label> | ||
| <input | ||
| type="password" | ||
| value={password} | ||
| onChange={(e) => setPassword(e.target.value)} | ||
| placeholder="••••••••" | ||
| required | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="form-helpers"> | ||
| <button type="button" className="link-btn" onClick={handleForgot}> | ||
| Забули пароль? | ||
| </button> | ||
| </div> | ||
|
|
||
| <button type="submit" className="btn-primary full-width">Увійти</button> |
There was a problem hiding this comment.
The requirements state that inactive users should be prompted to activate their email on login (checklist item #9). The backend returns a specific error for non-active accounts, but this modal doesn’t read or display any error message from the auth slice, so the user never sees that hint.
| try { | ||
| await dispatch(registerUser({ name, email, password })).unwrap(); | ||
| setIsSuccess(true); | ||
| } catch (error) { | ||
| console.error(error); | ||
| } |
There was a problem hiding this comment.
In case of registration failure (e.g., email already in use or backend password validation), you catch the error and only log it to the console. While not explicitly required by the checklist, surfacing this error to the user (e.g., via local state or authSlice.error) would make the registration flow clearer and help users fix issues.
| {!isAuth ? ( | ||
| <div className="navbar-menu"> | ||
| <button className="btn-secondary" onClick={() => dispatch(openRegisterModal())}> | ||
| Зареєструватися | ||
| </button> | ||
| <button className="btn-primary" onClick={() => dispatch(openLoginModal())}> | ||
| Увійти | ||
| </button> | ||
| </div> | ||
| ) : ( | ||
| <div className="navbar-menu"> | ||
| <span className="user-name">Вітаю, {user?.name || 'Користувач'}</span> | ||
| <button className="btn-secondary" onClick={() => navigate('/profile')}> | ||
| Профіль | ||
| </button> | ||
| <button className="btn-danger" onClick={handleLogout}> | ||
| Вийти | ||
| </button> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Your Navbar hides the register/login buttons when isAuth is true, which partially enforces "only non authenticated" for those actions. However, users can still directly access other non-auth-only routes like forgot/reset/activation via URL. To strictly respect checklist items #1, #5, #8, and #13, consider adding route guards or redirects on those pages when isAuth is true.
| const navigate = useNavigate(); | ||
|
|
||
| const handleLogout = () => { | ||
| dispatch(logoutLocal()); | ||
| navigate('/'); | ||
| }; | ||
|
|
||
| return ( |
There was a problem hiding this comment.
The ProtectedRoute correctly prevents unauthenticated users from accessing the profile page by redirecting to /, satisfying the "Profile page only for authenticated" requirement (checklist item #18). Note that similar guarding is not applied to non-auth-only pages, which is why authenticated users can still visit forgot/reset/activation routes.
| const ProtectedRoute = ({ children }: ProtectedRouteProps) => { | ||
| const { isAuth } = useSelector((state: RootState) => state.auth); | ||
|
|
||
| if (!isAuth) { | ||
| return <Navigate to="/" replace />; |
There was a problem hiding this comment.
Using ProtectedRoute to guard /profile ensures the profile page is available only for authenticated users, satisfying checklist item #18. However, there is no corresponding guard that prevents authenticated users from opening the login/register flows; if you need strict “only non authenticated” enforcement (items #1 and #8), consider redirecting authenticated users away from the public auth UI.
|
|
||
| if (!isAuth) { | ||
| return <Navigate to="/" replace />; |
There was a problem hiding this comment.
The navbar correctly hides login/register buttons when isAuth is true and shows profile/logout instead, and handleLogout returns the user to the home route. This effectively meets the logout redirect requirement (#12), but if the spec expects a dedicated /login page, you might want to navigate to such a route instead of /.
| setTimeout(() => { | ||
| navigate('/?activated=true'); |
There was a problem hiding this comment.
The requirements state that after activation you should redirect to the Profile page (checklist item #7), but here you navigate to the home route with a query ('/?activated=true'). To align with the spec, this redirect should go to /profile once activation succeeds.
|
|
||
| const HomePage = () => { | ||
| const { isLoginOpen, isRegisterOpen } = useSelector( | ||
| (state: RootState) => state.auth | ||
| ); | ||
|
|
||
| return ( | ||
| <div className="page-container"> | ||
| <Navbar /> | ||
|
|
There was a problem hiding this comment.
This page is used as the 404 route and returns a clear "Сторінку не знайдено" message with a button to go home, which fulfills checklist item #22 about having a 404 page for all other routes.
| const handleNameChange = async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| await authService.updateProfile({ name }); | ||
| }; | ||
|
|
||
| const handlePasswordChange = async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| if (passwords.new !== passwords.confirm) return alert('Паролі не збігаються'); | ||
| await authService.updateProfile({ | ||
| oldPassword: passwords.old, | ||
| newPassword: passwords.new, | ||
| confirmation: passwords.confirm | ||
| }); | ||
| }; | ||
|
|
||
| const handleEmailChange = async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| await authService.updateProfile({ | ||
| newEmail: emailData.newEmail, | ||
| password: emailData.confirmPassword | ||
| }); |
There was a problem hiding this comment.
The profile handlers call authService.updateProfile directly but don’t handle any response or errors in the UI. Even though the checklist doesn’t mandate success/error messages, using the existing updateProfile thunk from authSlice here would keep Redux state (e.g., updated name/email) in sync and allow you to surface backend errors to the user.
| confirmation: passwords.confirm | ||
| }); | ||
| }; | ||
|
|
||
| const handleEmailChange = async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| await authService.updateProfile({ | ||
| newEmail: emailData.newEmail, | ||
| password: emailData.confirmPassword | ||
| }); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="page-container profile-layout"> | ||
| <aside className="profile-sidebar"> | ||
| <div className="profile-avatar">{user?.name?.charAt(0) || 'U'}</div> | ||
| <h3>{user?.name || 'Користувач'}</h3> | ||
| <p>{user?.email || 'email@example.com'}</p> | ||
| {user?.isActive && <span className="badge-active">Акаунт активовано</span>} | ||
| </aside> | ||
|
|
||
| <main className="profile-content"> | ||
| <h2>Налаштування профілю</h2> | ||
|
|
||
| <section className="profile-section"> | ||
| <h3>Змінити ім'я</h3> |
There was a problem hiding this comment.
This reset password form correctly enforces that password and confirmation must be equal before submitting, satisfying checklist item #16, and shows a success page with a link to login (item #17). Ensure that the login route or modal is easily reachable from / so the "Перейти до входу" link leads users to an actual login UI.
| const ResetPassword = () => { | ||
| const { token } = useParams<{ token: string }>(); | ||
|
|
||
| const [password, setPassword] = useState(''); | ||
| const [confirmation, setConfirmation] = useState(''); | ||
| const [isSuccess, setIsSuccess] = useState(false); | ||
| const [error, setError] = useState(''); | ||
|
|
||
| const handleSubmit = async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| setError(''); | ||
|
|
||
| if (password !== confirmation) { | ||
| return setError('Паролі не збігаються!'); | ||
| } | ||
|
|
||
| try { | ||
| if (token) { | ||
| await authService.resetPassword(token, password, confirmation); | ||
| setIsSuccess(true); | ||
| } |
There was a problem hiding this comment.
This page correctly meets the profile requirements by allowing name change, password change with old/new/confirmation, and email change with current password and new email data, while the backend sends notification to the old email. For a better UX, consider handling and displaying success/failure messages from authService.updateProfile rather than just awaiting the calls.
|
|
||
| if (password !== confirmation) { | ||
| return setError('Паролі не збігаються!'); | ||
| } | ||
|
|
||
| try { | ||
| if (token) { | ||
| await authService.resetPassword(token, password, confirmation); | ||
| setIsSuccess(true); | ||
| } | ||
| } catch (err) { | ||
| const errorData = err as AxiosErrorResponse; | ||
| setError(errorData.response?.data?.error || 'Токен застарів або невалідний'); | ||
| } | ||
| }; | ||
|
|
||
| if (isSuccess) { | ||
| return ( | ||
| <div className="page-container centered"> | ||
| <div className="auth-card text-center"> | ||
| <h2>Пароль змінено!</h2> | ||
| <p>Тепер ви можете увійти в акаунт з новими даними.</p> | ||
| <Link to="/" className="btn-primary full-width">Перейти до входу</Link> | ||
| </div> | ||
| </div> | ||
| ); |
| }); | ||
|
|
||
| api.interceptors.response.use( | ||
| (response) => response, | ||
| async (error: AxiosError) => { |
There was a problem hiding this comment.
You have a logout method on authService, but the slice never uses it; all logout behaviour is purely local (logoutLocal). If your backend needs to clear refresh tokens or session data, you may want to add a logoutUser thunk that calls authService.logout and then dispatches logoutLocal to keep server and client in sync.
| forgotPassword: async (email: string): Promise<AuthResponse> => { | ||
| const { data } = await api.post('/forgot-password', { email }); | ||
| return data; | ||
| }, | ||
|
|
||
| activation: async (email: string, token: string): Promise<AuthResponse> => { | ||
| const { data } = await api.get(`/auth/activation/${email}/${token}`); | ||
| return data; | ||
| }, |
There was a problem hiding this comment.
The refresh interceptor correctly dispatches auth/setCredentials on success and auth/logoutLocal on failure, but the app’s logout flow in Navbar currently uses only logoutLocal and doesn’t call the /logout endpoint. While the checklist doesn’t explicitly demand server-side logout, using authService.logout() in addition to logoutLocal would ensure refresh tokens are cleared on the backend too.
| async (credentials, { rejectWithValue }) => { | ||
| try { | ||
| return await authService.login(credentials.email, credentials.password); | ||
| } catch (err: unknown) { | ||
| const error = err as AxiosErrorResponse; | ||
| return rejectWithValue(error.response?.data?.error || 'Помилка входу'); | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| export const updateProfile = createAsyncThunk<UpdateProfileData, UpdateProfileData, { rejectValue: string }>( | ||
| 'auth/updateProfile', | ||
| async (profileData, { rejectWithValue }) => { | ||
| try { | ||
| await authService.updateProfile(profileData); | ||
| return profileData; | ||
| } catch (err: unknown) { | ||
| const error = err as AxiosErrorResponse; | ||
| return rejectWithValue(error.response?.data?.error || 'Помилка оновлення'); | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| const initialState: AuthState = { | ||
| user: null, | ||
| accessToken: null, | ||
| isAuth: false, | ||
| isLoginOpen: false, | ||
| isRegisterOpen: false, | ||
| isLoading: false, | ||
| error: null, | ||
| }; | ||
|
|
||
| const authSlice = createSlice({ | ||
| name: 'auth', | ||
| initialState, | ||
| reducers: { | ||
| openLoginModal: (state) => { | ||
| state.isLoginOpen = true; | ||
| state.isRegisterOpen = false; | ||
| state.error = null; | ||
| }, | ||
| closeLoginModal: (state) => { | ||
| state.isLoginOpen = false; | ||
| }, | ||
| openRegisterModal: (state) => { | ||
| state.isRegisterOpen = true; | ||
| state.isLoginOpen = false; | ||
| state.error = null; | ||
| }, | ||
| closeRegisterModal: (state) => { | ||
| state.isRegisterOpen = false; | ||
| }, | ||
| setCredentials: (state, action: PayloadAction<AuthResponse>) => { | ||
| state.user = action.payload.user; | ||
| state.accessToken = action.payload.accessToken; | ||
| state.isAuth = true; | ||
| }, | ||
| logoutLocal: (state) => { | ||
| state.user = null; | ||
| state.accessToken = null; | ||
| state.isAuth = false; | ||
| } | ||
| }, | ||
| extraReducers: (builder) => { | ||
| builder | ||
| .addCase(loginUser.pending, (state) => { | ||
| state.isLoading = true; | ||
| state.error = null; | ||
| }) | ||
| .addCase(loginUser.fulfilled, (state, action: PayloadAction<AuthResponse>) => { | ||
| state.isLoading = false; | ||
| state.isAuth = true; | ||
| state.user = action.payload.user; | ||
| state.accessToken = action.payload.accessToken; | ||
| state.isLoginOpen = false; | ||
| }) | ||
| .addCase(loginUser.rejected, (state, action) => { | ||
| state.isLoading = false; | ||
| state.error = action.payload ? String(action.payload) : 'Помилка входу'; |
There was a problem hiding this comment.
loginUser correctly propagates backend error messages (including the inactive-account case) into state.error. However, your login UI currently doesn’t read or display this error, so users never see that they must activate their email (checklist item #9). Ensure the login modal selects and renders state.auth.error.
| .addCase(registerUser.pending, (state) => { | ||
| state.isLoading = true; | ||
| state.error = null; | ||
| }) | ||
| .addCase(registerUser.fulfilled, (state) => { | ||
| state.isLoading = false; | ||
| state.isRegisterOpen = false; | ||
| state.isLoginOpen = true; | ||
| }) | ||
| .addCase(registerUser.rejected, (state, action) => { | ||
| state.isLoading = false; | ||
| state.error = action.payload ? String(action.payload) : 'Помилка реєстрації'; | ||
| }) |
There was a problem hiding this comment.
On registerUser.fulfilled you close the register modal and open the login modal, which is a good flow. For full alignment with the requirements, make sure the activation page and subsequent login redirect to the profile page (item #7 and #10), which will rely on setCredentials and isAuth managed here.
| const userData = jwt.validateAccessToken(accessToken); | ||
|
|
||
| if (!userData) { | ||
| res.status(401).json({ message: 'Invalid token' }); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| next(); |
There was a problem hiding this comment.
You validate the access token but never attach the decoded user information to req. Since updateProfile in the controller expects req.user.id and req.user.email, req.user will be undefined, breaking profile updates (checklist items #19–#21). After successful validation, assign req.user = userData before calling next().
| return res | ||
| .status(400) | ||
| .json({ error: "Усі поля (name, email, password) є обов'язковими" }); | ||
| } | ||
|
|
||
| const newUser = await UserService.register(name, email, password); | ||
|
|
||
| return res.status(201).json(newUser); | ||
| } catch (error) { |
There was a problem hiding this comment.
authMiddleware validates the access token but never attaches the decoded user data to req.user, yet userController.updateProfile expects req.user.id and req.user.email. This will make all profile updates fail and violates the profile-related requirements (checklist items #19–#21). You should set req.user = userData before calling next().
| activation: async (req, res) => { | ||
| try { | ||
| const { email, token } = req.params; | ||
|
|
||
| console.log('--- RAW ACTIVATION ---'); | ||
| console.log('Email in URL:', email); | ||
| console.log('Token in URL:', token); | ||
|
|
||
| const user = await UserService.findByEmail(email); | ||
|
|
||
| if (!user) { | ||
| return res.status(404).json({ error: 'Користувача не знайдено' }); | ||
| } | ||
|
|
||
| console.log('Token in DB:', user.activationToken); | ||
| console.log('Are tokens equal?', user.activationToken === token); | ||
| console.log('Length in DB:', user.activationToken?.length); | ||
| console.log('Length in URL:', token?.length); | ||
|
|
||
| if (user.activationToken !== token) { | ||
| return res.status(404).json({ error: 'Невалідний токен' }); | ||
| } | ||
|
|
||
| await UserService.activation(email, token); | ||
|
|
||
| return res.status(200).json({ message: 'Акаунт успішно активовано' }); |
There was a problem hiding this comment.
The activation endpoint correctly validates the token and activates the account, but the frontend activation flow currently redirects the user back to /?activated=true instead of the Profile page. Checklist item #7 explicitly requires redirecting to Profile after activation, so you’ll need to adjust the frontend redirect target.
| login: async (req, res) => { | ||
| try { | ||
| const { email, password } = req.body; | ||
|
|
||
| if (!email || !password) { | ||
| return res.status(400).json({ error: 'Введіть всі поля' }); | ||
| } | ||
|
|
||
| const user = await UserService.login(email, password); | ||
|
|
||
| if (!user) { | ||
| return res.status(404).json({ error: 'Помилка під час авторизації' }); | ||
| } | ||
|
|
||
| const normalizedUser = UserService.normalize(user); | ||
| const refreshToken = jwt.generateRefreshToken(normalizedUser); | ||
|
|
||
| await UserService.saveToken(user.id, refreshToken); | ||
|
|
||
| res.cookie('refreshToken', refreshToken, { | ||
| maxAge: 30 * 24 * 60 * 60 * 1000, | ||
| httpOnly: true, | ||
| sameSite: 'none', | ||
| secure: true, | ||
| }); | ||
|
|
||
| return res.status(200).json({ | ||
| user: normalizedUser, | ||
| accessToken: jwt.generateAccessToken(normalizedUser), | ||
| }); | ||
| } catch (error) { | ||
| return res | ||
| .status(401) | ||
| .json({ error: error.message || 'Помилка під час авторизації' }); |
There was a problem hiding this comment.
The login controller and service correctly enforce that inactive users cannot log in and return a specific error message. However, the frontend login modal does not render state.auth.error, so users aren’t explicitly asked to activate their email, which is required by checklist item #9. Make sure the UI shows these backend error messages.
| login: async (req, res) => { | ||
| try { | ||
| const { email, password } = req.body; | ||
|
|
||
| if (!email || !password) { | ||
| return res.status(400).json({ error: 'Введіть всі поля' }); | ||
| } | ||
|
|
||
| const user = await UserService.login(email, password); | ||
|
|
||
| if (!user) { | ||
| return res.status(404).json({ error: 'Помилка під час авторизації' }); | ||
| } | ||
|
|
||
| const normalizedUser = UserService.normalize(user); | ||
| const refreshToken = jwt.generateRefreshToken(normalizedUser); | ||
|
|
||
| await UserService.saveToken(user.id, refreshToken); | ||
|
|
||
| res.cookie('refreshToken', refreshToken, { | ||
| maxAge: 30 * 24 * 60 * 60 * 1000, | ||
| httpOnly: true, | ||
| sameSite: 'none', | ||
| secure: true, | ||
| }); | ||
|
|
||
| return res.status(200).json({ | ||
| user: normalizedUser, | ||
| accessToken: jwt.generateAccessToken(normalizedUser), | ||
| }); |
There was a problem hiding this comment.
The backend refresh logic and token issuance are correct, but there is no behavior in the frontend that redirects the user to /profile after successful login, as required by checklist item #10. After loginUser succeeds, you need to navigate to the profile page in your React components.
| cors({ | ||
| origin: process.env.CLIENT_URL, | ||
| credentials: true, | ||
| }), | ||
| ); | ||
|
|
||
| app.use(express.json()); | ||
| app.use(cookieParser()); | ||
|
|
There was a problem hiding this comment.
The middleware validates the access token but doesn’t attach the decoded user to the request object. Since updateProfile in User.controller uses req.user.id and req.user.email, req.user will be undefined, causing profile updates to fail and breaking checklist items #19–#21. After successful validation, assign req.user = userData before calling next().
| const sequelize = new Sequelize(POSTGRES_URI, { | ||
| dialect: 'postgres', | ||
| logging: false, | ||
| dialectOptions: { | ||
| ssl: { | ||
| require: true, | ||
| rejectUnauthorized: false, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
authMiddleware validates the access token but never attaches the decoded user object to the request, yet updateProfile in User.controller reads req.user.id and req.user.email. This will cause profile updates (name/password/email) to fail at runtime and violates checklist items #19–#21. After successful validation, assign req.user = userData before next().
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am approving your solution because all core auth and profile requirements are correctly implemented and work together end-to-end. You’ve covered registration with visible and enforced password rules, activation via emailed link with a redirect to the profile page, login for non-authenticated users with proper handling of inactive accounts, logout that clears client auth state and returns the user to the login entry point, the full password reset flow (request, email-sent page, reset with password+confirmation equality, success page with link to login), and a protected profile page that can change name, password (old/new/confirmation), and email (with password verification and old-email notification). There are no critical blockers: middleware now correctly sets req.user, protected routes (like profile updates) function, and the frontend redirects and error messages behave as specified.
The remaining points are medium/low-level improvements rather than requirement violations: you could wrap public routes (home, activation, forgot, reset) with AuthGuard isPublicOnly to strictly enforce non-auth-only access, and in ProfilePage you might dispatch the updateProfile Redux thunk instead of calling authService.updateProfile directly so the global user state (e.g., name in the navbar) stays in sync and centralized success/error handling is reused. It would also be a nice UX enhancement to surface backend registration errors in RegisterModal by reading state.auth.error, similar to how you already handle login errors. Overall, you’ve done a solid job implementing a complete auth flow with good attention to edge cases and integration between backend and frontend—well done.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Footnotes
| const handleNameChange = async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| await authService.updateProfile({ name }); |
There was a problem hiding this comment.
You’re calling authService.updateProfile directly in all three handlers, which successfully invokes the backend but bypasses the updateProfile Redux thunk you defined. That means the local user state in Redux won’t reflect changes (e.g., updated name) and you don’t show success/error messages from the store. Consider using dispatch(updateProfile(...)) here to keep the global auth state in sync and reuse your centralized error handling.
| const ResetPassword = () => { | ||
| const { token } = useParams<{ token: string }>(); | ||
|
|
There was a problem hiding this comment.
Right now ProfilePage calls authService.updateProfile directly, which updates the backend but leaves your Redux user state unchanged and bypasses the updateProfile thunk’s success/error handling. Since you already have a thunk that updates state.user.name and manages isLoading/error, consider dispatching updateProfile here instead so the UI stays in sync and you can show global success/errors.
No description provided.