✨ Post replies list
This commit is contained in:
128
app/composables/useRepliesList.ts
Normal file
128
app/composables/useRepliesList.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { ref, computed } from "vue"
|
||||
import type { SnPost } from "~/types/api"
|
||||
|
||||
export interface RepliesListParams {
|
||||
postId: string
|
||||
}
|
||||
|
||||
export interface RepliesListState {
|
||||
replies: SnPost[]
|
||||
loading: boolean
|
||||
error: string | null
|
||||
hasMore: boolean
|
||||
cursor: string | null
|
||||
total: number
|
||||
}
|
||||
|
||||
export const useRepliesList = (params: RepliesListParams) => {
|
||||
const api = useSolarNetwork()
|
||||
const pageSize = 20
|
||||
|
||||
const state = ref<RepliesListState>({
|
||||
replies: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
hasMore: true,
|
||||
cursor: null,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const isLoading = computed(() => state.value.loading)
|
||||
const hasError = computed(() => state.value.error !== null)
|
||||
const replies = computed(() => state.value.replies)
|
||||
const hasMore = computed(() => state.value.hasMore)
|
||||
|
||||
const buildQueryParams = (cursor: string | null = null) => {
|
||||
const offset = cursor ? parseInt(cursor) : 0
|
||||
|
||||
const queryParams: Record<string, string | number> = {
|
||||
offset,
|
||||
take: pageSize
|
||||
}
|
||||
|
||||
return queryParams
|
||||
}
|
||||
|
||||
const fetchReplies = async (cursor: string | null = null, append = false) => {
|
||||
try {
|
||||
state.value.loading = true
|
||||
state.value.error = null
|
||||
|
||||
const queryParams = buildQueryParams(cursor)
|
||||
|
||||
let total: number = 0
|
||||
const response = await api<SnPost[]>(
|
||||
`/sphere/posts/${params.postId}/replies`,
|
||||
{
|
||||
method: "GET",
|
||||
query: queryParams,
|
||||
onResponse({ response }) {
|
||||
total = parseInt(response.headers.get("x-total") || "0")
|
||||
if (response._data) {
|
||||
response._data = keysToCamel(response._data)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const fetchedReplies = response
|
||||
|
||||
if (append) {
|
||||
state.value.replies = [...state.value.replies, ...fetchedReplies]
|
||||
} else {
|
||||
state.value.replies = fetchedReplies
|
||||
}
|
||||
|
||||
// Check if we've reached the end based on X-Total header
|
||||
const currentTotal = state.value.replies.length
|
||||
const hasReachedEnd = total > 0 && currentTotal >= total
|
||||
|
||||
state.value.hasMore = !hasReachedEnd && fetchedReplies.length === pageSize
|
||||
state.value.cursor = state.value.hasMore
|
||||
? ((cursor ? parseInt(cursor) : 0) + fetchedReplies.length).toString()
|
||||
: null
|
||||
|
||||
return { hasReachedEnd }
|
||||
} catch (error) {
|
||||
state.value.error =
|
||||
error instanceof Error ? error.message : "Failed to fetch replies"
|
||||
console.error("Error fetching replies:", error)
|
||||
return { hasReachedEnd: false }
|
||||
} finally {
|
||||
state.value.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadMore = async (options?: {
|
||||
side: string
|
||||
done: (status: "empty" | "loading" | "error" | "ok") => void
|
||||
}) => {
|
||||
if (!state.value.hasMore || state.value.loading) {
|
||||
options?.done("empty")
|
||||
return
|
||||
}
|
||||
|
||||
const result = await fetchReplies(state.value.cursor, true)
|
||||
|
||||
if (result.hasReachedEnd) {
|
||||
options?.done("empty")
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
fetchReplies(null, false)
|
||||
}
|
||||
|
||||
// Initial load
|
||||
fetchReplies()
|
||||
|
||||
return {
|
||||
replies,
|
||||
isLoading,
|
||||
hasError,
|
||||
hasMore,
|
||||
error: computed(() => state.value.error),
|
||||
loadMore,
|
||||
refresh
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user