Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions backend/app/api/contributors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from app.auth import get_current_user_id
from app.constants import INTERNAL_SYSTEM_USER_ID
from app.exceptions import ContributorNotFoundError, TierNotUnlockedError

Check failure on line 14 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/contributors.py:14:54: F401 `app.exceptions.TierNotUnlockedError` imported but unused help: Remove unused import: `app.exceptions.TierNotUnlockedError`

Check failure on line 14 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/contributors.py:14:54: F401 `app.exceptions.TierNotUnlockedError` imported but unused help: Remove unused import: `app.exceptions.TierNotUnlockedError`

Check failure on line 14 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/contributors.py:14:54: F401 `app.exceptions.TierNotUnlockedError` imported but unused help: Remove unused import: `app.exceptions.TierNotUnlockedError`
from app.models.contributor import (
ContributorCreate,
ContributorListResponse,
Expand Down Expand Up @@ -254,5 +254,59 @@
return await reputation_service.record_reputation(data)
except ContributorNotFoundError as error:
raise HTTPException(status_code=404, detail=str(error))
except TierNotUnlockedError as error:
raise HTTPException(status_code=400, detail=str(error))
class DashboardData(BaseModel):

Check failure on line 257 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F821)

app/api/contributors.py:257:21: F821 Undefined name `BaseModel`

Check failure on line 257 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F821)

app/api/contributors.py:257:21: F821 Undefined name `BaseModel`

Check failure on line 257 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F821)

app/api/contributors.py:257:21: F821 Undefined name `BaseModel`
"""Payload for the contributor dashboard."""
stats: dict
bounties: list
activities: list
notifications: list = []
earnings: list = []
linkedAccounts: list = []


@router.get("/me/dashboard", response_model=DashboardData)
async def get_my_dashboard(
user_id: str = Depends(get_current_user_id)
) -> DashboardData:
"""Get metrics and state for the authenticated contributor's dashboard.

Args:
user_id: Authenticated user (contributor) ID.

Returns:
Structured dashboard data with stats, bounties, and activity history.

Raises:
HTTPException 404: Contributor profile not found.
"""
contributor = await contributor_service.get_contributor(user_id)
if not contributor:
raise HTTPException(status_code=404, detail="Contributor profile not found")

# In a real app, query database/indexers for these lists
stats = {
"totalEarned": contributor.total_earnings,
"activeBounties": 2, # Mocked count
"pendingPayouts": 0,
"reputationRank": 15,
"totalContributors": 120,
}

bounties = [
{"id": "b1", "title": "Implement API Rate Limiting", "reward": 500, "deadline": "2026-03-30", "status": "in_progress", "progress": 75},
{"id": "b2", "title": "Refactor Frontend Hooks", "reward": 300, "deadline": "2026-03-29", "status": "claimed", "progress": 10},
]

activities = [
{"id": "a1", "type": "payout", "title": "Payout Received", "description": "Earned 500 $FNDRY for rate limiter", "timestamp": "2026-03-21T10:00:00Z", "amount": 500},
{"id": "a2", "type": "pr_submitted", "title": "PR Submitted", "description": "Submitted PR for security middleware", "timestamp": "2026-03-21T09:00:00Z"},
]

return DashboardData(
stats=stats,
bounties=bounties,
activities=activities,
notifications=[],
earnings=[],
linkedAccounts=[{"type": "github", "username": contributor.username, "connected": True}],
)
47 changes: 45 additions & 2 deletions backend/app/api/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import logging
import time
from datetime import datetime, timezone
from typing import Dict, List, Optional

Check failure on line 10 in backend/app/api/stats.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/stats.py:10:26: F401 `typing.List` imported but unused help: Remove unused import: `typing.List`

Check failure on line 10 in backend/app/api/stats.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/stats.py:10:26: F401 `typing.List` imported but unused help: Remove unused import: `typing.List`

