diff --git a/src/lib/api.ts b/src/lib/api.ts index 886fc65..fddbae7 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -135,11 +135,15 @@ export async function listVideos(cursor?: string, pageSize = 9): Promise<{ }; } -/** Fetch videos by a specific creator DID */ -export async function listVideosByCreator(creatorDid: string): Promise { +/** Fetch paginated videos by a specific creator DID */ +export async function listVideosByCreator(creatorDid: string, cursor?: string, pageSize = 9): Promise<{ + records: VideoRecord[]; + hasMore: boolean; + endCursor?: string; +}> { const query = ` -query GetVideosByCreator($creatorDid: String!) { - placeStreamVideo(first: 50, where: { creator: { eq: $creatorDid } }) { +query GetVideosByCreator($creatorDid: String!, $first: Int, $after: String) { + placeStreamVideo(first: $first, after: $after, where: { creator: { eq: $creatorDid } }) { edges { node { uri @@ -156,20 +160,25 @@ query GetVideosByCreator($creatorDid: String!) { } } } + pageInfo { + hasNextPage + endCursor + } } }`; const res = await fetch(VODSLICE_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables: { creatorDid } }) + body: JSON.stringify({ query, variables: { creatorDid, first: pageSize, after: cursor || null } }) }); if (!res.ok) throw new Error(`Failed to fetch creator videos: ${res.status}`); const json = await res.json(); - const edges = json.data?.placeStreamVideo?.edges || []; + const data = json.data?.placeStreamVideo; + if (!data) return { records: [], hasMore: false }; - return edges.map((edge: VodSliceEdge) => { + const records = data.edges.map((edge: VodSliceEdge) => { const n = edge.node; let livestream: { cid: string; uri: string } | undefined; if (typeof n.livestream === 'string') { @@ -191,6 +200,12 @@ query GetVideosByCreator($creatorDid: String!) { } }; }); + + return { + records, + hasMore: data.pageInfo.hasNextPage, + endCursor: data.pageInfo.endCursor + }; } /** Fetch all videos (for deep-link lookup). Uses pagination internally. */ diff --git a/src/routes/profile/[handle]/+page.svelte b/src/routes/profile/[handle]/+page.svelte index 1969059..22dad16 100644 --- a/src/routes/profile/[handle]/+page.svelte +++ b/src/routes/profile/[handle]/+page.svelte @@ -4,26 +4,64 @@ import { getProfile, listVideosByCreator, getPlaylistUrl, formatDuration, formatDate, type BskyProfile, type VideoRecord } from '$lib/api'; import WavyBorder from '$lib/WavyBorder.svelte'; import WavyCircle from '$lib/WavyCircle.svelte'; + import WavyButton from '$lib/WavyButton.svelte'; import VideoCard from '$lib/VideoCard.svelte'; import VideoPlayer from '$lib/VideoPlayer.svelte'; import FrogHeader from '$lib/FrogHeader.svelte'; + const PAGE_SIZE = 9; + let profile: BskyProfile | null = $state(null); let videos: VideoRecord[] = $state([]); let loading = $state(true); + let videosLoading = $state(false); let error = $state(''); let selectedVideo: VideoRecord | null = $state(null); + // Pagination + let cursorHistory: (string | undefined)[] = $state([undefined]); + let pageIndex = $state(0); + let hasMore = $state(false); + let handle = $derived($page.params.handle); + async function loadVideos() { + if (!profile?.did) return; + videosLoading = true; + try { + const res = await listVideosByCreator(profile.did, cursorHistory[pageIndex], PAGE_SIZE); + videos = res.records; + hasMore = res.hasMore; + if (res.hasMore && res.endCursor && cursorHistory.length <= pageIndex + 1) { + cursorHistory = [...cursorHistory, res.endCursor]; + } + } catch (e: any) { + error = e.message; + } + videosLoading = false; + } + + function nextPage() { + if (!hasMore || videosLoading) return; + pageIndex++; + loadVideos(); + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + + function prevPage() { + if (pageIndex <= 0 || videosLoading) return; + pageIndex--; + loadVideos(); + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + onMount(async () => { loading = true; error = ''; try { profile = await getProfile(handle); - // Fetch videos created by this account if (profile?.did) { - videos = await listVideosByCreator(profile.did); + await loadVideos(); } } catch (e: any) { error = e.message; @@ -137,9 +175,21 @@ {/each} - {:else} + {:else if !videosLoading}

no videos found for this creator

{/if} + + {#if videos.length > 0 || pageIndex > 0} + + {/if} {/if}