diff --git a/app/bounty/page.tsx b/app/bounty/page.tsx index e371b5d..b20bbc3 100644 --- a/app/bounty/page.tsx +++ b/app/bounty/page.tsx @@ -1,25 +1,124 @@ "use client"; -import { useMemo } from "react"; +import { useState } from "react"; +import Link from "next/link"; import { useBounties } from "@/hooks/use-bounties"; +import { useDebounce } from "@/hooks/use-debounce"; +import { BountyCard } from "@/components/bounty/bounty-card"; +import { BountyListSkeleton } from "@/components/bounty/bounty-card-skeleton"; +import { BountyError } from "@/components/bounty/bounty-error"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; import { - useBountyFilters, - BOUNTY_TYPES, - STATUSES, -} from "@/hooks/use-bounty-filters"; -import { FiltersSidebar } from "@/components/bounty/filters-sidebar"; -import { BountyToolbar } from "@/components/bounty/bounty-toolbar"; -import { BountyGrid } from "@/components/bounty/bounty-grid"; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Label } from "@/components/ui/label"; +import { Search, Filter } from "lucide-react"; +import { MiniLeaderboard } from "@/components/leaderboard/mini-leaderboard"; +import { + BountyStatus, + BountyType, + type BountyQueryInput, +} from "@/lib/graphql/generated"; export default function BountiesPage() { - const { data, isLoading, isError, error, refetch } = useBounties(); - const allBounties = useMemo(() => data?.data ?? [], [data?.data]); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedType, setSelectedType] = useState("all"); + const [statusFilter, setStatusFilter] = useState( + BountyStatus.Open, + ); + const [sortOption, setSortOption] = useState("newest"); + const [page, setPage] = useState(1); + + const BOUNTY_TYPES: { value: BountyType; label: string }[] = [ + { value: BountyType.FixedPrice, label: "Fixed Price" }, + { value: BountyType.MilestoneBased, label: "Milestone Based" }, + { value: BountyType.Competition, label: "Competition" }, + ]; + + const STATUSES: { value: BountyStatus | "all"; label: string }[] = [ + { value: BountyStatus.Open, label: "Open" }, + { value: BountyStatus.InProgress, label: "In Progress" }, + { value: BountyStatus.Completed, label: "Completed" }, + { value: BountyStatus.Cancelled, label: "Cancelled" }, + { value: BountyStatus.Draft, label: "Draft" }, + { value: BountyStatus.Submitted, label: "Submitted" }, + { value: BountyStatus.UnderReview, label: "Under Review" }, + { value: BountyStatus.Disputed, label: "Disputed" }, + { value: "all", label: "All Statuses" }, + ]; + + const debouncedSearchQuery = useDebounce(searchQuery, 500); + + const getSortParams = () => { + switch (sortOption) { + case "highest_reward": + return { sortBy: "rewardAmount", sortOrder: "desc" }; + case "recently_updated": + return { sortBy: "updatedAt", sortOrder: "desc" }; + case "newest": + default: + return { sortBy: "createdAt", sortOrder: "desc" }; + } + }; + + const queryParams: BountyQueryInput = { + page, + limit: 20, + ...(debouncedSearchQuery && { search: debouncedSearchQuery }), + ...(selectedType !== "all" && { type: selectedType }), + ...(statusFilter !== "all" && { status: statusFilter }), + ...getSortParams(), + }; + + const { data, isLoading, isError, error, refetch } = useBounties(queryParams); + + const bounties = data?.data ?? []; + const pagination = data?.pagination; + const totalResults = pagination?.total ?? 0; + const currentPage = pagination?.page ?? page; + const totalPages = pagination?.totalPages ?? 1; + + const toggleType = (type: BountyType) => { + setSelectedType((prev) => (prev === type ? "all" : type)); + setPage(1); + }; - const filters = useBountyFilters(allBounties); + const handleStatusChange = (status: string) => { + setStatusFilter(status as BountyStatus | "all"); + setPage(1); + }; + + const handleSortChange = (sort: string) => { + setSortOption(sort); + setPage(1); + }; + + const clearFilters = () => { + setSearchQuery(""); + setSelectedType("all"); + setStatusFilter(BountyStatus.Open); + setSortOption("newest"); + setPage(1); + }; + + const hasPreviousPage = currentPage > 1; + const hasNextPage = currentPage < totalPages; return (
- {/* Background ambient glow */}
@@ -34,42 +133,214 @@ export default function BountiesPage() {
- +
- - +
+
+ {totalResults} results + found +
+
+ + Sort by: + + +
+
+ + {isLoading ? ( + + ) : isError ? ( + refetch()} + /> + ) : bounties.length > 0 ? ( + <> +
+ {bounties.map((bounty) => ( + + + + ))} +
+ +
+

+ Page {currentPage} of {totalPages} +

+
+ + +
+
+ + ) : ( +
+
+ +
+

+ No bounties found +

+

+ We couldn't find any bounties matching your current + filters. Try adjusting your search terms or filters. +

+ +
+ )}