From d6fcbbfbc22e29654313be4aad2c17cf87230c37 Mon Sep 17 00:00:00 2001 From: Aframp Dev Date: Wed, 29 Apr 2026 07:09:42 +0100 Subject: [PATCH] feat(frontend): add pool management dashboard page for creators --- nevo_frontend/app/dashboard/page.tsx | 547 +++++++++++++++++++++++++++ 1 file changed, 547 insertions(+) create mode 100644 nevo_frontend/app/dashboard/page.tsx diff --git a/nevo_frontend/app/dashboard/page.tsx b/nevo_frontend/app/dashboard/page.tsx new file mode 100644 index 0000000..f90efe4 --- /dev/null +++ b/nevo_frontend/app/dashboard/page.tsx @@ -0,0 +1,547 @@ +'use client'; + +import React, { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { useWalletStore } from '@/src/store/walletStore'; +import { WalletAddress } from '@/components/WalletAddress'; +import type { Pool } from '@/src/store/poolsStore'; + +// TODO: Replace with real API call once backend pool endpoints are implemented +const MOCK_CREATOR_POOLS: Pool[] = [ + { + id: '1', + title: 'Clean Water Initiative', + description: 'Providing clean drinking water to rural communities in need.', + category: 'Humanitarian', + status: 'Active', + target: 10000, + raised: 6800, + imageColor: '#27926e', + creator: 'GABCDE1234567890ABCDE1234567890ABCDE1234567890ABCDE1234567890', + createdAt: '2025-03-01', + }, + { + id: '2', + title: 'Open Source Dev Fund', + description: 'Supporting open source contributors building on Stellar.', + category: 'Technology', + status: 'Active', + target: 5000, + raised: 5000, + imageColor: '#1c7459', + creator: 'GABCDE1234567890ABCDE1234567890ABCDE1234567890ABCDE1234567890', + createdAt: '2025-01-15', + }, + { + id: '3', + title: 'Community Garden Project', + description: 'Building urban gardens to improve food security locally.', + category: 'Environment', + status: 'Completed', + target: 3000, + raised: 3200, + imageColor: '#47ae88', + creator: 'GABCDE1234567890ABCDE1234567890ABCDE1234567890ABCDE1234567890', + createdAt: '2024-11-10', + }, +]; + +// TODO: Replace with real contributor counts from backend +const MOCK_CONTRIBUTOR_COUNTS: Record = { + '1': 42, + '2': 87, + '3': 31, +}; + +type ActionModal = + | { type: 'withdraw'; pool: Pool } + | { type: 'archive'; pool: Pool } + | null; + +export default function DashboardPage() { + const { publicKey, loading, initialize } = useWalletStore(); + const [pools, setPools] = useState([]); + const [loadingPools, setLoadingPools] = useState(true); + const [actionModal, setActionModal] = useState(null); + const [selectedIds, setSelectedIds] = useState>(new Set()); + + useEffect(() => { + initialize(); + }, [initialize]); + + useEffect(() => { + if (!publicKey) return; + // TODO: Replace with real fetch filtered by creator === publicKey + const timer = setTimeout(() => { + setPools(MOCK_CREATOR_POOLS); + setLoadingPools(false); + }, 400); + return () => clearTimeout(timer); + }, [publicKey]); + + // ── Wallet not connected ─────────────────────────────────────────────── + if (!loading && !publicKey) { + return ( +
+
+
+ +
+

Connect your wallet

+

+ Your dashboard is only accessible to pool creators. Connect your + Stellar wallet to continue. +

+
+
+ ); + } + + // ── Summary metrics ──────────────────────────────────────────────────── + const totalRaised = pools.reduce((s, p) => s + p.raised, 0); + const activePools = pools.filter((p) => p.status === 'Active').length; + const totalContributors = Object.values(MOCK_CONTRIBUTOR_COUNTS).reduce( + (s, n) => s + n, + 0 + ); + + // ── Bulk selection helpers ───────────────────────────────────────────── + const allSelected = pools.length > 0 && selectedIds.size === pools.length; + + function toggleAll() { + setSelectedIds(allSelected ? new Set() : new Set(pools.map((p) => p.id))); + } + + function toggleOne(id: string) { + setSelectedIds((prev) => { + const next = new Set(prev); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + return next; + }); + } + + return ( +
+ {/* ── Page header ─────────────────────────────────────────────────── */} +
+
+

