Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/app/components/search/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ export const SearchBar: React.FC<SearchBarProps> = ({ className, isExpanded = fa
)}
/>

{query && (
{isLoading && query && (
<div className="h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin shrink-0" />
)}

{query && !isLoading && (
<button
onClick={handleClear}
className="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 shrink-0 transition-colors"
Expand Down
87 changes: 49 additions & 38 deletions src/app/hooks/useSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState, useCallback, useEffect } from 'react';
import { useState, useCallback, useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';

export interface SearchResult {
Expand Down Expand Up @@ -118,6 +118,8 @@ const MOCK_DATA = {
],
};

const DEBOUNCE_MS = 300;

export const useSearch = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState<CategorizedResults>({
Expand All @@ -128,6 +130,7 @@ export const useSearch = () => {
const [isLoading, setIsLoading] = useState(false);
const [searchHistory, setSearchHistory] = useState<string[]>([]);
const [isOpen, setIsOpen] = useState(false);
const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const router = useRouter();

// Load search history from localStorage
Expand All @@ -142,49 +145,57 @@ export const useSearch = () => {
}
}, []);

// Perform search
const search = useCallback((searchQuery: string) => {
setQuery(searchQuery);
setIsLoading(true);
// Execute the actual filtering (called after debounce)
const executeSearch = useCallback((searchQuery: string) => {
if (!searchQuery.trim()) {
setResults({ courses: [], instructors: [], topics: [] });
setIsLoading(false);
return;
}

const lowerQuery = searchQuery.toLowerCase();

setResults({
courses: MOCK_DATA.courses
.filter(
(c) =>
c.title.toLowerCase().includes(lowerQuery) ||
c.instructor?.toLowerCase().includes(lowerQuery),
)
.slice(0, 3),
instructors: MOCK_DATA.instructors
.filter((i) => i.title.toLowerCase().includes(lowerQuery))
.slice(0, 3),
topics: MOCK_DATA.topics
.filter(
(t) =>
t.title.toLowerCase().includes(lowerQuery) ||
t.description?.toLowerCase().includes(lowerQuery),
)
.slice(0, 3),
});

setIsLoading(false);
}, []);

// Debounced search: set loading immediately, delay execution
const search = useCallback(
(searchQuery: string) => {
setQuery(searchQuery);

if (debounceTimer.current) clearTimeout(debounceTimer.current);

// Simulate API delay
const timer = setTimeout(() => {
if (!searchQuery.trim()) {
setResults({ courses: [], instructors: [], topics: [] });
setIsLoading(false);
setResults({ courses: [], instructors: [], topics: [] });
return;
}

const lowerQuery = searchQuery.toLowerCase();

// Filter results from mock data
const filteredCourses = MOCK_DATA.courses.filter(
(course) =>
course.title.toLowerCase().includes(lowerQuery) ||
course.instructor?.toLowerCase().includes(lowerQuery),
);

const filteredInstructors = MOCK_DATA.instructors.filter((instructor) =>
instructor.title.toLowerCase().includes(lowerQuery),
);

const filteredTopics = MOCK_DATA.topics.filter(
(topic) =>
topic.title.toLowerCase().includes(lowerQuery) ||
topic.description?.toLowerCase().includes(lowerQuery),
);

setResults({
courses: filteredCourses.slice(0, 3),
instructors: filteredInstructors.slice(0, 3),
topics: filteredTopics.slice(0, 3),
});

setIsLoading(false);
}, 300);

return () => clearTimeout(timer);
}, []);
setIsLoading(true);
debounceTimer.current = setTimeout(() => executeSearch(searchQuery), DEBOUNCE_MS);
},
[executeSearch],
);

// Add to search history
const addToHistory = useCallback(
Expand Down
Loading