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:
parent
6d197e9f08
commit
1365e4c95c
5 changed files with 156 additions and 45 deletions
|
|
@ -8,6 +8,7 @@ import RaidDropdown from '~components/RaidDropdown'
|
|||
import { appState } from '~utils/appState'
|
||||
|
||||
import './index.scss'
|
||||
import { raidGroups } from '~utils/raidGroups'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
|
|
@ -15,7 +16,7 @@ interface Props {
|
|||
element?: number
|
||||
raidSlug?: string
|
||||
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) => {
|
||||
|
|
@ -36,25 +37,26 @@ const FilterBar = (props: Props) => {
|
|||
'shadow': props.scrolled
|
||||
})
|
||||
|
||||
function selectChanged(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||
function elementSelectChanged() {
|
||||
const elementValue = (elementSelect.current) ? parseInt(elementSelect.current.value) : -1
|
||||
const recencyValue = (recencySelect.current) ? parseInt(recencySelect.current.value) : -1
|
||||
let raidValue = ''
|
||||
|
||||
if (app.raids) {
|
||||
const raid = app.raids.find((raid: Raid) => raid.slug === raidSelect.current?.value)
|
||||
raidValue = (raid) ? raid.id : ''
|
||||
props.onFilter({ element: elementValue })
|
||||
}
|
||||
|
||||
props.onFilter(elementValue, raidValue, recencyValue)
|
||||
function recencySelectChanged() {
|
||||
const recencyValue = (recencySelect.current) ? parseInt(recencySelect.current.value) : -1
|
||||
props.onFilter({ recency: recencyValue })
|
||||
}
|
||||
|
||||
function raidSelectChanged(raid?: Raid) {
|
||||
props.onFilter({ raid: raid })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
{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="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="fire" key={2} value={2}>{t('elements.full.fire')}</option>
|
||||
<option data-element="water" key={3} value={3}>{t('elements.full.water')}</option>
|
||||
|
|
@ -64,10 +66,10 @@ const FilterBar = (props: Props) => {
|
|||
</select>
|
||||
<RaidDropdown
|
||||
showAllRaidsOption={true}
|
||||
onChange={selectChanged}
|
||||
onChange={raidSelectChanged}
|
||||
ref={raidSelect}
|
||||
/>
|
||||
<select onChange={selectChanged} ref={recencySelect}>
|
||||
<select onChange={recencySelectChanged} ref={recencySelect}>
|
||||
<option key={-1} value={-1}>{t('recency.all_time')}</option>
|
||||
<option key={86400} value={86400}>{t('recency.last_day')}</option>
|
||||
<option key={604800} value={604800}>{t('recency.last_week')}</option>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import './index.scss'
|
|||
interface Props {
|
||||
showAllRaidsOption: boolean
|
||||
currentRaid?: string
|
||||
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
onChange?: (raid?: Raid) => 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>) {
|
||||
if (raids) {
|
||||
const raid = raids.find(raid => raid.slug === event.target.value)
|
||||
if (props.onChange) props.onChange(event)
|
||||
if (props.onChange) props.onChange(raid)
|
||||
setCurrentRaid(raid)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,39 +3,77 @@ import Head from 'next/head'
|
|||
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCookies } from 'react-cookie'
|
||||
import { queryTypes, useQueryState } from 'next-usequerystate'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
||||
import clonedeep from 'lodash.clonedeep'
|
||||
|
||||
|
||||
import api from '~utils/api'
|
||||
import { elements, allElement } from '~utils/Element'
|
||||
|
||||
import GridRep from '~components/GridRep'
|
||||
import GridRepCollection from '~components/GridRepCollection'
|
||||
import FilterBar from '~components/FilterBar'
|
||||
|
||||
const TeamsRoute: React.FC = () => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Cookies
|
||||
// Set up cookies
|
||||
const [cookies] = useCookies(['account'])
|
||||
const headers = (cookies.account != null) ? {
|
||||
'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 [scrolled, setScrolled] = useState(false)
|
||||
|
||||
// Set up page-specific states
|
||||
const [parties, setParties] = useState<Party[]>([])
|
||||
const [raid, setRaid] = useState<Raid>()
|
||||
|
||||
// Filter states
|
||||
const [element, setElement] = useState<number | null>(null)
|
||||
const [raidId, setRaidId] = useState<string | null>(null)
|
||||
const [recencyInSeconds, setRecencyInSeconds] = useState<number | null>(null)
|
||||
// Set up filter-specific query states
|
||||
// Recency is in seconds
|
||||
const [element, setElement] = useQueryState("element", {
|
||||
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(() => {
|
||||
window.addEventListener("scroll", handleScroll)
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
|
|
@ -52,12 +90,14 @@ const TeamsRoute: React.FC = () => {
|
|||
const fetchTeams = useCallback(() => {
|
||||
const filters = {
|
||||
params: {
|
||||
element: element,
|
||||
raid: raidId,
|
||||
recency: recencyInSeconds
|
||||
element: (element != -1) ? element : undefined,
|
||||
raid: (raidSlug !== "all") ? raid?.id : undefined,
|
||||
recency: (recency != -1) ? recency : undefined
|
||||
}
|
||||
}
|
||||
|
||||
console.log(filters)
|
||||
|
||||
const headers = (cookies.account) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
||||
|
|
@ -77,27 +117,24 @@ const TeamsRoute: React.FC = () => {
|
|||
setLoading(false)
|
||||
})
|
||||
.catch(error => handleError(error))
|
||||
}, [element, raidId, recencyInSeconds, cookies.account, handleError])
|
||||
}, [element, raid, recency, cookies.account, handleError])
|
||||
|
||||
useEffect(() => {
|
||||
fetchTeams()
|
||||
}, [fetchTeams])
|
||||
|
||||
function receiveFilters(element?: number, raid?: string, recency?: number) {
|
||||
if (element != null && element >= 0)
|
||||
function receiveFilters({ element, raid, recency }: {element?: number, raid?: Raid, recency?: number}) {
|
||||
if (element == 0)
|
||||
setElement(0)
|
||||
else if (element)
|
||||
setElement(element)
|
||||
else
|
||||
setElement(null)
|
||||
|
||||
if (raid && raid != '0')
|
||||
setRaidId(raid)
|
||||
else
|
||||
setRaidId(null)
|
||||
if (raid) {
|
||||
setRaid(raid)
|
||||
setRaidSlug(raid.slug)
|
||||
}
|
||||
|
||||
if (recency && recency > 0)
|
||||
setRecencyInSeconds(recency)
|
||||
else
|
||||
setRecencyInSeconds(null)
|
||||
if (recency) setRecency(recency)
|
||||
}
|
||||
|
||||
function toggleFavorite(teamId: string, favorited: boolean) {
|
||||
|
|
@ -167,7 +204,13 @@ const TeamsRoute: React.FC = () => {
|
|||
<meta name="twitter:title" content="Discover Teams" />
|
||||
<meta name="twitter:description" content="Find different Granblue Fantasy teams by raid, element or recency" />
|
||||
</Head>
|
||||
<FilterBar onFilter={receiveFilters} scrolled={scrolled}>
|
||||
|
||||
<FilterBar
|
||||
onFilter={receiveFilters}
|
||||
scrolled={scrolled}
|
||||
element={element}
|
||||
raidSlug={raidSlug}
|
||||
recency={recency}>
|
||||
<h1>{t('teams.title')}</h1>
|
||||
</FilterBar>
|
||||
|
||||
|
|
|
|||
7
types/TeamElement.d.ts
vendored
Normal file
7
types/TeamElement.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
interface TeamElement {
|
||||
id: number,
|
||||
name: {
|
||||
en: string,
|
||||
ja: string
|
||||
}
|
||||
}
|
||||
59
utils/Element.tsx
Normal file
59
utils/Element.tsx
Normal 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: "光"
|
||||
}
|
||||
}
|
||||
]
|
||||
Loading…
Reference in a new issue