My Pools

+ {publicKey && ( +
+ +
+ )} +
+ + + New Pool + +
+ + {/* ── Summary stats ───────────────────────────────────────────────── */} +
+ {[ + { label: 'Total Pools', value: pools.length }, + { label: 'Active Pools', value: activePools }, + { + label: 'Total Raised', + value: `${totalRaised.toLocaleString()} XLM`, + }, + { label: 'Contributors', value: totalContributors }, + ].map(({ label, value }) => ( +
+

{label}

+

{value}

+
+ ))} +
+ + {/* ── Bulk action bar ─────────────────────────────────────────────── */} + {selectedIds.size > 0 && ( +
+ + {selectedIds.size} selected + + + +
+ )} + + {/* ── Pool list ───────────────────────────────────────────────────── */} + {loading || loadingPools ? ( + + ) : pools.length === 0 ? ( + + ) : ( +
+ {/* Table header — desktop only */} +
+ + Pool + Progress + Status + Actions +
+ +
    + {pools.map((pool) => ( + toggleOne(pool.id)} + onWithdraw={() => setActionModal({ type: 'withdraw', pool })} + onArchive={() => setActionModal({ type: 'archive', pool })} + /> + ))} +
+
+ )} + + {/* ── Confirmation modal ──────────────────────────────────────────── */} + {actionModal && ( + setActionModal(null)} + onConfirm={() => { + // TODO: wire to real withdraw / archive contract calls + setActionModal(null); + }} + /> + )} +
+ ); +} + +/* ── PoolRow ──────────────────────────────────────────────────────────────── */ + +interface PoolRowProps { + pool: Pool; + contributors: number; + selected: boolean; + onToggle: () => void; + onWithdraw: () => void; + onArchive: () => void; +} + +function PoolRow({ + pool, + contributors, + selected, + onToggle, + onWithdraw, + onArchive, +}: PoolRowProps) { + const pct = Math.min(100, Math.round((pool.raised / pool.target) * 100)); + const isCompleted = pool.status === 'Completed'; + + return ( +
  • +
    + {/* Checkbox + title */} +
    + + +
    +
    + + {pool.title} + + +
    +

    + {pool.description} +

    + + {/* Progress */} +
    +
    + + {pool.raised.toLocaleString()} /{' '} + {pool.target.toLocaleString()} XLM + + {pct}% +
    +
    +
    +
    +
    + + {/* Meta row */} +
    + {contributors} contributors + {pool.category} + {pool.createdAt && Created {pool.createdAt}} +
    +
    +
    + + {/* Actions */} +
    + + Edit + + + +
    +
    +
  • + ); +} + +/* ── StatusBadge ──────────────────────────────────────────────────────────── */ + +function StatusBadge({ status }: { status: Pool['status'] }) { + const styles = + status === 'Active' + ? 'bg-success-light text-success-dark' + : 'bg-[var(--color-surface-raised)] text-[var(--color-text-muted)] border border-[var(--color-border)]'; + + return ( + + {status} + + ); +} + +/* ── ConfirmModal ─────────────────────────────────────────────────────────── */ + +function ConfirmModal({ + modal, + onClose, + onConfirm, +}: { + modal: NonNullable; + onClose: () => void; + onConfirm: () => void; +}) { + const isWithdraw = modal.type === 'withdraw'; + + return ( +
    + {/* Backdrop */} + + ); +} + +/* ── Skeleton ─────────────────────────────────────────────────────────────── */ + +function PoolListSkeleton() { + return ( +
      + {[1, 2, 3].map((i) => ( +
    • + ))} +
    + ); +} + +/* ── Empty state ──────────────────────────────────────────────────────────── */ + +function EmptyState() { + return ( +
    +
    + +
    +

    No pools yet

    +

    + Create your first pool to start raising funds on-chain. +

    + + Create a Pool + +
    + ); +} + +/* ── Icons ────────────────────────────────────────────────────────────────── */ + +function PlusIcon() { + return ( + + ); +} + +function LockIcon() { + return ( + + ); +} + +function PoolIcon() { + return ( + + ); +}