@@ -12,6 +12,7 @@ import { Quest } from '@hyperplay/utils'
12
12
import { QuestRewardClaimedToast } from 'frontend/components/UI/QuestRewardClaimedToast'
13
13
import useGetHyperPlayListings from 'frontend/hooks/useGetHyperPlayListings'
14
14
import useGetQuests from 'frontend/hooks/useGetQuests'
15
+ import Fuse from 'fuse.js'
15
16
import {
16
17
Alert ,
17
18
Background ,
@@ -41,6 +42,15 @@ export function QuestsPage() {
41
42
const [ searchText , setSearchText ] = useState ( searchParam ?? '' )
42
43
const [ activeFilter , setActiveFilter ] = useState < QuestFilter > ( 'all' )
43
44
45
+ interface FuseResult < T > {
46
+ id : number
47
+ project_id : string
48
+ name : string
49
+ title : string
50
+ item : T
51
+ refIndex : number
52
+ }
53
+
44
54
useEffect ( ( ) => {
45
55
window . api . trackScreen ( 'Quests Page' )
46
56
} , [ ] )
@@ -117,63 +127,100 @@ export function QuestsPage() {
117
127
)
118
128
}
119
129
} )
130
+ //Search Quest
131
+ const fuseOptions = {
132
+ keys : [ 'name' , 'title' ] ,
133
+ threshold : 0.2
134
+ }
135
+
136
+ const questsWithGameNames =
137
+ quests ?. map ( ( quest ) => {
138
+ const gameName = listings ?. [ quest . project_id ] ?. project_meta ?. name ?? ''
139
+ return {
140
+ ...quest ,
141
+ title : gameName ,
142
+ name : quest . name
143
+ }
144
+ } ) ?? [ ]
120
145
121
- const gameTitleMatches = ( quest : Quest ) => {
122
- const title = listings ? listings [ quest . project_id ] ?. project_meta ?. name : ''
123
- return title ?. toLowerCase ( ) . startsWith ( searchText . toLowerCase ( ) )
146
+ const fuse = new Fuse ( questsWithGameNames ?? [ ] , fuseOptions )
147
+ const searchFilteredQuests = searchText
148
+ ? ( fuse . search ( searchText ) as FuseResult < Quest > [ ] )
149
+ : quests ?. map ( ( quest ) => ( {
150
+ id : quest . id ,
151
+ project_id : quest . project_id ,
152
+ name : quest . name ,
153
+ title : listings ?. [ quest . project_id ] ?. project_meta ?. name ?? '' ,
154
+ item : quest ,
155
+ refIndex : 0
156
+ } ) ) ?? [ ]
157
+
158
+ const searchQuests = ( quests : Quest [ ] , query : string ) => {
159
+ if ( ! query ) return quests
160
+ const results = fuse . search ( query )
161
+ return results . map ( ( result ) => result . item )
124
162
}
125
163
126
- const searchFilteredQuests = quests ?. filter ( ( quest ) => {
127
- const questTitleMatch = quest . name
128
- . toLowerCase ( )
129
- . startsWith ( searchText . toLowerCase ( ) )
130
- const gameTitleMatch = gameTitleMatches ( quest )
131
- const searchByKeywords = searchText
132
- . toLowerCase ( )
133
- . split ( ' ' )
134
- . some (
135
- ( term ) =>
136
- quest . name ?. toLowerCase ( ) . includes ( term ) ||
137
- quest . description ?. toLowerCase ( ) . includes ( term )
164
+ const filteredQuests = searchQuests ( quests ?? [ ] , searchText )
165
+
166
+ useEffect ( ( ) => {
167
+ if ( filteredQuests . length > 0 ) {
168
+ const currentQuestExists = filteredQuests . find (
169
+ ( quest ) => quest . id === selectedQuestId
138
170
)
139
- return questTitleMatch || gameTitleMatch || searchByKeywords
140
- } )
171
+ if ( ! currentQuestExists ) {
172
+ const firstQuest = filteredQuests [ 0 ]
173
+ if ( firstQuest ) {
174
+ const searchParams = new URLSearchParams ( window . location . search )
175
+ const newUrl = `/quests/${ firstQuest . id } ${
176
+ searchParams . toString ( ) ? `?${ searchParams . toString ( ) } ` : ''
177
+ } #card-${ firstQuest . id } `
178
+ navigate ( newUrl )
179
+ }
180
+ }
181
+ }
182
+ } , [ filteredQuests , navigate , selectedQuestId ] )
141
183
142
- const initialQuestId = searchFilteredQuests ?. [ 0 ] ?. id ?? null
184
+ const initialQuestId = searchFilteredQuests ?. [ 0 ] ?. item ?. id ?? null
143
185
const visibleQuestId = selectedQuestId ?? initialQuestId
144
186
145
187
const imagesToPreload : string [ ] = [ ]
146
188
const gameElements =
147
- searchFilteredQuests ?. map ( ( { id , project_id , name , ... rest } ) => {
189
+ searchFilteredQuests ?. map ( ( result : FuseResult < Quest > ) => {
148
190
const imageUrl = listings
149
- ? listings [ project_id ] ?. project_meta ?. main_capsule
191
+ ? listings [ result . item . project_id ] ?. project_meta ?. main_capsule
150
192
: ''
151
193
if ( imageUrl ) {
152
194
imagesToPreload . push ( imageUrl )
153
195
}
154
- const title = listings ? listings [ project_id ] ?. project_meta ?. name : ''
196
+ const title = listings
197
+ ? listings [ result . item . project_id ] ?. project_meta ?. name
198
+ : ''
155
199
return (
156
200
< QuestCard
157
- key = { id }
201
+ key = { result . item . id }
158
202
image = { imageUrl ?? '' }
159
203
title = { title }
160
- { ...rest }
161
204
onClick = { ( ) => {
162
- if ( selectedQuestId !== id ) {
163
- navigate ( `/quests/${ id } ` )
205
+ if ( selectedQuestId !== result . item . id ) {
206
+ navigate ( `/quests/${ result . item . id } ` )
164
207
}
165
208
} }
166
- selected = { id === visibleQuestId }
167
- description = { name }
168
- className = { id === visibleQuestId ? 'gradientBorder' : undefined }
209
+ selected = { result . item . id === visibleQuestId }
210
+ description = { result . item . name }
211
+ className = {
212
+ result . item . id === visibleQuestId ? 'gradientBorder' : undefined
213
+ }
169
214
/>
170
215
)
171
216
} ) ?? [ ]
172
217
173
218
let suggestedSearchTitles = undefined
174
-
175
219
if ( searchText ) {
176
- suggestedSearchTitles = searchFilteredQuests ?. map ( ( val ) => val . name )
220
+ suggestedSearchTitles = searchFilteredQuests ?. map ( ( val ) => {
221
+ const gameName = listings ?. [ val . item . project_id ] ?. project_meta ?. name ?? ''
222
+ return `${ val . item . name } (${ gameName } )`
223
+ } )
177
224
}
178
225
179
226
return (
0 commit comments