fix loading feed
This commit is contained in:
parent
cf5494c15b
commit
95ea2a5f23
4 changed files with 54 additions and 71 deletions
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
3
src/utils/delay.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function delay(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue