Initial commit — OFApp client + server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
147
client/src/pages/Users.jsx
Normal file
147
client/src/pages/Users.jsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { getSubscriptions, getUser, startDownload } from '../api'
|
||||
import UserCard from '../components/UserCard'
|
||||
import Spinner from '../components/Spinner'
|
||||
import LoadMoreButton from '../components/LoadMoreButton'
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
|
||||
export default function Users() {
|
||||
const [users, setUsers] = useState([])
|
||||
const [offset, setOffset] = useState(0)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [downloadingUsers, setDownloadingUsers] = useState(new Set())
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers()
|
||||
}, [])
|
||||
|
||||
const enrichUsers = (items) => {
|
||||
items.forEach(async (item) => {
|
||||
const profile = await getUser(item.username)
|
||||
if (profile && !profile.error) {
|
||||
setUsers((prev) =>
|
||||
prev.map((u) =>
|
||||
u.id === item.id
|
||||
? { ...u, postsCount: profile.postsCount, mediasCount: profile.mediasCount, photosCount: profile.photosCount, videosCount: profile.videosCount }
|
||||
: u
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const loadUsers = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
const data = await getSubscriptions(0)
|
||||
if (data.error) {
|
||||
setError(data.error)
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
const items = data.list || data || []
|
||||
setUsers(items)
|
||||
setOffset(PAGE_SIZE)
|
||||
setHasMore(items.length >= PAGE_SIZE)
|
||||
setLoading(false)
|
||||
enrichUsers(items)
|
||||
}
|
||||
|
||||
const loadMore = async () => {
|
||||
if (loadingMore) return
|
||||
|
||||
setLoadingMore(true)
|
||||
const data = await getSubscriptions(offset)
|
||||
|
||||
if (!data.error) {
|
||||
const items = data.list || data || []
|
||||
setUsers((prev) => [...prev, ...items])
|
||||
setOffset((prev) => prev + PAGE_SIZE)
|
||||
setHasMore(items.length >= PAGE_SIZE)
|
||||
enrichUsers(items)
|
||||
}
|
||||
|
||||
setLoadingMore(false)
|
||||
}
|
||||
|
||||
const handleDownload = async (userId, username) => {
|
||||
setDownloadingUsers((prev) => new Set([...prev, userId]))
|
||||
|
||||
const result = await startDownload(userId, null, null, username)
|
||||
if (result.error) {
|
||||
console.error('Download failed:', result.error)
|
||||
}
|
||||
|
||||
// Remove from downloading set after a brief delay to show feedback
|
||||
setTimeout(() => {
|
||||
setDownloadingUsers((prev) => {
|
||||
const next = new Set(prev)
|
||||
next.delete(userId)
|
||||
return next
|
||||
})
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
if (loading) return <Spinner />
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="text-center py-16">
|
||||
<p className="text-red-400 mb-4">{error}</p>
|
||||
<button
|
||||
onClick={loadUsers}
|
||||
className="text-[#0095f6] hover:underline text-sm"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-white mb-1">Subscriptions</h1>
|
||||
<p className="text-gray-500 text-sm">
|
||||
{users.length} user{users.length !== 1 ? 's' : ''} found
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{users.length === 0 ? (
|
||||
<div className="text-center py-16">
|
||||
<p className="text-gray-500">No subscriptions found</p>
|
||||
<p className="text-gray-600 text-sm mt-1">
|
||||
Check your auth credentials in Settings
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{[...users].sort((a, b) => (a.name || '').localeCompare(b.name || '')).map((user) => (
|
||||
<UserCard
|
||||
key={user.id}
|
||||
user={user}
|
||||
onDownload={handleDownload}
|
||||
downloading={downloadingUsers.has(user.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<LoadMoreButton
|
||||
onClick={loadMore}
|
||||
loading={loadingMore}
|
||||
hasMore={hasMore}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user