-
Notifications
You must be signed in to change notification settings - Fork 63
Profile Page Added in Frontend #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
❌ Deploy Preview for github-spy failed.
|
WalkthroughThe changes introduce GitHub authentication state management and user-specific UI elements to the Navbar, persist authentication data using localStorage in a custom hook, and enable authenticated users to edit their bio on the UserProfile page. The UserProfile UI is updated with improved styling, editing controls, and enhanced pull request displays. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Navbar
participant useGitHubAuth
participant UserProfile
participant GitHubAPI
User->>Navbar: Loads page
Navbar->>useGitHubAuth: Get username, token
useGitHubAuth-->>Navbar: Returns auth state
alt User clicks Logout
Navbar->>useGitHubAuth: logout()
useGitHubAuth-->>Navbar: Clears state & localStorage
Navbar->>User: Navigates to login
end
User->>UserProfile: Navigates to profile page
UserProfile->>useGitHubAuth: Get username, token
useGitHubAuth-->>UserProfile: Returns auth state
alt User edits own profile
User->>UserProfile: Clicks Edit
UserProfile->>User: Shows textarea
User->>UserProfile: Clicks Save
UserProfile->>GitHubAPI: PATCH user bio with token
GitHubAPI-->>UserProfile: Returns updated bio
UserProfile->>User: Updates UI
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used🪛 Biome (2.1.2)src/pages/UserProfile/UserProfile.tsx[error] 3-3: numbers cannot be followed by identifiers directly after an identifier cannot appear here (parse) [error] 31-31: numbers cannot be followed by identifiers directly after an identifier cannot appear here (parse) [error] 78-78: numbers cannot be followed by identifiers directly after an identifier cannot appear here (parse) [error] 104-105: numbers cannot be followed by identifiers directly after an identifier cannot appear here (parse) [error] 105-105: expected Remove gsoc (parse) [error] 105-106: numbers cannot be followed by identifiers directly after an identifier cannot appear here (parse) [error] 106-106: Expected a semicolon or an implicit semicolon after a statement, but found none An explicit or implicit semicolon is expected here... ...Which is required to end this statement (parse) [error] 107-107: expected Remove && (parse) [error] 108-108: expected Remove < (parse) [error] 108-108: expected Remove className (parse) [error] 109-109: Expected a parenthesis '(' but instead found '<'. Expected a parenthesis '(' here. (parse) [error] 109-109: expected Remove className (parse) [error] 110-110: Expected a parenthesis '(' but instead found '<'. Expected a parenthesis '(' here. (parse) [error] 111-111: expected Remove src (parse) [error] 111-111: ';' expected' An explicit or implicit semicolon is expected here... (parse) [error] 112-112: expected Remove alt (parse) [error] 113-113: expected Remove className (parse) [error] 114-114: expected Remove / (parse) [error] 114-115: Expected an expression but instead found '>'. Expected an expression here. (parse) [error] 115-115: expected Remove && (parse) [error] 116-116: expected Remove < (parse) [error] 116-116: expected Remove className (parse) [error] 116-116: expected Remove title (parse) [error] 116-116: expected Remove onClick (parse) [error] 116-116: ';' expected' An explicit or implicit semicolon is expected here... (parse) [error] 116-116: expected Remove ? (parse) [error] 116-116: expected Remove edit (parse) [error] 116-116: expected Remove ` (parse) [error] 117-117: Expected a parenthesis '(' but instead found '<'. Expected a parenthesis '(' here. (parse) [error] 117-117: expected Remove className (parse) [error] 117-117: expected Remove fill (parse) [error] 117-117: expected Remove stroke (parse) [error] 117-117: expected Remove strokeWidth (parse) [error] 117-117: expected Remove viewBox (parse) [error] 118-118: Expected a parenthesis '(' but instead found '<'. Expected a parenthesis '(' here. (parse) [error] 118-118: expected Remove strokeLinecap (parse) [error] 118-118: expected Remove strokeLinejoin (parse) [error] 118-118: expected Remove d (parse) [error] 118-118: expected Remove / (parse) [error] 119-119: Expected a parenthesis '(' but instead found '<'. Expected a parenthesis '(' here. (parse) [error] 119-119: Expected a type parameter but instead found '/'. Expected a type parameter here. (parse) [error] 119-119: expected Remove svg (parse) [error] 120-120: Expected a parenthesis '(' but instead found '<'. Expected a parenthesis '(' here. (parse) [error] 120-120: Expected a type parameter but instead found '/'. Expected a type parameter here. (parse) [error] 120-120: expected Remove span (parse) [error] 121-121: Expected a parenthesis '(' but instead found ')'. Expected a parenthesis '(' here. (parse) [error] 122-122: Expected an expression but instead found '<'. Expected an expression here. (parse) [error] 123-124: Expected an expression but instead found ''. Expected an expression here. (parse) [error] 158-158: Expected an expression but instead found '<'. Expected an expression here. (parse) [error] 177-177: Expected an expression but instead found '<'. Expected an expression here. (parse) src/components/Navbar.tsx[error] 1-1: numbers cannot be followed by identifiers directly after an identifier cannot appear here (parse) 🔇 Additional comments (2)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 Thank you @gaurav123-4 for your contribution. Please make sure your PR follows https://github.com/GitMetricsLab/github_tracker/blob/main/CONTRIBUTING.md#-pull-request-guidelines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (4)
src/hooks/useGitHubAuth.ts (1)
17-22: LGTM! Consider adding error handling for localStorage operations.The logout implementation correctly clears both state and storage. For robustness:
const logout = () => { setUsernameState(''); setTokenState(''); - localStorage.removeItem('username'); - localStorage.removeItem('token'); + try { + localStorage.removeItem('username'); + localStorage.removeItem('token'); + } catch (error) { + console.error('Failed to clear localStorage:', error); + } };src/components/Navbar.tsx (2)
54-100: Consider adding click-outside handler for dropdown.The dropdown implementation is functional but could benefit from closing when clicking outside:
// Add this after the imports import { useEffect, useRef } from 'react'; // Inside the component, add: const dropdownRef = useRef<HTMLDivElement>(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setDropdownOpen(false); } }; if (dropdownOpen) { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); } }, [dropdownOpen]); // Update the dropdown container div: <div className="relative" ref={dropdownRef}>
160-207: Consider extracting the user menu to reduce duplication.The mobile implementation works well, but the user dropdown logic is duplicated. Consider extracting it into a separate component:
Would you like me to create a
UserDropdowncomponent to eliminate the duplication between desktop and mobile views?src/pages/UserProfile/UserProfile.tsx (1)
89-122: Well-implemented bio editing UI.The editing interface is user-friendly with character count and proper form controls. Consider adding visual feedback for save errors based on the earlier error handling suggestion.
Consider adding aria-label attributes for better accessibility:
<textarea className="w-full border-2 border-blue-200 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-400 text-gray-800 text-base resize-none shadow" rows={3} maxLength={maxBioLength} value={editBio} onChange={e => setEditBio(e.target.value)} placeholder="Edit your bio" + aria-label="Bio text" />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/Navbar.tsx(3 hunks)src/hooks/useGitHubAuth.ts(2 hunks)src/pages/UserProfile/UserProfile.tsx(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/Navbar.tsx (1)
src/hooks/useGitHubAuth.ts (1)
useGitHubAuth(4-39)
🔇 Additional comments (5)
src/hooks/useGitHubAuth.ts (1)
37-37: LGTM!The logout function is correctly exposed in the hook's API.
src/components/Navbar.tsx (2)
1-14: LGTM! Clean integration of authentication state.The authentication hook integration and logout handling are well implemented.
77-97: LGTM! Well-structured dropdown menu.The menu items are properly implemented with appropriate navigation and state management.
src/pages/UserProfile/UserProfile.tsx (2)
12-23: LGTM! Clear state management and ownership logic.Good use of descriptive variable names and proper separation of concerns between viewing and editing states.
61-68: LGTM! Nice loading spinner implementation.The animated SVG spinner provides good visual feedback during data loading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🔭 Outside diff range comments (5)
src/components/Navbar.tsx (3)
6-26: Remove duplicate component definitions and imports.The component has duplicate imports and initialization logic due to unresolved merge conflicts. The
ThemeContextimport and usage are missing in the first version.Properly merge the authentication and theme functionality:
+import { Link, useNavigate } from 'react-router-dom'; +import { useState, useContext } from 'react'; +import { useGitHubAuth } from '../hooks/useGitHubAuth'; +import { ThemeContext } from "../ThemeContext"; const Navbar: React.FC = () => { const [isOpen, setIsOpen] = useState<boolean>(false); const [dropdownOpen, setDropdownOpen] = useState(false); const { username, logout } = useGitHubAuth(); const navigate = useNavigate(); + const themeContext = useContext(ThemeContext); + if (!themeContext) return null; + const { toggleTheme, mode } = themeContext; const handleLogout = () => { logout(); navigate('/login'); };
67-128: Remove duplicate authentication UI and Login link.There's duplicate authentication logic and a redundant Login link that appears even when the user is authenticated.
Remove the duplicate Login link (lines 116-121) since authentication is already handled conditionally above:
)} - <Link - to="/login" - className="text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded" - > - Login - </Link> <button onClick={toggleTheme} className="text-sm font-semibold px-3 py-1 rounded border border-gray-500 hover:text-gray-300 hover:border-gray-300 transition duration-200" > {mode === "dark" ? "🌞 Light" : "🌙 Dark"} </button>
237-254: Remove duplicate Login link in mobile menu.Similar to the desktop menu, there's a duplicate Login link in the mobile menu.
Remove the duplicate Login link in mobile menu:
)} - <Link - to="/login" - className="block text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded" - onClick={() => setIsOpen(false)} - > - Login - </Link> <button onClick={() => { toggleTheme(); setIsOpen(false); }} className="text-sm font-semibold px-3 py-1 rounded border border-gray-500 hover:text-gray-300 hover:border-gray-300 transition duration-200 w-full text-left" > {mode === "dark" ? "🌞 Light" : "🌙 Dark"} </button>src/pages/UserProfile/UserProfile.tsx (2)
31-57: Remove duplicate fetchData logic and resolve merge conflicts.The useEffect contains duplicate data fetching logic due to unresolved merge conflicts. The first version lacks error handling while the second has proper error handling.
Keep only the version with proper error handling and remove the duplicate:
useEffect(() => { async function fetchData() { -gsoc-2025Gaurav - if (!paramUsername) return; - const userRes = await fetch(`https://api.github.com/users/${paramUsername}`); - const userData = await userRes.json(); - setProfile(userData); - setEditBio(userData.bio || ""); - const prsRes = await fetch(`https://api.github.com/search/issues?q=author:${paramUsername}+type:pr`); - const prsData = await prsRes.json(); - setPRs(prsData.items); - setLoading(false); - - if (!username) return; + if (!paramUsername) return; try { - const userRes = await fetch(`https://api.github.com/users/${username}`); + const userRes = await fetch(`https://api.github.com/users/${paramUsername}`); const userData = await userRes.json(); setProfile(userData); + setEditBio(userData.bio || ""); - const prsRes = await fetch(`https://api.github.com/search/issues?q=author:${username}+type:pr`); + const prsRes = await fetch(`https://api.github.com/search/issues?q=author:${paramUsername}+type:pr`); const prsData = await prsRes.json(); setPRs(prsData.items); } catch (error) { toast.error("Failed to fetch user data."); } finally { setLoading(false); } -main } fetchData(); }, [paramUsername]);
178-214: Remove duplicate profile display section.There's a complete duplicate of the profile display section at the bottom of the component, which creates confusion and unnecessary code duplication.
Remove the duplicate profile section:
- <div className="max-w-3xl mx-auto mt-10 p-4 bg-white shadow-xl rounded-xl"> - <div className="text-center"> - <img src={profile.avatar_url} alt="Avatar" className="w-24 h-24 mx-auto rounded-full" /> - <h2 className="text-2xl font-bold mt-2">{profile.login}</h2> - <p className="text-gray-600">{profile.bio}</p> - <button - onClick={handleCopyLink} - className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition" - > - Copy Shareable Link - </button> - </div> - - <h3 className="text-xl font-semibold mt-6 mb-2">Pull Requests</h3> - {prs.length > 0 ? ( - <ul className="list-disc ml-6 space-y-2"> - {prs.map((pr, i) => { - const repoName = pr.repository_url.split("/").slice(-2).join("/"); - return ( - <li key={i}> - <a - href={pr.html_url} - target="_blank" - rel="noopener noreferrer" - className="text-blue-600 hover:underline" - > - [{repoName}] {pr.title} - </a> - </li> - ); - })} - </ul> - ) : ( - <p className="text-gray-600">No pull requests found for this user.</p> - )} -main - </div>If you need the "Copy Shareable Link" functionality, integrate it into the main profile section above.
♻️ Duplicate comments (2)
src/pages/UserProfile/UserProfile.tsx (2)
62-76: Add error handling and improve navigation.The handleSave function still lacks error handling and uses window.location.href for navigation, which are issues flagged in previous reviews.
This was already identified in past review comments. Apply the suggested error handling and navigation improvements:
+import { useNavigate } from 'react-router-dom'; +const navigate = useNavigate(); const handleSave = async () => { if (!token) return; setSaving(true); - await fetch("https://api.github.com/user", { - method: "PATCH", - headers: { - "Authorization": `token ${token}`, - "Accept": "application/vnd.github+json", - "Content-Type": "application/json", - }, - body: JSON.stringify({ bio: editBio }), - }); - setSaving(false); - window.location.href = `/user/${paramUsername}`; + try { + const response = await fetch("https://api.github.com/user", { + method: "PATCH", + headers: { + "Authorization": `token ${token}`, + "Accept": "application/vnd.github+json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ bio: editBio }), + }); + + if (!response.ok) { + throw new Error(`Failed to update bio: ${response.status}`); + } + + setProfile(prev => ({ ...prev, bio: editBio })); + navigate(`/user/${paramUsername}`, { replace: true }); + } catch (error) { + console.error('Failed to save bio:', error); + toast.error("Failed to save bio. Please try again."); + } finally { + setSaving(false); + } };
78-82: Fix handleCancel navigation and resolve merge conflict marker.The handleCancel function has unused editMode state and contains merge conflict markers.
Remove merge conflict markers and improve navigation as suggested in past reviews:
+import { useNavigate } from 'react-router-dom'; +const navigate = useNavigate(); -gsoc-2025Gaurav const handleCancel = () => { setEditBio(profile.bio || ""); - setEditMode(false); + navigate(`/user/${paramUsername}`, { replace: true }); };
🧹 Nitpick comments (1)
src/components/Navbar.tsx (1)
114-114: Improve accessibility and user experience for dropdown.The dropdown implementation could benefit from better accessibility and click-outside handling.
Consider adding click-outside handling and proper ARIA attributes:
+import { useEffect, useRef } from 'react'; +const dropdownRef = useRef<HTMLDivElement>(null); +useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setDropdownOpen(false); + } + }; + if (dropdownOpen) { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } +}, [dropdownOpen]); - <div className="relative"> + <div className="relative" ref={dropdownRef}> <button className="flex items-center space-x-2 focus:outline-none" onClick={() => setDropdownOpen((v) => !v)} + aria-expanded={dropdownOpen} + aria-haspopup="true" >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/Navbar.tsx(7 hunks)src/pages/UserProfile/UserProfile.tsx(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/Navbar.tsx (1)
src/hooks/useGitHubAuth.ts (1)
useGitHubAuth(4-39)
🪛 Biome (2.1.2)
src/pages/UserProfile/UserProfile.tsx
[error] 3-3: numbers cannot be followed by identifiers directly after
an identifier cannot appear here
(parse)
[error] 31-31: numbers cannot be followed by identifiers directly after
an identifier cannot appear here
(parse)
[error] 78-78: numbers cannot be followed by identifiers directly after
an identifier cannot appear here
(parse)
[error] 104-105: numbers cannot be followed by identifiers directly after
an identifier cannot appear here
(parse)
[error] 105-105: expected ) but instead found className
Remove className
(parse)
[error] 106-106: expected , but instead found &&
Remove &&
(parse)
[error] 107-107: expected , but instead found <
Remove <
(parse)
[error] 107-107: expected , but instead found className
Remove className
(parse)
[error] 108-108: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 108-108: expected , but instead found className
Remove className
(parse)
[error] 109-109: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 110-110: expected , but instead found src
Remove src
(parse)
[error] 110-110: ';' expected'
An explicit or implicit semicolon is expected here...
(parse)
[error] 111-111: expected , but instead found alt
Remove alt
(parse)
[error] 112-112: expected , but instead found className
Remove className
(parse)
[error] 113-113: expected , but instead found /
Remove /
(parse)
[error] 113-114: Expected an expression but instead found '>'.
Expected an expression here.
(parse)
[error] 114-114: expected , but instead found &&
Remove &&
(parse)
[error] 115-115: expected , but instead found <
Remove <
(parse)
[error] 115-115: expected , but instead found className
Remove className
(parse)
[error] 115-115: expected , but instead found title
Remove title
(parse)
[error] 115-115: expected , but instead found onClick
Remove onClick
(parse)
[error] 115-115: ';' expected'
An explicit or implicit semicolon is expected here...
(parse)
[error] 115-115: expected , but instead found ?
Remove ?
(parse)
[error] 115-115: expected , but instead found edit
Remove edit
(parse)
[error] 115-115: expected , but instead found ```
Remove `
(parse)
[error] 116-116: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 116-116: expected , but instead found className
Remove className
(parse)
[error] 116-116: expected , but instead found fill
Remove fill
(parse)
[error] 116-116: expected , but instead found stroke
Remove stroke
(parse)
[error] 116-116: expected , but instead found strokeWidth
Remove strokeWidth
(parse)
[error] 116-116: expected , but instead found viewBox
Remove viewBox
(parse)
[error] 117-117: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 117-117: expected , but instead found strokeLinecap
Remove strokeLinecap
(parse)
[error] 117-117: expected , but instead found strokeLinejoin
Remove strokeLinejoin
(parse)
[error] 117-117: expected , but instead found d
Remove d
(parse)
[error] 117-117: expected , but instead found /
Remove /
(parse)
[error] 118-118: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 118-118: Expected a type parameter but instead found '/'.
Expected a type parameter here.
(parse)
[error] 118-118: expected , but instead found svg
Remove svg
(parse)
[error] 119-119: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 119-119: Expected a type parameter but instead found '/'.
Expected a type parameter here.
(parse)
[error] 119-119: expected , but instead found span
Remove span
(parse)
[error] 120-120: Expected a parenthesis '(' but instead found ')'.
Expected a parenthesis '(' here.
(parse)
[error] 121-121: Expected an expression but instead found '<'.
Expected an expression here.
(parse)
[error] 122-123: Expected an expression but instead found ''.
Expected an expression here.
(parse)
[error] 157-157: Expected an expression but instead found '<'.
Expected an expression here.
(parse)
[error] 176-176: Expected an expression but instead found '<'.
Expected an expression here.
(parse)
src/components/Navbar.tsx
[error] 1-1: numbers cannot be followed by identifiers directly after
an identifier cannot appear here
(parse)
| gsoc-2025Gaurav | ||
| <div className="flex flex-col items-center bg-gradient-to-br from-white to-gray-100 shadow-2xl rounded-2xl p-8 max-w-xl mx-auto mt-16"> | ||
| {profile && ( | ||
| <div className="flex flex-col items-center w-full"> | ||
| <div className="relative group"> | ||
| <img | ||
| src={profile.avatar_url} | ||
| alt="Avatar" | ||
| className="w-32 h-32 rounded-full border-4 border-blue-400 shadow-lg transition-transform group-hover:scale-105" | ||
| /> | ||
| {isOwnProfile && !isEdit && ( | ||
| <span className="absolute bottom-2 right-2 bg-blue-600 text-white rounded-full p-1 shadow-lg cursor-pointer" title="Edit Profile" onClick={() => window.location.href = `/user/${paramUsername}?edit=1`}> | ||
| <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="round" strokeLinejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 112.828 2.828L11.828 15.828a2 2 0 01-2.828 0L9 13zm0 0V17h4" /> | ||
| </svg> | ||
| </span> | ||
| )} | ||
| </div> | ||
| <h2 className="text-3xl font-bold mt-4">{profile.login}</h2> | ||
| {isEdit && isOwnProfile ? ( | ||
| <div className="mt-4 w-full"> | ||
| <div className="flex items-center mb-2"> | ||
| <span className="font-semibold text-gray-700 mr-2">Bio</span> | ||
| <span className="text-xs text-gray-400">({editBio.length}/{maxBioLength})</span> | ||
| </div> | ||
| <textarea | ||
| className="w-full border-2 border-blue-200 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-400 text-gray-800 text-base resize-none shadow" | ||
| rows={3} | ||
| maxLength={maxBioLength} | ||
| value={editBio} | ||
| onChange={e => setEditBio(e.target.value)} | ||
| placeholder="Edit your bio" | ||
| /> | ||
| <div className="flex gap-3 mt-3"> | ||
| <button | ||
| className="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow transition" | ||
| onClick={handleSave} | ||
| disabled={saving} | ||
| > | ||
| {saving ? "Saving..." : "Save Bio"} | ||
| </button> | ||
| <button | ||
| className="px-6 py-2 bg-gray-300 hover:bg-gray-400 text-gray-800 rounded-full shadow transition" | ||
| onClick={handleCancel} | ||
| disabled={saving} | ||
| > | ||
| Cancel | ||
| </button> | ||
| </div> | ||
| </div> | ||
| ) : ( | ||
| <p className="text-lg text-gray-700 mt-4 text-center max-w-md">{profile.bio || <span className="italic text-gray-400">No bio provided.</span>}</p> | ||
| )} | ||
| </div> | ||
| )} | ||
| <h3 className="text-2xl font-semibold mt-10 mb-4 w-full text-left">Pull Requests</h3> | ||
| <div className="grid grid-cols-1 gap-4 w-full"> | ||
| {prs.length === 0 && <div className="text-gray-400 italic">No pull requests found.</div>} | ||
| {prs.map((pr, i) => ( | ||
| <div key={i} className="bg-white rounded-xl shadow flex flex-col md:flex-row items-start md:items-center p-4 border border-gray-100 hover:shadow-lg transition"> | ||
| <div className="flex-1"> | ||
| <a href={pr.html_url} target="_blank" rel="noopener noreferrer" className="text-blue-600 font-semibold hover:underline text-lg flex items-center"> | ||
| <svg className="w-5 h-5 mr-2 text-blue-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="round" strokeLinejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3" /> | ||
| </svg> | ||
| {pr.title} | ||
| </a> | ||
| <div className="text-sm text-gray-500 mt-1">{pr.repository_url.split("/").pop()}</div> | ||
| </div> | ||
| <span className="ml-0 md:ml-4 mt-2 md:mt-0 inline-block bg-green-100 text-green-700 text-xs font-semibold px-3 py-1 rounded-full">PR</span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolve merge conflict markers in JSX.
The JSX contains merge conflict markers that are causing multiple parse errors.
Remove all merge conflict markers throughout the JSX:
-gsoc-2025Gaurav
<div className="flex flex-col items-center bg-gradient-to-br from-white to-gray-100 shadow-2xl rounded-2xl p-8 max-w-xl mx-auto mt-16">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| gsoc-2025Gaurav | |
| <div className="flex flex-col items-center bg-gradient-to-br from-white to-gray-100 shadow-2xl rounded-2xl p-8 max-w-xl mx-auto mt-16"> | |
| {profile && ( | |
| <div className="flex flex-col items-center w-full"> | |
| <div className="relative group"> | |
| <img | |
| src={profile.avatar_url} | |
| alt="Avatar" | |
| className="w-32 h-32 rounded-full border-4 border-blue-400 shadow-lg transition-transform group-hover:scale-105" | |
| /> | |
| {isOwnProfile && !isEdit && ( | |
| <span className="absolute bottom-2 right-2 bg-blue-600 text-white rounded-full p-1 shadow-lg cursor-pointer" title="Edit Profile" onClick={() => window.location.href = `/user/${paramUsername}?edit=1`}> | |
| <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 112.828 2.828L11.828 15.828a2 2 0 01-2.828 0L9 13zm0 0V17h4" /> | |
| </svg> | |
| </span> | |
| )} | |
| </div> | |
| <h2 className="text-3xl font-bold mt-4">{profile.login}</h2> | |
| {isEdit && isOwnProfile ? ( | |
| <div className="mt-4 w-full"> | |
| <div className="flex items-center mb-2"> | |
| <span className="font-semibold text-gray-700 mr-2">Bio</span> | |
| <span className="text-xs text-gray-400">({editBio.length}/{maxBioLength})</span> | |
| </div> | |
| <textarea | |
| className="w-full border-2 border-blue-200 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-400 text-gray-800 text-base resize-none shadow" | |
| rows={3} | |
| maxLength={maxBioLength} | |
| value={editBio} | |
| onChange={e => setEditBio(e.target.value)} | |
| placeholder="Edit your bio" | |
| /> | |
| <div className="flex gap-3 mt-3"> | |
| <button | |
| className="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow transition" | |
| onClick={handleSave} | |
| disabled={saving} | |
| > | |
| {saving ? "Saving..." : "Save Bio"} | |
| </button> | |
| <button | |
| className="px-6 py-2 bg-gray-300 hover:bg-gray-400 text-gray-800 rounded-full shadow transition" | |
| onClick={handleCancel} | |
| disabled={saving} | |
| > | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| ) : ( | |
| <p className="text-lg text-gray-700 mt-4 text-center max-w-md">{profile.bio || <span className="italic text-gray-400">No bio provided.</span>}</p> | |
| )} | |
| </div> | |
| )} | |
| <h3 className="text-2xl font-semibold mt-10 mb-4 w-full text-left">Pull Requests</h3> | |
| <div className="grid grid-cols-1 gap-4 w-full"> | |
| {prs.length === 0 && <div className="text-gray-400 italic">No pull requests found.</div>} | |
| {prs.map((pr, i) => ( | |
| <div key={i} className="bg-white rounded-xl shadow flex flex-col md:flex-row items-start md:items-center p-4 border border-gray-100 hover:shadow-lg transition"> | |
| <div className="flex-1"> | |
| <a href={pr.html_url} target="_blank" rel="noopener noreferrer" className="text-blue-600 font-semibold hover:underline text-lg flex items-center"> | |
| <svg className="w-5 h-5 mr-2 text-blue-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3" /> | |
| </svg> | |
| {pr.title} | |
| </a> | |
| <div className="text-sm text-gray-500 mt-1">{pr.repository_url.split("/").pop()}</div> | |
| </div> | |
| <span className="ml-0 md:ml-4 mt-2 md:mt-0 inline-block bg-green-100 text-green-700 text-xs font-semibold px-3 py-1 rounded-full">PR</span> | |
| </div> | |
| ))} | |
| </div> | |
| <div className="flex flex-col items-center bg-gradient-to-br from-white to-gray-100 shadow-2xl rounded-2xl p-8 max-w-xl mx-auto mt-16"> | |
| {profile && ( | |
| <div className="flex flex-col items-center w-full"> | |
| <div className="relative group"> | |
| <img | |
| src={profile.avatar_url} | |
| alt="Avatar" | |
| className="w-32 h-32 rounded-full border-4 border-blue-400 shadow-lg transition-transform group-hover:scale-105" | |
| /> | |
| {isOwnProfile && !isEdit && ( | |
| <span className="absolute bottom-2 right-2 bg-blue-600 text-white rounded-full p-1 shadow-lg cursor-pointer" title="Edit Profile" onClick={() => window.location.href = `/user/${paramUsername}?edit=1`}> | |
| <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 112.828 2.828L11.828 15.828a2 2 0 01-2.828 0L9 13zm0 0V17h4" /> | |
| </svg> | |
| </span> | |
| )} | |
| </div> | |
| <h2 className="text-3xl font-bold mt-4">{profile.login}</h2> | |
| {isEdit && isOwnProfile ? ( | |
| <div className="mt-4 w-full"> | |
| <div className="flex items-center mb-2"> | |
| <span className="font-semibold text-gray-700 mr-2">Bio</span> | |
| <span className="text-xs text-gray-400">({editBio.length}/{maxBioLength})</span> | |
| </div> | |
| <textarea | |
| className="w-full border-2 border-blue-200 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-400 text-gray-800 text-base resize-none shadow" | |
| rows={3} | |
| maxLength={maxBioLength} | |
| value={editBio} | |
| onChange={e => setEditBio(e.target.value)} | |
| placeholder="Edit your bio" | |
| /> | |
| <div className="flex gap-3 mt-3"> | |
| <button | |
| className="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow transition" | |
| onClick={handleSave} | |
| disabled={saving} | |
| > | |
| {saving ? "Saving..." : "Save Bio"} | |
| </button> | |
| <button | |
| className="px-6 py-2 bg-gray-300 hover:bg-gray-400 text-gray-800 rounded-full shadow transition" | |
| onClick={handleCancel} | |
| disabled={saving} | |
| > | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| ) : ( | |
| <p className="text-lg text-gray-700 mt-4 text-center max-w-md">{profile.bio || <span className="italic text-gray-400">No bio provided.</span>}</p> | |
| )} | |
| </div> | |
| )} | |
| <h3 className="text-2xl font-semibold mt-10 mb-4 w-full text-left">Pull Requests</h3> | |
| <div className="grid grid-cols-1 gap-4 w-full"> | |
| {prs.length === 0 && <div className="text-gray-400 italic">No pull requests found.</div>} | |
| {prs.map((pr, i) => ( | |
| <div key={i} className="bg-white rounded-xl shadow flex flex-col md:flex-row items-start md:items-center p-4 border border-gray-100 hover:shadow-lg transition"> | |
| <div className="flex-1"> | |
| <a href={pr.html_url} target="_blank" rel="noopener noreferrer" className="text-blue-600 font-semibold hover:underline text-lg flex items-center"> | |
| <svg className="w-5 h-5 mr-2 text-blue-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3" /> | |
| </svg> | |
| {pr.title} | |
| </a> | |
| <div className="text-sm text-gray-500 mt-1">{pr.repository_url.split("/").pop()}</div> | |
| </div> | |
| <span className="ml-0 md:ml-4 mt-2 md:mt-0 inline-block bg-green-100 text-green-700 text-xs font-semibold px-3 py-1 rounded-full">PR</span> | |
| </div> | |
| ))} | |
| </div> |
🧰 Tools
🪛 Biome (2.1.2)
[error] 104-105: numbers cannot be followed by identifiers directly after
an identifier cannot appear here
(parse)
[error] 105-105: expected ) but instead found className
Remove className
(parse)
[error] 106-106: expected , but instead found &&
Remove &&
(parse)
[error] 107-107: expected , but instead found <
Remove <
(parse)
[error] 107-107: expected , but instead found className
Remove className
(parse)
[error] 108-108: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 108-108: expected , but instead found className
Remove className
(parse)
[error] 109-109: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 110-110: expected , but instead found src
Remove src
(parse)
[error] 110-110: ';' expected'
An explicit or implicit semicolon is expected here...
(parse)
[error] 111-111: expected , but instead found alt
Remove alt
(parse)
[error] 112-112: expected , but instead found className
Remove className
(parse)
[error] 113-113: expected , but instead found /
Remove /
(parse)
[error] 113-114: Expected an expression but instead found '>'.
Expected an expression here.
(parse)
[error] 114-114: expected , but instead found &&
Remove &&
(parse)
[error] 115-115: expected , but instead found <
Remove <
(parse)
[error] 115-115: expected , but instead found className
Remove className
(parse)
[error] 115-115: expected , but instead found title
Remove title
(parse)
[error] 115-115: expected , but instead found onClick
Remove onClick
(parse)
[error] 115-115: ';' expected'
An explicit or implicit semicolon is expected here...
(parse)
[error] 115-115: expected , but instead found ?
Remove ?
(parse)
[error] 115-115: expected , but instead found edit
Remove edit
(parse)
[error] 115-115: expected , but instead found ```
Remove `
(parse)
[error] 116-116: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 116-116: expected , but instead found className
Remove className
(parse)
[error] 116-116: expected , but instead found fill
Remove fill
(parse)
[error] 116-116: expected , but instead found stroke
Remove stroke
(parse)
[error] 116-116: expected , but instead found strokeWidth
Remove strokeWidth
(parse)
[error] 116-116: expected , but instead found viewBox
Remove viewBox
(parse)
[error] 117-117: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 117-117: expected , but instead found strokeLinecap
Remove strokeLinecap
(parse)
[error] 117-117: expected , but instead found strokeLinejoin
Remove strokeLinejoin
(parse)
[error] 117-117: expected , but instead found d
Remove d
(parse)
[error] 117-117: expected , but instead found /
Remove /
(parse)
[error] 118-118: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 118-118: Expected a type parameter but instead found '/'.
Expected a type parameter here.
(parse)
[error] 118-118: expected , but instead found svg
Remove svg
(parse)
[error] 119-119: Expected a parenthesis '(' but instead found '<'.
Expected a parenthesis '(' here.
(parse)
[error] 119-119: Expected a type parameter but instead found '/'.
Expected a type parameter here.
(parse)
[error] 119-119: expected , but instead found span
Remove span
(parse)
[error] 120-120: Expected a parenthesis '(' but instead found ')'.
Expected a parenthesis '(' here.
(parse)
[error] 121-121: Expected an expression but instead found '<'.
Expected an expression here.
(parse)
[error] 122-123: Expected an expression but instead found ''.
Expected an expression here.
(parse)
[error] 157-157: Expected an expression but instead found '<'.
Expected an expression here.
(parse)
[error] 176-176: Expected an expression but instead found '<'.
Expected an expression here.
(parse)
🤖 Prompt for AI Agents
In src/pages/UserProfile/UserProfile.tsx between lines 104 and 177, there are
unresolved merge conflict markers within the JSX code causing parse errors.
Locate and remove all conflict markers such as <<<<<<<, =======, and >>>>>>> to
clean up the JSX. Ensure the final JSX structure is syntactically correct and
consistent with the intended UI design.
| className="w-32 h-32 rounded-full border-4 border-blue-400 shadow-lg transition-transform group-hover:scale-105" | ||
| /> | ||
| {isOwnProfile && !isEdit && ( | ||
| <span className="absolute bottom-2 right-2 bg-blue-600 text-white rounded-full p-1 shadow-lg cursor-pointer" title="Edit Profile" onClick={() => window.location.href = `/user/${paramUsername}?edit=1`}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve edit profile navigation.
Using window.location.href for navigation is inconsistent with React Router patterns.
Use React Router navigation:
+import { useNavigate } from 'react-router-dom';
+const navigate = useNavigate();
- {isOwnProfile && !isEdit && (
- <span className="absolute bottom-2 right-2 bg-blue-600 text-white rounded-full p-1 shadow-lg cursor-pointer" title="Edit Profile" onClick={() => window.location.href = `/user/${paramUsername}?edit=1`}>
+ {isOwnProfile && !isEdit && (
+ <span className="absolute bottom-2 right-2 bg-blue-600 text-white rounded-full p-1 shadow-lg cursor-pointer" title="Edit Profile" onClick={() => navigate(`/user/${paramUsername}?edit=1`)}>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span className="absolute bottom-2 right-2 bg-blue-600 text-white rounded-full p-1 shadow-lg cursor-pointer" title="Edit Profile" onClick={() => window.location.href = `/user/${paramUsername}?edit=1`}> | |
| // src/pages/UserProfile/UserProfile.tsx | |
| import React from 'react'; | |
| import { useParams } from 'react-router-dom'; | |
| +import { useNavigate } from 'react-router-dom'; | |
| const UserProfile: React.FC = () => { | |
| const { paramUsername } = useParams<{ paramUsername: string }>(); | |
| + const navigate = useNavigate(); | |
| // … other hooks/state | |
| return ( | |
| <div> | |
| {/* … other JSX */} | |
| - {isOwnProfile && !isEdit && ( | |
| - <span | |
| - className="absolute bottom-2 right-2 bg-blue-600 text-white rounded-full p-1 shadow-lg cursor-pointer" | |
| - title="Edit Profile" | |
| - onClick={() => window.location.href = `/user/${paramUsername}?edit=1`} | |
| - > | |
| + {isOwnProfile && !isEdit && ( | |
| + <span | |
| + className="absolute bottom-2 right-2 bg-blue-600 text-white rounded-full p-1 shadow-lg cursor-pointer" | |
| + title="Edit Profile" | |
| + onClick={() => navigate(`/user/${paramUsername}?edit=1`)} | |
| + > | |
| {/* Edit Icon */} | |
| </span> | |
| )} | |
| {/* … rest of JSX */} | |
| </div> | |
| ); | |
| }; | |
| export default UserProfile; |
🧰 Tools
🪛 Biome (2.1.2)
[error] 115-115: expected , but instead found <
Remove <
(parse)
[error] 115-115: expected , but instead found className
Remove className
(parse)
[error] 115-115: expected , but instead found title
Remove title
(parse)
[error] 115-115: expected , but instead found onClick
Remove onClick
(parse)
[error] 115-115: ';' expected'
An explicit or implicit semicolon is expected here...
(parse)
[error] 115-115: expected , but instead found ?
Remove ?
(parse)
[error] 115-115: expected , but instead found edit
Remove edit
(parse)
[error] 115-115: expected , but instead found ```
Remove `
(parse)
🤖 Prompt for AI Agents
In src/pages/UserProfile/UserProfile.tsx at line 115, replace the use of
window.location.href for navigation with React Router's navigation method.
Import the useNavigate hook from react-router-dom, create a navigate function,
and use it in the onClick handler to programmatically navigate to the edit
profile URL, ensuring consistent routing behavior within the React app.
|
@gaurav123-4 : please fix conflicts |
Related Issue
Description
How Has This Been Tested?
Screenshots (if applicable)
Type of Change
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes