fix loading feed

This commit is contained in:
john 2025-06-11 23:12:03 +02:00
parent cf5494c15b
commit 95ea2a5f23
4 changed files with 54 additions and 71 deletions

View file

@ -1,10 +1,8 @@
import { paths } from './schema.ts' import { paths } from './schema.ts'
import createClient, { Client, Middleware } from 'openapi-fetch' import createClient, { Middleware } from 'openapi-fetch'
import { dispatchMessage } from '../messageBus/messageBus.ts' import { dispatchMessage } from '../messageBus/messageBus.ts'
export type ApiClient = Client<paths> export const initClient = () => {
export function initClient(): ApiClient {
const client = createClient<paths>({ baseUrl: import.meta.env.VITE_API_URL }) const client = createClient<paths>({ baseUrl: import.meta.env.VITE_API_URL })
const UnauthorizedHandlerMiddleware: Middleware = { const UnauthorizedHandlerMiddleware: Middleware = {
async onResponse({ response }) { async onResponse({ response }) {
@ -17,3 +15,5 @@ export function initClient(): ApiClient {
client.use(UnauthorizedHandlerMiddleware) client.use(UnauthorizedHandlerMiddleware)
return client return client
} }
export type ApiClient = ReturnType<typeof initClient>

View file

@ -1,4 +1,4 @@
import { useCallback, useRef, useState } from 'react' import { useRef, useState } from 'react'
import { PostsService } from '../posts/postsService.ts' import { PostsService } from '../posts/postsService.ts'
import { useUser } from '../../user/user.ts' import { useUser } from '../../user/user.ts'
import { MediaService } from '../../media/mediaService.ts' import { MediaService } from '../../media/mediaService.ts'
@ -11,6 +11,7 @@ import { Post } from '../posts/posts.ts'
import { produce, WritableDraft } from 'immer' import { produce, WritableDraft } from 'immer'
import PostItem from '../components/PostItem.tsx' import PostItem from '../components/PostItem.tsx'
import { useIntersectionLoad } from '../../../hooks/useIntersectionLoad.ts' import { useIntersectionLoad } from '../../../hooks/useIntersectionLoad.ts'
import { delay } from '../../../utils/delay.ts'
interface HomePageProps { interface HomePageProps {
postsService: PostsService postsService: PostsService
@ -24,13 +25,6 @@ export default function HomePage({ postsService, mediaService }: HomePageProps)
useSaveSignupCodeToLocalStorage() useSaveSignupCodeToLocalStorage()
const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false)
const fetchPosts = useCallback(
async (cursor: string | null, amount: number | null) => {
return postsService.loadPublicFeed(cursor, amount)
},
[postsService],
)
const [posts, setPosts] = useState<Post[]>([]) const [posts, setPosts] = useState<Post[]>([])
const [hasMore, setHasMore] = useState(true) const [hasMore, setHasMore] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
@ -38,56 +32,54 @@ export default function HomePage({ postsService, mediaService }: HomePageProps)
const cursor = useRef<string | null>(null) const cursor = useRef<string | null>(null)
const loading = useRef(false) const loading = useRef(false)
const loadNextPage = useCallback(async () => { const loadNextPage = async () => {
if (loading.current || !hasMore || error) return if (loading.current || !hasMore || error) return
loading.current = true loading.current = true
try { try {
const delay = new Promise((resolve) => setTimeout(resolve, 500)) const [{ posts, next }] = await Promise.all([
const pagePromise = fetchPosts(cursor.current, PageSize) postsService.loadPublicFeed(cursor.current, PageSize),
const [page] = await Promise.all([pagePromise, delay]) delay(500),
setHasMore(page.length >= PageSize) ])
cursor.current = page.at(-1)?.postId ?? null
setPosts((prev) => [...prev, ...page]) setHasMore(posts.length >= PageSize)
cursor.current = next
setPosts((prev) => [...prev, ...posts])
} catch (e: unknown) { } catch (e: unknown) {
const err = e as Error setError((e as Error).message)
setError(err.message)
} finally { } finally {
loading.current = false loading.current = false
} }
}, [fetchPosts, hasMore, error]) }
const onCreatePost = useCallback( const onCreatePost = async (
async ( content: string,
content: string, files: { file: File; width: number; height: number }[],
files: { file: File; width: number; height: number }[], isPublic: boolean,
isPublic: boolean, ) => {
) => { setIsSubmitting(true)
setIsSubmitting(true) if (user == null) throw new Error('Not logged in')
if (user == null) throw new Error('Not logged in') try {
try { const media = await Promise.all(
const media = await Promise.all( files.map(async ({ file, width, height }) => {
files.map(async ({ file, width, height }) => { const { mediaId, url } = await mediaService.uploadImage(file)
const { mediaId, url } = await mediaService.uploadImage(file)
return { return {
mediaId, mediaId,
url, url,
width, width,
height, height,
} }
}), }),
) )
const post = await postsService.createNew(user.id, content, media, isPublic) const post = await postsService.createNew(user.id, content, media, isPublic)
setPosts((pages) => [post, ...pages]) setPosts((pages) => [post, ...pages])
} catch (error) { } catch (error) {
console.error('Failed to create post:', error) console.error('Failed to create post:', error)
} finally { } finally {
setIsSubmitting(false) setIsSubmitting(false)
} }
}, }
[mediaService, postsService, setPosts, user],
)
const isLoggedIn = user != null const isLoggedIn = user != null

View file

@ -29,34 +29,22 @@ export class PostsService {
return Post.fromDto(response.data.post) return Post.fromDto(response.data.post)
} }
async loadPublicFeed(cursor: string | null, amount: number | null): Promise<Post[]> { async loadPublicFeed(
const response = await this.client.GET('/posts', {
query: { cursor, amount },
credentials: 'include',
})
if (!response.data) {
return []
}
return response.data?.posts.map((post) => Post.fromDto(post))
}
async loadByAuthor(
username: string,
cursor: string | null, cursor: string | null,
amount: number | null, amount: number | null,
): Promise<Post[]> { ): Promise<{ posts: Post[]; next: string | null }> {
const response = await this.client.GET('/posts', { const response = await this.client.GET('/posts', {
query: { From: cursor ?? undefined, Amount: amount ?? undefined, Author: username }, params: {
query: { From: cursor ?? undefined, Amount: amount ?? undefined },
},
credentials: 'include', credentials: 'include',
}) })
if (!response.data) { if (!response.data) {
return [] return { posts: [], next: null }
} }
return response.data?.posts.map((post) => Post.fromDto(post)) return { posts: response.data.posts.map(Post.fromDto), next: response.data.next }
} }
async addReaction(postId: string, emoji: string): Promise<void> { async addReaction(postId: string, emoji: string): Promise<void> {

3
src/utils/delay.ts Normal file
View file

@ -0,0 +1,3 @@
export function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}