128 lines
4.2 KiB
JavaScript
128 lines
4.2 KiB
JavaScript
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
|
import { createRoot } from 'react-dom/client'
|
|
import CommentsFeed from '../../components/comments/CommentsFeed'
|
|
|
|
const FILTER_TABS = [
|
|
{ key: 'all', label: 'All' },
|
|
{ key: 'following', label: 'Following', authRequired: true },
|
|
{ key: 'mine', label: 'My Comments', authRequired: true },
|
|
]
|
|
|
|
function LatestCommentsPage({ initialComments = [], initialMeta = {}, isAuthenticated = false }) {
|
|
const [activeFilter, setActiveFilter] = useState('all')
|
|
const [comments, setComments] = useState(initialComments)
|
|
const [meta, setMeta] = useState(initialMeta)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState(null)
|
|
|
|
// Track if we've moved off the initial server-rendered data
|
|
const initialized = useRef(false)
|
|
|
|
const fetchComments = useCallback(async (filter, page = 1) => {
|
|
setLoading(true)
|
|
setError(null)
|
|
try {
|
|
const url = `/api/comments/latest?type=${encodeURIComponent(filter)}&page=${page}`
|
|
const res = await fetch(url, {
|
|
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
|
|
credentials: 'same-origin',
|
|
})
|
|
|
|
if (res.status === 401) {
|
|
setError('Please log in to view this feed.')
|
|
setComments([])
|
|
setMeta({})
|
|
return
|
|
}
|
|
|
|
if (! res.ok) {
|
|
setError('Failed to load comments. Please try again.')
|
|
return
|
|
}
|
|
|
|
const json = await res.json()
|
|
setComments(json.data ?? [])
|
|
setMeta(json.meta ?? {})
|
|
} catch {
|
|
setError('Network error. Please try again.')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [])
|
|
|
|
const handleFilterChange = (key) => {
|
|
if (key === activeFilter) return
|
|
setActiveFilter(key)
|
|
initialized.current = true
|
|
fetchComments(key, 1)
|
|
}
|
|
|
|
const handlePageChange = (page) => {
|
|
initialized.current = true
|
|
fetchComments(activeFilter, page)
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
}
|
|
|
|
return (
|
|
<div className="px-4 pt-10 pb-20 sm:px-6 lg:px-8 max-w-5xl mx-auto">
|
|
{/* Page header */}
|
|
<div className="mb-7">
|
|
<p className="text-xs font-semibold uppercase tracking-widest text-white/30 mb-1">Community</p>
|
|
<h1 className="text-3xl font-bold text-white leading-tight">Latest Comments</h1>
|
|
<p className="mt-1 text-sm text-white/50">Most recent artwork comments from the community.</p>
|
|
</div>
|
|
|
|
{/* Filter tabs — pill style */}
|
|
<div className="flex items-center gap-2 mb-6">
|
|
{FILTER_TABS.map((tab) => {
|
|
const disabled = tab.authRequired && !isAuthenticated
|
|
const active = activeFilter === tab.key
|
|
|
|
return (
|
|
<button
|
|
key={tab.key}
|
|
onClick={() => !disabled && handleFilterChange(tab.key)}
|
|
disabled={disabled}
|
|
aria-current={active ? 'page' : undefined}
|
|
title={disabled ? 'Log in to use this filter' : undefined}
|
|
className={[
|
|
'px-4 py-1.5 rounded-full text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500',
|
|
active
|
|
? 'bg-sky-600/25 text-sky-300 ring-1 ring-sky-500/40'
|
|
: 'text-white/50 hover:text-white/80 hover:bg-white/[0.06]',
|
|
disabled && 'opacity-30 cursor-not-allowed',
|
|
].filter(Boolean).join(' ')}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Feed content */}
|
|
<CommentsFeed
|
|
comments={comments}
|
|
meta={meta}
|
|
loading={loading}
|
|
error={error}
|
|
onPageChange={handlePageChange}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Auto-mount when the Blade view provides #latest-comments-root
|
|
const mountEl = document.getElementById('latest-comments-root')
|
|
if (mountEl) {
|
|
let props = {}
|
|
try {
|
|
const propsEl = document.getElementById('latest-comments-props')
|
|
props = propsEl ? JSON.parse(propsEl.textContent || '{}') : {}
|
|
} catch {
|
|
props = {}
|
|
}
|
|
createRoot(mountEl).render(<LatestCommentsPage {...props} />)
|
|
}
|
|
|
|
export default LatestCommentsPage
|