@@ -2,12 +2,19 @@ import { JSX } from "preact";
2
2
import { Signal , useSignalEffect } from "@preact/signals" ;
3
3
import { useEffect , useRef , useState } from "preact/hooks" ;
4
4
5
- import { getSubtitleForSong , makeArtistString } from "../helpers.tsx" ;
5
+ import {
6
+ getSubtitleForSong ,
7
+ makeArtistString ,
8
+ makeErrorMessage ,
9
+ } from "../helpers.tsx" ;
6
10
7
11
export default function SearchBar (
8
- props : JSX . HTMLAttributes < HTMLInputElement > & { guessCount : Signal < number > } ,
12
+ props : JSX . HTMLAttributes < HTMLInputElement > & {
13
+ guessCount : Signal < number > ;
14
+ inputValue : string ;
15
+ setInputValue : ( value : string ) => void ;
16
+ } ,
9
17
) {
10
- const [ inputValue , setInputValue ] = useState ( "" ) ;
11
18
const [ selectedSong , setSelectedSong ] = useState < Song | null > ( null ) ;
12
19
const [ suggestions , setSuggestions ] = useState < Song [ ] > ( [ ] ) ;
13
20
const [ allSongs , setAllSongs ] = useState < Song [ ] > ( [ ] ) ;
@@ -16,24 +23,27 @@ export default function SearchBar(
16
23
17
24
useEffect ( ( ) => { // Fetch all songs when the component mounts
18
25
async function fetchSongs ( ) {
19
- try {
20
- const response = await fetch ( "/api/all-songs" ) ;
21
- if ( response . ok ) {
22
- const data : Song [ ] = await response . json ( ) ;
23
- setAllSongs ( data ) ;
24
- } else {
25
- console . error ( "Failed to fetch songs:" , response . statusText ) ;
26
- }
27
- } catch ( error ) {
28
- console . error ( "Error fetching songs:" , error ) ;
26
+ const response = await fetch ( "/api/all-songs" ) ;
27
+ if ( response . ok ) {
28
+ const data : Song [ ] = await response . json ( ) ;
29
+ setAllSongs ( data ) ;
30
+ } else {
31
+ throw new Error ( makeErrorMessage ( response ) ) ;
29
32
}
30
33
}
31
- fetchSongs ( ) . then ( ( ) => console . log ( "Songs fetched successfully" ) ) ;
34
+ fetchSongs ( )
35
+ . then ( ( ) => {
36
+ console . debug ( "Songs fetched successfully." ) ;
37
+ } ) . catch ( ( error ) => {
38
+ // This is the only place we alert the user that connection failed
39
+ alert ( "Unable to load song data. Please try again later." ) ;
40
+ console . error ( `Error while fetching songs: ${ error } .` ) ;
41
+ } ) ;
32
42
} , [ ] ) ; // Empty dependency array means this runs once on mount
33
43
34
44
useEffect ( ( ) => { // runs whenever inputValue or allSongs changes
35
- const query = inputValue . toLowerCase ( ) ;
36
- if ( inputValue . length > 0 ) {
45
+ const query = props . inputValue . toLowerCase ( ) ;
46
+ if ( props . inputValue . length > 0 ) {
37
47
const filteredSuggestions = allSongs . filter ( ( song ) =>
38
48
song . name . toLowerCase ( ) . includes ( query ) ||
39
49
makeArtistString ( song . artists ) . toLowerCase ( ) . includes ( query ) ||
@@ -45,31 +55,31 @@ export default function SearchBar(
45
55
setSuggestions ( [ ] ) ;
46
56
setShowSuggestions ( false ) ;
47
57
}
48
- } , [ inputValue , allSongs ] ) ;
58
+ } , [ props . inputValue , allSongs ] ) ;
49
59
50
60
useSignalEffect ( ( ) => { // Runs whenever guessCount Signal changes
51
61
if ( props . guessCount . value > 0 ) {
52
62
// Reset the input and selected song when a guess is made
53
- setInputValue ( "" ) ;
63
+ props . setInputValue ( "" ) ;
54
64
setSelectedSong ( null ) ;
55
65
setSuggestions ( [ ] ) ;
56
66
setShowSuggestions ( false ) ;
57
67
}
58
68
} ) ;
59
69
60
70
function handleInputChange ( event : JSX . TargetedEvent < HTMLInputElement > ) {
61
- setInputValue ( event . currentTarget . value ) ;
71
+ props . setInputValue ( event . currentTarget . value ) ;
62
72
setSelectedSong ( null ) ;
63
73
}
64
74
65
75
function handleSuggestionClick ( song : Song ) {
66
- setInputValue ( song . name ) ;
76
+ props . setInputValue ( song . name ) ;
67
77
setSelectedSong ( song ) ;
68
78
setShowSuggestions ( false ) ;
69
79
}
70
80
71
81
function handleFocus ( ) {
72
- if ( inputValue . length > 0 ) {
82
+ if ( props . inputValue . length > 0 ) {
73
83
setShowSuggestions ( true ) ;
74
84
}
75
85
}
@@ -87,44 +97,43 @@ export default function SearchBar(
87
97
}
88
98
89
99
return (
90
- < div class = "relative" >
100
+ < div class = "relative text-sm w-full " >
91
101
{ showSuggestions && suggestions . length > 0 && (
92
102
< div
93
103
ref = { suggestionsRef }
94
- class = "bg-gray-100 border border-gray-300 rounded absolute z-10 mb-2 w-full max-h-40 bottom-full overflow-y-auto"
104
+ class = "bg-gray-100 dark:bg-slate-500 border border-gray-300 dark:border-white rounded absolute z-10 mb-2 w-full max-h-40 bottom-full overflow-y-auto"
95
105
>
96
106
{ suggestions . map ( ( song ) => (
97
107
< div
98
108
key = { song }
99
109
tabindex = { 0 }
100
- class = "z-10 p-2 border border-gray-300 hover:bg-cyan-200"
110
+ class = "z-10 p-2 border border-gray-300 dark:border-white hover:bg-cyan-200 hover:dark:bg-gray-600 text-black dark:text-white "
101
111
onClick = { ( ) => handleSuggestionClick ( song ) }
102
112
onKeyDown = { ( e ) =>
103
113
e . key === "Enter" ? handleSuggestionClick ( song ) : undefined }
104
114
>
105
- < p > { song . name } </ p >
115
+ < p class = "font-semibold" > { song . name } </ p >
106
116
< p > By { getSubtitleForSong ( song ) } </ p >
107
117
</ div >
108
118
) ) }
109
119
</ div >
110
120
) }
111
121
< input
112
122
{ ...props }
113
- type = "text "
123
+ type = "search "
114
124
tabindex = { 0 }
115
- class = "border border-gray-300 rounded text-xl p-2"
116
- value = { inputValue }
125
+ class = "w-full border border-gray-300 text-black rounded p-2"
126
+ value = { props . inputValue }
117
127
onInput = { handleInputChange }
118
128
onFocus = { handleFocus }
119
129
onBlur = { handleBlur }
120
130
/>
121
- < div class = "text-xs text-right py-1 pe-1" >
122
- { ( selectedSong && < p > By { getSubtitleForSong ( selectedSong ) } </ p > ) || (
123
- < i > Type a valid guess above ⬆️</ i >
124
- ) }
125
- </ div >
126
- { /* todo: handle text overflow (rather than new line) for long song/artists names (e.g. Sweet Crazy Love Eng) */ }
127
- < span class = "hidden" id = "songId" >
131
+ < p class = "p-1 text-xs truncate text-right" >
132
+ { selectedSong
133
+ ? < > By { getSubtitleForSong ( selectedSong ) } </ >
134
+ : < i > Type a valid guess above ⬆️</ i > }
135
+ </ p >
136
+ < span hidden id = "songId" >
128
137
{ selectedSong ? selectedSong . id : "" }
129
138
</ span >
130
139
</ div >
0 commit comments