Skip to content

Commit

Permalink
Merge pull request #869 from bounswe/FRONTEND-855
Browse files Browse the repository at this point in the history
Frontend 855: implemented all profile image related features
  • Loading branch information
Elifnurdeniz authored Dec 15, 2024
2 parents 3778858 + f6f9071 commit 6424819
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 28 deletions.
31 changes: 28 additions & 3 deletions frontend/src/components/common/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,55 @@ 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();
const { pathname } = useLocation();
const username = Cookies.get("username");

const { logout, removeTokens, getToken } = AuthActions();

const [profileImage, setProfileImage] = useState<string | null>(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) {
setIsNotificationsViewed(true);
}
}, [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/`, {
Expand Down Expand Up @@ -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"
/>
</PopoverTrigger>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/user-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
/>
<div className="flex flex-col items-start justify-center">
<h4 className="text-small font-semibold leading-none text-default-600">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
30 changes: 28 additions & 2 deletions frontend/src/components/post/post-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { useState } from "react";
import { useEffect, useState } from "react";
import {
Card,
CardHeader,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -64,11 +65,36 @@ export default function PostCard({
const token = getToken("access");
const isGuest = !token;
const [guestModalOpen, setGuestModalOpen] = useState(false);
const [profileImage, setProfileImage] = useState<string | null>(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
Expand Down Expand Up @@ -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"}
/>
<div className="flex flex-col gap-1 items-start justify-center">
<h5 className="text-small tracking-tight text-default-400">
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/quiz/question-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
30 changes: 28 additions & 2 deletions frontend/src/components/quiz/quiz-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { useState } from "react";
import { useEffect, useState } from "react";
import {
Card,
CardHeader,
Expand All @@ -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

Expand Down Expand Up @@ -64,11 +65,36 @@ export default function QuizCard({
const token = getToken("access");
const isGuest = !token;
const [guestModalOpen, setGuestModalOpen] = useState(false);
const [profileImage, setProfileImage] = useState<string | null>(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(
Expand Down Expand Up @@ -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"}
/>
<div className="flex flex-col gap-1 items-start justify-center">
<h5 className="text-small tracking-tight text-default-400">
Expand Down
70 changes: 54 additions & 16 deletions frontend/src/pages/profile-update.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);


const [formData, setFormData] = useState({
username: "",
Expand All @@ -25,7 +28,15 @@ export default function EditProfile() {
avatar: "",
});

// Fetch Profile Data (unchanged from your logic)
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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")}/`, {
Expand All @@ -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);
Expand All @@ -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 (
<div className="h-full w-full items-center overflow-hidden flex flex-col">
<Navbar />
<div className="flex flex-col justify-center gap-6 items-center w-full px-32 py-5">
{/* Avatar */}
<div className="flex flex-col items-center">
<Avatar src={formData.avatar} className="w-32 h-32 mb-4" />
<Button
variant="faded"
color="primary"
className="font-bold"
//onClick={() => alert("Avatar change logic goes here!")} // Mocked logic
>
Change Avatar
</Button>
{/* Avatar Display */}
<Avatar
src={preview || formData.avatar} // Show preview if a file is selected, else show the current avatar
className="w-32 h-32 mb-4"
/>

{/* Change Avatar Button */}
<label className="relative cursor-pointer">
<div className={`bg-default-100 hover:bg-default-200 flex flex-col justify-center items-center rounded-3xl w-[120px] h-[40px] overflow-hidden`}>
<span className="text-primary text-sm font-bold">Change Avatar</span>
</div>
{/* File Input */}
<input
type="file"
className="hidden"
onChange={handleFileChange} // Handle file selection
accept="image/*"
/>
</label>
</div>


<form className="flex flex-col gap-6 w-full max-w-lg">
{/* Bio Field */}
<div className="flex flex-col items-start">
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -311,7 +311,7 @@ export default function Profile() {
<div className="flex justify-center gap-6 items-center w-full px-32 py-3">
<div className="flex items-center px-2 rounded-lg">
<Avatar
src="https://nextui.org/avatars/avatar-1.png"
src={profile.image}
className="mr-2 w-24 h-24"
/>
<div className="mx-4 max-w-52">
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 6424819

Please sign in to comment.