@@ -77,6 +85,10 @@ export const OrgMenu = (props: any) => {
}`}
>
+ {/* Mobile Search */}
+
+
+
diff --git a/apps/web/components/Objects/Search/SearchBar.tsx b/apps/web/components/Objects/Search/SearchBar.tsx
new file mode 100644
index 00000000..a76c0c1e
--- /dev/null
+++ b/apps/web/components/Objects/Search/SearchBar.tsx
@@ -0,0 +1,147 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Search } 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;
+ authors: Array<{
+ first_name: string;
+ last_name: string;
+ avatar_image: string;
+ }>;
+}
+
+interface SearchBarProps {
+ orgslug: string;
+ className?: string;
+ isMobile?: boolean;
+}
+
+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 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([]);
+ return;
+ }
+
+ setIsLoading(true);
+ try {
+ const results = await searchOrgCourses(
+ orgslug,
+ debouncedSearch,
+ 1,
+ 5,
+ null,
+ session?.data?.tokens?.access_token
+ );
+ setCourses(results);
+ } catch (error) {
+ console.error('Error searching courses:', error);
+ setCourses([]);
+ }
+ setIsLoading(false);
+ };
+ fetchCourses();
+ }, [debouncedSearch, orgslug, session?.data?.tokens?.access_token]);
+
+ const handleSearchFocus = () => {
+ if (searchQuery.trim().length > 0) {
+ setShowResults(true);
+ }
+ };
+
+ return (
+
+
+ {
+ setSearchQuery(e.target.value);
+ setShowResults(true);
+ }}
+ onFocus={handleSearchFocus}
+ placeholder="Search courses..."
+ className="w-full h-9 pl-10 pr-4 rounded-lg border border-gray-200 bg-white/50 focus:outline-none focus:ring-2 focus:ring-gray-200 focus:border-transparent text-sm placeholder:text-gray-400"
+ />
+
+
+
+ {showResults && (searchQuery.trim().length > 0 || isLoading) && (
+
+ {isLoading ? (
+
+ ) : courses.length > 0 ? (
+
+ {courses.map((course) => (
+
setShowResults(false)}
+ >
+
+ {course.thumbnail_image && (
+

+ )}
+
+
{course.name}
+
{course.description}
+ {course.authors && course.authors[0] && (
+
+ by {course.authors[0].first_name} {course.authors[0].last_name}
+
+ )}
+
+
+
+ ))}
+
+ ) : (
+
+ No courses found
+
+ )}
+
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/apps/web/hooks/useDebounce.ts b/apps/web/hooks/useDebounce.ts
new file mode 100644
index 00000000..76324598
--- /dev/null
+++ b/apps/web/hooks/useDebounce.ts
@@ -0,0 +1,17 @@
+import { useState, useEffect } from 'react';
+
+export function useDebounce(value: T, delay: number): T {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+}
\ No newline at end of file
diff --git a/apps/web/services/courses/courses.ts b/apps/web/services/courses/courses.ts
index ba6a1e90..e9796148 100644
--- a/apps/web/services/courses/courses.ts
+++ b/apps/web/services/courses/courses.ts
@@ -24,6 +24,22 @@ export async function getOrgCourses(
return res
}
+export async function searchOrgCourses(
+ org_slug: string,
+ query: string,
+ page: number = 1,
+ limit: number = 10,
+ next: any,
+ access_token?: any
+) {
+ const result: any = await fetch(
+ `${getAPIUrl()}courses/org_slug/${org_slug}/search?query=${encodeURIComponent(query)}&page=${page}&limit=${limit}`,
+ RequestBodyWithAuthHeader('GET', null, next, access_token)
+ )
+ const res = await errorHandling(result)
+ return res
+}
+
export async function getCourseMetadata(
course_uuid: any,
next: any,