diff --git a/next.config.mjs b/next.config.mjs index 4678774..fee7091 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + domains: ['avatars.githubusercontent.com'], + }, +}; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index a3b6e42..8582ac4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "next-themes": "^0.3.0", "react": "^18.3.1", "react-dom": "^18", - "react-hot-toast": "^2.4.1" + "react-hot-toast": "^2.4.1", + "react-icons": "^5.3.0" }, "devDependencies": { "@types/react": "^18.3.11", @@ -3957,6 +3958,15 @@ "react-dom": ">=16" } }, + "node_modules/react-icons": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", + "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 76a0e7d..80acf58 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "next-themes": "^0.3.0", "react": "^18.3.1", "react-dom": "^18", - "react-hot-toast": "^2.4.1" + "react-hot-toast": "^2.4.1", + "react-icons": "^5.3.0" }, "devDependencies": { "@types/react": "^18.3.11", diff --git a/src/app/[username]/page.jsx b/src/app/[username]/page.jsx index a75e817..8e64ba9 100644 --- a/src/app/[username]/page.jsx +++ b/src/app/[username]/page.jsx @@ -1,71 +1,73 @@ -'use client'; +"use client"; import { useSearchParams, useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import axios from "axios"; -import NotFound from '@/app/not-found'; +import NotFound from "@/app/not-found"; +import Image from "next/image"; +import { FaMapPin, FaClock, FaGithub, FaLink } from "react-icons/fa"; // Create a separate 404 error component function UserPage({ params }) { const { username } = params; const searchParams = useSearchParams(); const router = useRouter(); + const [userData, setUserData] = useState(null); + const [repositories, setRepositories] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const reposPerPage = 6; const [theme, setTheme] = useState("#ffffff"); const [textColor, setTextColor] = useState("#000000"); const [tagline, setTagline] = useState(""); - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(false); const [is404, setIs404] = useState(false); useEffect(() => { - const themeParam = searchParams.get("theme"); - const colorParam = searchParams.get("color"); - - if (themeParam) setTheme(decodeURIComponent(themeParam)); - if (colorParam) setTextColor(decodeURIComponent(colorParam)); - }, [searchParams]); - - const fetchTagline = async (retries = 3) => { - setIsLoading(true); - - for (let attempt = 0; attempt < retries; attempt++) { - try { - const res = await axios.post( - 'https://gityzer.vercel.app/api/ai', - { username }, - { - headers: { - 'Content-Type': 'application/json', - }, - timeout: 10000, - } - ); - - if (res.data && res.data.tagline) { - setTagline(res.data.tagline); - setIsLoading(false); - return; - } - } catch (err) { - console.error(`Attempt ${attempt + 1} failed:`, err); - - // If this is the last attempt - if (attempt === retries - 1) { - setIs404(true); - return; - } else { - // Wait before retrying (exponential backoff) - await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt))); + const fetchUserData = async () => { + setIsLoading(true); // Start loading + setIs404(false); // Reset 404 state for each new fetch + + try { + const userResponse = await fetch(`https://api.github.com/users/${username}`); + + if (userResponse.status === 404) { + setIs404(true); + setUserData(null); + setRepositories([]); + setIsLoading(false); + return; + } + + const userData = await userResponse.json(); + setUserData(userData); + + const reposResponse = await fetch(userData.repos_url); + const reposData = await reposResponse.json(); + setRepositories(reposData); + } catch (error) { + console.error("Fetch error:", error); + } finally { + setIsLoading(false); // End loading } - } - } - - setIsLoading(false); + }; + + if (username) fetchUserData(); +}, [username]); + + const totalPages = Math.ceil(repositories.length / reposPerPage); + const indexOfLastRepo = currentPage * reposPerPage; + const indexOfFirstRepo = indexOfLastRepo - reposPerPage; + const currentRepos = repositories.slice(indexOfFirstRepo, indexOfLastRepo); + + const handleNextPage = () => { + if (currentPage < totalPages) setCurrentPage(currentPage + 1); }; - useEffect(() => { - if (username) { - fetchTagline(); - } - }, [username]); + const handlePreviousPage = () => { + if (currentPage > 1) setCurrentPage(currentPage - 1); + }; + + + const handleThemeChange = (e) => { const newTheme = e.target.value; @@ -80,7 +82,9 @@ function UserPage({ params }) { }; const updateUrl = (theme, color) => { - const queryString = `?theme=${encodeURIComponent(theme)}&color=${encodeURIComponent(color)}`; + const queryString = `?theme=${encodeURIComponent( + theme + )}&color=${encodeURIComponent(color)}`; router.push(`/${username}${queryString}`); }; @@ -99,31 +103,132 @@ function UserPage({ params }) { } return ( -
-

{tagline}!

-
-
- - -
-
- - +
+ {/* Stylish Gradient Background */} +
+ + {/* User Profile Section */} + {userData && ( +
+
+ {/* Profile Image */} +
+ Profile +
+ + {/* Profile Info */} +
+

+ {userData.name || "Unknown User"} +

+

+ @{userData.login || "username"} +

+ +
+

+ + {userData.location || "Unknown"} +

+

+ + {new Date().toLocaleString("en-IN", { + timeZone: "Asia/Kolkata", + })} +

+
+ +
+
+ + {userData.followers} followers +
+
+ + {userData.following} following +
+
+
+
+ + {/* Repositories Section */} +
+

+ {userData.login.toUpperCase()}'s Repositories +

+
+ {currentRepos.map((repo) => ( +
+

+ + {repo.name} + +

+

+ {repo.description || "No description available."} +

+
+ {repo.language || "Unknown"} + ★ {repo.stargazers_count || 0} +
+
+ ))} +
+ + {/* Pagination */} +
+ + + Page {currentPage} of {totalPages} + + +
+
+
+

{tagline}

+
+
+ + +
+
+
-
+ )}
); } -export default UserPage; \ No newline at end of file +export default UserPage; diff --git a/src/app/api/user/route.js b/src/app/api/user/route.js index 147a78c..699e742 100644 --- a/src/app/api/user/route.js +++ b/src/app/api/user/route.js @@ -1,37 +1,22 @@ -import { NextResponse } from "next/server"; - -const GITHUB_API_URL = "https://api.github.com/graphql"; +import { NextResponse } from 'next/server'; export async function POST(req) { + // Parse the request body to extract the username const { username } = await req.json(); if (!username) { return new NextResponse("Username required", { status: 400 }); } - const query = ` - query ($login: String!) { - user(login: $login) { - login - } - } - `; - try { - const response = await fetch(GITHUB_API_URL, { - method: "POST", - headers: { - Accept: "application/json", - Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query, - variables: { login: username }, - }), + // Fetch the user's GitHub profile using the REST API + const response = await fetch(`https://api.github.com/users/${username}`, { + method: "GET", // Using GET since it's a public REST API request + }); if (!response.ok) { + // Handle different errors from the GitHub API return new NextResponse(await response.text(), { status: response.status, }); @@ -39,8 +24,10 @@ export async function POST(req) { const data = await response.json(); console.log(data); - if (data.data.user) { - return NextResponse.json({ exists: true }); + + // If the user exists, return a response with `exists: true` + if (data && data.login) { + return NextResponse.json({ exists: true, userData: data }); } else { return NextResponse.json({ exists: false }); }