Fixed the hardcoded dashboard stats issue by connecting to real data sources:
- GET /api/me for learner profile via new
useLearnerProfilehook - Learn Token contract for real LRN balance via existing
useLearnTokenhook - Course Milestone contract for enrolled courses & milestone progress via
existing
useCoursehook - Added skeleton loaders during data fetching
- Graceful handling of unauthenticated state (wallet not connected)
File: src/hooks/useLearnerProfile.ts
- Queries GET
/api/meendpoint to fetch authenticated learner profile - Returns
{ profile, isLoading, error, address } - Automatically disabled when no wallet is connected
- Uses React Query for caching with 5-minute stale time
- Extensible interface for future profile fields (bio, avatar, etc.)
Key features:
export interface LearnerProfile {
address: string
}
export function useLearnerProfile() {
// Fetches from GET /api/me with Bearer token auth
// Caches for 5 minutes
// Auto-disabled when address is undefined
}File: src/pages/Dashboard.tsx
- Removed static
statsarray with hardcoded LRN balance (142), courses (2), milestones (14) - Removed static
enrolledCoursesarray with fake course data
// Fetch learner profile from backend
const { profile, isLoading: isLoadingProfile } = useLearnerProfile()
// Fetch LRN balance from contract (converts stroops → LRN)
const { balance: lrnBalance, isLoading: isLoadingBalance } =
useLearnToken(address)
// Fetch enrolled courses and milestone progress from contract
const { enrolledCourses, progressMap, isCompletingMilestone } = useCourse()- LRN Balance: From contract; converts stroops to human-readable format with locale formatting
- Courses Enrolled: From
enrolledCourses.length - Milestones: Calculated from
progressMapby summing completed milestone IDs - Gov Tokens: Placeholder (remains 0)
- 4 placeholder cards during loading state
- 2 placeholder course cards during loading state
- Using CSS
animate-pulsewith.glass-cardstyling for consistency
if (!address) {
return (
<div className="min-h-screen flex items-center justify-center px-4">
<div className="max-w-md text-center">
<h1>Connect Your Wallet</h1>
<p>To view your learning dashboard...</p>
<Link to="/">Connect Wallet →</Link>
</div>
</div>
)
}| Criteria | Status | Implementation |
|---|---|---|
| LRN balance from contract, not hardcoded | ✅ | useLearnToken(address) with stroops-to-LRN conversion |
| Enrolled courses from backend/contract, not static array | ✅ | useCourse().enrolledCourses |
| Skeleton loaders shown while fetching | ✅ | Conditional rendering: isLoading ? <Skeleton /> : <Data /> |
| Unauthenticated users see connect-wallet prompt | ✅ | Renders connect wallet CTA instead of returning null |
| All hardcoded stat values removed | ✅ | All stats now calculated from real data sources |
Dashboard Component
├─ useLearnerProfile()
│ └─ GET /api/me → { address: string }
│
├─ useLearnToken(address)
│ └─ learn_token contract → balance: bigint (stroops)
│
└─ useCourse()
├─ course_milestone contract → enrolledCourses: Course[]
└─ Returns progressMap with completed milestone counts
Stats Calculation:
- LRN Balance: convertStroopsToLRN(balance)
- Courses: enrolledCourses.length
- Milestones: sum(progressMap[courseId].completedMilestoneIds)
-
Wallet Connected Scenario:
- Verify stats Display shows loading state
- Verify real LRN balance appears (from contract)
- Verify enrolled courses display (from useCourse)
- Verify milestone count calculated correctly
-
Wallet Not Connected Scenario:
- Verify "Connect Your Wallet" prompt displays
- Verify link to "/" works
-
Loading States:
- Verify skeleton loaders show while useLearnToken/useCourse hooks are loading
- Verify proper transition from skeleton to real data
-
Data Updates:
- Verify stats update when new milestones are completed
- Verify new course enrollments appear in the list