Skip to content

feat: implement Public Earnings Section in Profile#426

Merged
Benjtalkshow merged 8 commits into
boundlessfi:mainfrom
Josue19-08:feat/public-earnings-profile-section
Mar 2, 2026
Merged

feat: implement Public Earnings Section in Profile#426
Benjtalkshow merged 8 commits into
boundlessfi:mainfrom
Josue19-08:feat/public-earnings-profile-section

Conversation

@Josue19-08
Copy link
Copy Markdown
Contributor

@Josue19-08 Josue19-08 commented Mar 2, 2026

Summary

  • Add public earnings section to user profile page (/profile/[username])
  • Display total earnings, breakdown by category, and verified activity history
  • Implements visibility filtering to show only public data (no pending withdrawals or claim buttons)

Changes

  • types/earnings.ts: Add TypeScript types for public earnings API response
  • lib/api/earnings.ts: Add getPublicEarnings function for fetching public earnings data
  • components/profile/PublicEarningsTab.tsx: New component displaying earnings summary and activity
  • app/(landing)/profile/[username]/profile-data.tsx: Integrate Earnings tab in profile page

Test plan

  • Open public profile from incognito window or different account
  • Verify total earnings, categorized breakdown, and activity history display correctly
  • Confirm pending withdrawals, claim buttons, and sensitive data are NOT visible
  • Run npm run lint - passes
  • Run npm run build - passes

Closes #391

Summary by CodeRabbit

  • New Features
    • Earnings tab on user profiles showing total earned, breakdown by source (Hackathons, Grants, Crowdfunding, Bounties), and a list of verified earning activities with amounts and relative timestamps.
  • Refactor
    • Profile view now recognizes signed-in vs. other users and manages loading/error states for profile data.
  • Chores
    • Centralized tab styling for consistent appearance across profile tabs.
Captura de pantalla 2026-03-01 a la(s) 9 12 29 p  m

@Josue19-08 Josue19-08 requested a review from 0xdevcollins as a code owner March 2, 2026 04:27
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 2, 2026

@Josue19-08 is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 2, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Profile Page
app/(landing)/profile/[username]/profile-data.tsx
Adds authUsername, loading/error state around profile fetch, introduces an Earnings tab using PublicEarningsTab, extracts TAB_CLASS constant, and updates function return type to React.ReactElement.
Earnings UI Component
components/profile/PublicEarningsTab.tsx
New client-side component that fetches public earnings via getPublicEarnings, shows total earned, a breakdown by sources (hackathons, grants, crowdfunding, bounties) with icons/colors, and a (verified) activities list with loading/error handling and animations.
API Client
lib/api/earnings.ts
New getPublicEarnings function and GetPublicEarningsParams interface; validates params, builds query string, and calls /users/earnings/public.
Types
types/earnings.ts
New types: EarningSource, EarningActivity, EarningsBreakdown, and PublicEarningsResponse describing the public earnings payload.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • 0xdevcollins
  • Benjtalkshow

Poem

🐰 I hopped along the profile trail,

Found earnings bright like morning grail,
Hackathons, grants in tidy rows,
A public tab where good work shows,
I nibble carrots, cheer and sail 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: implementing a public earnings section in the user profile, which aligns with all code modifications.
Linked Issues check ✅ Passed All linked issue requirements are met: public earnings endpoint implemented with visibility filtering, component follows existing patterns, input validation included, and production-grade quality maintained.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the public earnings section; no unrelated modifications detected in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Don’t block public profile loading on session lookup, and avoid exposing raw error text.

authClient.getSession() and getUserProfileByUsername() are in the same try. If session retrieval fails, public profile rendering fails too. Also, interpolating err into 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.

ProfileData and loadProfile are 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 .ts file; 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, and loadEarnings are 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 04c46d6 and 70ba410.

📒 Files selected for processing (5)
  • .prettierignore
  • app/(landing)/profile/[username]/profile-data.tsx
  • components/profile/PublicEarningsTab.tsx
  • lib/api/earnings.ts
  • types/earnings.ts

