133 lines
3.9 KiB
JavaScript
133 lines
3.9 KiB
JavaScript
import React, { useState, useEffect } from 'react'
|
||
import { collections } from '../api/atproto.js'
|
||
import AvatarImage from './AvatarImage.jsx'
|
||
import LoadingSkeleton from './LoadingSkeleton.jsx'
|
||
|
||
const ProfileRecordList = ({ apiConfig, user, agent, onRecordDeleted }) => {
|
||
const [profiles, setProfiles] = useState([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState(null)
|
||
|
||
useEffect(() => {
|
||
if (apiConfig?.admin && apiConfig?.collection) {
|
||
fetchProfiles()
|
||
}
|
||
}, [apiConfig])
|
||
|
||
const fetchProfiles = async () => {
|
||
try {
|
||
setLoading(true)
|
||
setError(null)
|
||
|
||
const adminProfiles = await collections.getProfiles(
|
||
apiConfig.pds,
|
||
apiConfig.admin,
|
||
apiConfig.collection
|
||
)
|
||
|
||
// Sort profiles: admin type first, then user type
|
||
const sortedProfiles = adminProfiles.sort((a, b) => {
|
||
if (a.value.type === 'admin' && b.value.type !== 'admin') return -1
|
||
if (a.value.type !== 'admin' && b.value.type === 'admin') return 1
|
||
return 0
|
||
})
|
||
|
||
setProfiles(sortedProfiles)
|
||
} catch (err) {
|
||
console.error('Failed to fetch profiles:', err)
|
||
setError('プロフィールの読み込みに失敗しました')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleDelete = async (uri) => {
|
||
if (!user || !agent) return
|
||
if (!confirm('このプロフィールを削除しますか?')) return
|
||
|
||
try {
|
||
const rkey = uri.split('/').pop()
|
||
await agent.api.com.atproto.repo.deleteRecord({
|
||
repo: user.did,
|
||
collection: `${apiConfig.collection}.profile`,
|
||
rkey: rkey
|
||
})
|
||
|
||
// Invalidate cache and refresh
|
||
collections.invalidateCache(`${apiConfig.collection}.profile`)
|
||
await fetchProfiles()
|
||
|
||
if (onRecordDeleted) {
|
||
onRecordDeleted()
|
||
}
|
||
} catch (err) {
|
||
console.error('Failed to delete profile:', err)
|
||
setError('プロフィールの削除に失敗しました')
|
||
}
|
||
}
|
||
|
||
if (loading) {
|
||
return <LoadingSkeleton count={3} showTitle={true} />
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="error-state">
|
||
<p>{error}</p>
|
||
<button onClick={fetchProfiles} className="retry-btn">再試行</button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (profiles.length === 0) {
|
||
return (
|
||
<div className="empty-state">
|
||
<p>プロフィールがありません</p>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="record-list profile-record-list">
|
||
{profiles.map((profile) => (
|
||
<div key={profile.uri} className={`record-item comment-style ${profile.value.type}`}>
|
||
<div className="message-header">
|
||
<div className="avatar">
|
||
<AvatarImage
|
||
src={profile.value.author.avatar}
|
||
alt={profile.value.author.displayName || profile.value.author.handle}
|
||
size={40}
|
||
/>
|
||
</div>
|
||
<div className="user-info">
|
||
<div className="display-name">
|
||
{profile.value.author.displayName || profile.value.author.handle}
|
||
{profile.value.type === 'admin' && (
|
||
<span className="admin-badge">Admin</span>
|
||
)}
|
||
</div>
|
||
<div className="handle">@{profile.value.author.handle}</div>
|
||
<div className="timestamp">
|
||
{new Date(profile.value.createdAt).toLocaleString()}
|
||
</div>
|
||
</div>
|
||
{user && (
|
||
<div className="record-actions">
|
||
<button
|
||
onClick={() => handleDelete(profile.uri)}
|
||
className="delete-btn"
|
||
title="削除"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div className="message-content">{profile.value.text}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default ProfileRecordList |