T.ME/BIBIL_0DAY
CasperSecurity


Server : Apache/2
System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64
User : gositeme ( 1004)
PHP Version : 8.2.29
Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Directory :  /home/gositeme/domains/lavocat.ca/private_html/src/components/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/private_html/src/components/AdvancedSearch.tsx
import React, { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/router';
import { Search, Filter, X, Users, FileText, Building2, Gavel, Star, MapPin, Calendar, TrendingUp } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { useDebounce } from '@/hooks/useDebounce';

interface SearchResult {
  id: string;
  type: 'user' | 'case' | 'document' | 'business';
  displayName: string;
  displayDescription: string;
  url: string;
  relevance: number;
  createdAt: string;
  isVerified?: boolean;
  averageRating?: number;
  totalCases?: number;
  wonCases?: number;
  location?: string;
  legalArea?: string;
  jurisdiction?: string;
  urgencyLevel?: string;
  _count?: {
    followers?: number;
    supporters?: number;
    reviews?: number;
    offers?: number;
    comments?: number;
  };
}

interface SearchFacets {
  types: {
    users: number;
    cases: number;
    documents: number;
    businesses: number;
  };
  categories: Record<string, number>;
  jurisdictions: Record<string, number>;
  verified: {
    verified: number;
    unverified: number;
  };
}

interface AdvancedSearchProps {
  initialQuery?: string;
  className?: string;
  onResultClick?: (result: SearchResult) => void;
}

const AdvancedSearch: React.FC<AdvancedSearchProps> = ({
  initialQuery = '',
  className = '',
  onResultClick
}) => {
  const router = useRouter();
  const [query, setQuery] = useState(initialQuery);
  const [results, setResults] = useState<SearchResult[]>([]);
  const [facets, setFacets] = useState<SearchFacets | null>(null);
  const [loading, setLoading] = useState(false);
  const [showFilters, setShowFilters] = useState(false);
  const [filters, setFilters] = useState({
    type: 'all',
    category: 'all',
    jurisdiction: 'all',
    verified: 'all',
    sortBy: 'relevance'
  });
  const [suggestions, setSuggestions] = useState<string[]>([]);
  const [showSuggestions, setShowSuggestions] = useState(false);
  const [selectedSuggestion, setSelectedSuggestion] = useState(-1);
  const [searchStart, setSearchStart] = useState<number | null>(null);

  const debouncedQuery = useDebounce(query, 300);

  // Search suggestions
  const searchSuggestions = [
    'criminal law', 'family law', 'immigration', 'human rights',
    'corporate law', 'civil litigation', 'real estate', 'tax law',
    'Montreal', 'Quebec', 'Toronto', 'Vancouver',
    'urgent cases', 'pro bono', 'verified lawyers', 'top rated'
  ];

  useEffect(() => {
    if (debouncedQuery.length >= 2) {
      performSearch();
      generateSuggestions();
    } else {
      setResults([]);
      setFacets(null);
      setSuggestions([]);
    }
  }, [debouncedQuery, filters]);

  const performSearch = async () => {
    if (!debouncedQuery.trim()) return;

    setLoading(true);
    setSearchStart(Date.now());
    try {
      const params = new URLSearchParams({
        q: debouncedQuery,
        ...filters,
        page: '1',
        limit: '50'
      });

      const response = await fetch(`/api/search/global?${params}`);
      if (response.ok) {
        const data = await response.json();
        setResults(data.results || []);
        setFacets(data.facets || null);
        // Analytics: track search event
        if (typeof window !== 'undefined') {
          fetch('/api/search/analytics', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              query: debouncedQuery,
              filters,
              resultCount: (data.results || []).length,
              searchTime: searchStart ? Date.now() - searchStart : 0
            })
          }).catch(() => {});
        }
      }
    } catch (error) {
      console.error('Search error:', error);
    } finally {
      setLoading(false);
      setSearchStart(null);
    }
  };

  const generateSuggestions = () => {
    const filtered = searchSuggestions.filter(suggestion =>
      suggestion.toLowerCase().includes(debouncedQuery.toLowerCase())
    );
    setSuggestions(filtered.slice(0, 5));
  };

  const handleResultClick = (result: SearchResult) => {
    // Analytics: track result click
    if (typeof window !== 'undefined') {
      fetch('/api/search/analytics', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          query,
          filters,
          resultCount: results.length,
          clickedResult: result.id,
          searchTime: 0
        })
      }).catch(() => {});
    }
    if (onResultClick) {
      onResultClick(result);
    } else {
      router.push(result.url);
    }
  };

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

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      setSelectedSuggestion(prev => 
        prev < suggestions.length - 1 ? prev + 1 : prev
      );
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      setSelectedSuggestion(prev => prev > 0 ? prev - 1 : -1);
    } else if (e.key === 'Enter' && selectedSuggestion >= 0) {
      e.preventDefault();
      handleSuggestionClick(suggestions[selectedSuggestion]);
    } else if (e.key === 'Escape') {
      setShowSuggestions(false);
      setSelectedSuggestion(-1);
    }
  };

  const getTypeIcon = (type: string) => {
    switch (type) {
      case 'user': return <Users className="h-4 w-4" />;
      case 'case': return <Gavel className="h-4 w-4" />;
      case 'document': return <FileText className="h-4 w-4" />;
      case 'business': return <Building2 className="h-4 w-4" />;
      default: return <Search className="h-4 w-4" />;
    }
  };

  const getTypeColor = (type: string) => {
    switch (type) {
      case 'user': return 'bg-blue-100 text-blue-800';
      case 'case': return 'bg-green-100 text-green-800';
      case 'document': return 'bg-purple-100 text-purple-800';
      case 'business': return 'bg-orange-100 text-orange-800';
      default: return 'bg-gray-100 text-gray-800';
    }
  };

  const getTypeLabel = (type: string) => {
    switch (type) {
      case 'user': return 'Professional';
      case 'case': return 'Case';
      case 'document': return 'Document';
      case 'business': return 'Business';
      default: return type;
    }
  };

  return (
    <div className={`relative ${className}`}>
      {/* Search Input */}
      <div className="relative">
        <Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-gray-400" />
        <input
          type="text"
          placeholder="Search professionals, cases, documents, businesses..."
          value={query}
          onChange={(e) => {
            setQuery(e.target.value);
            setShowSuggestions(true);
            setSelectedSuggestion(-1);
          }}
          onKeyDown={handleKeyDown}
          onFocus={() => setShowSuggestions(true)}
          className="w-full pl-12 pr-20 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
        />
        <div className="absolute right-2 top-1/2 transform -translate-y-1/2 flex items-center space-x-2">
          {loading && (
            <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600"></div>
          )}
          <button
            onClick={() => setShowFilters(!showFilters)}
            className={`p-1 rounded ${showFilters ? 'bg-blue-100 text-blue-600' : 'text-gray-400 hover:text-gray-600'}`}
          >
            <Filter className="h-4 w-4" />
          </button>
          {query && (
            <button
              onClick={() => setQuery('')}
              className="p-1 text-gray-400 hover:text-gray-600"
            >
              <X className="h-4 w-4" />
            </button>
          )}
        </div>
      </div>

      {/* Search Suggestions */}
      <AnimatePresence>
        {showSuggestions && suggestions.length > 0 && (
          <motion.div
            initial={{ opacity: 0, y: -10 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -10 }}
            className="absolute top-full left-0 right-0 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50"
          >
            {suggestions.map((suggestion, index) => (
              <button
                key={suggestion}
                onClick={() => handleSuggestionClick(suggestion)}
                className={`w-full px-4 py-2 text-left hover:bg-gray-50 flex items-center space-x-2 ${
                  index === selectedSuggestion ? 'bg-blue-50' : ''
                }`}
              >
                <Search className="h-4 w-4 text-gray-400" />
                <span>{suggestion}</span>
              </button>
            ))}
          </motion.div>
        )}
      </AnimatePresence>

      {/* Advanced Filters */}
      <AnimatePresence>
        {showFilters && (
          <motion.div
            initial={{ opacity: 0, height: 0 }}
            animate={{ opacity: 1, height: 'auto' }}
            exit={{ opacity: 0, height: 0 }}
            className="mt-4 bg-white border border-gray-200 rounded-lg p-4"
          >
            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
              {/* Type Filter */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Type
                </label>
                <select
                  value={filters.type}
                  onChange={(e) => setFilters(prev => ({ ...prev, type: e.target.value }))}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
                >
                  <option value="all">All Types</option>
                  <option value="users">Professionals</option>
                  <option value="cases">Cases</option>
                  <option value="documents">Documents</option>
                  <option value="businesses">Businesses</option>
                </select>
              </div>

              {/* Category Filter */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Category
                </label>
                <select
                  value={filters.category}
                  onChange={(e) => setFilters(prev => ({ ...prev, category: e.target.value }))}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
                >
                  <option value="all">All Categories</option>
                  <option value="criminal">Criminal Law</option>
                  <option value="civil">Civil Law</option>
                  <option value="family">Family Law</option>
                  <option value="immigration">Immigration</option>
                  <option value="corporate">Corporate Law</option>
                  <option value="human_rights">Human Rights</option>
                </select>
              </div>

              {/* Jurisdiction Filter */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Jurisdiction
                </label>
                <select
                  value={filters.jurisdiction}
                  onChange={(e) => setFilters(prev => ({ ...prev, jurisdiction: e.target.value }))}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
                >
                  <option value="all">All Jurisdictions</option>
                  <option value="quebec">Quebec</option>
                  <option value="ontario">Ontario</option>
                  <option value="british_columbia">British Columbia</option>
                  <option value="alberta">Alberta</option>
                  <option value="federal">Federal</option>
                </select>
              </div>

              {/* Verification Filter */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Verification
                </label>
                <select
                  value={filters.verified}
                  onChange={(e) => setFilters(prev => ({ ...prev, verified: e.target.value }))}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
                >
                  <option value="all">All</option>
                  <option value="verified">Verified Only</option>
                  <option value="unverified">Unverified</option>
                </select>
              </div>

              {/* Sort By */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Sort By
                </label>
                <select
                  value={filters.sortBy}
                  onChange={(e) => setFilters(prev => ({ ...prev, sortBy: e.target.value }))}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
                >
                  <option value="relevance">Relevance</option>
                  <option value="newest">Newest</option>
                  <option value="popular">Most Popular</option>
                </select>
              </div>
            </div>
          </motion.div>
        )}
      </AnimatePresence>

      {/* Search Results */}
      {results.length > 0 && (
        <div className="mt-6">
          {/* Results Summary */}
          <div className="flex items-center justify-between mb-4">
            <div className="text-sm text-gray-600">
              {results.length} results for "{query}"
            </div>
            {facets && (
              <div className="flex items-center space-x-4 text-sm text-gray-500">
                {Object.entries(facets.types).map(([type, count]) => (
                  count > 0 && (
                    <span key={type} className="flex items-center space-x-1">
                      {getTypeIcon(type)}
                      <span>{count} {getTypeLabel(type)}s</span>
                    </span>
                  )
                ))}
              </div>
            )}
          </div>

          {/* Results List */}
          <div className="space-y-3">
            {results.map((result) => (
              <motion.div
                key={`${result.type}-${result.id}`}
                initial={{ opacity: 0, y: 20 }}
                animate={{ opacity: 1, y: 0 }}
                className="bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow cursor-pointer"
                onClick={() => handleResultClick(result)}
              >
                <div className="flex items-start space-x-3">
                  <div className={`p-2 rounded-lg ${getTypeColor(result.type)}`}>
                    {getTypeIcon(result.type)}
                  </div>
                  
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center space-x-2 mb-1">
                      <h3 className="text-lg font-semibold text-gray-900 truncate">
                        {result.displayName}
                      </h3>
                      {result.isVerified && (
                        <span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">
                          Verified
                        </span>
                      )}
                      <span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${getTypeColor(result.type)}`}>
                        {getTypeLabel(result.type)}
                      </span>
                    </div>
                    
                    <p className="text-gray-600 text-sm mb-2 line-clamp-2">
                      {result.displayDescription}
                    </p>
                    
                    <div className="flex items-center space-x-4 text-xs text-gray-500">
                      {result.location && (
                        <span className="flex items-center space-x-1">
                          <MapPin className="h-3 w-3" />
                          <span>{result.location}</span>
                        </span>
                      )}
                      
                      {result.averageRating && (
                        <span className="flex items-center space-x-1">
                          <Star className="h-3 w-3" />
                          <span>{result.averageRating.toFixed(1)}</span>
                        </span>
                      )}
                      
                      {result.totalCases && (
                        <span className="flex items-center space-x-1">
                          <Gavel className="h-3 w-3" />
                          <span>{result.totalCases} cases</span>
                        </span>
                      )}
                      
                      {result._count?.followers && (
                        <span className="flex items-center space-x-1">
                          <Users className="h-3 w-3" />
                          <span>{result._count.followers} followers</span>
                        </span>
                      )}
                      
                      {result._count?.supporters && (
                        <span className="flex items-center space-x-1">
                          <TrendingUp className="h-3 w-3" />
                          <span>{result._count.supporters} supporters</span>
                        </span>
                      )}
                      
                      <span className="flex items-center space-x-1">
                        <Calendar className="h-3 w-3" />
                        <span>{new Date(result.createdAt).toLocaleDateString()}</span>
                      </span>
                    </div>
                  </div>
                  
                  <div className="text-right">
                    <div className="text-xs text-gray-400">
                      {Math.round(result.relevance)}% match
                    </div>
                  </div>
                </div>
              </motion.div>
            ))}
          </div>
        </div>
      )}

      {/* No Results */}
      {!loading && query.length >= 2 && results.length === 0 && (
        <div className="mt-6 text-center py-8">
          <Search className="h-12 w-12 text-gray-300 mx-auto mb-4" />
          <h3 className="text-lg font-medium text-gray-900 mb-2">No results found</h3>
          <p className="text-gray-600">
            Try adjusting your search terms or filters to find what you're looking for.
          </p>
        </div>
      )}
    </div>
  );
};

export default AdvancedSearch; 

CasperSecurity Mini