story-research-zapwall/components/ArticleFilters.tsx
2025-12-22 09:48:57 +01:00

157 lines
5.1 KiB
TypeScript

import type { Article } from '@/types/nostr'
export type SortOption = 'newest' | 'oldest' | 'price-low' | 'price-high'
export interface ArticleFilters {
authorPubkey: string | null
minPrice: number | null
maxPrice: number | null
sortBy: SortOption
}
interface ArticleFiltersProps {
filters: ArticleFilters
onFiltersChange: (filters: ArticleFilters) => void
articles: Article[]
}
export function ArticleFiltersComponent({
filters,
onFiltersChange,
articles,
}: ArticleFiltersProps) {
// Get unique authors from articles
const authors = Array.from(
new Map(articles.map((a) => [a.pubkey, a.pubkey])).values()
)
// Get price range from articles
const prices = articles.map((a) => a.zapAmount).sort((a, b) => a - b)
const minAvailablePrice = prices[0] || 0
const maxAvailablePrice = prices[prices.length - 1] || 1000
const handleAuthorChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value === '' ? null : e.target.value
onFiltersChange({ ...filters, authorPubkey: value })
}
const handleMinPriceChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value === '' ? null : parseInt(e.target.value, 10)
onFiltersChange({ ...filters, minPrice: value })
}
const handleMaxPriceChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value === '' ? null : parseInt(e.target.value, 10)
onFiltersChange({ ...filters, maxPrice: value })
}
const handleSortChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
onFiltersChange({ ...filters, sortBy: e.target.value as SortOption })
}
const handleClearFilters = () => {
onFiltersChange({
authorPubkey: null,
minPrice: null,
maxPrice: null,
sortBy: 'newest',
})
}
const hasActiveFilters =
filters.authorPubkey !== null ||
filters.minPrice !== null ||
filters.maxPrice !== null ||
filters.sortBy !== 'newest'
return (
<div className="bg-white border border-gray-200 rounded-lg p-4 mb-6">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold text-gray-900">Filters & Sort</h3>
{hasActiveFilters && (
<button
onClick={handleClearFilters}
className="text-sm text-blue-600 hover:text-blue-700 font-medium"
>
Clear all
</button>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Author filter */}
<div>
<label htmlFor="author-filter" className="block text-sm font-medium text-gray-700 mb-1">
Author
</label>
<select
id="author-filter"
value={filters.authorPubkey || ''}
onChange={handleAuthorChange}
className="block w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="">All authors</option>
{authors.map((pubkey) => (
<option key={pubkey} value={pubkey}>
{pubkey.substring(0, 16)}...
</option>
))}
</select>
</div>
{/* Min price filter */}
<div>
<label htmlFor="min-price" className="block text-sm font-medium text-gray-700 mb-1">
Min price (sats)
</label>
<input
id="min-price"
type="number"
min={minAvailablePrice}
max={maxAvailablePrice}
value={filters.minPrice ?? ''}
onChange={handleMinPriceChange}
placeholder={`Min: ${minAvailablePrice}`}
className="block w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
{/* Max price filter */}
<div>
<label htmlFor="max-price" className="block text-sm font-medium text-gray-700 mb-1">
Max price (sats)
</label>
<input
id="max-price"
type="number"
min={minAvailablePrice}
max={maxAvailablePrice}
value={filters.maxPrice ?? ''}
onChange={handleMaxPriceChange}
placeholder={`Max: ${maxAvailablePrice}`}
className="block w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
{/* Sort */}
<div>
<label htmlFor="sort" className="block text-sm font-medium text-gray-700 mb-1">
Sort by
</label>
<select
id="sort"
value={filters.sortBy}
onChange={handleSortChange}
className="block w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="newest">Newest first</option>
<option value="oldest">Oldest first</option>
<option value="price-low">Price: Low to High</option>
<option value="price-high">Price: High to Low</option>
</select>
</div>
</div>
</div>
)
}