test blog profile

This commit is contained in:
2025-06-25 20:17:31 +09:00
parent 7791399314
commit 66712bfa38
8 changed files with 529 additions and 6 deletions

View File

@@ -0,0 +1,133 @@
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