import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import { Search, ArrowRight, Sparkles, Book, GraduationCap, ArrowUpRight, TextSearch, ScanSearch } from 'lucide-react'; import { searchOrgCourses } from '@services/courses/courses'; import { useLHSession } from '@components/Contexts/LHSessionContext'; import Link from 'next/link'; import { getCourseThumbnailMediaDirectory } from '@services/media/media'; import { useDebounce } from '@/hooks/useDebounce'; import { useOrg } from '@components/Contexts/OrgContext'; import { getUriWithOrg } from '@services/config/config'; import { removeCoursePrefix } from '../Thumbnails/CourseThumbnail'; interface Course { name: string; description: string; thumbnail_image: string; course_uuid: string; tags?: string[]; authors: Array<{ first_name: string; last_name: string; avatar_image: string; }>; } interface SearchBarProps { orgslug: string; className?: string; isMobile?: boolean; } const CourseResultsSkeleton = () => (
{[1, 2].map((i) => (
))}
); export const SearchBar: React.FC = ({ orgslug, className = '', isMobile = false, }) => { const org = useOrg() as any; const [searchQuery, setSearchQuery] = useState(''); const [courses, setCourses] = useState([]); const [isLoading, setIsLoading] = useState(false); const [showResults, setShowResults] = useState(false); const searchRef = useRef(null); const session = useLHSession() as any; const [isInitialLoad, setIsInitialLoad] = useState(true); // Debounce the search query value const debouncedSearch = useDebounce(searchQuery, 300); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (searchRef.current && !searchRef.current.contains(event.target as Node)) { setShowResults(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); useEffect(() => { const fetchCourses = async () => { if (debouncedSearch.trim().length === 0) { setCourses([]); setIsLoading(false); return; } setIsLoading(true); try { const results = await searchOrgCourses( orgslug, debouncedSearch, 1, 3, null, session?.data?.tokens?.access_token ); setCourses(results); } catch (error) { console.error('Error searching courses:', error); setCourses([]); } setIsLoading(false); setIsInitialLoad(false); }; fetchCourses(); }, [debouncedSearch, orgslug, session?.data?.tokens?.access_token]); const MemoizedEmptyState = useMemo(() => { if (!searchQuery.trim()) { return (

Discover Your Next Learning Journey

Start typing to search through available content

); } return null; }, [searchQuery]); const searchTerms = useMemo(() => [ { term: searchQuery, type: 'exact', icon: }, { term: `${searchQuery} courses`, type: 'courses', icon: }, { term: `${searchQuery} collections`, type: 'collections', icon: }, ], [searchQuery]); const MemoizedSearchSuggestions = useMemo(() => { if (searchQuery.trim()) { return (
Search suggestions
{searchTerms.map(({ term, type, icon }) => (
{icon} {term}
))}
); } return null; }, [searchQuery, searchTerms, orgslug]); const MemoizedCourseResults = useMemo(() => { if (!courses.length) return null; return (
Quick Results
{courses.map((course) => (
{course.thumbnail_image ? ( {course.name} ) : (
)}

{course.name}

Course

{course.description}

))}
); }, [courses, orgslug, org?.org_uuid]); const handleSearchChange = useCallback((e: React.ChangeEvent) => { setSearchQuery(e.target.value); setShowResults(true); }, []); return (
setShowResults(true)} placeholder="Search courses, users, collections..." className="w-full h-9 pl-11 pr-4 rounded-xl nice-shadow bg-white focus:outline-none focus:ring-1 focus:ring-black/5 focus:border-black/20 text-sm placeholder:text-black/40 transition-all" />
{(!searchQuery.trim() || isInitialLoad) ? ( MemoizedEmptyState ) : ( <> {MemoizedSearchSuggestions} {isLoading ? ( ) : ( <> {MemoizedCourseResults} {(courses.length > 0 || searchQuery.trim()) && ( View all results )} )} )}
); };