feat(refer) : Implement referral system with UI components and API integration#430
Conversation
- Added to document the referral system, including reward structure, rules, data model, migration, core services, and API endpoints. - Created component to display referral stats and allow users to apply referral codes. - Integrated referral functionality into the onboarding process with for applying referral codes. - Updated page to include the new . - Implemented API calls in for fetching referral stats and applying referral codes. - Added tests for the referral API to ensure proper functionality and data normalization. - Introduced device fingerprinting for referral code application to prevent abuse. This commit establishes a comprehensive referral system, enhancing user engagement and incentivizing referrals.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds a referral rewards system: documentation, frontend UI and onboarding steps for applying codes, frontend API/types/tests, device-fingerprint utility, mock endpoints, backend RPC ops/schemas/registry, and a migration to backfill legacy referral data. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant FE as Frontend (Rewards UI)
participant API as referralApi
participant Core as Core RPC Adapter
participant Backend as Hosted Backend
User->>FE: Open Rewards page
FE->>API: getStats()
API->>Core: openhuman.referral_get_stats()
Core->>Backend: GET /referral/stats
Backend-->>Core: JSON stats payload
Core-->>API: RpcOutcome(JSON)
API->>API: normalizeReferralStats(raw)
API-->>FE: ReferralStats
FE-->>User: Render referral UI & activity
sequenceDiagram
actor User
participant Step as ReferralApplyStep
participant Utils as deviceFingerprint
participant API as referralApi
participant Core as Core RPC Adapter
participant Backend as Hosted Backend
User->>Step: Enter code & click Apply
Step->>Utils: getOrCreateDeviceFingerprint()
Utils-->>Step: deviceFingerprint
Step->>API: applyCode(code)
API->>Core: openhuman.referral_apply(code, deviceFingerprint)
Core->>Backend: POST /referral/apply { code, deviceFingerprint }
Backend-->>Core: acknowledgment JSON
Core-->>API: RpcOutcome(JSON)
API-->>Step: resolved
Step->>Step: refresh() user state, call onApplied, auto-advance
Step-->>User: Success UI
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
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)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
app/src/pages/onboarding/Onboarding.tsx (1)
23-30: Use aconstarrow helper forhasReferralFromProfile.This small local helper is the only remaining function declaration in the file; converting it keeps the onboarding code aligned with the repo’s TS style.
As per coding guidelines,
**/*.{js,jsx,ts,tsx}: Prefer arrow functions over function declarations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/pages/onboarding/Onboarding.tsx` around lines 23 - 30, Replace the function declaration hasReferralFromProfile with a const arrow helper to match the project's TS style: declare a const named hasReferralFromProfile assigned to an arrow function with the same parameter type union and body (returning !!(user?.referral?.invitedBy || user?.referral?.invitedByCode)); keep the exact logic and exported/used references unchanged so all callers continue to work.app/src/components/referral/ReferralRewardsSection.tsx (1)
8-32: Prefer arrow helpers for these local utilities.These new local helpers are function declarations, while the app TS style in this repo prefers
constarrow helpers for new code.As per coding guidelines,
**/*.{js,jsx,ts,tsx}: Prefer arrow functions over function declarations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/referral/ReferralRewardsSection.tsx` around lines 8 - 32, Replace the three local function declarations with const arrow helpers: convert function formatUsd, statusBadgeClass, and statusLabel into const formatUsd = (n: number) => ..., const statusBadgeClass = (status: ReferralRelationshipStatus) => ..., and const statusLabel = (status: ReferralRelationshipStatus) => ...; keep the same parameter types, return types, switch logic, and exported semantics (if any) so behavior is unchanged but style matches repo preference for arrow helpers.app/src/services/api/referralApi.ts (1)
10-260: Useconstarrow helpers throughout this new TS module.The new normalizer stack is built from function declarations, which drifts from the repo’s TS style and makes this module inconsistent with the rest of the app code.
As per coding guidelines,
**/*.{js,jsx,ts,tsx}: Prefer arrow functions over function declarations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/services/api/referralApi.ts` around lines 10 - 260, The module uses function declarations rather than the project's preferred const arrow helpers; convert top-level functions (e.g., num, coerceMoney, coerceId, asRecord, normalizeStatus, rowRewardUsd, normalizeRow, deriveTotalsFromReferrals, normalizeReferralStats) into const arrow functions preserving their behavior and exported signature (keep normalizeReferralStats exported), ensure types and returns remain identical, and run/type-check to confirm no behavioral changes.
🤖 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/src/components/referral/ReferralRewardsSection.tsx`:
- Around line 162-166: The display currently rounds basis points to whole
percent in ReferralRewardsSection by using (stats.rewardRateBps /
100).toFixed(0); change the formatting so the percent shows up to two decimal
places (e.g., use toFixed(2) or an Intl.NumberFormat with
maximumFractionDigits:2) when rendering stats.rewardRateBps to preserve values
like 12.5%; update the text rendering that references stats.rewardRateBps
accordingly so the percent and the "(basis points ...)" part remain correct.
- Around line 49-74: loadStats can write stale results after a logout/account
switch; add a request identity guard so only the most recent call updates state.
Introduce a ref like latestRequestIdRef incremented inside loadStats before
calling referralApi.getStats(), capture that id in the async call, and before
calling setStats/setLoadError/setLoading check that the captured id equals
latestRequestIdRef.current (or use an AbortController and pass signal to
referralApi.getStats if it supports aborting) so stale responses are ignored;
update references to loadStats, referralApi.getStats, and the useEffect that
invokes loadStats accordingly.
- Around line 267-271: In ReferralRewardsSection, avoid rendering raw internal
IDs by removing the fallback to row.referredUserId in the table cell rendering
(currently falling back after row.referredUserMasked and
row.referredDisplayName); instead, only render row.referredUserMasked or
row.referredDisplayName and then the default '—'. Update the JSX that constructs
the cell (the td where row.referredUserMasked || row.referredDisplayName ||
row.referredUserId || '—' is used) to drop row.referredUserId so it becomes
row.referredUserMasked || row.referredDisplayName || '—'.
In `@app/src/pages/onboarding/Onboarding.tsx`:
- Around line 48-90: The effect that fetches referral stats is currently
performing synchronous state updates (calls to setSkipReferralStep,
setReferralGateReady, setCurrentStep) inside useEffect which trips the
react-hooks/set-state-in-effect rule; refactor by moving the synchronous
branches into derived render logic or initial state/handlers and keep the effect
only for the async referralApi.getStats call and its results: i.e., compute and
render the referral gate readiness and skipReferralStep when token or
profileAlreadyReferred change (use derived booleans or initialize useState
accordingly) instead of calling setSkipReferralStep/setReferralGateReady
directly in those non-async branches inside the first useEffect, and change the
second useEffect (which advances currentStep when skipReferralStep &&
currentStep === 1) to run only when skipReferralStep changes or perform the step
advance in the render flow/handlers rather than synchronously in effect; retain
referralApi.getStats and its setSkipReferralStep/setReferralGateReady updates
only after the async response.
In `@app/src/pages/onboarding/steps/WelcomeStep.tsx`:
- Line 95: In the WelcomeStep component update the button label text from the
incorrect "Let Start" to the correct "Let's Start" by changing the label prop
value on the relevant element (label="Let Start") to label="Let's Start" inside
the WelcomeStep JSX so the displayed button reads correctly; ensure proper
escaping/quoting for the apostrophe in the string literal used in the component.
In `@app/src/services/api/__tests__/referralApi.test.ts`:
- Around line 110-117: Add a regression test that verifies
normalizeReferralStats prefers backend-provided totals even when they are zero
by creating a case where totals: { totalRewardUsd: 0, pendingCount: 0,
convertedCount: 0 } is returned alongside non-zero referral rows (e.g.,
referrals with rewardUsd > 0 and status 'converted'), call
normalizeReferralStats and assert stats.totals.totalRewardUsd === 0 and
stats.totals.convertedCount === 0 to ensure the explicit zero aggregates win
over any truthiness-based fallback; place the new it(...) beside the existing
test that references normalizeReferralStats and stats.totals to cover the
regression.
In `@app/src/services/api/referralApi.ts`:
- Around line 267-289: Wrap RPC calls in getStats and applyCode with try/catch
and normalize thrown errors into the expected contract before rethrowing: catch
the error from callCoreCommand in getStats and in applyCode, extract a useful
message from err.error or err.message (falling back to String(err)), and throw
an object like { success: false, error: <message> } so callers (e.g.,
ReferralApplyStep.tsx and ReferralRewardsSection.tsx) can read err.error
reliably; locate the callCoreCommand usages inside getStats and applyCode to
implement this change.
---
Nitpick comments:
In `@app/src/components/referral/ReferralRewardsSection.tsx`:
- Around line 8-32: Replace the three local function declarations with const
arrow helpers: convert function formatUsd, statusBadgeClass, and statusLabel
into const formatUsd = (n: number) => ..., const statusBadgeClass = (status:
ReferralRelationshipStatus) => ..., and const statusLabel = (status:
ReferralRelationshipStatus) => ...; keep the same parameter types, return types,
switch logic, and exported semantics (if any) so behavior is unchanged but style
matches repo preference for arrow helpers.
In `@app/src/pages/onboarding/Onboarding.tsx`:
- Around line 23-30: Replace the function declaration hasReferralFromProfile
with a const arrow helper to match the project's TS style: declare a const named
hasReferralFromProfile assigned to an arrow function with the same parameter
type union and body (returning !!(user?.referral?.invitedBy ||
user?.referral?.invitedByCode)); keep the exact logic and exported/used
references unchanged so all callers continue to work.
In `@app/src/services/api/referralApi.ts`:
- Around line 10-260: The module uses function declarations rather than the
project's preferred const arrow helpers; convert top-level functions (e.g., num,
coerceMoney, coerceId, asRecord, normalizeStatus, rowRewardUsd, normalizeRow,
deriveTotalsFromReferrals, normalizeReferralStats) into const arrow functions
preserving their behavior and exported signature (keep normalizeReferralStats
exported), ensure types and returns remain identical, and run/type-check to
confirm no behavioral changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2c7b4e5a-113d-4574-a14b-57a3e4e034a1
📒 Files selected for processing (16)
Referral-doc.mdapp/src/components/referral/ReferralRewardsSection.tsxapp/src/pages/Rewards.tsxapp/src/pages/onboarding/Onboarding.tsxapp/src/pages/onboarding/steps/ReferralApplyStep.tsxapp/src/pages/onboarding/steps/WelcomeStep.tsxapp/src/services/api/__tests__/referralApi.test.tsapp/src/services/api/referralApi.tsapp/src/types/referral.tsapp/src/utils/deviceFingerprint.tsscripts/mock-api-core.mjssrc/core/all.rssrc/openhuman/mod.rssrc/openhuman/referral/mod.rssrc/openhuman/referral/ops.rssrc/openhuman/referral/schemas.rs
- Added a new function to format reward rates from basis points to percentage for better display in the ReferralRewardsSection. - Improved loading state management in the referral stats loading process to prevent race conditions. - Updated the Onboarding component to handle referral step skipping more effectively, ensuring a smoother user experience. - Fixed a typo in the WelcomeStep component's button label for clarity. - Enhanced error handling in the referral API to provide clearer feedback on failures. These changes improve the usability and reliability of the referral system and onboarding experience.
Summary
Rewards page
Onboarding
/referral/stats)Core (Rust)
openhuman.referral_get_statsopenhuman.referral_applyBackendOAuthClientwith session JWT (same pattern as billing)App client
referralApiusescallCoreCommandreferredUserMaskedtotalRewardsEarnedUsdDecimal128rewardAmountUsdtransactionsJoined/CompletedreferralId/joinedAtMock API
GET /referral/statsPOST /referral/applyTests
normalizeReferralStatsreferralApiwiringProblem
Referral functionality existed on backend but not exposed in desktop app
Users couldn’t:
Direct WebView
fetchcaused failures:Backend response inconsistencies:
0or—despite valid dataSolution
UI + Onboarding
GET /referral/statsPOST /referral/applyRust referral module
reqwestadaptersTypeScript normalization layer
Tradeoff
callCoreCommandflows)Submission Checklist
Unit tests
app/src/services/api/__tests__/referralApi.test.tsE2E / Integration
tests/json_rpc_e2e.rs(mock backend)Doc comments
//!module-level docs///for stricter Rust API docsInline comments
Impact
Desktop (Tauri)
Web / CLI
api_urlSecurity
Compatibility
openhumanbinaryPerformance
Related
Summary by CodeRabbit
New Features
Documentation
Tests