Issue 340 - Implement Forget password on Login page#353
Issue 340 - Implement Forget password on Login page#353Alien501 wants to merge 2 commits intodjangoindia:mainfrom
Conversation
|
@CodeRabbit fullreview |
|
@Yadavanurag13 I'll conduct a full review of the PR implementing the forgot password functionality. Let me examine all the changes and provide comprehensive feedback. ✅ Actions performedFull review triggered. |
WalkthroughAdds forgot/reset password feature: new containers, pages, types, schemas, and endpoints. Login now links to forgot password. User profile and change-password forms refactored (validation, visibility toggles, submission flow). Privacy policy content adjusted. Barrel exports updated. Several files received formatting/styling changes without behavioral impact. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant LoginPage as Login Page
participant ForgotPage as ForgotPassword Container
participant API as Backend API
participant Notify as Snackbar
User->>LoginPage: Click "Forgot Password?"
LoginPage->>ForgotPage: Navigate to /forgot-password
User->>ForgotPage: Enter email + Submit
ForgotPage->>API: POST /forgot-password { email }
alt success
API-->>ForgotPage: 200 OK
ForgotPage->>Notify: Show success "Check your email"
else error
API-->>ForgotPage: 4xx/5xx
ForgotPage->>Notify: Show error message
end
sequenceDiagram
autonumber
actor User
participant ResetPage as ResetPassword Container
participant API as Backend API
participant Router as Next Router
participant Notify as Snackbar
User->>ResetPage: Open /reset-password?uidb64=...&token=...
ResetPage->>ResetPage: Validate uidb64 & token (decode if needed)
alt invalid link
ResetPage->>Notify: Show invalid link error
ResetPage->>Router: Redirect to "/"
else valid
User->>ResetPage: Enter new & confirm password + Submit
ResetPage->>API: POST /reset-password/{uidb64}/{token} { new_password }
alt success
API-->>ResetPage: 200 OK
ResetPage->>Notify: Show success
ResetPage->>Router: Redirect to "/"
else error
API-->>ResetPage: 4xx/5xx
ResetPage->>Notify: Show error
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (9)
frontend/src/containers/Users/UserProfileForm.tsx (3)
74-82: Surface server-side field errors viasetErrorinstead of many snackbars.Improves UX and aligns with
<FormMessage>rendering.- const { + const { register, control, reset, handleSubmit, - formState: { errors, ...restFormState }, + formState: { errors, ...restFormState }, + setError, ...rest } = useForm<ProfileFormValues>({- } else { - // Handle nested error message object - const errorMessages = res.error?.message; - - if (typeof errorMessages === 'object') { - Object.entries(errorMessages).forEach(([field, errors]) => { - if (Array.isArray(errors)) { - errors.forEach((error) => - enqueueSnackbar(`${field}: ${error}`, { variant: 'error' }), - ); - } - }); - } else { - enqueueSnackbar('An error occurred', { variant: 'error' }); - } - } + } else { + const errorMessages = res.error?.message; + if (errorMessages && typeof errorMessages === 'object') { + Object.entries(errorMessages).forEach(([field, errs]) => { + if (Array.isArray(errs)) { + errs.forEach((msg) => + setError(field as any, { type: 'server', message: String(msg) }), + ); + } else if (errs) { + setError(field as any, { type: 'server', message: String(errs) }); + } + }); + enqueueSnackbar('Please fix the highlighted fields', { variant: 'error' }); + } else { + enqueueSnackbar('An error occurred', { variant: 'error' }); + } + }Also applies to: 119-133
116-117: Remove debug log before shipping.- console.log('res after updated', res);
241-249: RHF props duplication:{...field}already suppliesonChangeandvalue.Drop the extra
onChange/valueunless you need custom mapping.Also applies to: 323-331
frontend/src/containers/ResetPassword/ResetPassword.tsx (6)
28-39: Centralize/reset-link param extraction and avoid top-levelwindowaccess.Refactor to parse from
useSearchParams()andlocation.pathname, and run in an effect to sidestep hydration gotchas.-// Retriving raw URL to handle potential HTML encoded parameters -const url = new URL(window.location.href); -// Replace HTML-encoded ampersands (&) and other encoded characters -const fixedSearch = url.search - .replace(/&%3B/g, '&') - .replace(/&/g, '&'); -const params = new URLSearchParams(fixedSearch); -const uidb64 = params.get("uidb64") || url.searchParams.get("uidb64") || ""; -const token = params.get("token") || url.searchParams.get("token") || ""; +const [uidb64, setUidb64] = useState(''); +const [token, setToken] = useState(''); + +useEffect(() => { + const sp = new URLSearchParams(window.location.search.replace(/&/g, '&')); + let u = sp.get('uidb64') ?? ''; + let t = sp.get('token') ?? ''; + if ((!u || !t)) { + const parts = window.location.pathname.split('/').filter(Boolean); + const idx = parts.indexOf('reset-password'); + if (idx >= 0 && parts[idx + 1] && parts[idx + 2]) { + u = parts[idx + 1]; + t = parts[idx + 2]; + } + } + setUidb64(u); + setToken(t); +}, []);Also applies to: 53-87
99-105: Send JSON header with the POST.Make payload/content-type explicit for backends that require it.
const response = await fetchData( `/reset-password/${uidb64}/${token}`, { method: 'POST', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ new_password: data.new_password }), } );
116-117: Honorredirectafter success.Route users back to their intended destination (or login) instead of always
/.- router.push('/'); + router.push(redirect || '/login');Also applies to: 26-27
193-207: Accessibility: addaria-labeland properautoCompletefor password fields and toggles.<Input {...register('new_password')} type={showPassword ? "text" : "password"} id="new_password" placeholder="Enter your new password" + autoComplete="new-password" className={`${errors.new_password ? 'text-red-500 !outline-red-500' : ''} pr-10`} /> <button type="button" onClick={togglePassword} + aria-label={showPassword ? 'Hide password' : 'Show password'} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500" ><Input {...register('confirm_password')} type={showConfirmPassword ? "text" : "password"} id="confirm_password" placeholder="Confirm your new password" + autoComplete="new-password" className={`${errors.confirm_password ? 'text-red-500 !outline-red-500' : ''} pr-10`} /> <button type="button" onClick={toggleConfirmPassword} + aria-label={showConfirmPassword ? 'Hide confirm password' : 'Show confirm password'} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500" >Also applies to: 221-235
45-51: Disable submit while submitting to prevent duplicate requests.-const { - register, - handleSubmit, - formState: { errors }, -} = useForm<ResetPasswordType>({ +const { + register, + handleSubmit, + formState: { errors, isSubmitting }, +} = useForm<ResetPasswordType>({ resolver: yupResolver(RESET_PASSWORD_SCHEMA), });-<Button className="mt-4" type="submit">Reset Password</Button> +<Button className="mt-4" type="submit" disabled={isSubmitting}> + Reset Password +</Button>Also applies to: 241-242
134-142: Optional: “Validating reset link…” is misleading without a server check.Either call a lightweight validate endpoint or change the copy to “Checking reset link…”.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
frontend/src/app/(auth)/forgot-password/page.tsx(1 hunks)frontend/src/app/(auth)/reset-password/page.tsx(1 hunks)frontend/src/app/(core)/privacy-policy/page.tsx(3 hunks)frontend/src/components/Modal/modal.tsx(1 hunks)frontend/src/constants/endpoints.ts(1 hunks)frontend/src/constants/schema.tsx(1 hunks)frontend/src/containers/ForgotPassword/ForgotPassword.tsx(1 hunks)frontend/src/containers/ForgotPassword/ForgotPassword.types.ts(1 hunks)frontend/src/containers/ForgotPassword/index.ts(1 hunks)frontend/src/containers/Login/Login.tsx(1 hunks)frontend/src/containers/ResetPassword/ResetPassword.tsx(1 hunks)frontend/src/containers/ResetPassword/ResetPassword.types.ts(1 hunks)frontend/src/containers/ResetPassword/index.ts(1 hunks)frontend/src/containers/Signup/Signup.tsx(5 hunks)frontend/src/containers/Users/ChangePasswordForm.config.ts(1 hunks)frontend/src/containers/Users/ChangePasswordForm.tsx(1 hunks)frontend/src/containers/Users/ChangePasswordForm.types.ts(1 hunks)frontend/src/containers/Users/User.tsx(2 hunks)frontend/src/containers/Users/UserAvatar.tsx(1 hunks)frontend/src/containers/Users/UserProfileForm.config.ts(2 hunks)frontend/src/containers/Users/UserProfileForm.tsx(1 hunks)frontend/src/containers/Users/UserProfileForm.types.ts(1 hunks)frontend/src/containers/index.ts(1 hunks)frontend/src/middleware.ts(2 hunks)frontend/src/sections/Navbar/Navbar.tsx(1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
frontend/src/constants/schema.tsx
[error] 146-146: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 212-212: Do not add then to an object.
(lint/suspicious/noThenProperty)
frontend/src/containers/Users/UserProfileForm.tsx
[error] 63-63: Shouldn't redeclare 'ProfileForm'. Consider to delete it or rename it.
'ProfileForm' is defined here:
(lint/suspicious/noRedeclare)
🔇 Additional comments (8)
frontend/src/sections/Navbar/Navbar.tsx (1)
293-301: Formatting tidy, no behavioral impact.Avatar image/fallback layout is cleaner and keeps the same data flow, so nothing functionally changes.
frontend/src/containers/Users/UserProfileForm.types.ts (1)
2-36: Whitespace-only type formatting looks good.The reindent keeps the type surface identical while improving readability.
frontend/src/middleware.ts (1)
16-38: Reflow preserves redirect logic.Splitting the
baseUrlconstruction across lines improves readability without altering the fallback order.frontend/src/containers/ResetPassword/ResetPassword.types.ts (1)
1-4: Type surface looks good.The reset-password form fields are captured cleanly with string typing; no issues spotted.
frontend/src/components/Modal/modal.tsx (1)
67-98: Layout tweak confirmed harmless.Only indentation and spacing around the subscriber inputs changed; validation wiring and bindings remain intact.
frontend/src/containers/Login/Login.tsx (1)
121-134: Forgot-password link integration looks solid.The link sits alongside the password label without affecting form validation or submission.
frontend/src/containers/Users/UserAvatar.tsx (1)
11-26: Avatar styling update LGTM.Responsive size utilities and fallback styling are tidy and preserve previous behavior.
frontend/src/containers/ResetPassword/index.ts (1)
1-2: Export barrel is clean.Container and related types are re-exported in line with the project pattern.
| If you have any questions about this Privacy Policy, please contact | ||
| us at:{' '} | ||
| <Link href='mailto:contact@djangoindia.org'> | ||
| <u>admin@djangoindia.org</u> | ||
| </Link> |
There was a problem hiding this comment.
Fix contact email href mismatch.
The visible email shows admin@djangoindia.org, but the href sends users to mailto:contact@djangoindia.org. That mismatch will route messages to the wrong inbox and confuse users who copy the address. Please align the href with the displayed address (or update the display text) so clicks and copy-paste go to the same destination. (djangoindia.org)
- <Link href='mailto:contact@djangoindia.org'>
- <u>admin@djangoindia.org</u>
- </Link>
+ <Link href='mailto:admin@djangoindia.org'>
+ <u>admin@djangoindia.org</u>
+ </Link>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| If you have any questions about this Privacy Policy, please contact | |
| us at:{' '} | |
| <Link href='mailto:contact@djangoindia.org'> | |
| <u>admin@djangoindia.org</u> | |
| </Link> | |
| If you have any questions about this Privacy Policy, please contact | |
| us at:{' '} | |
| <Link href='mailto:admin@djangoindia.org'> | |
| <u>admin@djangoindia.org</u> | |
| </Link> |
🤖 Prompt for AI Agents
frontend/src/app/(core)/privacy-policy/page.tsx around lines 120 to 124: the
mailto href and displayed email are inconsistent (href uses
contact@djangoindia.org while visible text shows admin@djangoindia.org); fix by
making them identical—either change the href to mailto:admin@djangoindia.org to
match the visible text or change the visible text to
admin@contact@djangoindia.org (preferably set both to
mailto:admin@djangoindia.org and display admin@djangoindia.org) so clicks and
copy-paste go to the same address.
| confirmPassword: yup.string().when('newPassword', { | ||
| is: (newPassword: string) => newPassword && newPassword.length > 0, | ||
| then: (schema) => | ||
| schema | ||
| .required('Confirm Password is required') | ||
| .oneOf([yup.ref('newPassword')], 'Passwords must match'), | ||
| otherwise: (schema) => schema.optional().nullable() | ||
| }), | ||
| otherwise: (schema) => schema.optional().nullable(), | ||
| }), |
There was a problem hiding this comment.
Resolve Biome noThenProperty lint errors in yup .when usage
Biome fails the build here because the object literal handed to .when exposes a callable then property, which the lint/suspicious/noThenProperty rule flags as a potential thenable. Switch to the callback overload so we keep the same validation while satisfying the linter.
- confirmPassword: yup.string().when('newPassword', {
- is: (newPassword: string) => newPassword && newPassword.length > 0,
- then: (schema) =>
- schema
- .required('Confirm Password is required')
- .oneOf([yup.ref('newPassword')], 'Passwords must match'),
- otherwise: (schema) => schema.optional().nullable(),
- }),
+ confirmPassword: yup
+ .string()
+ .when('newPassword', (newPassword, schema) =>
+ newPassword && newPassword.length > 0
+ ? schema
+ .required('Confirm Password is required')
+ .oneOf([yup.ref('newPassword')], 'Passwords must match')
+ : schema.optional().nullable(),
+ ),- confirmPassword: yup.string().when('newPassword', {
- is: (newPassword: string) => newPassword && newPassword.length > 0,
- then: (schema) =>
- schema
- .required('Confirm Password is required')
- .oneOf([yup.ref('newPassword')], 'Passwords must match'),
- otherwise: (schema) => schema.optional().nullable(),
- }),
+ confirmPassword: yup
+ .string()
+ .when('newPassword', (newPassword, schema) =>
+ newPassword && newPassword.length > 0
+ ? schema
+ .required('Confirm Password is required')
+ .oneOf([yup.ref('newPassword')], 'Passwords must match')
+ : schema.optional().nullable(),
+ ),As reported by Biome.
Also applies to: 210-217
🧰 Tools
🪛 Biome (2.1.2)
[error] 146-146: Do not add then to an object.
(lint/suspicious/noThenProperty)
🤖 Prompt for AI Agents
frontend/src/constants/schema.tsx around lines 144-151 (and similarly update
lines 210-217): the yup .when currently uses an object with a then property
which triggers Biome's noThenProperty lint rule; replace the object overload
with the callback overload signature: call .when('newPassword',
(newPasswordValue, schema) => { return newPasswordValue &&
newPasswordValue.length > 0 ? schema.required('Confirm Password is
required').oneOf([yup.ref('newPassword')], 'Passwords must match') :
schema.optional().nullable(); }); apply the same pattern to the other occurrence
on lines 210-217 to remove the callable then property and satisfy the linter.
| <div className='grid w-full grid-cols-2 items-center gap-1.5'> | ||
| <div className='grid items-center gap-1.5'> | ||
| <Label | ||
| htmlFor='firstName' | ||
| className={`${errors.firstName ? 'text-red-500' : ''}`} | ||
| > | ||
| First Name | ||
| </Label> | ||
| <Input | ||
| {...register('firstName', { required: true })} | ||
| type='text' | ||
| id='firstName' | ||
| placeholder='Enter your first name' | ||
| className={`${errors.firstName ? 'text-red-500 !outline-red-500' : ''}`} | ||
| /> | ||
| <p className='h-[20px] text-sm text-red-500'> | ||
| {errors.firstName?.message ?? ' '} | ||
| </p> | ||
| <Label | ||
| htmlFor='firstName' | ||
| className={`${errors.firstName ? 'text-red-500' : ''}`} | ||
| > | ||
| First Name | ||
| </Label> | ||
| <Input | ||
| {...register('firstName', { required: true })} | ||
| type='text' | ||
| id='firstName' | ||
| placeholder='Enter your first name' | ||
| className={`${errors.firstName ? 'text-red-500 !outline-red-500' : ''}`} | ||
| /> | ||
| <p className='h-[20px] text-sm text-red-500'> | ||
| {errors.firstName?.message ?? ' '} | ||
| </p> | ||
| </div> | ||
| <div className='grid items-center gap-1.5'> | ||
| <Label | ||
| htmlFor='lastName' | ||
| className={`${errors.lastName ? 'text-red-500' : ''}`} | ||
| > | ||
| Last Name | ||
| </Label> | ||
| <Input | ||
| {...register('lastName', { required: true })} | ||
| type='text' | ||
| id='lastName' | ||
| placeholder='Enter your last name' | ||
| className={`${errors.lastName ? 'text-red-500 !outline-red-500' : ''}`} | ||
| /> | ||
| <p className='h-[20px] text-sm text-red-500'> | ||
| {errors.lastName?.message ?? ' '} | ||
| </p> | ||
| <Label | ||
| htmlFor='lastName' | ||
| className={`${errors.lastName ? 'text-red-500' : ''}`} | ||
| > | ||
| Last Name | ||
| </Label> | ||
| <Input | ||
| {...register('lastName', { required: true })} | ||
| type='text' | ||
| id='lastName' | ||
| placeholder='Enter your last name' | ||
| className={`${errors.lastName ? 'text-red-500 !outline-red-500' : ''}`} | ||
| /> | ||
| <p className='h-[20px] text-sm text-red-500'> | ||
| {errors.lastName?.message ?? ' '} | ||
| </p> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Restore single-column layout for mobile viewports.
Locking this block to grid-cols-2 at the base breakpoint forces every screen (including ≤640px) into a two-column layout. On phones, each input drops below ~180 px width, which makes the form difficult to use and regresses the previous mobile-friendly stack. Please keep the form single-column on small screens and only split into two columns once there’s room.
- <div className='grid w-full grid-cols-2 items-center gap-1.5'>
+ <div className='grid w-full grid-cols-1 items-center gap-1.5 sm:grid-cols-2'>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className='grid w-full grid-cols-2 items-center gap-1.5'> | |
| <div className='grid items-center gap-1.5'> | |
| <Label | |
| htmlFor='firstName' | |
| className={`${errors.firstName ? 'text-red-500' : ''}`} | |
| > | |
| First Name | |
| </Label> | |
| <Input | |
| {...register('firstName', { required: true })} | |
| type='text' | |
| id='firstName' | |
| placeholder='Enter your first name' | |
| className={`${errors.firstName ? 'text-red-500 !outline-red-500' : ''}`} | |
| /> | |
| <p className='h-[20px] text-sm text-red-500'> | |
| {errors.firstName?.message ?? ' '} | |
| </p> | |
| <Label | |
| htmlFor='firstName' | |
| className={`${errors.firstName ? 'text-red-500' : ''}`} | |
| > | |
| First Name | |
| </Label> | |
| <Input | |
| {...register('firstName', { required: true })} | |
| type='text' | |
| id='firstName' | |
| placeholder='Enter your first name' | |
| className={`${errors.firstName ? 'text-red-500 !outline-red-500' : ''}`} | |
| /> | |
| <p className='h-[20px] text-sm text-red-500'> | |
| {errors.firstName?.message ?? ' '} | |
| </p> | |
| </div> | |
| <div className='grid items-center gap-1.5'> | |
| <Label | |
| htmlFor='lastName' | |
| className={`${errors.lastName ? 'text-red-500' : ''}`} | |
| > | |
| Last Name | |
| </Label> | |
| <Input | |
| {...register('lastName', { required: true })} | |
| type='text' | |
| id='lastName' | |
| placeholder='Enter your last name' | |
| className={`${errors.lastName ? 'text-red-500 !outline-red-500' : ''}`} | |
| /> | |
| <p className='h-[20px] text-sm text-red-500'> | |
| {errors.lastName?.message ?? ' '} | |
| </p> | |
| <Label | |
| htmlFor='lastName' | |
| className={`${errors.lastName ? 'text-red-500' : ''}`} | |
| > | |
| Last Name | |
| </Label> | |
| <Input | |
| {...register('lastName', { required: true })} | |
| type='text' | |
| id='lastName' | |
| placeholder='Enter your last name' | |
| className={`${errors.lastName ? 'text-red-500 !outline-red-500' : ''}`} | |
| /> | |
| <p className='h-[20px] text-sm text-red-500'> | |
| {errors.lastName?.message ?? ' '} | |
| </p> | |
| </div> | |
| </div> | |
| <div className='grid w-full grid-cols-1 items-center gap-1.5 sm:grid-cols-2'> | |
| <div className='grid items-center gap-1.5'> | |
| <Label | |
| htmlFor='firstName' | |
| className={`${errors.firstName ? 'text-red-500' : ''}`} | |
| > | |
| First Name | |
| </Label> | |
| <Input | |
| {...register('firstName', { required: true })} | |
| type='text' | |
| id='firstName' | |
| placeholder='Enter your first name' | |
| className={`${errors.firstName ? 'text-red-500 !outline-red-500' : ''}`} | |
| /> | |
| <p className='h-[20px] text-sm text-red-500'> | |
| {errors.firstName?.message ?? ' '} | |
| </p> | |
| </div> | |
| <div className='grid items-center gap-1.5'> | |
| <Label | |
| htmlFor='lastName' | |
| className={`${errors.lastName ? 'text-red-500' : ''}`} | |
| > | |
| Last Name | |
| </Label> | |
| <Input | |
| {...register('lastName', { required: true })} | |
| type='text' | |
| id='lastName' | |
| placeholder='Enter your last name' | |
| className={`${errors.lastName ? 'text-red-500 !outline-red-500' : ''}`} | |
| /> | |
| <p className='h-[20px] text-sm text-red-500'> | |
| {errors.lastName?.message ?? ' '} | |
| </p> | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
In frontend/src/containers/Signup/Signup.tsx around lines 131 to 168, the parent
grid is fixed to two columns at all breakpoints which forces a cramped
two-column layout on small screens; change the grid class to be responsive
(e.g., grid-cols-1 sm:grid-cols-2) so the form is single-column on mobile and
becomes two-column at the small breakpoint or larger, preserving existing item
alignment and gap classes.
| } from '@/components/Card'; | ||
| import { API_ENDPOINTS, EDIT_PROFILE_FORM_SCHEMA } from '@/constants'; | ||
| import { fetchData } from '@/utils'; | ||
| import { getAccessToken } from '@/utils/getAccesstoken'; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Verify import path casing (getAccesstoken vs getAccessToken).
Case-sensitive filesystems will break if the filename/export don’t match.
🏁 Script executed:
#!/bin/bash
# Check for file and export casing mismatches
fd -IH --glob 'getAccessToken.*' -a
fd -IH --glob 'getAccesstoken.*' -a
rg -nP "export\s+(?:const|function)\s+getAccessToken\b" -C1
rg -nP "export\s+(?:const|function)\s+getAccesstoken\b" -C1Length of output: 283
Rename util file to match exported function casing
The file frontend/src/utils/getAccesstoken.ts should be renamed to getAccessToken.ts (uppercase ‘T’) and all import paths (e.g. in UserProfileForm.tsx) updated to @/utils/getAccessToken to ensure consistency on case-sensitive filesystems.
🤖 Prompt for AI Agents
In frontend/src/containers/Users/UserProfileForm.tsx around line 34, the import
references '@/utils/getAccesstoken' which mismatches the exported function
casing; rename the util file from frontend/src/utils/getAccesstoken.ts to
frontend/src/utils/getAccessToken.ts (uppercase 'T') and update this import to
'@/utils/getAccessToken' (and update any other imports across the codebase) so
file name and import casing match the exported function on case-sensitive
filesystems.
| import type { ProfileForm } from './UserProfileForm.types'; | ||
| import { enqueueSnackbar } from 'notistack'; | ||
| import { useState, useEffect } from 'react'; | ||
| import type * as yup from 'yup'; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Resolve identifier collision: component vs imported type ProfileForm (Biome noRedeclare).
Alias the imported type and use the alias in generics/handler to satisfy linter and avoid confusion.
-import type { ProfileForm } from './UserProfileForm.types';
+import type { ProfileForm as ProfileFormValues } from './UserProfileForm.types';
-} = useForm<ProfileForm>({
+} = useForm<ProfileFormValues>({
-const onSubmit: SubmitHandler<ProfileForm> = async (data: any) => {
+const onSubmit: SubmitHandler<ProfileFormValues> = async (data) => {Also applies to: 63-63, 81-82, 104-104
🤖 Prompt for AI Agents
In frontend/src/containers/Users/UserProfileForm.tsx around lines 39-41 (and
also apply at 63, 81-82, 104), the imported type ProfileForm collides with the
component identifier; rename the import to an alias (e.g. ProfileFormType) and
update all usages in generics and handler signatures to use that alias so the
linter noRedeclare error is resolved and the component identifier remains
unchanged.
| defaultValues: { | ||
| username: userData?.username || '', | ||
| email: userData?.email || '', | ||
| first_name: userData?.first_name || '', | ||
| last_name: userData?.last_name || '', | ||
| gender: userData?.gender || null, | ||
| bio: userData?.bio || '', | ||
| about: userData?.about || '', | ||
| website: userData?.website || null, | ||
| linkedin: userData?.linkedin || null, | ||
| instagram: userData?.instagram || null, | ||
| github: userData?.github || null, | ||
| twitter: userData?.twitter || null, | ||
| mastodon: userData?.mastodon || null, | ||
| organization: userData?.organization || null, | ||
| country: userData?.country || null, | ||
| user_timezone: userData?.user_timezone || '', | ||
| }, |
There was a problem hiding this comment.
Avoid null in RHF defaultValues for text/selects (prevents controlled/uncontrolled warnings).
Use empty strings for text inputs and an empty string (or undefined) for selects.
- gender: userData?.gender || null,
- website: userData?.website || null,
- linkedin: userData?.linkedin || null,
- instagram: userData?.instagram || null,
- github: userData?.github || null,
- twitter: userData?.twitter || null,
- mastodon: userData?.mastodon || null,
- organization: userData?.organization || null,
- country: userData?.country || null,
+ gender: userData?.gender || '',
+ website: userData?.website || '',
+ linkedin: userData?.linkedin || '',
+ instagram: userData?.instagram || '',
+ github: userData?.github || '',
+ twitter: userData?.twitter || '',
+ mastodon: userData?.mastodon || '',
+ organization: userData?.organization || '',
+ country: userData?.country || '',📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| defaultValues: { | |
| username: userData?.username || '', | |
| email: userData?.email || '', | |
| first_name: userData?.first_name || '', | |
| last_name: userData?.last_name || '', | |
| gender: userData?.gender || null, | |
| bio: userData?.bio || '', | |
| about: userData?.about || '', | |
| website: userData?.website || null, | |
| linkedin: userData?.linkedin || null, | |
| instagram: userData?.instagram || null, | |
| github: userData?.github || null, | |
| twitter: userData?.twitter || null, | |
| mastodon: userData?.mastodon || null, | |
| organization: userData?.organization || null, | |
| country: userData?.country || null, | |
| user_timezone: userData?.user_timezone || '', | |
| }, | |
| defaultValues: { | |
| username: userData?.username || '', | |
| email: userData?.email || '', | |
| first_name: userData?.first_name || '', | |
| last_name: userData?.last_name || '', | |
| gender: userData?.gender || '', | |
| bio: userData?.bio || '', | |
| about: userData?.about || '', | |
| website: userData?.website || '', | |
| linkedin: userData?.linkedin || '', | |
| instagram: userData?.instagram || '', | |
| github: userData?.github || '', | |
| twitter: userData?.twitter || '', | |
| mastodon: userData?.mastodon || '', | |
| organization: userData?.organization || '', | |
| country: userData?.country || '', | |
| user_timezone: userData?.user_timezone || '', | |
| }, |
🤖 Prompt for AI Agents
In frontend/src/containers/Users/UserProfileForm.tsx around lines 83 to 100,
several defaultValues use null for text/select fields which causes React Hook
Form controlled/uncontrolled warnings; change those nulls to empty strings (or
undefined for select if you prefer) — specifically replace gender, website,
linkedin, instagram, github, twitter, mastodon, organization, and country
defaultValues from null to '' (or undefined for selects) so all text
inputs/selects have string/undefined defaults and avoid control state warnings.
| const onSubmit: SubmitHandler<ProfileForm> = async (data: any) => { | ||
| try { | ||
| const res = await fetchData<{ message: string }>(API_ENDPOINTS.profile, { | ||
| method: 'PATCH', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${accessToken}`, | ||
| }, | ||
| mode: 'onChange' | ||
| }); | ||
| body: JSON.stringify(data), | ||
| }); | ||
|
|
There was a problem hiding this comment.
Guard API call on missing token and disable submit until ready.
Prevents sending Authorization: Bearer null and accidental double-submits.
const onSubmit: SubmitHandler<ProfileFormValues> = async (data) => {
try {
+ if (!accessToken) {
+ enqueueSnackbar('Please wait, preparing authentication…', { variant: 'info' });
+ return;
+ }
const res = await fetchData<{ message: string }>(API_ENDPOINTS.profile, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify(data),
});-<Button type='submit'>Save Profile</Button>
+<Button type='submit' disabled={!accessToken || restFormState.isSubmitting}>
+ Save Profile
+</Button>Also applies to: 345-346, 66-72
🤖 Prompt for AI Agents
In frontend/src/containers/Users/UserProfileForm.tsx around lines 104-114 (and
also apply same fix at lines 345-346 and 66-72): the API call is currently sent
even when accessToken is null and the form allows double submits; guard the
submit handler so it returns early (or shows an error) if accessToken is
missing, avoid attaching an Authorization header with a null token, and prevent
double submissions by disabling the submit control while the request is
in-flight (use formState.isSubmitting or a local submitting flag). Ensure the UI
submit button is disabled when token is absent or submitting, and surface a
clear message/validation when the token is missing before calling fetchData.
| {type === 'checkbox' ? ( | ||
| <div className='flex items-center gap-0'> | ||
| <FormControl> | ||
| <Input | ||
| type='checkbox' | ||
| onChange={field.onChange} | ||
| disabled={disabled} | ||
| value={field.value ?? undefined} | ||
| /> | ||
| </FormControl> |
There was a problem hiding this comment.
Checkbox wired as text input: use checked and map boolean values.
Current code passes value for a checkbox; RHF expects booleans.
-<Input
- type='checkbox'
- onChange={field.onChange}
- disabled={disabled}
- value={field.value ?? undefined}
-/>
+<Input
+ type='checkbox'
+ checked={!!field.value}
+ onChange={(e) => field.onChange(e.currentTarget.checked)}
+ disabled={disabled}
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {type === 'checkbox' ? ( | |
| <div className='flex items-center gap-0'> | |
| <FormControl> | |
| <Input | |
| type='checkbox' | |
| onChange={field.onChange} | |
| disabled={disabled} | |
| value={field.value ?? undefined} | |
| /> | |
| </FormControl> | |
| {type === 'checkbox' ? ( | |
| <div className='flex items-center gap-0'> | |
| <FormControl> | |
| <Input | |
| type='checkbox' | |
| checked={!!field.value} | |
| onChange={(e) => field.onChange(e.currentTarget.checked)} | |
| disabled={disabled} | |
| /> | |
| </FormControl> |
🤖 Prompt for AI Agents
In frontend/src/containers/Users/UserProfileForm.tsx around lines 182-191, the
checkbox input is incorrectly using the value prop (text input behavior) instead
of boolean checked handling; change the Input to use
checked={Boolean(field.value)} and update the onChange to pass the boolean state
(e.g. onChange={e => field.onChange(e.target.checked)}), ensuring disabled
remains, and map any stored string values to booleans before rendering so RHF
receives a true/false value.
| <Select | ||
| onValueChange={field.onChange} | ||
| defaultValue={String(field.value)} | ||
| disabled={disabled} | ||
| value={field.value ?? undefined} | ||
| > | ||
| <FormControl> | ||
| <SelectTrigger> | ||
| <SelectValue | ||
| placeholder={placeholder} | ||
| /> | ||
| </SelectTrigger> | ||
| </FormControl> | ||
| <SelectContent> | ||
| {options.map(({ label, value }) => ( | ||
| <SelectItem | ||
| key={value} | ||
| value={value} | ||
| > | ||
| {label} | ||
| </SelectItem> | ||
| ))} | ||
| </SelectContent> | ||
| </Select> | ||
| ) : ( |
There was a problem hiding this comment.
Select is both controlled and uncontrolled (value + defaultValue); remove defaultValue.
Also avoid String(field.value) which yields "null"/"undefined".
-<Select
- onValueChange={field.onChange}
- defaultValue={String(field.value)}
- disabled={disabled}
- value={field.value ?? undefined}
->
+<Select
+ onValueChange={field.onChange}
+ disabled={disabled}
+ value={field.value ?? undefined}
+>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Select | |
| onValueChange={field.onChange} | |
| defaultValue={String(field.value)} | |
| disabled={disabled} | |
| value={field.value ?? undefined} | |
| > | |
| <FormControl> | |
| <SelectTrigger> | |
| <SelectValue | |
| placeholder={placeholder} | |
| /> | |
| </SelectTrigger> | |
| </FormControl> | |
| <SelectContent> | |
| {options.map(({ label, value }) => ( | |
| <SelectItem | |
| key={value} | |
| value={value} | |
| > | |
| {label} | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| ) : ( | |
| <Select | |
| onValueChange={field.onChange} | |
| disabled={disabled} | |
| value={field.value ?? undefined} | |
| > |
🤖 Prompt for AI Agents
In frontend/src/containers/Users/UserProfileForm.tsx around lines 205 to 229,
the Select is being used as both controlled and uncontrolled (it has both value
and defaultValue) and also uses String(field.value) which can produce
"null"/"undefined"; remove the defaultValue prop so the component is purely
controlled, stop calling String(field.value), and ensure value is a proper
string or undefined (e.g., use field.value ?? undefined or String(field.value)
only after a null/undefined guard), and keep onValueChange wired to
field.onChange ensuring any necessary type conversion happens there.
| <Select | ||
| onValueChange={field.onChange} | ||
| defaultValue={String(field.value)} | ||
| disabled={disabled} | ||
| value={field.value ?? undefined} | ||
| > | ||
| <FormControl> | ||
| <SelectTrigger> | ||
| <SelectValue placeholder={item.placeholder} /> | ||
| </SelectTrigger> | ||
| </FormControl> | ||
| <SelectContent> | ||
| {item.options.map(({ label, value }) => ( | ||
| <SelectItem key={value} value={value}> | ||
| {label} | ||
| </SelectItem> | ||
| ))} | ||
| </SelectContent> | ||
| </Select> | ||
| ) : ( |
There was a problem hiding this comment.
Same select issue in single-field renderer.
Remove defaultValue and keep a single source of truth via value.
-<Select
- onValueChange={field.onChange}
- defaultValue={String(field.value)}
- disabled={disabled}
- value={field.value ?? undefined}
->
+<Select
+ onValueChange={field.onChange}
+ disabled={disabled}
+ value={field.value ?? undefined}
+>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Select | |
| onValueChange={field.onChange} | |
| defaultValue={String(field.value)} | |
| disabled={disabled} | |
| value={field.value ?? undefined} | |
| > | |
| <FormControl> | |
| <SelectTrigger> | |
| <SelectValue placeholder={item.placeholder} /> | |
| </SelectTrigger> | |
| </FormControl> | |
| <SelectContent> | |
| {item.options.map(({ label, value }) => ( | |
| <SelectItem key={value} value={value}> | |
| {label} | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| ) : ( | |
| <Select | |
| onValueChange={field.onChange} | |
| disabled={disabled} | |
| value={field.value ?? undefined} | |
| > | |
| <FormControl> | |
| <SelectTrigger> | |
| <SelectValue placeholder={item.placeholder} /> | |
| </SelectTrigger> | |
| </FormControl> | |
| <SelectContent> | |
| {item.options.map(({ label, value }) => ( | |
| <SelectItem key={value} value={value}> | |
| {label} | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> |
🤖 Prompt for AI Agents
In frontend/src/containers/Users/UserProfileForm.tsx around lines 292 to 311,
the Select is using both defaultValue and value which creates two sources of
truth; remove the defaultValue prop (defaultValue={String(field.value)}) and
rely solely on the controlled value prop (value={field.value ?? undefined}) with
onValueChange={field.onChange} so the component remains fully controlled and
consistent.
Closes #340
Implemented forgot password logic in login page from start to end.
Changes
Type of change
Demo
How has this been tested?
Summary by CodeRabbit
New Features
Documentation
Style/Refactor