Comment thread components/profile/PublicEarningsTab.tsx Outdated
Comment thread components/profile/PublicEarningsTab.tsx
Comment thread lib/api/earnings.ts Outdated
Comment thread types/earnings.ts
Josue19-08 and others added 4 commits March 1, 2026 22:34
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>
@Josue19-08 Josue19-08 force-pushed the feat/public-earnings-profile-section branch from 70ba410 to cf8ea72 Compare March 2, 2026 04:34
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Harden async profile loading against stale updates and sticky errors.

This effect can set state after unmount/username switch, and it doesn’t clear previous error before 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 | 🟠 Major

Use activity.currency when 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 | 🟠 Major

Guard state updates against stale/unmounted async requests.

A slower previous request can still call setEarnings, setError, or setLoading after username changes 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 typed const arrow functions in TSX for consistency.

Helpers/components here are declared with function declarations instead of typed const arrows.

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 typed const arrow functions in TSX.

ProfileData/loadProfile are function declarations; repo style asks for typed const arrow 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 70ba410 and cf8ea72.

📒 Files selected for processing (4)
  • app/(landing)/profile/[username]/profile-data.tsx
  • components/profile/PublicEarningsTab.tsx
  • lib/api/earnings.ts
  • types/earnings.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/api/earnings.ts
  • types/earnings.ts

Comment thread app/(landing)/profile/[username]/profile-data.tsx
Josue19-08 and others added 3 commits March 1, 2026 22:44
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>
@Josue19-08
Copy link
Copy Markdown
Contributor Author

Hey @coderabbitai I've applied the requested corrections.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 2, 2026

@Josue19-08, thank you for applying the requested corrections! I appreciate you addressing the feedback.

I'll go ahead and review the updated changes to verify everything looks good.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Collaborator

@Benjtalkshow Benjtalkshow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Josue19-08
Here are some fixes you should implement:

  1. I have noticed a crash in the PublicEarningsTab component.
    The issue was caused by Object.entries() being called on an undefined earnings.breakdown property. Kindly add a guard clause and a fallback value to ensure the component handles missing data gracefully.
  2. Adjust the typography in PublicEarningsTab.tsx
    to align with the text sizes and styles used in ActivityTab.tsx
    and ActivityFeedPublic.tsx. This ensures a consistent look and feel across the profile tabs.
  • Reduce "Total Earned" from text-3xl to text-2xl.
  • Align activity titles and metadata to text-sm and text-xs.
  • Update section headers to match the uppercase, tracking-wider style used in the activity feed.
  1. 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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
lib/api/earnings.ts (1)

15-25: ⚠️ Potential issue | 🟠 Major

Normalize inputs before building query params (trim + finite integer coercion).

Line 15 validates username before trimming, so whitespace-only values can still become empty at Line 23.
Line 19-20 clamps 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.

📥 Commits

Reviewing files that changed from the base of the PR and between cf8ea72 and 8fbb442.

📒 Files selected for processing (3)
  • components/profile/PublicEarningsTab.tsx
  • lib/api/earnings.ts
  • types/earnings.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • types/earnings.ts

@Josue19-08
Copy link
Copy Markdown
Contributor Author

Hi @Josue19-08 Here are some fixes you should implement:

  1. I have noticed a crash in the PublicEarningsTab component.
    The issue was caused by Object.entries() being called on an undefined earnings.breakdown property. Kindly add a guard clause and a fallback value to ensure the component handles missing data gracefully.
  2. Adjust the typography in PublicEarningsTab.tsx
    to align with the text sizes and styles used in ActivityTab.tsx
    and ActivityFeedPublic.tsx. This ensures a consistent look and feel across the profile tabs.
  • Reduce "Total Earned" from text-3xl to text-2xl.
  • Align activity titles and metadata to text-sm and text-xs.
  • Update section headers to match the uppercase, tracking-wider style used in the activity feed.
  1. It shows "No earnings data available" on empty state. Kindly reuse our empty state component in components/EmptyState.tsx

Hi @Benjtalkshow, corrections applied, please review again and LMK.

Copy link
Copy Markdown
Collaborator

@Benjtalkshow Benjtalkshow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@Benjtalkshow Benjtalkshow merged commit 6fc1293 into boundlessfi:main Mar 2, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Public Earnings Section in Profile

2 participants