From 43a0cf7a733b1f9e2cdbbf460fea8b7e7be7a62c Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sun, 22 Feb 2026 08:29:59 +0800 Subject: [PATCH] feat(post): persist new posts and wire like interactions --- app/(main)/post/[id]/page.tsx | 25 ++++++----------- app/(main)/post/new/page.tsx | 31 ++++++++++++++++----- components/post-comments.tsx | 17 ++++++++++++ components/post-like-button.tsx | 35 ++++++++++++++++++++++++ lib/api/posts.ts | 8 +++--- store/posts.ts | 48 +++++++++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 components/post-like-button.tsx create mode 100644 store/posts.ts diff --git a/app/(main)/post/[id]/page.tsx b/app/(main)/post/[id]/page.tsx index cf4bdaa..251041d 100644 --- a/app/(main)/post/[id]/page.tsx +++ b/app/(main)/post/[id]/page.tsx @@ -1,27 +1,24 @@ -import { ArrowLeft, Heart, MessageCircle, Pin, Star } from "lucide-react" +import { ArrowLeft, MessageCircle, Pin, Star } from "lucide-react" import Image from "next/image" import Link from "next/link" import { notFound } from "next/navigation" import { PostComments } from "@/components/post-comments" +import { PostLikeButton } from "@/components/post-like-button" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader } from "@/components/ui/card" import { Separator } from "@/components/ui/separator" +import { getOrderById, getPlayerById, getPostById, listCommentsByPost } from "@/lib/api" import { roleLabels } from "@/lib/constants" -import { mockComments, mockOrders, mockPlayers, mockPosts } from "@/lib/mock" export default async function PostDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params - const post = mockPosts.find((p) => p.id === id) + const post = getPostById(id) if (!post) notFound() - const comments = mockComments.filter((c) => c.postId === id) - const linkedOrder = post.linkedOrderId - ? mockOrders.find((o) => o.id === post.linkedOrderId) - : null - const linkedPlayer = linkedOrder - ? mockPlayers.find((player) => player.id === linkedOrder.playerId) - : null + const comments = listCommentsByPost(id) + const linkedOrder = post.linkedOrderId ? getOrderById(post.linkedOrderId) : null + const linkedPlayer = linkedOrder ? getPlayerById(linkedOrder.playerId) : null return (
@@ -94,13 +91,7 @@ export default async function PostDetailPage({ params }: { params: Promise<{ id: )}
- + {post.commentCount} diff --git a/app/(main)/post/new/page.tsx b/app/(main)/post/new/page.tsx index 468f59f..7322b6a 100644 --- a/app/(main)/post/new/page.tsx +++ b/app/(main)/post/new/page.tsx @@ -20,10 +20,10 @@ import { SelectValue, } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" -import { mockPosts } from "@/lib/mock" import { useRequireAuth } from "@/lib/use-require-auth" import { useAuthStore } from "@/store/auth" import { useOrderStore } from "@/store/orders" +import { usePostStore } from "@/store/posts" const postSchema = z.object({ title: z.string().min(2, "标题至少2个字符").max(50, "标题最多50个字符"), @@ -37,7 +37,10 @@ export default function NewPostPage() { const { isAuthenticated, requireAuth } = useRequireAuth() const currentRole = useAuthStore((state) => state.currentRole) const userId = useAuthStore((state) => state.user?.id) + const user = useAuthStore((state) => state.user) const orders = useOrderStore((state) => state.orders) + const posts = usePostStore((state) => state.posts) + const createPost = usePostStore((state) => state.createPost) const [postType, setPostType] = useState("normal") const [selectedTags, setSelectedTags] = useState([]) const [imageCount, setImageCount] = useState(0) @@ -48,7 +51,7 @@ export default function NewPostPage() { register, handleSubmit, formState: { errors, isSubmitting }, - } = useForm({ + } = useForm>({ resolver: standardSchemaResolver(postSchema), }) @@ -68,14 +71,28 @@ export default function NewPostPage() { (order) => order.status === "completed" && order.consumerId === userId, ) - const onSubmit = async () => { + const onSubmit = async (data: z.infer) => { if (!isAuthenticated) { requireAuth(() => undefined) return } - await new Promise((r) => setTimeout(r, 500)) - router.push("/community") + requireAuth(() => { + if (!user) return + + createPost({ + author: user, + authorRole: currentRole, + title: data.title, + content: data.content, + images: Array.from({ length: imageCount }).map(() => "/posts/p1-1.jpg"), + tags: selectedTags, + linkedOrderId: postType === "show_order" ? selectedOrderId : undefined, + quotedPostId: postType === "quote" ? selectedQuotePostId : undefined, + }) + + router.push("/community") + }) } return ( @@ -134,7 +151,7 @@ export default function NewPostPage() { - {mockPosts.map((post) => ( + {posts.map((post) => ( {post.title} @@ -145,7 +162,7 @@ export default function NewPostPage() {

预览:

{selectedQuotePostId ? ( (() => { - const post = mockPosts.find((p) => p.id === selectedQuotePostId) + const post = posts.find((p) => p.id === selectedQuotePostId) if (!post) return

未找到帖子

return (
diff --git a/components/post-comments.tsx b/components/post-comments.tsx index 29c64e3..e579d16 100644 --- a/components/post-comments.tsx +++ b/components/post-comments.tsx @@ -83,6 +83,23 @@ export function PostComments({ initialComments, postId }: PostCommentsProps) { + ) +} diff --git a/lib/api/posts.ts b/lib/api/posts.ts index 3a8154a..7319158 100644 --- a/lib/api/posts.ts +++ b/lib/api/posts.ts @@ -1,13 +1,13 @@ -import { mockPosts } from "@/lib/mock" +import { usePostStore } from "@/store/posts" export function listPosts() { - return mockPosts + return usePostStore.getState().posts } export function getPostById(postId: string) { - return mockPosts.find((post) => post.id === postId) + return usePostStore.getState().posts.find((post) => post.id === postId) } export function listPostsByAuthor(userId: string) { - return mockPosts.filter((post) => post.author.id === userId) + return usePostStore.getState().posts.filter((post) => post.author.id === userId) } diff --git a/store/posts.ts b/store/posts.ts new file mode 100644 index 0000000..c1357a1 --- /dev/null +++ b/store/posts.ts @@ -0,0 +1,48 @@ +import { create } from "zustand" +import { generateId } from "@/lib/id" +import { mockPosts } from "@/lib/mock" +import type { Post, User, UserRole } from "@/lib/types" + +interface CreatePostInput { + author: User + authorRole: UserRole + title: string + content: string + images: string[] + tags: string[] + linkedOrderId?: string + quotedPostId?: string +} + +interface PostState { + posts: Post[] + createPost: (input: CreatePostInput) => Post +} + +export const usePostStore = create((set) => ({ + posts: mockPosts, + createPost: (input) => { + const post: Post = { + id: generateId("post"), + author: input.author, + authorRole: input.authorRole, + title: input.title.trim(), + content: input.content.trim(), + images: input.images, + tags: input.tags, + linkedOrderId: input.linkedOrderId, + quotedPostId: input.quotedPostId, + likeCount: 0, + commentCount: 0, + liked: false, + pinned: false, + createdAt: new Date().toISOString(), + } + + set((state) => ({ + posts: [post, ...state.posts], + })) + + return post + }, +}))