diff --git a/apps/frontend/app/bounties/[id]/loading.tsx b/apps/frontend/app/bounties/[id]/loading.tsx new file mode 100644 index 00000000..bb2d8f54 --- /dev/null +++ b/apps/frontend/app/bounties/[id]/loading.tsx @@ -0,0 +1,5 @@ +import BountyDetailSkeleton from "../../components/BountyDetailSkeleton"; + +export default function Loading() { + return ; +} \ No newline at end of file diff --git a/apps/frontend/app/dashboard/loading.tsx b/apps/frontend/app/dashboard/loading.tsx new file mode 100644 index 00000000..074eecea --- /dev/null +++ b/apps/frontend/app/dashboard/loading.tsx @@ -0,0 +1,5 @@ +import DashboardSkeleton from "../components/DashboardSkeleton"; + +export default function Loading() { + return ; +} \ No newline at end of file diff --git a/apps/frontend/app/dashboard/page.tsx b/apps/frontend/app/dashboard/page.tsx index 872fcede..4af7b824 100644 --- a/apps/frontend/app/dashboard/page.tsx +++ b/apps/frontend/app/dashboard/page.tsx @@ -2,10 +2,9 @@ import { useEffect, useState } from "react"; import { useWallet } from "../../components/WalletContext"; +import { useAuth } from "../../lib/api"; import { StatusBadge } from "../../components/StatusBadge"; -const API_URL = process.env.NEXT_PUBLIC_API_URL ?? ""; - type SubmissionStatus = "pending" | "approved" | "rejected"; type BountyStatus = "open" | "in_progress" | "completed" | "cancelled"; @@ -32,6 +31,7 @@ function EmptyState({ message }: { message: string }) { export default function DashboardPage() { const { publicKey, connect } = useWallet(); + const { getToken, apiUrl } = useAuth(); const [activeTab, setActiveTab] = useState<"submissions" | "bounties">("submissions"); const [submissions, setSubmissions] = useState([]); const [bounties, setBounties] = useState([]); @@ -45,8 +45,18 @@ export default function DashboardPage() { setError(null); Promise.all([ - fetch(`${API_URL}/submissions?contributor=${publicKey}`).then((r) => r.json()), - fetch(`${API_URL}/api/v1/bounties?owner=${publicKey}`).then((r) => r.json()), + (async () => { + const token = await getToken(publicKey); + const res = await fetch(`${apiUrl}/api/v1/me/submissions`, { + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }); + if (!res.ok) return []; + const body = await res.json(); + /* Normalise the API response — the backend may return + * { submissions: [...] } or a plain array. */ + return Array.isArray(body) ? body : body?.submissions ?? []; + })(), + fetch(`${apiUrl}/api/v1/bounties?owner=${publicKey}`).then((r) => r.json()), ]) .then(([subs, bounts]) => { setSubmissions(subs); @@ -54,7 +64,7 @@ export default function DashboardPage() { }) .catch(() => setError("Failed to load dashboard data.")) .finally(() => setLoading(false)); - }, [publicKey]); + }, [publicKey, getToken, apiUrl]); if (!publicKey) { return ( @@ -158,4 +168,4 @@ export default function DashboardPage() { )} ); -} +} \ No newline at end of file diff --git a/apps/frontend/app/loading.tsx b/apps/frontend/app/loading.tsx index c41a752e..86f56bf3 100644 --- a/apps/frontend/app/loading.tsx +++ b/apps/frontend/app/loading.tsx @@ -1,16 +1,5 @@ -const skeletonCards = Array.from({ length: 6 }, (_, index) => index); +import BountyListSkeleton from "./components/BountyListSkeleton"; export default function Loading() { - return ( - - - - - {skeletonCards.map((card) => ( - - ))} - - - - ); -} + return ; +} \ No newline at end of file diff --git a/apps/frontend/components/BountyDetailSkeleton.tsx b/apps/frontend/components/BountyDetailSkeleton.tsx new file mode 100644 index 00000000..53dd12f5 --- /dev/null +++ b/apps/frontend/components/BountyDetailSkeleton.tsx @@ -0,0 +1,56 @@ +export default function BountyDetailSkeleton() { + return ( + + + {/* Main content skeleton */} + + {/* Badge + metadata row */} + + + + + + + {/* Title */} + + + + {/* Description */} + + + + + + + + {/* Info cards */} + + {[0, 1, 2].map((i) => ( + + + + + ))} + + + + {/* Sidebar skeleton */} + + + + ); +} \ No newline at end of file diff --git a/apps/frontend/components/BountyListSkeleton.tsx b/apps/frontend/components/BountyListSkeleton.tsx new file mode 100644 index 00000000..e6cca1ae --- /dev/null +++ b/apps/frontend/components/BountyListSkeleton.tsx @@ -0,0 +1,52 @@ +export default function BountyListSkeleton() { + return ( + + + {/* Hero section skeleton */} + + + + + + + + + + + {/* Filters skeleton */} + + + + + + + + + + + + + {/* Bounty cards grid */} + + {[...Array(6)].map((_, i) => ( + + + + + + + + + + + + + ))} + + + + ); +} \ No newline at end of file diff --git a/apps/frontend/components/DashboardSkeleton.tsx b/apps/frontend/components/DashboardSkeleton.tsx new file mode 100644 index 00000000..9668058e --- /dev/null +++ b/apps/frontend/components/DashboardSkeleton.tsx @@ -0,0 +1,34 @@ +export default function DashboardSkeleton() { + return ( + + + + {/* Tabs */} + + {["submissions", "bounties"].map((tab) => ( + + ))} + + + {/* Table header */} + + + + + + + {/* Table rows */} + {[...Array(4)].map((_, i) => ( + + ))} + + ); +} \ No newline at end of file