diff --git a/app.js b/app.js index 0a88df8..266cd4a 100644 --- a/app.js +++ b/app.js @@ -47,18 +47,17 @@ app.get("/profile", (req, res) => { if (!req.session.user) { return res.render("pages/login", { client_id: process.env.GITHUB_CLIENT_ID }); } - console.log(req.session.user) return res.render("pages/profile", { userData: req.session.user }); }); +app.get("/user/github/:username", (req, res) => { + const username = req.params.username; + res.render("pages/user-profile", { username: username }) +}); // Token fetching stuff app.get("/token/:service", (req, res) => { const service = req.params.service; - console.log( - service.toUpperCase() + "_TOKEN", - process.env[service.toUpperCase() + "_TOKEN"], - ); res.send(process.env[service.toUpperCase() + "_TOKEN"] || "No token found"); }); diff --git a/public/pages/gh-username-search.ejs b/public/pages/gh-username-search.ejs index b0631d8..95511a2 100644 --- a/public/pages/gh-username-search.ejs +++ b/public/pages/gh-username-search.ejs @@ -5,16 +5,14 @@ - - GitHub Username Search | FetchCV - +
@@ -27,73 +25,13 @@
- + person_search
- - - - - -
- - - - - -
-

Created with FetchCV

- -
- - - - - - + diff --git a/public/pages/user-profile.ejs b/public/pages/user-profile.ejs new file mode 100644 index 0000000..cebc0bd --- /dev/null +++ b/public/pages/user-profile.ejs @@ -0,0 +1,103 @@ + + + + + + + + + + + + + My Profile | FetchCV + + + +
+ +
+ +
+ home +
+
+ +
+ + person_search +
+ +
+ + + + + +
+ + + + + +
+

Created with FetchCV

