feat: implement Public Earnings Section in Profile#426
Conversation
|
@Josue19-08 is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughAdds a public Earnings tab to user profiles: new PublicEarningsTab component, API client to fetch public earnings, TypeScript earnings types, and updates ProfileData to show an earnings tab with auth-aware rendering and loading/error states. Changes
Sequence DiagramsequenceDiagram
participant User as User (Browser)
participant Profile as ProfileData
participant Earnings as PublicEarningsTab
participant API as getPublicEarnings
participant Backend as Backend API
User->>Profile: Navigate to /profile/[username]
activate Profile
Profile->>Profile: determine authUsername, set loading
Profile->>Earnings: render Earnings tab (props: username)
activate Earnings
Earnings->>Earnings: useEffect on username -> set loading
Earnings->>API: getPublicEarnings(username, limit, offset)
activate API
API->>Backend: GET /users/earnings/public?username=...
activate Backend
Backend-->>API: 200 PublicEarningsResponse
deactivate Backend
API-->>Earnings: return data
deactivate API
Earnings->>Earnings: update state, render summary, breakdown, activities
Earnings-->>Profile: display tab content
Profile-->>User: show profile with Earnings tab
deactivate Earnings
deactivate Profile
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/(landing)/profile/[username]/profile-data.tsx (1)
50-60:⚠️ Potential issue | 🟠 MajorDon’t block public profile loading on session lookup, and avoid exposing raw error text.
authClient.getSession()andgetUserProfileByUsername()are in the sametry. If session retrieval fails, public profile rendering fails too. Also, interpolatingerrinto UI can leak internals.Proposed fix
- async function loadProfile(): Promise<void> { + async function loadProfile(): Promise<void> { try { setLoading(true); - const { data: session } = await authClient.getSession(); - setAuthUsername(session?.user?.profile?.username || null); - setIsAuthenticated(!!session?.user); + setError(null); + + try { + const { data: session } = await authClient.getSession(); + setAuthUsername(session?.user?.profile?.username || null); + setIsAuthenticated(!!session?.user); + } catch { + setAuthUsername(null); + setIsAuthenticated(false); + } + const data = await getUserProfileByUsername(username); setUserData(data); - } catch (err) { - setError(`Failed to load user profile: ${err}`); + } catch { + setError('Failed to load user profile'); } finally { setLoading(false); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/profile/[username]/profile-data.tsx around lines 50 - 60, loadProfile currently wraps authClient.getSession() and getUserProfileByUsername() in the same try which blocks public profile rendering when session lookup fails and also exposes raw error text; change loadProfile to first attempt getUserProfileByUsername(username) in its own try/catch and call setUserData on success, then separately attempt authClient.getSession() in a separate try to setAuthUsername and setIsAuthenticated (on failure just log the error with console.error or a logger and do not set UI error), and in the catch that sets setError replace raw err interpolation with a generic message like "Failed to load user profile" while logging the detailed error to the console/logger so internals are not leaked.
🧹 Nitpick comments (3)
app/(landing)/profile/[username]/profile-data.tsx (1)
39-41: Use typed const-arrow functions in this TSX module for consistency.
ProfileDataandloadProfileare function declarations; style guide prefers typed const-arrow definitions.As per coding guidelines,
**/*.{ts,tsx}: "Prefer const arrow functions with explicit type annotations over function declarations".Also applies to: 50-50
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/profile/[username]/profile-data.tsx around lines 39 - 41, The module currently uses function declarations for ProfileData (and loadProfile); change them to typed const-arrow functions to match the style guide: convert "function ProfileData({ username }: PublicProfileDataProps): React.ReactElement" to a const arrow with explicit types (e.g., const ProfileData: React.FC<PublicProfileDataProps> = ({ username }): React.ReactElement => { ... }) and do the same for loadProfile (give it an explicit function type or return type and assign it to a const using an arrow) so both ProfileData and loadProfile become typed const-arrow definitions.lib/api/earnings.ts (1)
10-14: Prefer a typed const-arrow export for this TS API helper.This is a function declaration in a
.tsfile; repo style prefers const arrow functions with explicit annotations.As per coding guidelines,
**/*.{ts,tsx}: "Prefer const arrow functions with explicit type annotations over function declarations".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/earnings.ts` around lines 10 - 14, Convert the function declaration getPublicEarnings into an exported const arrow function with explicit TypeScript types: change to "export const getPublicEarnings = async ({ username, limit = 100, offset = 0 }: GetPublicEarningsParams): Promise<ApiResponse<PublicEarningsResponse>> => { ... }" so the parameter destructuring keeps the GetPublicEarningsParams annotation and the function has the explicit Promise<ApiResponse<PublicEarningsResponse>> return type, preserving existing logic and export.components/profile/PublicEarningsTab.tsx (1)
35-50: Prefer typed const-arrow functions for TSX consistency.
formatCurrency,EarningActivityItem,PublicEarningsTab, andloadEarningsare function declarations; repo style prefers const-arrow definitions.As per coding guidelines,
**/*.{ts,tsx}: "Prefer const arrow functions with explicit type annotations over function declarations".Also applies to: 83-85, 91-91
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/profile/PublicEarningsTab.tsx` around lines 35 - 50, The functions declared as plain function declarations (formatCurrency, EarningActivityItem, PublicEarningsTab, and loadEarnings) should be converted to const-assigned arrow functions with explicit TypeScript type annotations to match the repo style; for example, change formatCurrency to a const formatCurrency: (amount: number) => string = (amount) => ..., change EarningActivityItem to const EarningActivityItem: (props: EarningActivityItemProps) => React.ReactElement = ({ activity }) => ..., annotate PublicEarningsTab with the appropriate component type (e.g., React.FC or a precise React.ReactElement return type), and type loadEarnings with its explicit parameter and return types (e.g., const loadEarnings: () => Promise<YourReturnType> = async () => ...); keep existing logic and exports identical, and update any internal references to use the new const names.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/profile/PublicEarningsTab.tsx`:
- Around line 35-41: The formatCurrency function currently hardcodes 'USD';
change it to accept a currency parameter (e.g., formatCurrency(amount: number,
currency = 'USD')) and use that currency when constructing Intl.NumberFormat so
callers can pass activity.currency; update all call sites (including where
formatCurrency is used around the activity rendering at the other occurrence) to
pass activity.currency (falling back to 'USD' if undefined) so amounts render
with the correct currency symbol and formatting.
- Around line 90-106: The effect's async loadEarnings can update state after a
stale request resolves when username changes or the component unmounts; modify
useEffect to cancel/stale-guard the request by using an AbortController or a
mounted flag: create an AbortController (or a let mounted = true) inside
useEffect, pass its signal to getPublicEarnings if supported or check the
mounted/aborted flag before calling setEarnings, setError, and setLoading, and
in the cleanup return handler call controller.abort() or set mounted = false so
that only the latest request (or while mounted) updates state in loadEarnings.
In `@lib/api/earnings.ts`:
- Around line 10-19: In getPublicEarnings validate inputs early: return a
rejected ApiResponse or throw on empty/whitespace username, coerce limit and
offset to safe integers (use Number.parseInt or Number and isFinite), clamp
limit to a sensible range (e.g., 1..1000) and offset to >=0, and replace NaN
with defaults; only build URLSearchParams after these checks. Update the
parameter handling in getPublicEarnings to perform early returns for invalid
username and to normalize/clamp limit and offset before calling new
URLSearchParams so malformed/negative/NaN values are never sent downstream.
In `@types/earnings.ts`:
- Around line 7-14: EarningActivity currently exposes an entity identifier via
the id field; remove id from the public contract by deleting the id property
from the EarningActivity interface in types/earnings.ts and ensure callers that
mapped internal entities to EarningActivity (e.g., any mapper/serializer
functions) use an internal-only identifier (like entityId/internalId)
server-side or omit it entirely when constructing EarningActivity, updating
references to EarningActivity throughout the codebase to stop relying on id and
to use server-side logging/metadata for tracing instead.
---
Outside diff comments:
In `@app/`(landing)/profile/[username]/profile-data.tsx:
- Around line 50-60: loadProfile currently wraps authClient.getSession() and
getUserProfileByUsername() in the same try which blocks public profile rendering
when session lookup fails and also exposes raw error text; change loadProfile to
first attempt getUserProfileByUsername(username) in its own try/catch and call
setUserData on success, then separately attempt authClient.getSession() in a
separate try to setAuthUsername and setIsAuthenticated (on failure just log the
error with console.error or a logger and do not set UI error), and in the catch
that sets setError replace raw err interpolation with a generic message like
"Failed to load user profile" while logging the detailed error to the
console/logger so internals are not leaked.
---
Nitpick comments:
In `@app/`(landing)/profile/[username]/profile-data.tsx:
- Around line 39-41: The module currently uses function declarations for
ProfileData (and loadProfile); change them to typed const-arrow functions to
match the style guide: convert "function ProfileData({ username }:
PublicProfileDataProps): React.ReactElement" to a const arrow with explicit
types (e.g., const ProfileData: React.FC<PublicProfileDataProps> = ({ username
}): React.ReactElement => { ... }) and do the same for loadProfile (give it an
explicit function type or return type and assign it to a const using an arrow)
so both ProfileData and loadProfile become typed const-arrow definitions.
In `@components/profile/PublicEarningsTab.tsx`:
- Around line 35-50: The functions declared as plain function declarations
(formatCurrency, EarningActivityItem, PublicEarningsTab, and loadEarnings)
should be converted to const-assigned arrow functions with explicit TypeScript
type annotations to match the repo style; for example, change formatCurrency to
a const formatCurrency: (amount: number) => string = (amount) => ..., change
EarningActivityItem to const EarningActivityItem: (props:
EarningActivityItemProps) => React.ReactElement = ({ activity }) => ...,
annotate PublicEarningsTab with the appropriate component type (e.g., React.FC
or a precise React.ReactElement return type), and type loadEarnings with its
explicit parameter and return types (e.g., const loadEarnings: () =>
Promise<YourReturnType> = async () => ...); keep existing logic and exports
identical, and update any internal references to use the new const names.
In `@lib/api/earnings.ts`:
- Around line 10-14: Convert the function declaration getPublicEarnings into an
exported const arrow function with explicit TypeScript types: change to "export
const getPublicEarnings = async ({ username, limit = 100, offset = 0 }:
GetPublicEarningsParams): Promise<ApiResponse<PublicEarningsResponse>> => { ...
}" so the parameter destructuring keeps the GetPublicEarningsParams annotation
and the function has the explicit Promise<ApiResponse<PublicEarningsResponse>>
return type, preserving existing logic and export.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
.prettierignoreapp/(landing)/profile/[username]/profile-data.tsxcomponents/profile/PublicEarningsTab.tsxlib/api/earnings.tstypes/earnings.ts
Add TypeScript types for the public earnings API response: - EarningSource union type for earning categories - EarningActivity interface for individual earnings - EarningsBreakdown interface for category totals - PublicEarningsResponse interface for API response
Add getPublicEarnings function to fetch public earnings data for user profiles. This endpoint is unauthenticated and returns visibility-filtered earnings data. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add new component to display public earnings on user profiles: - Total earnings summary with prominent display - Breakdown by source (hackathons, grants, crowdfunding, bounties) - Verified activity history with timestamps - Consistent styling with existing ActivityTab component Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Earnings tab to the public profile page tabs: - New tab alongside Activity and Projects - Displays PublicEarningsTab component - Extract TAB_CLASS constant to reduce code duplication - Rename authUser to authUsername for clarity Closes boundlessfi#391 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
70ba410 to
cf8ea72
Compare
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)
app/(landing)/profile/[username]/profile-data.tsx (1)
49-66:⚠️ Potential issue | 🟠 MajorHarden async profile loading against stale updates and sticky errors.
This effect can set state after unmount/username switch, and it doesn’t clear previous
errorbefore a new fetch attempt.🧩 Proposed fix
useEffect(() => { - async function loadProfile(): Promise<void> { + let isActive = true; + const loadProfile = async (): Promise<void> => { try { setLoading(true); + setError(null); const { data: session } = await authClient.getSession(); - setAuthUsername(session?.user?.profile?.username || null); - setIsAuthenticated(!!session?.user); + if (!isActive) return; + setAuthUsername(session?.user?.profile?.username || null); + setIsAuthenticated(!!session?.user); const data = await getUserProfileByUsername(username); - setUserData(data); + if (isActive) setUserData(data); } catch (err) { - setError(`Failed to load user profile: ${err}`); + if (isActive) { + setError('Failed to load user profile'); + console.error('Failed to load user profile:', err); + } } finally { - setLoading(false); + if (isActive) setLoading(false); } - } + }; loadProfile(); + return () => { + isActive = false; + }; }, [username]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/profile/[username]/profile-data.tsx around lines 49 - 66, The effect’s async loader (loadProfile) can update state after unmount or after a username change and doesn’t clear previous errors; modify useEffect/loadProfile to (1) clear any previous error at start by calling setError(null) before fetching, (2) use an abort/stale marker (e.g., let cancelled = false or a ref currentRequestId) inside loadProfile and check it before calling setLoading, setAuthUsername, setIsAuthenticated, setUserData, and setError, and (3) in the effect cleanup set cancelled = true (or increment the requestId) so stale responses won’t call state setters; apply these changes to the loadProfile function and its finally/try blocks to prevent sticky errors and stale updates.
♻️ Duplicate comments (2)
components/profile/PublicEarningsTab.tsx (2)
35-41:⚠️ Potential issue | 🟠 MajorUse
activity.currencywhen formatting activity amounts.The formatter is hardcoded to USD, so non-USD activities render with the wrong currency symbol/format.
💱 Proposed fix
-function formatCurrency(amount: number): string { +const formatCurrency = (amount: number, currency = 'USD'): string => { return new Intl.NumberFormat('en-US', { style: 'currency', - currency: 'USD', + currency, minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(amount); -} +}; @@ - {formatCurrency(activity.amount)} + {formatCurrency(activity.amount, activity.currency || 'USD')}Also applies to: 74-77
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/profile/PublicEarningsTab.tsx` around lines 35 - 41, The currency formatter is hardcoded to USD; update formatCurrency to accept a currency parameter (e.g., formatCurrency(amount: number, currency?: string)) and use that value for the Intl.NumberFormat currency option (falling back to 'USD' if undefined), and then update the calls that format activity amounts to pass activity.currency (where amounts are currently formatted around the activity object). Ensure usages that currently call formatCurrency(amount) are changed to formatCurrency(amount, activity.currency) so non-USD activities render with the correct symbol/format.
90-106:⚠️ Potential issue | 🟠 MajorGuard state updates against stale/unmounted async requests.
A slower previous request can still call
setEarnings,setError, orsetLoadingafterusernamechanges or unmount.🛡️ Proposed fix
useEffect(() => { - async function loadEarnings(): Promise<void> { + let isActive = true; + const loadEarnings = async (): Promise<void> => { try { setLoading(true); setError(null); const response = await getPublicEarnings({ username }); - setEarnings(response.data); + if (isActive) setEarnings(response.data); } catch (err) { - setError('Unable to load earnings data'); - console.error('Failed to load earnings:', err); + if (isActive) { + setError('Unable to load earnings data'); + console.error('Failed to load earnings:', err); + } } finally { - setLoading(false); + if (isActive) setLoading(false); } - } + }; loadEarnings(); + return () => { + isActive = false; + }; }, [username]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/profile/PublicEarningsTab.tsx` around lines 90 - 106, The effect's async loader (loadEarnings inside the useEffect) can call setEarnings/setError/setLoading after the component has unmounted or after username changed, so add a cancellation guard: create a local "isMounted" (or use AbortController) inside the useEffect, set it true at start and false in the cleanup, then before any state updates in loadEarnings check the guard (e.g., if (!isMounted) return) or use the AbortController signal to bail out; update the useEffect cleanup to flip the flag / abort the request so setEarnings, setError and setLoading are only called when the component is still mounted and the request is current.
🧹 Nitpick comments (2)
components/profile/PublicEarningsTab.tsx (1)
35-50: Prefer typedconstarrow functions in TSX for consistency.Helpers/components here are declared with
functiondeclarations instead of typedconstarrows.As per coding guidelines "
**/*.{ts,tsx}: Prefer const arrow functions with explicit type annotations over function declarations."Also applies to: 83-91
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/profile/PublicEarningsTab.tsx` around lines 35 - 50, Convert the function declarations to typed const arrow functions: replace the standalone function formatCurrency(amount: number): string with a const formatCurrency: (amount: number) => string arrow function that returns the same Intl.NumberFormat result, and replace the component function EarningActivityItem({ activity }: EarningActivityItemProps): React.ReactElement with a const EarningActivityItem: React.FC<EarningActivityItemProps> (or const EarningActivityItem = ({ activity }: EarningActivityItemProps): React.ReactElement => ...) arrow function form, ensuring explicit TypeScript type annotations are preserved for props and return types; apply the same pattern to the other helper/component at lines 83-91.app/(landing)/profile/[username]/profile-data.tsx (1)
39-41: Prefer typedconstarrow functions in TSX.
ProfileData/loadProfileare function declarations; repo style asks for typedconstarrow functions.As per coding guidelines "
**/*.{ts,tsx}: Prefer const arrow functions with explicit type annotations over function declarations."Also applies to: 50-50
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/profile/[username]/profile-data.tsx around lines 39 - 41, Replace the function declarations with typed const arrow functions: change "export function ProfileData({ username, }: PublicProfileDataProps): React.ReactElement { ... }" to a const arrow with explicit type (e.g. "export const ProfileData = ({ username }: PublicProfileDataProps): React.ReactElement => { ... }"), and do the same for "loadProfile" (e.g. "export const loadProfile = async (username: string): Promise<YourProfileType | null> => { ... }"), preserving the original names/exports and return types/signatures used in the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(landing)/profile/[username]/profile-data.tsx:
- Around line 121-126: The mobile TabsTrigger for organizations (TabsTrigger
with value='organizations' and className using TAB_CLASS) is always rendered
while the organizations tab content is conditionally gated, which can leave
users on an empty tab; fix it by wrapping this TabsTrigger (and the other
occurrence at the later block) in the same ownership/authentication condition
used to gate the organizations content (the is-owner/isOwnProfile check used
elsewhere) so the trigger only renders when the corresponding content is
available.
---
Outside diff comments:
In `@app/`(landing)/profile/[username]/profile-data.tsx:
- Around line 49-66: The effect’s async loader (loadProfile) can update state
after unmount or after a username change and doesn’t clear previous errors;
modify useEffect/loadProfile to (1) clear any previous error at start by calling
setError(null) before fetching, (2) use an abort/stale marker (e.g., let
cancelled = false or a ref currentRequestId) inside loadProfile and check it
before calling setLoading, setAuthUsername, setIsAuthenticated, setUserData, and
setError, and (3) in the effect cleanup set cancelled = true (or increment the
requestId) so stale responses won’t call state setters; apply these changes to
the loadProfile function and its finally/try blocks to prevent sticky errors and
stale updates.
---
Duplicate comments:
In `@components/profile/PublicEarningsTab.tsx`:
- Around line 35-41: The currency formatter is hardcoded to USD; update
formatCurrency to accept a currency parameter (e.g., formatCurrency(amount:
number, currency?: string)) and use that value for the Intl.NumberFormat
currency option (falling back to 'USD' if undefined), and then update the calls
that format activity amounts to pass activity.currency (where amounts are
currently formatted around the activity object). Ensure usages that currently
call formatCurrency(amount) are changed to formatCurrency(amount,
activity.currency) so non-USD activities render with the correct symbol/format.
- Around line 90-106: The effect's async loader (loadEarnings inside the
useEffect) can call setEarnings/setError/setLoading after the component has
unmounted or after username changed, so add a cancellation guard: create a local
"isMounted" (or use AbortController) inside the useEffect, set it true at start
and false in the cleanup, then before any state updates in loadEarnings check
the guard (e.g., if (!isMounted) return) or use the AbortController signal to
bail out; update the useEffect cleanup to flip the flag / abort the request so
setEarnings, setError and setLoading are only called when the component is still
mounted and the request is current.
---
Nitpick comments:
In `@app/`(landing)/profile/[username]/profile-data.tsx:
- Around line 39-41: Replace the function declarations with typed const arrow
functions: change "export function ProfileData({ username, }:
PublicProfileDataProps): React.ReactElement { ... }" to a const arrow with
explicit type (e.g. "export const ProfileData = ({ username }:
PublicProfileDataProps): React.ReactElement => { ... }"), and do the same for
"loadProfile" (e.g. "export const loadProfile = async (username: string):
Promise<YourProfileType | null> => { ... }"), preserving the original
names/exports and return types/signatures used in the file.
In `@components/profile/PublicEarningsTab.tsx`:
- Around line 35-50: Convert the function declarations to typed const arrow
functions: replace the standalone function formatCurrency(amount: number):
string with a const formatCurrency: (amount: number) => string arrow function
that returns the same Intl.NumberFormat result, and replace the component
function EarningActivityItem({ activity }: EarningActivityItemProps):
React.ReactElement with a const EarningActivityItem:
React.FC<EarningActivityItemProps> (or const EarningActivityItem = ({ activity
}: EarningActivityItemProps): React.ReactElement => ...) arrow function form,
ensuring explicit TypeScript type annotations are preserved for props and return
types; apply the same pattern to the other helper/component at lines 83-91.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
app/(landing)/profile/[username]/profile-data.tsxcomponents/profile/PublicEarningsTab.tsxlib/api/earnings.tstypes/earnings.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- lib/api/earnings.ts
- types/earnings.ts
Entity IDs should not be exposed in public earnings data as specified in issue requirements. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Validate username is a non-empty string - Sanitize limit to range [1, 100] - Sanitize offset to be non-negative - Trim whitespace from username - Convert to const arrow function for consistency Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add AbortController to prevent race conditions in useEffect - Update formatCurrency to accept currency parameter - Use composite key for activity list (source + occurredAt + index) - Convert function declarations to const arrow functions - Remove redundant currency label display Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Hey @coderabbitai I've applied the requested corrections. |
|
I'll go ahead and review the updated changes to verify everything looks good. ✅ Actions performedReview triggered.
|
Benjtalkshow
left a comment
There was a problem hiding this comment.
Hi @Josue19-08
Here are some fixes you should implement:
- I have noticed a crash in the
PublicEarningsTabcomponent.
The issue was caused byObject.entries()being called on an undefinedearnings.breakdownproperty. Kindly add a guard clause and a fallback value to ensure the component handles missing data gracefully. - Adjust the typography in
PublicEarningsTab.tsx
to align with the text sizes and styles used inActivityTab.tsx
andActivityFeedPublic.tsx. This ensures a consistent look and feel across the profile tabs.
- Reduce "Total Earned" from
text-3xltotext-2xl. - Align activity titles and metadata to
text-smandtext-xs. - Update section headers to match the uppercase, tracking-wider style used in the activity feed.
- It shows "No earnings data available" on empty state. Kindly reuse our empty state component in
components/EmptyState.tsx
- Add guard clause for undefined earnings.breakdown to prevent crash - Add fallback values for earnings.summary and earnings.activities - Use EmptyState component for error/empty states - Adjust typography to match ActivityTab and ActivityFeedPublic: - Reduce "Total Earned" from text-3xl to text-2xl - Activity titles to text-sm, metadata to text-xs - Section header with uppercase and tracking-wider style Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
lib/api/earnings.ts (1)
15-25:⚠️ Potential issue | 🟠 MajorNormalize inputs before building query params (trim + finite integer coercion).
Line 15validatesusernamebefore trimming, so whitespace-only values can still become empty atLine 23.
Line 19-20clamps bounds but does not coerce/guard non-finite values, which can leak invalid query values.Suggested patch
export const getPublicEarnings = async ({ username, limit = 100, offset = 0, }: GetPublicEarningsParams): Promise<ApiResponse<PublicEarningsResponse>> => { - if (!username || typeof username !== 'string') { - throw new Error('Username is required and must be a string'); + const normalizedUsername = username?.trim(); + if (!normalizedUsername) { + throw new Error('Username is required'); } - const sanitizedLimit = Math.max(1, Math.min(limit, 100)); - const sanitizedOffset = Math.max(0, offset); + const parsedLimit = Number.isFinite(limit) ? Math.trunc(limit) : 100; + const parsedOffset = Number.isFinite(offset) ? Math.trunc(offset) : 0; + const sanitizedLimit = Math.max(1, Math.min(parsedLimit, 100)); + const sanitizedOffset = Math.max(0, parsedOffset); const params = new URLSearchParams({ - username: username.trim(), + username: normalizedUsername, limit: sanitizedLimit.toString(), offset: sanitizedOffset.toString(), });As per coding guidelines,
**/*.{js,jsx,ts,tsx}: "Use early returns to improve code clarity".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/earnings.ts` around lines 15 - 25, Normalize and validate inputs before building params: trim username first (username = String(username || '').trim()) and throw early if it becomes empty, then coerce limit/offset to finite integers (e.g., parseInt or Number, fall back to defaults if NaN or not finite), then clamp those integer values into the allowed range and assign to sanitizedLimit/sanitizedOffset; finally use those sanitized values when constructing the URLSearchParams (referencing the username, limit, offset, sanitizedLimit, sanitizedOffset symbols and the code that creates URLSearchParams) so no whitespace-only usernames or non-finite numeric values are sent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@lib/api/earnings.ts`:
- Around line 15-25: Normalize and validate inputs before building params: trim
username first (username = String(username || '').trim()) and throw early if it
becomes empty, then coerce limit/offset to finite integers (e.g., parseInt or
Number, fall back to defaults if NaN or not finite), then clamp those integer
values into the allowed range and assign to sanitizedLimit/sanitizedOffset;
finally use those sanitized values when constructing the URLSearchParams
(referencing the username, limit, offset, sanitizedLimit, sanitizedOffset
symbols and the code that creates URLSearchParams) so no whitespace-only
usernames or non-finite numeric values are sent.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
components/profile/PublicEarningsTab.tsxlib/api/earnings.tstypes/earnings.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- types/earnings.ts
Hi @Benjtalkshow, corrections applied, please review again and LMK. |
Summary
/profile/[username])Changes
getPublicEarningsfunction for fetching public earnings dataTest plan
npm run lint- passesnpm run build- passesCloses #391
Summary by CodeRabbit