Skip to content

Commit 22ff5c9

Browse files
committed
feat: implement SearchSongPage component for song search functionality with pagination and mock data handling
1 parent fc3b3ce commit 22ff5c9

File tree

1 file changed

+206
-0
lines changed
  • apps/frontend/src/app/(content)/search-song

1 file changed

+206
-0
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
'use client';
2+
3+
import {
4+
faEllipsis,
5+
faMagnifyingGlass,
6+
} from '@fortawesome/free-solid-svg-icons';
7+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8+
import { SongPreviewDtoType } from '@nbw/database';
9+
import { useSearchParams } from 'next/navigation';
10+
import { useEffect, useState } from 'react';
11+
12+
import LoadMoreButton from '@web/modules/browse/components/client/LoadMoreButton';
13+
import SongCard from '@web/modules/browse/components/SongCard';
14+
import SongCardGroup from '@web/modules/browse/components/SongCardGroup';
15+
16+
// Mock data for testing
17+
const mockSongs: SongPreviewDtoType[] = [
18+
{
19+
publicId: '1',
20+
uploader: {
21+
username: 'musicmaker',
22+
profileImage: '/img/note-block-pfp.jpg',
23+
},
24+
title: 'Beautiful Melody',
25+
description: 'A peaceful song for relaxation',
26+
originalAuthor: 'John Doe',
27+
duration: 180,
28+
noteCount: 150,
29+
thumbnailUrl: '/img/note-block-grayscale.png',
30+
createdAt: new Date('2024-01-15'),
31+
updatedAt: new Date('2024-01-15'),
32+
playCount: 1245,
33+
visibility: 'public',
34+
},
35+
{
36+
publicId: '2',
37+
uploader: {
38+
username: 'composer123',
39+
profileImage: '/img/note-block-pfp.jpg',
40+
},
41+
title: 'Epic Adventure Theme',
42+
description: 'An exciting soundtrack for your adventures',
43+
originalAuthor: 'Jane Smith',
44+
duration: 240,
45+
noteCount: 320,
46+
thumbnailUrl: '/img/note-block-grayscale.png',
47+
createdAt: new Date('2024-01-14'),
48+
updatedAt: new Date('2024-01-14'),
49+
playCount: 856,
50+
visibility: 'public',
51+
},
52+
{
53+
publicId: '3',
54+
uploader: {
55+
username: 'beatmaster',
56+
profileImage: '/img/note-block-pfp.jpg',
57+
},
58+
title: 'Minecraft Nostalgia',
59+
description: 'Classic minecraft-inspired music',
60+
originalAuthor: 'C418',
61+
duration: 195,
62+
noteCount: 280,
63+
thumbnailUrl: '/img/note-block-grayscale.png',
64+
createdAt: new Date('2024-01-13'),
65+
updatedAt: new Date('2024-01-13'),
66+
playCount: 2134,
67+
visibility: 'public',
68+
},
69+
];
70+
71+
const SearchSongPage = () => {
72+
const searchParams = useSearchParams();
73+
const query = searchParams.get('q') || '';
74+
const page = parseInt(searchParams.get('page') || '1');
75+
const limit = parseInt(searchParams.get('limit') || '20');
76+
77+
const [songs, setSongs] = useState<SongPreviewDtoType[]>([]);
78+
const [loading, setLoading] = useState(true);
79+
const [hasMore, setHasMore] = useState(true);
80+
const [currentPage, setCurrentPage] = useState(page);
81+
82+
// Mock search function
83+
const searchSongs = (searchQuery: string, pageNum: number) => {
84+
setLoading(true);
85+
86+
// Simulate API call delay
87+
setTimeout(() => {
88+
if (pageNum === 1) {
89+
// Filter mock songs based on query
90+
const filtered = mockSongs.filter(
91+
(song) =>
92+
song.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
93+
song.description
94+
.toLowerCase()
95+
.includes(searchQuery.toLowerCase()) ||
96+
song.originalAuthor
97+
.toLowerCase()
98+
.includes(searchQuery.toLowerCase()) ||
99+
song.uploader.username
100+
.toLowerCase()
101+
.includes(searchQuery.toLowerCase()),
102+
);
103+
setSongs(filtered);
104+
setHasMore(filtered.length >= limit);
105+
} else {
106+
// For pagination, just add duplicates with modified IDs for demo
107+
const additionalSongs = mockSongs.slice(0, 2).map((song, index) => ({
108+
...song,
109+
publicId: `${song.publicId}-page-${pageNum}-${index}`,
110+
title: `${song.title} (Page ${pageNum})`,
111+
}));
112+
setSongs((prev) => [...prev, ...additionalSongs]);
113+
setHasMore(pageNum < 3); // Mock: only show 3 pages max
114+
}
115+
setLoading(false);
116+
}, 500);
117+
};
118+
119+
const loadMore = () => {
120+
const nextPage = currentPage + 1;
121+
setCurrentPage(nextPage);
122+
searchSongs(query, nextPage);
123+
};
124+
125+
useEffect(() => {
126+
setCurrentPage(page);
127+
searchSongs(query, page);
128+
}, [query, page]);
129+
130+
if (loading && songs.length === 0) {
131+
return (
132+
<div className='container mx-auto px-4 py-8'>
133+
<div className='flex items-center gap-4 mb-6'>
134+
<FontAwesomeIcon
135+
icon={faMagnifyingGlass}
136+
className='text-2xl text-zinc-400'
137+
/>
138+
<h1 className='text-2xl font-bold'>Searching...</h1>
139+
</div>
140+
<SongCardGroup>
141+
{Array.from({ length: 6 }).map((_, i) => (
142+
<SongCard key={i} song={null} />
143+
))}
144+
</SongCardGroup>
145+
</div>
146+
);
147+
}
148+
149+
return (
150+
<div className='container mx-auto px-4 py-8'>
151+
{/* Search header */}
152+
<div className='flex items-center gap-4 mb-6'>
153+
<FontAwesomeIcon
154+
icon={faMagnifyingGlass}
155+
className='text-2xl text-zinc-400'
156+
/>
157+
<div>
158+
<h1 className='text-2xl font-bold'>Search Results</h1>
159+
{query && (
160+
<p className='text-zinc-400'>
161+
{songs.length > 0
162+
? `Found ${songs.length} songs for "${query}"`
163+
: `No songs found for "${query}"`}
164+
</p>
165+
)}
166+
</div>
167+
</div>
168+
169+
{/* Results */}
170+
{songs.length > 0 ? (
171+
<>
172+
<SongCardGroup>
173+
{songs.map((song, i) => (
174+
<SongCard key={`${song.publicId}-${i}`} song={song} />
175+
))}
176+
</SongCardGroup>
177+
178+
{/* Load more / End indicator */}
179+
<div className='flex flex-col w-full justify-between items-center mt-4'>
180+
{loading ? (
181+
<div className='text-zinc-400'>Loading more songs...</div>
182+
) : hasMore ? (
183+
<LoadMoreButton onClick={loadMore} />
184+
) : (
185+
<FontAwesomeIcon
186+
icon={faEllipsis}
187+
className='text-2xl text-zinc-500'
188+
/>
189+
)}
190+
</div>
191+
</>
192+
) : !loading ? (
193+
<div className='text-center py-12 text-zinc-400'>
194+
<FontAwesomeIcon icon={faMagnifyingGlass} className='text-4xl mb-4' />
195+
<h2 className='text-xl mb-2'>No songs found</h2>
196+
<p>
197+
Try adjusting your search terms or browse our featured songs
198+
instead.
199+
</p>
200+
</div>
201+
) : null}
202+
</div>
203+
);
204+
};
205+
206+
export default SearchSongPage;

0 commit comments

Comments
 (0)