Files
psf-memo-client/src/components/app-body/posts/index.js
T

151 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Display the most recent Memo posts from psf-memo-db.
*/
// Global npm libraries
import React, { useState, useEffect } from 'react'
import { Container, Row, Col, Spinner, Button } from 'react-bootstrap'
// Local libraries
import MemoDb from '../../../services/memo-db'
import PostFeedItem from '../../post-feed/post-feed-item'
import PostThreadModal from '../../post-thread-modal'
import {
collectPostAddrs,
loadThreadProfiles
} from '../../post-thread-modal/thread-profiles'
import '../../../App.css'
import '../../post-feed/post-feed.css'
const PAGE_SIZE = 100
function RecentPosts () {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [posts, setPosts] = useState([])
const [profiles, setProfiles] = useState({})
const [pagination, setPagination] = useState(null)
const [offset, setOffset] = useState(0)
const [threadTxid, setThreadTxid] = useState(null)
const [showThreadModal, setShowThreadModal] = useState(false)
const openThread = (txid) => {
setThreadTxid(txid)
setShowThreadModal(true)
}
const closeThread = () => {
setShowThreadModal(false)
setThreadTxid(null)
}
useEffect(() => {
const loadPosts = async () => {
setLoading(true)
setError(null)
setProfiles({})
try {
const memoDb = new MemoDb()
const data = await memoDb.getRecentPosts({ limit: PAGE_SIZE, offset })
const loadedPosts = data.posts || []
const addrs = collectPostAddrs(loadedPosts)
const profileMap = await loadThreadProfiles(addrs, memoDb)
setPosts(loadedPosts)
setProfiles(profileMap)
setPagination(data.pagination || null)
} catch (err) {
setError(err.message || 'Failed to load recent posts')
setPosts([])
setProfiles({})
setPagination(null)
}
setLoading(false)
}
loadPosts()
}, [offset])
const canGoBack = offset > 0
const canGoNext = pagination?.hasMore ?? false
const handlePrevious = () => {
setOffset((prev) => Math.max(0, prev - PAGE_SIZE))
}
const handleNext = () => {
setOffset((prev) => prev + PAGE_SIZE)
}
return (
<Container>
<Row>
<Col>
<h1 className='mt-4'>Recent Posts</h1>
{pagination && posts.length > 0 && (
<p className='text-muted'>
Showing {pagination.offset + 1}{pagination.offset + posts.length} of {pagination.total} posts
</p>
)}
{pagination && posts.length === 0 && (
<p className='text-muted'>No posts on this page.</p>
)}
{error && <p className='text-danger'>{error}</p>}
{loading && (
<div className='text-center my-5'>
<Spinner animation='border' role='status' variant='primary'>
<span className='visually-hidden'>Loading...</span>
</Spinner>
</div>
)}
{!loading && !error && posts.length > 0 && (
<div className='posts-feed mt-3'>
{posts.map((post) => (
<PostFeedItem
key={post.txid}
post={post}
profiles={profiles}
onReplyClick={() => openThread(post.txid)}
showFooterMeta
/>
))}
</div>
)}
{!loading && !error && (pagination || offset > 0) && (
<div className='d-flex justify-content-between mt-3 mb-4'>
<Button
variant='outline-primary'
onClick={handlePrevious}
disabled={!canGoBack}
>
Previous
</Button>
<Button
variant='outline-primary'
onClick={handleNext}
disabled={!canGoNext}
>
Next
</Button>
</div>
)}
</Col>
</Row>
<PostThreadModal
show={showThreadModal}
txid={threadTxid}
onHide={closeThread}
/>
</Container>
)
}
export default RecentPosts