[PET-43] Tap leaderboard entry -> view user profile#44
Conversation
…try to user profile screen
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdds a User Profile screen reachable from leaderboards, interactive leaderboard entries, a modal back-button component, adjustments to accessibility roles, test additions/updates, Day.js locale initialization for tests, and configuration/documentation changes for formatter/import organization and date/testing conventions. Changes
Sequence Diagram(s)sequenceDiagram
participant Leaderboard as Leaderboard UI
participant Router as Expo Router
participant UserProfile as UserProfileScreen
participant Queries as useUserQuery / usePuzzlesStatisticsQuery
participant DB as Convex/Database
Leaderboard->>Router: onEntryPress(userId)
Router->>UserProfile: navigate to /user-profile?userId
UserProfile->>Queries: fetch user(id) & stats(puzzleType.daily, userId)
Queries->>DB: query user & statistics
DB-->>Queries: userData & statsData
Queries-->>UserProfile: resolved data
UserProfile->>UserProfile: compute formatted date & win-rate
UserProfile-->>Leaderboard: (renders UI / updates title via Stack.Screen options)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 Tip You can generate walkthrough in a markdown collapsible section to save space.Enable the |
|
🚀 Expo preview is ready!
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
db/seeders/dictionaryEntries/getDictionaryWords.js (1)
86-94:⚠️ Potential issue | 🔴 CriticalCritical:
forEachwith async callback doesn't wait for promises.The
forEachloop with an async callback will not wait for theisValidDictionaryTermpromises to resolve before continuing. This meanswriteStream.end()on line 97 will execute immediately, likely before any validation or writes complete, resulting in incomplete or missing data.🔧 Recommended fix using for...of loop
- words.forEach(async (item) => { + for (const item of words) { const isValid = await isValidDictionaryTerm(item.word); if (isValid) { console.log(`Writing word: ${item.word} with frequency: ${item.frequency}`); writeStream.write(`${JSON.stringify(item)}\n`); } else { console.warn('Found illegal word: ', item.word); } - }); + }Note: This is existing code, not part of the current PR changes. However, it represents a data integrity risk that should be addressed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@db/seeders/dictionaryEntries/getDictionaryWords.js` around lines 86 - 94, The async callback passed to words.forEach does not await isValidDictionaryTerm, so validations and writes may run after writeStream.end(); replace the forEach with an explicit asynchronous iteration that awaits each validation and write (e.g., a for...of loop over words that awaits isValidDictionaryTerm(item.word) and then calls writeStream.write or console.warn accordingly) or collect promises via words.map(...) and await Promise.all before calling writeStream.end(); update the block referencing words.forEach, isValidDictionaryTerm, writeStream.write, and ensure writeStream.end() is called only after all validations/writes complete.
🧹 Nitpick comments (3)
db/seeders/dictionaryEntries/getDictionaryWordsExplanations.js (1)
50-50: Consider awaiting the async function call.The
processEntries()function is async but called withoutawait. In a script context, this could cause the process to exit before completion, potentially leaving incomplete output.⏳ Suggested fix to await the async operation
-processEntries(); +await processEntries();Note: This is existing code, not part of the current PR changes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@db/seeders/dictionaryEntries/getDictionaryWordsExplanations.js` at line 50, The call to the async function processEntries() is not awaited, so the script may exit before it completes; update the invocation to await its completion (e.g., use top-level await if the file is an ES module or wrap the call in an async IIFE) and ensure errors are handled (catch/rethrow or log and set non-zero exit) so processEntries() finishes before the script exits.src/components/elements/Leaderboard/Leaderboard.tsx (1)
16-16: Consider adding visual press feedback for better UX.The
Pressabledoesn't provide visual feedback when pressed. You could use thepressedstate to add opacity or highlight styling, similar to thecontainerPressedstyle in the Button component.💡 Optional: Add press feedback
- <Pressable key={score.position} onPress={() => onEntryPress(score.user._id)} style={styles.entry}> + <Pressable + key={score.position} + onPress={() => onEntryPress(score.user._id)} + style={({ pressed }) => [styles.entry, pressed && styles.entryPressed]} + >Then add to styles:
+ entryPressed: { + opacity: 0.6, + },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/elements/Leaderboard/Leaderboard.tsx` at line 16, The Pressable in Leaderboard (the element with key={score.position} and onPress={() => onEntryPress(score.user._id)}) currently uses a static style (styles.entry) and lacks visual feedback; change its style prop to the functional form that receives ({ pressed }) and returns an array/combination like [styles.entry, pressed && styles.containerPressed] (or apply opacity/highlight when pressed) and add a corresponding styles.containerPressed entry (matching the Button's visual feedback pattern) to the stylesheet so entries visibly respond to touch.src/components/navigation/ModalViewBackButton/ModalViewBackButton.tsx (1)
17-17: Consider setting icon color for theme consistency.The
ChevronLeftIcondoesn't have an explicitcolorprop. While this works, it may not matchheaderTintColorset in the layout, potentially causing a visual mismatch in dark mode where the icon could remain black whileheaderTintColoris the theme foreground.💡 Optional: Pass theme color to the icon
+import { useUnistyles } from 'react-native-unistyles'; + export function ModalViewBackButton({ canGoBack }: { canGoBack?: boolean }) { + const { theme } = useUnistyles(); const router = useRouter(); // ... return ( <Pressable onPress={router.back} style={styles.container}> - <ChevronLeftIcon size={28} strokeWidth={2} /> + <ChevronLeftIcon color={theme.colors.foreground} size={28} strokeWidth={2} /> <Text weight="medium">Nazaj</Text> </Pressable> ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/navigation/ModalViewBackButton/ModalViewBackButton.tsx` at line 17, The ChevronLeftIcon in ModalViewBackButton lacks an explicit color, which can cause it to not follow the current theme; update ModalViewBackButton to pass the theme foreground color (e.g., headerTintColor or colors.text from your theme) into the ChevronLeftIcon via its color prop—retrieve the color from the same source you use for header text (useTheme() or the headerTintColor prop) and supply it like <ChevronLeftIcon color={themeColor} size={28} strokeWidth={2} /> so the icon matches dark/light mode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/`(authenticated)/user-profile.tsx:
- Line 36: The accessibilityRole on the ActivityIndicator is incorrect—replace
or remove accessibilityRole="spinbutton" on the ActivityIndicator component:
either remove the prop so the ActivityIndicator uses its default accessible role
or change it to accessibilityRole="progressbar" to reflect a loading indicator;
update the JSX where ActivityIndicator (or the loading indicator component) is
rendered to use the correct accessible role.
---
Outside diff comments:
In `@db/seeders/dictionaryEntries/getDictionaryWords.js`:
- Around line 86-94: The async callback passed to words.forEach does not await
isValidDictionaryTerm, so validations and writes may run after
writeStream.end(); replace the forEach with an explicit asynchronous iteration
that awaits each validation and write (e.g., a for...of loop over words that
awaits isValidDictionaryTerm(item.word) and then calls writeStream.write or
console.warn accordingly) or collect promises via words.map(...) and await
Promise.all before calling writeStream.end(); update the block referencing
words.forEach, isValidDictionaryTerm, writeStream.write, and ensure
writeStream.end() is called only after all validations/writes complete.
---
Nitpick comments:
In `@db/seeders/dictionaryEntries/getDictionaryWordsExplanations.js`:
- Line 50: The call to the async function processEntries() is not awaited, so
the script may exit before it completes; update the invocation to await its
completion (e.g., use top-level await if the file is an ES module or wrap the
call in an async IIFE) and ensure errors are handled (catch/rethrow or log and
set non-zero exit) so processEntries() finishes before the script exits.
In `@src/components/elements/Leaderboard/Leaderboard.tsx`:
- Line 16: The Pressable in Leaderboard (the element with key={score.position}
and onPress={() => onEntryPress(score.user._id)}) currently uses a static style
(styles.entry) and lacks visual feedback; change its style prop to the
functional form that receives ({ pressed }) and returns an array/combination
like [styles.entry, pressed && styles.containerPressed] (or apply
opacity/highlight when pressed) and add a corresponding styles.containerPressed
entry (matching the Button's visual feedback pattern) to the stylesheet so
entries visibly respond to touch.
In `@src/components/navigation/ModalViewBackButton/ModalViewBackButton.tsx`:
- Line 17: The ChevronLeftIcon in ModalViewBackButton lacks an explicit color,
which can cause it to not follow the current theme; update ModalViewBackButton
to pass the theme foreground color (e.g., headerTintColor or colors.text from
your theme) into the ChevronLeftIcon via its color prop—retrieve the color from
the same source you use for header text (useTheme() or the headerTintColor prop)
and supply it like <ChevronLeftIcon color={themeColor} size={28} strokeWidth={2}
/> so the icon matches dark/light mode.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3ecef82d-6a3a-4cda-acac-c238be253bb6
📒 Files selected for processing (32)
.zed/settings.jsonCLAUDE.mdbiome.jsonconvex/leaderboards/queries.tsconvex/notifications/queries.tsconvex/puzzleGuessAttempts/queries.tsconvex/puzzles/internal.tsconvex/puzzles/queries.tsconvex/users/queries.tsdb/seeders/dictionaryEntries/getDictionaryWords.jsdb/seeders/dictionaryEntries/getDictionaryWordsExplanations.jsjest.setup.tsxsrc/__tests__/settings.test.tsxsrc/__tests__/user-profile.test.tsxsrc/app/(authenticated)/_layout.tsxsrc/app/(authenticated)/leaderboards/all-time-leaderboard.tsxsrc/app/(authenticated)/leaderboards/weekly-leaderboard.tsxsrc/app/(authenticated)/user-profile.tsxsrc/components/elements/GuessGrid/GuessGrid.hook.tssrc/components/elements/Leaderboard/Leaderboard.tsxsrc/components/navigation/ModalViewBackButton/ModalViewBackButton.test.tsxsrc/components/navigation/ModalViewBackButton/ModalViewBackButton.tsxsrc/components/navigation/ModalViewBackButton/index.tssrc/components/navigation/index.tssrc/components/ui/Button/Button.tsxsrc/components/ui/RadioInput/RadioInput.tsxsrc/hooks/useDailyPuzzle/useDailyPuzzle.test.tssrc/hooks/useLeaderboards/useLeaderboards.test.tssrc/hooks/usePushNotifications/usePushNotifications.test.tssrc/hooks/usePuzzlesStatistics/usePuzzlesStatistics.test.tssrc/hooks/useTrainingPuzzle/useTrainingPuzzle.test.tssrc/hooks/useUser/useUser.test.ts
💤 Files with no reviewable changes (14)
- convex/puzzles/internal.ts
- convex/notifications/queries.ts
- convex/leaderboards/queries.ts
- src/components/ui/RadioInput/RadioInput.tsx
- src/hooks/usePuzzlesStatistics/usePuzzlesStatistics.test.ts
- convex/users/queries.ts
- convex/puzzleGuessAttempts/queries.ts
- src/components/elements/GuessGrid/GuessGrid.hook.ts
- src/hooks/usePushNotifications/usePushNotifications.test.ts
- src/hooks/useUser/useUser.test.ts
- src/hooks/useDailyPuzzle/useDailyPuzzle.test.ts
- convex/puzzles/queries.ts
- src/hooks/useTrainingPuzzle/useTrainingPuzzle.test.ts
- src/hooks/useLeaderboards/useLeaderboards.test.ts
Summary
Adds a user profile screen accessible by tapping any entry in a leaderboard. Users can now see a player's daily puzzle stats — win rate, streaks, attempts distribution — without leaving the leaderboard flow.
No backend changes were needed: the existing
readUserPuzzleStatisticsquery already accepts anyuserId, so this is a pure frontend addition.Changes
Leaderboardcomponent — entries are now tappable. Added a requiredonEntryPress: (userId: string) => voidprop and wrapped each entry in aPressable.Leaderboard screens (weekly + all-time) — pass
onEntryPressnavigating to the newuser-profilescreen.New screen:
user-profile.tsx— shows the tapped user's profile:Stack.Screen("Nalagam podatke..."while loading → nickname once resolved)dayjsAttemptsDistributionGraphcomponent)_layout.tsx— registersuser-profileas a modal stack screen.Testing
user-profile.tsx(10 cases): query args, loading state, dynamic title, stats rendering, empty state, win rate edge caseModalViewBackButton(4 cases): null render, button render, press handlerjest.setup.tsx— addeddayjs.locale('sl')globally so date assertions match the real app behavioursettings.test.tsxdate assertion to use Slovenian month abbreviation (jul.)Summary by CodeRabbit
New Features
Accessibility
Tests