import { useState, useEffect, useRef, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMarkets } from '../hooks/useMarkets';
import { formatVolume, formatPrice, getMarketTitle } from '../utils/format';
const HISTORY_KEY = 'sz_search_history';
const MAX_HISTORY = 8;
const CATEGORIES = ['All', 'Sports', 'Politics', 'Crypto', 'Weather', 'Tech', 'Finance', 'Culture'];
function getHistory(): string[] {
try {
return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]');
} catch { return []; }
}
function saveHistory(term: string) {
const prev = getHistory().filter(h => h !== term);
const next = [term, ...prev].slice(0, MAX_HISTORY);
localStorage.setItem(HISTORY_KEY, JSON.stringify(next));
}
function removeHistory(term: string) {
const next = getHistory().filter(h => h !== term);
localStorage.setItem(HISTORY_KEY, JSON.stringify(next));
}
export default function SearchPage() {
const [query, setQuery] = useState('');
const [debouncedQuery, setDebouncedQuery] = useState('');
const [category, setCategory] = useState('All');
const [history, setHistory] = useState<string[]>(getHistory);
const [showHistory, setShowHistory] = useState(true);
const inputRef = useRef<HTMLInputElement>(null);
const navigate = useNavigate();
// Debounce search query (300ms)
useEffect(() => {
const timer = setTimeout(() => setDebouncedQuery(query), 300);
return () => clearTimeout(timer);
}, [query]);
const hasSearch = debouncedQuery.trim().length >= 2;
const { markets, loading } = useMarkets({
search: hasSearch ? debouncedQuery.trim() : undefined,
category,
sort: 'volume',
order: 'desc',
limit: 20,
offset: 0,
});
const handleSelect = useCallback((marketId: string) => {
if (query.trim()) {
saveHistory(query.trim());
setHistory(getHistory());
}
navigate(`/market/${marketId}`);
}, [query, navigate]);
const handleHistoryClick = useCallback((term: string) => {
setQuery(term);
setShowHistory(false);
}, []);
const handleRemoveHistory = useCallback((e: React.MouseEvent, term: string) => {
e.stopPropagation();
removeHistory(term);
setHistory(getHistory());
}, []);
const handleClear = useCallback(() => {
setQuery('');
setDebouncedQuery('');
setShowHistory(true);
inputRef.current?.focus();
}, []);
const handleClearAll = useCallback(() => {
localStorage.removeItem(HISTORY_KEY);
setHistory([]);
}, []);
// Show results or history
const showResults = hasSearch;
return (
<div className="sp-page">
{/* Search input */}
<div className="search-bar sp-bar">
<svg className="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="11" cy="11" r="8" />
<path d="M21 21l-4.35-4.35" />
</svg>
<input
ref={inputRef}
type="text"
placeholder="Search markets..."
value={query}
onChange={e => { setQuery(e.target.value); setShowHistory(false); }}
className="search-input"
autoFocus
/>
{query && (
<button className="search-clear" onClick={handleClear}>✕</button>
)}
</div>
{/* Category filter pills */}
<div className="sp-cats">
{CATEGORIES.map(cat => (
<button
key={cat}
className={`pill pill-sm ${category === cat ? 'pill-active' : ''}`}
onClick={() => { setCategory(cat); }}
>
{cat}
</button>
))}
</div>
{/* Search history (when no query) */}
{!showResults && showHistory && history.length > 0 && (
<div className="sp-history">
<div className="sp-history-head">
<span className="sp-history-label">Recent searches</span>
<button className="sp-history-clear" onClick={handleClearAll}>Clear all</button>
</div>
{history.map(h => (
<button key={h} className="sp-history-item" onClick={() => handleHistoryClick(h)}>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="14" height="14">
<circle cx="12" cy="12" r="10" />
<path d="M12 6v6l4 2" />
</svg>
<span>{h}</span>
<span className="sp-history-x" onClick={e => handleRemoveHistory(e, h)}>✕</span>
</button>
))}
</div>
)}
{/* Popular (when no query and no history) */}
{!showResults && (showHistory || history.length === 0) && (
<div className="sp-popular">
<span className="sp-history-label">Popular</span>
<div className="sp-popular-grid">
{['Bitcoin', 'Trump', 'Elections', 'AI', 'Ethereum', 'Fed Rate', 'Weather', 'FIFA'].map(q => (
<button key={q} className="sp-popular-btn" onClick={() => { setQuery(q); setShowHistory(false); }}>
{q}
</button>
))}
</div>
</div>
)}
{/* Inline results */}
{showResults && (
<div className="sp-results">
{loading ? (
<div className="sp-results-loading">
{Array.from({ length: 5 }, (_, i) => (
<div key={i} className="sp-result-skeleton">
<div className="skeleton-img" />
<div style={{ flex: 1 }}>
<div className="skeleton-text" style={{ width: `${60 + (i % 3) * 15}%`, height: 14 }} />
<div className="skeleton-text" style={{ width: 40, height: 11, marginTop: 6 }} />
</div>
</div>
))}
</div>
) : markets.length === 0 ? (
<div className="sp-empty">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" width="32" height="32">
<circle cx="11" cy="11" r="8" />
<path d="M21 21l-4.35-4.35" />
<path d="M8 11h6" />
</svg>
<p>No markets found for "{debouncedQuery}"</p>
</div>
) : (
<>
<div className="sp-results-count">{markets.length} result{markets.length !== 1 ? 's' : ''}</div>
{markets.map(m => {
const pct = m.yes_price * 100;
const priceColor = pct >= 70 ? 'var(--profit)' : pct <= 30 ? 'var(--loss)' : 'var(--text-primary)';
return (
<button key={m.id} className="sp-result" onClick={() => handleSelect(m.id)}>
{m.image_url && (
<img src={m.image_url} alt="" className="sp-result-img" loading="lazy"
onError={e => { (e.target as HTMLImageElement).style.display = 'none'; }} />
)}
<div className="sp-result-info">
<span className="sp-result-title">{getMarketTitle(m)}</span>
<span className="sp-result-meta">
<span className="sp-result-cat">{m.category}</span>
<span className="sp-result-vol">{formatVolume(m.volume_24h)}</span>
</span>
</div>
<span className="sp-result-price" style={{ color: priceColor }}>
{formatPrice(m.yes_price)}
</span>
</button>
);
})}
</>
)}
</div>
)}
</div>
);
}
📜 Git History
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...