simplify collection page to single code path
- use unified api for all users (no owner/viewer branching) - remove client-side filtering (api handles it) - pass userId to AddToCollectionModal
This commit is contained in:
parent
b8a48771dd
commit
4bbe2ed188
3 changed files with 26 additions and 92 deletions
|
|
@ -19,11 +19,12 @@
|
|||
type SearchResultItem = SearchPageResult['results'][number]
|
||||
|
||||
interface Props {
|
||||
userId: string
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}
|
||||
|
||||
let { open = $bindable(false), onOpenChange }: Props = $props()
|
||||
let { userId, open = $bindable(false), onOpenChange }: Props = $props()
|
||||
|
||||
// Search state
|
||||
let searchQuery = $state('')
|
||||
|
|
@ -45,7 +46,7 @@
|
|||
let sentinelEl = $state<HTMLElement>()
|
||||
|
||||
// Get IDs of characters already in collection
|
||||
const collectedIdsQuery = createQuery(() => collectionQueries.collectedCharacterIds())
|
||||
const collectedIdsQuery = createQuery(() => collectionQueries.collectedCharacterIds(userId))
|
||||
|
||||
// Build filters for search (using SearchFilters type from search.queries)
|
||||
const searchFilters = $derived<SearchFilters>({
|
||||
|
|
|
|||
|
|
@ -1,23 +1,12 @@
|
|||
import type { PageServerLoad } from './$types'
|
||||
import { error } from '@sveltejs/kit'
|
||||
import { collectionAdapter } from '$lib/api/adapters/collection.adapter'
|
||||
|
||||
export const load: PageServerLoad = async ({ parent }) => {
|
||||
const { user, isOwner } = await parent()
|
||||
|
||||
try {
|
||||
// Fetch the user's public character collection
|
||||
const characters = await collectionAdapter.getPublicCharacters(user.id)
|
||||
|
||||
return {
|
||||
characters,
|
||||
isOwner
|
||||
}
|
||||
} catch (e: any) {
|
||||
// 403 means collection is private
|
||||
if (e?.status === 403) {
|
||||
throw error(403, 'This collection is private')
|
||||
}
|
||||
throw error(e?.status || 502, e?.message || 'Failed to load collection')
|
||||
// User info comes from layout, collection data is fetched client-side via TanStack Query
|
||||
// The unified API endpoint handles privacy checks server-side
|
||||
return {
|
||||
user,
|
||||
isOwner
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,83 +31,30 @@
|
|||
// Sentinel for infinite scroll
|
||||
let sentinelEl = $state<HTMLElement>()
|
||||
|
||||
// Build filters for query
|
||||
// Build filters for query - all filters are now server-side for everyone
|
||||
const queryFilters = $derived({
|
||||
element: elementFilters.length > 0 ? elementFilters : undefined,
|
||||
rarity: rarityFilters.length > 0 ? rarityFilters : undefined
|
||||
rarity: rarityFilters.length > 0 ? rarityFilters : undefined,
|
||||
race: raceFilters.length > 0 ? raceFilters : undefined,
|
||||
proficiency: proficiencyFilters.length > 0 ? proficiencyFilters : undefined,
|
||||
gender: genderFilters.length > 0 ? genderFilters : undefined
|
||||
})
|
||||
|
||||
// For owner, use the authenticated collection query with infinite scroll
|
||||
// For non-owner, use the server-loaded public data
|
||||
const collectionQuery = createInfiniteQuery(() => ({
|
||||
...collectionQueries.characters(queryFilters),
|
||||
enabled: data.isOwner,
|
||||
initialData: data.isOwner
|
||||
? undefined
|
||||
: {
|
||||
pages: [
|
||||
{
|
||||
results: data.characters || [],
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: data.characters?.length || 0,
|
||||
perPage: data.characters?.length || 20
|
||||
}
|
||||
],
|
||||
pageParams: [1]
|
||||
}
|
||||
}))
|
||||
// Unified query for any user's collection (privacy enforced server-side)
|
||||
const collectionQuery = createInfiniteQuery(() => {
|
||||
const userId = data.user.id
|
||||
const filters = queryFilters
|
||||
return collectionQueries.characters(userId, filters)
|
||||
})
|
||||
|
||||
// Flatten all characters from pages
|
||||
const allCharacters = $derived.by((): CollectionCharacter[] => {
|
||||
if (!data.isOwner) {
|
||||
return data.characters || []
|
||||
}
|
||||
if (!collectionQuery.data?.pages) {
|
||||
return []
|
||||
}
|
||||
return collectionQuery.data.pages.flatMap((page) => page.results ?? [])
|
||||
})
|
||||
|
||||
// Client-side filtering for non-API-supported filters
|
||||
const filteredCharacters = $derived.by((): CollectionCharacter[] => {
|
||||
let result = allCharacters
|
||||
|
||||
// Apply element filter (client-side for non-owner)
|
||||
if (elementFilters.length > 0) {
|
||||
result = result.filter((c) => elementFilters.includes(c.character?.element ?? 0))
|
||||
}
|
||||
|
||||
// Apply rarity filter (client-side for non-owner)
|
||||
if (rarityFilters.length > 0) {
|
||||
result = result.filter((c) => rarityFilters.includes(c.character?.rarity ?? 0))
|
||||
}
|
||||
|
||||
// Apply race filter (client-side) - race is nested object
|
||||
if (raceFilters.length > 0) {
|
||||
result = result.filter((c) => {
|
||||
const race1 = c.character?.race?.race1 ?? 0
|
||||
const race2 = c.character?.race?.race2 ?? 0
|
||||
return raceFilters.includes(race1) || (race2 && raceFilters.includes(race2))
|
||||
})
|
||||
}
|
||||
|
||||
// Apply proficiency filter (client-side) - proficiency is an array
|
||||
if (proficiencyFilters.length > 0) {
|
||||
result = result.filter((c) => {
|
||||
const proficiencies = c.character?.proficiency ?? []
|
||||
return proficiencies.some((p) => proficiencyFilters.includes(p))
|
||||
})
|
||||
}
|
||||
|
||||
// Apply gender filter (client-side)
|
||||
if (genderFilters.length > 0) {
|
||||
result = result.filter((c) => genderFilters.includes(c.character?.gender ?? 0))
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// Infinite scroll
|
||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||
rootMargin: '200px'
|
||||
|
|
@ -115,7 +62,6 @@
|
|||
|
||||
$effect(() => {
|
||||
if (
|
||||
data.isOwner &&
|
||||
inViewport.current &&
|
||||
collectionQuery.hasNextPage &&
|
||||
!collectionQuery.isFetchingNextPage &&
|
||||
|
|
@ -125,11 +71,9 @@
|
|||
}
|
||||
})
|
||||
|
||||
const isLoading = $derived(data.isOwner && collectionQuery.isLoading)
|
||||
const isEmpty = $derived(!isLoading && filteredCharacters.length === 0)
|
||||
const showSentinel = $derived(
|
||||
data.isOwner && collectionQuery.hasNextPage && !collectionQuery.isFetchingNextPage
|
||||
)
|
||||
const isLoading = $derived(collectionQuery.isLoading)
|
||||
const isEmpty = $derived(!isLoading && allCharacters.length === 0)
|
||||
const showSentinel = $derived(collectionQuery.hasNextPage && !collectionQuery.isFetchingNextPage)
|
||||
|
||||
function handleFiltersChange(filters: CollectionFilterState) {
|
||||
elementFilters = filters.element
|
||||
|
|
@ -223,7 +167,7 @@
|
|||
</div>
|
||||
{:else}
|
||||
<div class="character-grid">
|
||||
{#each filteredCharacters as character (character.id)}
|
||||
{#each allCharacters as character (character.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="character-card"
|
||||
|
|
@ -270,9 +214,9 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if data.isOwner && !collectionQuery.hasNextPage && filteredCharacters.length > 0}
|
||||
{#if !collectionQuery.hasNextPage && allCharacters.length > 0}
|
||||
<div class="end-message">
|
||||
<p>{filteredCharacters.length} character{filteredCharacters.length === 1 ? '' : 's'} in your collection</p>
|
||||
<p>{allCharacters.length} character{allCharacters.length === 1 ? '' : 's'} in {data.isOwner ? 'your' : 'this'} collection</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
@ -281,7 +225,7 @@
|
|||
|
||||
<!-- Add to Collection Modal -->
|
||||
{#if data.isOwner}
|
||||
<AddToCollectionModal bind:open={addModalOpen} />
|
||||
<AddToCollectionModal userId={data.user.id} bind:open={addModalOpen} />
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
Loading…
Reference in a new issue