157 lines
5.1 KiB
TypeScript
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>
|
|
)
|
|
}
|