← back to Wine Finder Next

components/mobile/MobileSearchBar.tsx

191 lines

'use client'

import { useState, useCallback, useEffect, useRef } from 'react';
import { Wine } from '@/lib/types/wine';

interface MobileSearchBarProps {
  onSearch: (query: string, filters?: any) => void;
  onResults?: (wines: Wine[]) => void;
  placeholder?: string;
}

export default function MobileSearchBar({
  onSearch,
  onResults,
  placeholder = "Search 500,000+ wines..."
}: MobileSearchBarProps) {
  const [query, setQuery] = useState('');
  const [searching, setSearching] = useState(false);
  const [suggestions, setSuggestions] = useState<string[]>([]);
  const [showSuggestions, setShowSuggestions] = useState(false);
  const debounceTimer = useRef<NodeJS.Timeout | undefined>(undefined);

  // Debounced search suggestions
  const loadSuggestions = useCallback(async (searchQuery: string) => {
    if (!searchQuery || searchQuery.length < 2) {
      setSuggestions([]);
      return;
    }

    try {
      // Common wine searches (in production, fetch from API)
      const commonSearches = [
        'Cabernet Sauvignon',
        'Pinot Noir',
        'Chardonnay',
        'Merlot',
        'Sauvignon Blanc',
        'Syrah',
        'Zinfandel',
        'Malbec',
        'Riesling',
        'Bordeaux',
        'Burgundy',
        'Napa Valley',
        'Barolo',
        'Champagne'
      ];

      const filtered = commonSearches.filter(s =>
        s.toLowerCase().includes(searchQuery.toLowerCase())
      ).slice(0, 5);

      setSuggestions(filtered);
    } catch (error) {
      console.error('Failed to load suggestions:', error);
    }
  }, []);

  // Debounce search input
  useEffect(() => {
    if (debounceTimer.current) {
      clearTimeout(debounceTimer.current);
    }

    debounceTimer.current = setTimeout(() => {
      loadSuggestions(query);
    }, 300);

    return () => {
      if (debounceTimer.current) {
        clearTimeout(debounceTimer.current);
      }
    };
  }, [query, loadSuggestions]);

  const handleSearch = async (searchQuery: string) => {
    if (!searchQuery.trim()) return;

    setSearching(true);
    setShowSuggestions(false);

    try {
      onSearch(searchQuery);

      if (onResults) {
        const response = await fetch(
          `/api/search?q=${encodeURIComponent(searchQuery)}`
        );
        const data = await response.json();
        onResults(data.wines || []);
      }
    } catch (error) {
      console.error('Search error:', error);
    } finally {
      setSearching(false);
    }
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    handleSearch(query);
  };

  const selectSuggestion = (suggestion: string) => {
    setQuery(suggestion);
    setShowSuggestions(false);
    handleSearch(suggestion);
  };

  return (
    <div className="relative w-full">
      <form onSubmit={handleSubmit} className="relative">
        <div className="relative flex items-center">
          {/* Search Icon */}
          <div className="absolute left-4 text-gray-400 text-xl">
            🔍
          </div>

          {/* Input */}
          <input
            type="text"
            value={query}
            onChange={(e) => {
              setQuery(e.target.value);
              setShowSuggestions(true);
            }}
            onFocus={() => setShowSuggestions(true)}
            placeholder={placeholder}
            className="w-full pl-12 pr-24 py-4 text-base bg-white border-2 border-gray-300 rounded-2xl focus:border-purple-500 focus:outline-none shadow-lg transition-all"
            style={{ fontSize: '16px' }} // Prevent iOS zoom
            autoComplete="off"
            disabled={searching}
          />

          {/* Clear Button */}
          {query && (
            <button
              type="button"
              onClick={() => {
                setQuery('');
                setSuggestions([]);
              }}
              className="absolute right-20 text-gray-400 hover:text-gray-600 text-xl"
            >
              ✕
            </button>
          )}

          {/* Search Button */}
          <button
            type="submit"
            disabled={searching || !query.trim()}
            className="absolute right-2 px-4 py-2 bg-gradient-to-r from-purple-600 to-pink-600 text-white rounded-xl font-bold disabled:opacity-50 disabled:cursor-not-allowed hover:from-purple-700 hover:to-pink-700 transition-all"
          >
            {searching ? (
              <div className="animate-spin rounded-full h-5 w-5 border-2 border-white border-t-transparent"></div>
            ) : (
              'Go'
            )}
          </button>
        </div>
      </form>

      {/* Suggestions Dropdown */}
      {showSuggestions && suggestions.length > 0 && (
        <>
          {/* Backdrop to close suggestions */}
          <div
            className="fixed inset-0 z-10"
            onClick={() => setShowSuggestions(false)}
          />

          {/* Suggestions List */}
          <div className="absolute top-full left-0 right-0 mt-2 bg-white border-2 border-gray-200 rounded-2xl shadow-xl z-20 max-h-64 overflow-y-auto">
            {suggestions.map((suggestion, index) => (
              <button
                key={index}
                onClick={() => selectSuggestion(suggestion)}
                className="w-full text-left px-4 py-3 hover:bg-purple-50 transition-colors border-b border-gray-100 last:border-0 flex items-center gap-3"
              >
                <span className="text-gray-400">🔍</span>
                <span className="font-medium text-gray-900">{suggestion}</span>
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
}