Check failure on line 10 in backend/app/api/stats.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/stats.py:10:26: F401 `typing.List` imported but unused help: Remove unused import: `typing.List`

from fastapi import APIRouter
from pydantic import BaseModel
Expand Down Expand Up @@ -135,7 +135,36 @@
return data


@router.get("/api/stats", response_model=StatsResponse)
class TokenomicsData(BaseModel):
"""Tokenomics data response."""
tokenName: str = "$FNDRY"
tokenCA: str = "Fndry...1H7"
totalSupply: int = 1_000_000_000
circulatingSupply: int = 420_000_000
treasuryHoldings: int = 250_000_000
totalDistributed: int = 150_000_000
totalBuybacks: int = 50_000_000
totalBurned: int = 30_000_000
feeRevenueSol: float = 1245.50
distributionBreakdown: Dict[str, int] = {
"Circulating": 420_000_000,
"Treasury": 250_000_000,
"Staking": 200_000_000,
"Team_Vested": 100_000_000,
"Burned": 30_000_000,
}
lastUpdated: str = datetime.now(timezone.utc).isoformat()


class TreasuryStats(BaseModel):
"""Treasury stats response."""
solBalance: float = 1245.50
fndryBalance: int = 250_000_000
totalPayouts: int = 1240
treasuryWallet: str = "6v...m7p"


@router.get("/stats", response_model=StatsResponse)
async def get_stats() -> StatsResponse:
"""Get bounty program statistics.

Expand All @@ -151,4 +180,18 @@
Cached for 5 minutes.
"""
data = _get_cached_stats()
return StatsResponse(**data)
return StatsResponse(**data)


@router.get("/stats/tokenomics", response_model=TokenomicsData)
async def get_tokenomics() -> TokenomicsData:
"""Get tokenomics statistics for $FNDRY."""
# In a real app, this would query the blockchain/indexers
return TokenomicsData()


@router.get("/stats/treasury", response_model=TreasuryStats)
async def get_treasury() -> TreasuryStats:
"""Get treasury wallet statistics."""
# In a real app, this would query the treasury wallet address
return TreasuryStats()
6 changes: 5 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
"@solana/wallet-adapter-wallets": "^0.19.32",
"@solana/web3.js": "^1.95.0",
"@tailwindcss/postcss": "^4.2.2",
"@tanstack/react-query": "^5.94.5",
"@tanstack/react-query-devtools": "^5.94.5",
"autoprefixer": "^10.4.27",
"axios": "^1.13.6",
"lucide-react": "^0.577.0",
"postcss": "^8.5.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand All @@ -32,4 +36,4 @@
"vite": "^6.0.0",
"vitest": "^3.0.0"
}
}
}
35 changes: 25 additions & 10 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ import { SiteLayout } from './components/layout/SiteLayout';
import { ThemeProvider } from './contexts/ThemeContext';
import { ToastProvider } from './contexts/ToastContext';
import { ToastContainer } from './components/common/ToastContainer';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
staleTime: 1000 * 60 * 5, // 5 minutes
},
},
});

// ── Lazy-loaded page components ──────────────────────────────────────────────
const BountiesPage = lazy(() => import('./pages/BountiesPage'));
Expand Down Expand Up @@ -87,15 +99,18 @@ function AppLayout() {
// ── Root App ─────────────────────────────────────────────────────────────────
export default function App() {
return (
<BrowserRouter>
<ThemeProvider defaultTheme="dark">
<ToastProvider>
<WalletProvider defaultNetwork="mainnet-beta">
<AppLayout />
</WalletProvider>
<ToastContainer />
</ToastProvider>
</ThemeProvider>
</BrowserRouter>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<ThemeProvider defaultTheme="dark">
<ToastProvider>
<WalletProvider defaultNetwork="mainnet-beta">
<AppLayout />
</WalletProvider>
<ToastContainer />
</ToastProvider>
</ThemeProvider>
</BrowserRouter>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Loading
Loading