Add endorsement badges and endorsement history
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -9,7 +9,7 @@ const UserCard: React.FC<{ user: User }> = ({ user }) => {
|
|||||||
<div className="small">{user.bio}</div>
|
<div className="small">{user.bio}</div>
|
||||||
<div style={{marginTop:8}}>
|
<div style={{marginTop:8}}>
|
||||||
{user.specialties.map((s) => (
|
{user.specialties.map((s) => (
|
||||||
<span key={s} className="tag">{s}</span>
|
<span key={s} className="tag">{s} ({user.endorsements[s] ?? 0})</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom'
|
|||||||
import useAppStore from '../store/useAppStore'
|
import useAppStore from '../store/useAppStore'
|
||||||
import EndorseButton from '../components/EndorseButton'
|
import EndorseButton from '../components/EndorseButton'
|
||||||
import UserCard from '../components/UserCard'
|
import UserCard from '../components/UserCard'
|
||||||
|
import { formatTime } from '../utils/fileHelpers'
|
||||||
|
|
||||||
const Profile: React.FC = () => {
|
const Profile: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
@@ -10,6 +11,8 @@ const Profile: React.FC = () => {
|
|||||||
const posts = useAppStore((s) => s.posts.filter((p) => p.authorId === id))
|
const posts = useAppStore((s) => s.posts.filter((p) => p.authorId === id))
|
||||||
const endorseUser = useAppStore((s) => s.endorseUser)
|
const endorseUser = useAppStore((s) => s.endorseUser)
|
||||||
const currentUserId = useAppStore((s) => s.currentUserId)
|
const currentUserId = useAppStore((s) => s.currentUserId)
|
||||||
|
const endorsementHistory = useAppStore((s) => s.endorsementHistory)
|
||||||
|
const allUsers = useAppStore((s) => s.users)
|
||||||
|
|
||||||
if (!user) return <div className="card">User not found</div>
|
if (!user) return <div className="card">User not found</div>
|
||||||
|
|
||||||
@@ -31,6 +34,20 @@ const Profile: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{(() => {
|
||||||
|
const recent = endorsementHistory.filter((e) => e.type === 'user' && e.toUserId === id).slice(0,5)
|
||||||
|
if (recent.length === 0) return null
|
||||||
|
return (
|
||||||
|
<div className="card" style={{marginTop:8}}>
|
||||||
|
<h4>Recent endorsements</h4>
|
||||||
|
{recent.map((r) => {
|
||||||
|
const byName = allUsers.find((u) => u.id === r.by)?.name ?? 'Someone'
|
||||||
|
return <div key={r.id} className="small">{byName} endorsed {r.specialty} · {formatTime(r.createdAt)} ago</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
|
||||||
<h3>Posts</h3>
|
<h3>Posts</h3>
|
||||||
{posts.length === 0 && <div className="card">No posts yet.</div>}
|
{posts.length === 0 && <div className="card">No posts yet.</div>}
|
||||||
{posts.map((p) => (
|
{posts.map((p) => (
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type AppState = {
|
|||||||
currentUserId: string | null
|
currentUserId: string | null
|
||||||
selectedPostId: string | null
|
selectedPostId: string | null
|
||||||
ui: UIState
|
ui: UIState
|
||||||
|
endorsementHistory: { id: string; type: 'user' | 'post'; by?: string | null; toUserId?: string; postId?: string; specialty?: string; createdAt: number }[]
|
||||||
seedData: () => void
|
seedData: () => void
|
||||||
createUser: (data: { name: string; email: string; bio: string; specialties: string[] }) => User
|
createUser: (data: { name: string; email: string; bio: string; specialties: string[] }) => User
|
||||||
setCurrentUser: (id: string | null) => void
|
setCurrentUser: (id: string | null) => void
|
||||||
@@ -30,6 +31,7 @@ const useAppStore = create<AppState>((set, get) => ({
|
|||||||
currentUserId: null,
|
currentUserId: null,
|
||||||
selectedPostId: null,
|
selectedPostId: null,
|
||||||
ui: { isCreatePostOpen: false },
|
ui: { isCreatePostOpen: false },
|
||||||
|
endorsementHistory: [],
|
||||||
|
|
||||||
seedData: () => {
|
seedData: () => {
|
||||||
const users = seedUsers()
|
const users = seedUsers()
|
||||||
@@ -69,6 +71,7 @@ const useAppStore = create<AppState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
endorseUser: (userId, specialty) => {
|
endorseUser: (userId, specialty) => {
|
||||||
|
const by = get().currentUserId ?? null
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
users: state.users.map((u) => {
|
users: state.users.map((u) => {
|
||||||
if (u.id !== userId) return u
|
if (u.id !== userId) return u
|
||||||
@@ -76,15 +79,20 @@ const useAppStore = create<AppState>((set, get) => ({
|
|||||||
current[specialty] = (current[specialty] || 0) + 1
|
current[specialty] = (current[specialty] || 0) + 1
|
||||||
return { ...u, endorsements: current }
|
return { ...u, endorsements: current }
|
||||||
}),
|
}),
|
||||||
|
endorsementHistory: [{ id: makeId(), type: 'user', by, toUserId: userId, specialty, createdAt: Date.now() }, ...(state.endorsementHistory || [])],
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
endorsePost: (postId) => {
|
endorsePost: (postId) => {
|
||||||
|
const by = get().currentUserId ?? null
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
posts: state.posts.map((p) => (p.id === postId ? { ...p, endorsements: p.endorsements + 1 } : p)),
|
posts: state.posts.map((p) => (p.id === postId ? { ...p, endorsements: p.endorsements + 1 } : p)),
|
||||||
|
endorsementHistory: [{ id: makeId(), type: 'post', by, postId, createdAt: Date.now() }, ...(state.endorsementHistory || [])],
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
attachPDFToPost: (postId, file) => {
|
attachPDFToPost: (postId, file) => {
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user