Get query states working on teams page

This changes the URL to show query params for our three filters, making it easy for people to link to very specific subsets of raids.
This commit is contained in:
Justin Edmund 2022-03-07 02:43:21 -08:00
parent 6d197e9f08
commit 1365e4c95c
5 changed files with 156 additions and 45 deletions

View file

@ -8,6 +8,7 @@ import RaidDropdown from '~components/RaidDropdown'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import './index.scss' import './index.scss'
import { raidGroups } from '~utils/raidGroups'
interface Props { interface Props {
children: React.ReactNode children: React.ReactNode
@ -15,7 +16,7 @@ interface Props {
element?: number element?: number
raidSlug?: string raidSlug?: string
recency?: number recency?: number
onFilter: (element?: number, raid?: string, recency?: number) => void onFilter: ({element, raid, recency} : { element?: number, raid?: Raid, recency?: number}) => void
} }
const FilterBar = (props: Props) => { const FilterBar = (props: Props) => {
@ -36,25 +37,26 @@ const FilterBar = (props: Props) => {
'shadow': props.scrolled 'shadow': props.scrolled
}) })
function selectChanged(event: React.ChangeEvent<HTMLSelectElement>) { function elementSelectChanged() {
const elementValue = (elementSelect.current) ? parseInt(elementSelect.current.value) : -1 const elementValue = (elementSelect.current) ? parseInt(elementSelect.current.value) : -1
props.onFilter({ element: elementValue })
}
function recencySelectChanged() {
const recencyValue = (recencySelect.current) ? parseInt(recencySelect.current.value) : -1 const recencyValue = (recencySelect.current) ? parseInt(recencySelect.current.value) : -1
let raidValue = '' props.onFilter({ recency: recencyValue })
}
if (app.raids) { function raidSelectChanged(raid?: Raid) {
const raid = app.raids.find((raid: Raid) => raid.slug === raidSelect.current?.value) props.onFilter({ raid: raid })
raidValue = (raid) ? raid.id : ''
}
props.onFilter(elementValue, raidValue, recencyValue)
} }
return ( return (
<div className={classes}> <div className={classes}>
{props.children} {props.children}
<select onChange={selectChanged} ref={elementSelect} value={props.element}> <select onChange={elementSelectChanged} ref={elementSelect} value={props.element}>
<option data-element="all" key={-1} value={-1}>{t('elements.full.all')}</option> <option data-element="all" key={-1} value={-1}>{t('elements.full.all')}</option>
<option data-element="null" key={-0} value={0}>{t('elements.full.null')}</option> <option data-element="null" key={0} value={0}>{t('elements.full.null')}</option>
<option data-element="wind" key={1} value={1}>{t('elements.full.wind')}</option> <option data-element="wind" key={1} value={1}>{t('elements.full.wind')}</option>
<option data-element="fire" key={2} value={2}>{t('elements.full.fire')}</option> <option data-element="fire" key={2} value={2}>{t('elements.full.fire')}</option>
<option data-element="water" key={3} value={3}>{t('elements.full.water')}</option> <option data-element="water" key={3} value={3}>{t('elements.full.water')}</option>
@ -64,10 +66,10 @@ const FilterBar = (props: Props) => {
</select> </select>
<RaidDropdown <RaidDropdown
showAllRaidsOption={true} showAllRaidsOption={true}
onChange={selectChanged} onChange={raidSelectChanged}
ref={raidSelect} ref={raidSelect}
/> />
<select onChange={selectChanged} ref={recencySelect}> <select onChange={recencySelectChanged} ref={recencySelect}>
<option key={-1} value={-1}>{t('recency.all_time')}</option> <option key={-1} value={-1}>{t('recency.all_time')}</option>
<option key={86400} value={86400}>{t('recency.last_day')}</option> <option key={86400} value={86400}>{t('recency.last_day')}</option>
<option key={604800} value={604800}>{t('recency.last_week')}</option> <option key={604800} value={604800}>{t('recency.last_week')}</option>

View file

@ -11,7 +11,7 @@ import './index.scss'
interface Props { interface Props {
showAllRaidsOption: boolean showAllRaidsOption: boolean
currentRaid?: string currentRaid?: string
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void onChange?: (raid?: Raid) => void
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
} }
@ -75,7 +75,7 @@ const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFiel
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) { function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (raids) { if (raids) {
const raid = raids.find(raid => raid.slug === event.target.value) const raid = raids.find(raid => raid.slug === event.target.value)
if (props.onChange) props.onChange(event) if (props.onChange) props.onChange(raid)
setCurrentRaid(raid) setCurrentRaid(raid)
} }
} }

View file

@ -3,39 +3,77 @@ import Head from 'next/head'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useCookies } from 'react-cookie' import { useCookies } from 'react-cookie'
import { queryTypes, useQueryState } from 'next-usequerystate'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import clonedeep from 'lodash.clonedeep' import clonedeep from 'lodash.clonedeep'
import api from '~utils/api' import api from '~utils/api'
import { elements, allElement } from '~utils/Element'
import GridRep from '~components/GridRep' import GridRep from '~components/GridRep'
import GridRepCollection from '~components/GridRepCollection' import GridRepCollection from '~components/GridRepCollection'
import FilterBar from '~components/FilterBar' import FilterBar from '~components/FilterBar'
const TeamsRoute: React.FC = () => { const TeamsRoute: React.FC = () => {
const router = useRouter() // Set up cookies
const { t } = useTranslation('common')
// Cookies
const [cookies] = useCookies(['account']) const [cookies] = useCookies(['account'])
const headers = (cookies.account != null) ? { const headers = (cookies.account != null) ? {
'Authorization': `Bearer ${cookies.account.access_token}` 'Authorization': `Bearer ${cookies.account.access_token}`
} : {} } : {}
// const { raids } = useSnapshot(appState)
// Get the information we need from the router
const router = useRouter()
// Import translations
const { t } = useTranslation('common')
// Set up app-specific states
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [scrolled, setScrolled] = useState(false) const [scrolled, setScrolled] = useState(false)
// Set up page-specific states
const [parties, setParties] = useState<Party[]>([]) const [parties, setParties] = useState<Party[]>([])
const [raid, setRaid] = useState<Raid>()
// Filter states // Set up filter-specific query states
const [element, setElement] = useState<number | null>(null) // Recency is in seconds
const [raidId, setRaidId] = useState<string | null>(null) const [element, setElement] = useQueryState("element", {
const [recencyInSeconds, setRecencyInSeconds] = useState<number | null>(null) defaultValue: -1,
parse: (query: string) => parseElement(query),
serialize: value => serializeElement(value)
})
const [raidSlug, setRaidSlug] = useQueryState("raid", { defaultValue: "all" })
const [recency, setRecency] = useQueryState("recency", queryTypes.integer.withDefault(-1))
// Define transformers for element and raid
function parseElement(query: string) {
let element: TeamElement | undefined =
(query === 'all') ?
allElement : elements.find(element => element.name.en.toLowerCase() === query)
return (element) ? element.id : -1
}
function serializeElement(value: number | undefined) {
let name = ''
if (value != undefined) {
if (value == -1) {
name = allElement.name.en.toLowerCase()
} else {
console.log(value)
name = elements[value].name.en.toLowerCase()
}
}
return name
}
// Add scroll event listener for shadow on FilterBar
useEffect(() => { useEffect(() => {
window.addEventListener("scroll", handleScroll) window.addEventListener("scroll", handleScroll)
return () => window.removeEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll);
@ -52,12 +90,14 @@ const TeamsRoute: React.FC = () => {
const fetchTeams = useCallback(() => { const fetchTeams = useCallback(() => {
const filters = { const filters = {
params: { params: {
element: element, element: (element != -1) ? element : undefined,
raid: raidId, raid: (raidSlug !== "all") ? raid?.id : undefined,
recency: recencyInSeconds recency: (recency != -1) ? recency : undefined
} }
} }
console.log(filters)
const headers = (cookies.account) ? { const headers = (cookies.account) ? {
headers: { headers: {
'Authorization': `Bearer ${cookies.account.access_token}` 'Authorization': `Bearer ${cookies.account.access_token}`
@ -77,27 +117,24 @@ const TeamsRoute: React.FC = () => {
setLoading(false) setLoading(false)
}) })
.catch(error => handleError(error)) .catch(error => handleError(error))
}, [element, raidId, recencyInSeconds, cookies.account, handleError]) }, [element, raid, recency, cookies.account, handleError])
useEffect(() => { useEffect(() => {
fetchTeams() fetchTeams()
}, [fetchTeams]) }, [fetchTeams])
function receiveFilters(element?: number, raid?: string, recency?: number) { function receiveFilters({ element, raid, recency }: {element?: number, raid?: Raid, recency?: number}) {
if (element != null && element >= 0) if (element == 0)
setElement(0)
else if (element)
setElement(element) setElement(element)
else
setElement(null)
if (raid && raid != '0') if (raid) {
setRaidId(raid) setRaid(raid)
else setRaidSlug(raid.slug)
setRaidId(null) }
if (recency && recency > 0) if (recency) setRecency(recency)
setRecencyInSeconds(recency)
else
setRecencyInSeconds(null)
} }
function toggleFavorite(teamId: string, favorited: boolean) { function toggleFavorite(teamId: string, favorited: boolean) {
@ -167,8 +204,14 @@ const TeamsRoute: React.FC = () => {
<meta name="twitter:title" content="Discover Teams" /> <meta name="twitter:title" content="Discover Teams" />
<meta name="twitter:description" content="Find different Granblue Fantasy teams by raid, element or recency" /> <meta name="twitter:description" content="Find different Granblue Fantasy teams by raid, element or recency" />
</Head> </Head>
<FilterBar onFilter={receiveFilters} scrolled={scrolled}>
<h1>{t('teams.title')}</h1> <FilterBar
onFilter={receiveFilters}
scrolled={scrolled}
element={element}
raidSlug={raidSlug}
recency={recency}>
<h1>{t('teams.title')}</h1>
</FilterBar> </FilterBar>
<section> <section>

7
types/TeamElement.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
interface TeamElement {
id: number,
name: {
en: string,
ja: string
}
}

59
utils/Element.tsx Normal file
View file

@ -0,0 +1,59 @@
export const allElement: TeamElement = {
id: -1,
name: {
en: "All",
ja: "全s"
}
}
export const elements: TeamElement[] = [
{
id: 0,
name: {
en: "Null",
ja: "無"
}
},
{
id: 1,
name: {
en: "Wind",
ja: "風"
}
},
{
id: 2,
name: {
en: "Fire",
ja: "火"
}
},
{
id: 3,
name: {
en: "Water",
ja: "水"
}
},
{
id: 4,
name: {
en: "Earth",
ja: "土"
}
},
{
id: 5,
name: {
en: "Dark",
ja: "闇"
}
},
{
id: 6,
name: {
en: "Light",
ja: "光"
}
}
]