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 Interface

useEarnings Hook

Location: /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

  1. Single Import: Only need to import one hook

  2. Aggregated Data: Automatic calculation of combined totals

  3. Unified Loading: Single loading state for all systems

  4. Batch Refresh: Refresh all data with one function call

  5. Type Safety: Full TypeScript support

  6. 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 Dialog component

  • Modal overlay with backdrop blur

  • Fixed width (600px max-width)

  • Centered on screen

  • Smooth fade-in animation

Mobile (<768px)

  • Renders as Sheet component (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 visible

Flow 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 totals

Flow 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 systems

Flow 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 activities

Best 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 everything

For 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:

  1. Is userWallet prop provided?

  2. Check browser console for errors

  3. Verify all three hooks working individually

  4. Check network tab for failed requests

Totals Incorrect

Check:

  1. Verify useEarnings aggregation logic

  2. Check individual hook return values

  3. Ensure all values are numbers (not strings)

  4. Look for null/undefined values

Check:

  1. Is open prop being controlled?

  2. Verify onOpenChange callback

  3. Check for z-index conflicts

  4. Ensure Dialog/Sheet components imported

Tabs Not Switching

Check:

  1. Verify Tabs defaultValue matches TabsTrigger values

  2. Check for JavaScript errors

  3. Ensure TabsContent value matches triggers

  4. 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?