-
Notifications
You must be signed in to change notification settings - Fork 55
feat: Frontend-API Integration — Replace All Mock Data #317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| 'use client'; | ||
|
|
||
| import React, { useState, useCallback, useEffect } from 'react'; | ||
| import { apiClient } from '../services/apiClient'; | ||
|
|
||
| // ============================================================================ | ||
| // Types | ||
|
|
@@ -57,83 +58,20 @@ interface ContributorDashboardProps { | |
| } | ||
|
|
||
| // ============================================================================ | ||
| // Mock Data | ||
| // Data Fetcher — Real API with empty-state fallback | ||
| // ============================================================================ | ||
|
|
||
| const MOCK_STATS: DashboardStats = { | ||
| totalEarned: 2450000, | ||
| activeBounties: 3, | ||
| pendingPayouts: 500000, | ||
| reputationRank: 42, | ||
| totalContributors: 256, | ||
| }; | ||
|
|
||
| const MOCK_BOUNTIES: Bounty[] = [ | ||
| { id: '1', title: 'GitHub <-> Platform Bi-directional Sync', reward: 450000, deadline: '2026-03-27', status: 'in_progress', progress: 60 }, | ||
| { id: '2', title: 'Real-time WebSocket Server', reward: 400000, deadline: '2026-03-26', status: 'submitted', progress: 100 }, | ||
| { id: '3', title: 'Bounty Claiming System', reward: 500000, deadline: '2026-03-28', status: 'claimed', progress: 20 }, | ||
| ]; | ||
|
|
||
| const MOCK_ACTIVITIES: Activity[] = [ | ||
| { id: '1', type: 'payout', title: 'Payout Received', description: 'Received 500,000 $FNDRY for CI/CD Pipeline', timestamp: '2026-03-20T10:00:00Z', amount: 500000 }, | ||
| { id: '2', type: 'review_received', title: 'Review Completed', description: 'Your PR for Auth System received score 8/10', timestamp: '2026-03-20T08:30:00Z' }, | ||
| { id: '3', type: 'pr_submitted', title: 'PR Submitted', description: 'Submitted PR for WebSocket Server', timestamp: '2026-03-19T15:00:00Z' }, | ||
| { id: '4', type: 'bounty_claimed', title: 'Bounty Claimed', description: 'Claimed "GitHub <-> Platform Sync"', timestamp: '2026-03-19T12:00:00Z' }, | ||
| { id: '5', type: 'bounty_completed', title: 'Bounty Completed', description: 'CI/CD Pipeline bounty merged', timestamp: '2026-03-19T10:00:00Z' }, | ||
| ]; | ||
|
|
||
| const MOCK_NOTIFICATIONS: Notification[] = [ | ||
| { id: '1', type: 'success', title: 'PR Merged', message: 'Your PR #109 has been merged!', timestamp: '2026-03-20T10:00:00Z', read: false }, | ||
| { id: '2', type: 'info', title: 'New Bounty', message: 'A new T1 bounty is available: Twitter Post', timestamp: '2026-03-20T03:00:00Z', read: false }, | ||
| { id: '3', type: 'warning', title: 'Deadline Approaching', message: 'WebSocket Server bounty deadline in 2 days', timestamp: '2026-03-20T02:00:00Z', read: true }, | ||
| ]; | ||
|
|
||
| const MOCK_EARNINGS: EarningsData[] = [ | ||
| { date: '2026-03-01', amount: 0 }, | ||
| { date: '2026-03-05', amount: 0 }, | ||
| { date: '2026-03-10', amount: 100000 }, | ||
| { date: '2026-03-12', amount: 150000 }, | ||
| { date: '2026-03-15', amount: 500000 }, | ||
| { date: '2026-03-18', amount: 800000 }, | ||
| { date: '2026-03-20', amount: 950000 }, | ||
| ]; | ||
|
|
||
| const MOCK_LINKED_ACCOUNTS = [ | ||
| { type: 'github', username: 'HuiNeng6', connected: true }, | ||
| { type: 'twitter', username: '', connected: false }, | ||
| ]; | ||
|
|
||
| // ============================================================================ | ||
| // Data Fetcher (Simulates API calls) | ||
| // ============================================================================ | ||
|
|
||
| interface DashboardData { | ||
| stats: DashboardStats; | ||
| bounties: Bounty[]; | ||
| activities: Activity[]; | ||
| notifications: Notification[]; | ||
| earnings: EarningsData[]; | ||
| linkedAccounts: { type: string; username: string; connected: boolean }[]; | ||
| } | ||
|
|
||
| interface DashboardData { stats: DashboardStats; bounties: Bounty[]; activities: Activity[]; notifications: Notification[]; earnings: EarningsData[]; linkedAccounts: { type: string; username: string; connected: boolean }[]; } | ||
| const ES: DashboardStats = { totalEarned: 0, activeBounties: 0, pendingPayouts: 0, reputationRank: 0, totalContributors: 0 }; | ||
| async function safe<T>(ep: string, p?: Record<string, string | number | boolean | undefined>): Promise<T | null> { try { return await apiClient<T>(ep, { params: p, retries: 0 }); } catch { return null; } } | ||
| async function fetchDashboardData(userId: string | undefined): Promise<DashboardData> { | ||
| // Simulate network delay (100-300ms) | ||
| await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200)); | ||
|
|
||
| // In a real app, this would fetch from an API using userId | ||
| // For now, return mock data but log userId for future integration | ||
| if (process.env.NODE_ENV !== 'test') { | ||
| console.log('Fetching dashboard data for user:', userId || 'anonymous'); | ||
| } | ||
|
|
||
| return { | ||
| stats: MOCK_STATS, | ||
| bounties: MOCK_BOUNTIES, | ||
| activities: MOCK_ACTIVITIES, | ||
| notifications: MOCK_NOTIFICATIONS, | ||
| earnings: MOCK_EARNINGS, | ||
| linkedAccounts: MOCK_LINKED_ACCOUNTS, | ||
| }; | ||
| const d: DashboardData = { stats: { ...ES }, bounties: [], activities: [], notifications: [], earnings: [], linkedAccounts: [] }; | ||
| const [bRaw, nRaw, lRaw] = await Promise.all([safe<{ items?: unknown[] }>('/api/bounties', { claimed_by: userId ?? '', limit: 10 }), safe<{ items?: unknown[] }>('/api/notifications', { limit: 10 }), safe<unknown[]>('/api/leaderboard', { range: 'all', limit: 50 })]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty When 🤖 Prompt for AI Agents |
||
| if (bRaw) { const items = (Array.isArray(bRaw) ? bRaw : (bRaw.items ?? [])) as Record<string, unknown>[]; d.bounties = items.map((b) => ({ id: String(b.id ?? ''), title: String(b.title ?? ''), reward: Number(b.reward_amount ?? b.reward ?? 0), deadline: String(b.deadline ?? ''), status: String(b.status ?? 'claimed') as Bounty['status'], progress: Number(b.progress ?? 0) })); } | ||
| if (nRaw) { const items = (Array.isArray(nRaw) ? nRaw : (nRaw.items ?? [])) as Record<string, unknown>[]; d.notifications = items.map((n) => ({ id: String(n.id ?? ''), type: String(n.type ?? 'info') as Notification['type'], title: String(n.title ?? ''), message: String(n.message ?? ''), timestamp: String(n.created_at ?? n.timestamp ?? ''), read: Boolean(n.read ?? false) })); } | ||
| if (Array.isArray(lRaw)) { d.stats.totalContributors = lRaw.length; const me = (lRaw as Record<string, unknown>[]).find((e) => String(e.username ?? '').toLowerCase() === (userId ?? '').toLowerCase()); if (me) { d.stats.totalEarned = Number(me.earningsFndry ?? 0); d.stats.reputationRank = Number(me.rank ?? 0); } } | ||
| d.stats.activeBounties = d.bounties.length; | ||
| return d; | ||
| } | ||
|
|
||
| // ============================================================================ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,34 +1,42 @@ | ||||||
| /** Route for /agents/:agentId via apiClient. @module pages/AgentProfilePage */ | ||||||
| import { useState, useEffect } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | ||||||
| import { AgentProfile } from '../components/agents/AgentProfile'; | ||||||
| import { AgentProfileSkeleton } from '../components/agents/AgentProfileSkeleton'; | ||||||
| import { AgentNotFound } from '../components/agents/AgentNotFound'; | ||||||
| import { getAgentById } from '../data/mockAgents'; | ||||||
| import { apiClient } from '../services/apiClient'; | ||||||
| import type { AgentProfile as AgentProfileType } from '../types/agent'; | ||||||
|
|
||||||
| /** Map API response to AgentProfile. */ | ||||||
| function mapAgent(r: Record<string, unknown>): AgentProfileType { | ||||||
| const cb = Array.isArray(r.completed_bounties) ? r.completed_bounties as Record<string, unknown>[] : []; | ||||||
| return { | ||||||
| id: String(r.id ?? ''), name: String(r.name ?? ''), avatar: String(r.avatar ?? r.avatar_url ?? ''), | ||||||
| role: (r.role as AgentProfileType['role']) ?? 'developer', status: (r.status as AgentProfileType['status']) ?? 'offline', | ||||||
| bio: String(r.bio ?? r.description ?? ''), skills: (r.skills ?? []) as string[], languages: (r.languages ?? []) as string[], | ||||||
| bountiesCompleted: Number(r.bounties_completed ?? 0), successRate: Number(r.success_rate ?? 0), | ||||||
| avgReviewScore: Number(r.avg_review_score ?? 0), totalEarned: Number(r.total_earned ?? 0), | ||||||
| completedBounties: cb.map(b => ({ id: String(b.id ?? ''), title: String(b.title ?? ''), completedAt: String(b.completed_at ?? ''), score: Number(b.score ?? 0), reward: Number(b.reward ?? 0), currency: '$FNDRY' })), | ||||||
| joinedAt: String(r.joined_at ?? r.created_at ?? ''), | ||||||
| }; | ||||||
| } | ||||||
|
Comment on lines
+11
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Unsafe type assertions for enum-like fields. The casts ♻️ Safer pattern with validationconst VALID_ROLES = ['developer', 'reviewer', 'manager'] as const;
const VALID_STATUSES = ['online', 'offline', 'busy'] as const;
const role = VALID_ROLES.includes(r.role as any) ? r.role as AgentProfileType['role'] : 'developer';
const status = VALID_STATUSES.includes(r.status as any) ? r.status as AgentProfileType['status'] : 'offline';🤖 Prompt for AI Agents |
||||||
|
|
||||||
| export default function AgentProfilePage() { | ||||||
| const { agentId } = useParams<{ agentId: string }>(); | ||||||
| const [agent, setAgent] = useState<AgentProfileType | null>(null); | ||||||
| const [loading, setLoading] = useState(true); | ||||||
| const [notFound, setNotFound] = useState(false); | ||||||
|
|
||||||
| useEffect(() => { | ||||||
| setLoading(true); | ||||||
| setNotFound(false); | ||||||
| setAgent(null); | ||||||
|
|
||||||
| // Simulate network delay — will be replaced with real API call | ||||||
| const timer = setTimeout(() => { | ||||||
| const found = agentId ? getAgentById(agentId) : undefined; | ||||||
| if (found) { | ||||||
| setAgent(found); | ||||||
| } else { | ||||||
| setNotFound(true); | ||||||
| } | ||||||
| setLoading(false); | ||||||
| }, 600); | ||||||
|
|
||||||
| return () => clearTimeout(timer); | ||||||
| if (!agentId) { setNotFound(true); setLoading(false); return; } | ||||||
| setLoading(true); setNotFound(false); setAgent(null); | ||||||
| (async () => { | ||||||
| try { | ||||||
| const data = await apiClient<Record<string, unknown>>(`/api/agents/${agentId}`, { retries: 1 }); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing URL encoding for agentId parameter. Unlike - const data = await apiClient<Record<string, unknown>>(`/api/agents/${agentId}`, { retries: 1 });
+ const data = await apiClient<Record<string, unknown>>(`/api/agents/${encodeURIComponent(agentId)}`, { retries: 1 });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| setAgent(mapAgent(data)); | ||||||
| } catch { setNotFound(true); } | ||||||
| finally { setLoading(false); } | ||||||
| })(); | ||||||
| }, [agentId]); | ||||||
|
|
||||||
| if (loading) return <AgentProfileSkeleton />; | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Error swallowing prevents debugging and user feedback.
The
safe()wrapper catches all errors and returnsnull, making it impossible to distinguish between "API returned empty data" vs "API is down" vs "user is unauthorized." Consider returning a result type or at least logging errors.♻️ Alternative with error visibility
🤖 Prompt for AI Agents