Unified Dashboard
Overview
The Unified Earnings Dashboard combines all three earning systems (Referrals, Rewards, and Points) into a single, cohesive interface. It provides users with a complete view of their earnings, progress, and engagement across the Med.Fun platform.
Purpose
Instead of navigating to separate dashboards for each system, users can:
View total earnings from all sources in one place
Switch between detailed views using tabs
Track combined pending and claimed amounts
Refresh all data simultaneously
Access all earning systems from a single modal
Architecture
Component Structure
UnifiedEarningsDashboard
├── useEarnings (combined data hook)
│ ├── useReferrals
│ ├── useRewards
│ └── usePoints
├── Tabs (navigation)
│ ├── Overview (aggregated summary)
│ ├── Rewards (trading cashback)
│ ├── Referrals (commission earnings)
│ └── Points (gamification)
└── Mobile/Desktop Rendering
├── Sheet (mobile)
└── Dialog (desktop)Data Flow
User Wallet → useEarnings Hook
↓
┌───────────┴───────────┐
↓ ↓ ↓
useReferrals useRewards usePoints
↓ ↓ ↓
Referral Data Reward Data Points Data
↓ ↓ ↓
└───────────┬───────────┘
↓
Aggregated Earnings
↓
UnifiedEarningsDashboard
↓
User InterfaceuseEarnings Hook
useEarnings HookLocation: /src/hooks/useEarnings.tsx
Purpose
Combines data from all three earning systems into a single, easy-to-use hook.
Implementation
import { useReferrals } from './useReferrals';
import { useRewards } from './useRewards';
import { usePoints } from './usePoints';
export const useEarnings = (userWallet: string | null | undefined) => {
// Fetch data from all three systems
const referrals = useReferrals(userWallet);
const rewards = useRewards(userWallet);
const points = usePoints(userWallet);
// Calculate aggregated earnings
const totalEarnings =
(referrals.totalEarnings || 0) +
(rewards.totalCashback || 0);
const totalPending =
(referrals.pendingEarnings || 0) +
(rewards.pendingCashback || 0);
const totalClaimed =
(referrals.claimedEarnings || 0) +
(rewards.claimedCashback || 0);
// Combined loading state
const loading =
referrals.loading ||
rewards.loading ||
points.loading;
// Refresh all data
const refreshAll = async () => {
await Promise.all([
rewards.refreshData(),
points.refreshData()
]);
};
return {
// Referral data
referralEarnings: {
total: referrals.totalEarnings,
pending: referrals.pendingEarnings,
claimed: referrals.claimedEarnings,
referralCode: referrals.referralCode,
totalReferrals: referrals.totalReferrals,
currentTier: referrals.currentTier,
},
// Reward data
rewardEarnings: {
total: rewards.totalCashback,
pending: rewards.pendingCashback,
claimed: rewards.claimedCashback,
currentTier: rewards.currentTier,
totalVolume: rewards.userStats?.total_volume,
totalTrades: rewards.userStats?.total_trades,
},
// Points data
pointsData: {
totalPoints: points.totalPoints,
level: points.level,
badges: points.badges,
pointsToNextLevel: points.pointsToNextLevel,
levelProgress: points.levelProgress,
},
// Aggregated totals
totalEarnings,
totalPending,
totalClaimed,
// States and actions
loading,
refreshAll
};
};Usage
const {
referralEarnings,
rewardEarnings,
pointsData,
totalEarnings,
totalPending,
totalClaimed,
loading,
refreshAll
} = useEarnings(userWallet);Benefits
Single Import: Only need to import one hook
Aggregated Data: Automatic calculation of combined totals
Unified Loading: Single loading state for all systems
Batch Refresh: Refresh all data with one function call
Type Safety: Full TypeScript support
Performance: Hooks run in parallel, not sequentially
Component: UnifiedEarningsDashboard
Location: /src/components/UnifiedEarningsDashboard.tsx
Props
interface UnifiedEarningsDashboardProps {
open: boolean; // Modal visibility
onOpenChange: (open: boolean) => void; // Callback for visibility changes
userWallet: string | null; // User's wallet address
}Features
Responsive Design: Adapts to mobile and desktop
Tabbed Interface: Easy navigation between systems
Real-time Updates: Live data via Supabase subscriptions
Summary Cards: Quick overview of key metrics
Detailed Views: Deep dive into each system
Claim Actions: Direct access to claim buttons
Progress Tracking: Visual progress bars for tiers and levels
Tab Structure
Tab 1: Overview (Default)
Purpose: High-level summary of all earning systems
Content:
Total Earnings card (referral + rewards)
Pending Earnings card (claimable amount)
Claimed Earnings card (historical)
Trading Rewards summary (tier, cashback)
Referral Program summary (code, tier, earnings)
Points & Level summary (level, progress, badges)
UI Layout:
<div className="grid gap-4">
{/* Row 1: Aggregated Earnings */}
<div className="grid grid-cols-3 gap-4">
<Card>Total Earnings: ${totalEarnings}</Card>
<Card>Pending: ${totalPending}</Card>
<Card>Claimed: ${totalClaimed}</Card>
</div>
{/* Row 2: System Summaries */}
<Card>Trading Rewards Summary</Card>
<Card>Referral Program Summary</Card>
<Card>Points & Level Summary</Card>
</div>Tab 2: Rewards
Purpose: Detailed trading rewards and cashback
Content:
Current tier badge
Pending cashback with claim button
Claimed cashback total
Trading stats (volume, trade count)
Progress to next tier
Cashback earnings history
Features:
Interactive claim button (min $1.00)
Color-coded tier display
Progress bar with percentage
Scrollable earnings list
Real-time balance updates
Tab 3: Referrals
Purpose: Referral program details
Content:
Referral code display
Share link functionality
Total earnings (pending + claimed)
Referral stats (active referrals, volume)
Current tier and benefits
Referral list
Features:
Copy referral link button
Share via native share API
Generate custom code option
Claim earnings button
Tier progress visualization
Tab 4: Points
Purpose: Gamification and level progression
Content:
Current level display
Total points earned
Progress to next level
Earned badges showcase
Recent point transactions
Action history
Features:
Animated progress bar
Badge gallery
Transaction timeline
Point breakdown by action
Level-up celebrations
Responsive Behavior
Desktop (≥768px)
Renders as
DialogcomponentModal overlay with backdrop blur
Fixed width (600px max-width)
Centered on screen
Smooth fade-in animation
Mobile (<768px)
Renders as
Sheetcomponent (bottom sheet)Slides up from bottom
Full-width layout
Swipe-to-dismiss gesture
Native-feeling interactions
Implementation
export function UnifiedEarningsDashboard({
open,
onOpenChange,
userWallet
}: UnifiedEarningsDashboardProps) {
const isMobile = useIsMobile();
const {
referralEarnings,
rewardEarnings,
pointsData,
totalEarnings,
totalPending,
totalClaimed,
loading,
refreshAll
} = useEarnings(userWallet);
const { claimCashback, claiming: claimingRewards } = useRewards(userWallet);
const { addPoints } = usePoints(userWallet);
const content = (
<Tabs defaultValue="overview">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="rewards">Rewards</TabsTrigger>
<TabsTrigger value="referrals">Referrals</TabsTrigger>
<TabsTrigger value="points">Points</TabsTrigger>
</TabsList>
<TabsContent value="overview">
{/* Overview content */}
</TabsContent>
<TabsContent value="rewards">
{/* Rewards content */}
</TabsContent>
<TabsContent value="referrals">
{/* Referrals content */}
</TabsContent>
<TabsContent value="points">
{/* Points content */}
</TabsContent>
</Tabs>
);
// Mobile: Sheet
if (isMobile) {
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="bottom">
<SheetHeader>
<SheetTitle>Your Earnings</SheetTitle>
</SheetHeader>
{content}
</SheetContent>
</Sheet>
);
}
// Desktop: Dialog
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Your Earnings</DialogTitle>
</DialogHeader>
{content}
</DialogContent>
</Dialog>
);
}Integration Examples
Open Dashboard from Button
import { UnifiedEarningsDashboard } from '@/components/UnifiedEarningsDashboard';
function MyComponent() {
const [dashboardOpen, setDashboardOpen] = useState(false);
const { user } = usePrivy();
return (
<>
<Button onClick={() => setDashboardOpen(true)}>
View Earnings
</Button>
<UnifiedEarningsDashboard
open={dashboardOpen}
onOpenChange={setDashboardOpen}
userWallet={user?.wallet?.address}
/>
</>
);
}Display Earnings Summary
function EarningsSummaryCard() {
const { user } = usePrivy();
const { totalEarnings, totalPending } = useEarnings(user?.wallet?.address);
return (
<Card>
<CardHeader>
<CardTitle>Your Earnings</CardTitle>
</CardHeader>
<CardContent>
<div>
<p className="text-2xl font-bold">${totalEarnings.toFixed(2)}</p>
<p className="text-sm text-muted-foreground">
${totalPending.toFixed(2)} pending
</p>
</div>
</CardContent>
</Card>
);
}Quick Claim Button
function QuickClaimButton() {
const { user } = usePrivy();
const { totalPending } = useEarnings(user?.wallet?.address);
const { claimCashback, claiming } = useRewards(user?.wallet?.address);
const [dashboardOpen, setDashboardOpen] = useState(false);
if (totalPending < 1.00) return null;
return (
<>
<Button onClick={() => setDashboardOpen(true)}>
Claim ${totalPending.toFixed(2)}
</Button>
<UnifiedEarningsDashboard
open={dashboardOpen}
onOpenChange={setDashboardOpen}
userWallet={user?.wallet?.address}
/>
</>
);
}User Flows
Flow 1: Check Total Earnings
1. User clicks "View Earnings" button
2. Dashboard opens (Sheet on mobile, Dialog on desktop)
3. Overview tab shows by default
4. User sees aggregated totals:
- Total: $125.50 (referral + rewards)
- Pending: $15.75
- Claimed: $109.75
5. Quick summaries for each system visibleFlow 2: Claim Pending Earnings
1. User opens dashboard
2. Sees "$15.75 Pending" in overview
3. Clicks "Rewards" tab
4. Pending cashback displayed: $8.25
5. Clicks "Claim Cashback" button
6. Success toast appears
7. Balance updates in real-time
8. Switches to "Referrals" tab
9. Pending referral commission: $7.50
10. Clicks "Claim Earnings"
11. Success toast appears
12. Overview tab updates to show new totalsFlow 3: Track Progress
1. User opens dashboard
2. Clicks "Overview" tab
3. Sees Trading Rewards summary:
- Current tier: Chief Surgeon (2.0x)
- Progress: 65% to next tier
4. Sees Referral Program summary:
- Current tier: Silver (12%)
- Active referrals: 8
5. Sees Points & Level:
- Level 12
- Progress: 45% to Level 13
6. User understands current standing in all systemsFlow 4: Deep Dive into System
1. User opens dashboard
2. Overview shows: "Level 12"
3. User wants more details
4. Clicks "Points" tab
5. Sees full level progression
6. Views all earned badges
7. Scrolls through point transaction history
8. Understands point-earning activitiesBest Practices
For Developers
1. Always pass userWallet prop:
<UnifiedEarningsDashboard
open={open}
onOpenChange={setOpen}
userWallet={user?.wallet?.address} // Essential
/>2. Handle loading states:
const { loading, totalEarnings } = useEarnings(userWallet);
if (loading) {
return <Skeleton />;
}3. Use aggregated totals:
// Good - use unified hook
const { totalEarnings } = useEarnings(wallet);
// Avoid - don't manually aggregate
const referrals = useReferrals(wallet);
const rewards = useRewards(wallet);
const total = referrals.totalEarnings + rewards.totalCashback;4. Refresh all data together:
const { refreshAll } = useEarnings(wallet);
// After claim action
await claimCashback();
await refreshAll(); // Refresh everythingFor Product
1. Overview tab as default
Most users want quick summary first
Deep dives available via tabs
Progressive disclosure of information
2. Clear visual hierarchy
Largest numbers (totals) most prominent
System summaries secondary
Details accessible via tabs
3. Action buttons prominent
Claim buttons easily accessible
Clear minimum thresholds
Disabled state when below minimum
4. Mobile-first design
Bottom sheet on mobile feels native
Swipe gestures intuitive
Content scrolls within sheet
Performance Optimization
Data Fetching
// All hooks run in parallel
const referrals = useReferrals(wallet); // Parallel
const rewards = useRewards(wallet); // Parallel
const points = usePoints(wallet); // Parallel
// NOT sequential
// ✗ await useReferrals(wallet);
// ✗ await useRewards(wallet);
// ✗ await usePoints(wallet);Memoization
const aggregatedData = useMemo(() => ({
totalEarnings: (referrals.total || 0) + (rewards.total || 0),
totalPending: (referrals.pending || 0) + (rewards.pending || 0),
totalClaimed: (referrals.claimed || 0) + (rewards.claimed || 0)
}), [referrals, rewards]);Real-time Updates
Each system manages its own subscriptions
Updates propagate through hook return values
No additional subscriptions needed in dashboard
Automatic UI refresh on data changes
Troubleshooting
Data Not Loading
Check:
Is userWallet prop provided?
Check browser console for errors
Verify all three hooks working individually
Check network tab for failed requests
Totals Incorrect
Check:
Verify useEarnings aggregation logic
Check individual hook return values
Ensure all values are numbers (not strings)
Look for null/undefined values
Modal Not Opening
Check:
Is
openprop being controlled?Verify
onOpenChangecallbackCheck for z-index conflicts
Ensure Dialog/Sheet components imported
Tabs Not Switching
Check:
Verify Tabs defaultValue matches TabsTrigger values
Check for JavaScript errors
Ensure TabsContent value matches triggers
Test individual tab components
Future Enhancements
Potential Features
Export Data: Download earnings report as CSV/PDF
Analytics: Charts showing earnings over time
Notifications: Alert when claim threshold reached
Filters: Filter earnings by date range
Search: Search transaction history
Breakdown: Pie chart of earnings by source
Goals: Set and track earning goals
Achievements: Special badges for milestones
Technical Improvements
Caching: Cache aggregated data for faster loads
Pagination: Paginate transaction histories
Virtual Scrolling: For long transaction lists
Optimistic Updates: Instant UI feedback
Offline Support: View data while offline
Progressive Loading: Load overview first, details later
Last updated
Was this helpful?