From f179d81ae48527e00598f3f84b690e898dca8b25 Mon Sep 17 00:00:00 2001 From: Daniel Agbeni <99095841+DanielAgbeni@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:42:16 +0100 Subject: [PATCH 1/2] fix the profile and logo at the bottom --- client/src/components/Sidebar/HomeSidebar.tsx | 217 +++++++++--------- client/src/components/ui/skeleton.tsx | 15 ++ 2 files changed, 124 insertions(+), 108 deletions(-) create mode 100644 client/src/components/ui/skeleton.tsx diff --git a/client/src/components/Sidebar/HomeSidebar.tsx b/client/src/components/Sidebar/HomeSidebar.tsx index 672ba85..2db8e20 100644 --- a/client/src/components/Sidebar/HomeSidebar.tsx +++ b/client/src/components/Sidebar/HomeSidebar.tsx @@ -1,129 +1,130 @@ -import React, { useState } from "react"; +import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Sidebar, SidebarBody, SidebarLink } from "@/components/ui/sidebar"; -import { - IconArrowLeft, - IconBrandTabler -} from "@tabler/icons-react"; +import { Sidebar, SidebarBody, SidebarLink } from '@/components/ui/sidebar'; +import { IconArrowLeft, IconBrandTabler } from '@tabler/icons-react'; import { Link } from 'react-router-dom'; -import { motion } from "framer-motion"; +import { motion } from 'framer-motion'; interface HomeProps { - onLogout: () => void; - username: string; + onLogout: () => void; + username: string; } const HomeSidebar: React.FC = ({ onLogout, username }) => { - const navigate = useNavigate(); + const navigate = useNavigate(); - const handleLogout = async () => { - try { - await onLogout(); - navigate('/'); - } catch (error) { - console.error('Logout failed:', error); - } - }; + const handleLogout = async () => { + try { + await onLogout(); + navigate('/'); + } catch (error) { + console.error('Logout failed:', error); + } + }; - const goToProfile = () => { - navigate(`/u/${username}`); - }; + const goToProfile = () => { + navigate(`/u/${username}`); + }; - const goToHome = () => { - navigate('/home'); - }; + const goToHome = () => { + navigate('/home'); + }; - const links = [ - { - label: "Dashboard", - href: "#", - icon: ( - - ), - onClick: goToHome, - } - ]; + const links = [ + { + label: 'Dashboard', + href: '#', + icon: ( + + ), + onClick: goToHome, + }, + ]; - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(false); - return ( - - - -
- {open ? : } -
- {links.map((link, idx) => ( - - ))} -
-
-
- - ), - onClick: goToProfile - }} - - /> - - ), - onClick: handleLogout, - }} - /> - -
-
-
- - ); + return ( + + +
+ {open ? : } +
+ {links.map((link, idx) => ( + + ))} +
+
+
+ + ), + onClick: goToProfile, + }} + /> + + ), + onClick: handleLogout, + }} + /> +
+
+
+ ); }; export const Logo = () => { - return ( - - {/*
*/} -

dh

- - Devhub - - - ); + return ( + + {/*
*/} +

+ dh +

+ + Devhub + + + ); }; export const LogoIcon = () => { - return ( - -

dh

- - ); + return ( + +

+ dh +

+ + ); }; -export default HomeSidebar; \ No newline at end of file +export default HomeSidebar; diff --git a/client/src/components/ui/skeleton.tsx b/client/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..01b8b6d --- /dev/null +++ b/client/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } From 6071b5d00b12d7b9be8c33cd5acd7de37dc6f57e Mon Sep 17 00:00:00 2001 From: Daniel Agbeni <99095841+DanielAgbeni@users.noreply.github.com> Date: Wed, 16 Oct 2024 18:58:55 +0100 Subject: [PATCH 2/2] Updated Profile Page and EditProfile Form --- client/src/pages/EditProfileForm.tsx | 842 +++++++++++--------- client/src/pages/Profile.tsx | 1061 +++++++++++++------------- 2 files changed, 1014 insertions(+), 889 deletions(-) diff --git a/client/src/pages/EditProfileForm.tsx b/client/src/pages/EditProfileForm.tsx index 6490875..33a1838 100644 --- a/client/src/pages/EditProfileForm.tsx +++ b/client/src/pages/EditProfileForm.tsx @@ -1,399 +1,517 @@ import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { useParams } from 'react-router-dom'; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import TagInput from "@/components/MultiSelect/TagInput"; -import { toast } from "sonner"; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import TagInput from '@/components/MultiSelect/TagInput'; +import { toast } from 'sonner'; const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000'; interface Tag { - value: string; - label: string; + value: string; + label: string; } interface Project { - id?: number; - title: string; - description: string; - repoLink: string; - tags: Tag[]; + id?: number; + title: string; + description: string; + repoLink: string; + tags: Tag[]; } - interface ProfileData { - name: string; - bio: string; - githubUsername: string; - leetcodeUsername: string; - projects: Project[]; + name: string; + bio: string; + githubUsername: string; + leetcodeUsername: string; + projects: Project[]; } interface EditProfileFormProps { - onProjectAdded: (newProject: Project) => void; + onProjectAdded: (newProject: Project) => void; } -const EditProfileForm: React.FC = ({ onProjectAdded }) => { - const { username } = useParams<{ username: string }>(); - const [profileData, setProfileData] = useState({ - name: '', - bio: '', - githubUsername: '', - leetcodeUsername: '', - projects: [], - }); - const [isLoading, setIsLoading] = useState(false); - const [selectedProject, setSelectedProject] = useState(null); - const [newProjectMode, setNewProjectMode] = useState(false); - const [newProject, setNewProject] = useState({ - title: '', - description: '', - repoLink: '', - tags: [], - }); - +const EditProfileForm: React.FC = ({ + onProjectAdded, +}) => { + const { username } = useParams<{ username: string }>(); + const [profileData, setProfileData] = useState({ + name: '', + bio: '', + githubUsername: '', + leetcodeUsername: '', + projects: [], + }); + + const [isLoading, setIsLoading] = useState(false); + const [selectedProject, setSelectedProject] = useState(null); + const [newProjectMode, setNewProjectMode] = useState(false); + const [newProject, setNewProject] = useState({ + title: '', + description: '', + repoLink: '', + tags: [], + }); + + useEffect(() => { + const fetchProfile = async () => { + try { + const response = await axios.get(`${backendUrl}/profile/${username}`); + setProfileData(response.data); + } catch (error) { + console.error('Failed to fetch profile data:', error); + } + }; + + if (username) { + fetchProfile(); + } + }, [username]); - useEffect(() => { - const fetchProfile = async () => { - try { - const response = await axios.get(`${backendUrl}/profile/${username}`); - setProfileData(response.data); - } catch (error) { - console.error('Failed to fetch profile data:', error); - } - }; + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setProfileData((prevData: ProfileData) => ({ + ...prevData, + [name]: value, + })); + }; - if (username) { - fetchProfile(); - } - }, [username]); + const handleProjectChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + if (selectedProject) { + setSelectedProject((prevProject: Project | null) => { + if (prevProject) { + return { + ...prevProject, + [name]: value, + }; + } + return null; // or return a default Project object if you prefer + }); + } else { + setNewProject((prevProject: Project) => ({ + ...prevProject, + [name]: value, + })); + } + }; - const handleChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setProfileData((prevData: ProfileData) => ({ - ...prevData, - [name]: value, - })); - }; + const handleSubmit = async (e: React.SyntheticEvent) => { + e.preventDefault(); + setIsLoading(true); - const handleProjectChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - if (selectedProject) { - setSelectedProject((prevProject: Project | null) => { - if (prevProject) { - return { - ...prevProject, - [name]: value, - }; - } - return null; // or return a default Project object if you prefer - }); - } else { - setNewProject((prevProject: Project) => ({ - ...prevProject, - [name]: value, - })); - } - }; + try { + await axios.put(`${backendUrl}/profile/${username}`, profileData, { + withCredentials: true, + }); + toast.success('Profile updated successfully'); + // alert('Profile updated successfully'); + } catch (error) { + console.error('Failed to update profile:', error); + toast.error('Failed to update profile', { + description: 'Please check your details and try again.', + }); + // alert('Failed to update profile'); + } finally { + setIsLoading(false); + } + }; - const handleSubmit = async (e: React.SyntheticEvent) => { - e.preventDefault(); - setIsLoading(true); + const handleAddProject = async () => { + try { + console.log('Tags:', newProject.tags); + const tagsArray = newProject.tags.map((tag) => ({ + value: tag, + label: tag, + })); // Convert Tag objects to strings - try { - await axios.put(`${backendUrl}/profile/${username}`, profileData, { withCredentials: true }); - toast.success("Profile updated successfully"); - // alert('Profile updated successfully'); - } catch (error) { - console.error('Failed to update profile:', error); - toast.error("Failed to update profile", { - description: "Please check your details and try again.", - }); - // alert('Failed to update profile'); - } finally { - setIsLoading(false); - } - }; + console.log('Adding project:', tagsArray); + const response = await axios.post( + `${backendUrl}/profile/${username}/projects`, + { + title: newProject.title, + description: newProject.description, + repo_link: newProject.repoLink, + tags: tagsArray, // Use the formatted tagsArray + }, + { withCredentials: true }, + ); + if (response.status === 200) + // console.log("Project added successfully:", ); + toast.success('Project added successfully'); + const addedProject = response.data.project; - const handleAddProject = async () => { - try { - console.log("Tags:", newProject.tags); - const tagsArray = newProject.tags.map(tag => ({ value: tag, label: tag })); // Convert Tag objects to strings + if (addedProject) { + setProfileData((prevData: ProfileData) => ({ + ...prevData, + projects: [...prevData.projects, addedProject], + })); - console.log("Adding project:", tagsArray); - const response = await axios.post(`${backendUrl}/profile/${username}/projects`, { - title: newProject.title, - description: newProject.description, - repo_link: newProject.repoLink, - tags: tagsArray, // Use the formatted tagsArray - }, { withCredentials: true }); - if(response.status === 200) - // console.log("Project added successfully:", ); - toast.success("Project added successfully"); - const addedProject = response.data.project; - - if (addedProject) { - setProfileData((prevData: ProfileData) => ({ - ...prevData, - projects: [...prevData.projects, addedProject], - })); - - onProjectAdded(addedProject); - setNewProject({ title: '', description: '', repoLink: '', tags: [] }); - setNewProjectMode(false); - } else { - console.error('Project was not added correctly:', response.data); - toast.error("Failed to add project", { - action: { - label: "Try again", - onClick: () => console.log("Try again clicked"), - }, - }); - // alert('Failed to add project. Please try again.'); - } - } catch (error) { - console.error('Failed to add project:', error); - // alert('Failed to add project. Please try again.'); - } - }; - + onProjectAdded(addedProject); + setNewProject({ title: '', description: '', repoLink: '', tags: [] }); + setNewProjectMode(false); + } else { + console.error('Project was not added correctly:', response.data); + toast.error('Failed to add project', { + action: { + label: 'Try again', + onClick: () => console.log('Try again clicked'), + }, + }); + // alert('Failed to add project. Please try again.'); + } + } catch (error) { + console.error('Failed to add project:', error); + // alert('Failed to add project. Please try again.'); + } + }; - const handleUpdateProject = async () => { - try { - if (selectedProject) { - const updatedProject = { ...selectedProject }; - await axios.put(`${backendUrl}/profile/${username}/projects/${selectedProject.id}`, updatedProject, { withCredentials: true }); - - setProfileData((prevData: ProfileData) => ({ - ...prevData, - projects: prevData.projects.map((p: Project) => p.id === selectedProject.id ? updatedProject : p), - })); - setSelectedProject(null); - } - } catch (error) { - console.error('Failed to update project:', error); - toast.error("Failed to update project", { - description: "There was a problem with your request.", - action: { - label: "Try again", - onClick: () => console.log("Try again clicked"), - }, - }); - // alert('Failed to update project'); - } - }; - + const handleUpdateProject = async () => { + try { + if (selectedProject) { + const updatedProject = { ...selectedProject }; + await axios.put( + `${backendUrl}/profile/${username}/projects/${selectedProject.id}`, + updatedProject, + { withCredentials: true }, + ); - const handleDeleteProject = async (projectTitle: string) => { - try { - console.log("Deleting project:", projectTitle); - const response = await axios.delete(`${backendUrl}/profile/${username}/projects/${projectTitle}`, { withCredentials: true }); - if (response.status === 200) { - setProfileData((prevData: ProfileData) => ({ - ...prevData, - projects: prevData.projects.filter((project: Project) => project.title !== projectTitle), - })); - // alert('Project deleted successfully'); - toast.success("Project deleted successfully"); - if (selectedProject && selectedProject.title === projectTitle) { - setSelectedProject(null); - } - } else { - console.log(response.status); - toast.error("Failed to delete project", { - description: "There was a problem with your request.", - action: { - label: "Try again", - onClick: () => console.log("Try again clicked"), - }, - }); - // alert('Failed to delete project'); - } - } catch (error) { - console.error('Failed to delete project:', error); - // alert('Failed to delete project'); - toast.error("Failed to delete project", { - description: "There was a problem with your request.", - action: { - label: "Try again", - onClick: () => console.log("Try again clicked"), - }, - }); - } - }; + setProfileData((prevData: ProfileData) => ({ + ...prevData, + projects: prevData.projects.map((p: Project) => + p.id === selectedProject.id ? updatedProject : p, + ), + })); + setSelectedProject(null); + } + } catch (error) { + console.error('Failed to update project:', error); + toast.error('Failed to update project', { + description: 'There was a problem with your request.', + action: { + label: 'Try again', + onClick: () => console.log('Try again clicked'), + }, + }); + // alert('Failed to update project'); + } + }; - const openNewProjectForm = () => { - setNewProjectMode(true); - setSelectedProject(null); - }; + const handleDeleteProject = async (projectTitle: string) => { + try { + console.log('Deleting project:', projectTitle); + const response = await axios.delete( + `${backendUrl}/profile/${username}/projects/${projectTitle}`, + { withCredentials: true }, + ); + if (response.status === 200) { + setProfileData((prevData: ProfileData) => ({ + ...prevData, + projects: prevData.projects.filter( + (project: Project) => project.title !== projectTitle, + ), + })); + // alert('Project deleted successfully'); + toast.success('Project deleted successfully'); + if (selectedProject && selectedProject.title === projectTitle) { + setSelectedProject(null); + } + } else { + console.log(response.status); + toast.error('Failed to delete project', { + description: 'There was a problem with your request.', + action: { + label: 'Try again', + onClick: () => console.log('Try again clicked'), + }, + }); + // alert('Failed to delete project'); + } + } catch (error) { + console.error('Failed to delete project:', error); + // alert('Failed to delete project'); + toast.error('Failed to delete project', { + description: 'There was a problem with your request.', + action: { + label: 'Try again', + onClick: () => console.log('Try again clicked'), + }, + }); + } + }; - return ( -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
- -
-
+ const openNewProjectForm = () => { + setNewProjectMode(true); + setSelectedProject(null); + }; -

