Break collection pages into hooks (#414)
This refactors the collection pages (teams, saved and profiles) into a bunch of hooks that handle various chunks of functionality. This way, the actual "pages" have significantly less logic and significantly less repeated code. * **useFavorites** handles favoriting teams * **useFilterState** handles the URL query filters * **usePaginationState** simply holds data pertaining to pagination * **useFetchTeams** handles fetching and parsing team data from the server * **useTeamFilter** pulls all other states together and handles some logic that is closest to the page Co-authored-by: Justin Edmund <383021+jedmund@users.noreply.github.com>
This commit is contained in:
parent
4dc2279d68
commit
fc616aab01
17 changed files with 812 additions and 769 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"git.ignoreLimitWarning": true,
|
"git.ignoreLimitWarning": true,
|
||||||
"i18n-ally.localesPaths": ["public/locales"]
|
"i18n-ally.localesPaths": ["public/locales"],
|
||||||
|
"i18n-ally.keystyle": "nested"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ interface Props {
|
||||||
defaultFilterset: FilterSet
|
defaultFilterset: FilterSet
|
||||||
persistFilters?: boolean
|
persistFilters?: boolean
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
scrolled: boolean
|
|
||||||
element?: number
|
element?: number
|
||||||
raid?: string
|
raid?: string
|
||||||
raidGroups: RaidGroup[]
|
raidGroups: RaidGroup[]
|
||||||
|
|
@ -32,6 +31,8 @@ const FilterBar = (props: Props) => {
|
||||||
// Set up translation
|
// Set up translation
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
const [scrolled, setScrolled] = useState(false)
|
||||||
|
|
||||||
const [currentRaid, setCurrentRaid] = useState<Raid>()
|
const [currentRaid, setCurrentRaid] = useState<Raid>()
|
||||||
|
|
||||||
const [recencyOpen, setRecencyOpen] = useState(false)
|
const [recencyOpen, setRecencyOpen] = useState(false)
|
||||||
|
|
@ -44,7 +45,7 @@ const FilterBar = (props: Props) => {
|
||||||
// Set up classes object for showing shadow on scroll
|
// Set up classes object for showing shadow on scroll
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
[styles.filterBar]: true,
|
[styles.filterBar]: true,
|
||||||
[styles.shadow]: props.scrolled,
|
[styles.shadow]: scrolled,
|
||||||
})
|
})
|
||||||
|
|
||||||
const filterButtonClasses = classNames({
|
const filterButtonClasses = classNames({
|
||||||
|
|
@ -52,6 +53,17 @@ const FilterBar = (props: Props) => {
|
||||||
filtersActive: !matchesDefaultFilters,
|
filtersActive: !matchesDefaultFilters,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Add scroll event listener for shadow on FilterBar on mount
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('scroll', handleScroll)
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
function handleScroll() {
|
||||||
|
if (window.scrollY > 90) setScrolled(true)
|
||||||
|
else setScrolled(false)
|
||||||
|
}
|
||||||
|
|
||||||
// Convert raid slug to Raid object on mount
|
// Convert raid slug to Raid object on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const raid = appState.raidGroups
|
const raid = appState.raidGroups
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import type { SearchableObject, SearchableObjectArray } from '~types'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
import CrossIcon from '~public/icons/Cross.svg'
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
import useDidMountEffect from '~hooks/useDidMountEffect'
|
||||||
|
|
||||||
interface Props extends DialogProps {
|
interface Props extends DialogProps {
|
||||||
send: (object: SearchableObject, position: number) => any
|
send: (object: SearchableObject, position: number) => any
|
||||||
|
|
|
||||||
50
hooks/useFavorites.tsx
Normal file
50
hooks/useFavorites.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import clonedeep from 'lodash.clonedeep'
|
||||||
|
import api from '~utils/api'
|
||||||
|
import { PageContextObj } from '~types'
|
||||||
|
|
||||||
|
export const useFavorites = (
|
||||||
|
parties: Party[],
|
||||||
|
setParties: (value: Party[]) => void
|
||||||
|
) => {
|
||||||
|
// Methods: Favorites
|
||||||
|
function toggleFavorite(teamId: string, favorited: boolean) {
|
||||||
|
if (favorited) unsaveFavorite(teamId)
|
||||||
|
else saveFavorite(teamId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveFavorite(teamId: string) {
|
||||||
|
api.saveTeam({ id: teamId }).then((response) => {
|
||||||
|
if (response.status == 201) {
|
||||||
|
const index = parties.findIndex((p) => p.id === teamId)
|
||||||
|
const party = parties[index]
|
||||||
|
|
||||||
|
party.favorited = true
|
||||||
|
|
||||||
|
let clonedParties = clonedeep(parties)
|
||||||
|
clonedParties[index] = party
|
||||||
|
|
||||||
|
setParties(clonedParties)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsaveFavorite(teamId: string) {
|
||||||
|
api.unsaveTeam({ id: teamId }).then((response) => {
|
||||||
|
if (response.status == 200) {
|
||||||
|
const index = parties.findIndex((p) => p.id === teamId)
|
||||||
|
const party = parties[index]
|
||||||
|
|
||||||
|
party.favorited = false
|
||||||
|
|
||||||
|
let clonedParties = clonedeep(parties)
|
||||||
|
clonedParties[index] = party
|
||||||
|
|
||||||
|
setParties(clonedParties)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toggleFavorite,
|
||||||
|
}
|
||||||
|
}
|
||||||
146
hooks/useFetchTeams.tsx
Normal file
146
hooks/useFetchTeams.tsx
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import api from '~utils/api'
|
||||||
|
|
||||||
|
export const useFetchTeams = (
|
||||||
|
currentPage: number,
|
||||||
|
filters: { [key: string]: any },
|
||||||
|
parties: Party[],
|
||||||
|
setParties: (value: Party[]) => void,
|
||||||
|
setTotalPages: (value: number) => void,
|
||||||
|
setRecordCount: (value: number) => void
|
||||||
|
) => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [error, setError] = useState(false)
|
||||||
|
|
||||||
|
function parseTeams(response: { [key: string]: any }, replace: boolean) {
|
||||||
|
const { parties, meta } = response
|
||||||
|
|
||||||
|
setTotalPages(meta.total_pages)
|
||||||
|
setRecordCount(meta.count)
|
||||||
|
|
||||||
|
if (replace) {
|
||||||
|
replaceResults(parties)
|
||||||
|
setIsLoading(false)
|
||||||
|
} else appendResults(parties)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseError(error: any) {
|
||||||
|
setIsLoading(false)
|
||||||
|
setError(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function processTeams(list: Party[], shouldReplace: boolean) {
|
||||||
|
if (shouldReplace) {
|
||||||
|
replaceResults(list)
|
||||||
|
} else {
|
||||||
|
appendResults(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceResults(list: Party[]) {
|
||||||
|
if (list.length > 0) {
|
||||||
|
setParties(list.sort((a, b) => (a.created_at > b.created_at ? -1 : 1)))
|
||||||
|
} else {
|
||||||
|
setParties([])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendResults(list: Party[]) {
|
||||||
|
setParties([...parties, ...list])
|
||||||
|
}
|
||||||
|
|
||||||
|
function createParams() {
|
||||||
|
return {
|
||||||
|
params: Object.entries(filters).reduce((acc, [key, value]) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
acc[key] = value
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as { [key: string]: any }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchTeams = useCallback(
|
||||||
|
({ replace } = { replace: false }) => {
|
||||||
|
if (replace) setIsLoading(true)
|
||||||
|
|
||||||
|
const params = createParams()
|
||||||
|
|
||||||
|
api.endpoints.parties
|
||||||
|
.getAll(params)
|
||||||
|
.then((response) => {
|
||||||
|
const formedResponse = {
|
||||||
|
parties: response.data.results,
|
||||||
|
meta: response.data.meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTeams(formedResponse, replace)
|
||||||
|
})
|
||||||
|
.catch(parseError)
|
||||||
|
},
|
||||||
|
[filters, currentPage]
|
||||||
|
)
|
||||||
|
|
||||||
|
const fetchProfile = useCallback(
|
||||||
|
({
|
||||||
|
username,
|
||||||
|
replace,
|
||||||
|
}: {
|
||||||
|
username: string | undefined
|
||||||
|
replace: boolean
|
||||||
|
}) => {
|
||||||
|
if (replace) setIsLoading(true)
|
||||||
|
|
||||||
|
const params = createParams()
|
||||||
|
|
||||||
|
if (username && !Array.isArray(username)) {
|
||||||
|
api.endpoints.users
|
||||||
|
.getOne({
|
||||||
|
id: username,
|
||||||
|
params: params,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
const formedResponse = {
|
||||||
|
parties: response.data.profile.parties,
|
||||||
|
meta: response.data.meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTeams(formedResponse, replace)
|
||||||
|
})
|
||||||
|
.catch(parseError)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentPage, filters]
|
||||||
|
)
|
||||||
|
|
||||||
|
const fetchSaved = useCallback(
|
||||||
|
({ replace } = { replace: false }) => {
|
||||||
|
if (replace) setIsLoading(true)
|
||||||
|
|
||||||
|
const params = createParams()
|
||||||
|
|
||||||
|
api
|
||||||
|
.savedTeams(params)
|
||||||
|
.then((response) => {
|
||||||
|
const formedResponse = {
|
||||||
|
parties: response.data.results,
|
||||||
|
meta: response.data.meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTeams(formedResponse, replace)
|
||||||
|
})
|
||||||
|
.catch(parseError)
|
||||||
|
},
|
||||||
|
[filters, currentPage]
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetchTeams,
|
||||||
|
fetchProfile,
|
||||||
|
fetchSaved,
|
||||||
|
processTeams,
|
||||||
|
isLoading,
|
||||||
|
setIsLoading,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
}
|
||||||
56
hooks/useFilterState.tsx
Normal file
56
hooks/useFilterState.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { getCookie } from 'cookies-next'
|
||||||
|
import { useQueryState } from 'nuqs'
|
||||||
|
|
||||||
|
import { defaultFilterset } from '~utils/defaultFilters'
|
||||||
|
import { parseElement, serializeElement } from '~utils/parseElement'
|
||||||
|
|
||||||
|
import type { PageContextObj } from '~types'
|
||||||
|
|
||||||
|
export const useFilterState = (context?: PageContextObj) => {
|
||||||
|
const [element, setElement] = useQueryState('element', {
|
||||||
|
defaultValue: -1,
|
||||||
|
history: 'push',
|
||||||
|
parse: (query: string) => parseElement(query),
|
||||||
|
serialize: (value) => serializeElement(value),
|
||||||
|
})
|
||||||
|
|
||||||
|
const [raid, setRaid] = useQueryState('raid', {
|
||||||
|
defaultValue: 'all',
|
||||||
|
history: 'push',
|
||||||
|
parse: (query: string) => {
|
||||||
|
const raids = context?.raidGroups.flatMap((group) => group.raids)
|
||||||
|
const raid = raids?.find((r: Raid) => r.slug === query)
|
||||||
|
return raid ? raid.id : 'all'
|
||||||
|
},
|
||||||
|
serialize: (value) => value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [recency, setRecency] = useQueryState('recency', {
|
||||||
|
defaultValue: -1,
|
||||||
|
history: 'push',
|
||||||
|
parse: (query: string) => parseInt(query),
|
||||||
|
serialize: (value) => `${value}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [advancedFilters, setAdvancedFilters] = useState(defaultFilterset)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const filtersCookie = getCookie('filters')
|
||||||
|
const filters = filtersCookie
|
||||||
|
? JSON.parse(filtersCookie as string)
|
||||||
|
: defaultFilterset
|
||||||
|
setAdvancedFilters(filters)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
element,
|
||||||
|
setElement,
|
||||||
|
raid,
|
||||||
|
setRaid,
|
||||||
|
recency,
|
||||||
|
setRecency,
|
||||||
|
advancedFilters,
|
||||||
|
setAdvancedFilters,
|
||||||
|
}
|
||||||
|
}
|
||||||
16
hooks/usePaginationState.tsx
Normal file
16
hooks/usePaginationState.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export const usePaginationState = () => {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
const [totalPages, setTotalPages] = useState(1)
|
||||||
|
const [recordCount, setRecordCount] = useState(0)
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentPage,
|
||||||
|
setCurrentPage,
|
||||||
|
totalPages,
|
||||||
|
setTotalPages,
|
||||||
|
recordCount,
|
||||||
|
setRecordCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
182
hooks/useTeamFilter.tsx
Normal file
182
hooks/useTeamFilter.tsx
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
// Libraries
|
||||||
|
import { useState, useCallback, useEffect } from 'react'
|
||||||
|
import { getCookie } from 'cookies-next'
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import { useFetchTeams } from './useFetchTeams'
|
||||||
|
import { useFilterState } from './useFilterState'
|
||||||
|
import { usePaginationState } from './usePaginationState'
|
||||||
|
import useDidMountEffect from './useDidMountEffect'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import { CollectionPage } from '~utils/enums'
|
||||||
|
import { convertAdvancedFilters } from '~utils/convertAdvancedFilters'
|
||||||
|
import { defaultFilterset } from '~utils/defaultFilters'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type { PageContextObj, PaginationObject } from '~types'
|
||||||
|
|
||||||
|
export const useTeamFilter = (
|
||||||
|
page: CollectionPage,
|
||||||
|
context?: PageContextObj
|
||||||
|
) => {
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
|
const [parties, setParties] = useState<Party[]>([])
|
||||||
|
|
||||||
|
function constructFilters({
|
||||||
|
element,
|
||||||
|
raid,
|
||||||
|
recency,
|
||||||
|
currentPage,
|
||||||
|
filterSet,
|
||||||
|
}: {
|
||||||
|
element: number
|
||||||
|
raid: string
|
||||||
|
recency: number
|
||||||
|
currentPage: number
|
||||||
|
filterSet: FilterSet
|
||||||
|
}) {
|
||||||
|
const filters: { [key: string]: any } = {
|
||||||
|
element: element !== -1 ? element : undefined,
|
||||||
|
raid: raid === 'all' ? undefined : raid,
|
||||||
|
recency: recency !== -1 ? recency : undefined,
|
||||||
|
page: currentPage,
|
||||||
|
...convertAdvancedFilters(filterSet),
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(filters).forEach(
|
||||||
|
(key) => filters[key] === undefined && delete filters[key]
|
||||||
|
)
|
||||||
|
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
element,
|
||||||
|
setElement,
|
||||||
|
raid,
|
||||||
|
setRaid,
|
||||||
|
recency,
|
||||||
|
setRecency,
|
||||||
|
advancedFilters,
|
||||||
|
setAdvancedFilters,
|
||||||
|
} = useFilterState(context)
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentPage,
|
||||||
|
setCurrentPage,
|
||||||
|
totalPages,
|
||||||
|
setTotalPages,
|
||||||
|
recordCount,
|
||||||
|
setRecordCount,
|
||||||
|
} = usePaginationState()
|
||||||
|
|
||||||
|
const {
|
||||||
|
fetchTeams,
|
||||||
|
fetchProfile,
|
||||||
|
fetchSaved,
|
||||||
|
processTeams,
|
||||||
|
isLoading: isFetching,
|
||||||
|
setIsLoading: setFetching,
|
||||||
|
error: fetchError,
|
||||||
|
} = useFetchTeams(
|
||||||
|
currentPage,
|
||||||
|
constructFilters({
|
||||||
|
element,
|
||||||
|
raid,
|
||||||
|
recency,
|
||||||
|
currentPage,
|
||||||
|
filterSet: advancedFilters,
|
||||||
|
}),
|
||||||
|
parties,
|
||||||
|
setParties,
|
||||||
|
setTotalPages,
|
||||||
|
setRecordCount
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update the advancedFilters state based on cookies when the component mounts
|
||||||
|
useEffect(() => {
|
||||||
|
const filtersCookie = getCookie('filters')
|
||||||
|
const filters = filtersCookie
|
||||||
|
? JSON.parse(filtersCookie as string)
|
||||||
|
: defaultFilterset
|
||||||
|
setAdvancedFilters(filters)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Handle pagination object updates from fetchTeams
|
||||||
|
const setPagination = useCallback((value: PaginationObject) => {
|
||||||
|
setTotalPages(value.totalPages)
|
||||||
|
setRecordCount(value.count)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useDidMountEffect(() => {}, [currentPage])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (context && context.teams && context.pagination) {
|
||||||
|
setTotalPages(context.pagination.totalPages)
|
||||||
|
setRecordCount(context.pagination.count)
|
||||||
|
// processTeams(context.teams, context.pagination.count, true)
|
||||||
|
}
|
||||||
|
setCurrentPage(1)
|
||||||
|
|
||||||
|
setFetching(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// When the element, raid or recency filter changes,
|
||||||
|
// fetch all teams again.
|
||||||
|
useDidMountEffect(() => {
|
||||||
|
setCurrentPage(1)
|
||||||
|
|
||||||
|
if (mounted) fetch(true)
|
||||||
|
|
||||||
|
setMounted(true)
|
||||||
|
}, [element, raid, recency, advancedFilters])
|
||||||
|
|
||||||
|
// When the page changes, fetch all teams again.
|
||||||
|
useDidMountEffect(() => {
|
||||||
|
if (currentPage > 1) fetch(false)
|
||||||
|
else if (currentPage == 1 && mounted) fetch(true)
|
||||||
|
|
||||||
|
setMounted(true)
|
||||||
|
}, [currentPage])
|
||||||
|
|
||||||
|
function fetch(replace: boolean) {
|
||||||
|
switch (page) {
|
||||||
|
case CollectionPage.Teams:
|
||||||
|
return fetchTeams({ replace: replace })
|
||||||
|
case CollectionPage.Profile:
|
||||||
|
return fetchProfile({
|
||||||
|
username: context?.user?.username,
|
||||||
|
replace: replace,
|
||||||
|
})
|
||||||
|
case CollectionPage.Saved:
|
||||||
|
return fetchSaved({
|
||||||
|
replace: replace,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Expose the states and setters for the component to use
|
||||||
|
element,
|
||||||
|
setElement,
|
||||||
|
raid,
|
||||||
|
setRaid,
|
||||||
|
recency,
|
||||||
|
setRecency,
|
||||||
|
advancedFilters,
|
||||||
|
setAdvancedFilters,
|
||||||
|
parties,
|
||||||
|
setParties,
|
||||||
|
isFetching,
|
||||||
|
setFetching,
|
||||||
|
fetchError,
|
||||||
|
fetchTeams,
|
||||||
|
processTeams,
|
||||||
|
currentPage,
|
||||||
|
setCurrentPage,
|
||||||
|
totalPages,
|
||||||
|
recordCount,
|
||||||
|
setPagination,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,37 +1,42 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
// Libraries
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useQueryState } from 'nuqs'
|
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
|
|
||||||
import api from '~utils/api'
|
// Hooks
|
||||||
import extractFilters from '~utils/extractFilters'
|
import { useFavorites } from '~hooks/useFavorites'
|
||||||
|
import { useTeamFilter } from '~hooks/useTeamFilter'
|
||||||
|
import useDidMountEffect from '~hooks/useDidMountEffect'
|
||||||
|
|
||||||
|
// Utils
|
||||||
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
||||||
import { setHeaders } from '~utils/userToken'
|
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
|
import { convertAdvancedFilters } from '~utils/convertAdvancedFilters'
|
||||||
|
import { CollectionPage } from '~utils/enums'
|
||||||
import { permissiveFilterset } from '~utils/defaultFilters'
|
import { permissiveFilterset } from '~utils/defaultFilters'
|
||||||
import { elements, allElement } from '~data/elements'
|
import { setHeaders } from '~utils/userToken'
|
||||||
import { emptyPaginationObject } from '~utils/emptyStates'
|
import {
|
||||||
|
fetchRaidGroupsAndFilters,
|
||||||
|
fetchUserProfile,
|
||||||
|
parseAdvancedFilters,
|
||||||
|
} from '~utils/serverSideUtils'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type { AxiosError } from 'axios'
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import type { PageContextObj, ResponseStatus } from '~types'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import ErrorSection from '~components/ErrorSection'
|
||||||
|
import FilterBar from '~components/filters/FilterBar'
|
||||||
import GridRep from '~components/reps/GridRep'
|
import GridRep from '~components/reps/GridRep'
|
||||||
import GridRepCollection from '~components/reps/GridRepCollection'
|
import GridRepCollection from '~components/reps/GridRepCollection'
|
||||||
import LoadingRep from '~components/reps/LoadingRep'
|
import LoadingRep from '~components/reps/LoadingRep'
|
||||||
import ErrorSection from '~components/ErrorSection'
|
|
||||||
import FilterBar from '~components/filters/FilterBar'
|
|
||||||
import ProfileHead from '~components/head/ProfileHead'
|
import ProfileHead from '~components/head/ProfileHead'
|
||||||
import UserInfo from '~components/filters/UserInfo'
|
import UserInfo from '~components/filters/UserInfo'
|
||||||
|
|
||||||
import type { AxiosError } from 'axios'
|
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
import type {
|
|
||||||
FilterObject,
|
|
||||||
PageContextObj,
|
|
||||||
PaginationObject,
|
|
||||||
ResponseStatus,
|
|
||||||
} from '~types'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
context?: PageContextObj
|
context?: PageContextObj
|
||||||
version: AppUpdate
|
version: AppUpdate
|
||||||
|
|
@ -52,144 +57,48 @@ const ProfileRoute: React.FC<Props> = ({
|
||||||
// Import translations
|
// Import translations
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Set up app-specific states
|
|
||||||
const [mounted, setMounted] = useState(false)
|
|
||||||
const [scrolled, setScrolled] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
|
|
||||||
// Set up page-specific states
|
|
||||||
const [parties, setParties] = useState<Party[]>([])
|
|
||||||
const [raids, setRaids] = useState<Raid[]>()
|
const [raids, setRaids] = useState<Raid[]>()
|
||||||
|
|
||||||
// Set up infinite scrolling-related states
|
const {
|
||||||
const [recordCount, setRecordCount] = useState(0)
|
element,
|
||||||
const [currentPage, setCurrentPage] = useState(1)
|
setElement,
|
||||||
const [totalPages, setTotalPages] = useState(1)
|
raid,
|
||||||
|
setRaid,
|
||||||
|
recency,
|
||||||
|
setRecency,
|
||||||
|
advancedFilters,
|
||||||
|
setAdvancedFilters,
|
||||||
|
currentPage,
|
||||||
|
setCurrentPage,
|
||||||
|
totalPages,
|
||||||
|
recordCount,
|
||||||
|
parties,
|
||||||
|
setParties,
|
||||||
|
isFetching,
|
||||||
|
setFetching,
|
||||||
|
fetchError,
|
||||||
|
fetchTeams,
|
||||||
|
processTeams,
|
||||||
|
setPagination,
|
||||||
|
} = useTeamFilter(CollectionPage.Profile, context)
|
||||||
|
|
||||||
// Set up filter-specific query states
|
const { toggleFavorite } = useFavorites(parties, setParties)
|
||||||
// Recency is in seconds
|
|
||||||
const [element, setElement] = useQueryState('element', {
|
|
||||||
defaultValue: -1,
|
|
||||||
history: 'push',
|
|
||||||
parse: (query: string) => parseElement(query),
|
|
||||||
serialize: (value) => serializeElement(value),
|
|
||||||
})
|
|
||||||
const [raid, setRaid] = useQueryState('raid', {
|
|
||||||
defaultValue: 'all',
|
|
||||||
history: 'push',
|
|
||||||
parse: (query: string) => {
|
|
||||||
const raids = context?.raidGroups.flatMap((group) => group.raids)
|
|
||||||
const raid = raids?.find((r: Raid) => r.slug === query)
|
|
||||||
return raid ? raid.id : 'all'
|
|
||||||
},
|
|
||||||
serialize: (value) => value,
|
|
||||||
})
|
|
||||||
const [recency, setRecency] = useQueryState('recency', {
|
|
||||||
defaultValue: -1,
|
|
||||||
history: 'push',
|
|
||||||
parse: (query: string) => parseInt(query),
|
|
||||||
serialize: (value) => `${value}`,
|
|
||||||
})
|
|
||||||
const [advancedFilters, setAdvancedFilters] =
|
|
||||||
useState<FilterSet>(permissiveFilterset)
|
|
||||||
|
|
||||||
// Define transformers for element
|
|
||||||
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 name = elements[value].name.en.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the initial parties from props
|
// Set the initial parties from props
|
||||||
useEffect(() => {
|
useDidMountEffect(() => {
|
||||||
if (context && context.teams && context.pagination) {
|
if (context) {
|
||||||
setTotalPages(context.pagination.totalPages)
|
if (context.teams && context.pagination) {
|
||||||
setRecordCount(context.pagination.count)
|
processTeams(context.teams, true)
|
||||||
replaceResults(context.pagination.count, context.teams)
|
setPagination(context.pagination)
|
||||||
appState.raidGroups = context.raidGroups
|
|
||||||
appState.version = version
|
appState.raidGroups = context.raidGroups
|
||||||
|
appState.version = version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
}, [])
|
setFetching(false)
|
||||||
|
}, [context])
|
||||||
// Add scroll event listener for shadow on FilterBar on mount
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return () => window.removeEventListener('scroll', handleScroll)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
const handleError = useCallback((error: any) => {
|
|
||||||
if (error.response != null) {
|
|
||||||
console.error(error)
|
|
||||||
} else {
|
|
||||||
// TODO: Put an alert here
|
|
||||||
console.error('There was an error.')
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const fetchProfile = useCallback(
|
|
||||||
({ replace }: { replace: boolean }) => {
|
|
||||||
if (replace) setIsLoading(true)
|
|
||||||
|
|
||||||
const filters = {
|
|
||||||
params: {
|
|
||||||
element: element != -1 ? element : undefined,
|
|
||||||
raid: raid === 'all' ? undefined : raid,
|
|
||||||
recency: recency != -1 ? recency : undefined,
|
|
||||||
page: currentPage,
|
|
||||||
...advancedFilters,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username && !Array.isArray(username)) {
|
|
||||||
api.endpoints.users
|
|
||||||
.getOne({
|
|
||||||
id: username,
|
|
||||||
params: { ...filters },
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
const results = response.data.profile.parties
|
|
||||||
const meta = response.data.meta
|
|
||||||
|
|
||||||
setTotalPages(meta.total_pages)
|
|
||||||
setRecordCount(meta.count)
|
|
||||||
|
|
||||||
if (replace) {
|
|
||||||
setIsLoading(false)
|
|
||||||
replaceResults(meta.count, results)
|
|
||||||
} else appendResults(results)
|
|
||||||
})
|
|
||||||
.catch((error) => handleError(error))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentPage, username, parties, element, raid, recency, advancedFilters]
|
|
||||||
)
|
|
||||||
|
|
||||||
function replaceResults(count: number, list: Party[]) {
|
|
||||||
if (count > 0) {
|
|
||||||
setParties(list.sort((a, b) => (a.created_at > b.created_at ? -1 : 1)))
|
|
||||||
} else {
|
|
||||||
setParties([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendResults(list: Party[]) {
|
|
||||||
setParties([...parties, ...list])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all raids on mount, then find the raid in the URL if present
|
// Fetch all raids on mount, then find the raid in the URL if present
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -197,26 +106,6 @@ const ProfileRoute: React.FC<Props> = ({
|
||||||
setRaids(raids)
|
setRaids(raids)
|
||||||
}, [setRaids])
|
}, [setRaids])
|
||||||
|
|
||||||
// When the element, raid or recency filter changes,
|
|
||||||
// fetch all teams again.
|
|
||||||
useDidMountEffect(() => {
|
|
||||||
setCurrentPage(1)
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
fetchProfile({ replace: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
setMounted(true)
|
|
||||||
}, [username, element, raid, recency, advancedFilters])
|
|
||||||
|
|
||||||
// When the page changes, fetch all teams again.
|
|
||||||
useDidMountEffect(() => {
|
|
||||||
// Current page changed
|
|
||||||
if (currentPage > 1) fetchProfile({ replace: false })
|
|
||||||
else if (currentPage == 1) fetchProfile({ replace: true })
|
|
||||||
setMounted(true)
|
|
||||||
}, [currentPage])
|
|
||||||
|
|
||||||
// Receive filters from the filter bar
|
// Receive filters from the filter bar
|
||||||
function receiveFilters(filters: FilterSet) {
|
function receiveFilters(filters: FilterSet) {
|
||||||
if (filters.element == 0) setElement(0, { shallow: true })
|
if (filters.element == 0) setElement(0, { shallow: true })
|
||||||
|
|
@ -230,10 +119,6 @@ const ProfileRoute: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods: Navigation
|
// Methods: Navigation
|
||||||
function handleScroll() {
|
|
||||||
if (window.pageYOffset > 90) setScrolled(true)
|
|
||||||
else setScrolled(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function goTo(shortcode: string) {
|
function goTo(shortcode: string) {
|
||||||
router.push(`/p/${shortcode}`)
|
router.push(`/p/${shortcode}`)
|
||||||
|
|
@ -249,7 +134,7 @@ const ProfileRoute: React.FC<Props> = ({
|
||||||
else return <div />
|
else return <div />
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add save functions
|
// Page component rendering methods
|
||||||
|
|
||||||
function renderParties() {
|
function renderParties() {
|
||||||
return parties.map((party, i) => {
|
return parties.map((party, i) => {
|
||||||
|
|
@ -257,12 +142,14 @@ const ProfileRoute: React.FC<Props> = ({
|
||||||
<GridRep
|
<GridRep
|
||||||
party={party}
|
party={party}
|
||||||
key={`party-${i}`}
|
key={`party-${i}`}
|
||||||
loading={isLoading}
|
loading={isFetching}
|
||||||
onClick={goTo}
|
onClick={goTo}
|
||||||
|
onSave={(teamId, favorited) => toggleFavorite(teamId, favorited)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLoading(number: number) {
|
function renderLoading(number: number) {
|
||||||
return (
|
return (
|
||||||
<GridRepCollection>
|
<GridRepCollection>
|
||||||
|
|
@ -293,7 +180,6 @@ const ProfileRoute: React.FC<Props> = ({
|
||||||
onAdvancedFilter={receiveAdvancedFilters}
|
onAdvancedFilter={receiveAdvancedFilters}
|
||||||
onFilter={receiveFilters}
|
onFilter={receiveFilters}
|
||||||
persistFilters={false}
|
persistFilters={false}
|
||||||
scrolled={scrolled}
|
|
||||||
element={element}
|
element={element}
|
||||||
raid={raid}
|
raid={raid}
|
||||||
raidGroups={context.raidGroups}
|
raidGroups={context.raidGroups}
|
||||||
|
|
@ -337,46 +223,24 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
const version = await fetchLatestVersion()
|
const version = await fetchLatestVersion()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch and organize raids
|
// We don't pre-load advanced filters here
|
||||||
let raidGroups: RaidGroup[] = await api
|
const { raidGroups, filters } = await fetchRaidGroupsAndFilters(query)
|
||||||
.raidGroups()
|
|
||||||
.then((response) => response.data)
|
|
||||||
|
|
||||||
// Create filter object
|
let context: PageContextObj | undefined = undefined
|
||||||
const filters: FilterObject = extractFilters(query, raidGroups)
|
|
||||||
const params = {
|
|
||||||
params: { ...filters, ...permissiveFilterset },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up empty variables
|
|
||||||
let user: User | undefined = undefined
|
|
||||||
let teams: Party[] | undefined = undefined
|
|
||||||
let pagination: PaginationObject = emptyPaginationObject
|
|
||||||
|
|
||||||
// Perform a request only if we received a username
|
// Perform a request only if we received a username
|
||||||
if (query.username) {
|
if (query.username) {
|
||||||
const response = await api.endpoints.users.getOne({
|
const { user, teams, pagination } = await fetchUserProfile(
|
||||||
id: query.username,
|
query.username,
|
||||||
params,
|
filters
|
||||||
})
|
)
|
||||||
|
|
||||||
// Assign values to pass to props
|
context = {
|
||||||
user = response.data.profile
|
user: user,
|
||||||
|
teams: teams,
|
||||||
if (response.data.profile.parties) teams = response.data.profile.parties
|
raidGroups: raidGroups,
|
||||||
else teams = []
|
pagination: pagination,
|
||||||
|
}
|
||||||
pagination.count = response.data.meta.count
|
|
||||||
pagination.totalPages = response.data.meta.total_pages
|
|
||||||
pagination.perPage = response.data.meta.per_page
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consolidate data into context object
|
|
||||||
const context: PageContextObj = {
|
|
||||||
user: user,
|
|
||||||
teams: teams,
|
|
||||||
raidGroups: raidGroups,
|
|
||||||
pagination: pagination,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass to the page component as props
|
// Pass to the page component as props
|
||||||
|
|
|
||||||
319
pages/saved.tsx
319
pages/saved.tsx
|
|
@ -1,37 +1,41 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
// Libraries
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useQueryState } from 'nuqs'
|
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
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 InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
|
|
||||||
import api from '~utils/api'
|
// Hooks
|
||||||
import { setHeaders } from '~utils/userToken'
|
import { useFavorites } from '~hooks/useFavorites'
|
||||||
import extractFilters from '~utils/extractFilters'
|
import { useTeamFilter } from '~hooks/useTeamFilter'
|
||||||
|
import useDidMountEffect from '~hooks/useDidMountEffect'
|
||||||
|
|
||||||
|
// Utils
|
||||||
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
|
import { convertAdvancedFilters } from '~utils/convertAdvancedFilters'
|
||||||
|
import { CollectionPage } from '~utils/enums'
|
||||||
import { permissiveFilterset } from '~utils/defaultFilters'
|
import { permissiveFilterset } from '~utils/defaultFilters'
|
||||||
import { elements, allElement } from '~data/elements'
|
import { setHeaders } from '~utils/userToken'
|
||||||
import { emptyPaginationObject } from '~utils/emptyStates'
|
import {
|
||||||
|
fetchRaidGroupsAndFilters,
|
||||||
|
fetchSaved,
|
||||||
|
parseAdvancedFilters,
|
||||||
|
} from '~utils/serverSideUtils'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type { AxiosError } from 'axios'
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import type { PageContextObj, ResponseStatus } from '~types'
|
||||||
|
|
||||||
|
// Components
|
||||||
import ErrorSection from '~components/ErrorSection'
|
import ErrorSection from '~components/ErrorSection'
|
||||||
|
import FilterBar from '~components/filters/FilterBar'
|
||||||
import GridRep from '~components/reps/GridRep'
|
import GridRep from '~components/reps/GridRep'
|
||||||
import GridRepCollection from '~components/reps/GridRepCollection'
|
import GridRepCollection from '~components/reps/GridRepCollection'
|
||||||
import LoadingRep from '~components/reps/LoadingRep'
|
import LoadingRep from '~components/reps/LoadingRep'
|
||||||
import FilterBar from '~components/filters/FilterBar'
|
|
||||||
import SavedHead from '~components/head/SavedHead'
|
import SavedHead from '~components/head/SavedHead'
|
||||||
|
|
||||||
import type { AxiosError } from 'axios'
|
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
import type {
|
|
||||||
FilterObject,
|
|
||||||
PageContextObj,
|
|
||||||
PaginationObject,
|
|
||||||
ResponseStatus,
|
|
||||||
} from '~types'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
context?: PageContextObj
|
context?: PageContextObj
|
||||||
version: AppUpdate
|
version: AppUpdate
|
||||||
|
|
@ -51,148 +55,48 @@ const SavedRoute: React.FC<Props> = ({
|
||||||
// Import translations
|
// Import translations
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Set up app-specific states
|
|
||||||
const [mounted, setMounted] = useState(false)
|
|
||||||
const [scrolled, setScrolled] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
|
|
||||||
// Set up page-specific states
|
|
||||||
const [parties, setParties] = useState<Party[]>([])
|
|
||||||
const [raids, setRaids] = useState<Raid[]>()
|
const [raids, setRaids] = useState<Raid[]>()
|
||||||
|
|
||||||
// Set up infinite scrolling-related states
|
const {
|
||||||
const [recordCount, setRecordCount] = useState(0)
|
element,
|
||||||
const [currentPage, setCurrentPage] = useState(1)
|
setElement,
|
||||||
const [totalPages, setTotalPages] = useState(1)
|
raid,
|
||||||
|
setRaid,
|
||||||
|
recency,
|
||||||
|
setRecency,
|
||||||
|
advancedFilters,
|
||||||
|
setAdvancedFilters,
|
||||||
|
currentPage,
|
||||||
|
setCurrentPage,
|
||||||
|
totalPages,
|
||||||
|
recordCount,
|
||||||
|
parties,
|
||||||
|
setParties,
|
||||||
|
isFetching,
|
||||||
|
setFetching,
|
||||||
|
fetchError,
|
||||||
|
fetchTeams,
|
||||||
|
processTeams,
|
||||||
|
setPagination,
|
||||||
|
} = useTeamFilter(CollectionPage.Saved, context)
|
||||||
|
|
||||||
// Set up filter-specific query states
|
const { toggleFavorite } = useFavorites(parties, setParties)
|
||||||
// Recency is in seconds
|
|
||||||
const [element, setElement] = useQueryState('element', {
|
|
||||||
defaultValue: -1,
|
|
||||||
history: 'push',
|
|
||||||
parse: (query: string) => parseElement(query),
|
|
||||||
serialize: (value) => serializeElement(value),
|
|
||||||
})
|
|
||||||
const [raid, setRaid] = useQueryState('raid', {
|
|
||||||
defaultValue: 'all',
|
|
||||||
history: 'push',
|
|
||||||
parse: (query: string) => {
|
|
||||||
const raids = context?.raidGroups.flatMap((group) => group.raids)
|
|
||||||
const raid = raids?.find((r: Raid) => r.slug === query)
|
|
||||||
return raid ? raid.id : 'all'
|
|
||||||
},
|
|
||||||
serialize: (value) => value,
|
|
||||||
})
|
|
||||||
const [recency, setRecency] = useQueryState('recency', {
|
|
||||||
defaultValue: -1,
|
|
||||||
history: 'push',
|
|
||||||
parse: (query: string) => parseInt(query),
|
|
||||||
serialize: (value) => `${value}`,
|
|
||||||
})
|
|
||||||
const [advancedFilters, setAdvancedFilters] =
|
|
||||||
useState<FilterSet>(permissiveFilterset)
|
|
||||||
|
|
||||||
// Define transformers for element
|
|
||||||
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 name = elements[value].name.en.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the initial parties from props
|
// Set the initial parties from props
|
||||||
useEffect(() => {
|
useDidMountEffect(() => {
|
||||||
if (context && context.teams && context.pagination) {
|
if (context) {
|
||||||
setTotalPages(context.pagination.totalPages)
|
if (context.teams && context.pagination) {
|
||||||
setRecordCount(context.pagination.count)
|
processTeams(context.teams, true)
|
||||||
replaceResults(context.pagination.count, context.teams)
|
setPagination(context.pagination)
|
||||||
appState.raidGroups = context.raidGroups
|
|
||||||
appState.version = version
|
appState.raidGroups = context.raidGroups
|
||||||
|
appState.version = version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
}, [])
|
setFetching(false)
|
||||||
|
}, [context])
|
||||||
// Add scroll event listener for shadow on FilterBar on mount
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return () => window.removeEventListener('scroll', handleScroll)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
const handleError = useCallback((error: any) => {
|
|
||||||
if (error.response != null) {
|
|
||||||
console.error(error)
|
|
||||||
} else {
|
|
||||||
console.error('There was an error.')
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const fetchTeams = useCallback(
|
|
||||||
({ replace }: { replace: boolean }) => {
|
|
||||||
if (replace) setIsLoading(true)
|
|
||||||
|
|
||||||
const filters: {
|
|
||||||
[key: string]: any
|
|
||||||
} = {
|
|
||||||
element: element !== -1 ? element : undefined,
|
|
||||||
raid: raid === 'all' ? undefined : raid,
|
|
||||||
recency: recency !== -1 ? recency : undefined,
|
|
||||||
page: currentPage,
|
|
||||||
...advancedFilters,
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(filters).forEach(
|
|
||||||
(key) => filters[key] === undefined && delete filters[key]
|
|
||||||
)
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
params: {
|
|
||||||
...filters,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
api
|
|
||||||
.savedTeams(params)
|
|
||||||
.then((response) => {
|
|
||||||
const results = response.data.results
|
|
||||||
const meta = response.data.meta
|
|
||||||
|
|
||||||
setTotalPages(meta.total_pages)
|
|
||||||
setRecordCount(meta.count)
|
|
||||||
|
|
||||||
if (replace) {
|
|
||||||
setIsLoading(false)
|
|
||||||
replaceResults(meta.count, results)
|
|
||||||
} else appendResults(results)
|
|
||||||
})
|
|
||||||
.catch((error) => handleError(error))
|
|
||||||
},
|
|
||||||
[currentPage, parties, element, raid, recency, advancedFilters]
|
|
||||||
)
|
|
||||||
|
|
||||||
function replaceResults(count: number, list: Party[]) {
|
|
||||||
if (count > 0) {
|
|
||||||
setParties(list)
|
|
||||||
} else {
|
|
||||||
setParties([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendResults(list: Party[]) {
|
|
||||||
setParties([...parties, ...list])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all raids on mount, then find the raid in the URL if present
|
// Fetch all raids on mount, then find the raid in the URL if present
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -200,26 +104,6 @@ const SavedRoute: React.FC<Props> = ({
|
||||||
setRaids(raids)
|
setRaids(raids)
|
||||||
}, [setRaids])
|
}, [setRaids])
|
||||||
|
|
||||||
// When the element, raid or recency filter changes,
|
|
||||||
// fetch all teams again.
|
|
||||||
useDidMountEffect(() => {
|
|
||||||
setCurrentPage(1)
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
fetchTeams({ replace: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
setMounted(true)
|
|
||||||
}, [element, raid, recency, advancedFilters])
|
|
||||||
|
|
||||||
// When the page changes, fetch all teams again.
|
|
||||||
useDidMountEffect(() => {
|
|
||||||
// Current page changed
|
|
||||||
if (currentPage > 1) fetchTeams({ replace: false })
|
|
||||||
else if (currentPage == 1) fetchTeams({ replace: true })
|
|
||||||
setMounted(true)
|
|
||||||
}, [currentPage])
|
|
||||||
|
|
||||||
// Receive filters from the filter bar
|
// Receive filters from the filter bar
|
||||||
function receiveFilters(filters: FilterSet) {
|
function receiveFilters(filters: FilterSet) {
|
||||||
if (filters.element == 0) setElement(0, { shallow: true })
|
if (filters.element == 0) setElement(0, { shallow: true })
|
||||||
|
|
@ -232,49 +116,7 @@ const SavedRoute: React.FC<Props> = ({
|
||||||
setAdvancedFilters(filters)
|
setAdvancedFilters(filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods: Favorites
|
|
||||||
function toggleFavorite(teamId: string, favorited: boolean) {
|
|
||||||
if (favorited) unsaveFavorite(teamId)
|
|
||||||
else saveFavorite(teamId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveFavorite(teamId: string) {
|
|
||||||
api.saveTeam({ id: teamId }).then((response) => {
|
|
||||||
if (response.status == 201) {
|
|
||||||
const index = parties.findIndex((p) => p.id === teamId)
|
|
||||||
const party = parties[index]
|
|
||||||
|
|
||||||
party.favorited = true
|
|
||||||
|
|
||||||
let clonedParties = clonedeep(parties)
|
|
||||||
clonedParties[index] = party
|
|
||||||
|
|
||||||
setParties(clonedParties)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function unsaveFavorite(teamId: string) {
|
|
||||||
api.unsaveTeam({ id: teamId }).then((response) => {
|
|
||||||
if (response.status == 200) {
|
|
||||||
const index = parties.findIndex((p) => p.id === teamId)
|
|
||||||
const party = parties[index]
|
|
||||||
|
|
||||||
party.favorited = false
|
|
||||||
|
|
||||||
let clonedParties = clonedeep(parties)
|
|
||||||
clonedParties.splice(index, 1)
|
|
||||||
|
|
||||||
setParties(clonedParties)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods: Navigation
|
// Methods: Navigation
|
||||||
function handleScroll() {
|
|
||||||
if (window.pageYOffset > 90) setScrolled(true)
|
|
||||||
else setScrolled(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function goTo(shortcode: string) {
|
function goTo(shortcode: string) {
|
||||||
router.push(`/p/${shortcode}`)
|
router.push(`/p/${shortcode}`)
|
||||||
|
|
@ -290,13 +132,15 @@ const SavedRoute: React.FC<Props> = ({
|
||||||
else return <div />
|
else return <div />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Page component rendering methods
|
||||||
|
|
||||||
function renderParties() {
|
function renderParties() {
|
||||||
return parties.map((party, i) => {
|
return parties.map((party, i) => {
|
||||||
return (
|
return (
|
||||||
<GridRep
|
<GridRep
|
||||||
party={party}
|
party={party}
|
||||||
key={`party-${i}`}
|
key={`party-${i}`}
|
||||||
loading={isLoading}
|
loading={isFetching}
|
||||||
onClick={goTo}
|
onClick={goTo}
|
||||||
onSave={toggleFavorite}
|
onSave={toggleFavorite}
|
||||||
/>
|
/>
|
||||||
|
|
@ -334,7 +178,6 @@ const SavedRoute: React.FC<Props> = ({
|
||||||
onFilter={receiveFilters}
|
onFilter={receiveFilters}
|
||||||
onAdvancedFilter={receiveAdvancedFilters}
|
onAdvancedFilter={receiveAdvancedFilters}
|
||||||
persistFilters={false}
|
persistFilters={false}
|
||||||
scrolled={scrolled}
|
|
||||||
element={element}
|
element={element}
|
||||||
raid={raid}
|
raid={raid}
|
||||||
raidGroups={context.raidGroups}
|
raidGroups={context.raidGroups}
|
||||||
|
|
@ -378,42 +221,14 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
const version = await fetchLatestVersion()
|
const version = await fetchLatestVersion()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch and organize raids
|
// We don't pre-load advanced filters here
|
||||||
let raidGroups: RaidGroup[] = await api
|
const { raidGroups, filters } = await fetchRaidGroupsAndFilters(query)
|
||||||
.raidGroups()
|
const { teams, pagination } = await fetchSaved(filters)
|
||||||
.then((response) => response.data)
|
|
||||||
|
|
||||||
// Create filter object
|
|
||||||
const filters: FilterObject = extractFilters(query, raidGroups)
|
|
||||||
const params = {
|
|
||||||
params: { ...filters, ...permissiveFilterset },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up empty variables
|
|
||||||
let teams: Party[] | undefined = undefined
|
|
||||||
let pagination: PaginationObject = emptyPaginationObject
|
|
||||||
|
|
||||||
// Fetch initial set of saved parties
|
|
||||||
const response = await api.savedTeams(params)
|
|
||||||
|
|
||||||
// Assign values to pass to props
|
|
||||||
teams = response.data.results
|
|
||||||
pagination.count = response.data.meta.count
|
|
||||||
pagination.totalPages = response.data.meta.total_pages
|
|
||||||
pagination.perPage = response.data.meta.per_page
|
|
||||||
|
|
||||||
// Consolidate data into context object
|
|
||||||
const context: PageContextObj = {
|
|
||||||
teams: teams,
|
|
||||||
raidGroups: raidGroups,
|
|
||||||
pagination: pagination,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass to the page component as props
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
context: context,
|
context: { teams, raidGroups, pagination },
|
||||||
version: version,
|
version,
|
||||||
error: false,
|
error: false,
|
||||||
...(await serverSideTranslations(locale, ['common'])),
|
...(await serverSideTranslations(locale, ['common'])),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
377
pages/teams.tsx
377
pages/teams.tsx
|
|
@ -1,38 +1,40 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
// Libraries
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { getCookie } from 'cookies-next'
|
|
||||||
import { useQueryState } from 'nuqs'
|
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
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 axios, { AxiosResponse } from 'axios'
|
||||||
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
|
|
||||||
import api from '~utils/api'
|
// Hooks
|
||||||
import { setHeaders } from '~utils/userToken'
|
import { useFavorites } from '~hooks/useFavorites'
|
||||||
import extractFilters from '~utils/extractFilters'
|
import { useTeamFilter } from '~hooks/useTeamFilter'
|
||||||
|
import useDidMountEffect from '~hooks/useDidMountEffect'
|
||||||
|
|
||||||
|
// Utils
|
||||||
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
import { defaultFilterset } from '~utils/defaultFilters'
|
|
||||||
import { elements, allElement } from '~data/elements'
|
|
||||||
import { emptyPaginationObject } from '~utils/emptyStates'
|
|
||||||
import { convertAdvancedFilters } from '~utils/convertAdvancedFilters'
|
import { convertAdvancedFilters } from '~utils/convertAdvancedFilters'
|
||||||
|
import { defaultFilterset } from '~utils/defaultFilters'
|
||||||
|
import { setHeaders } from '~utils/userToken'
|
||||||
|
import {
|
||||||
|
fetchParties,
|
||||||
|
fetchRaidGroupsAndFilters,
|
||||||
|
parseAdvancedFilters,
|
||||||
|
} from '~utils/serverSideUtils'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import type { PageContextObj, ResponseStatus } from '~types'
|
||||||
|
|
||||||
|
// Components
|
||||||
import ErrorSection from '~components/ErrorSection'
|
import ErrorSection from '~components/ErrorSection'
|
||||||
|
import FilterBar from '~components/filters/FilterBar'
|
||||||
import GridRep from '~components/reps/GridRep'
|
import GridRep from '~components/reps/GridRep'
|
||||||
import GridRepCollection from '~components/reps/GridRepCollection'
|
import GridRepCollection from '~components/reps/GridRepCollection'
|
||||||
import LoadingRep from '~components/reps/LoadingRep'
|
import LoadingRep from '~components/reps/LoadingRep'
|
||||||
import FilterBar from '~components/filters/FilterBar'
|
|
||||||
import TeamsHead from '~components/head/TeamsHead'
|
import TeamsHead from '~components/head/TeamsHead'
|
||||||
|
import { CollectionPage } from '~utils/enums'
|
||||||
import type { AxiosError } from 'axios'
|
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
import type {
|
|
||||||
FilterObject,
|
|
||||||
PageContextObj,
|
|
||||||
PaginationObject,
|
|
||||||
ResponseStatus,
|
|
||||||
} from '~types'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
context?: PageContextObj
|
context?: PageContextObj
|
||||||
|
|
@ -47,166 +49,51 @@ const TeamsRoute: React.FC<Props> = ({
|
||||||
error,
|
error,
|
||||||
status,
|
status,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
// Set up router
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// Import translations
|
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Set up app-specific states
|
|
||||||
const [mounted, setMounted] = useState(false)
|
|
||||||
const [scrolled, setScrolled] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
|
|
||||||
// Set up page-specific states
|
|
||||||
const [parties, setParties] = useState<Party[]>([])
|
|
||||||
const [raids, setRaids] = useState<Raid[]>()
|
const [raids, setRaids] = useState<Raid[]>()
|
||||||
|
|
||||||
// Set up infinite scrolling-related states
|
const {
|
||||||
const [recordCount, setRecordCount] = useState(0)
|
element,
|
||||||
const [currentPage, setCurrentPage] = useState(1)
|
setElement,
|
||||||
const [totalPages, setTotalPages] = useState(1)
|
raid,
|
||||||
|
setRaid,
|
||||||
|
recency,
|
||||||
|
setRecency,
|
||||||
|
advancedFilters,
|
||||||
|
setAdvancedFilters,
|
||||||
|
currentPage,
|
||||||
|
setCurrentPage,
|
||||||
|
totalPages,
|
||||||
|
recordCount,
|
||||||
|
parties,
|
||||||
|
setParties,
|
||||||
|
isFetching,
|
||||||
|
setFetching,
|
||||||
|
fetchError,
|
||||||
|
fetchTeams,
|
||||||
|
processTeams,
|
||||||
|
setPagination,
|
||||||
|
} = useTeamFilter(CollectionPage.Teams, context)
|
||||||
|
|
||||||
// Set up filter-specific query states
|
const { toggleFavorite } = useFavorites(parties, setParties)
|
||||||
// Recency is in seconds
|
|
||||||
const [element, setElement] = useQueryState('element', {
|
|
||||||
defaultValue: -1,
|
|
||||||
history: 'push',
|
|
||||||
parse: (query: string) => parseElement(query),
|
|
||||||
serialize: (value) => serializeElement(value),
|
|
||||||
})
|
|
||||||
const [raid, setRaid] = useQueryState('raid', {
|
|
||||||
defaultValue: 'all',
|
|
||||||
history: 'push',
|
|
||||||
parse: (query: string) => {
|
|
||||||
const raids = context?.raidGroups.flatMap((group) => group.raids)
|
|
||||||
const raid = raids?.find((r: Raid) => r.slug === query)
|
|
||||||
return raid ? raid.id : 'all'
|
|
||||||
},
|
|
||||||
serialize: (value) => value,
|
|
||||||
})
|
|
||||||
const [recency, setRecency] = useQueryState('recency', {
|
|
||||||
defaultValue: -1,
|
|
||||||
history: 'push',
|
|
||||||
parse: (query: string) => parseInt(query),
|
|
||||||
serialize: (value) => `${value}`,
|
|
||||||
})
|
|
||||||
const [advancedFilters, setAdvancedFilters] =
|
|
||||||
useState<FilterSet>(defaultFilterset)
|
|
||||||
|
|
||||||
// Define transformers for element
|
|
||||||
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 name = elements[value].name.en.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the initial parties from props
|
// Set the initial parties from props
|
||||||
useEffect(() => {
|
useDidMountEffect(() => {
|
||||||
if (context && context.teams && context.pagination) {
|
if (context) {
|
||||||
setTotalPages(context.pagination.totalPages)
|
if (context.teams && context.pagination) {
|
||||||
setRecordCount(context.pagination.count)
|
processTeams(context.teams, true)
|
||||||
replaceResults(context.pagination.count, context.teams)
|
setPagination(context.pagination)
|
||||||
appState.raidGroups = context.raidGroups
|
|
||||||
appState.version = version
|
appState.raidGroups = context.raidGroups
|
||||||
|
appState.version = version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
|
setFetching(false)
|
||||||
setIsLoading(false)
|
}, [context])
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Add scroll event listener for shadow on FilterBar on mount
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return () => window.removeEventListener('scroll', handleScroll)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Fetch the user's advanced filters
|
|
||||||
useEffect(() => {
|
|
||||||
const filtersCookie = getCookie('filters')
|
|
||||||
const filters = filtersCookie
|
|
||||||
? JSON.parse(filtersCookie as string)
|
|
||||||
: defaultFilterset
|
|
||||||
|
|
||||||
setAdvancedFilters(filters)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
const handleError = useCallback((error: any) => {
|
|
||||||
if (error.response != null) {
|
|
||||||
console.error(error)
|
|
||||||
} else {
|
|
||||||
console.error('There was an error.')
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const fetchTeams = useCallback(
|
|
||||||
({ replace }: { replace: boolean }) => {
|
|
||||||
if (replace) setIsLoading(true)
|
|
||||||
|
|
||||||
const filters: {
|
|
||||||
[key: string]: any
|
|
||||||
} = {
|
|
||||||
element: element !== -1 ? element : undefined,
|
|
||||||
raid: raid === 'all' ? undefined : raid,
|
|
||||||
recency: recency !== -1 ? recency : undefined,
|
|
||||||
page: currentPage,
|
|
||||||
...convertAdvancedFilters(advancedFilters),
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(filters).forEach(
|
|
||||||
(key) => filters[key] === undefined && delete filters[key]
|
|
||||||
)
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
params: {
|
|
||||||
...filters,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
api.endpoints.parties
|
|
||||||
.getAll(params)
|
|
||||||
.then((response) => {
|
|
||||||
const results = response.data.results
|
|
||||||
const meta = response.data.meta
|
|
||||||
|
|
||||||
setTotalPages(meta.total_pages)
|
|
||||||
setRecordCount(meta.count)
|
|
||||||
|
|
||||||
if (replace) {
|
|
||||||
replaceResults(meta.count, results)
|
|
||||||
setIsLoading(false)
|
|
||||||
} else appendResults(results)
|
|
||||||
})
|
|
||||||
.catch((error) => handleError(error))
|
|
||||||
},
|
|
||||||
[currentPage, parties, element, raid, recency, advancedFilters]
|
|
||||||
)
|
|
||||||
|
|
||||||
function replaceResults(count: number, list: Party[]) {
|
|
||||||
if (count > 0) {
|
|
||||||
setParties(list.sort((a, b) => (a.created_at > b.created_at ? -1 : 1)))
|
|
||||||
} else {
|
|
||||||
setParties([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendResults(list: Party[]) {
|
|
||||||
setParties([...parties, ...list])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all raids on mount, then find the raid in the URL if present
|
// Fetch all raids on mount, then find the raid in the URL if present
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -214,26 +101,6 @@ const TeamsRoute: React.FC<Props> = ({
|
||||||
setRaids(raids)
|
setRaids(raids)
|
||||||
}, [setRaids])
|
}, [setRaids])
|
||||||
|
|
||||||
// When the element, raid or recency filter changes,
|
|
||||||
// fetch all teams again.
|
|
||||||
useDidMountEffect(() => {
|
|
||||||
setCurrentPage(1)
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
fetchTeams({ replace: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
setMounted(true)
|
|
||||||
}, [element, raid, recency, advancedFilters])
|
|
||||||
|
|
||||||
// When the page changes, fetch all teams again.
|
|
||||||
useDidMountEffect(() => {
|
|
||||||
// Current page changed
|
|
||||||
if (currentPage > 1) fetchTeams({ replace: false })
|
|
||||||
else if (currentPage == 1 && mounted) fetchTeams({ replace: true })
|
|
||||||
setMounted(true)
|
|
||||||
}, [currentPage])
|
|
||||||
|
|
||||||
// Receive filters from the filter bar
|
// Receive filters from the filter bar
|
||||||
function receiveFilters(filters: FilterSet) {
|
function receiveFilters(filters: FilterSet) {
|
||||||
if (filters.element == 0) setElement(0, { shallow: true })
|
if (filters.element == 0) setElement(0, { shallow: true })
|
||||||
|
|
@ -246,49 +113,7 @@ const TeamsRoute: React.FC<Props> = ({
|
||||||
setAdvancedFilters(filters)
|
setAdvancedFilters(filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods: Favorites
|
|
||||||
function toggleFavorite(teamId: string, favorited: boolean) {
|
|
||||||
if (favorited) unsaveFavorite(teamId)
|
|
||||||
else saveFavorite(teamId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveFavorite(teamId: string) {
|
|
||||||
api.saveTeam({ id: teamId }).then((response) => {
|
|
||||||
if (response.status == 201) {
|
|
||||||
const index = parties.findIndex((p) => p.id === teamId)
|
|
||||||
const party = parties[index]
|
|
||||||
|
|
||||||
party.favorited = true
|
|
||||||
|
|
||||||
let clonedParties = clonedeep(parties)
|
|
||||||
clonedParties[index] = party
|
|
||||||
|
|
||||||
setParties(clonedParties)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function unsaveFavorite(teamId: string) {
|
|
||||||
api.unsaveTeam({ id: teamId }).then((response) => {
|
|
||||||
if (response.status == 200) {
|
|
||||||
const index = parties.findIndex((p) => p.id === teamId)
|
|
||||||
const party = parties[index]
|
|
||||||
|
|
||||||
party.favorited = false
|
|
||||||
|
|
||||||
let clonedParties = clonedeep(parties)
|
|
||||||
clonedParties[index] = party
|
|
||||||
|
|
||||||
setParties(clonedParties)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods: Navigation
|
// Methods: Navigation
|
||||||
function handleScroll() {
|
|
||||||
if (window.pageYOffset > 90) setScrolled(true)
|
|
||||||
else setScrolled(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function goTo(shortcode: string) {
|
function goTo(shortcode: string) {
|
||||||
router.push(`/p/${shortcode}`)
|
router.push(`/p/${shortcode}`)
|
||||||
|
|
@ -304,24 +129,23 @@ const TeamsRoute: React.FC<Props> = ({
|
||||||
else return <div />
|
else return <div />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Page component rendering methods
|
||||||
function renderParties() {
|
function renderParties() {
|
||||||
return parties.map((party, i) => {
|
return parties.map((party, i) => (
|
||||||
return (
|
<GridRep
|
||||||
<GridRep
|
party={party}
|
||||||
party={party}
|
key={`party-${i}`}
|
||||||
key={`party-${i}`}
|
loading={isFetching}
|
||||||
loading={isLoading}
|
onClick={() => goTo(party.shortcode)}
|
||||||
onClick={goTo}
|
onSave={(teamId, favorited) => toggleFavorite(teamId, favorited)}
|
||||||
onSave={toggleFavorite}
|
/>
|
||||||
/>
|
))
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLoading(number: number) {
|
function renderLoading(number: number) {
|
||||||
return (
|
return (
|
||||||
<GridRepCollection>
|
<GridRepCollection>
|
||||||
{Array.from(Array(number)).map((x, i) => (
|
{Array.from({ length: number }, (_, i) => (
|
||||||
<LoadingRep key={`loading-${i}`} />
|
<LoadingRep key={`loading-${i}`} />
|
||||||
))}
|
))}
|
||||||
</GridRepCollection>
|
</GridRepCollection>
|
||||||
|
|
@ -348,7 +172,6 @@ const TeamsRoute: React.FC<Props> = ({
|
||||||
onFilter={receiveFilters}
|
onFilter={receiveFilters}
|
||||||
onAdvancedFilter={receiveAdvancedFilters}
|
onAdvancedFilter={receiveAdvancedFilters}
|
||||||
persistFilters={true}
|
persistFilters={true}
|
||||||
scrolled={scrolled}
|
|
||||||
element={element}
|
element={element}
|
||||||
raid={raid}
|
raid={raid}
|
||||||
raidGroups={context.raidGroups}
|
raidGroups={context.raidGroups}
|
||||||
|
|
@ -381,65 +204,35 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
// Fetch latest version
|
// Fetch latest version
|
||||||
const version = await fetchLatestVersion()
|
const version = await fetchLatestVersion()
|
||||||
|
|
||||||
// Fetch user's advanced filters
|
|
||||||
const filtersCookie = getCookie('filters', { req: req, res: res })
|
|
||||||
const advancedFilters = filtersCookie ? JSON.parse(filtersCookie as string) : undefined
|
|
||||||
const convertedFilters = advancedFilters ? convertAdvancedFilters(advancedFilters) : undefined
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch and organize raids
|
const advancedFilters = parseAdvancedFilters(req, res)
|
||||||
let raidGroups: RaidGroup[] = await api
|
const convertedFilters = advancedFilters
|
||||||
.raidGroups()
|
? convertAdvancedFilters(advancedFilters)
|
||||||
.then((response) => response.data)
|
: undefined
|
||||||
|
const { raidGroups, filters } = await fetchRaidGroupsAndFilters(query)
|
||||||
|
const { teams, pagination } = await fetchParties(filters, convertedFilters)
|
||||||
|
|
||||||
// Create filter object
|
|
||||||
const filters: FilterObject = extractFilters(query, raidGroups)
|
|
||||||
const params = {
|
|
||||||
params: { ...filters, ...convertedFilters },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up empty variables
|
|
||||||
let teams: Party[] | undefined = undefined
|
|
||||||
let pagination: PaginationObject = emptyPaginationObject
|
|
||||||
|
|
||||||
// Fetch initial set of parties
|
|
||||||
const response = await api.endpoints.parties.getAll(params)
|
|
||||||
|
|
||||||
// Assign values to pass to props
|
|
||||||
teams = response.data.results
|
|
||||||
pagination.count = response.data.meta.count
|
|
||||||
pagination.totalPages = response.data.meta.total_pages
|
|
||||||
pagination.perPage = response.data.meta.per_page
|
|
||||||
|
|
||||||
// Consolidate data into context object
|
|
||||||
const context: PageContextObj = {
|
|
||||||
teams: teams,
|
|
||||||
raidGroups: raidGroups,
|
|
||||||
pagination: pagination,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass to the page component as props
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
context: context,
|
context: { teams, raidGroups, pagination },
|
||||||
version: version,
|
version,
|
||||||
error: false,
|
error: false,
|
||||||
...(await serverSideTranslations(locale, ['common'])),
|
...(await serverSideTranslations(locale, ['common'])),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Extract the underlying Axios error
|
// If error is of type AxiosError, extract the response into a variable
|
||||||
const axiosError = error as AxiosError
|
let response: AxiosResponse<any, any> | undefined = axios.isAxiosError(error)
|
||||||
const response = axiosError.response
|
? error.response
|
||||||
|
: undefined
|
||||||
// Pass to the page component as props
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
context: null,
|
context: null,
|
||||||
error: true,
|
error: true,
|
||||||
status: {
|
status: {
|
||||||
code: response ? response.status : -999,
|
code: response?.status ?? -999,
|
||||||
text: response ? response.statusText : "unspecified_error",
|
text: response?.statusText ?? 'unspecified_error',
|
||||||
},
|
},
|
||||||
...(await serverSideTranslations(locale, ['common'])),
|
...(await serverSideTranslations(locale, ['common'])),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
5
types/FilterSet.d.ts
vendored
5
types/FilterSet.d.ts
vendored
|
|
@ -16,3 +16,8 @@ interface FilterSet {
|
||||||
includes?: MentionItem[]
|
includes?: MentionItem[]
|
||||||
excludes?: MentionItem[]
|
excludes?: MentionItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ConvertedFilters extends Omit<FilterSet, 'includes' | 'excludes'> {
|
||||||
|
includes: string
|
||||||
|
excludes: string
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
import cloneDeep from 'lodash.clonedeep'
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
export function convertAdvancedFilters(filters: FilterSet) {
|
export function convertAdvancedFilters(filters: FilterSet): ConvertedFilters {
|
||||||
let copy = cloneDeep(filters)
|
let copy: FilterSet = cloneDeep(filters)
|
||||||
|
|
||||||
const includes = filterString(filters.includes || [])
|
const includes: string = filterString(filters.includes || [])
|
||||||
const excludes = filterString(filters.excludes || [])
|
const excludes: string = filterString(filters.excludes || [])
|
||||||
|
|
||||||
delete copy.includes
|
delete (copy as any).includes
|
||||||
delete copy.excludes
|
delete (copy as any).excludes
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...copy,
|
...copy,
|
||||||
includes,
|
includes,
|
||||||
excludes,
|
excludes,
|
||||||
}
|
} as ConvertedFilters
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterString(list: MentionItem[]) {
|
export function filterString(list: MentionItem[]): string {
|
||||||
return list.map((item) => item.granblue_id).join(',')
|
return list.map((item) => item.granblue_id).join(',')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,3 +24,9 @@ export enum AboutTabs {
|
||||||
Updates,
|
Updates,
|
||||||
Roadmap,
|
Roadmap,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CollectionPage {
|
||||||
|
Teams,
|
||||||
|
Profile,
|
||||||
|
Saved,
|
||||||
|
}
|
||||||
|
|
|
||||||
20
utils/parseElement.tsx
Normal file
20
utils/parseElement.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { elements, allElement } from '~data/elements'
|
||||||
|
|
||||||
|
export function parseElement(query: string) {
|
||||||
|
let element: TeamElement | undefined =
|
||||||
|
query === 'all'
|
||||||
|
? allElement
|
||||||
|
: elements.find((element) => element.name.en.toLowerCase() === query)
|
||||||
|
return element ? element.id : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeElement(value: number | undefined) {
|
||||||
|
let name = ''
|
||||||
|
|
||||||
|
if (value != undefined) {
|
||||||
|
if (value == -1) name = allElement.name.en.toLowerCase()
|
||||||
|
else name = elements[value].name.en.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
77
utils/serverSideUtils.tsx
Normal file
77
utils/serverSideUtils.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { getCookie } from 'cookies-next'
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import api from './api'
|
||||||
|
import extractFilters from './extractFilters'
|
||||||
|
import { FilterObject } from '~types'
|
||||||
|
import { permissiveFilterset } from './defaultFilters'
|
||||||
|
|
||||||
|
// Parse advanced filters from cookies
|
||||||
|
export function parseAdvancedFilters(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
const filtersCookie = getCookie('filters', { req, res })
|
||||||
|
return filtersCookie ? JSON.parse(filtersCookie as string) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch raid groups and create filter object
|
||||||
|
export async function fetchRaidGroupsAndFilters(query: {
|
||||||
|
[index: string]: string
|
||||||
|
}) {
|
||||||
|
const raidGroups = await api.raidGroups().then((response) => response.data)
|
||||||
|
const filters = extractFilters(query, raidGroups)
|
||||||
|
return { raidGroups, filters }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch initial set of parties
|
||||||
|
export async function fetchParties(
|
||||||
|
filters: FilterObject,
|
||||||
|
convertedFilters: ConvertedFilters | undefined
|
||||||
|
) {
|
||||||
|
const params = { params: { ...filters, ...convertedFilters } }
|
||||||
|
const response = await api.endpoints.parties.getAll(params)
|
||||||
|
|
||||||
|
return {
|
||||||
|
teams: response.data.results,
|
||||||
|
pagination: {
|
||||||
|
count: response.data.meta.count,
|
||||||
|
totalPages: response.data.meta.total_pages,
|
||||||
|
perPage: response.data.meta.per_page,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchUserProfile(
|
||||||
|
username: string,
|
||||||
|
filters: FilterObject
|
||||||
|
) {
|
||||||
|
const params = { params: { ...filters, ...permissiveFilterset } }
|
||||||
|
const response = await api.endpoints.users.getOne({
|
||||||
|
id: username,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: response.data.profile,
|
||||||
|
teams: response.data.profile.parties,
|
||||||
|
pagination: {
|
||||||
|
count: response.data.meta.count,
|
||||||
|
totalPages: response.data.meta.total_pages,
|
||||||
|
perPage: response.data.meta.per_page,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchSaved(filters: FilterObject) {
|
||||||
|
const params = { params: { ...filters, ...permissiveFilterset } }
|
||||||
|
const response = await api.savedTeams(params)
|
||||||
|
|
||||||
|
return {
|
||||||
|
teams: response.data.results,
|
||||||
|
pagination: {
|
||||||
|
count: response.data.meta.count,
|
||||||
|
totalPages: response.data.meta.total_pages,
|
||||||
|
perPage: response.data.meta.per_page,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue