Files
juwan-frontend/app/(main)/post/new/page.tsx
T
zetaloop 4d8877f588 fix(pages): adapt all pages to backend-aligned types
Replace removed fields with available data sources throughout UI:
- order pages: use service.title instead of consumer/player names
- chat: look up sender from session.participants, remove readonly
- community: simplify post cards, keep pinned icon
- post detail: keep pinned/linkedOrderId display
- shop rules: use string commissionValue
- dashboard: parse string amounts for income display
- dispute/review: remove initiator/avatar references
2026-04-23 21:15:28 +08:00

261 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { useRequireAuth } from "@/lib/use-require-auth"
import { useAuthStore } from "@/store/auth"
import { useOrderStore } from "@/store/orders"
import { usePostStore } from "@/store/posts"
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"
import { ArrowLeft, ImagePlus, X } from "lucide-react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { z } from "zod"
const postSchema = z.object({
title: z.string().min(2, "标题至少2个字符").max(50, "标题最多50个字符"),
content: z.string().min(10, "内容至少10个字符"),
})
const tagOptions = ["英雄联盟", "王者荣耀", "CS2", "原神", "上分", "攻略", "好评", "吐槽", "求组队"]
export default function NewPostPage() {
const router = useRouter()
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<string[]>([])
const [imageCount, setImageCount] = useState(0)
const [selectedQuotePostId, setSelectedQuotePostId] = useState<string | undefined>(undefined)
const [selectedOrderId, setSelectedOrderId] = useState<string | undefined>(undefined)
const canShowOrder = currentRole === "consumer"
const effectivePostType = canShowOrder || postType !== "show_order" ? postType : "normal"
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<z.infer<typeof postSchema>>({
resolver: standardSchemaResolver(postSchema),
})
const toggleTag = (tag: string) => {
setSelectedTags((prev) =>
prev.includes(tag) ? prev.filter((t) => t !== tag) : prev.length < 5 ? [...prev, tag] : prev,
)
}
const availableOrders = orders.filter(
(order) => order.status === "completed" && order.consumerId === userId,
)
const onSubmit = async (data: z.infer<typeof postSchema>) => {
if (!isAuthenticated) {
requireAuth(() => undefined)
return
}
requireAuth(() => {
if (!user) return
createPost({
author: user,
title: data.title,
content: data.content,
images: Array.from({ length: imageCount }).map(() => "/posts/p1-1.jpg"),
tags: selectedTags,
linkedOrderId: effectivePostType === "show_order" ? selectedOrderId : undefined,
})
router.push("/community")
})
}
return (
<div className="container mx-auto max-w-2xl px-4 py-8 space-y-6">
<Link
href="/community"
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground mb-4"
>
<ArrowLeft className="h-4 w-4" />
</Link>
<Card className="hover:shadow-card-hover">
<CardHeader>
<CardTitle className="tracking-tighter leading-tight"></CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div className="space-y-2">
<Label></Label>
<Select value={effectivePostType} onValueChange={setPostType}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="normal"></SelectItem>
{canShowOrder && <SelectItem value="show_order"></SelectItem>}
<SelectItem value="quote"></SelectItem>
</SelectContent>
</Select>
</div>
{effectivePostType === "show_order" && (
<div className="space-y-2">
<Label></Label>
<Select value={selectedOrderId} onValueChange={setSelectedOrderId}>
<SelectTrigger>
<SelectValue placeholder="选择要展示的订单" />
</SelectTrigger>
<SelectContent>
{availableOrders.map((order) => (
<SelectItem key={order.id} value={order.id}>
{order.service.title}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{effectivePostType === "quote" && (
<div className="space-y-2">
<Label></Label>
<Select value={selectedQuotePostId} onValueChange={setSelectedQuotePostId}>
<SelectTrigger>
<SelectValue placeholder="选择要引用的帖子" />
</SelectTrigger>
<SelectContent>
{posts.map((post) => (
<SelectItem key={post.id} value={post.id}>
{post.title}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="mt-2 rounded-xl border bg-muted/50 p-3 text-sm text-muted-foreground">
<p className="font-medium text-foreground"></p>
{selectedQuotePostId ? (
(() => {
const post = posts.find((p) => p.id === selectedQuotePostId)
if (!post) return <p className="mt-1"></p>
return (
<div className="mt-2 rounded-lg border bg-background p-3">
<p className="font-medium text-foreground">{post.title}</p>
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
{post.content}
</p>
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
<span>@{post.author.nickname}</span>
<span>·</span>
<span>{new Date(post.createdAt).toLocaleDateString("zh-CN")}</span>
</div>
</div>
)
})()
) : (
<p className="mt-1">...</p>
)}
</div>
</div>
)}
<div className="space-y-2">
<Label htmlFor="title"></Label>
<Input id="title" placeholder="请输入帖子标题" {...register("title")} />
{errors.title && <p className="text-xs text-destructive">{errors.title.message}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="content"></Label>
<Textarea id="content" placeholder="写点什么..." rows={6} {...register("content")} />
{errors.content && (
<p className="text-xs text-destructive">{errors.content.message}</p>
)}
</div>
<div className="space-y-2">
<Label>9</Label>
<div className="flex gap-2 flex-wrap">
{Array.from({ length: imageCount }).map((_, i) => (
<div
key={`img-${i.toString()}`}
className="h-20 w-20 rounded-lg bg-muted flex items-center justify-center relative"
>
<span className="text-xs text-muted-foreground"></span>
<button
type="button"
onClick={() => setImageCount((c) => c - 1)}
className="absolute -top-1 -right-1 h-4 w-4 rounded-full bg-destructive text-destructive-foreground flex items-center justify-center"
>
<X className="h-3 w-3" />
</button>
</div>
))}
{imageCount < 9 && (
<button
type="button"
onClick={() => setImageCount((c) => c + 1)}
className="h-20 w-20 rounded-lg border-2 border-dashed border-muted-foreground/25 flex flex-col items-center justify-center gap-1 text-muted-foreground hover:border-muted-foreground/50 transition-colors"
>
<ImagePlus className="h-5 w-5" />
<span className="text-[10px]"></span>
</button>
)}
</div>
</div>
<div className="space-y-2">
<Label>5</Label>
<div className="flex flex-wrap gap-2">
{tagOptions.map((tag) => (
<Badge
key={tag}
variant={selectedTags.includes(tag) ? "default" : "outline"}
className="cursor-pointer"
onClick={() => toggleTag(tag)}
>
{tag}
</Badge>
))}
</div>
</div>
<div className="flex gap-2">
<Button
type="submit"
className="flex-1"
disabled={isSubmitting || (postType === "show_order" && !selectedOrderId)}
>
{isSubmitting ? "发布中..." : "发布"}
</Button>
<Button type="button" variant="outline" asChild>
<Link href="/community"></Link>
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
)
}