fix(settings): enable profile save and avatar selection
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { Camera } from "lucide-react"
|
import { Camera } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
import { useEffect, useRef, useState } from "react"
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
@@ -11,11 +12,22 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
|||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
|
import { notifySuccess } from "@/lib/toast"
|
||||||
import type { UserRole } from "@/lib/types"
|
import type { UserRole } from "@/lib/types"
|
||||||
import { useAuthStore } from "@/store/auth"
|
import { useAuthStore } from "@/store/auth"
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const { currentRole, verifiedRoles, switchRole, user } = useAuthStore()
|
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)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNickname(user?.nickname ?? "")
|
||||||
|
setBio(user?.bio ?? "")
|
||||||
|
setAvatar(user?.avatar ?? "")
|
||||||
|
}, [user])
|
||||||
|
|
||||||
const isRoleVerified = (role: UserRole) => verifiedRoles.includes(role)
|
const isRoleVerified = (role: UserRole) => verifiedRoles.includes(role)
|
||||||
|
|
||||||
@@ -30,12 +42,25 @@ export default function SettingsPage() {
|
|||||||
<CardContent className="flex items-center gap-4">
|
<CardContent className="flex items-center gap-4">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Avatar className="h-20 w-20">
|
<Avatar className="h-20 w-20">
|
||||||
<AvatarImage src={user?.avatar} />
|
<AvatarImage src={avatar} />
|
||||||
<AvatarFallback className="text-lg">{user?.nickname?.[0] ?? "?"}</AvatarFallback>
|
<AvatarFallback className="text-lg">{user?.nickname?.[0] ?? "?"}</AvatarFallback>
|
||||||
</Avatar>
|
</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
|
<button
|
||||||
type="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"
|
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" />
|
<Camera className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -51,18 +76,38 @@ export default function SettingsPage() {
|
|||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="nickname">昵称</Label>
|
<Label htmlFor="nickname">昵称</Label>
|
||||||
<Input id="nickname" defaultValue={user?.nickname ?? ""} />
|
<Input
|
||||||
|
id="nickname"
|
||||||
|
value={nickname}
|
||||||
|
onChange={(event) => setNickname(event.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="bio">个人简介</Label>
|
<Label htmlFor="bio">个人简介</Label>
|
||||||
<Textarea id="bio" defaultValue={user?.bio ?? ""} rows={3} />
|
<Textarea
|
||||||
|
id="bio"
|
||||||
|
value={bio}
|
||||||
|
onChange={(event) => setBio(event.target.value)}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="phone">手机号</Label>
|
<Label htmlFor="phone">手机号</Label>
|
||||||
<Input id="phone" defaultValue={user?.phone ?? ""} disabled />
|
<Input id="phone" defaultValue={user?.phone ?? ""} disabled />
|
||||||
<p className="text-xs text-muted-foreground">如需更换手机号请联系客服</p>
|
<p className="text-xs text-muted-foreground">如需更换手机号请联系客服</p>
|
||||||
</div>
|
</div>
|
||||||
<Button>保存修改</Button>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
updateProfile({
|
||||||
|
nickname: nickname.trim() || user?.nickname,
|
||||||
|
bio: bio.trim(),
|
||||||
|
avatar,
|
||||||
|
})
|
||||||
|
notifySuccess("资料已保存")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存修改
|
||||||
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface AuthState {
|
|||||||
submitVerification: (role: UserRole) => void
|
submitVerification: (role: UserRole) => void
|
||||||
approveVerification: (role: UserRole) => void
|
approveVerification: (role: UserRole) => void
|
||||||
rejectVerification: (role: UserRole, reason: string) => void
|
rejectVerification: (role: UserRole, reason: string) => void
|
||||||
|
updateProfile: (patch: { nickname?: string; bio?: string; avatar?: string }) => void
|
||||||
login: (user: User, verifiedRoles?: UserRole[]) => void
|
login: (user: User, verifiedRoles?: UserRole[]) => void
|
||||||
logout: () => void
|
logout: () => void
|
||||||
}
|
}
|
||||||
@@ -83,6 +84,19 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
updateProfile: (patch) =>
|
||||||
|
set((state) => {
|
||||||
|
if (!state.user) return state
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
...state.user,
|
||||||
|
nickname: patch.nickname ?? state.user.nickname,
|
||||||
|
bio: patch.bio ?? state.user.bio,
|
||||||
|
avatar: patch.avatar ?? state.user.avatar,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
login: (user, verifiedRoles = ["consumer"]) =>
|
login: (user, verifiedRoles = ["consumer"]) =>
|
||||||
set({
|
set({
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user