From eac782b8beaa37ea4649e74b9dfdab9effb9f469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elio=20Brice=C3=B1o?= <38574891+eliobricenov@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:17:04 -0400 Subject: [PATCH] feat(quest-page): add auto expand and tab filters (#1168) * tech(quests): remove QuestsSummaryTableWrapper and implement directly in page * feat(quest-page): add filtering * tech(quest-page): add loading for quest state * tech(quest-page): remove tab changes * tech(quest-page): add details styles * tech(quest-page): 3 row columns when a quest is selected * tech(quest-page): compare with visibleQuestId for border * tech(quest-page): add loading container * tech(quest-page): add missing styles * fix(quest-page): use searchFilteredQuests for suggested titles --- .../index.module.scss | 12 -- .../QuestsSummaryTableWrapper/index.tsx | 163 ------------------ src/frontend/screens/Quests/index.module.scss | 29 ++++ src/frontend/screens/Quests/index.tsx | 158 +++++++++++++++-- 4 files changed, 176 insertions(+), 186 deletions(-) delete mode 100644 src/frontend/screens/Quests/components/QuestsSummaryTableWrapper/index.module.scss delete mode 100644 src/frontend/screens/Quests/components/QuestsSummaryTableWrapper/index.tsx diff --git a/src/frontend/screens/Quests/components/QuestsSummaryTableWrapper/index.module.scss b/src/frontend/screens/Quests/components/QuestsSummaryTableWrapper/index.module.scss deleted file mode 100644 index 1cf94d8ac..000000000 --- a/src/frontend/screens/Quests/components/QuestsSummaryTableWrapper/index.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -@import '@hyperplay/ui/utilities/_variables.scss'; - -.tableContainer { - background-color: var(--color-neutral-800); - overflow-y: hidden; -} - -.questSelectedLayout { - @include for-big-desktop-up { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} diff --git a/src/frontend/screens/Quests/components/QuestsSummaryTableWrapper/index.tsx b/src/frontend/screens/Quests/components/QuestsSummaryTableWrapper/index.tsx deleted file mode 100644 index 89f0aa140..000000000 --- a/src/frontend/screens/Quests/components/QuestsSummaryTableWrapper/index.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { - QuestsSummaryTable, - QuestCard, - QuestFilter, - SearchBar -} from '@hyperplay/ui' -import useGetQuests from 'frontend/hooks/useGetQuests' -import { useTranslation } from 'react-i18next' -import styles from './index.module.scss' -import { itemType } from '@hyperplay/ui/dist/components/Dropdowns/Dropdown' -import useGetHyperPlayListings from 'frontend/hooks/useGetHyperPlayListings' -import { useLocation, useNavigate } from 'react-router-dom' -import { Quest } from 'common/types' -import cn from 'classnames' - -export interface QuestsSummaryTableWrapperProps { - selectedQuestId: number | null -} - -export function QuestsSummaryTableWrapper({ - selectedQuestId -}: QuestsSummaryTableWrapperProps) { - const { t } = useTranslation() - const questsResults = useGetQuests() - const quests = questsResults?.data?.data - const hyperplayListings = useGetHyperPlayListings() - const listings = hyperplayListings.data.data - const navigate = useNavigate() - - const { search } = useLocation() - const searchParams = new URLSearchParams(search) - const searchParam = searchParams.get('search') - - const [searchText, setSearchText] = useState(searchParam ?? '') - - useEffect(() => { - setSearchText(searchParam ?? '') - }, [searchParam]) - - const [activeFilter, setActiveFilter] = useState('all') - - const achievementsSortOptions = [ - { text: 'Alphabetically (ASC)', id: 'ALPHA_ASC' }, - { text: 'Alphabetically (DES)', id: 'ALPHA_DES' } - ] - const [selectedSort, setSelectedSort] = useState( - achievementsSortOptions[0] - ) - - if (selectedSort.id === 'ALPHA_ASC') { - quests?.sort((a, b) => { - if (a.name < b.name) { - return -1 - } else if (a.name > b.name) { - return 1 - } - return 0 - }) - } else if (selectedSort.id === 'ALPHA_DES') { - quests?.sort((a, b) => { - if (a.name > b.name) { - return -1 - } else if (a.name < b.name) { - return 1 - } - return 0 - }) - } - - function gameTitleMatches(quest: Quest) { - const title = listings ? listings[quest.project_id]?.project_meta?.name : '' - const gameTitleMatch = title - ?.toLowerCase() - .startsWith(searchText.toLowerCase()) - return gameTitleMatch - } - - const imagesToPreload: string[] = [] - const filteredQuests = quests?.filter((quest) => { - const questTitleMatch = quest.name - .toLowerCase() - .startsWith(searchText.toLowerCase()) - return questTitleMatch || gameTitleMatches(quest) - }) - - // set outline css on selected - const gameElements = - filteredQuests?.map(({ id, project_id, name, ...rest }) => { - const imageUrl = listings - ? listings[project_id]?.project_meta?.main_capsule - : '' - if (imageUrl) { - imagesToPreload.push(imageUrl) - } - const title = listings ? listings[project_id]?.project_meta?.name : '' - return ( - { - if (selectedQuestId === id) { - navigate('/quests') - } else { - navigate(`/quests/${id}`) - } - }} - selected={id === selectedQuestId} - description={name} - className={id === selectedQuestId ? 'gradientBorder' : undefined} - /> - ) - }) ?? [] - - let suggestedSearchTitles = undefined - if (searchText) { - suggestedSearchTitles = filteredQuests?.map((val) => val.name) - } - - return ( - - } - /> - ) -} diff --git a/src/frontend/screens/Quests/index.module.scss b/src/frontend/screens/Quests/index.module.scss index ae2036faf..9eff7db33 100644 --- a/src/frontend/screens/Quests/index.module.scss +++ b/src/frontend/screens/Quests/index.module.scss @@ -1,3 +1,5 @@ +@import '@hyperplay/ui/utilities/_variables.scss'; + .root { display: grid; grid-template-columns: 1fr auto; @@ -35,11 +37,38 @@ width: 800px; max-width: unset !important; + background-color: var(--color-neutral-800); + border: none; + @media (max-width: 1300px) { width: 540px; } } +.tableContainer { + background-color: var(--color-neutral-800); + overflow-y: hidden; +} + +.questSelectedLayout { + @include for-big-desktop-up { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +.loadingContainer { + padding: var(--space-2lg); + height: 100%; + + display: flex; + justify-content: center; + align-items: center; +} + +.loadingSpinner { + font-size: var(--text-3xl); +} + .toast { position: fixed; bottom: 24px; diff --git a/src/frontend/screens/Quests/index.tsx b/src/frontend/screens/Quests/index.tsx index c06bea700..ac6a618e6 100644 --- a/src/frontend/screens/Quests/index.tsx +++ b/src/frontend/screens/Quests/index.tsx @@ -1,17 +1,28 @@ -import React, { useEffect } from 'react' -import { QuestsSummaryTableWrapper } from './components/QuestsSummaryTableWrapper' +import React, { useEffect, useState } from 'react' import styles from './index.module.scss' -import { Alert, Background } from '@hyperplay/ui' import classNames from 'classnames' import useAuthSession from 'frontend/hooks/useAuthSession' import { useTranslation } from 'react-i18next' -import { useNavigate, useParams } from 'react-router-dom' +import { useNavigate, useParams, useLocation } from 'react-router-dom' import QuestDetails from 'frontend/components/UI/QuestDetails' import { fetchEpicListing, getGameInfo } from 'frontend/helpers' import { useMutation } from '@tanstack/react-query' import { Runner } from 'common/types' import { Quest } from '@hyperplay/utils' import { QuestRewardClaimedToast } from 'frontend/components/UI/QuestRewardClaimedToast' +import { itemType } from '@hyperplay/ui/dist/components/Dropdowns/Dropdown' +import useGetHyperPlayListings from 'frontend/hooks/useGetHyperPlayListings' +import useGetQuests from 'frontend/hooks/useGetQuests' +import { + Alert, + Background, + QuestCard, + QuestFilter, + QuestsSummaryTable, + SearchBar, + DarkContainer, + LoadingSpinner +} from '@hyperplay/ui' export function QuestsPage() { const navigate = useNavigate() @@ -20,10 +31,29 @@ export function QuestsPage() { const { isSignedIn } = useAuthSession() const { t } = useTranslation() + const questsResults = useGetQuests() + const quests = questsResults?.data?.data + const hyperplayListings = useGetHyperPlayListings() + const listings = hyperplayListings.data.data + + const { search } = useLocation() + const searchParams = new URLSearchParams(search) + const searchParam = searchParams.get('search') + const [searchText, setSearchText] = useState(searchParam ?? '') + const [activeFilter, setActiveFilter] = useState('all') + const [selectedSort, setSelectedSort] = useState({ + text: 'Alphabetically (ASC)', + id: 'ALPHA_ASC' + }) + useEffect(() => { window.api.trackScreen('Quests Page') }, []) + useEffect(() => { + setSearchText(searchParam ?? '') + }, [searchParam]) + let alertComponent = null const showAlert = !isSignedIn let contentWithAlertClass = styles['no-alert'] @@ -93,6 +123,65 @@ export function QuestsPage() { } }) + const sortedQuests = [...(quests ?? [])].sort((a, b) => { + const sortMultiplier = selectedSort.id === 'ALPHA_ASC' ? 1 : -1 + return a.name.localeCompare(b.name) * sortMultiplier + }) + + const gameTitleMatches = (quest: Quest) => { + const title = listings ? listings[quest.project_id]?.project_meta?.name : '' + return title?.toLowerCase().startsWith(searchText.toLowerCase()) + } + + const searchFilteredQuests = sortedQuests?.filter((quest) => { + const questTitleMatch = quest.name + .toLowerCase() + .startsWith(searchText.toLowerCase()) + return questTitleMatch || gameTitleMatches(quest) + }) + + const initialQuestId = searchFilteredQuests?.[0]?.id ?? null + const visibleQuestId = selectedQuestId ?? initialQuestId + + const achievementsSortOptions = [ + { text: 'Alphabetically (ASC)', id: 'ALPHA_ASC' }, + { text: 'Alphabetically (DES)', id: 'ALPHA_DES' } + ] + + const imagesToPreload: string[] = [] + const gameElements = + searchFilteredQuests?.map(({ id, project_id, name, ...rest }) => { + const imageUrl = listings + ? listings[project_id]?.project_meta?.main_capsule + : '' + if (imageUrl) { + imagesToPreload.push(imageUrl) + } + const title = listings ? listings[project_id]?.project_meta?.name : '' + return ( + { + if (selectedQuestId !== id) { + navigate(`/quests/${id}`) + } + }} + selected={id === visibleQuestId} + description={name} + className={id === visibleQuestId ? 'gradientBorder' : undefined} + /> + ) + }) ?? [] + + let suggestedSearchTitles = undefined + + if (searchText) { + suggestedSearchTitles = searchFilteredQuests?.map((val) => val.name) + } + return ( <> @@ -105,15 +194,62 @@ export function QuestsPage() { > {alertComponent} - - { - navigateToGame.mutate(quest) + + } /> + {visibleQuestId ? ( + { + navigateToGame.mutate(quest) + }} + /> + ) : ( + <> + + + + + )} )