From b2076f4b1216fe5fc6f09f59b1f6e8ca33892204 Mon Sep 17 00:00:00 2001 From: Elifnurdeniz Date: Sun, 15 Dec 2024 17:12:33 +0300 Subject: [PATCH 1/3] feat(frontend): added image uploading part to edit profile --- frontend/src/pages/profile-update.tsx | 70 +++++++++++++++++++++------ 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/frontend/src/pages/profile-update.tsx b/frontend/src/pages/profile-update.tsx index d71be0ab..0d161f6a 100644 --- a/frontend/src/pages/profile-update.tsx +++ b/frontend/src/pages/profile-update.tsx @@ -17,6 +17,9 @@ export default function EditProfile() { const token = getToken("access"); const tags = ["A1", "A2", "B1", "B2", "C1", "C2"]; const navigate = useNavigate(); + const [file, setFile] = useState(null); + const [preview, setPreview] = useState(null); + const [formData, setFormData] = useState({ username: "", @@ -25,7 +28,15 @@ export default function EditProfile() { avatar: "", }); - // Fetch Profile Data (unchanged from your logic) + const handleFileChange = (e: React.ChangeEvent) => { + const selectedFile = e.target.files?.[0]; + if (selectedFile && selectedFile.type.startsWith("image/")) { + setFile(selectedFile); + setPreview(URL.createObjectURL(selectedFile)); + } + }; + + // Fetch Profile Data useEffect(() => { axios .get(`${BASE_URL}/profile/${Cookies.get("username")}/`, { @@ -42,8 +53,10 @@ export default function EditProfile() { username: profile.username, level: profile.level, bio: profile.bio || "Hey, new learner here!", - avatar: "https://nextui.org/avatars/avatar-1.png", // Mocked default avatar + avatar: profile.image || "https://nextui.org/avatars/avatar-1.png", }); + + setPreview(profile.image || null); }) .catch((error) => { console.log(error); @@ -59,41 +72,66 @@ export default function EditProfile() { const handleLevelChange = (level: string) => { setFormData({ ...formData, level }); }; + + const handleSubmit = () => { + const formDataToSend = new FormData(); + + // Add basic profile data + formDataToSend.append("username", formData.username); + formDataToSend.append("level", formData.level); + formDataToSend.append("bio", formData.bio); + + // Add avatar file if it exists + if (file) { + formDataToSend.append("profile_picture", file); + } + axios - .post(`${BASE_URL}/profile/update/`, formData, { + .post(`${BASE_URL}/profile/update/`, formDataToSend, { headers: { Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + "Content-Type": "multipart/form-data", }, }) - .then(() => { - console.log("Profile updated successfully"); + .then((response) => { + console.log("Profile updated successfully", response.data); navigate(`/profile/${Cookies.get("username")}`); - //setProfile(formData); // Update profile with new values }) .catch((error) => { console.log(error); }); }; + return (
{/* Avatar */}
- - + {/* Avatar Display */} + + + {/* Change Avatar Button */} +
+
{/* Bio Field */}
From 4937f735d288333faa75465eacab954ed4275b19 Mon Sep 17 00:00:00 2001 From: Elifnurdeniz Date: Sun, 15 Dec 2024 17:13:04 +0300 Subject: [PATCH 2/3] feat(frontend): connected profile image with backend --- frontend/src/components/common/utils.tsx | 2 +- frontend/src/pages/profile.tsx | 4 ++-- frontend/src/types.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/common/utils.tsx b/frontend/src/components/common/utils.tsx index 32fb1f31..efddabd4 100644 --- a/frontend/src/components/common/utils.tsx +++ b/frontend/src/components/common/utils.tsx @@ -135,7 +135,7 @@ export const convertProfileResponseToProfile = (profileResponse: ProfileResponse bio: profileResponse.bio, followers: profileResponse.follower_count, following: profileResponse.following_count, - image: profileResponse.image || '', + image: profileResponse.profile_picture || 'https://nextui.org/avatars/avatar-1.png', posts: profileResponse.posts.map(convertPostResponseToPost), quizzes: profileResponse.quizzes || [], is_followed: profileResponse.is_followed, diff --git a/frontend/src/pages/profile.tsx b/frontend/src/pages/profile.tsx index ae8e0bcf..a672d0d6 100644 --- a/frontend/src/pages/profile.tsx +++ b/frontend/src/pages/profile.tsx @@ -87,7 +87,7 @@ export default function Profile() { }) .then((response) => { const data: ProfileResponse = response.data; - console.log(data); + console.log("profile",data); const profile = convertProfileResponseToProfile(data); const sortedVersion = [...profile.posts].sort((a, b) => { return ( @@ -311,7 +311,7 @@ export default function Profile() {
diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 6fad0802..6df6c0fc 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -157,6 +157,7 @@ export type ProfileResponse = { follower_count: number; following_count: number; is_followed: boolean; + profile_picture: string; level: string; bio: string; name: string; From f6f9071ba49456626070de6d90e5a24c28a756f7 Mon Sep 17 00:00:00 2001 From: Elifnurdeniz Date: Sun, 15 Dec 2024 17:32:01 +0300 Subject: [PATCH 3/3] feat(frontend): connected profile image with backend everywhere necessary --- frontend/src/components/common/navbar.tsx | 31 +++++++++++++++++-- frontend/src/components/common/user-card.tsx | 2 +- frontend/src/components/post/post-card.tsx | 30 ++++++++++++++++-- .../src/components/quiz/question-card.tsx | 1 - frontend/src/components/quiz/quiz-card.tsx | 30 ++++++++++++++++-- 5 files changed, 85 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/common/navbar.tsx b/frontend/src/components/common/navbar.tsx index d9da7ba6..9a0ae7b7 100644 --- a/frontend/src/components/common/navbar.tsx +++ b/frontend/src/components/common/navbar.tsx @@ -23,9 +23,10 @@ import { useState } from "react"; import { useEffect } from "react"; import axios from "axios"; import { BASE_URL } from "../../lib/baseURL"; -import { formatTimeAgo } from "./utils"; +import { convertProfileResponseToProfile, formatTimeAgo } from "./utils"; import NotificationCard from "../notification/notification-card"; import GuestAuthModal from "../auth/guest-auth-modal"; +import { Profile, ProfileResponse } from "../../types"; export default function Navbar() { const navigate = useNavigate(); @@ -33,13 +34,14 @@ export default function Navbar() { const username = Cookies.get("username"); const { logout, removeTokens, getToken } = AuthActions(); - + const [profileImage, setProfileImage] = useState(null); const token = getToken("access"); const isGuest = !token; const [notifications, setNotifications] = useState([]); const [isNotificationsViewed, setIsNotificationsViewed] = useState(false); const [search, setSearch] = useState(""); const [guestModalOpen, setGuestModalOpen] = useState(false); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { if (!username) { @@ -47,6 +49,29 @@ export default function Navbar() { } }, [username]); + useEffect(() => { + if (username) { + setIsLoading(true); + axios + .get(`${BASE_URL}/profile/${username}/`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((response) => { + const data: ProfileResponse = response.data; + console.log("profile",data); + setProfileImage(response.data.profile_picture); + }) + .catch((error) => { + console.log(error); + }) + .finally(() => { + setIsLoading(false); + }); + } + }, [username, token]); + useEffect(() => { axios .get(`${BASE_URL}/user-activities-as-object/`, { @@ -260,7 +285,7 @@ export default function Navbar() { as="button" isBordered color="success" - src="https://nextui.org/avatars/avatar-1.png" + src={profileImage || "https://nextui.org/avatars/avatar-1.png"} className="ml-4" /> diff --git a/frontend/src/components/common/user-card.tsx b/frontend/src/components/common/user-card.tsx index 60c3e614..4f862d7e 100644 --- a/frontend/src/components/common/user-card.tsx +++ b/frontend/src/components/common/user-card.tsx @@ -115,7 +115,7 @@ export const UserCard = ({ isBordered radius="full" size="md" - src="https://nextui.org/avatars/avatar-1.png" + src={profile?.image || profile_image || "https://nextui.org/avatars/avatar-1.png"} />

diff --git a/frontend/src/components/post/post-card.tsx b/frontend/src/components/post/post-card.tsx index 6d7942a7..9beb55a5 100644 --- a/frontend/src/components/post/post-card.tsx +++ b/frontend/src/components/post/post-card.tsx @@ -1,5 +1,5 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Card, CardHeader, @@ -29,6 +29,7 @@ import { BASE_URL } from "../../lib/baseURL"; import { UserCard } from "../common/user-card"; import GuestAuthModal from "../auth/guest-auth-modal"; import ClickableText from "../common/clickable-text"; +import { ProfileResponse } from "../../types"; const maxLength = 250; // Maximum length of the content to be displayed @@ -64,11 +65,36 @@ export default function PostCard({ const token = getToken("access"); const isGuest = !token; const [guestModalOpen, setGuestModalOpen] = useState(false); + const [profileImage, setProfileImage] = useState(null); + const [isLoading, setIsLoading] = useState(true); const toggleExpand = () => { setIsExpanded(!isExpanded); }; + useEffect(() => { + if (username) { + setIsLoading(true); + axios + .get(`${BASE_URL}/profile/${username}/`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((response) => { + const data: ProfileResponse = response.data; + console.log("profile",data); + setProfileImage(response.data.profile_picture); + }) + .catch((error) => { + console.log(error); + }) + .finally(() => { + setIsLoading(false); + }); + } + }, [username, token]); + const toggleLike = () => { if (title) { axios @@ -158,7 +184,7 @@ export default function PostCard({ isBordered radius="full" className="w-6 h-6 text-tiny" - src="https://nextui.org/avatars/avatar-1.png" + src={profileImage || "https://nextui.org/avatars/avatar-1.png"} />
diff --git a/frontend/src/components/quiz/question-card.tsx b/frontend/src/components/quiz/question-card.tsx index ae9cdc73..11b5915a 100644 --- a/frontend/src/components/quiz/question-card.tsx +++ b/frontend/src/components/quiz/question-card.tsx @@ -6,7 +6,6 @@ import { Divider, Button, } from "@nextui-org/react"; -import { option } from "framer-motion/client"; import { BASE_URL } from "../../lib/baseURL.ts"; import axios from "axios"; import { AuthActions } from "../../components/auth/utils.tsx"; diff --git a/frontend/src/components/quiz/quiz-card.tsx b/frontend/src/components/quiz/quiz-card.tsx index d2597a88..1896738e 100644 --- a/frontend/src/components/quiz/quiz-card.tsx +++ b/frontend/src/components/quiz/quiz-card.tsx @@ -1,5 +1,5 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Card, CardHeader, @@ -25,6 +25,7 @@ import axios from "axios"; import { BASE_URL } from "../../lib/baseURL"; import { UserCard } from "../common/user-card"; import GuestAuthModal from "../auth/guest-auth-modal"; +import { ProfileResponse } from "../../types"; const maxLength = 250; // Maximum length of the content to be displayed @@ -64,11 +65,36 @@ export default function QuizCard({ const token = getToken("access"); const isGuest = !token; const [guestModalOpen, setGuestModalOpen] = useState(false); + const [profileImage, setProfileImage] = useState(null); + const [isLoading, setIsLoading] = useState(true); const toggleExpand = () => { setIsExpanded(!isExpanded); }; + useEffect(() => { + if (username) { + setIsLoading(true); + axios + .get(`${BASE_URL}/profile/${username}/`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((response) => { + const data: ProfileResponse = response.data; + console.log("profile",data); + setProfileImage(response.data.profile_picture); + }) + .catch((error) => { + console.log(error); + }) + .finally(() => { + setIsLoading(false); + }); + } + }, [username, token]); + const toggleLike = () => { axios .post( @@ -129,7 +155,7 @@ export default function QuizCard({ isBordered radius="full" className="w-6 h-6 text-tiny" - src="https://nextui.org/avatars/avatar-1.png" + src={profileImage || "https://nextui.org/avatars/avatar-1.png"} />