From 058a86ae2de766eb715d65d171ff97c640a75808 Mon Sep 17 00:00:00 2001 From: Vansh Chaurasiya Date: Sat, 4 Jan 2025 22:17:52 +0530 Subject: [PATCH] Add user interface and refactor API routes; integrate Header component --- src/app/[username]/badge/page.jsx | 328 ------------------------ src/app/[username]/page.jsx | 316 ++++++++++++++++++++++- src/app/api/ai/route.ts | 2 +- src/app/api/user/interface.ts | 3 + src/app/api/user/{route.js => route.ts} | 7 +- src/app/layout.js | 2 + src/components/Header.jsx | 23 ++ src/pages/Home.jsx | 19 -- src/pages/UserProfile.jsx | 294 --------------------- 9 files changed, 342 insertions(+), 652 deletions(-) delete mode 100644 src/app/[username]/badge/page.jsx create mode 100644 src/app/api/user/interface.ts rename src/app/api/user/{route.js => route.ts} (84%) create mode 100644 src/components/Header.jsx delete mode 100644 src/pages/UserProfile.jsx diff --git a/src/app/[username]/badge/page.jsx b/src/app/[username]/badge/page.jsx deleted file mode 100644 index 2c50677..0000000 --- a/src/app/[username]/badge/page.jsx +++ /dev/null @@ -1,328 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import Canvas from "@/components/Canvas"; -import { useRouter } from "next/navigation"; - -export default function Badge({ params }) { - const username = params.username; - const router = useRouter(); - const [userData, setUserData] = useState(null); - const [loading, setLoading] = useState(true); // Loading state - const [Tagline, setTagline] = useState(""); - const [taglineGenerated, setTaglineGenerated] = useState(false); - const [userFetched, setUserFetched] = useState(false); - const Languages = {}; - const Description = {}; - const Stars = {}; - const Forks = {}; - const Issues = {}; - const [config, setConfig] = useState({ - theme: "dark", - font: "helvetica", - pattern: "shape 1", - update: "14", // future implementation - image: "", - username: true, - tagline: true, - lang: false, // future implementation - star: false, - fork: false, - issue: false, - UserName: username, - Tagline: Tagline, - star_count: 0, - fork_count: 0, - issue_count: 0, - }); - - // Utility function to update the URL - const updateURL = () => { - const params = new URLSearchParams(); - - // List of keys to exclude from the URL - const excludeKeys = ["username", "tagline", "lang", "UserName", "Tagline", "star_count", "fork_count", "issue_count"]; - - Object.entries(config).forEach(([key, value]) => { - if (!excludeKeys.includes(key) && value !== false && value !== "" && value !== null) { - params.set(key, value); - } - }); - - router.replace(`/${username}/badge?${params.toString()}`, undefined, { shallow: true }); - }; - - // Handle changes to config and update the URL - useEffect(() => { - updateURL(); - }, [config]); - - const handleChange = (e) => { - const { name, value, type, checked } = e.target; - setConfig((prev) => ({ - ...prev, - [name]: type === "checkbox" ? checked : value, - })); - }; - - const handleUser = async () => { - try { - setLoading(true); // Set loading to true while fetching data - const res = await fetch("../api/github_profile", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ username }), - }); - - const data = await res.json(); - if (res.ok) { - setUserData(data); - } else { - setUserData(new Error("Failed to fetch user data.")); - } - } catch (error) { - setUserData(new Error("Failed to fetch user data.")); - } finally { - setLoading(false); // Data loading complete - } - }; - - useEffect(() => { - if (!userFetched) { - handleUser(); - setUserFetched(true); - } - }, [userFetched]); - - useEffect(() => { - if (userData !== null && !loading) { - userData.forEach((item) => { - if (item.language !== null) { - Languages[item.language] = (Languages[item.language] || 0) + 1; - } - - if (item.description !== null) { - Description[item.name] = item.description; - } - if (item.stargazers_count !== null) { - Stars[item.name] = item.stargazers_count; - } - if (item.forks_count !== null) { - Forks[item.name] = item.forks_count; - } - if (item.open_issues_count !== null) { - Issues[item.name] = item.open_issues_count; - } - }); - - const stars = userData.reduce( - (acc, item) => acc + (item.stargazers_count || 0), - 0 - ); - const forks = userData.reduce( - (acc, item) => acc + (item.forks_count || 0), - 0 - ); - const issues = userData.reduce( - (acc, item) => acc + (item.open_issues_count || 0), - 0 - ); - - setConfig((prevConfig) => ({ - ...prevConfig, - star_count: stars, - fork_count: forks, - issue_count: issues, - })); - } - }, [userData, loading]); - - const handleGenerate = async () => { - const prompt = `Generate a unique tagline for ${username} based on their GitHub profile activity: - - The user works with the following languages: ${Object.keys(Languages).join( - ", " - )} - Repo highlights: - ${Object.entries(Description) - .map(([repo, desc]) => `${repo}: ${desc}`) - .join(", ")} - Total Stars: ${config.star_count}, Total Forks: ${config.fork_count}, Total Issues: ${config.issue_count} - - The tagline should summarize their impact and versatility in 100 characters or less, in a meaningful tone. - - Only generate one tagline.`; - - - try { - const res = await fetch("../api/ai", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt }), - }); - - const data = await res.json(); - if (data.success) { - setTagline(data.response); - } else { - console.warn("Error generating tagline!", data.error) - setTagline(`Error: ${data.error}`); - } - } catch (error) { - console.error("Error in tagline generation", error) - setTagline("Error communicating with the server."); - } - }; - - useEffect(() => { - if (!loading && userData !== null && !taglineGenerated) { - handleGenerate(); // Trigger tagline generation only after data is loaded - setTaglineGenerated(true); - } - }, [loading, userData]); - - useEffect(() => { - setConfig((prevConfig) => ({ - ...prevConfig, - Tagline, // Sync the tagline to the config - })); - }, [Tagline]); - - - return ( -
-
- - - - -

Gityzer

-
-
-
- {/* Conditionally render Canvas only if tagline exists */} - {Tagline && Tagline.trim() ? ( - - ) : ( -

Loading your badge...

- )} -
-
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
- - -
-
- - -
-
- - -
- {/* Add more checkboxes similar to this for other fields */} -
-
-
-
- ); -} diff --git a/src/app/[username]/page.jsx b/src/app/[username]/page.jsx index 32b923e..d17c109 100644 --- a/src/app/[username]/page.jsx +++ b/src/app/[username]/page.jsx @@ -1,9 +1,311 @@ -'use client' -import UserProfile from '@/pages/UserProfile' +"use client"; -function UserPage({ params }) { - const { username } = params - return -} +import { useEffect, useState } from "react"; +import Canvas from "@/components/Canvas"; +import { useRouter } from "next/navigation"; + +export default function Badge({ params }) { + const username = params.username; + const router = useRouter(); + const [userData, setUserData] = useState(null); // user's github data + const [loading, setLoading] = useState(true); // Loading state + const [tagline, setTagline] = useState(""); + const [taglineGenerated, setTaglineGenerated] = useState(false); + const [userFetched, setUserFetched] = useState(false); + const Languages = {}; + const Description = {}; + const Stars = {}; + const Forks = {}; + const Issues = {}; + const [config, setConfig] = useState({ + theme: "dark", + font: "helvetica", + pattern: "shape 1", + update: "14", // future implementation + image: "", + username: true, + tagline: true, + lang: false, // future implementation + star: false, + fork: false, + issue: false, + UserName: username, + Tagline: tagline, + star_count: 0, + fork_count: 0, + issue_count: 0, + }); + + // Utility function to update the URL + const updateURL = () => { + const params = new URLSearchParams(); + + // List of keys to exclude from the URL + const excludeKeys = ["username", "tagline", "lang", "UserName", "Tagline", "star_count", "fork_count", "issue_count"]; + + Object.entries(config).forEach(([key, value]) => { + if (!excludeKeys.includes(key) && value !== false && value !== "" && value !== null) { + params.set(key, value); + } + }); + + router.replace(`/${username}?${params.toString()}`, undefined, { shallow: true }); + }; + + // Handle changes to config and update the URL + useEffect(() => { + updateURL(); + }, [config]); + + const handleChange = (e) => { + const { name, value, type, checked } = e.target; + setConfig((prev) => ({ + ...prev, + [name]: type === "checkbox" ? checked : value, + })); + }; + + const handleUser = async () => { + try { + setLoading(true); // Set loading to true while fetching data + const res = await fetch("../api/github_profile", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username }), + }); + + const data = await res.json(); + if (res.ok) { + setUserData(data); + } else { + setUserData(new Error("Failed to fetch user data.")); + } + } catch (error) { + setUserData(new Error("Failed to fetch user data.")); + } finally { + setLoading(false); // Data loading complete + } + }; + + useEffect(() => { + if (!userFetched) { + handleUser(); + setUserFetched(true); + } + }, [userFetched]); + + useEffect(() => { + if (userData !== null && !loading) { + userData.forEach((item) => { + if (item.language !== null) { + Languages[item.language] = (Languages[item.language] || 0) + 1; + } + + if (item.description !== null) { + Description[item.name] = item.description; + } + if (item.stargazers_count !== null) { + Stars[item.name] = item.stargazers_count; + } + if (item.forks_count !== null) { + Forks[item.name] = item.forks_count; + } + if (item.open_issues_count !== null) { + Issues[item.name] = item.open_issues_count; + } + }); -export default UserPage + const stars = userData.reduce( + (acc, item) => acc + (item.stargazers_count || 0), + 0 + ); + const forks = userData.reduce( + (acc, item) => acc + (item.forks_count || 0), + 0 + ); + const issues = userData.reduce( + (acc, item) => acc + (item.open_issues_count || 0), + 0 + ); + + setConfig((prevConfig) => ({ + ...prevConfig, + star_count: stars, + fork_count: forks, + issue_count: issues, + })); + } + }, [userData, loading]); + + const handleGenerate = async () => { + const prompt = `Generate a unique tagline for ${username} based on their GitHub profile activity: + + The user works with the following languages: ${Object.keys(Languages).join( + ", " + )} + Repo highlights: + ${Object.entries(Description) + .map(([repo, desc]) => `${repo}: ${desc}`) + .join(", ")} + Total Stars: ${config.star_count}, Total Forks: ${config.fork_count}, Total Issues: ${config.issue_count} + + The tagline should summarize their impact and versatility in 100 characters or less, in a meaningful tone. + + Only generate one tagline.`; + + + try { + const res = await fetch("../api/ai", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ prompt }), + }); + + const data = await res.json(); + if (data.success) { + setTagline(data.response); + } else { + console.warn("Error generating tagline!", data.error) + setTagline(`Error: ${data.error}`); + } + } catch (error) { + console.error("Error in tagline generation", error) + setTagline("Error communicating with the server."); + } + }; + + useEffect(() => { + if (!loading && userData !== null && !taglineGenerated) { + handleGenerate(); // Trigger tagline generation only after data is loaded + setTaglineGenerated(true); + } + }, [loading, userData]); + + useEffect(() => { + setConfig((prevConfig) => ({ + ...prevConfig, + tagline, // Sync the tagline to the config + })); + }, [tagline]); + + + return ( +
+
+ {/* Conditionally render Canvas only if tagline exists */} + {tagline && tagline.trim() ? ( + + ) : ( +

Loading your badge...

+ )} +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+ {/* Add more checkboxes similar to this for other fields */} +
+
+
+
+ ); +} diff --git a/src/app/api/ai/route.ts b/src/app/api/ai/route.ts index cc273a3..e8f1db3 100644 --- a/src/app/api/ai/route.ts +++ b/src/app/api/ai/route.ts @@ -1,7 +1,7 @@ import { GoogleGenerativeAI } from "@google/generative-ai"; import { NextResponse } from "next/server"; -export const POST = async (request: Request) => { +export const POST = async (request: Request): Promise => { const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!); try{ diff --git a/src/app/api/user/interface.ts b/src/app/api/user/interface.ts new file mode 100644 index 0000000..a53b053 --- /dev/null +++ b/src/app/api/user/interface.ts @@ -0,0 +1,3 @@ +export interface RequestBody { + username: string +} \ No newline at end of file diff --git a/src/app/api/user/route.js b/src/app/api/user/route.ts similarity index 84% rename from src/app/api/user/route.js rename to src/app/api/user/route.ts index 58546bd..55d4596 100644 --- a/src/app/api/user/route.js +++ b/src/app/api/user/route.ts @@ -1,8 +1,9 @@ import { NextResponse } from 'next/server' +import { RequestBody } from './interface' -export async function POST(req) { +export async function POST(req: Request): Promise { // Parse the request body to extract the username - const { username } = await req.json() + const { username }: RequestBody = await req.json() if (!username) { return new NextResponse('Username required', { status: 400 }) @@ -22,7 +23,7 @@ export async function POST(req) { } const data = await response.json() - console.log(data) + // console.log(data) // If the user exists, return a response with `exists: true` if (data && data.login) { diff --git a/src/app/layout.js b/src/app/layout.js index 6a2ef89..2eb874f 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -1,5 +1,6 @@ import localFont from 'next/font/local' import './globals.css' +import Header from '@/components/Header' const geistSans = localFont({ src: './fonts/GeistVF.woff', @@ -63,6 +64,7 @@ export default function RootLayout({ children }) { return ( +
{children} diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 0000000..a7a066c --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,23 @@ +import React from 'react' + +export default function Header() { + return ( +
+ + + + +

Gityzer

+
+
+ ) +} diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 5ade066..32fae04 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -61,25 +61,6 @@ function Home() { <>
- {/* Header */} -
- - - - -

Gityzer

-
-
- {/* Main Content */}
<> diff --git a/src/pages/UserProfile.jsx b/src/pages/UserProfile.jsx deleted file mode 100644 index a72246e..0000000 --- a/src/pages/UserProfile.jsx +++ /dev/null @@ -1,294 +0,0 @@ -'use client' -import { useRouter } from 'next/navigation' -import { useEffect, useState } from 'react' -import NotFound from '@/pages/NotFound' -import Image from 'next/image' -import { FaMapPin, FaClock, FaGithub, FaLink, FaDownload, FaBackward, FaPlusCircle } from 'react-icons/fa' -import { IoArrowBackCircle } from "react-icons/io5"; -import React from 'react' - -export default function UserProfile({ username }) { - 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(false) - const [is404, setIs404] = useState(false) - - useEffect(() => { - 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 - } - } - - if (username !== 'contributors') 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) - } - - const handlePreviousPage = () => { - if (currentPage > 1) setCurrentPage(currentPage - 1) - } - - const handleThemeChange = (e) => { - const newTheme = e.target.value - setTheme(newTheme) - updateUrl(newTheme, textColor) - } - - const handleTextColorChange = (e) => { - const newTextColor = e.target.value - setTextColor(newTextColor) - updateUrl(theme, newTextColor) - } - - const updateUrl = (theme, color) => { - const queryString = `?theme=${encodeURIComponent(theme)}&color=${encodeURIComponent(color)}` - router.push(`/${username}${queryString}`) - } - - const handleBadgeCreation = () => { - window.location.href = `${window.location.origin}${window.location.pathname}/badge`; - } - - // Function to download repo stats in .md format - const downloadRepoStats = (repo) => { - const statsMarkdown = `# Repository: ${repo.name} - - **Description**: ${repo.description || 'No description available'} - - ## Stats: - - **Stars**: ${repo.stargazers_count} - - **Forks**: ${repo.forks_count} - - **Open Issues**: ${repo.open_issues_count} - - **Language**: ${repo.language || 'Unknown'} - - ## Links: - - **Repository URL**: [${repo.html_url}](${repo.html_url}) - - **Created At**: ${new Date(repo.created_at).toLocaleDateString()} - - **Last Updated**: ${new Date(repo.updated_at).toLocaleDateString()} - ` - - const blob = new Blob([statsMarkdown], { type: 'text/markdown' }) - const link = document.createElement('a') - link.href = URL.createObjectURL(blob) - link.download = `${repo.name}_stats.md` - link.click() - } - - // Function to download profile stats in .md format with additional info - const downloadProfileStats = () => { - const profileMarkdown = `# GitHub Profile: ${userData.name || 'Unknown User'} - - **Username**: ${userData.login} - - **Bio**: ${userData.bio || 'No bio available'} - - **Location**: ${userData.location || 'Unknown'} - - **Company**: ${userData.company || 'Unknown'} - - **Email**: ${userData.email || 'Not provided'} - - **Followers**: ${userData.followers} - - **Following**: ${userData.following} - - **Public Repositories**: ${userData.public_repos} - - **Public Gists**: ${userData.public_gists} - - **Account Created On**: ${new Date(userData.created_at).toLocaleDateString()} - - **Last Updated On**: ${new Date(userData.updated_at).toLocaleDateString()} - - --- - - ## Additional Information: - - - **URL**: [GitHub Profile Link](https://github.com/${userData.login}) - - **Profile Image**: ![Profile Image](${userData.avatar_url}) - ` - - const blob = new Blob([profileMarkdown], { type: 'text/markdown' }) - const link = document.createElement('a') - link.href = URL.createObjectURL(blob) - link.download = `${userData.login}_profile_stats.md` - link.click() - } - - if (is404) { - return - } - - if (isLoading) { - return ( -
-
-
- ) - } - return ( -
-
- - - - -

Gityzer

-
-
-
-
- {userData && ( -
- Try Another Profile -
-
- Profile -
-
-

- {userData.name || 'Unknown User'} -

-

@{userData.login || 'username'}

-
-

- - {userData.location || 'Unknown'} -

-

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

-
-
-
- - {userData.followers} followers -
-
- - {userData.following} following -
-
-
-
- -
-
- -
-

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

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

- - {repo.name} - -

-

- {repo.description || 'No description available.'} -

-
- {repo.language || 'Unknown'} - ★ {repo.stargazers_count || 0} -
- -
- ))} -
- -
- - - Page {currentPage} of {totalPages} - - -
-
-
- )} -
-
- ) -}