- Add getUserProfile function to fetch user data with teams from /users/{username} endpoint
- Update profile page to use the correct API response structure (profile.parties)
- Fix UserInfo component to receive user object instead of individual props
- Update ProfilePageClient to fetch additional pages from the correct endpoint
- Fix pagination metadata extraction from the API response
The profile page now correctly displays user teams with working infinite scroll pagination.
243 lines
6.5 KiB
TypeScript
243 lines
6.5 KiB
TypeScript
'use client'
|
|
|
|
import React, { useEffect, useState } from 'react'
|
|
import { useTranslations } from 'next-intl'
|
|
import { useRouter } from '~/i18n/navigation'
|
|
import { useSearchParams } from 'next/navigation'
|
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
|
|
|
// Components
|
|
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 UserInfo from '~/components/filters/UserInfo'
|
|
|
|
// Utils
|
|
import { defaultFilterset } from '~/utils/defaultFilters'
|
|
import { appState } from '~/utils/appState'
|
|
|
|
// Types
|
|
interface Pagination {
|
|
current_page: number;
|
|
total_pages: number;
|
|
record_count: number;
|
|
}
|
|
|
|
interface Party {
|
|
id: string;
|
|
shortcode: string;
|
|
name: string;
|
|
element: number;
|
|
// Add other properties as needed
|
|
}
|
|
|
|
interface User {
|
|
id: string;
|
|
username: string;
|
|
avatar: {
|
|
picture: string;
|
|
element: string;
|
|
};
|
|
gender: string;
|
|
// Add other properties as needed
|
|
}
|
|
|
|
interface Props {
|
|
initialData: {
|
|
user: User;
|
|
teams: Party[];
|
|
raidGroups: any[];
|
|
pagination: Pagination;
|
|
};
|
|
initialElement?: number;
|
|
initialRaid?: string;
|
|
initialRecency?: string;
|
|
}
|
|
|
|
const ProfilePageClient: React.FC<Props> = ({
|
|
initialData,
|
|
initialElement,
|
|
initialRaid,
|
|
initialRecency
|
|
}) => {
|
|
const t = useTranslations('common')
|
|
const router = useRouter()
|
|
const searchParams = useSearchParams()
|
|
|
|
// State management
|
|
const [parties, setParties] = useState<Party[]>(initialData.teams)
|
|
const [currentPage, setCurrentPage] = useState(initialData.pagination.current_page)
|
|
const [totalPages, setTotalPages] = useState(initialData.pagination.total_pages)
|
|
const [recordCount, setRecordCount] = useState(initialData.pagination.record_count)
|
|
const [loaded, setLoaded] = useState(true)
|
|
const [fetching, setFetching] = useState(false)
|
|
const [element, setElement] = useState(initialElement || 0)
|
|
const [raid, setRaid] = useState(initialRaid || '')
|
|
const [recency, setRecency] = useState(initialRecency || '')
|
|
|
|
// Initialize app state with raid groups
|
|
useEffect(() => {
|
|
if (initialData.raidGroups.length > 0) {
|
|
appState.raidGroups = initialData.raidGroups
|
|
}
|
|
}, [initialData.raidGroups])
|
|
|
|
// Update URL when filters change
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(searchParams.toString())
|
|
|
|
// Update or remove parameters based on filter values
|
|
if (element) {
|
|
params.set('element', element.toString())
|
|
} else {
|
|
params.delete('element')
|
|
}
|
|
|
|
if (raid) {
|
|
params.set('raid', raid)
|
|
} else {
|
|
params.delete('raid')
|
|
}
|
|
|
|
if (recency) {
|
|
params.set('recency', recency)
|
|
} else {
|
|
params.delete('recency')
|
|
}
|
|
|
|
// Only update URL if filters are changed
|
|
const newQueryString = params.toString()
|
|
const currentQuery = searchParams.toString()
|
|
|
|
if (newQueryString !== currentQuery) {
|
|
router.push(`/${initialData.user.username}${newQueryString ? `?${newQueryString}` : ''}`)
|
|
}
|
|
}, [element, raid, recency, router, searchParams, initialData.user.username])
|
|
|
|
// Load more parties when scrolling
|
|
async function loadMoreParties() {
|
|
if (fetching || currentPage >= totalPages) return
|
|
|
|
setFetching(true)
|
|
|
|
try {
|
|
// Construct URL for fetching more data - using the users endpoint
|
|
const url = new URL(`${process.env.NEXT_PUBLIC_SIERO_API_URL}/users/${initialData.user.username}`, window.location.origin)
|
|
url.searchParams.set('page', (currentPage + 1).toString())
|
|
|
|
if (element) url.searchParams.set('element', element.toString())
|
|
if (raid) url.searchParams.set('raid_id', raid)
|
|
if (recency) url.searchParams.set('recency', recency)
|
|
|
|
const response = await fetch(url.toString(), {
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
const data = await response.json()
|
|
|
|
// Extract parties from the profile response
|
|
const newParties = data.profile?.parties || []
|
|
|
|
if (newParties.length > 0) {
|
|
setParties([...parties, ...newParties])
|
|
|
|
// Update pagination from meta
|
|
if (data.meta) {
|
|
setCurrentPage(currentPage + 1)
|
|
setTotalPages(data.meta.total_pages || totalPages)
|
|
setRecordCount(data.meta.count || recordCount)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading more parties', error)
|
|
} finally {
|
|
setFetching(false)
|
|
}
|
|
}
|
|
|
|
// Receive filters from the filter bar
|
|
function receiveFilters(filters: FilterSet) {
|
|
if ('element' in filters) {
|
|
setElement(filters.element || 0)
|
|
}
|
|
if ('recency' in filters) {
|
|
setRecency(filters.recency || '')
|
|
}
|
|
if ('raid' in filters) {
|
|
setRaid(filters.raid || '')
|
|
}
|
|
|
|
// Reset to page 1 when filters change
|
|
setCurrentPage(1)
|
|
}
|
|
|
|
// Methods: Navigation
|
|
function goToParty(shortcode: string) {
|
|
router.push(`/p/${shortcode}`)
|
|
}
|
|
|
|
// Page component rendering methods
|
|
function renderParties() {
|
|
return parties.map((party, i) => (
|
|
<GridRep
|
|
party={party}
|
|
key={`party-${i}`}
|
|
loading={fetching}
|
|
onClick={() => goToParty(party.shortcode)}
|
|
/>
|
|
))
|
|
}
|
|
|
|
function renderLoading(number: number) {
|
|
return (
|
|
<GridRepCollection>
|
|
{Array.from({ length: number }, (_, i) => (
|
|
<LoadingRep key={`loading-${i}`} />
|
|
))}
|
|
</GridRepCollection>
|
|
)
|
|
}
|
|
|
|
const renderInfiniteScroll = (
|
|
<>
|
|
{parties.length === 0 && !loaded && renderLoading(3)}
|
|
{parties.length === 0 && loaded && (
|
|
<div className="notFound">
|
|
<h2>{t('teams.not_found')}</h2>
|
|
</div>
|
|
)}
|
|
{parties.length > 0 && (
|
|
<InfiniteScroll
|
|
dataLength={parties.length}
|
|
next={loadMoreParties}
|
|
hasMore={totalPages > currentPage}
|
|
loader={renderLoading(3)}
|
|
>
|
|
<GridRepCollection>{renderParties()}</GridRepCollection>
|
|
</InfiniteScroll>
|
|
)}
|
|
</>
|
|
)
|
|
|
|
return (
|
|
<>
|
|
<FilterBar
|
|
defaultFilterset={defaultFilterset}
|
|
onFilter={receiveFilters}
|
|
persistFilters={false}
|
|
element={element}
|
|
raid={raid}
|
|
raidGroups={initialData.raidGroups}
|
|
recency={recency}
|
|
>
|
|
<UserInfo user={initialData.user} />
|
|
</FilterBar>
|
|
|
|
<section>{renderInfiniteScroll}</section>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default ProfilePageClient
|