Existing Projects

-
    - {profileData.projects && profileData.projects.map((project: Project, index: number) => ( - project && project.title ? ( -
  • -

    setSelectedProject(project)}>{project.title}

    -
  • - ) : null - ))} -
+ return ( +
+ {/* Profile Form */} +
+

+ Update Profile +

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
- {selectedProject && ( -
-
- - -
-
- - -
-
- - -
- - -
- )} + {/* Existing Projects */} +
+

+ Existing Projects +

+
    + {profileData.projects?.map((project, index) => + project?.title ? ( +
  • setSelectedProject(project)}> + {project.title} +
  • + ) : null, + )} +
+
- {newProjectMode && ( -
-
- - -
-
- - -
-
- - setNewProject({ ...newProject, tags })} // Ensure tags are of type Tag[] - /> + {/* Selected Project Form */} + {profileData.selectedProject && ( +
+

+ Edit Project +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ )} -
-
- - -
- -
- )} + {/* New Project Form */} + {newProjectMode && ( +
+

+ Add New Project +

+
+
+ + +
+
+ + +
+
+ + + setNewProject({ ...profileData.newProject, tags }) + } + className='mt-2 dark:bg-neutral-700 dark:text-neutral-100' + /> +
+
+ + +
+ +
+
+ )} - {!newProjectMode && ( - - )} -
- ); + {/* Add New Project Button */} + {!newProjectMode && ( + + )} +
+ ); }; -export default EditProfileForm; \ No newline at end of file +export default EditProfileForm; diff --git a/client/src/pages/Profile.tsx b/client/src/pages/Profile.tsx index 90465ac..6a16f91 100644 --- a/client/src/pages/Profile.tsx +++ b/client/src/pages/Profile.tsx @@ -1,63 +1,80 @@ -import React, { useEffect, useState } from "react"; -import { useParams } from "react-router-dom"; -import axios from "axios"; -import EditProfileForm from "./EditProfileForm"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; -import HomeSidebar from "@/components/Sidebar/HomeSidebar"; -import { Card, CardHeader, CardContent } from "@/components/ui/card"; -import { useDispatch, useSelector } from "react-redux"; -import { RootState, AppDispatch } from "../lib/store"; -import { setFriends, setFriendStatus } from "../lib/userSlice"; -import { FaExternalLinkAlt } from "react-icons/fa"; - -const backendUrl = import.meta.env.VITE_BACKEND_URL || "http://localhost:5000"; +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import axios from 'axios'; +import EditProfileForm from './EditProfileForm'; +import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; +import HomeSidebar from '@/components/Sidebar/HomeSidebar'; +import { Card, CardHeader, CardContent } from '@/components/ui/card'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState, AppDispatch } from '../lib/store'; +import { setFriends, setFriendStatus } from '../lib/userSlice'; +import { FaExternalLinkAlt, FaStar, FaCodeBranch } from 'react-icons/fa'; +import { Skeleton } from '@/components/ui/skeleton'; + +const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000'; interface ProfileProps { - onLogout: () => void; - username: string; + onLogout: () => void; + username: string; } export interface Tag { - value: string; - label: string; + value: string; + label: string; } +// interface Project { +// description: string; +// repoLink: string; +// tags: Tag[]; +// title: string; +// repo?: string; +// link?: string; +// language?: string; +// stars?: number; +// forks?: number; +// } + interface Project { - description: string; - repoLink: string; - tags: Tag[]; - title: string; - repo?: string; - link?: string; - language?: string; - stars?: number; - forks?: number; + owner: string; + repo: string; + link: string; + description: string; + image: string; + website: string[]; + language: string; + languageColor: string; + stars: number; + forks: number; + title?: string; // Add this if you still need it + tags?: Tag[]; // Add this if you still need it + repoLink?: string; // Add this if you still need it } interface UserResponse { - bio: string; - email: string; - githubUsername: string; - isFriend: boolean; - leetcodeUsername: string; - name: string | null; - projects: Project[]; - username: string; + bio: string; + email: string; + githubUsername: string; + isFriend: boolean; + leetcodeUsername: string; + name: string | null; + projects: Project[]; + username: string; } interface GitHubData { - avatar_url: string; - name: string; - public_repos: number; - followers: number; - following: number; - bio: string; + avatar_url: string; + name: string; + public_repos: number; + followers: number; + following: number; + bio: string; } interface Language { - language: string; - percentage: string; + language: string; + percentage: string; } // interface LeetCodeData { @@ -84,496 +101,486 @@ interface Language { // } const Profile: React.FC = ({ onLogout, username }) => { - return ( -
- - -
- ); + return ( +
+ + +
+ ); }; interface DashboardProps { - loggedInUsername: string; + loggedInUsername: string; } const Dashboard: React.FC = ({ loggedInUsername }) => { - const { username } = useParams<{ username: string }>(); - const dispatch = useDispatch(); - const friends = useSelector((state: RootState) => state.user.friends); - // const friendStatus = useSelector( - // (state: RootState) => state.user.friendStatus - // ); - const [profileData, setProfileData] = useState(); - const [editing, setEditing] = useState(false); - const [githubData, setGithubData] = useState(null); - const [languages, setLanguages] = useState([]); - const [streakStats, setStreakStats] = useState(null); - const [pinnedRepos, setPinnedRepos] = useState([]); - const [leetcodeSvg, setLeetcodeSvg] = useState(null); - const [githubStreakSvg, setGithubStreakSvg] = useState(null); - - useEffect(() => { - const fetchGithubData = async () => { - if (profileData?.githubUsername) { - try { - const githubResponse = await axios.post( - `${backendUrl}/analyze/github_data`, - { - "github-id": profileData.githubUsername, - } - ); - setGithubData(githubResponse.data); - - const languagesResponse = await axios.post( - `${backendUrl}/analyze/top_languages`, - { - "github-id": profileData.githubUsername, - } - ); - setLanguages(languagesResponse.data); - console.log("languagesResponse", languagesResponse); - - const streakResponse = await axios.post( - `${backendUrl}/analyze/streak_stats`, - { - "github-id": profileData.githubUsername, - } - ); - setStreakStats(streakResponse.data); - - const pinnedReposResponse = await axios.post( - `${backendUrl}/analyze/pinned_repos`, - { - "github-id": profileData.githubUsername, - } - ); - setPinnedRepos(pinnedReposResponse.data); - - const fetchGithubStreakChart = async () => { - if (profileData?.githubUsername) { - try { - const githubResponse = await axios.post( - `${backendUrl}/analyze/streak_chart`, - { - "github-id": profileData.githubUsername, - }, - { - responseType: "text", // Get the SVG as text - } - ); - setGithubStreakSvg(githubResponse.data); - } catch (error) { - console.error("Failed to fetch GitHub streak chart:", error); - } - } - }; - fetchGithubStreakChart(); - } catch (error) { - console.error("Failed to fetch GitHub data:", error); - } - } - }; - - fetchGithubData(); - }, [profileData]); - - useEffect(() => { - const fetchLeetcodeCard = async () => { - if (profileData?.leetcodeUsername) { - try { - const leetcodeResponse = await axios.post( - `${backendUrl}/analyze/leetcode_card`, - { - "leetcode-id": profileData.leetcodeUsername, - }, - { - responseType: "text", // Important to get the SVG as text - } - ); - - setLeetcodeSvg(leetcodeResponse.data); - } catch (error) { - console.error("Failed to fetch LeetCode card:", error); - } - } - }; - - fetchLeetcodeCard(); - }, [profileData]); - - useEffect(() => { - const fetchProfileAndFriends = async () => { - try { - // Fetch profile data - const profileResponse = await axios.get( - `${backendUrl}/profile/${username}`, - { - params: { logged_in_user: loggedInUsername }, - } - ); - setProfileData(profileResponse.data); - - // Fetch friends data - const friendsResponse = await axios.get( - `${backendUrl}/profile/${username}/friends` - ); - dispatch(setFriends(friendsResponse.data.friends)); - - // Update friend status in Redux - dispatch( - setFriendStatus( - friendsResponse.data.friends.includes(loggedInUsername) - ) - ); - } catch (error) { - console.error("Failed to fetch profile or friends data:", error); - } - }; - - if (username) { - fetchProfileAndFriends(); - } - }, [username, loggedInUsername, dispatch]); - - const handleProjectAdded = (newProject: Project) => { - setProfileData((prevData) => - prevData - ? { - ...prevData, - projects: [...prevData.projects, newProject], - } - : prevData - ); - }; - - const handleFriendRequest = async () => { - try { - if (profileData?.isFriend) { - await axios.delete(`${backendUrl}/profile/${username}/friends`, { - data: { friend_username: loggedInUsername }, - }); - setProfileData((prev) => (prev ? { ...prev, isFriend: false } : prev)); - dispatch(setFriendStatus({ username: loggedInUsername, isFriend: false })); // Updated to pass an object - } else { - await axios.post(`${backendUrl}/profile/${username}/friends`, { - friend_username: loggedInUsername, - }); - setProfileData((prev) => (prev ? { ...prev, isFriend: true } : prev)); - dispatch(setFriendStatus({ username: loggedInUsername, isFriend: true })); // Updated to pass an object - } - } catch (error) { - console.error("Failed to update friend status:", error); - } - }; - - if (!username) { - return
Error: No username provided
; - } - - if (!profileData) { - return
Loading...
; - } - - const isOwnProfile = loggedInUsername === username; - - return ( -
-
- {editing ? ( - - ) : ( -
-
-
- {githubData ? ( - Avatar - ) : ( - Avatar - )} - -
-

{profileData.name}

-

- @{profileData.username} -

-
-
-
-

Bio

-

- {profileData.bio} -

-
-
-

Email

-

- {profileData.email} -

-
- {/*
-

Github

-

- {profileData.githubUsername} -

-
-
-

Leetcode

-

- {profileData.leetcodeUsername} -

-
*/} - - -
-

Top Languages

- {languages.length > 0 ? ( -
- {languages.map((lang, index) => ( - - {lang.language}: {lang.percentage} - - ))} -
- ) : ( -

Loading languages...

- )} -
- -
- {streakStats ? ( -
- ) : ( -

Loading streak stats...

- )} -
- - {!isOwnProfile && ( - - )} - - {isOwnProfile && ( - - )} -
- -
-
-
-

Projects

-
-
-
- {profileData.projects.map((project: Project) => ( - - -
-
-
{project.title}
-
- @{profileData.username} -
-
-
-
- -

{project.description}

-
-
- ))} -
-
- -
-
-

Friends

- {friends.length > 0 ? ( - friends.map((friend: string) => ( - - )) - ) : ( -

- No friends yet -

- )} -
- -
- {leetcodeSvg ? ( -
- ) : ( -

Loading LeetCode data...

- )} -
- - {githubData ? ( -
-

GitHub Profile Summary

- -
-
-

{githubData.public_repos}

-

- Repositories -

-
-
-

{githubData.followers}

-

- Followers -

-
-
-

{githubData.following}

-

- Following -

-
-
-
- ) : ( -

Loading GitHub data...

- )} - {githubStreakSvg ? ( -
- ) : ( -

Loading GitHub streak chart...

- )} -
-
- )} - -
-

Pinned Repositories:

- {pinnedRepos.length > 0 ? ( -
- {pinnedRepos.map((repo, index) => ( - - - -

- {repo.title} -

-

- {repo.description} -

-
-
- -
- {repo.language} -
- - {repo.stars && ( -
- Star -
- {repo.stars} -
-
- )} -
-
- {repo.forks != null && repo.forks > 0 && ( -
- Fork -
- {repo.forks} -
-
- )} -
-
- - ))} -
- ) : ( -

No pinned repositories found.

- )} -
-
-
- ); + const { username } = useParams<{ username: string }>(); + const dispatch = useDispatch(); + const friends = useSelector((state: RootState) => state.user.friends); + // const friendStatus = useSelector( + // (state: RootState) => state.user.friendStatus + // ); + const [profileData, setProfileData] = useState(); + const [editing, setEditing] = useState(false); + const [githubData, setGithubData] = useState(null); + const [languages, setLanguages] = useState([]); + // const [streakStats, setStreakStats] = useState(null); + const [streakStat, setStreakStat] = useState(null); + const [pinnedRepos, setPinnedRepos] = useState([]); + const [leetcodeSvg, setLeetcodeSvg] = useState(null); + const [githubStreakSvg, setGithubStreakSvg] = useState(null); + const [githubContributionsSvg, setGithubContributionsSvg] = useState< + string | null + >(null); + + useEffect(() => { + const fetchGithubData = async () => { + if (profileData?.githubUsername) { + try { + const githubResponse = await axios.post( + `${backendUrl}/analyze/github_data`, + { + 'github-id': profileData.githubUsername, + }, + ); + setGithubData(githubResponse.data); + + const languagesResponse = await axios.post( + `${backendUrl}/analyze/top_languages`, + { + 'github-id': profileData.githubUsername, + }, + ); + setLanguages(languagesResponse.data); + + const pinnedReposResponse = await axios.post( + `${backendUrl}/analyze/pinned_repos`, + { + 'github-id': profileData.githubUsername, + }, + ); + setPinnedRepos(pinnedReposResponse.data); + + const streakResponse = await axios.post( + `${backendUrl}/analyze/streak_chart`, + { + 'github-id': profileData.githubUsername, + }, + { responseType: 'text' }, + ); + setGithubStreakSvg(streakResponse.data); + + const streakStats = await axios.post( + `${backendUrl}/analyze/streak_stats`, + { + 'github-id': profileData.githubUsername, + }, + ); + setStreakStat(streakStats.data); + + const contributionsResponse = await axios.post( + `${backendUrl}/analyze/contributions_chart`, + { + 'github-id': profileData.githubUsername, + }, + { responseType: 'text' }, + ); + setGithubContributionsSvg(contributionsResponse.data); + } catch (error) { + console.error('Failed to fetch GitHub data:', error); + } + } + }; + + fetchGithubData(); + }, [profileData]); + + useEffect(() => { + const fetchLeetcodeCard = async () => { + if (profileData?.leetcodeUsername) { + try { + const leetcodeResponse = await axios.post( + `${backendUrl}/analyze/leetcode_card`, + { + 'leetcode-id': profileData.leetcodeUsername, + }, + { + responseType: 'text', // Important to get the SVG as text + }, + ); + + setLeetcodeSvg(leetcodeResponse.data); + } catch (error) { + console.error('Failed to fetch LeetCode card:', error); + } + } + }; + + fetchLeetcodeCard(); + }, [profileData]); + + useEffect(() => { + const fetchProfileAndFriends = async () => { + try { + // Fetch profile data + const profileResponse = await axios.get( + `${backendUrl}/profile/${username}`, + { + params: { logged_in_user: loggedInUsername }, + }, + ); + setProfileData(profileResponse.data); + + // Fetch friends data + const friendsResponse = await axios.get( + `${backendUrl}/profile/${username}/friends`, + ); + dispatch(setFriends(friendsResponse.data.friends)); + + // Update friend status in Redux + dispatch( + setFriendStatus( + friendsResponse.data.friends.includes(loggedInUsername), + ), + ); + } catch (error) { + console.error('Failed to fetch profile or friends data:', error); + } + }; + + if (username) { + fetchProfileAndFriends(); + } + }, [username, loggedInUsername, dispatch]); + + const handleProjectAdded = (newProject: Project) => { + setProfileData((prevData) => + prevData + ? { + ...prevData, + projects: [...prevData.projects, newProject], + } + : prevData, + ); + }; + + const handleFriendRequest = async () => { + try { + if (profileData?.isFriend) { + await axios.delete(`${backendUrl}/profile/${username}/friends`, { + data: { friend_username: loggedInUsername }, + }); + setProfileData((prev) => (prev ? { ...prev, isFriend: false } : prev)); + dispatch( + setFriendStatus({ username: loggedInUsername, isFriend: false }), + ); // Updated to pass an object + } else { + await axios.post(`${backendUrl}/profile/${username}/friends`, { + friend_username: loggedInUsername, + }); + setProfileData((prev) => (prev ? { ...prev, isFriend: true } : prev)); + dispatch( + setFriendStatus({ username: loggedInUsername, isFriend: true }), + ); // Updated to pass an object + } + } catch (error) { + console.error('Failed to update friend status:', error); + } + }; + + if (!username) { + return
Error: No username provided
; + } + + const isOwnProfile = loggedInUsername === username; + return ( +
+
+ {editing ? ( + + ) : ( +
+ {/* Sidebar */} +
+
+ {githubData ? ( + Avatar + ) : ( + + )} +
+

+ {profileData ? ( + profileData.name + ) : ( + + )} +

+

+ {profileData ? ( + `@${profileData.username}` + ) : ( + + )} +

+
+
+ {/* Profile Info Section */} +
+
+

Bio

+ {profileData ? ( +

+ {profileData.bio} +

+ ) : ( + + )} +
+ +
+

Email

+ {profileData ? ( +

+ {profileData.email} +

+ ) : ( + + )} +
+
+ {/* GitHub and LeetCode Links */} +
+ {profileData ? ( + <> + + {' '} + GitHub + + + {' '} + LeetCode + + + ) : ( + <> + + + + )} +
+ +
+

Top Languages

+ {languages.length > 0 ? ( +
+ {languages.map((lang, index) => ( + + {lang.language}: {lang.percentage} + + ))} +
+ ) : ( +
+ {[1, 2, 3].map((i) => ( + + ))} +
+ )} +
+ {/* Connect Button */} + +
+ + {/* Main Content Section */} +
+ {/* Friends Section */} +
+

Friends

+ {friends.length > 0 ? ( + friends.map((friend, index) => ( + + )) + ) : ( + <>No friends + )} +
+

Projects

+
+ {profileData ? ( + profileData.projects.map((project, index) => ( + + +

{project.title}

+

+ @{profileData.username} +

+
+ +

{project.description}

+
+
+ )) + ) : ( + <> + + + + )} +
+ + {/* GitHub Stats */} +
+

GitHub Streak Stats

+ {streakStat ? ( +
+ ) : ( + + )} + +

GitHub Streak

+ {githubStreakSvg ? ( +
+ ) : ( + + )} + +

LeetCode Stats

+ {leetcodeSvg ? ( +
+ ) : ( + + )} +
+ +
+

Pinned Repositories

+ {pinnedRepos.length > 0 ? ( +
+ {pinnedRepos.map((repo, index) => ( + + {/*
*/} + +

+ {repo.repo.trim()} +

+

+ {repo.owner} +

+
+ +

+ {repo.description} +

+
+ {repo.language && ( + + + {repo.language} + + )} + + {repo.stars} + + + {repo.forks} + +
+
+ + View Repository{' '} + + + {repo.website && repo.website.length > 0 && ( + + Live Demo + + )} +
+
+ + ))} +
+ ) : ( + + )} +
+
+
+ )} +
+
+ ); }; export default Profile;