@{post.authorName}• {formattedDate}
+ {!hideViewButton && (
+ <>
+ {' • '}
+
+ View
+
+ >
+ )}
{post.content}
diff --git a/src/app/feed/pages/PostPage.tsx b/src/app/feed/pages/PostPage.tsx
new file mode 100644
index 0000000..6e51c75
--- /dev/null
+++ b/src/app/feed/pages/PostPage.tsx
@@ -0,0 +1,127 @@
+import { useEffect, useState } from 'react'
+import { useNavigate, useParams } from 'react-router-dom'
+import { Post } from '../posts/posts.ts'
+import { PostsService } from '../posts/postsService.ts'
+import SingleColumnLayout from '../../../layouts/SingleColumnLayout.tsx'
+import NavBar from '../../../components/NavBar.tsx'
+import AuthNavButtons from '../../auth/components/AuthNavButtons.tsx'
+import PostItem from '../components/PostItem.tsx'
+
+interface PostPageProps {
+ postsService: PostsService
+}
+
+export default function PostPage({ postsService }: PostPageProps) {
+ const { postId } = useParams<{ postId: string }>()
+ const navigate = useNavigate()
+ const [post, setPost] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ useEffect(() => {
+ const fetchPost = async () => {
+ if (!postId) {
+ setError('Post ID is required')
+ setLoading(false)
+ return
+ }
+
+ try {
+ // Load posts and find the one with matching ID
+ const { posts } = await postsService.loadPublicFeed(null, 100)
+ const foundPost = posts.find(p => p.postId === postId)
+
+ if (foundPost) {
+ setPost(foundPost)
+ } else {
+ setError('Post not found')
+ }
+ } catch (e: unknown) {
+ setError((e as Error).message)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ fetchPost()
+ }, [postId, postsService])
+
+ const onAddReaction = async (emoji: string) => {
+ if (!post) return
+
+ await postsService.addReaction(post.postId, emoji)
+
+ setPost(prevPost => {
+ if (!prevPost) return null
+
+ const updatedReactions = [...prevPost.reactions]
+ const theReaction = updatedReactions.find(r => r.emoji === emoji)
+
+ if (theReaction) {
+ theReaction.count++
+ theReaction.didReact = true
+ } else {
+ updatedReactions.push({ emoji, count: 1, didReact: true })
+ }
+
+ return {
+ ...prevPost,
+ reactions: updatedReactions
+ }
+ })
+ }
+
+ const onClearReaction = async (emoji: string) => {
+ if (!post) return
+
+ await postsService.removeReaction(post.postId, emoji)
+
+ setPost(prevPost => {
+ if (!prevPost) return null
+
+ const updatedReactions = [...prevPost.reactions]
+ const theReaction = updatedReactions.find(r => r.emoji === emoji)
+
+ if (theReaction) {
+ theReaction.count = Math.max(theReaction.count - 1, 0)
+ theReaction.didReact = false
+ }
+
+ return {
+ ...prevPost,
+ reactions: updatedReactions
+ }
+ })
+ }
+
+ return (
+
+
+
+ }
+ >
+
+ {loading && Loading...
}
+
+ {error && (
+
+ Error: {error}
+
+ )}
+
+ {post && (
+
+ )}
+
+
+ )
+}