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]
|
type SearchResultItem = SearchPageResult['results'][number]
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
userId: string
|
||||||
open?: boolean
|
open?: boolean
|
||||||
onOpenChange?: (open: boolean) => void
|
onOpenChange?: (open: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
let { open = $bindable(false), onOpenChange }: Props = $props()
|
let { userId, open = $bindable(false), onOpenChange }: Props = $props()
|
||||||
|
|
||||||
// Search state
|
// Search state
|
||||||
let searchQuery = $state('')
|
let searchQuery = $state('')
|
||||||
|
|
@ -45,7 +46,7 @@
|
||||||
let sentinelEl = $state<HTMLElement>()
|
let sentinelEl = $state<HTMLElement>()
|
||||||
|
|
||||||
// Get IDs of characters already in collection
|
// 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)
|
// Build filters for search (using SearchFilters type from search.queries)
|
||||||
const searchFilters = $derived<SearchFilters>({
|
const searchFilters = $derived<SearchFilters>({
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,12 @@
|
||||||
import type { PageServerLoad } from './$types'
|
import type { PageServerLoad } from './$types'
|
||||||
import { error } from '@sveltejs/kit'
|
|
||||||
import { collectionAdapter } from '$lib/api/adapters/collection.adapter'
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ parent }) => {
|
export const load: PageServerLoad = async ({ parent }) => {
|
||||||
const { user, isOwner } = await parent()
|
const { user, isOwner } = await parent()
|
||||||
|
|
||||||
try {
|
// User info comes from layout, collection data is fetched client-side via TanStack Query
|
||||||
// Fetch the user's public character collection
|
// The unified API endpoint handles privacy checks server-side
|
||||||
const characters = await collectionAdapter.getPublicCharacters(user.id)
|
return {
|
||||||
|
user,
|
||||||
return {
|
isOwner
|
||||||
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')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,83 +31,30 @@
|
||||||
// Sentinel for infinite scroll
|
// Sentinel for infinite scroll
|
||||||
let sentinelEl = $state<HTMLElement>()
|
let sentinelEl = $state<HTMLElement>()
|
||||||
|
|
||||||
// Build filters for query
|
// Build filters for query - all filters are now server-side for everyone
|
||||||
const queryFilters = $derived({
|
const queryFilters = $derived({
|
||||||
element: elementFilters.length > 0 ? elementFilters : undefined,
|
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
|
// Unified query for any user's collection (privacy enforced server-side)
|
||||||
// For non-owner, use the server-loaded public data
|
const collectionQuery = createInfiniteQuery(() => {
|
||||||
const collectionQuery = createInfiniteQuery(() => ({
|
const userId = data.user.id
|
||||||
...collectionQueries.characters(queryFilters),
|
const filters = queryFilters
|
||||||
enabled: data.isOwner,
|
return collectionQueries.characters(userId, filters)
|
||||||
initialData: data.isOwner
|
})
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
results: data.characters || [],
|
|
||||||
page: 1,
|
|
||||||
totalPages: 1,
|
|
||||||
total: data.characters?.length || 0,
|
|
||||||
perPage: data.characters?.length || 20
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pageParams: [1]
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Flatten all characters from pages
|
// Flatten all characters from pages
|
||||||
const allCharacters = $derived.by((): CollectionCharacter[] => {
|
const allCharacters = $derived.by((): CollectionCharacter[] => {
|
||||||
if (!data.isOwner) {
|
|
||||||
return data.characters || []
|
|
||||||
}
|
|
||||||
if (!collectionQuery.data?.pages) {
|
if (!collectionQuery.data?.pages) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return collectionQuery.data.pages.flatMap((page) => page.results ?? [])
|
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
|
// Infinite scroll
|
||||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||||
rootMargin: '200px'
|
rootMargin: '200px'
|
||||||
|
|
@ -115,7 +62,6 @@
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (
|
if (
|
||||||
data.isOwner &&
|
|
||||||
inViewport.current &&
|
inViewport.current &&
|
||||||
collectionQuery.hasNextPage &&
|
collectionQuery.hasNextPage &&
|
||||||
!collectionQuery.isFetchingNextPage &&
|
!collectionQuery.isFetchingNextPage &&
|
||||||
|
|
@ -125,11 +71,9 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const isLoading = $derived(data.isOwner && collectionQuery.isLoading)
|
const isLoading = $derived(collectionQuery.isLoading)
|
||||||
const isEmpty = $derived(!isLoading && filteredCharacters.length === 0)
|
const isEmpty = $derived(!isLoading && allCharacters.length === 0)
|
||||||
const showSentinel = $derived(
|
const showSentinel = $derived(collectionQuery.hasNextPage && !collectionQuery.isFetchingNextPage)
|
||||||
data.isOwner && collectionQuery.hasNextPage && !collectionQuery.isFetchingNextPage
|
|
||||||
)
|
|
||||||
|
|
||||||
function handleFiltersChange(filters: CollectionFilterState) {
|
function handleFiltersChange(filters: CollectionFilterState) {
|
||||||
elementFilters = filters.element
|
elementFilters = filters.element
|
||||||
|
|
@ -223,7 +167,7 @@
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="character-grid">
|
<div class="character-grid">
|
||||||
{#each filteredCharacters as character (character.id)}
|
{#each allCharacters as character (character.id)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="character-card"
|
class="character-card"
|
||||||
|
|
@ -270,9 +214,9 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if data.isOwner && !collectionQuery.hasNextPage && filteredCharacters.length > 0}
|
{#if !collectionQuery.hasNextPage && allCharacters.length > 0}
|
||||||
<div class="end-message">
|
<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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -281,7 +225,7 @@
|
||||||
|
|
||||||
<!-- Add to Collection Modal -->
|
<!-- Add to Collection Modal -->
|
||||||
{#if data.isOwner}
|
{#if data.isOwner}
|
||||||
<AddToCollectionModal bind:open={addModalOpen} />
|
<AddToCollectionModal userId={data.user.id} bind:open={addModalOpen} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue