2
2
3
3
import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons' ;
4
4
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' ;
5
+ import { UserSearchViewDto } from '@shared/validation/user/dto/UserSearchView.dto' ;
6
+ import Image from 'next/image' ;
5
7
import { useSearchParams } from 'next/navigation' ;
6
- import { useEffect } from 'react' ;
8
+ import { useEffect , useState } from 'react' ;
7
9
8
10
import { useSearch } from './client/context/useSearch' ;
9
11
12
+ type UserCardProps = {
13
+ user : UserSearchViewDto ;
14
+ } ;
15
+
16
+ export const UserCard = ( { user } : UserCardProps ) => {
17
+ const { id, profileImage, songCount, username } = user ;
18
+
19
+ return (
20
+ < div className = 'max-w-sm p-6 bg-zinc-800 rounded-lg shadow-md hover:bg-zinc-750 transition-colors cursor-pointer' >
21
+ { /* Profile Image */ }
22
+ < div className = 'flex justify-center' >
23
+ < Image
24
+ src = { profileImage }
25
+ alt = { `Profile picture of ${ username } ` }
26
+ className = 'w-24 h-24 rounded-full'
27
+ width = { 96 }
28
+ height = { 96 }
29
+ />
30
+ </ div >
31
+
32
+ { /* Username */ }
33
+ < h2 className = 'mt-4 text-xl font-bold text-center text-zinc-100' >
34
+ { username }
35
+ </ h2 >
36
+
37
+ { /* Song Count */ }
38
+ < p className = 'mt-2 text-sm text-center text-zinc-400' >
39
+ { songCount } { songCount === 1 ? 'song' : 'songs' }
40
+ </ p >
41
+
42
+ { /* User ID (Optional) */ }
43
+ < p className = 'mt-2 text-xs text-center text-zinc-500' > ID: { id } </ p >
44
+ </ div >
45
+ ) ;
46
+ } ;
47
+
48
+ export const UserCardSkeleton = ( ) => {
49
+ return (
50
+ < div className = 'max-w-sm p-6 bg-zinc-800 rounded-lg shadow-md animate-pulse' >
51
+ { /* Profile Image Skeleton */ }
52
+ < div className = 'flex justify-center' >
53
+ < div className = 'w-24 h-24 bg-zinc-700 rounded-full' > </ div >
54
+ </ div >
55
+
56
+ { /* Username Skeleton */ }
57
+ < div className = 'mt-4 h-6 bg-zinc-700 rounded mx-auto w-3/4' > </ div >
58
+
59
+ { /* Song Count Skeleton */ }
60
+ < div className = 'mt-2 h-4 bg-zinc-700 rounded mx-auto w-1/2' > </ div >
61
+
62
+ { /* User ID Skeleton */ }
63
+ < div className = 'mt-2 h-3 bg-zinc-700 rounded mx-auto w-1/3' > </ div >
64
+ </ div >
65
+ ) ;
66
+ } ;
67
+
10
68
export const SearchPageComponent = ( ) => {
11
69
const searchParams = useSearchParams ( ) ;
12
- const { results, query, fetchSearchResults } = useSearch ( ) ;
70
+ const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
71
+
72
+ const { data, query, isLoading, limit, page, fetchSearchResults } =
73
+ useSearch ( ) ;
13
74
14
75
useEffect ( ( ) => {
15
76
const query = searchParams . get ( 'query' ) || '' ;
16
77
const page = searchParams . get ( 'page' ) || '1' ;
17
78
const limit = searchParams . get ( 'limit' ) || '20' ;
18
79
19
80
fetchSearchResults ( query , parseInt ( page ) , parseInt ( limit ) ) ;
20
- } , [ ] ) ;
81
+ setCurrentPage ( parseInt ( page ) ) ;
82
+ // eslint-disable-next-line react-hooks/exhaustive-deps
83
+ } , [ searchParams ] ) ;
84
+
85
+ const handlePageChange = ( newPage : number ) => {
86
+ const query = searchParams . get ( 'query' ) || '' ;
87
+ const limit = searchParams . get ( 'limit' ) || '20' ;
88
+
89
+ fetchSearchResults ( query , newPage , parseInt ( limit ) ) ;
90
+ setCurrentPage ( newPage ) ;
91
+ } ;
21
92
22
93
return (
23
94
< >
@@ -38,14 +109,44 @@ export const SearchPageComponent = () => {
38
109
</ h2 >
39
110
) }
40
111
41
- { results . length === 0 ? (
112
+ { /* Loading State */ }
113
+ { isLoading ? (
114
+ < div className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4' >
115
+ { Array . from ( { length : 6 } ) . map ( ( _ , i ) => (
116
+ < UserCardSkeleton key = { i } />
117
+ ) ) }
118
+ </ div >
119
+ ) : data . length === 0 ? (
42
120
< div className = 'text-center text-xl font-light' >
43
121
No results found. Try searching for something else.
44
122
</ div >
45
123
) : (
46
- < div className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4' >
47
- { JSON . stringify ( results , null , 2 ) }
48
- </ div >
124
+ < >
125
+ { /* User Cards */ }
126
+ < div className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4' >
127
+ { data . map ( ( user ) => (
128
+ < UserCard key = { user . id } user = { user } />
129
+ ) ) }
130
+ </ div >
131
+
132
+ { /* Pagination Controls */ }
133
+ < div className = 'flex justify-center gap-4 mt-8' >
134
+ < button
135
+ onClick = { ( ) => handlePageChange ( currentPage - 1 ) }
136
+ disabled = { currentPage === 1 }
137
+ className = 'px-4 py-2 bg-zinc-700 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed'
138
+ >
139
+ Previous
140
+ </ button >
141
+ < button
142
+ onClick = { ( ) => handlePageChange ( currentPage + 1 ) }
143
+ disabled = { data . length < limit }
144
+ className = 'px-4 py-2 bg-zinc-700 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed'
145
+ >
146
+ Next
147
+ </ button >
148
+ </ div >
149
+ </ >
49
150
) }
50
151
</ >
51
152
) ;
0 commit comments