feat: implement private earnings dashboard with summary cards, source breakdown, activity feed, and claim action#410
Conversation
… breakdown, activity feed, and claim action
|
@System625 is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughImplements a new earnings dashboard feature for authenticated users at /me/earnings, including a React page component with summary cards and activity feed, sidebar navigation link, TypeScript type definitions for earnings data, and API client functions for fetching and claiming earnings. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant EarningsPage as EarningsPage<br/>(React Component)
participant API as Backend API
participant DB as Database
User->>EarningsPage: Visit /me/earnings
activate EarningsPage
EarningsPage->>API: GET /user/earnings
activate API
API->>DB: Query user earnings data
activate DB
DB-->>API: Return earnings breakdown & activities
deactivate DB
API-->>EarningsPage: GetUserEarningsResponse
deactivate API
EarningsPage->>EarningsPage: Render summary cards,<br/>breakdown, activity feed
deactivate EarningsPage
EarningsPage-->>User: Display earnings dashboard
User->>EarningsPage: Click Claim button on activity
activate EarningsPage
EarningsPage->>EarningsPage: Show "Claiming..." state
EarningsPage->>API: POST /user/earnings/{activityId}/claim
activate API
API->>DB: Update activity status to claimed
activate DB
DB-->>API: Confirmation
deactivate DB
API-->>EarningsPage: ClaimEarningResponse (success)
deactivate API
EarningsPage->>API: GET /user/earnings (refresh)
activate API
API->>DB: Query updated earnings data
activate DB
DB-->>API: Return updated data
deactivate DB
API-->>EarningsPage: GetUserEarningsResponse
deactivate API
EarningsPage->>EarningsPage: Update earnings state,<br/>refresh display
deactivate EarningsPage
EarningsPage-->>User: Show updated dashboard
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Poem
🚥 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)
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: 3
🧹 Nitpick comments (4)
lib/api/user/earnings.ts (1)
10-14: UseEarningStatusinstead ofstringforstatus.
ClaimEarningData.statusshould reuse the existingEarningStatusunion to keep the contract strict and consistent.♻️ Proposed refactor
-import { ApiResponse, EarningsData } from '../types'; +import { ApiResponse, EarningsData, EarningStatus } from '../types'; @@ export interface ClaimEarningData { activityId: string; - status: string; + status: EarningStatus; transactionHash?: string; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/user/earnings.ts` around lines 10 - 14, Update the ClaimEarningData interface to use the existing EarningStatus union instead of string: change the status property type to EarningStatus on the ClaimEarningData interface and add the appropriate import for EarningStatus at the top of the file; ensure any code creating or validating ClaimEarningData values is adjusted to use or accept EarningStatus values (e.g., where ClaimEarningData is constructed/validated) so types remain consistent with the EarningStatus union.app/me/earnings/page.tsx (3)
27-78: Use typedconstarrow components/functions to match repo standard.This file introduces multiple function declarations in TSX; convert them to
constarrow functions with explicit annotations for consistency with the codebase rule.As per coding guidelines
**/*.{ts,tsx}: Prefer const arrow functions with explicit type annotations over function declarations.Also applies to: 80-143, 145-194, 196-310
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/me/earnings/page.tsx` around lines 27 - 78, Change the plain function declarations to typed const arrow components: replace "function EarningsSkeleton() { ... }" with a const arrow form (for example "const EarningsSkeleton: React.FC = () => { ... }" or "const EarningsSkeleton = (): JSX.Element => { ... }"), and do the same for the other function components in this file (the ones in ranges 80-143, 145-194, 196-310). Ensure each component uses a const binding, an explicit return type annotation, and preserves the original implementation and exported names.
13-25: Narrow map keys to domain unions for stronger type safety.
Record<string, string>weakens checks here. PreferRecord<EarningSource, string>andRecord<EarningStatus, string>to enforce completeness and catch typos at compile time.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/me/earnings/page.tsx` around lines 13 - 25, SOURCE_LABELS and STATUS_CLASSES are typed too broadly as Record<string,string>; change their types to Record<EarningSource, string> and Record<EarningStatus, string> respectively (import or reference the EarningSource and EarningStatus union types used elsewhere) so the compiler enforces all keys and prevents typos, then update the object literals to include every union member or adjust the unions if a key is intentionally omitted; ensure any lookup sites still accept the narrowed key types (or cast explicitly if necessary).
175-178: Prefer a class helper for conditional badge classes.Inline string interpolation for conditional classes is harder to scale; use a class helper (
clsx/cn) for consistency.♻️ Proposed refactor
-<Badge - variant='outline' - className={`mt-0.5 text-xs ${STATUS_CLASSES[activity.status] ?? ''}`} -> +<Badge + variant='outline' + className={cn('mt-0.5 text-xs', STATUS_CLASSES[activity.status])} +>As per coding guidelines
**/*.{jsx,tsx}: For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/me/earnings/page.tsx` around lines 175 - 178, Replace the inline template literal used for Badge's className with the project's class helper (e.g., cn or clsx): change className={`mt-0.5 text-xs ${STATUS_CLASSES[activity.status] ?? ''}`} to className={cn('mt-0.5 text-xs', STATUS_CLASSES[activity.status])} (or the equivalent clsx call) so conditional classes come from the helper; update the import if needed and keep references to STATUS_CLASSES and activity.status intact and unchanged.
🤖 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/me/earnings/page.tsx`:
- Around line 203-211: The fetchEarnings success path doesn't clear a prior
error, so a past failure can keep the page in error state; inside fetchEarnings
after a successful getUserEarnings() and setEarnings(response.data) call, reset
the error state (e.g., call setError(null) or setError('')) so that error no
longer blocks the render logic that checks error || !earnings; ensure
setLoading(false) remains in finally.
- Around line 215-218: When the component detects that !isLoading && !authUser
and returns early it must also clear any private earnings state to avoid showing
stale data; update the unauthenticated branch (the conditional using isLoading
and authUser) to call the earnings state reset (e.g., setEarnings([]) or
setEarnings(null)) before setLoading(false) and return, and also clear any
related derived state (e.g., totalEarnings or pagination state) if present so
all private data is wiped when authUser becomes null.
In `@lib/api/user/earnings.ts`:
- Around line 24-27: The functions getUserEarnings and claimEarning currently
return res.data (the inner payload) but their declared return types
GetUserEarningsResponse and ClaimEarningResponse are the envelope
ApiResponse<T>; fix by returning the full response object (res) instead of
res.data so the returned value matches the envelope types, or alternatively
change the function return types to the raw data types (e.g., EarningsData /
ClaimEarningData) if you intentionally want to unwrap; update the return
statement in getUserEarnings and claimEarning accordingly and ensure
imports/types reflect the chosen shape.
---
Nitpick comments:
In `@app/me/earnings/page.tsx`:
- Around line 27-78: Change the plain function declarations to typed const arrow
components: replace "function EarningsSkeleton() { ... }" with a const arrow
form (for example "const EarningsSkeleton: React.FC = () => { ... }" or "const
EarningsSkeleton = (): JSX.Element => { ... }"), and do the same for the other
function components in this file (the ones in ranges 80-143, 145-194, 196-310).
Ensure each component uses a const binding, an explicit return type annotation,
and preserves the original implementation and exported names.
- Around line 13-25: SOURCE_LABELS and STATUS_CLASSES are typed too broadly as
Record<string,string>; change their types to Record<EarningSource, string> and
Record<EarningStatus, string> respectively (import or reference the
EarningSource and EarningStatus union types used elsewhere) so the compiler
enforces all keys and prevents typos, then update the object literals to include
every union member or adjust the unions if a key is intentionally omitted;
ensure any lookup sites still accept the narrowed key types (or cast explicitly
if necessary).
- Around line 175-178: Replace the inline template literal used for Badge's
className with the project's class helper (e.g., cn or clsx): change
className={`mt-0.5 text-xs ${STATUS_CLASSES[activity.status] ?? ''}`} to
className={cn('mt-0.5 text-xs', STATUS_CLASSES[activity.status])} (or the
equivalent clsx call) so conditional classes come from the helper; update the
import if needed and keep references to STATUS_CLASSES and activity.status
intact and unchanged.
In `@lib/api/user/earnings.ts`:
- Around line 10-14: Update the ClaimEarningData interface to use the existing
EarningStatus union instead of string: change the status property type to
EarningStatus on the ClaimEarningData interface and add the appropriate import
for EarningStatus at the top of the file; ensure any code creating or validating
ClaimEarningData values is adjusted to use or accept EarningStatus values (e.g.,
where ClaimEarningData is constructed/validated) so types remain consistent with
the EarningStatus union.
ℹ️ 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 ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (4)
app/me/earnings/page.tsxcomponents/app-sidebar.tsxlib/api/types.tslib/api/user/earnings.ts
| const fetchEarnings = async () => { | ||
| try { | ||
| const response = await getUserEarnings(); | ||
| setEarnings(response.data); | ||
| } catch { | ||
| setError('Failed to load earnings data.'); | ||
| } finally { | ||
| setLoading(false); | ||
| } |
There was a problem hiding this comment.
Clear stale error state on successful fetch.
Line 208 sets error, but success path never resets it. Because Line 239 checks error || !earnings, a previous failure can keep the page in error state even after later successful responses.
🐛 Proposed fix
const fetchEarnings = async () => {
+ setError(null);
try {
const response = await getUserEarnings();
setEarnings(response.data);
} catch {
setError('Failed to load earnings data.');
} finally {
setLoading(false);
}
};Also applies to: 239-249
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/me/earnings/page.tsx` around lines 203 - 211, The fetchEarnings success
path doesn't clear a prior error, so a past failure can keep the page in error
state; inside fetchEarnings after a successful getUserEarnings() and
setEarnings(response.data) call, reset the error state (e.g., call
setError(null) or setError('')) so that error no longer blocks the render logic
that checks error || !earnings; ensure setLoading(false) remains in finally.
| if (!isLoading && !authUser) { | ||
| setLoading(false); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Reset earnings state when user becomes unauthenticated.
The unauthenticated branch returns early without clearing earnings. If auth state flips from signed-in to signed-out, stale private earnings data may remain visible.
🔒 Proposed fix
if (!isLoading && !authUser) {
+ setEarnings(null);
setLoading(false);
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!isLoading && !authUser) { | |
| setLoading(false); | |
| return; | |
| } | |
| if (!isLoading && !authUser) { | |
| setEarnings(null); | |
| setLoading(false); | |
| return; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/me/earnings/page.tsx` around lines 215 - 218, When the component detects
that !isLoading && !authUser and returns early it must also clear any private
earnings state to avoid showing stale data; update the unauthenticated branch
(the conditional using isLoading and authUser) to call the earnings state reset
(e.g., setEarnings([]) or setEarnings(null)) before setLoading(false) and
return, and also clear any related derived state (e.g., totalEarnings or
pagination state) if present so all private data is wiped when authUser becomes
null.
| export const getUserEarnings = async (): Promise<GetUserEarningsResponse> => { | ||
| const res = await api.get('/user/earnings'); | ||
| return res.data; | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== lib/api/api.ts return signatures =="
rg -nP --type=ts 'export const api|get:\s*<T|post:\s*<T|clientApi\.(get|post)' lib/api/api.ts
echo
echo "== user API modules returning res vs res.data =="
rg -nP --type=ts -C2 'const res = await api\.(get|post)\(|return\s+res(\.data)?;' lib/api/userRepository: boundlessfi/boundless
Length of output: 985
🏁 Script executed:
#!/bin/bash
# Get full content of api.ts to understand the structure
echo "== Content of lib/api/api.ts =="
head -100 lib/api/api.ts
echo
echo "== Type definitions for GetUserEarningsResponse and ClaimEarningResponse =="
rg -nP --type=ts 'GetUserEarningsResponse|ClaimEarningResponse' lib/api/user/earnings.ts -A3 -B1Repository: boundlessfi/boundless
Length of output: 3929
🏁 Script executed:
#!/bin/bash
# Check if these response types are ApiResponse wrappers
echo "== Search for type definitions =="
rg -nP --type=ts 'type.*GetUserEarningsResponse|type.*ClaimEarningResponse|interface.*GetUserEarningsResponse|interface.*ClaimEarningResponse' lib/api/
echo
echo "== Check ApiResponse type definition =="
rg -nP --type=ts 'type ApiResponse|interface ApiResponse' lib/Repository: boundlessfi/boundless
Length of output: 581
Fix response-shape mismatch: functions declare envelope types but return unwrapped data.
getUserEarnings and claimEarning are declared to return GetUserEarningsResponse and ClaimEarningResponse (which extend ApiResponse<T> with success and message fields), but both return res.data instead. Since api.get() and api.post() return Promise<ApiResponse<T>>, the code unwraps one level and returns only the inner data, creating a type error. Either return the full res object or change return types to the data types (EarningsData, ClaimEarningData).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/api/user/earnings.ts` around lines 24 - 27, The functions getUserEarnings
and claimEarning currently return res.data (the inner payload) but their
declared return types GetUserEarningsResponse and ClaimEarningResponse are the
envelope ApiResponse<T>; fix by returning the full response object (res) instead
of res.data so the returned value matches the envelope types, or alternatively
change the function return types to the raw data types (e.g., EarningsData /
ClaimEarningData) if you intentionally want to unwrap; update the return
statement in getUserEarnings and claimEarning accordingly and ensure
imports/types reflect the chosen shape.
Benjtalkshow
left a comment
There was a problem hiding this comment.
Thank you for your contribution. I appreciate the effort.
However, I advise that you wait until the issue is unblocked. Did you see my comment here?
#390 (comment)
Please wait until the user earnings data and claiming functionality are returned through a new endpoint. That is why the issue is currently blocked.
Right now, your implementation is based on assumptions because the response format has not been defined yet.
Thanks.
|
Hello @Benjtalkshow thank you for the information, that was my bad |
Implement Private Earnings Dashboard (/me/earnings)
Description:
Implements the
/me/earningspage as specified.What was done
lib/api/types.ts— AddedEarningSource,EarningStatus,EarningActivity,EarningSourceBreakdown, andEarningsDatainterfaces. Zeroanytypes.lib/api/user/earnings.ts— New API module following thelib/api/hackathons/rewards.tspattern. ExportsgetUserEarnings()(GET /user/earnings) andclaimEarning(id)(POST /user/earnings/:id/claim).app/me/earnings/page.tsx— Client component with:Skeletoncomponent patternsClaimbutton that calls the withdrawal endpoint and refreshes on successcomponents/app-sidebar.tsx— AddedIconCurrencyDollarand an Earnings link after Analytics in the main nav section.npm run buildpasses with no type errors./me/earningsis confirmed in the route output.Why no screenshot
The
GET /user/earningsendpoint does not yet exist on the backend (staging or otherwise). The page renders its error state correctly when the API returns a non-success response — this is expected and intentional behavior. A screenshot/recording can be provided as soon as the backend implements the endpoint and returns data matching theEarningsDatashape defined inlib/api/types.ts.Closes #390
Summary by CodeRabbit