fix(settings): enable profile save and avatar selection

This commit is contained in:
zetaloop
2026-02-22 08:30:40 +08:00
parent 4beb610f23
commit 2ade1780c1
2 changed files with 64 additions and 5 deletions
+50 -5
View File
@@ -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>
+14
View File
@@ -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,