519fb92c34
Turn on react-hooks/set-state-in-effect and react-hooks/incompatible-library, then remove effect-driven local state sync patterns across affected pages. Keep behavior stable by deriving values from source state, remounting tab state by role key, and replacing useForm watch with useWatch.
202 lines
7.3 KiB
TypeScript
202 lines
7.3 KiB
TypeScript
"use client"
|
||
|
||
import { Camera } from "lucide-react"
|
||
import Link from "next/link"
|
||
import { useRef, useState } from "react"
|
||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||
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 { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||
import { Separator } from "@/components/ui/separator"
|
||
import { Switch } from "@/components/ui/switch"
|
||
import { Textarea } from "@/components/ui/textarea"
|
||
import { notifySuccess } from "@/lib/toast"
|
||
import type { UserRole } from "@/lib/types"
|
||
import { useAuthStore } from "@/store/auth"
|
||
|
||
export default function SettingsPage() {
|
||
const { currentRole, verifiedRoles, switchRole, user, updateProfile } = useAuthStore()
|
||
const [nickname, setNickname] = useState(user?.nickname ?? "")
|
||
const [bio, setBio] = useState(user?.bio ?? "")
|
||
const [avatar, setAvatar] = useState(user?.avatar ?? "")
|
||
const fileRef = useRef<HTMLInputElement>(null)
|
||
|
||
const isRoleVerified = (role: UserRole) => verifiedRoles.includes(role)
|
||
|
||
return (
|
||
<div className="max-w-2xl space-y-6">
|
||
<h1 className="text-2xl font-bold">个人设置</h1>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-base">头像</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="flex items-center gap-4">
|
||
<div className="relative">
|
||
<Avatar className="h-20 w-20">
|
||
<AvatarImage src={avatar} />
|
||
<AvatarFallback className="text-lg">{user?.nickname?.[0] ?? "?"}</AvatarFallback>
|
||
</Avatar>
|
||
<input
|
||
ref={fileRef}
|
||
type="file"
|
||
accept="image/*"
|
||
className="hidden"
|
||
onChange={(event) => {
|
||
const file = event.target.files?.[0]
|
||
if (!file) return
|
||
setAvatar(URL.createObjectURL(file))
|
||
event.target.value = ""
|
||
}}
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="absolute bottom-0 right-0 h-7 w-7 rounded-full bg-primary text-primary-foreground flex items-center justify-center"
|
||
onClick={() => fileRef.current?.click()}
|
||
>
|
||
<Camera className="h-3.5 w-3.5" />
|
||
</button>
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">点击更换头像,支持 JPG、PNG 格式</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-base">基本信息</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="nickname">昵称</Label>
|
||
<Input
|
||
id="nickname"
|
||
value={nickname}
|
||
onChange={(event) => setNickname(event.target.value)}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="bio">个人简介</Label>
|
||
<Textarea
|
||
id="bio"
|
||
value={bio}
|
||
onChange={(event) => setBio(event.target.value)}
|
||
rows={3}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="phone">手机号</Label>
|
||
<Input id="phone" defaultValue={user?.phone ?? ""} disabled />
|
||
<p className="text-xs text-muted-foreground">如需更换手机号请联系客服</p>
|
||
</div>
|
||
<Button
|
||
onClick={() => {
|
||
updateProfile({
|
||
nickname: nickname.trim() || user?.nickname,
|
||
bio: bio.trim(),
|
||
avatar,
|
||
})
|
||
notifySuccess("资料已保存")
|
||
}}
|
||
>
|
||
保存修改
|
||
</Button>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-base">身份切换</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
<p className="text-sm text-muted-foreground">切换身份后,导航和功能将对应变化</p>
|
||
<RadioGroup
|
||
value={currentRole}
|
||
onValueChange={(v) => {
|
||
const role = v as UserRole
|
||
if (isRoleVerified(role)) {
|
||
switchRole(role)
|
||
}
|
||
}}
|
||
>
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="consumer" id="role-consumer" />
|
||
<Label htmlFor="role-consumer">消费者</Label>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem
|
||
value="player"
|
||
id="role-player"
|
||
disabled={!isRoleVerified("player")}
|
||
/>
|
||
<Label
|
||
htmlFor="role-player"
|
||
className={!isRoleVerified("player") ? "text-muted-foreground" : ""}
|
||
>
|
||
打手
|
||
</Label>
|
||
</div>
|
||
{!isRoleVerified("player") && (
|
||
<Link href="/verify" className="text-sm text-primary hover:underline">
|
||
去认证
|
||
</Link>
|
||
)}
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="owner" id="role-owner" disabled={!isRoleVerified("owner")} />
|
||
<Label
|
||
htmlFor="role-owner"
|
||
className={!isRoleVerified("owner") ? "text-muted-foreground" : ""}
|
||
>
|
||
店主
|
||
</Label>
|
||
</div>
|
||
{!isRoleVerified("owner") && (
|
||
<Link href="/verify" className="text-sm text-primary hover:underline">
|
||
去认证
|
||
</Link>
|
||
)}
|
||
</div>
|
||
</RadioGroup>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-base">通知偏好</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium">订单通知</p>
|
||
<p className="text-xs text-muted-foreground">接单、完成、争议等状态变更</p>
|
||
</div>
|
||
<Switch defaultChecked />
|
||
</div>
|
||
<Separator />
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium">社区通知</p>
|
||
<p className="text-xs text-muted-foreground">点赞、评论、关注</p>
|
||
</div>
|
||
<Switch defaultChecked />
|
||
</div>
|
||
<Separator />
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium">系统通知</p>
|
||
<p className="text-xs text-muted-foreground">平台公告、活动推送</p>
|
||
</div>
|
||
<Switch />
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
)
|
||
}
|