Skip to content
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

🚀Added Github Userdata Fetching using Github Ofiicial Api #59

Merged
merged 2 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
images: {
domains: ['avatars.githubusercontent.com'],
},
};

export default nextConfig;
12 changes: 11 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
253 changes: 179 additions & 74 deletions src/app/[username]/page.jsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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}`);
};

Expand All @@ -99,31 +103,132 @@ function UserPage({ params }) {
}

return (
<div
className="flex flex-col justify-center items-center h-screen"
style={{ backgroundColor: theme, color: textColor }}
>
<h1 className="text-2xl font-bold mb-4">{tagline}!</h1>
<div className="flex space-x-4">
<div>
<label className="block mb-2">Background Color</label>
<input
type="color"
value={theme}
onChange={handleThemeChange}
/>
</div>
<div>
<label className="block mb-2">Text Color</label>
<input
type="color"
value={textColor}
onChange={handleTextColorChange}
/>
<div className="min-h-screen bg-gray-900 text-white p-8 md:p-16 relative">
{/* Stylish Gradient Background */}
<div className="absolute inset-0 bg-gradient-to-tr from-indigo-600 via-purple-600 to-indigo-600 opacity-30 -z-10"></div>

{/* User Profile Section */}
{userData && (
<div className="max-w-6xl mx-auto bg-gray-800 bg-opacity-80 rounded-xl p-8 shadow-lg">
<div className="flex flex-col md:flex-row items-center md:items-start gap-8">
{/* Profile Image */}
<div className="flex-shrink-0">
<Image
src={userData.avatar_url || "/default_avatar.jpg"}
alt="Profile"
width={180}
height={180}
className="rounded-full border-4 border-purple-500"
/>
</div>

{/* Profile Info */}
<div className="text-center md:text-left">
<h1 className="text-4xl font-semibold text-transparent bg-clip-text bg-gradient-to-r from-purple-500 to-purple-800">
{userData.name || "Unknown User"}
</h1>
<p className="text-gray-400 mt-2">
@{userData.login || "username"}
</p>

<div className="mt-4 space-y-2">
<p className="flex justify-center md:justify-start items-center gap-2 text-gray-400">
<FaMapPin className="w-4 h-4" />
{userData.location || "Unknown"}
</p>
<p className="flex justify-center md:justify-start items-center gap-2 text-gray-400">
<FaClock className="w-4 h-4" />
{new Date().toLocaleString("en-IN", {
timeZone: "Asia/Kolkata",
})}
</p>
</div>

<div className="mt-4 flex justify-center md:justify-start gap-6 text-gray-500">
<div>
<FaGithub className="inline mr-1" />
<span>{userData.followers} followers</span>
</div>
<div>
<FaLink className="inline mr-1" />
<span>{userData.following} following</span>
</div>
</div>
</div>
</div>

{/* Repositories Section */}
<div className="mt-10">
<h3 className="text-3xl font-semibold mb-6 text-purple-400">
{userData.login.toUpperCase()}'s Repositories
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
{currentRepos.map((repo) => (
<div
key={repo.id}
className="bg-gray-700 p-6 rounded-lg overflow-hidden shadow-lg"
>
<h4 className="text-xl font-semibold text-blue-400 hover:underline">
<a
href={repo.html_url}
target="_blank"
rel="noopener noreferrer"
>
{repo.name}
</a>
</h4>
<p className="text-gray-300 mt-2">
{repo.description || "No description available."}
</p>
<div className="flex bg-stone-500/10 p-1.5 rounded-md justify-between mt-4 text-gray-400 text-sm">
<span>{repo.language || "Unknown"}</span>
<span>★ {repo.stargazers_count || 0}</span>
</div>
</div>
))}
</div>

{/* Pagination */}
<div className="flex mb-5 justify-between items-center mt-8">
<button
onClick={handlePreviousPage}
disabled={currentPage === 1}
className="px-4 py-2 bg-purple-600 text-white rounded-md disabled:opacity-50"
>
Previous
</button>
<span className="text-gray-300">
Page {currentPage} of {totalPages}
</span>
<button
onClick={handleNextPage}
disabled={currentPage === totalPages}
className="px-4 py-2 bg-purple-600 text-white rounded-md disabled:opacity-50"
>
Next
</button>
</div>
</div>
<div
className="flex flex-col justify-center items-center h-screen"
style={{ backgroundColor: theme, color: textColor }}
>
<h1 className="text-2xl font-bold mb-4">{tagline} <FaGithub /></h1>
<div className="flex space-x-4">
<div>
<label className="block mb-2">Background Color</label>
<input
type="color"
value={theme}
onChange={handleThemeChange}
/>
</div>
</div>
</div>
</div>
</div>
)}
</div>
);
}

export default UserPage;
export default UserPage;
35 changes: 11 additions & 24 deletions src/app/api/user/route.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,33 @@
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,
});
}

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 });
}
Expand Down
Loading