← Back
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...