new profile feature with modal and updated settings#17
new profile feature with modal and updated settings#17ransuum wants to merge 1 commit intoengly817chat:mainfrom
Conversation
|
@ransuum is attempting to deploy a commit to the Oleksanra's projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis PR introduces a user profile management feature with a new profile page component, profile modal, API endpoints for updating profile and settings data, expanded type definitions, navigation updates linking to the profile page, configuration changes including API URL update and i18n namespace defaults, and a single translation update. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ProfilePage as ProfilePage<br/>(Client)
participant ReactQuery as React Query<br/>(Cache)
participant API as Auth API
participant Server
User->>ProfilePage: Click profile nav / Page loads
ProfilePage->>ReactQuery: Check cache for profile
alt Cache miss
ReactQuery->>API: getProfile()
API->>Server: GET /api/profile/check
Server-->>API: UserProfile response
API-->>ReactQuery: Profile data
ReactQuery-->>ProfilePage: Profile + cached state
else Cache hit
ReactQuery-->>ProfilePage: Cached profile
end
ProfilePage->>ProfilePage: Render editable sections<br/>(Account Info, Learning Profile)
User->>ProfilePage: Edit fields & apply changes
ProfilePage->>ProfilePage: Accumulate changes<br/>(profileChanges bag)
ProfilePage->>API: updateProfile(changes)
API->>Server: PUT /api/profile
Server-->>API: Updated UserProfile
API-->>ReactQuery: Update cache on success
ReactQuery-->>ProfilePage: Trigger re-render
ProfilePage->>User: Show success toast
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (7)
src/shared/constants/app-consts.ts (1)
2-2: Consider using environment variables for API URL configuration.The HTTPS upgrade improves security. However, hardcoding the API URL makes it difficult to switch between environments (development, staging, production). While the change is correct, using environment variables would be a better practice.
The URL is currently used as the axios
baseURLand in OAuth redirects. Since no environment variables are currently in use in this project, introducingNEXT_PUBLIC_API_URLwould follow Next.js conventions and allow different URLs across build environments.src/features/chats/ui/chat-sidebar.tsx (1)
142-150: Profile button won't show as active after click.When clicking the profile item,
item.onClick()executes and navigates away, butsetActiveItem(item)is never called due to theelsebranch. This means the profile button won't visually indicate it's active before navigation occurs. This may be intentional since navigation leaves this component, but consider if you want brief visual feedback.🔎 Optional: Set active state before executing onClick
onClick={() => { if (!item.disabled) { if (item.onClick) { + setActiveItem(item) item.onClick() } else { setActiveItem(item) } } }}src/app/(root)/profile/page.tsx (2)
28-29: Consider stronger typing for change tracking state.Using
Record<string, any>loses type safety. Consider using partial types of the request interfaces for better compile-time checks.🔎 Proposed improvement
- const [profileChanges, setProfileChanges] = useState<Record<string, any>>({}) - const [settingsChanges, setSettingsChanges] = useState<Record<string, any>>({}) + const [profileChanges, setProfileChanges] = useState<Partial<UpdateProfileRequest>>({}) + const [settingsChanges, setSettingsChanges] = useState<Partial<UpdateSettingsRequest>>({})Import
UpdateProfileRequestandUpdateSettingsRequestfrom@/entities/auth:import { authApi, UpdateProfileRequest, UpdateSettingsRequest } from '@/entities/auth'
36-58: Hardcoded user-facing strings should use i18n.Toast messages like "Profile updated successfully", "Failed to update profile", "Settings updated successfully", and "Failed to update settings" are hardcoded in English. These should use the translation function for consistency with the rest of the app.
🔎 Example fix
const updateProfileMutation = useMutation({ mutationFn: authApi.updateProfile, onSuccess: (data) => { queryClient.setQueryData(['profile'], data) - toast.success('Profile updated successfully') + toast.success(t('profile.updateSuccess')) setProfileChanges({}) }, onError: () => { - toast.error('Failed to update profile') + toast.error(t('profile.updateError')) }, })Then add corresponding keys to
common.json.src/entities/auth/types.ts (1)
71-76: Consider makinguserSettingsoptional if it can be undefined.The
ProfilePagecomponent at line 409 checks{profile.userSettings && ...}suggestinguserSettingsmight not always be present. If the API can return a profile withoutuserSettings, the type should reflect this:🔎 Proposed fix if userSettings can be absent
- userSettings: { + userSettings?: { id: string theme: string notifications: boolean interfaceLanguage: string - } + } | null }If
userSettingsis always guaranteed by the API, the null check inProfilePageis unnecessary and can be removed.src/features/profile/ui/profile-modal.tsx (2)
14-15: Translation hook is imported but not used.The
tfunction fromuseTranslationis imported but all UI strings in the modal are hardcoded in English (e.g., "Account Information", "Learning Profile", "Member since", etc.). Either uset()for these strings or remove the unused import.
68-78: Consider adding keyboard accessibility for the modal.The modal lacks common accessibility features:
- No ESC key handler to close the modal
- No click-outside-to-close behavior
- No focus trap within the modal
These are nice-to-have improvements for better UX and accessibility.
🔎 Example: Add ESC key handler
import { useEffect } from 'react' // Inside ProfileModal component: useEffect(() => { const handleEsc = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } window.addEventListener('keydown', handleEsc) return () => window.removeEventListener('keydown', handleEsc) }, [onClose])
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
public/locales/en/common.jsonsrc/app/(root)/profile/page.tsxsrc/entities/auth/api.tssrc/entities/auth/types.tssrc/features/chats/ui/chat-sidebar.tsxsrc/features/profile/index.tssrc/features/profile/ui/profile-modal.tsxsrc/shared/constants/app-consts.tssrc/shared/lib/i18n.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/entities/auth/types.ts (1)
src/entities/auth/model/auth-context.tsx (2)
AuthContextType(9-16)userData(48-50)
src/features/profile/ui/profile-modal.tsx (2)
src/shared/ui/common/avatar.tsx (3)
Avatar(50-50)AvatarImage(50-50)AvatarFallback(50-50)src/shared/ui/message-modal.tsx (2)
Props(8-25)Props(1-6)
src/entities/auth/api.ts (2)
src/entities/auth/types.ts (3)
UpdateProfileRequest(36-41)UserProfile(53-77)UpdateSettingsRequest(43-47)src/shared/api/axios.ts (1)
axiosWithAuth(42-42)
🔇 Additional comments (9)
public/locales/en/common.json (1)
206-206: Verify the translation change is intentional.The button label changed from "Send" to "Go" which is less descriptive for a message-sending action. Confirm this is the intended UX decision.
src/features/profile/index.ts (1)
1-1: LGTM!Clean barrel export following the feature module pattern.
src/shared/lib/i18n.ts (1)
16-19: LGTM!The namespace configuration is consistent with the hardcoded
loadPathforcommon.json. If additional namespaces are needed in the future, both thensarray andloadPathpattern would need to be updated together.src/features/chats/ui/chat-sidebar.tsx (1)
83-84: LGTM!Profile navigation is properly enabled with router push to
/profile.src/app/(root)/profile/page.tsx (1)
175-176: LGTM!The component properly uses
AccessGuardwithrequireAuthto protect the profile page, and correctly handles loading and error states.src/entities/auth/api.ts (2)
116-123: LGTM!New API methods follow the existing patterns in the file and are properly typed. The use of
PUTfor updates is appropriate.
26-29: Endpoint paths are correctly defined.Note: Pre-existing inconsistency at line 19 where
checkEmaillacks a leading slash ('valid/check-email?email='), unlike other endpoints. Consider fixing for consistency in a follow-up.src/entities/auth/types.ts (1)
36-47: LGTM!The request interfaces use optional fields appropriately for partial update semantics.
src/features/profile/ui/profile-modal.tsx (1)
80-216: LGTM!The conditional rendering for loading, success, and error states is well-structured. The UI layout and styling are clean.
| const formatDate = (dateString: string) => { | ||
| return new Date(dateString).toLocaleDateString('en-US', { | ||
| year: 'numeric', | ||
| month: 'long', | ||
| day: 'numeric', | ||
| hour: '2-digit', | ||
| minute: '2-digit', | ||
| }) | ||
| } | ||
|
|
||
| const formatEnglishLevel = (level: string) => { | ||
| const levels: Record<string, string> = { | ||
| A1: 'A1 - Beginner', | ||
| A2: 'A2 - Elementary', | ||
| B1: 'B1 - Intermediate', | ||
| B2: 'B2 - Upper Intermediate', | ||
| C1: 'C1 - Advanced', | ||
| C2: 'C2 - Proficient', | ||
| } | ||
| return levels[level] || level | ||
| } | ||
|
|
||
| const formatGoal = (goal: string) => { | ||
| const goals: Record<string, string> = { | ||
| DEFAULT: 'No specific goal', | ||
| IMPROVE_ENGLISH: 'Improve English', | ||
| LEARN_NEW_LANGUAGE: 'Learn new language', | ||
| MEET_NEW_PEOPLE: 'Meet new people', | ||
| } | ||
| return goals[goal] || goal.replace(/_/g, ' ').toLowerCase().replace(/^\w/, c => c.toUpperCase()) | ||
| } | ||
|
|
||
| const formatNativeLanguage = (lang: string) => { | ||
| const languages: Record<string, string> = { | ||
| ENGLISH: 'English', | ||
| SPANISH: 'Spanish', | ||
| FRENCH: 'French', | ||
| GERMAN: 'German', | ||
| CHINESE: 'Chinese', | ||
| JAPANESE: 'Japanese', | ||
| RUSSIAN: 'Russian', | ||
| ARABIC: 'Arabic', | ||
| PORTUGUESE: 'Portuguese', | ||
| HINDI: 'Hindi', | ||
| UKRAINIAN: 'Ukrainian', | ||
| POLISH: 'Polish', | ||
| } | ||
| return languages[lang] || lang.charAt(0) + lang.slice(1).toLowerCase() | ||
| } | ||
|
|
||
| const ENGLISH_LEVELS = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'] | ||
| const GOALS = ['DEFAULT', 'IMPROVE_ENGLISH', 'LEARN_NEW_LANGUAGE', 'MEET_NEW_PEOPLE'] | ||
| const NATIVE_LANGUAGES = [ | ||
| 'ENGLISH', 'SPANISH', 'FRENCH', 'GERMAN', 'CHINESE', | ||
| 'JAPANESE', 'RUSSIAN', 'ARABIC', 'PORTUGUESE', 'HINDI', | ||
| 'UKRAINIAN', 'POLISH' | ||
| ] | ||
| const THEMES = ['DARK', 'BRIGHT', 'CYAN'] | ||
| const INTERFACE_LANGUAGES = ['ENGLISH', 'UKRAINIAN'] |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Extract shared formatting utilities to reduce duplication.
The formatDate, formatEnglishLevel, formatGoal, and formatNativeLanguage functions are duplicated between this file and profile-modal.tsx. Additionally, there are inconsistencies:
formatDatehere includes time (hour/minute), while the modal version shows date onlyformatGoaluses different keys (DEFAULT,IMPROVE_ENGLISH,LEARN_NEW_LANGUAGE,MEET_NEW_PEOPLE) vs modal (IMPROVE_ENGLISH,LEARN_NEW_LANG,MEET_PEOPLE)formatNativeLanguagehere includesPOLISHwhich the modal lacks
Consider extracting these to a shared utility in @/shared/utils or @/entities/auth to ensure consistency.
🤖 Prompt for AI Agents
In src/app/(root)/profile/page.tsx around lines 90 to 148 the formatting helpers
(formatDate, formatEnglishLevel, formatGoal, formatNativeLanguage) are
duplicated and inconsistent with profile-modal.tsx; extract them into a single
shared utility (e.g. src/shared/utils/formatters.ts or
@/entities/auth/formatters) exporting: a configurable formatDate (optionally
includeTime flag), canonical formatEnglishLevel, a single formatGoal with
unified keys (map both variants like LEARN_NEW_LANG → LEARN_NEW_LANGUAGE,
MEET_PEOPLE → MEET_NEW_PEOPLE) and a unified formatNativeLanguage map (including
POLISH), then replace the local functions and constant arrays in both
profile/page.tsx and profile-modal.tsx with imports from that utility and update
the constants (ENGLISH_LEVELS, GOALS, NATIVE_LANGUAGES, THEMES,
INTERFACE_LANGUAGES) to reference the shared definitions to ensure consistency.
| const formatGoal = (goal: string) => { | ||
| const goals: Record<string, string> = { | ||
| IMPROVE_ENGLISH: 'Improve English', | ||
| LEARN_NEW_LANG: 'Learn new language', | ||
| MEET_PEOPLE: 'Meet new people', | ||
| } | ||
| return goals[goal] || goal | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for all goal key variations
rg -n "LEARN_NEW_LANG|MEET_PEOPLE|LEARN_NEW_LANGUAGE|MEET_NEW_PEOPLE" --type=ts --type=tsxRepository: engly817chat/engly-client
Length of output: 96
🏁 Script executed:
#!/bin/bash
# Find ProfilePage file
fd -type f -name "*profile*" | grep -i pageRepository: engly817chat/engly-client
Length of output: 239
🏁 Script executed:
#!/bin/bash
# Look for API types or constants related to goals
rg -n "goal|Goal" --type=ts --type=tsx | head -50Repository: engly817chat/engly-client
Length of output: 96
🏁 Script executed:
#!/bin/bash
# Search for all goal key variations without type restriction
rg "LEARN_NEW_LANG|MEET_PEOPLE|LEARN_NEW_LANGUAGE|MEET_NEW_PEOPLE"Repository: engly817chat/engly-client
Length of output: 735
🏁 Script executed:
#!/bin/bash
# Find ProfilePage file
find . -type f -name "*ProfilePage*" -o -name "*profile-page*"Repository: engly817chat/engly-client
Length of output: 51
🏁 Script executed:
#!/bin/bash
# Look at the file structure to understand the project
ls -la src/features/profile/Repository: engly817chat/engly-client
Length of output: 285
🏁 Script executed:
#!/bin/bash
# Examine profile-modal.tsx to see how goals are used
cat -n src/features/profile/ui/profile-modal.tsx | head -60Repository: engly817chat/engly-client
Length of output: 2191
🏁 Script executed:
#!/bin/bash
# Examine profile/page.tsx to see how they format goals correctly
cat -n src/app/\(root\)/profile/page.tsx | grep -A 10 "formatGoal\|LEARN_NEW_LANGUAGE\|goals"Repository: engly817chat/engly-client
Length of output: 1761
🏁 Script executed:
#!/bin/bash
# Check auth constants to confirm API values
cat -n src/features/auth/model/constants.ts | grep -A 5 "GOALS\|goal"Repository: engly817chat/engly-client
Length of output: 692
Update goal key names in formatGoal to match API values.
The formatGoal function uses LEARN_NEW_LANG and MEET_PEOPLE, but the API returns LEARN_NEW_LANGUAGE and MEET_NEW_PEOPLE (as confirmed by auth constants and the GOALS array in profile/page.tsx). This mismatch will cause unformatted goal values to display instead of the intended labels. Update lines 45-46 to:
LEARN_NEW_LANGUAGE: 'Learn new language',
MEET_NEW_PEOPLE: 'Meet new people',
🤖 Prompt for AI Agents
In src/features/profile/ui/profile-modal.tsx around lines 42 to 49, the
formatGoal map uses keys LEARN_NEW_LANG and MEET_PEOPLE which do not match API
values; update the keys to LEARN_NEW_LANGUAGE and MEET_NEW_PEOPLE respectively
so the function returns the intended human-readable labels for those API goal
values.
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.