From ca3336b0cc6f17e1fc4256d885e8441a38eabfdc Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Sat, 6 Sep 2025 12:19:21 +0300 Subject: [PATCH] Fix profile --- backend/routes/profile.py | 56 +++++++++++++++++++ frontend/src/__userPanel/profile/api.ts | 21 ++++++- frontend/src/ui/api/profileApi.ts | 21 ++++++- .../ui/components/profile/ProfileDialog.tsx | 5 +- 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/backend/routes/profile.py b/backend/routes/profile.py index c1fa26f..421ee09 100644 --- a/backend/routes/profile.py +++ b/backend/routes/profile.py @@ -8,9 +8,15 @@ import io from dependencies import get_db, get_current_user from models import User, UpdateBioRequest, UserProfileResponse +from pydantic import BaseModel router = APIRouter() +# Request models +class UpdateProfileRequest(BaseModel): + nickname: str | None = None + description: str | None = None + # Create uploads directory if it doesn't exist PROFILE_PICTURES_DIR = Path("data/uploads/pfp") @@ -98,6 +104,56 @@ async def get_user_profile( "created_at": current_user.created_at } +@router.put("/user/profile") +async def update_user_profile( + request: UpdateProfileRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + Update current user's profile information + """ + updated = False + + # Update username if provided + if request.nickname is not None: + nickname = request.nickname.strip() + if len(nickname) < 3: + raise HTTPException(status_code=400, detail="Username must be at least 3 characters long") + if len(nickname) > 50: + raise HTTPException(status_code=400, detail="Username must be 50 characters or less") + + # Check if username is already taken by another user + existing_user = db.query(User).filter(User.username == nickname, User.id != current_user.id).first() + if existing_user: + raise HTTPException(status_code=400, detail="Username already taken") + + current_user.username = nickname + updated = True + + # Update bio if provided + if request.description is not None: + bio = request.description.strip() + if len(bio) > 500: + raise HTTPException(status_code=400, detail="Bio must be 500 characters or less") + + current_user.bio = bio + updated = True + + if updated: + db.commit() + return { + "message": "Profile updated successfully", + "username": current_user.username, + "bio": current_user.bio + } + else: + return { + "message": "No changes made", + "username": current_user.username, + "bio": current_user.bio + } + @router.put("/user/bio") async def update_user_bio( diff --git a/frontend/src/__userPanel/profile/api.ts b/frontend/src/__userPanel/profile/api.ts index e3b7cad..826c583 100644 --- a/frontend/src/__userPanel/profile/api.ts +++ b/frontend/src/__userPanel/profile/api.ts @@ -25,7 +25,13 @@ export async function loadProfile(): Promise { }); if (response.ok) { - return await response.json(); + const data = await response.json(); + // Map backend fields to frontend fields + return { + profile_picture: data.profile_picture, + nickname: data.username, + description: data.bio + }; } return null; @@ -83,10 +89,19 @@ export async function uploadProfilePicture(file: Blob): Promise): Promise { try { + // Map frontend fields to backend fields + const backendData = { + nickname: data.nickname, + description: data.description + }; + const response = await fetch('/api/user/profile', { method: 'PUT', - headers: getAuthHeaders(), - body: JSON.stringify(data) + headers: { + ...getAuthHeaders(), + 'Content-Type': 'application/json' + }, + body: JSON.stringify(backendData) }); return response.ok; diff --git a/frontend/src/ui/api/profileApi.ts b/frontend/src/ui/api/profileApi.ts index 32a4b61..1939e93 100644 --- a/frontend/src/ui/api/profileApi.ts +++ b/frontend/src/ui/api/profileApi.ts @@ -22,7 +22,13 @@ export async function loadProfile(token: string): Promise { }); if (response.ok) { - return await response.json(); + const data = await response.json(); + // Map backend fields to frontend fields + return { + profile_picture: data.profile_picture, + nickname: data.username, + description: data.bio + }; } return null; @@ -61,10 +67,19 @@ export async function uploadProfilePicture(token: string, file: Blob): Promise): Promise { try { + // Map frontend fields to backend fields + const backendData = { + nickname: data.nickname, + description: data.description + }; + const response = await fetch(`${API_BASE_URL}/user/profile`, { method: 'PUT', - headers: getAuthHeaders(token), - body: JSON.stringify(data) + headers: { + ...getAuthHeaders(token), + 'Content-Type': 'application/json' + }, + body: JSON.stringify(backendData) }); return response.ok; diff --git a/frontend/src/ui/components/profile/ProfileDialog.tsx b/frontend/src/ui/components/profile/ProfileDialog.tsx index fbfdee4..1479460 100644 --- a/frontend/src/ui/components/profile/ProfileDialog.tsx +++ b/frontend/src/ui/components/profile/ProfileDialog.tsx @@ -9,8 +9,9 @@ import { MaterialTextField } from "../core/TextField"; export function ProfileDialog({ isOpen, onOpenChange }: DialogProps) { const { profileData, isLoading, isUpdating, updateProfileData, uploadProfilePictureData } = useProfile(); - const [username, setUsername] = useState(""); - const [description, setDescription] = useState(""); + + const [username, setUsername] = useState(profileData?.nickname ?? ""); + const [description, setDescription] = useState(profileData?.description ?? ""); const [selectedImage, setSelectedImage] = useState(null); const [showCropper, setShowCropper] = useState(false);