refactor: remove data-table component and related functionality

refactor: update sidebar item interface to use IconType

refactor: modify nav-main and nav-ucenter components to use IconType

refactor: delete unused nav-secondary component

refactor: clean up user profile page logic and improve avatar handling

refactor: remove section-cards component

refactor: add icon type definition for better type safety
This commit is contained in:
2025-09-20 14:36:29 +08:00
parent 709aa82337
commit 640b3299ac
17 changed files with 308 additions and 1327 deletions

View File

@ -14,6 +14,7 @@ import { getFallbackAvatarFromUsername } from "@/utils/common/username";
import { useEffect, useState } from "react";
import { toast } from "sonner";
interface UploadConstraints {
allowedTypes: string[];
maxSize: number;
@ -25,24 +26,28 @@ interface PictureInputChangeEvent {
export function UserProfilePage() {
const { user } = useAuth();
if (!user) return null
const [nickname, setNickname] = useState(user.nickname || '')
const [username, setUsername] = useState(user.username || '')
const [avatarUrl, setAvatarUrl] = useState(user.avatarUrl || '')
const [nickname, setNickname] = useState(user?.nickname || '')
const [username, setUsername] = useState(user?.username || '')
const [avatarFile, setAvatarFile] = useState<File | null>(null)
const [gender, setGender] = useState(user.gender || '')
const [avatarFileUrl, setAvatarFileUrl] = useState<string | null>(null) // 这部分交由useEffect控制监听 avatarFile 变化
const [gender, setGender] = useState(user?.gender || '')
useEffect(() => {
// if (!avatarFile) return
// uploadFile({ file: avatarFile! }).then(res => {
// setAvatarUrl(getFileUri(res.data.id))
// toast.success('Avatar uploaded successfully')
// }).catch(err => {
// console.log(err)
// toast.error(`Error: ${err?.response?.data?.message || err.message || 'Failed to upload avatar'}`)
// })
}, [avatarFile])
if (!user) return;
if (!avatarFile) {
setAvatarFileUrl(getGravatarFromUser({ user }));
return;
}
const url = URL.createObjectURL(avatarFile);
setAvatarFileUrl(url);
return () => {
URL.revokeObjectURL(url);
setAvatarFileUrl(getGravatarFromUser({ user }));
};
}, [avatarFile, user]);
const handlePictureSelected = (e: PictureInputChangeEvent): void => {
const file: File | null = e.target.files?.[0] ?? null;
@ -68,27 +73,63 @@ export function UserProfilePage() {
}
const handleSubmit = () => {
if (nickname.trim() === '' || username.trim() === '') {
if (!user) return;
if (
nickname.trim() === '' ||
username.trim() === ''
) {
toast.error('Nickname and Username cannot be empty')
return
}
if ((username.length < 3 || username.length > 20) || (nickname.length < 1 || nickname.length > 20)) {
if (
(username.length < 3 || username.length > 20) ||
(nickname.length < 1 || nickname.length > 20)
) {
toast.error('Nickname and Username must be between 3 and 20 characters')
return
}
if (username === user.username && nickname === user.nickname && avatarUrl === user.avatarUrl && gender === user.gender) {
if (
username === user.username &&
nickname === user.nickname &&
gender === user.gender &&
avatarFile === null
) {
toast.warning('No changes made')
return
}
updateUser({ nickname, username, avatarUrl, gender, id: user.id }).then(res => {
toast.success('Profile updated successfully')
window.location.reload()
}).catch(err => {
console.log(err)
toast.error(`Error: ${err?.response.data?.message || err.message || 'Failed to update profile'}`)
})
let avatarUrl = user.avatarUrl;
(async () => {
if (avatarFile) {
try {
const resp = await uploadFile({ file: avatarFile });
avatarUrl = getFileUri(resp.data.id);
console.log('Uploaded avatar, got URL:', avatarUrl);
} catch (error: unknown) {
toast.error(`Failed to upload avatar ${error}`);
return;
}
}
try {
await updateUser({ nickname, username, avatarUrl, gender, id: user.id });
toast.success('Profile updated successfully');
window.location.reload();
} catch (error: unknown) {
toast.error(`Failed to update profile ${error}`);
}
})();
}
const handleCropped = (blob: Blob) => {
const file = new File([blob], 'avatar.png', { type: blob.type });
setAvatarFile(file);
}
if (!user) return null
return (
<div>
<h1 className="text-2xl font-bold">
@ -98,26 +139,19 @@ export function UserProfilePage() {
<div className="grid w-full max-w-sm items-center gap-3">
<Label htmlFor="picture">Picture</Label>
<Avatar className="h-40 w-40 rounded-xl border-2">
{!avatarFile && <AvatarImage src={getGravatarFromUser({ user })} alt={user.username} />}
{avatarFile && <AvatarImage src={URL.createObjectURL(avatarFile)} alt={user.username} />}
{avatarFileUrl ?
<AvatarImage src={avatarFileUrl} alt={nickname || username} /> :
<AvatarImage src={getGravatarFromUser({ user })} alt={nickname || username} />}
<AvatarFallback>{getFallbackAvatarFromUsername(nickname || username)}</AvatarFallback>
</Avatar>
<div className="flex gap-3"><Input
id="picture"
type="file"
accept="image/png,image/jpeg,image/webp,image/gif,image/*"
onChange={handlePictureSelected}
/>
<ImageCropper />
<ImageCropper image={avatarFile} onCropped={handleCropped} />
</div>
<Input
id="picture-url"
type="url"
value={avatarUrl}
onChange={(e) => setAvatarUrl(e.target.value)}
placeholder="若要用外链图像,请直接填写,不支持裁剪"
/>
<Label htmlFor="nickname">Nickname</Label>
<Input type="nickname" id="nickname" value={nickname} onChange={(e) => setNickname(e.target.value)} />
<Label htmlFor="username">Username</Label>
@ -130,6 +164,6 @@ export function UserProfilePage() {
)
}
export function PictureEditor({}){
export function PictureEditor({ }) {
}