Spaces:
Configuration error
Configuration error
| "use client" | |
| import { useState, useEffect, useCallback } from "react" | |
| import { useSearchParams, useRouter } from "next/navigation" | |
| import { PromptCard } from "@/components/explore/prompt-card" | |
| import { SearchBar, SearchFilters } from "@/components/explore/search-bar" | |
| import { Button } from "@/components/ui/button" | |
| import { Loader2, Sparkles } from "lucide-react" | |
| interface Prompt { | |
| id: string | |
| slug: string | |
| title: string | |
| description: string | null | |
| category: string | null | |
| totalRuns: number | |
| starsCount: number | |
| remixesCount: number | |
| badges: string[] | |
| creator: { | |
| name: string | null | |
| username: string | null | |
| image: string | null | |
| } | null | |
| } | |
| interface ExploreClientProps { | |
| initialPrompts: Prompt[] | |
| } | |
| export function ExploreClient({ initialPrompts }: ExploreClientProps) { | |
| const router = useRouter() | |
| const searchParams = useSearchParams() | |
| const [prompts, setPrompts] = useState<Prompt[]>(initialPrompts) | |
| const [isLoading, setIsLoading] = useState(false) | |
| const [hasMore, setHasMore] = useState(true) | |
| const [offset, setOffset] = useState(initialPrompts.length) | |
| // Fetch prompts with current filters | |
| const fetchPrompts = useCallback(async (query: string, filters: SearchFilters, reset = false) => { | |
| setIsLoading(true) | |
| try { | |
| const params = new URLSearchParams() | |
| if (query) params.set("search", query) | |
| if (filters.category) params.set("category", filters.category) | |
| if (filters.sortBy) params.set("sort", filters.sortBy) | |
| if (filters.dateRange && filters.dateRange !== 'all') params.set("dateRange", filters.dateRange) | |
| params.set("limit", "12") | |
| params.set("offset", reset ? "0" : offset.toString()) | |
| const res = await fetch(`/api/prompts?${params.toString()}`) | |
| const data = await res.json() | |
| if (reset) { | |
| setPrompts(data) | |
| setOffset(data.length) | |
| } else { | |
| setPrompts((prev) => { | |
| const existingIds = new Set(prev.map(p => p.id)) | |
| const newPrompts = data.filter((p: Prompt) => !existingIds.has(p.id)) | |
| return [...prev, ...newPrompts] | |
| }) | |
| setOffset((prev) => prev + data.length) | |
| } | |
| setHasMore(data.length === 12) | |
| } catch (error) { | |
| console.error("Failed to fetch prompts:", error) | |
| } finally { | |
| setIsLoading(false) | |
| } | |
| }, [offset]) | |
| const handleSearch = (query: string, filters: SearchFilters) => { | |
| // Update URL with search params | |
| const params = new URLSearchParams() | |
| if (query) params.set("q", query) | |
| if (filters.category) params.set("category", filters.category) | |
| if (filters.sortBy && filters.sortBy !== 'trending') params.set("sort", filters.sortBy) | |
| if (filters.dateRange && filters.dateRange !== 'all') params.set("date", filters.dateRange) | |
| const queryString = params.toString() | |
| router.push(queryString ? `/explore?${queryString}` : "/explore", { scroll: false }) | |
| // Fetch with new filters | |
| fetchPrompts(query, filters, true) | |
| } | |
| return ( | |
| <div className="space-y-8"> | |
| {/* Advanced Search Bar */} | |
| <SearchBar | |
| initialQuery={searchParams.get("q") || ""} | |
| onSearch={handleSearch} | |
| /> | |
| {/* Results Count */} | |
| <div className="text-sm text-muted-foreground"> | |
| {prompts.length} prompt{prompts.length !== 1 ? "s" : ""} found | |
| </div> | |
| {/* Prompts Grid */} | |
| {prompts.length > 0 ? ( | |
| <div className="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"> | |
| {prompts.map((prompt) => ( | |
| <PromptCard | |
| key={prompt.id} | |
| slug={prompt.slug} | |
| title={prompt.title} | |
| description={prompt.description} | |
| category={prompt.category} | |
| runs={prompt.totalRuns} | |
| stars={prompt.starsCount} | |
| remixes={prompt.remixesCount} | |
| badges={prompt.badges} | |
| creator={prompt.creator} | |
| /> | |
| ))} | |
| </div> | |
| ) : ( | |
| <div className="text-center py-16 text-muted-foreground"> | |
| <Sparkles className="h-12 w-12 mx-auto mb-4 opacity-20" /> | |
| <p className="text-lg font-medium mb-2">No prompts found</p> | |
| <p className="text-sm"> | |
| Try adjusting your search or filters | |
| </p> | |
| </div> | |
| )} | |
| {/* Load More */} | |
| {hasMore && prompts.length > 0 && ( | |
| <div className="flex justify-center pt-4"> | |
| <Button | |
| variant="outline" | |
| onClick={() => { | |
| const query = searchParams.get("q") || "" | |
| const filters: SearchFilters = { | |
| category: searchParams.get("category") || undefined, | |
| sortBy: (searchParams.get("sort") as any) || 'trending', | |
| dateRange: (searchParams.get("date") as any) || 'all', | |
| } | |
| fetchPrompts(query, filters, false) | |
| }} | |
| disabled={isLoading} | |
| className="gap-2" | |
| > | |
| {isLoading ? ( | |
| <> | |
| <Loader2 className="h-4 w-4 animate-spin" /> | |
| Loading... | |
| </> | |
| ) : ( | |
| "Load More" | |
| )} | |
| </Button> | |
| </div> | |
| )} | |
| </div> | |
| ) | |
| } | |