+ +
+
+ + + + + + + + diff --git a/public/scripts-src/github-username-search.ts b/public/scripts-src/github-username-search.ts new file mode 100644 index 0000000..be799ec --- /dev/null +++ b/public/scripts-src/github-username-search.ts @@ -0,0 +1,21 @@ +//=============================================== +// Username search +//=============================================== + +function newUsername() { + let usernameElement = document.getElementById("username") as HTMLInputElement; + if (usernameElement) { + username = usernameElement.value; + window.location.href = window.location.origin + "/user/github/" + username; + } + } + + const usernameInput = document.getElementById("username"); + if (usernameInput) { + usernameInput.addEventListener("keydown", function (event) { + if (event.key == "Enter") { + newUsername(); + } + }); + } + \ No newline at end of file diff --git a/public/scripts/gitlab.ts b/public/scripts-src/gitlab.ts similarity index 100% rename from public/scripts/gitlab.ts rename to public/scripts-src/gitlab.ts diff --git a/public/scripts/profile-github.ts b/public/scripts-src/profile-github.ts similarity index 100% rename from public/scripts/profile-github.ts rename to public/scripts-src/profile-github.ts diff --git a/public/scripts/profile.ts b/public/scripts-src/profile.ts similarity index 100% rename from public/scripts/profile.ts rename to public/scripts-src/profile.ts diff --git a/public/scripts-src/user-profile-github.ts b/public/scripts-src/user-profile-github.ts new file mode 100644 index 0000000..a6db293 --- /dev/null +++ b/public/scripts-src/user-profile-github.ts @@ -0,0 +1,243 @@ +interface UserData { + avatar_url: string, + name: string, + login: string, + bio: string, + public_repos: number, + followers: number, + following: number, + location: string, + email: string, + blog: string, + html_url: string + + } + + let userData: UserData; // global variable + let totalStars: number = 0; + let languages: [string, number][] = []; + let TOKEN: string; + let tokenRecieved: boolean = false; + let header: RequestInit | undefined = {}; + + getToken("github"); + + async function getToken(service: string) { + console.log("Fetching token..."); + const response = await fetch(`/token/${service}`); + if (response.ok) { + const token = await response.text() as string; + tokenRecieved = true; + console.log("Token received."); + TOKEN = token; + header = { + 'headers': { + 'Authorization': `token ${TOKEN}` + } + } + } else { + showError("Token Error", "Failed to fetch token. Try reloading the page.", "bg-red-800"); + console.error('Error fetching token:', response.statusText); + } + } + + + + + async function getUserInfo() { + if (!tokenRecieved) { + console.log('Waiting for token to be received...'); + setTimeout(getUserInfo, 200); + return; + } + + try { + console.log(header); + const response = await fetch(`https://api.github.com/users/${username}`, header); + if (response.ok) { + userData = await response.json(); + updateData(); + updateGithubStats(); + await getRepoData(); + } else if (response.status === 404) { + showError("Oh no!", "User does not exist. Try another username.", "bg-red-800"); + } else { + showError("Connection Error", "Failed to fetch user data. Try reloading the page.", "bg-red-800"); + console.error("Failed to fetch user data. Status:", response.status, response); + } + } + catch (error) { + showError("Connection Error", "Could not request user data.", "bg-red-800"); + console.error("Failed to request user data. Error - ", error); + } + } + + async function getRepoData() { + try { + const response = await fetch(`https://api.github.com/users/${username}/repos`, header); + const repos = await response.json(); + + getRepoStars(repos); + getRepoLangs(repos); + } catch (error) { + showError("Oh no!", "Could not get user repository data.", "bg-red-800"); + console.error('Error fetching data:', error); + } + } + + function updateData() { + if (!userData) { + showError("Oh no!", "Could get the user data, my bad", "bg-red-800"); + return; + } + (document.querySelector(".user-info") as HTMLElement).classList.remove("hidden"); + (document.querySelector(".profile-picture") as HTMLImageElement).src = userData.avatar_url; + (document.querySelector(".profile-name") as HTMLElement).textContent = userData.name; + (document.querySelector(".profile-handle") as HTMLElement).textContent = userData.login; + (document.querySelector(".profile-desc") as HTMLElement).textContent = userData.bio; + + (document.querySelector(".profile-repos") as HTMLElement).textContent = userData.public_repos.toString(); + + (document.querySelector(".profile-followers") as HTMLElement).textContent = userData.followers.toString(); + (document.querySelector(".profile-following") as HTMLElement).textContent = userData.following.toString(); + + + (document.querySelector(".profile-location") as HTMLElement).textContent = userData.location; + (document.querySelector(".profile-email") as HTMLElement).textContent = userData.email; + (document.querySelector(".profile-website") as HTMLAnchorElement).textContent = "Personal Website"; + (document.querySelector(".profile-github") as HTMLAnchorElement).textContent = "Github Profile"; + (document.querySelector(".profile-website") as HTMLAnchorElement).href = userData.blog; + (document.querySelector(".profile-github") as HTMLAnchorElement).href = userData.html_url; + } + + async function getRepoStars(repos: any[]) { + for (const repo of repos) { + const repoResponse = await fetch(`https://api.github.com/repos/${repo.owner.login}/${repo.name}`, header); + const repoData = await repoResponse.json(); + totalStars += repoData.stargazers_count; + } + + (document.querySelector(".profile-stars") as HTMLElement).textContent = totalStars.toString(); + return totalStars; + } + + async function getRepoLangs(repos: any[]) { + let langs: { [key: string]: number } = {}; + languages = []; + + for (const repo of repos) { + const repoResponse = await fetch(`https://api.github.com/repos/${repo.owner.login}/${repo.name}/languages`, header); + const repoData = await repoResponse.json(); + + for (const lang in repoData) { + if (langs.hasOwnProperty(lang)) { + langs[lang] += repoData[lang]; + } else { + langs[lang] = repoData[lang]; + } + } + } + + let sortedLangs: [string, number][] = []; + for (let lang in langs) { + languages.push([lang, langs[lang]] as [string, number]); + } + + languages.sort(function (a, b) { + return b[1] - a[1]; + }); + + const total = languages.reduce((acc, curr) => acc + curr[1], 0); + const percentLanguages: [string, number][] = languages.map(lang => [lang[0], Math.round((lang[1] / total) * 100)]); + + generateLanguageElements(percentLanguages); + + return languages; + } + + function generateLanguageElements(languages: [string, number][]) { + const langList = document.querySelector(".profile-langs"); + if (langList) { + langList.innerHTML = ""; + for (const lang of languages) { + const langElement = document.createElement("li"); + langElement.textContent = `${lang[0]}: ${lang[1]}%`; + langElement.classList.add("inline-block", "bg-zinc-200", "dark:bg-zinc-700", "px-2", "m-1", "py-1", "rounded-lg", "border-[1px]", "border-zinc-400", "dark:border-zinc-700", "border-t-zinc-300", "dark:border-t-zinc-600", "inline-flex", "items-center"); + langElement.innerHTML = ` ` + langElement.textContent; + langList.appendChild(langElement); + } + } + function getLangIcon(lang: string) { + switch (lang) { + case "JavaScript": + return "javascript"; + case "TypeScript": + return "typescript"; + case "EJS": + case "HTML": + return "html5"; + case "CSS": + return "css3"; + case "Python": + return "python"; + case "Java": + return "java"; + case "C": + return "c"; + case "C++": + return "cplusplus"; + case "Lua": + return "lua"; + case "Shell": + return "bash"; + case "Ruby": + return "ruby"; + case "PHP": + return "php"; + case "Swift": + return "swift"; + case "Go": + return "go"; + case "Rust": + return "rust"; + case "Kotlin": + return "kotlin"; + case "GDScript": + return "godot"; + case "QML": + return "Qt"; + default: + return "gimp hidden"; + } + } +} + + function updateGithubStats() { + console.log(`Username: ${username}`); + const githubStatsElement = document.querySelector(".github-stats"); + if (githubStatsElement) { + githubStatsElement.innerHTML = ` + + + + `; + } + } + + async function getRateLimit() { + if (header) { + const response = await fetch('https://api.github.com/rate_limit', header); + + if (response.ok) { + const data = await response.json(); + } else { + console.error('Error fetching rate limit:', response.statusText); + } + } + } diff --git a/public/scripts/main.ts b/public/scripts-src/user-profile.ts similarity index 82% rename from public/scripts/main.ts rename to public/scripts-src/user-profile.ts index 7a31d65..19debb9 100644 --- a/public/scripts/main.ts +++ b/public/scripts-src/user-profile.ts @@ -4,14 +4,7 @@ // First check for username window.addEventListener("load", () => { - if (localStorage.getItem("savedUsername")) { - username = JSON.parse(localStorage.getItem("savedUsername") as string); - getUserInfo(); - } - else { - console.log("No username saved."); - showError("No username", "Please enter a username.", "bg-blue-800"); - } + getUserInfo(); }); @@ -24,7 +17,7 @@ function newUsername() { let usernameElement = document.getElementById("username") as HTMLInputElement; if (usernameElement) { username = usernameElement.value; - localStorage.setItem("savedUsername", JSON.stringify(username)); + window.location.href = window.location.origin + "/user/github/" + username; getUserInfo(); } } @@ -69,4 +62,4 @@ function hideError() { if (errorBox) { errorBox.classList.add("hidden"); } -} \ No newline at end of file +} diff --git a/public/scripts/github-username-search.js b/public/scripts/github-username-search.js new file mode 100644 index 0000000..6f51ae8 --- /dev/null +++ b/public/scripts/github-username-search.js @@ -0,0 +1,18 @@ +//=============================================== +// Username search +//=============================================== +function newUsername() { + let usernameElement = document.getElementById("username"); + if (usernameElement) { + username = usernameElement.value; + window.location.href = window.location.origin + "/user/github/" + username; + } +} +const usernameInput = document.getElementById("username"); +if (usernameInput) { + usernameInput.addEventListener("keydown", function (event) { + if (event.key == "Enter") { + newUsername(); + } + }); +} diff --git a/public/scripts/github.ts b/public/scripts/github.ts deleted file mode 100644 index b1a030a..0000000 --- a/public/scripts/github.ts +++ /dev/null @@ -1,242 +0,0 @@ -interface UserData { - avatar_url: string, - name: string, - login: string, - bio: string, - public_repos: number, - followers: number, - following: number, - location: string, - email: string, - blog: string, - html_url: string - -} - -let userData: UserData; // global variable -let username: string = ""; -let totalStars: number = 0; -let languages: [string, number][] = []; -let TOKEN: string; -let tokenRecieved: boolean = false; -let header: RequestInit | undefined = {}; - -getToken("github"); - -async function getToken(service: string) { - console.log("Fetching token..."); - const response = await fetch(`/token/${service}`); - if (response.ok) { - const token = await response.text() as string; - tokenRecieved = true; - console.log("Token received."); - TOKEN = token; - header = { - 'headers': { - 'Authorization': `token ${TOKEN}` - } - } - } else { - showError("Token Error", "Failed to fetch token. Try reloading the page.", "bg-red-800"); - console.error('Error fetching token:', response.statusText); - } -} - - - - -async function getUserInfo() { - if (!tokenRecieved) { - console.log('Waiting for token to be received...'); - setTimeout(getUserInfo, 200); - return; - } - - try { - console.log(header); - const response = await fetch(`https://api.github.com/users/${username}`, header); - if (response.ok) { - userData = await response.json(); - updateData(); - updateGithubStats(); - await getRepoData(); - } else if (response.status === 404) { - showError("Oh no!", "User does not exist. Try another username.", "bg-red-800"); - } else { - showError("Connection Error", "Failed to fetch user data. Try reloading the page.", "bg-red-800"); - console.error("Failed to fetch user data. Status:", response.status, response); - } - } - catch (error) { - showError("Connection Error", "Could not request user data.", "bg-red-800"); - console.error("Failed to request user data. Error - ", error); - } -} - -async function getRepoData() { - try { - const response = await fetch(`https://api.github.com/users/${username}/repos`, header); - const repos = await response.json(); - - getRepoStars(repos); - getRepoLangs(repos); - } catch (error) { - showError("Oh no!", "Could not get user repository data.", "bg-red-800"); - console.error('Error fetching data:', error); - } -} - -function updateData() { - if (!userData) { - showError("Oh no!", "Could get the user data, my bad", "bg-red-800"); - return; - } - (document.querySelector(".user-info") as HTMLElement).classList.remove("hidden"); - (document.querySelector(".profile-picture") as HTMLImageElement).src = userData.avatar_url; - (document.querySelector(".profile-name") as HTMLElement).textContent = userData.name; - (document.querySelector(".profile-handle") as HTMLElement).textContent = userData.login; - (document.querySelector(".profile-desc") as HTMLElement).textContent = userData.bio; - - (document.querySelector(".profile-repos") as HTMLElement).textContent = userData.public_repos.toString(); - - (document.querySelector(".profile-followers") as HTMLElement).textContent = userData.followers.toString(); - (document.querySelector(".profile-following") as HTMLElement).textContent = userData.following.toString(); - - - (document.querySelector(".profile-location") as HTMLElement).textContent = userData.location; - (document.querySelector(".profile-email") as HTMLElement).textContent = userData.email; - (document.querySelector(".profile-website") as HTMLAnchorElement).textContent = "Personal Website"; - (document.querySelector(".profile-github") as HTMLAnchorElement).textContent = "Github Profile"; - (document.querySelector(".profile-website") as HTMLAnchorElement).href = userData.blog; - (document.querySelector(".profile-github") as HTMLAnchorElement).href = userData.html_url; -} - -async function getRepoStars(repos: any[]) { - for (const repo of repos) { - const repoResponse = await fetch(`https://api.github.com/repos/${repo.owner.login}/${repo.name}`, header); - const repoData = await repoResponse.json(); - totalStars += repoData.stargazers_count; - } - - (document.querySelector(".profile-stars") as HTMLElement).textContent = totalStars.toString(); - return totalStars; -} - -async function getRepoLangs(repos: any[]) { - let langs: { [key: string]: number } = {}; - languages = []; - - for (const repo of repos) { - const repoResponse = await fetch(`https://api.github.com/repos/${repo.owner.login}/${repo.name}/languages`, header); - const repoData = await repoResponse.json(); - - for (const lang in repoData) { - if (langs.hasOwnProperty(lang)) { - langs[lang] += repoData[lang]; - } else { - langs[lang] = repoData[lang]; - } - } - } - - let sortedLangs: [string, number][] = []; - for (let lang in langs) { - languages.push([lang, langs[lang]] as [string, number]); - } - - languages.sort(function (a, b) { - return b[1] - a[1]; - }); - - const total = languages.reduce((acc, curr) => acc + curr[1], 0); - const percentLanguages: [string, number][] = languages.map(lang => [lang[0], Math.round((lang[1] / total) * 100)]); - - generateLanguageElements(percentLanguages); - - return languages; -} - -function generateLanguageElements(languages: [string, number][]) { - const langList = document.querySelector(".profile-langs"); - if (langList) { - langList.innerHTML = ""; - for (const lang of languages) { - const langElement = document.createElement("li"); - langElement.textContent = `${lang[0]}: ${lang[1]}%`; - langElement.classList.add("inline-block", "bg-zinc-200", "dark:bg-zinc-700", "px-2", "m-1", "py-1", "rounded-lg", "border-[1px]", "border-zinc-400", "dark:border-zinc-700", "border-t-zinc-300", "dark:border-t-zinc-600", "inline-flex", "items-center"); - langElement.innerHTML = ` ` + langElement.textContent; - langList.appendChild(langElement); - } - } - function getLangIcon(lang: string) { - switch (lang) { - case "JavaScript": - return "javascript"; - case "TypeScript": - return "typescript"; - case "EJS": - case "HTML": - return "html5"; - case "CSS": - return "css3"; - case "Python": - return "python"; - case "Java": - return "java"; - case "C": - return "c"; - case "C++": - return "cplusplus"; - case "Lua": - return "lua"; - case "Shell": - return "bash"; - case "Ruby": - return "ruby"; - case "PHP": - return "php"; - case "Swift": - return "swift"; - case "Go": - return "go"; - case "Rust": - return "rust"; - case "Kotlin": - return "kotlin"; - case "GDScript": - return "godot"; - default: - return "gimp hidden"; - } - } -} - -function updateGithubStats() { - console.log(`Username: ${username}`); - const githubStatsElement = document.querySelector(".github-stats"); - if (githubStatsElement) { - githubStatsElement.innerHTML = ` - - - - `; - } -} - -async function getRateLimit() { - if (header) { - const response = await fetch('https://api.github.com/rate_limit', header); - - if (response.ok) { - const data = await response.json(); - } else { - console.error('Error fetching rate limit:', response.statusText); - } - } -} \ No newline at end of file diff --git a/public/scripts/github.js b/public/scripts/user-profile-github.js similarity index 92% rename from public/scripts/github.js rename to public/scripts/user-profile-github.js index bbf984c..ced6105 100644 --- a/public/scripts/github.js +++ b/public/scripts/user-profile-github.js @@ -1,5 +1,4 @@ let userData; // global variable -let username = ""; let totalStars = 0; let languages = []; let TOKEN; @@ -170,6 +169,8 @@ function generateLanguageElements(languages) { return "kotlin"; case "GDScript": return "godot"; + case "QML": + return "Qt"; default: return "gimp hidden"; } @@ -180,16 +181,16 @@ function updateGithubStats() { const githubStatsElement = document.querySelector(".github-stats"); if (githubStatsElement) { githubStatsElement.innerHTML = ` - - - - `; + + + + `; } } async function getRateLimit() { diff --git a/public/scripts/main.js b/public/scripts/user-profile.js similarity index 82% rename from public/scripts/main.js rename to public/scripts/user-profile.js index c8b337f..06cb515 100644 --- a/public/scripts/main.js +++ b/public/scripts/user-profile.js @@ -3,14 +3,7 @@ //=============================================== // First check for username window.addEventListener("load", () => { - if (localStorage.getItem("savedUsername")) { - username = JSON.parse(localStorage.getItem("savedUsername")); - getUserInfo(); - } - else { - console.log("No username saved."); - showError("No username", "Please enter a username.", "bg-blue-800"); - } + getUserInfo(); }); //=============================================== // Setting username @@ -20,7 +13,7 @@ function newUsername() { let usernameElement = document.getElementById("username"); if (usernameElement) { username = usernameElement.value; - localStorage.setItem("savedUsername", JSON.stringify(username)); + window.location.href = window.location.origin + "/user/github/" + username; getUserInfo(); } } diff --git a/public/styles/tailwind.css b/public/styles/tailwind.css index 526b10c..f66150c 100644 --- a/public/styles/tailwind.css +++ b/public/styles/tailwind.css @@ -679,6 +679,14 @@ video { height: 100%; } +.h-\[100v\] { + height: 100v; +} + +.h-\[100vw\] { + height: 100vw; +} + .w-\[100\%\] { width: 100%; } @@ -699,6 +707,14 @@ video { width: 100vw; } +.w-\[80vw\] { + width: 80vw; +} + +.w-\[400px\] { + width: 400px; +} + .max-w-\[45vw\] { max-width: 45vw; } @@ -1030,12 +1046,26 @@ video { } @media (min-width: 640px) { + .sm\:w-\[100vw\] { + width: 100vw; + } + + .sm\:w-\[80vw\] { + width: 80vw; + } + .sm\:px-0 { padding-left: 0px; padding-right: 0px; } } +@media (min-width: 768px) { + .md\:w-\[400px\] { + width: 400px; + } +} + @media (min-width: 1024px) { .lg\:px-\[20vw\] { padding-left: 20vw; diff --git a/tsconfig.json b/tsconfig.json index 92ff512..af5a1d2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "strictNullChecks": true, "strictFunctionTypes": true, "noEmit": false, - "noEmitOnError": false + "noEmitOnError": false, + "outDir": "public/scripts" }, "exclude": [ "node_modules",