open-prompt / src /components /explore /explore-client.tsx
GitHub Action
Automated sync to Hugging Face
bcce530
"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>
)
}