@@ -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,47 +127,86 @@ 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
+ } ) ?? [ ]
145
+
146
+ const fuse = new Fuse ( questsWithGameNames ?? [ ] , fuseOptions )
147
+
148
+ let searchFilteredQuests : FuseResult < Quest > [ ] = [ ]
149
+ if ( searchText ) {
150
+ searchFilteredQuests = fuse . search ( searchText ) as FuseResult < Quest > [ ]
151
+ } else if ( quests ) {
152
+ searchFilteredQuests = quests . map ( ( quest ) => ( {
153
+ id : quest . id ,
154
+ project_id : quest . project_id ,
155
+ name : quest . name ,
156
+ title : listings ?. [ quest . project_id ] ?. project_meta ?. name ?? '' ,
157
+ item : quest ,
158
+ refIndex : 0
159
+ } ) )
160
+ }
120
161
121
- const gameTitleMatches = ( quest : Quest ) => {
122
- const title = listings ? listings [ quest . project_id ] ?. project_meta ?. name : ''
123
- return title ?. toLowerCase ( ) . startsWith ( searchText . toLowerCase ( ) )
162
+ const searchQuests = ( quests : Quest [ ] , query : string ) => {
163
+ if ( ! query ) return quests
164
+ const results = fuse . search ( query )
165
+ return results . map ( ( result ) => result . item )
124
166
}
125
167
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 )
168
+ const filteredQuests = searchQuests ( quests ?? [ ] , searchText )
169
+
170
+ useEffect ( ( ) => {
171
+ if ( filteredQuests . length > 0 ) {
172
+ const currentQuestExists = filteredQuests . find (
173
+ ( quest ) => quest . id === selectedQuestId
138
174
)
139
- return questTitleMatch || gameTitleMatch || searchByKeywords
140
- } )
175
+ if ( ! currentQuestExists ) {
176
+ const firstQuest = filteredQuests [ 0 ]
177
+ if ( firstQuest ) {
178
+ const searchParams = new URLSearchParams ( window . location . search )
179
+ const newUrl = `/quests/${ firstQuest . id } ${
180
+ searchParams . toString ( ) ? `?${ searchParams . toString ( ) } ` : ''
181
+ } #card-${ firstQuest . id } `
182
+ navigate ( newUrl )
183
+ }
184
+ }
185
+ }
186
+ } , [ filteredQuests , navigate , selectedQuestId ] )
141
187
142
- const initialQuestId = searchFilteredQuests ?. [ 0 ] ?. id ?? null
188
+ const initialQuestId = searchFilteredQuests ?. [ 0 ] ?. item ?. id ?? null
143
189
const visibleQuestId = selectedQuestId ?? initialQuestId
144
190
145
191
const imagesToPreload : string [ ] = [ ]
146
192
const gameElements =
147
- searchFilteredQuests ?. map ( ( { id , project_id , name , ... rest } ) => {
193
+ searchFilteredQuests ?. map ( ( result : FuseResult < Quest > ) => {
148
194
const imageUrl = listings
149
- ? listings [ project_id ] ?. project_meta ?. main_capsule
195
+ ? listings [ result . item . project_id ] ?. project_meta ?. main_capsule
150
196
: ''
151
197
if ( imageUrl ) {
152
198
imagesToPreload . push ( imageUrl )
153
199
}
154
- const title = listings ? listings [ project_id ] ?. project_meta ?. name : ''
200
+ const title = listings
201
+ ? listings [ result . item . project_id ] ?. project_meta ?. name
202
+ : ''
203
+ const id = result . item . id
204
+ const name = result . item . name
155
205
return (
156
206
< QuestCard
157
207
key = { id }
158
208
image = { imageUrl ?? '' }
159
209
title = { title }
160
- { ...rest }
161
210
onClick = { ( ) => {
162
211
if ( selectedQuestId !== id ) {
163
212
navigate ( `/quests/${ id } ` )
@@ -171,9 +220,11 @@ export function QuestsPage() {
171
220
} ) ?? [ ]
172
221
173
222
let suggestedSearchTitles = undefined
174
-
175
223
if ( searchText ) {
176
- suggestedSearchTitles = searchFilteredQuests ?. map ( ( val ) => val . name )
224
+ suggestedSearchTitles = searchFilteredQuests ?. map ( ( val ) => {
225
+ const gameName = listings ?. [ val . item . project_id ] ?. project_meta ?. name ?? ''
226
+ return `${ val . item . name } (${ gameName } )`
227
+ } )
177
228
}
178
229
179
230
return (
0 commit comments