Skip to content

Commit dd5448f

Browse files
committed
feat: implemented Fuse.js to search by quest name and game title.
1 parent 15aae12 commit dd5448f

File tree

1 file changed

+77
-30
lines changed

1 file changed

+77
-30
lines changed

src/frontend/screens/Quests/index.tsx

+77-30
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Quest } from '@hyperplay/utils'
1212
import { QuestRewardClaimedToast } from 'frontend/components/UI/QuestRewardClaimedToast'
1313
import useGetHyperPlayListings from 'frontend/hooks/useGetHyperPlayListings'
1414
import useGetQuests from 'frontend/hooks/useGetQuests'
15+
import Fuse from 'fuse.js'
1516
import {
1617
Alert,
1718
Background,
@@ -41,6 +42,15 @@ export function QuestsPage() {
4142
const [searchText, setSearchText] = useState(searchParam ?? '')
4243
const [activeFilter, setActiveFilter] = useState<QuestFilter>('all')
4344

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+
4454
useEffect(() => {
4555
window.api.trackScreen('Quests Page')
4656
}, [])
@@ -117,63 +127,100 @@ export function QuestsPage() {
117127
)
118128
}
119129
})
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+
}) ?? []
120145

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)
124162
}
125163

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
138170
)
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])
141183

142-
const initialQuestId = searchFilteredQuests?.[0]?.id ?? null
184+
const initialQuestId = searchFilteredQuests?.[0]?.item?.id ?? null
143185
const visibleQuestId = selectedQuestId ?? initialQuestId
144186

145187
const imagesToPreload: string[] = []
146188
const gameElements =
147-
searchFilteredQuests?.map(({ id, project_id, name, ...rest }) => {
189+
searchFilteredQuests?.map((result: FuseResult<Quest>) => {
148190
const imageUrl = listings
149-
? listings[project_id]?.project_meta?.main_capsule
191+
? listings[result.item.project_id]?.project_meta?.main_capsule
150192
: ''
151193
if (imageUrl) {
152194
imagesToPreload.push(imageUrl)
153195
}
154-
const title = listings ? listings[project_id]?.project_meta?.name : ''
196+
const title = listings
197+
? listings[result.item.project_id]?.project_meta?.name
198+
: ''
155199
return (
156200
<QuestCard
157-
key={id}
201+
key={result.item.id}
158202
image={imageUrl ?? ''}
159203
title={title}
160-
{...rest}
161204
onClick={() => {
162-
if (selectedQuestId !== id) {
163-
navigate(`/quests/${id}`)
205+
if (selectedQuestId !== result.item.id) {
206+
navigate(`/quests/${result.item.id}`)
164207
}
165208
}}
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+
}
169214
/>
170215
)
171216
}) ?? []
172217

173218
let suggestedSearchTitles = undefined
174-
175219
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+
})
177224
}
178225

179226
return (

0 commit comments

Comments
 (0)