From fc616aab01a7fa239048cf56e8945f212df713bc Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sun, 21 Apr 2024 00:46:04 -0700 Subject: [PATCH] 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> --- .vscode/settings.json | 3 +- components/filters/FilterBar/index.tsx | 16 +- components/search/SearchModal/index.tsx | 2 +- {utils => hooks}/useDidMountEffect.tsx | 0 hooks/useFavorites.tsx | 50 ++++ hooks/useFetchTeams.tsx | 146 +++++++++ hooks/useFilterState.tsx | 56 ++++ hooks/usePaginationState.tsx | 16 + hooks/useTeamFilter.tsx | 182 ++++++++++++ pages/[username].tsx | 290 +++++------------- pages/saved.tsx | 319 +++++--------------- pages/teams.tsx | 377 ++++++------------------ types/FilterSet.d.ts | 5 + utils/convertAdvancedFilters.tsx | 16 +- utils/enums.tsx | 6 + utils/parseElement.tsx | 20 ++ utils/serverSideUtils.tsx | 77 +++++ 17 files changed, 812 insertions(+), 769 deletions(-) rename {utils => hooks}/useDidMountEffect.tsx (100%) create mode 100644 hooks/useFavorites.tsx create mode 100644 hooks/useFetchTeams.tsx create mode 100644 hooks/useFilterState.tsx create mode 100644 hooks/usePaginationState.tsx create mode 100644 hooks/useTeamFilter.tsx create mode 100644 utils/parseElement.tsx create mode 100644 utils/serverSideUtils.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 454186aa..4d6602f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "git.ignoreLimitWarning": true, - "i18n-ally.localesPaths": ["public/locales"] + "i18n-ally.localesPaths": ["public/locales"], + "i18n-ally.keystyle": "nested" } diff --git a/components/filters/FilterBar/index.tsx b/components/filters/FilterBar/index.tsx index 8be8e831..a94e2dcd 100644 --- a/components/filters/FilterBar/index.tsx +++ b/components/filters/FilterBar/index.tsx @@ -19,7 +19,6 @@ interface Props { defaultFilterset: FilterSet persistFilters?: boolean children: React.ReactNode - scrolled: boolean element?: number raid?: string raidGroups: RaidGroup[] @@ -32,6 +31,8 @@ const FilterBar = (props: Props) => { // Set up translation const { t } = useTranslation('common') + const [scrolled, setScrolled] = useState(false) + const [currentRaid, setCurrentRaid] = useState() const [recencyOpen, setRecencyOpen] = useState(false) @@ -44,7 +45,7 @@ const FilterBar = (props: Props) => { // Set up classes object for showing shadow on scroll const classes = classNames({ [styles.filterBar]: true, - [styles.shadow]: props.scrolled, + [styles.shadow]: scrolled, }) const filterButtonClasses = classNames({ @@ -52,6 +53,17 @@ const FilterBar = (props: Props) => { 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 useEffect(() => { const raid = appState.raidGroups diff --git a/components/search/SearchModal/index.tsx b/components/search/SearchModal/index.tsx index 6cc18e64..1ccb2112 100644 --- a/components/search/SearchModal/index.tsx +++ b/components/search/SearchModal/index.tsx @@ -27,7 +27,7 @@ import type { SearchableObject, SearchableObjectArray } from '~types' import styles from './index.module.scss' import CrossIcon from '~public/icons/Cross.svg' import classNames from 'classnames' -import useDidMountEffect from '~utils/useDidMountEffect' +import useDidMountEffect from '~hooks/useDidMountEffect' interface Props extends DialogProps { send: (object: SearchableObject, position: number) => any diff --git a/utils/useDidMountEffect.tsx b/hooks/useDidMountEffect.tsx similarity index 100% rename from utils/useDidMountEffect.tsx rename to hooks/useDidMountEffect.tsx diff --git a/hooks/useFavorites.tsx b/hooks/useFavorites.tsx new file mode 100644 index 00000000..d81d70c7 --- /dev/null +++ b/hooks/useFavorites.tsx @@ -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, + } +} diff --git a/hooks/useFetchTeams.tsx b/hooks/useFetchTeams.tsx new file mode 100644 index 00000000..2a65677b --- /dev/null +++ b/hooks/useFetchTeams.tsx @@ -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, + } +} diff --git a/hooks/useFilterState.tsx b/hooks/useFilterState.tsx new file mode 100644 index 00000000..f105c347 --- /dev/null +++ b/hooks/useFilterState.tsx @@ -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, + } +} diff --git a/hooks/usePaginationState.tsx b/hooks/usePaginationState.tsx new file mode 100644 index 00000000..d99e1ea9 --- /dev/null +++ b/hooks/usePaginationState.tsx @@ -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, + } +} diff --git a/hooks/useTeamFilter.tsx b/hooks/useTeamFilter.tsx new file mode 100644 index 00000000..ac6cf9f6 --- /dev/null +++ b/hooks/useTeamFilter.tsx @@ -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([]) + + 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, + } +} diff --git a/pages/[username].tsx b/pages/[username].tsx index a9703ea5..7636bc25 100644 --- a/pages/[username].tsx +++ b/pages/[username].tsx @@ -1,37 +1,42 @@ -import React, { useCallback, useEffect, useState } from 'react' -import InfiniteScroll from 'react-infinite-scroll-component' -import { useQueryState } from 'nuqs' +// Libraries +import React, { useEffect, useState } from 'react' import { useRouter } from 'next/router' import { useTranslation } from 'next-i18next' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import InfiniteScroll from 'react-infinite-scroll-component' -import api from '~utils/api' -import extractFilters from '~utils/extractFilters' +// Hooks +import { useFavorites } from '~hooks/useFavorites' +import { useTeamFilter } from '~hooks/useTeamFilter' +import useDidMountEffect from '~hooks/useDidMountEffect' + +// Utils import fetchLatestVersion from '~utils/fetchLatestVersion' -import { setHeaders } from '~utils/userToken' -import useDidMountEffect from '~utils/useDidMountEffect' import { appState } from '~utils/appState' +import { convertAdvancedFilters } from '~utils/convertAdvancedFilters' +import { CollectionPage } from '~utils/enums' import { permissiveFilterset } from '~utils/defaultFilters' -import { elements, allElement } from '~data/elements' -import { emptyPaginationObject } from '~utils/emptyStates' +import { setHeaders } from '~utils/userToken' +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 GridRepCollection from '~components/reps/GridRepCollection' import LoadingRep from '~components/reps/LoadingRep' -import ErrorSection from '~components/ErrorSection' -import FilterBar from '~components/filters/FilterBar' import ProfileHead from '~components/head/ProfileHead' 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 { context?: PageContextObj version: AppUpdate @@ -52,144 +57,48 @@ const ProfileRoute: React.FC = ({ // Import translations 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([]) const [raids, setRaids] = useState() - // Set up infinite scrolling-related states - const [recordCount, setRecordCount] = useState(0) - const [currentPage, setCurrentPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) + const { + element, + setElement, + 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 - // 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(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 - } + const { toggleFavorite } = useFavorites(parties, setParties) // Set the initial parties from props - useEffect(() => { - if (context && context.teams && context.pagination) { - setTotalPages(context.pagination.totalPages) - setRecordCount(context.pagination.count) - replaceResults(context.pagination.count, context.teams) - appState.raidGroups = context.raidGroups - appState.version = version + useDidMountEffect(() => { + if (context) { + if (context.teams && context.pagination) { + processTeams(context.teams, true) + setPagination(context.pagination) + + appState.raidGroups = context.raidGroups + appState.version = version + } } + setCurrentPage(1) - }, []) - - // 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]) - } + setFetching(false) + }, [context]) // Fetch all raids on mount, then find the raid in the URL if present useEffect(() => { @@ -197,26 +106,6 @@ const ProfileRoute: React.FC = ({ setRaids(raids) }, [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 function receiveFilters(filters: FilterSet) { if (filters.element == 0) setElement(0, { shallow: true }) @@ -230,10 +119,6 @@ const ProfileRoute: React.FC = ({ } // Methods: Navigation - function handleScroll() { - if (window.pageYOffset > 90) setScrolled(true) - else setScrolled(false) - } function goTo(shortcode: string) { router.push(`/p/${shortcode}`) @@ -249,7 +134,7 @@ const ProfileRoute: React.FC = ({ else return
} - // TODO: Add save functions + // Page component rendering methods function renderParties() { return parties.map((party, i) => { @@ -257,12 +142,14 @@ const ProfileRoute: React.FC = ({ toggleFavorite(teamId, favorited)} /> ) }) } + function renderLoading(number: number) { return ( @@ -293,7 +180,6 @@ const ProfileRoute: React.FC = ({ onAdvancedFilter={receiveAdvancedFilters} onFilter={receiveFilters} persistFilters={false} - scrolled={scrolled} element={element} raid={raid} raidGroups={context.raidGroups} @@ -337,46 +223,24 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex const version = await fetchLatestVersion() try { - // Fetch and organize raids - let raidGroups: RaidGroup[] = await api - .raidGroups() - .then((response) => response.data) + // We don't pre-load advanced filters here + const { raidGroups, filters } = await fetchRaidGroupsAndFilters(query) - // Create filter object - 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 + let context: PageContextObj | undefined = undefined // Perform a request only if we received a username if (query.username) { - const response = await api.endpoints.users.getOne({ - id: query.username, - params, - }) + const { user, teams, pagination } = await fetchUserProfile( + query.username, + filters + ) - // Assign values to pass to props - user = response.data.profile - - if (response.data.profile.parties) teams = response.data.profile.parties - else teams = [] - - 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, + context = { + user: user, + teams: teams, + raidGroups: raidGroups, + pagination: pagination, + } } // Pass to the page component as props diff --git a/pages/saved.tsx b/pages/saved.tsx index c19af934..ae20a6bc 100644 --- a/pages/saved.tsx +++ b/pages/saved.tsx @@ -1,37 +1,41 @@ -import React, { useCallback, useEffect, useState } from 'react' -import InfiniteScroll from 'react-infinite-scroll-component' -import { useQueryState } from 'nuqs' +// Libraries +import React, { useEffect, useState } from 'react' import { useRouter } from 'next/router' import { useTranslation } from 'next-i18next' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' -import clonedeep from 'lodash.clonedeep' +import InfiniteScroll from 'react-infinite-scroll-component' -import api from '~utils/api' -import { setHeaders } from '~utils/userToken' -import extractFilters from '~utils/extractFilters' +// Hooks +import { useFavorites } from '~hooks/useFavorites' +import { useTeamFilter } from '~hooks/useTeamFilter' +import useDidMountEffect from '~hooks/useDidMountEffect' + +// Utils import fetchLatestVersion from '~utils/fetchLatestVersion' -import useDidMountEffect from '~utils/useDidMountEffect' import { appState } from '~utils/appState' +import { convertAdvancedFilters } from '~utils/convertAdvancedFilters' +import { CollectionPage } from '~utils/enums' import { permissiveFilterset } from '~utils/defaultFilters' -import { elements, allElement } from '~data/elements' -import { emptyPaginationObject } from '~utils/emptyStates' +import { setHeaders } from '~utils/userToken' +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 FilterBar from '~components/filters/FilterBar' import GridRep from '~components/reps/GridRep' import GridRepCollection from '~components/reps/GridRepCollection' import LoadingRep from '~components/reps/LoadingRep' -import FilterBar from '~components/filters/FilterBar' 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 { context?: PageContextObj version: AppUpdate @@ -51,148 +55,48 @@ const SavedRoute: React.FC = ({ // Import translations 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([]) const [raids, setRaids] = useState() - // Set up infinite scrolling-related states - const [recordCount, setRecordCount] = useState(0) - const [currentPage, setCurrentPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) + const { + element, + setElement, + 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 - // 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(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 - } + const { toggleFavorite } = useFavorites(parties, setParties) // Set the initial parties from props - useEffect(() => { - if (context && context.teams && context.pagination) { - setTotalPages(context.pagination.totalPages) - setRecordCount(context.pagination.count) - replaceResults(context.pagination.count, context.teams) - appState.raidGroups = context.raidGroups - appState.version = version + useDidMountEffect(() => { + if (context) { + if (context.teams && context.pagination) { + processTeams(context.teams, true) + setPagination(context.pagination) + + appState.raidGroups = context.raidGroups + appState.version = version + } } + setCurrentPage(1) - }, []) - - // 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]) - } + setFetching(false) + }, [context]) // Fetch all raids on mount, then find the raid in the URL if present useEffect(() => { @@ -200,26 +104,6 @@ const SavedRoute: React.FC = ({ setRaids(raids) }, [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 function receiveFilters(filters: FilterSet) { if (filters.element == 0) setElement(0, { shallow: true }) @@ -232,49 +116,7 @@ const SavedRoute: React.FC = ({ 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 - function handleScroll() { - if (window.pageYOffset > 90) setScrolled(true) - else setScrolled(false) - } function goTo(shortcode: string) { router.push(`/p/${shortcode}`) @@ -290,13 +132,15 @@ const SavedRoute: React.FC = ({ else return
} + // Page component rendering methods + function renderParties() { return parties.map((party, i) => { return ( @@ -334,7 +178,6 @@ const SavedRoute: React.FC = ({ onFilter={receiveFilters} onAdvancedFilter={receiveAdvancedFilters} persistFilters={false} - scrolled={scrolled} element={element} raid={raid} raidGroups={context.raidGroups} @@ -378,42 +221,14 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex const version = await fetchLatestVersion() try { - // Fetch and organize raids - let raidGroups: RaidGroup[] = await api - .raidGroups() - .then((response) => response.data) + // We don't pre-load advanced filters here + const { raidGroups, filters } = await fetchRaidGroupsAndFilters(query) + const { teams, pagination } = await fetchSaved(filters) - // 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 { props: { - context: context, - version: version, + context: { teams, raidGroups, pagination }, + version, error: false, ...(await serverSideTranslations(locale, ['common'])), }, diff --git a/pages/teams.tsx b/pages/teams.tsx index dac95c85..b25edb31 100644 --- a/pages/teams.tsx +++ b/pages/teams.tsx @@ -1,38 +1,40 @@ -import React, { useCallback, useEffect, useState } from 'react' -import InfiniteScroll from 'react-infinite-scroll-component' -import { getCookie } from 'cookies-next' -import { useQueryState } from 'nuqs' +// Libraries +import React, { useEffect, useState } from 'react' import { useRouter } from 'next/router' import { useTranslation } from 'next-i18next' 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' -import { setHeaders } from '~utils/userToken' -import extractFilters from '~utils/extractFilters' +// Hooks +import { useFavorites } from '~hooks/useFavorites' +import { useTeamFilter } from '~hooks/useTeamFilter' +import useDidMountEffect from '~hooks/useDidMountEffect' + +// Utils import fetchLatestVersion from '~utils/fetchLatestVersion' -import useDidMountEffect from '~utils/useDidMountEffect' 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 { 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 FilterBar from '~components/filters/FilterBar' import GridRep from '~components/reps/GridRep' import GridRepCollection from '~components/reps/GridRepCollection' import LoadingRep from '~components/reps/LoadingRep' -import FilterBar from '~components/filters/FilterBar' import TeamsHead from '~components/head/TeamsHead' - -import type { AxiosError } from 'axios' -import type { NextApiRequest, NextApiResponse } from 'next' -import type { - FilterObject, - PageContextObj, - PaginationObject, - ResponseStatus, -} from '~types' +import { CollectionPage } from '~utils/enums' interface Props { context?: PageContextObj @@ -47,166 +49,51 @@ const TeamsRoute: React.FC = ({ error, status, }: Props) => { - // Set up router const router = useRouter() - - // Import translations 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([]) const [raids, setRaids] = useState() - // Set up infinite scrolling-related states - const [recordCount, setRecordCount] = useState(0) - const [currentPage, setCurrentPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) + const { + element, + setElement, + 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 - // 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(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 - } + const { toggleFavorite } = useFavorites(parties, setParties) // Set the initial parties from props - useEffect(() => { - if (context && context.teams && context.pagination) { - setTotalPages(context.pagination.totalPages) - setRecordCount(context.pagination.count) - replaceResults(context.pagination.count, context.teams) - appState.raidGroups = context.raidGroups - appState.version = version + useDidMountEffect(() => { + if (context) { + if (context.teams && context.pagination) { + processTeams(context.teams, true) + setPagination(context.pagination) + + appState.raidGroups = context.raidGroups + appState.version = version + } } + setCurrentPage(1) - - setIsLoading(false) - }, []) - - // 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]) - } + setFetching(false) + }, [context]) // Fetch all raids on mount, then find the raid in the URL if present useEffect(() => { @@ -214,26 +101,6 @@ const TeamsRoute: React.FC = ({ setRaids(raids) }, [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 function receiveFilters(filters: FilterSet) { if (filters.element == 0) setElement(0, { shallow: true }) @@ -246,49 +113,7 @@ const TeamsRoute: React.FC = ({ 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 - function handleScroll() { - if (window.pageYOffset > 90) setScrolled(true) - else setScrolled(false) - } function goTo(shortcode: string) { router.push(`/p/${shortcode}`) @@ -304,24 +129,23 @@ const TeamsRoute: React.FC = ({ else return
} + // Page component rendering methods function renderParties() { - return parties.map((party, i) => { - return ( - - ) - }) + return parties.map((party, i) => ( + goTo(party.shortcode)} + onSave={(teamId, favorited) => toggleFavorite(teamId, favorited)} + /> + )) } function renderLoading(number: number) { return ( - {Array.from(Array(number)).map((x, i) => ( + {Array.from({ length: number }, (_, i) => ( ))} @@ -348,7 +172,6 @@ const TeamsRoute: React.FC = ({ onFilter={receiveFilters} onAdvancedFilter={receiveAdvancedFilters} persistFilters={true} - scrolled={scrolled} element={element} raid={raid} raidGroups={context.raidGroups} @@ -381,65 +204,35 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex // Fetch latest version 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 { - // Fetch and organize raids - let raidGroups: RaidGroup[] = await api - .raidGroups() - .then((response) => response.data) + const advancedFilters = parseAdvancedFilters(req, res) + const convertedFilters = advancedFilters + ? convertAdvancedFilters(advancedFilters) + : 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 { props: { - context: context, - version: version, + context: { teams, raidGroups, pagination }, + version, error: false, ...(await serverSideTranslations(locale, ['common'])), }, } } catch (error) { - // Extract the underlying Axios error - const axiosError = error as AxiosError - const response = axiosError.response - - // Pass to the page component as props + // If error is of type AxiosError, extract the response into a variable + let response: AxiosResponse | undefined = axios.isAxiosError(error) + ? error.response + : undefined + return { props: { context: null, error: true, status: { - code: response ? response.status : -999, - text: response ? response.statusText : "unspecified_error", + code: response?.status ?? -999, + text: response?.statusText ?? 'unspecified_error', }, ...(await serverSideTranslations(locale, ['common'])), }, diff --git a/types/FilterSet.d.ts b/types/FilterSet.d.ts index 98bd1b02..9fe42a5e 100644 --- a/types/FilterSet.d.ts +++ b/types/FilterSet.d.ts @@ -16,3 +16,8 @@ interface FilterSet { includes?: MentionItem[] excludes?: MentionItem[] } + +interface ConvertedFilters extends Omit { + includes: string + excludes: string +} diff --git a/utils/convertAdvancedFilters.tsx b/utils/convertAdvancedFilters.tsx index 3fc4540f..73251879 100644 --- a/utils/convertAdvancedFilters.tsx +++ b/utils/convertAdvancedFilters.tsx @@ -1,21 +1,21 @@ import cloneDeep from 'lodash.clonedeep' -export function convertAdvancedFilters(filters: FilterSet) { - let copy = cloneDeep(filters) +export function convertAdvancedFilters(filters: FilterSet): ConvertedFilters { + let copy: FilterSet = cloneDeep(filters) - const includes = filterString(filters.includes || []) - const excludes = filterString(filters.excludes || []) + const includes: string = filterString(filters.includes || []) + const excludes: string = filterString(filters.excludes || []) - delete copy.includes - delete copy.excludes + delete (copy as any).includes + delete (copy as any).excludes return { ...copy, includes, excludes, - } + } as ConvertedFilters } -export function filterString(list: MentionItem[]) { +export function filterString(list: MentionItem[]): string { return list.map((item) => item.granblue_id).join(',') } diff --git a/utils/enums.tsx b/utils/enums.tsx index df2c0c84..bc3ca3fd 100644 --- a/utils/enums.tsx +++ b/utils/enums.tsx @@ -24,3 +24,9 @@ export enum AboutTabs { Updates, Roadmap, } + +export enum CollectionPage { + Teams, + Profile, + Saved, +} diff --git a/utils/parseElement.tsx b/utils/parseElement.tsx new file mode 100644 index 00000000..ff9e09e9 --- /dev/null +++ b/utils/parseElement.tsx @@ -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 +} diff --git a/utils/serverSideUtils.tsx b/utils/serverSideUtils.tsx new file mode 100644 index 00000000..a0cdf088 --- /dev/null +++ b/utils/serverSideUtils.tsx @@ -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, + }, + } +}