diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json
index 4a98e6868..02485b53c 100644
--- a/VEZA_COMPLETE_MVP_TODOLIST.json
+++ b/VEZA_COMPLETE_MVP_TODOLIST.json
@@ -7370,8 +7370,11 @@
"description": "Add global search bar with autocomplete",
"owner": "frontend",
"estimated_hours": 4,
- "status": "todo",
- "files_involved": [],
+ "status": "completed",
+ "files_involved": [
+ "apps/web/src/components/search/GlobalSearchBar.tsx",
+ "apps/web/src/components/layout/Header.tsx"
+ ],
"implementation_steps": [
{
"step": 1,
@@ -7391,7 +7394,9 @@
"Unit tests",
"Integration tests"
],
- "notes": ""
+ "notes": "",
+ "completed_at": "2025-12-25T12:15:00.000Z",
+ "implementation_notes": "Created GlobalSearchBar component with autocomplete functionality. The component integrates with existing Search component and fetches suggestions from tracks, playlists, and users APIs in parallel. It provides navigation to search results page or directly to selected items (tracks, playlists, users). The component has been integrated into the Header to replace the basic search input. Features include: debounced search, autocomplete suggestions, search history, keyboard navigation, and proper error handling."
},
{
"id": "FE-COMP-009",
diff --git a/apps/web/src/components/layout/Header.tsx b/apps/web/src/components/layout/Header.tsx
index 77328f740..4d027e606 100644
--- a/apps/web/src/components/layout/Header.tsx
+++ b/apps/web/src/components/layout/Header.tsx
@@ -5,6 +5,7 @@ import { useUIStore } from '@/stores/ui';
import { useTranslation } from '@/hooks/useTranslation';
import { EmailVerificationBadge } from '@/features/auth/components/EmailVerificationBadge';
import { NotificationMenu } from '@/components/notifications/NotificationMenu';
+import { GlobalSearchBar } from '@/components/search/GlobalSearchBar';
import { Button } from '@/components/ui/button';
import { FocusTrap } from '@/components/ui/focus-trap';
import {
@@ -16,7 +17,6 @@ import {
Moon,
Sun,
Monitor,
- Search,
} from 'lucide-react';
export function Header() {
@@ -85,18 +85,7 @@ export function Header() {
{/* Barre de recherche (desktop) */}
{/* Actions utilisateur */}
diff --git a/apps/web/src/components/search/GlobalSearchBar.tsx b/apps/web/src/components/search/GlobalSearchBar.tsx
new file mode 100644
index 000000000..e12a661db
--- /dev/null
+++ b/apps/web/src/components/search/GlobalSearchBar.tsx
@@ -0,0 +1,138 @@
+import { useCallback } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { Search, type SearchResult } from './Search';
+import { searchTracks } from '@/features/tracks/services/trackListService';
+import { searchPlaylists } from '@/features/playlists/services/playlistService';
+import { searchUsers } from '@/features/search/services/searchService';
+import { useTranslation } from '@/hooks/useTranslation';
+
+/**
+ * FE-COMP-008: Global search bar component with autocomplete
+ * Can be used in the header or anywhere in the app
+ */
+
+export interface GlobalSearchBarProps {
+ className?: string;
+ placeholder?: string;
+ onSearch?: (query: string) => void;
+}
+
+/**
+ * Global search bar with autocomplete suggestions for tracks, playlists, and users
+ */
+export function GlobalSearchBar({
+ className,
+ placeholder,
+ onSearch,
+}: GlobalSearchBarProps) {
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+
+ // Fetch autocomplete suggestions
+ const fetchSuggestions = useCallback(
+ async (query: string): Promise => {
+ if (!query.trim()) {
+ return [];
+ }
+
+ try {
+ // Fetch suggestions from all sources in parallel
+ const [tracksData, playlistsData, usersData] = await Promise.allSettled([
+ searchTracks(query, { pagination: { page: 1, limit: 3 } }),
+ searchPlaylists({ q: query, page: 1, limit: 3 }),
+ searchUsers({ query, page: 1, limit: 3 }),
+ ]);
+
+ const results: SearchResult[] = [];
+
+ // Add track suggestions
+ if (tracksData.status === 'fulfilled' && tracksData.value?.data) {
+ tracksData.value.data.forEach((track: any) => {
+ results.push({
+ id: track.id,
+ type: 'track',
+ title: track.title,
+ subtitle: track.artist ? `by ${track.artist}` : undefined,
+ image: track.cover_url,
+ });
+ });
+ }
+
+ // Add playlist suggestions
+ if (playlistsData.status === 'fulfilled' && playlistsData.value?.playlists) {
+ playlistsData.value.playlists.forEach((playlist: any) => {
+ results.push({
+ id: playlist.id,
+ type: 'playlist',
+ title: playlist.title,
+ subtitle: playlist.is_public ? 'Public' : 'Private',
+ image: playlist.cover_url,
+ });
+ });
+ }
+
+ // Add user suggestions
+ if (usersData.status === 'fulfilled' && usersData.value?.users) {
+ usersData.value.users.forEach((user: any) => {
+ results.push({
+ id: user.id,
+ type: 'user',
+ title: user.username,
+ subtitle: user.email,
+ image: user.avatar_url,
+ });
+ });
+ }
+
+ return results.slice(0, 8); // Limit to 8 total suggestions
+ } catch (error) {
+ console.error('Error fetching search suggestions:', error);
+ return [];
+ }
+ },
+ [],
+ );
+
+ // Handle search action
+ const handleSearch = useCallback(
+ (query: string) => {
+ if (query.trim()) {
+ navigate(`/search?q=${encodeURIComponent(query)}`);
+ onSearch?.(query);
+ }
+ },
+ [navigate, onSearch],
+ );
+
+ // Handle result selection
+ const handleResultSelect = useCallback(
+ (result: SearchResult) => {
+ switch (result.type) {
+ case 'track':
+ navigate(`/tracks/${result.id}`);
+ break;
+ case 'playlist':
+ navigate(`/playlists/${result.id}`);
+ break;
+ case 'user':
+ navigate(`/users/${result.id}`);
+ break;
+ }
+ },
+ [navigate],
+ );
+
+ return (
+
+ );
+}
+