From be500e3c97c29d066e4b0a51ed0781e416de255b Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Wed, 7 Aug 2024 22:13:22 +0530 Subject: [PATCH 01/23] feat: small fix --- client/src/app/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 4fef394..3d159ca 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -23,7 +23,6 @@ export default function Home() { const res = await fetch("/api/fetchdata"); const data = await res.json(); setDatas(data.data); - console.log(data.data); setLoading(false); } catch (error) { console.error("Error fetching messages:", error); From de9c45da4846bfefe4a2498bbd532fd1d40da1a7 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Fri, 9 Aug 2024 14:53:02 +0530 Subject: [PATCH 02/23] feat: Add error handling to generateStats function for improved user experience --- client/src/app/components/Card.tsx | 40 +++++++------ client/src/app/components/GenerateStats.tsx | 6 ++ client/src/app/components/SocialLinks.tsx | 65 +++++---------------- client/src/app/page.tsx | 52 +++++++++++++---- 4 files changed, 83 insertions(+), 80 deletions(-) diff --git a/client/src/app/components/Card.tsx b/client/src/app/components/Card.tsx index 3e8739b..335ca72 100644 --- a/client/src/app/components/Card.tsx +++ b/client/src/app/components/Card.tsx @@ -5,31 +5,37 @@ import SocialLinks from "./SocialLinks"; export const data = { profileData: { + username: "druv_kotwani", + rank: 263817, image: "https://assets.leetcode.com/users/avatars/avatar_1672478903.png", - fullName: "John Doe", - username: "johndoe", - rank: "203432", + fullName: "Dhruv Kotwani", }, aboutData: { - github: { link: "https://github.com/druvkotwani", text: "druvkotwani" }, - twitter: { link: "https://twitter.com/druv_kotwani", text: "druv_kotwani" }, + github: { + link: "https://github.com/druvkotwani", + text: "Github", + }, + website: { + link: "https://dhruvkotwani.vercel.app", + text: "Website", + }, linkedin: { link: "https://linkedin.com/in/dhruv-kotwani", - text: "dhruv-kotwani", + text: "LinkedIn", }, - website: { - link: "https://dhruvkotwani.me", - text: "dhruvkotwani.vercel.app", + twitter: { + link: "https://twitter.com/druv_kotwani", + text: "Twitter", }, }, - - totalSolved: 100, - easySolved: 50, - easyTotal: 100, - mediumSolved: 30, - mediumTotal: 50, - hardSolved: 20, - hardTotal: 50, + total: 3247, + easyTotal: 817, + mediumTotal: 1704, + hardTotal: 726, + totalSolved: 306, + easySolved: 175, + mediumSolved: 110, + hardSolved: 21, }; export default function Card({ userData = data, index }: any) { diff --git a/client/src/app/components/GenerateStats.tsx b/client/src/app/components/GenerateStats.tsx index 48af1af..14dc456 100644 --- a/client/src/app/components/GenerateStats.tsx +++ b/client/src/app/components/GenerateStats.tsx @@ -50,6 +50,12 @@ const GenerateStats = ({ showStats, setShowStats }: any) => { body: JSON.stringify(data), }); const result = await response.json(); + + if (!result.success) { + toast("👻 Username already exists in LeetBoard"); + return; + } + if (result.success) { setDatas([...datas, data]); toast("🥷Data added to the hall of fame"); diff --git a/client/src/app/components/SocialLinks.tsx b/client/src/app/components/SocialLinks.tsx index 53926ca..42da24e 100644 --- a/client/src/app/components/SocialLinks.tsx +++ b/client/src/app/components/SocialLinks.tsx @@ -4,25 +4,14 @@ import Image from "next/image"; import Link from "next/link"; import { useState } from "react"; const SocialLinks = ({ result, index }: any) => { - const [hovered, setHovered] = useState<string | null>(null); - const random = Math.floor(Math.random() * 1); - - function truncateText(text: any, maxLength: any) { - if (text && text.length > maxLength) { - return text.substring(0, maxLength) + "..."; - } - return text; - } return ( - <div className="mt-4 mb-2 flex flex-col justify-self-start"> + <div className="mt-2 mb-2 grid grid-cols-2 gap-2"> {/* <p className="flex"> - <iconify-icon icon="carbon:location" width="17" height="19" style={{ color: 'white', marginRight: '5px' }}></iconify-icon> + <iconify-icon icon="carbon:location" width="21" height="23" style={{ color: 'white', marginRight: '5px' }}></iconify-icon> <span className='text-sm text-[#BDBEC3] '>India</span> </p> */} {result?.website?.link && ( <Link - onMouseEnter={() => setHovered("website")} - onMouseLeave={() => setHovered(null)} target="_blank" rel="noreferrer" href={result?.website?.link} @@ -30,22 +19,15 @@ const SocialLinks = ({ result, index }: any) => { > <Image src="/assets/icons/globe.svg" - width={17} - height={19} + width={25} + height={23} alt="Website" - className="mr-1" + className="mr-1 hover:scale-125 ease-in transition-all transform duration-300 hover:rotate-180" /> - <span className="text-sm text-[#BDBEC3] "> - <p className={`${hovered === "website" ? "text-[#f7f7f7]" : ""}`}> - {truncateText(result?.website?.text, 10)} - </p> - </span> </Link> )} {result?.github?.link && ( <Link - onMouseEnter={() => setHovered("github")} - onMouseLeave={() => setHovered(null)} target="_blank" rel="noreferrer" href={result?.github?.link} @@ -53,22 +35,15 @@ const SocialLinks = ({ result, index }: any) => { > <Image src="/assets/icons/github.svg" - width={17} - height={19} + width={21} + height={23} alt="Github" - className="mr-1" + className="mr-1 hover:scale-125 ease-in transition-all transform duration-300 " /> - <span className="text-sm text-[#BDBEC3] "> - <p className={`${hovered === "github" ? "text-[#f7f7f7]" : ""}`}> - {truncateText(result?.github?.text, 10)} - </p> - </span> </Link> )} {result?.twitter?.link && ( <Link - onMouseEnter={() => setHovered("twitter")} - onMouseLeave={() => setHovered(null)} target="_blank" rel="noreferrer" href={result?.twitter?.link} @@ -77,21 +52,14 @@ const SocialLinks = ({ result, index }: any) => { <Image src="/assets/icons/twitter.svg" alt="twitter" - width={17} - height={19} - className="mr-1" + width={21} + height={23} + className="mr-1 hover:scale-125 ease-in transition-all transform duration-300 " /> - <span className="text-sm text-[#BDBEC3] "> - <p className={`${hovered === "twitter" ? "text-[#f7f7f7]" : ""}`}> - {truncateText(result?.twitter?.text, 10)} - </p> - </span> </Link> )} {result?.linkedin?.link && ( <Link - onMouseEnter={() => setHovered("linkedin")} - onMouseLeave={() => setHovered(null)} target="_blank" rel="noreferrer" href={result?.linkedin?.link} @@ -100,15 +68,10 @@ const SocialLinks = ({ result, index }: any) => { <Image src="/assets/icons/linkedin.svg" alt="linkedin" - width={17} - height={19} - className="mr-1" + width={21} + height={23} + className="mr-1 hover:scale-125 ease-in transition-all transform duration-300 " /> - <span className="text-sm text-[#BDBEC3] "> - <p className={`${hovered === "linkedin" ? "text-[#f7f7f7]" : ""}`}> - {truncateText(result?.linkedin?.text, 10)} - </p> - </span> </Link> )} </div> diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 3d159ca..32000ba 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -5,7 +5,7 @@ import Navbar from "./components/Navbar"; import Card from "./components/Card"; import Footer from "./components/Footer"; import GenerateStats from "./components/GenerateStats"; -import { useContext, useEffect, useState } from "react"; +import { use, useContext, useEffect, useState } from "react"; import PromotionCard from "./components/PromotionCard"; import { DataContext } from "./context/DataContext"; import { ToastContainer } from "react-toastify"; @@ -17,6 +17,7 @@ export default function Home() { const [loading, setLoading] = useState(true); const [search, setSearch] = useState<string>(""); const [showStats, setShowStats] = useState(false); + const [sortBy, setSortBy] = useState("default"); const fetchData = async () => { try { @@ -33,9 +34,32 @@ export default function Home() { fetchData(); }, [datas && datas.length]); + useEffect(() => { + setTimeout(() => { + setShowStats(true); + }, 5000); + }, []); + + const handleSortChange = (e: React.ChangeEvent<HTMLSelectElement>) => { + setSortBy(e.target.value); + }; + const searchedData = datas?.filter((data: any) => data.profileData.fullName.toLowerCase().includes(search.toLowerCase()) ); + const sorting = (data: any[]) => { + switch (sortBy) { + case "question-solved": + return data.slice().sort((a, b) => b.totalSolved - a.totalSolved); + case "default": + default: + return data.slice().sort((a, b) => { + return ( + new Date(b.timeStamp).getTime() - new Date(a.timeStamp).getTime() + ); + }); + } + }; return ( <section className="px-4 lg:px-24 relative "> @@ -55,7 +79,18 @@ export default function Home() { <GenerateStats showStats={showStats} setShowStats={setShowStats} /> - <div className="mt-32 max-w-7xl mx-auto place-items-center grid grid-cols-1 md:grid-cols-2 gap-y-8 xl:grid-cols-3 font-sourcecodepro gap-x-4"> + <div className="w-full flex items-center justify-center "> + <select + value={sortBy} + onChange={handleSortChange} + className="mt-28 rounded border-2 border-[#f7f7f7] w-64 bg-[#0e0e0e] text-white p-2 font-sourcecodepro" + > + <option value="default">Sort By Default</option> + <option value="question-solved">Sort By Questions Solved</option> + </select> + </div> + + <div className="mt-8 max-w-7xl mx-auto place-items-center grid grid-cols-1 md:grid-cols-2 gap-y-8 xl:grid-cols-3 font-sourcecodepro gap-x-4"> <PromotionCard /> {loading ? ( <> @@ -64,16 +99,9 @@ export default function Home() { ))} </> ) : ( - searchedData && - searchedData - .sort( - (a: any, b: any) => - new Date(b.timeStamp).getTime() - - new Date(a.timeStamp).getTime() - ) - .map((userData: any, index: number) => ( - <Card userData={userData} index={index} key={index} /> - )) + sorting(searchedData).map((userData: any, index: number) => ( + <Card userData={userData} index={index} key={index} /> + )) )} </div> From 36608ace9b245a7dc091188bbada7739024184ed Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 11 Aug 2024 21:10:41 +0530 Subject: [PATCH 03/23] favicon updated --- client/src/app/favicon.ico | Bin 25931 -> 3646 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/client/src/app/favicon.ico b/client/src/app/favicon.ico index 718d6fea4835ec2d246af9800eddb7ffb276240c..baf682e2df029fc6d41ce80895a0c5c3149768f3 100644 GIT binary patch literal 3646 zcmeHKOKVd>6u$ldU)2_D6{@0C5M7A)TquGY7oxax>rN2Gof`$$ZUj*`))$J3k0eE{ z#z-(}VnY|js$GcKXkufr#g^QbygtvDo3Zy^CfD3Z5yTVDow+A-zHiRV$()H{%#%+? zry<Wyqica-v>ArcCE6jnOO$rXnfZpXdV{4^gFMiHeE0#J9RRjJ1lq3yZP$S1H-OzC z;QSk4)NBBxfgxklz@@jqmIuI+s}>Krs7vL2nQ-ra3Z%X@0Om2IQ~`=*pdxL*V6DFk zEbazcM9JG)<7|;W=Mo?F0Q2~YJY`WOQw8Ag3*gW*;OHyhKrgWBmK7UgB==TX<368! zZXWWHYj^^fE?Mkm5qOfe_E4=Y+ik*^%&8fAw>lo3O`*INT;`0OZ|5W6z0Au?beKAu z8zUinj7RZt|1L^C)e{}2&Q{N#`$RtG&Pv(G-p~4xI!v8S?>%cB{0^KO1bRk*g5<AT zm}|tfOEWxn|9XAm+Nr#F%H=XjrIK%@AEjs`;&0X+qncwTlR+|>L_8k%t*KNBxm?bH zT`wlnG!cnJ5C{Yi3<jH0`rG4RpRu_mpU)#44*wAs{q0=L6Z^TOI4d%NbUKY#EQV+_ z>RS_u1hUy|^=ws`Y^$-^4Yq!<SVW;v@U6@z3bF06a0$Kms!mNlblA$~7n0|5_b_m> z4|thz{+1M$F4z6?ea!>M;#TckbVcf!oL3`Liox0H&JUf<>1DkQjROazp0Q4|ezIMA z8=$?O=&*G*<<Iqa{sq_}HAJ29t-{kTcahU6V5U~<)OdHR!{*t-HJX(DP%<$CTz&_v zyEDtfc-BZf7lr`yH#`c*otw0OoCHqw11DtPkBjc@vF1;FbqCmd9|*}A&-wZh9bZCf z)F#1OD0}~F&1(Cz?=dhi=7+&KRyRH7^2U4CU5fXq6*sNBPP$IcIT8K8g+2j$<Q{q? hYSqQO+;iqixW40(8@k#%SDgQG|Lqk}|HJ-P`x9t$#%TZm literal 25931 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 LrJugUA?W`A8`#=m From bf18d78fe6c3486ca87001c0dd575b47db1a65b8 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 11 Aug 2024 21:17:27 +0530 Subject: [PATCH 04/23] favicon updated --- client/src/app/favicon.ico | Bin 3646 -> 187116 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/client/src/app/favicon.ico b/client/src/app/favicon.ico index baf682e2df029fc6d41ce80895a0c5c3149768f3..c4042f8bbc8988724946f94ce9405ecd92c5dcef 100644 GIT binary patch literal 187116 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%0D6t8hGuWu_Y;*dEzImX|@Q; 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 D_GDsG literal 3646 zcmeHKOKVd>6u$ldU)2_D6{@0C5M7A)TquGY7oxax>rN2Gof`$$ZUj*`))$J3k0eE{ z#z-(}VnY|js$GcKXkufr#g^QbygtvDo3Zy^CfD3Z5yTVDow+A-zHiRV$()H{%#%+? zry<Wyqica-v>ArcCE6jnOO$rXnfZpXdV{4^gFMiHeE0#J9RRjJ1lq3yZP$S1H-OzC z;QSk4)NBBxfgxklz@@jqmIuI+s}>Krs7vL2nQ-ra3Z%X@0Om2IQ~`=*pdxL*V6DFk zEbazcM9JG)<7|;W=Mo?F0Q2~YJY`WOQw8Ag3*gW*;OHyhKrgWBmK7UgB==TX<368! zZXWWHYj^^fE?Mkm5qOfe_E4=Y+ik*^%&8fAw>lo3O`*INT;`0OZ|5W6z0Au?beKAu z8zUinj7RZt|1L^C)e{}2&Q{N#`$RtG&Pv(G-p~4xI!v8S?>%cB{0^KO1bRk*g5<AT zm}|tfOEWxn|9XAm+Nr#F%H=XjrIK%@AEjs`;&0X+qncwTlR+|>L_8k%t*KNBxm?bH zT`wlnG!cnJ5C{Yi3<jH0`rG4RpRu_mpU)#44*wAs{q0=L6Z^TOI4d%NbUKY#EQV+_ z>RS_u1hUy|^=ws`Y^$-^4Yq!<SVW;v@U6@z3bF06a0$Kms!mNlblA$~7n0|5_b_m> z4|thz{+1M$F4z6?ea!>M;#TckbVcf!oL3`Liox0H&JUf<>1DkQjROazp0Q4|ezIMA z8=$?O=&*G*<<Iqa{sq_}HAJ29t-{kTcahU6V5U~<)OdHR!{*t-HJX(DP%<$CTz&_v zyEDtfc-BZf7lr`yH#`c*otw0OoCHqw11DtPkBjc@vF1;FbqCmd9|*}A&-wZh9bZCf z)F#1OD0}~F&1(Cz?=dhi=7+&KRyRH7^2U4CU5fXq6*sNBPP$IcIT8K8g+2j$<Q{q? hYSqQO+;iqixW40(8@k#%SDgQG|Lqk}|HJ-P`x9t$#%TZm From e06a5f4139f59748e01cf6140bbd63c56e79cdd1 Mon Sep 17 00:00:00 2001 From: Dhruv Kotwani <96691139+druvkotwani@users.noreply.github.com> Date: Sun, 11 Aug 2024 21:25:08 +0530 Subject: [PATCH 05/23] Update README.md --- README.md | 68 +++++-------------------------------------------------- 1 file changed, 6 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 4074ca5..5782a6b 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ A platform to create your LeetCode statistics and showcase your LeetCode profile ## Languages/Tools <a href=""> - <img src="https://skillicons.dev/icons?i=tailwindcss,js,react,firebase,nodejs,vercel" /> + <img src="https://skillicons.dev/icons?i=tailwindcss,js,react,mongodb,nodejs,vercel,typescript" /> </a> ## 👩🏽💻 Demo -Check out the website: [𝙻𝚎𝚎𝚝𝙲𝚘𝚍𝚎 𝙿𝚛𝚘𝚏𝚒𝚕𝚎𝚜](https://leetcode-profiles.vercel.app/) +Check out the website: [𝙻𝚎𝚎𝚝𝙲𝚘𝚍𝚎 𝙿𝚛𝚘𝚏𝚒𝚕𝚎𝚜](https://leetcode-profiles-delta.vercel.app/) ## 🛠️ Installation Steps @@ -27,80 +27,24 @@ Check out the website: [𝙻𝚎𝚎𝚝𝙲𝚘𝚍𝚎 𝙿𝚛𝚘𝚏𝚒 - Enter the name of your new branch in the text box. (Branch names usually make a reference to what is being changed. Example: `FixMargin`). - Click on `Create branch <new branch name>` and this will automatically take you to your new branch. You can make edits on the main branch, but this may cause issues down the line. Best practice is to create a new branch for each separate issue you work on. That way, your `main` branch remains in sync with `leetcode-profiles` `main` branch. -### 3. Setup Firebase: -- Below are the steps to setup firebase. - -### 4. Edit: +### 3. Edit: - Do the desired changes, you want. -### 5. Raise a Pull Request: +### 4. Raise a Pull Request: - And finally, create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)! - Great job! -## Setting up Firebase - -1. **Firebase Account Setup:** - - Sign up for a free Firebase account at [Firebase Console](https://console.firebase.google.com/). - -2. **Create a Firebase Project:** - - Create a new Firebase project via the Firebase Console. - -3. **Add Firebase to your web app:** - - Set up the firebase for your webapp and then Add Firebase SDK. - -4. **Create Database(firestore Database)** - -5. **Create a collection with the file format below (not necessary):** - - - ### 👇🏽 File Format -- `userName(String) = druv_kotwani` -- `website(Map) = {link: https://dhruvkotwani.me/, text: dhruvkotwani.me}` -- `badgeImg(String) = https://leetcode.com/static/images/badges/dcc-2023-10.png` -- `mediumBeats(String) = 85.6%` -- `easyBeats(String) = 96.9%` -- `totalQuestions(Number) = 2940` -- `fullName(String) = Dhruv Kotwani` -- `github(Map) = {link: https://github.com/druvkotwani, text: druvkotwani}` -- `image(String) = https://assets.leetcode.com/users/avatars/avatar_1672478903.png` -- `hardBeats(String) = 77.62` -- `totalSolved(Number) = 282` -- `easySolved(String) = 165` -- `linkedin(Map) = {link: https://linkedin.com/in/dhruv-kotwani, text: dhruv-kotwani}` -- `rank(String) = 230,203` -- `easyTotal(String) = 746` -- `mediumSolved(String) = 97` -- `hardTotal(String) = 645` -- `hardSolved(String) = 20` -- `twitter(Map) = {link: https://twitter.com/druv_kotwani, text: druv_kotwani}` -- `mediumTotal(String) = 1549` - -6. **Replace the firebaseConfig inside firebase.js with your actual apiKeys, and rest of the data** -7. **Add now you are good to go** - ## 🚀 Running Frontend To run locally, just `cd` into the `client` and run the following commands to run node modules and serve the website locally. ```bash -npm i +yarn install ``` ```bash -npm run dev +yarn run dev ``` -## 🚀 Running Backend(only necessary if you had made changes in the api) -To run locally, just `cd` into the `server` and run the following commands to run node modules and serve the website locally. -```bash -npm i -``` - -```bash -node index.js -``` - - - <hr/> From fa02d80563b9a1f8a679d862972f889aef75b28b Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Tue, 27 Aug 2024 11:01:04 +0530 Subject: [PATCH 06/23] feat: Added vercel analytics --- client/package.json | 1 + client/src/app/layout.tsx | 2 ++ client/yarn.lock | 12 ++++++++++++ 3 files changed, 15 insertions(+) diff --git a/client/package.json b/client/package.json index 3cc214e..de05c21 100644 --- a/client/package.json +++ b/client/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@vercel/analytics": "^1.3.1", "axios": "^1.7.3", "mongoose": "^8.5.2", "next": "14.2.5", diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index ddd24ee..0ea16e1 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Inter, Source_Code_Pro } from "next/font/google"; import "./globals.css"; import { DataProvider } from "./context/DataContext"; +import { Analytics } from "@vercel/analytics/react"; const sourcecodepro = Source_Code_Pro({ subsets: ["latin"], @@ -22,6 +23,7 @@ export default function RootLayout({ <html lang="en"> <body className={sourcecodepro.variable}> <DataProvider>{children}</DataProvider> + <Analytics /> </body> </html> ); diff --git a/client/yarn.lock b/client/yarn.lock index f47bb94..abc16ae 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -305,6 +305,13 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@vercel/analytics@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@vercel/analytics/-/analytics-1.3.1.tgz#e2b1deac1b5d14fa2e4fe36186ac5054c6385ae4" + integrity sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA== + dependencies: + server-only "^0.0.1" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -2410,6 +2417,11 @@ semver@^7.5.4: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +server-only@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/server-only/-/server-only-0.0.1.tgz#0f366bb6afb618c37c9255a314535dc412cd1c9e" + integrity sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA== + set-function-length@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" From 3c27c273643723c824afcacec6d4c3b0453bb587 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2024 05:31:44 +0000 Subject: [PATCH 07/23] Bump micromatch from 4.0.7 to 4.0.8 in /client Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> --- client/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/yarn.lock b/client/yarn.lock index abc16ae..5ae2a84 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1852,9 +1852,9 @@ merge2@^1.3.0, merge2@^1.4.1: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.7" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" - integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: braces "^3.0.3" picomatch "^2.3.1" From 3665625149a0e783bfe2ceef50804dee5772ff33 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Tue, 27 Aug 2024 11:02:24 +0530 Subject: [PATCH 08/23] feat: Added Speed insights --- client/package.json | 1 + client/src/app/layout.tsx | 2 ++ client/yarn.lock | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/client/package.json b/client/package.json index de05c21..fbc2d72 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@vercel/analytics": "^1.3.1", + "@vercel/speed-insights": "^1.0.12", "axios": "^1.7.3", "mongoose": "^8.5.2", "next": "14.2.5", diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index 0ea16e1..79e5542 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Inter, Source_Code_Pro } from "next/font/google"; import "./globals.css"; import { DataProvider } from "./context/DataContext"; import { Analytics } from "@vercel/analytics/react"; +import { SpeedInsights } from "@vercel/speed-insights/next"; const sourcecodepro = Source_Code_Pro({ subsets: ["latin"], @@ -24,6 +25,7 @@ export default function RootLayout({ <body className={sourcecodepro.variable}> <DataProvider>{children}</DataProvider> <Analytics /> + <SpeedInsights /> </body> </html> ); diff --git a/client/yarn.lock b/client/yarn.lock index abc16ae..653e795 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -312,6 +312,11 @@ dependencies: server-only "^0.0.1" +"@vercel/speed-insights@^1.0.12": + version "1.0.12" + resolved "https://registry.yarnpkg.com/@vercel/speed-insights/-/speed-insights-1.0.12.tgz#71c2edffdedae98d34e306d7b0a573e6816898b4" + integrity sha512-ZGQ+a7bcfWJD2VYEp2R1LHvRAMyyaFBYytZXsfnbOMkeOvzGNVxUL7aVUvisIrTZjXTSsxG45DKX7yiw6nq2Jw== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" From 910723e25a1a87290dd83fd65f2eaae2166545c4 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 8 Sep 2024 20:00:35 +0530 Subject: [PATCH 09/23] feat: Add dependencies for react-calendar-heatmap and react-tooltip --- client/package.json | 5 ++- client/public/assets/icons/loading.svg | 6 +++ client/public/assets/icons/money.svg | 10 +++++ client/public/assets/icons/money2.svg | 6 +++ client/yarn.lock | 55 +++++++++++++++++++++++++- 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 client/public/assets/icons/loading.svg create mode 100644 client/public/assets/icons/money.svg create mode 100644 client/public/assets/icons/money2.svg diff --git a/client/package.json b/client/package.json index fbc2d72..75bd137 100644 --- a/client/package.json +++ b/client/package.json @@ -9,14 +9,17 @@ "lint": "next lint" }, "dependencies": { + "@types/react-calendar-heatmap": "^1.6.7", "@vercel/analytics": "^1.3.1", "@vercel/speed-insights": "^1.0.12", "axios": "^1.7.3", "mongoose": "^8.5.2", "next": "14.2.5", "react": "^18", + "react-calendar-heatmap": "^1.9.0", "react-dom": "^18", - "react-toastify": "^10.0.5" + "react-toastify": "^10.0.5", + "react-tooltip": "^5.28.0" }, "devDependencies": { "@types/node": "^20", diff --git a/client/public/assets/icons/loading.svg b/client/public/assets/icons/loading.svg new file mode 100644 index 0000000..02e78c4 --- /dev/null +++ b/client/public/assets/icons/loading.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <path fill="none" stroke="#999999" stroke-dasharray="16" stroke-dashoffset="16" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3c4.97 0 9 4.03 9 9"> + <animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="16;0" /> + <animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12" /> + </path> +</svg> \ No newline at end of file diff --git a/client/public/assets/icons/money.svg b/client/public/assets/icons/money.svg new file mode 100644 index 0000000..1046586 --- /dev/null +++ b/client/public/assets/icons/money.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 128 128"> + <path fill="#ffca28" d="M93.46 39.45c6.71-1.49 15.45-8.15 16.78-11.43c.78-1.92-3.11-4.92-4.15-6.13c-2.38-2.76-1.42-4.12-.5-7.41c1.05-3.74-1.44-7.87-4.97-9.49s-7.75-1.11-11.3.47s-6.58 4.12-9.55 6.62c-2.17-1.37-5.63-7.42-11.23-3.49c-3.87 2.71-4.22 8.61-3.72 13.32c1.17 10.87 3.85 16.51 8.9 18.03c6.38 1.92 13.44.91 19.74-.49" /> + <path fill="#e2a610" d="M104.36 8.18c-.85 14.65-15.14 24.37-21.92 28.65l4.4 3.78s2.79.06 6.61-1.16c6.55-2.08 16.12-7.96 16.78-11.43c.97-5.05-4.21-3.95-5.38-7.94c-.61-2.11 2.97-6.1-.49-11.9m-24.58 3.91s-2.55-2.61-4.44-3.8c-.94 1.77-1.61 3.69-1.94 5.67c-.59 3.48 0 8.42 1.39 12.1c.22.57 1.04.48 1.13-.12c1.2-7.91 3.86-13.85 3.86-13.85" /> + <path fill="#ffca28" d="M61.96 38.16S30.77 41.53 16.7 68.61s-2.11 43.5 10.55 49.48s44.56 8.09 65.31 3.17s25.94-15.12 24.97-24.97c-1.41-14.38-14.77-23.22-14.77-23.22s.53-17.76-13.25-29.29c-12.23-10.24-27.55-5.62-27.55-5.62" /> + <path fill="#6b4b46" d="M74.76 83.73c-6.69-8.44-14.59-9.57-17.12-12.6c-1.38-1.65-2.19-3.32-1.88-5.39c.33-2.2 2.88-3.72 4.86-4.09c2.31-.44 7.82-.21 12.45 4.2c1.1 1.04.7 2.66.67 4.11c-.08 3.11 4.37 6.13 7.97 3.53c3.61-2.61.84-8.42-1.49-11.24c-1.76-2.13-8.14-6.82-16.07-7.56c-2.23-.21-11.2-1.54-16.38 8.31c-1.49 2.83-2.04 9.67 5.76 15.45c1.63 1.21 10.09 5.51 12.44 8.3c4.07 4.83 1.28 9.08-1.9 9.64c-8.67 1.52-13.58-3.17-14.49-5.74c-.65-1.83.03-3.81-.81-5.53c-.86-1.77-2.62-2.47-4.48-1.88c-6.1 1.94-4.16 8.61-1.46 12.28c2.89 3.93 6.44 6.3 10.43 7.6c14.89 4.85 22.05-2.81 23.3-8.42c.92-4.11.82-7.67-1.8-10.97" /> + <path fill="none" stroke="#6b4b46" stroke-miterlimit="10" stroke-width="5" d="M71.16 48.99c-12.67 27.06-14.85 61.23-14.85 61.23" /> + <path fill="#6d4c41" d="M81.67 31.96c8.44 2.75 10.31 10.38 9.7 12.46c-.73 2.44-10.08-7.06-23.98-6.49c-4.86.2-3.45-2.78-1.2-4.5c2.97-2.27 7.96-3.91 15.48-1.47" /> + <path fill="#6b4b46" d="M81.67 31.96c8.44 2.75 10.31 10.38 9.7 12.46c-.73 2.44-10.08-7.06-23.98-6.49c-4.86.2-3.45-2.78-1.2-4.5c2.97-2.27 7.96-3.91 15.48-1.47" /> + <path fill="#e2a610" d="M96.49 58.86c1.06-.73 4.62.53 5.62 7.5c.49 3.41.64 6.71.64 6.71s-4.2-3.77-5.59-6.42c-1.75-3.35-2.43-6.59-.67-7.79" /> +</svg> \ No newline at end of file diff --git a/client/public/assets/icons/money2.svg b/client/public/assets/icons/money2.svg new file mode 100644 index 0000000..8d454b3 --- /dev/null +++ b/client/public/assets/icons/money2.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 32 32"> + <g fill="#999999"> + <path d="M15.84 19.345h.07c1.5.04 2.7 1.26 2.7 2.76c0 1.28-.87 2.35-2.05 2.67v1.12c0 .4-.32.72-.72.72s-.72-.32-.72-.72v-1.12a2.77 2.77 0 0 1-2.05-2.67c0-.4.32-.72.72-.72s.72.32.72.72c0 .74.59 1.33 1.32 1.33s1.33-.6 1.33-1.33s-.6-1.33-1.33-1.33h-.07a2.765 2.765 0 0 1-2.69-2.76c0-1.28.87-2.35 2.05-2.67v-1.12c0-.4.32-.72.72-.72s.72.32.72.72v1.12c1.18.32 2.05 1.39 2.05 2.67c0 .4-.32.72-.72.72s-.72-.32-.72-.72c0-.73-.6-1.33-1.33-1.33s-1.33.6-1.33 1.33s.6 1.33 1.33 1.33" /> + <path d="m10.532 5.1l2.786 3.26l-.301.336C7.283 9.982 3 15.103 3 21.225c0 5.382 4.368 9.75 9.75 9.75h6.17c5.382 0 9.75-4.367 9.75-9.749c.01-6.123-4.273-11.244-10.007-12.53a1.1 1.1 0 0 0-.11-.615l2.37-2.713l.153-.236a1.956 1.956 0 0 0-2.892-2.423l-.843-1a2.02 2.02 0 0 0-3.008-.005l-.883.986a1.96 1.96 0 0 0-2.918 2.41m3.799 1.385l-1.696-1.96a1.98 1.98 0 0 0 2.365-.5l.8-1.038l.888 1.052a1.97 1.97 0 0 0 2.3.513L17.3 6.485zM5 21.225c0-5.988 4.852-10.84 10.84-10.84s10.84 4.852 10.83 10.838v.002a7.753 7.753 0 0 1-7.75 7.75h-6.17A7.753 7.753 0 0 1 5 21.225" /> + </g> +</svg> \ No newline at end of file diff --git a/client/yarn.lock b/client/yarn.lock index 653e795..25fd1e6 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -39,6 +39,26 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@floating-ui/core@^1.6.0": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12" + integrity sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g== + dependencies: + "@floating-ui/utils" "^0.2.7" + +"@floating-ui/dom@^1.6.1": + version "1.6.10" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.10.tgz#b74c32f34a50336c86dcf1f1c845cf3a39e26d6f" + integrity sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.7" + +"@floating-ui/utils@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e" + integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA== + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -227,6 +247,13 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== +"@types/react-calendar-heatmap@^1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@types/react-calendar-heatmap/-/react-calendar-heatmap-1.6.7.tgz#dec75660db7abb9b87a9ce9688221623e849d97b" + integrity sha512-xWBS9iOvw+aCidPk8QwCH69OCO7jnj6/9TjooqGQ9W+rA5m1aw36GjQMlSYKAg86otDeg9dzA+hSAIcvw/y9Rg== + dependencies: + "@types/react" "*" + "@types/react-dom@^18": version "18.3.0" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" @@ -614,6 +641,11 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +classnames@^2.3.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + client-only@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" @@ -1846,6 +1878,11 @@ lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +memoize-one@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + memory-pager@^1.0.2: version "1.5.0" resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" @@ -2251,7 +2288,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -2275,6 +2312,14 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +react-calendar-heatmap@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/react-calendar-heatmap/-/react-calendar-heatmap-1.9.0.tgz#b691310a150d9c52e4ede21ebaa79734fc170d18" + integrity sha512-mGed9any6QLOVckxwxC/eeP9s9wE8mTUW/FCE0V27xF9WOaCGuOftGSRH8DSDoSwgzMSVF6uuH7M1xvc+aZ8sg== + dependencies: + memoize-one "^5.0.0" + prop-types "^15.6.2" + react-dom@^18: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -2295,6 +2340,14 @@ react-toastify@^10.0.5: dependencies: clsx "^2.1.0" +react-tooltip@^5.28.0: + version "5.28.0" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.28.0.tgz#c7b5343ab2d740a428494a3d8315515af1f26f46" + integrity sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg== + dependencies: + "@floating-ui/dom" "^1.6.1" + classnames "^2.3.0" + react@^18: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" From c3342528aaf7ab2748544b7176e542c66f63f1db Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 8 Sep 2024 20:01:11 +0530 Subject: [PATCH 10/23] Refactor Circle component class name + Added submission calendar into the api --- client/src/app/components/Circle.tsx | 2 +- client/src/pages/api/[username].ts | 35 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/client/src/app/components/Circle.tsx b/client/src/app/components/Circle.tsx index edc8eda..0700c54 100644 --- a/client/src/app/components/Circle.tsx +++ b/client/src/app/components/Circle.tsx @@ -29,7 +29,7 @@ const Circle = ({ total }: any) => { strokeWidth="5" strokeLinecap="round" stroke="#FFC11F" - className="cursor-pointer " + className="" strokeDasharray={`${dashLength} ${circumference}`} strokeDashoffset="0" data-difficulty="ALL" diff --git a/client/src/pages/api/[username].ts b/client/src/pages/api/[username].ts index e11d884..e21100d 100644 --- a/client/src/pages/api/[username].ts +++ b/client/src/pages/api/[username].ts @@ -91,18 +91,46 @@ export default async function handler(req: any, res: any) { username: username, }, }; + const payload3 = { + operationName: "userProfileCalendar", + query: ` + query userProfileCalendar($username: String!, $year: Int) { + matchedUser(username: $username) { + userCalendar(year: $year) { + activeYears + streak + totalActiveDays + dccBadges { + timestamp + badge { + name + icon + } + } + submissionCalendar + } + } + } + `, + variables: { + username: username, + }, + }; try { const response1 = await axios.post(url, payload1, { headers }); const response2 = await axios.post(url, payload2, { headers }); + const response3 = await axios.post(url, payload3, { headers }); const data1 = response1.data; const data2 = response2.data; + const data3 = response3.data; // Merge the data from both responses const combinedData = { userSessionProgress: data1.data, userPublicProfile: data2.data, + userProfileCalendar: data3.data, }; const total = combinedData.userSessionProgress.allQuestionsCount[0].count; @@ -151,6 +179,10 @@ export default async function handler(req: any, res: any) { }, }; + const calendarData = combinedData.userProfileCalendar.matchedUser; + const activeYears = calendarData.userCalendar.activeYears; + const submissionCalendar = calendarData.userCalendar.submissionCalendar; + res.status(200).json({ profileData, aboutData, @@ -162,6 +194,9 @@ export default async function handler(req: any, res: any) { easySolved, mediumSolved, hardSolved, + calendarData, + activeYears, + submissionCalendar, }); } catch (error) { console.error(error); From 3dce7102b388fa6834efd3a6c3697df7eaf74c79 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 8 Sep 2024 20:01:26 +0530 Subject: [PATCH 11/23] css for heatmap --- client/src/app/globals.css | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/client/src/app/globals.css b/client/src/app/globals.css index c28c3b3..1ce297c 100644 --- a/client/src/app/globals.css +++ b/client/src/app/globals.css @@ -137,3 +137,52 @@ body { left: 50px; top: 9px; } + +/* Leetcode Heatmap styles */ +.react-calendar-heatmap text { + font-size: 10px; + fill: #aaa; +} + +.react-calendar-heatmap .react-calendar-heatmap-small-text { + font-size: 5px; +} + +.react-calendar-heatmap rect:hover { + stroke: #555; + stroke-width: 1px; +} +.react-calendar-heatmap .color-empty { + fill: #393939; +} +.react-calendar-heatmap rect { + rx: 2px; + ry: 2px; +} +.react-calendar-heatmap .month-label { + fill: #9ca3af; +} + +.react-calendar-heatmap .color-filled { + fill: #8cc665; +} + +/* + * Github color scale + */ + +.react-calendar-heatmap .color-github-0 { + fill: #eeeeee; +} +.react-calendar-heatmap .color-github-1 { + fill: #d6e685; +} +.react-calendar-heatmap .color-github-2 { + fill: #8cc665; +} +.react-calendar-heatmap .color-github-3 { + fill: #44a340; +} +.react-calendar-heatmap .color-github-4 { + fill: #1e6823; +} From f2eae8ece734ade1a21d2be1942af169aec03f24 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 8 Sep 2024 20:02:10 +0530 Subject: [PATCH 12/23] some improvements in Navbar + Data Context --- client/src/app/components/Navbar.tsx | 59 +++++++++++++++----------- client/src/app/context/DataContext.tsx | 5 +++ client/src/app/worth/page.tsx | 15 +++++++ 3 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 client/src/app/worth/page.tsx diff --git a/client/src/app/components/Navbar.tsx b/client/src/app/components/Navbar.tsx index 672831f..17c0eef 100644 --- a/client/src/app/components/Navbar.tsx +++ b/client/src/app/components/Navbar.tsx @@ -4,16 +4,25 @@ import Image from "next/image"; import Link from "next/link"; import React, { useState } from "react"; -type NavbarProps = { - search: string; - setSearch: React.Dispatch<React.SetStateAction<string>>; -}; +interface NavbarProps { + search?: string; + setSearch?: (search: string) => void; + searchBarPresent?: boolean; +} -const Navbar: React.FC<NavbarProps> = ({ search, setSearch }) => { +const Navbar: React.FC<NavbarProps> = ({ + search, + setSearch, + searchBarPresent, +}) => { const [hoveredItem, setHoveredItem] = useState<string | null>(null); return ( - <nav className="bg-[#0e0e0e] z-10 border-b border-b-white fixed top-0 left-0 w-full md:px-8 px-4 py-4 flex justify-around items-center"> + <nav + className={`bg-[#0e0e0e] z-10 border-b border-b-white fixed top-0 left-0 w-full md:px-8 px-4 py-4 flex ${ + searchBarPresent ? "justify-around" : "justify-between !px-24" + } items-center`} + > {/* Logo */} <Link href="/"> <Image @@ -25,24 +34,26 @@ const Navbar: React.FC<NavbarProps> = ({ search, setSearch }) => { </Link> {/* Search */} - <form className=""> - <div className="relative flex "> - <input - value={search} - onChange={(e) => setSearch(e.target.value)} - type="text" - placeholder="Search by username" - className="bg-[#1f1f1f] sm:w-[260px] w-[100px] font-sourcecodepro text-white rounded px-4 py-3 focus:outline-none pl-10" - /> - <Image - src="/assets/icons/search.svg" - alt="Search Icon" - width={20} - height={20} - className="absolute left-2 -translate-y-1/2 top-1/2 " - /> - </div> - </form> + {searchBarPresent && ( + <form className=""> + <div className="relative flex "> + <input + value={search} + onChange={(e) => setSearch && setSearch(e.target.value)} + type="text" + placeholder="Search by username" + className="bg-[#1f1f1f] sm:w-[260px] w-[100px] font-sourcecodepro text-white rounded px-4 py-3 focus:outline-none pl-10" + /> + <Image + src="/assets/icons/search.svg" + alt="Search Icon" + width={20} + height={20} + className="absolute left-2 -translate-y-1/2 top-1/2 " + /> + </div> + </form> + )} {/* Profile */} diff --git a/client/src/app/context/DataContext.tsx b/client/src/app/context/DataContext.tsx index 82d359c..86847d0 100644 --- a/client/src/app/context/DataContext.tsx +++ b/client/src/app/context/DataContext.tsx @@ -5,15 +5,20 @@ import { createContext, useState } from "react"; export const DataContext = createContext({ datas: [], setDatas: (datas: any) => {}, + search: "", + setSearch: (search: string) => {}, }); export const DataProvider = ({ children }: any) => { const [datas, setDatas] = useState<any>(); + const [search, setSearch] = useState<string>(""); return ( <DataContext.Provider value={{ datas, setDatas, + search, + setSearch, }} > {children} diff --git a/client/src/app/worth/page.tsx b/client/src/app/worth/page.tsx new file mode 100644 index 0000000..625f8c3 --- /dev/null +++ b/client/src/app/worth/page.tsx @@ -0,0 +1,15 @@ +import "react-toastify/dist/ReactToastify.css"; +import Heatmap from "../components/Heatmap"; +import Navbar from "../components/Navbar"; + +const Page = () => { + return ( + <div className="relative"> + <Navbar searchBarPresent={false} /> + + <Heatmap /> + </div> + ); +}; + +export default Page; From d733bf7fcf70db7eb953c6c7a0a66e8e728dae2e Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 8 Sep 2024 20:46:24 +0530 Subject: [PATCH 13/23] main page modified --- client/src/app/page.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 32000ba..e0ff921 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -5,7 +5,7 @@ import Navbar from "./components/Navbar"; import Card from "./components/Card"; import Footer from "./components/Footer"; import GenerateStats from "./components/GenerateStats"; -import { use, useContext, useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import PromotionCard from "./components/PromotionCard"; import { DataContext } from "./context/DataContext"; import { ToastContainer } from "react-toastify"; @@ -13,9 +13,8 @@ import "react-toastify/dist/ReactToastify.css"; import Skeleton from "./components/Skeleton"; export default function Home() { - const { datas, setDatas } = useContext(DataContext); + const { datas, setDatas, search, setSearch } = useContext(DataContext); const [loading, setLoading] = useState(true); - const [search, setSearch] = useState<string>(""); const [showStats, setShowStats] = useState(false); const [sortBy, setSortBy] = useState("default"); @@ -75,7 +74,7 @@ export default function Home() { /> </button> - <Navbar search={search} setSearch={setSearch} /> + <Navbar search={search} setSearch={setSearch} searchBarPresent={true} /> <GenerateStats showStats={showStats} setShowStats={setShowStats} /> From 333d5407fcd60780f5ea654387579593a0478e82 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 8 Sep 2024 20:47:14 +0530 Subject: [PATCH 14/23] main heatmap component + download functionality working --- client/package.json | 1 + client/src/app/components/Heatmap.tsx | 447 ++++++++++++++++++++++++++ client/yarn.lock | 34 ++ 3 files changed, 482 insertions(+) create mode 100644 client/src/app/components/Heatmap.tsx diff --git a/client/package.json b/client/package.json index 75bd137..0bca9cf 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,7 @@ "@vercel/analytics": "^1.3.1", "@vercel/speed-insights": "^1.0.12", "axios": "^1.7.3", + "html2canvas": "^1.4.1", "mongoose": "^8.5.2", "next": "14.2.5", "react": "^18", diff --git a/client/src/app/components/Heatmap.tsx b/client/src/app/components/Heatmap.tsx new file mode 100644 index 0000000..7f6d1ec --- /dev/null +++ b/client/src/app/components/Heatmap.tsx @@ -0,0 +1,447 @@ +"use client"; + +import React, { useState, useEffect, useRef } from "react"; +import dynamic from "next/dynamic"; +import Image from "next/image"; +import { toast, ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import { data as TempData } from "./Card"; +import Link from "next/link"; +import Circle from "./Circle"; +import Questions from "./Questions"; +import html2canvas from "html2canvas"; + +const CalendarHeatmap = dynamic(() => import("react-calendar-heatmap"), { + ssr: false, +}); + +function convertSubmissionCalendar(submissionCalendar: any) { + // Parse the JSON string to convert it into a JavaScript object + const submissionCalendarData = JSON.parse(submissionCalendar); + + return submissionCalendarData; +} + +const submissionCalendarData = { + "1704067200": 2, + "1704153600": 4, + "1704240000": 3, + "1704326400": 1, + "1704412800": 2, + "1704499200": 1, + "1704585600": 3, + "1704672000": 1, + "1704758400": 2, + "1704844800": 1, + "1704931200": 1, + "1705017600": 1, + "1705104000": 1, + "1705190400": 1, + "1705276800": 1, + "1705363200": 1, + "1705449600": 1, + "1705520000": 1, + "1705536000": 1, + "1705622400": 1, + "1705708800": 1, + "1705795200": 1, + "1705881600": 1, + "1705968000": 1, + "1706054400": 1, + "1706140800": 1, + "1706227200": 1, + + "1702080000": 8, + "1702166400": 4, + "1702252800": 2, + "1702339200": 1, + "1702425600": 1, + "1702512000": 1, + "1702598400": 1, + "1702684800": 1, + "1702771200": 1, + "1702857600": 1, + "1702944000": 1, + "1703030400": 1, + "1703116800": 1, + "1703203200": 1, + "1703289600": 1, + "1703376000": 1, + "1703402400": 1, + + // Random data for each month of 2024 + "1703443200": 3, + "1703529600": 5, + "1703616000": 2, + "1703702400": 4, + "1703788800": 1, + "1703875200": 2, + "1703961600": 6, + "1704048000": 3, + "1704134400": 5, + "1704220800": 2, + "1704307200": 4, + "1704393600": 1, + "1704480000": 3, + "1704566400": 5, + "1704652800": 2, + "1704739200": 4, + + "1704825600": 6, + "1704912000": 1, + "1704998400": 2, + "1705084800": 3, + "1705171200": 5, + "1705257600": 4, + "1705344000": 1, + "1705430400": 3, + "1705516800": 5, + "1705603200": 2, + "1705689600": 4, + "1705776000": 6, + "1705862400": 1, + "1705948800": 2, + "1706035200": 3, + + // More random values for the remaining months + "1706121600": 1, + "1706208000": 4, + "1706294400": 2, + "1706380800": 5, + "1706467200": 1, + "1706553600": 3, + "1706640000": 6, + "1706726400": 2, + "1706812800": 4, + "1706899200": 1, + "1706985600": 5, + "1707072000": 3, + "1707158400": 6, + "1707244800": 2, + "1707331200": 4, + "1707417600": 1, +}; + +export default function Heatmap() { + const [isMounted, setIsMounted] = useState(false); + const [username, setUsername] = useState(""); + const [submissionCalendar, setSubmissionCalendar] = useState( + submissionCalendarData + ); + const [loading, setLoading] = useState(false); + const [data, setData] = useState(TempData); + const [tooltip, setTooltip] = useState({ + show: false, + content: "", + x: 0, + y: 0, + }); + + useEffect(() => { + setIsMounted(true); + }, []); + + const generateStats = async (e: React.FormEvent) => { + setLoading(true); + e.preventDefault(); + + if (!username) { + return; + } + + try { + const res = await fetch("/api/" + username); + if (!res.ok) { + toast("👻 User not found"); + setLoading(false); + return; + } + + const data = await res.json(); + setData(data); + setSubmissionCalendar(convertSubmissionCalendar(data.submissionCalendar)); + toast("🫡 Stats generated successfully"); + } catch (error) { + console.error("An error occurred. Please try again later"); + toast("😞 An error occurred. Please try again later"); + } finally { + setLoading(false); + } + }; + + const today = new Date(); + const endDate = today; + const startDate = new Date( + today.getFullYear() - 1, + today.getMonth(), + today.getDate() + 1 + ); + + const values = Object.entries(submissionCalendar) + .map(([timestamp, count]) => ({ + date: new Date(parseInt(timestamp) * 1000), + count: count, + })) + .filter((value) => value.date >= startDate && value.date <= endDate); + + const formatDate = (date: Date) => { + const months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + return `${ + months[date.getMonth()] + } ${date.getDate()}, ${date.getFullYear()}`; + }; + + const contentRef = useRef<HTMLDivElement>(null); + + const downloadAsImage = async () => { + if (contentRef.current) { + try { + // Apply a background color to the element before capturing + contentRef.current.style.backgroundColor = "#1c1c1c"; + contentRef.current.style.padding = "20px"; + contentRef.current.style.borderRadius = "10px"; + + const canvas = await html2canvas(contentRef.current, { + scale: 2, // Increase scale for better quality + useCORS: true, // This can help with loading cross-origin images + logging: true, // This can help debug issues + backgroundColor: "#1c1c1c", // Set the background color + }); + + // Reset the inline styles after capturing + contentRef.current.style.backgroundColor = ""; + contentRef.current.style.padding = ""; + contentRef.current.style.borderRadius = ""; + + const image = canvas.toDataURL("image/png"); + const link = document.createElement("a"); + link.href = image; + link.download = "leetcode-stats.png"; + link.click(); + toast("🖼️ Image downloaded successfully"); + } catch (error) { + console.error("Error generating image:", error); + toast("😞 Error generating image. Please try again."); + } + } + }; + + const handleMouseOver = (event: any, value: any) => { + if (value && value.date) { + const rect = event.target.getBoundingClientRect(); + setTooltip({ + show: true, + content: `${value.count} submission${ + value.count !== 1 ? "s" : "" + } on ${formatDate(value.date)}`, + x: rect.left + window.scrollX, + y: rect.top + window.scrollY - 40, + }); + } + }; + + const handleMouseLeave = () => { + setTooltip({ ...tooltip, show: false }); + }; + + if (!isMounted) return null; + + return ( + <> + <div className="w-full font-sourcecodepro mt-24 relative mb-8"> + <div className=" flex flex-col items-center justify-center w-full"> + <h1 className="text-3xl font-semibold mb-4 text-center bg-gradient-to-r from-[#09C4FF] to-[#f38d90] bg-clip-text text-transparent"> + Estimate LeetCode Worth Generator + </h1> + + <form + onSubmit={generateStats} + className="flex items-center justify-center flex-col" + > + <div className="relative "> + <input + value={username} + onChange={(e) => setUsername(e.target.value)} + type="text" + placeholder="Enter the leetcode username" + className="bg-[#1f1f1f] pl-10 max-w-[600px] w-[300px] md:w-[450px] lg:w-[500px] h-[40px] text-white rounded px-4 py-6 md:text-lg text-base focus:outline-none" + /> + <Image + src="/assets/icons/money.svg" + alt="MOney Icon" + width={24} + height={24} + className="absolute left-2 -translate-y-1/2 top-1/2 " + /> + </div> + + <div className="flex gap-2 "> + <button + type="submit" + disabled={!username || loading} + onClick={generateStats} + className={`border flex items-center text-center bg-gradient-to-r from-[#78ff09] to-[#f3ac8d] bg-clip-text text-transparent justify-center gap-2 bg-[#010101] px-4 h-[40px] rounded-md font-semibold text-lg mt-4 ${ + loading || !username + ? "cursor-not-allowed opacity-50" + : "cursor-pointer" + }`} + > + Generate + <Image + src={ + loading + ? "/assets/icons/loading.svg" + : "/assets/icons/money2.svg" + } + alt="Money Icon" + width={24} + height={24} + className="" + />{" "} + Worth + </button> + <button + type="button" + onClick={downloadAsImage} + disabled={!username || loading} + className={`border flex items-center text-center bg-gradient-to-r from-[#cb42b2] to-[#f38d90] bg-clip-text text-transparent justify-center gap-2 px-4 h-[40px] rounded-md font-semibold text-lg mt-4 ${ + loading || !username + ? "cursor-not-allowed opacity-50" + : "cursor-pointer" + }`} + > + Download Image + </button> + </div> + </form> + </div> + + <div + ref={contentRef} + className="max-w-screen-sm mx-auto rounded border px-8 py-4 mt-8" + > + <div className="flex justify-between items-center md:flex-row flex-col"> + <div className="flex flex-row md:gap-0 gap-2 md:flex-col"> + <Image + src={data.profileData.image} + width={100} + height={100} + alt={data.profileData.fullName} + className="rounded-full" + /> + <div className="flex flex-col justify-center items-start"> + <h2 className="text-2xl font-semibold mt-2 text-white"> + {data.profileData.fullName} + </h2> + <p className="text-gray-400">@{data.profileData.username}</p> + </div> + </div> + <div className="h-40 w-[1px] rounded mx-1 bg-gray-600 hidden md:block" /> + <div className=" lg:flex-row gap-5 items-center h-full justify-center hidden md:flex"> + {/* Circle */} + + <Circle total={data?.totalSolved} /> + + <div className="flex flex-col gap-3"> + {/* Questions */} + + <Questions + type={"Easy"} + solved={data?.easySolved} + total={data?.easyTotal} + line="bg-[#2db55d26]" + line2="bg-[#01B8A2]" + /> + + <Questions + type={"Medium"} + solved={data?.mediumSolved} + total={data?.mediumTotal} + line="bg-[#ffb80026]" + line2="bg-[#FFC11F]" + /> + + <Questions + type={"Hard"} + solved={data?.hardSolved} + total={data?.hardTotal} + line="bg-[#ef474326]" + line2="bg-[#EF4642]" + /> + </div> + </div> + </div> + + <div className="mt-4"> + <CalendarHeatmap + startDate={startDate} + endDate={endDate} + values={values} + classForValue={(value) => { + if (!value || value.count === 0) { + return "color-empty"; + } + return `color-github-${Math.min(value.count, 4)}`; + }} + onMouseOver={handleMouseOver} + onMouseLeave={handleMouseLeave} + gutterSize={2} + horizontal={true} + /> + + <h2 className="text-sm font-semibold mt-4 text-gray-400 text-center"> + <span className="text-[#f3e58d] text-2xl"> + {data.totalSolved * 10}$ + </span> + <br /> + Estimated Worth + </h2> + + <p className="text-white mt-4 flex w-full items-center justify-center"> + Get Yours at: + <Link + href="https://leetcode-profiles-delta.vercel.app/worth" + className="text-blue-300" + > + leetcode-profiles-delta.vercel.app + </Link> + </p> + </div> + {tooltip.show && ( + <div + className="absolute bg-gray-800 text-white p-2 rounded shadow-lg text-sm z-10 pointer-events-none w-fit " + style={{ left: `${tooltip.x}px`, top: `${tooltip.y}px` }} + > + {tooltip.content} + </div> + )} + </div> + </div> + <ToastContainer + position="bottom-right" + autoClose={5000} + hideProgressBar={false} + newestOnTop={false} + closeOnClick + rtl={false} + pauseOnFocusLoss + draggable + pauseOnHover + theme="dark" + /> + </> + ); +} diff --git a/client/yarn.lock b/client/yarn.lock index 25fd1e6..9f75c2c 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -553,6 +553,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-arraybuffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" @@ -694,6 +699,13 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +css-line-break@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0" + integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w== + dependencies: + utrie "^1.0.2" + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -1489,6 +1501,14 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +html2canvas@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543" + integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA== + dependencies: + css-line-break "^2.1.0" + text-segmentation "^1.0.3" + ignore@^5.2.0: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" @@ -2748,6 +2768,13 @@ tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +text-segmentation@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943" + integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw== + dependencies: + utrie "^1.0.2" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -2894,6 +2921,13 @@ util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +utrie@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645" + integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw== + dependencies: + base64-arraybuffer "^1.0.2" + webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" From c34270437bf477e96417aeb3c3d56552ac64e954 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 8 Sep 2024 21:16:04 +0530 Subject: [PATCH 15/23] added worthAlgo + addtemp data in card --- client/src/app/components/Card.tsx | 7 +++++ client/src/app/components/Heatmap.tsx | 39 ++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/client/src/app/components/Card.tsx b/client/src/app/components/Card.tsx index 335ca72..073112c 100644 --- a/client/src/app/components/Card.tsx +++ b/client/src/app/components/Card.tsx @@ -36,6 +36,13 @@ export const data = { easySolved: 175, mediumSolved: 110, hardSolved: 21, + activeYears: [2021, 2024], + calendarData: { + userCalendar: { + streak: 39, + totalActiveDays: 89, + }, + }, }; export default function Card({ userData = data, index }: any) { diff --git a/client/src/app/components/Heatmap.tsx b/client/src/app/components/Heatmap.tsx index 7f6d1ec..c33f95a 100644 --- a/client/src/app/components/Heatmap.tsx +++ b/client/src/app/components/Heatmap.tsx @@ -122,6 +122,31 @@ const submissionCalendarData = { "1707417600": 1, }; +function worthCalculator( + streak: number, + easySolved: number, + mediumSolved: number, + hardSolved: number, + activeYears: number, + totalActiveDays: number +) { + const easyPoints = 1; + const mediumPoints = 2; + const hardPoints = 5; + const streakPoints = streak >= 30 ? 10 : 5; + const activeYearPoints = 2; + const totalActiveDaysPoints = 10; + + return ( + easySolved * easyPoints + + mediumSolved * mediumPoints + + hardSolved * hardPoints + + streak * streakPoints + + activeYears * activeYearPoints + + totalActiveDays * totalActiveDaysPoints + ); +} + export default function Heatmap() { const [isMounted, setIsMounted] = useState(false); const [username, setUsername] = useState(""); @@ -316,9 +341,9 @@ export default function Heatmap() { <button type="button" onClick={downloadAsImage} - disabled={!username || loading} + disabled={!data || loading} className={`border flex items-center text-center bg-gradient-to-r from-[#cb42b2] to-[#f38d90] bg-clip-text text-transparent justify-center gap-2 px-4 h-[40px] rounded-md font-semibold text-lg mt-4 ${ - loading || !username + loading || !data ? "cursor-not-allowed opacity-50" : "cursor-pointer" }`} @@ -404,7 +429,15 @@ export default function Heatmap() { <h2 className="text-sm font-semibold mt-4 text-gray-400 text-center"> <span className="text-[#f3e58d] text-2xl"> - {data.totalSolved * 10}$ + {worthCalculator( + data.easySolved, + data.mediumSolved, + data.hardSolved, + data?.activeYears.length, + data?.calendarData.userCalendar.streak, + data?.calendarData.userCalendar.totalActiveDays + )} + $ </span> <br /> Estimated Worth From ded2dbc60ec39317a648fbb1bb845dc785237df0 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sun, 8 Sep 2024 21:45:48 +0530 Subject: [PATCH 16/23] added button to navigate to worth section + some minor fix in heatmap component --- client/src/app/components/Heatmap.tsx | 2 +- client/src/app/page.tsx | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/client/src/app/components/Heatmap.tsx b/client/src/app/components/Heatmap.tsx index c33f95a..dddc084 100644 --- a/client/src/app/components/Heatmap.tsx +++ b/client/src/app/components/Heatmap.tsx @@ -443,7 +443,7 @@ export default function Heatmap() { Estimated Worth </h2> - <p className="text-white mt-4 flex w-full items-center justify-center"> + <p className="text-white mt-4 flex w-full items-center justify-center flex-col md:flex-row"> Get Yours at: <Link href="https://leetcode-profiles-delta.vercel.app/worth" diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index e0ff921..6763ff9 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -11,6 +11,7 @@ import { DataContext } from "./context/DataContext"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import Skeleton from "./components/Skeleton"; +import Link from "next/link"; export default function Home() { const { datas, setDatas, search, setSearch } = useContext(DataContext); @@ -78,11 +79,23 @@ export default function Home() { <GenerateStats showStats={showStats} setShowStats={setShowStats} /> - <div className="w-full flex items-center justify-center "> + <div className="w-full max-w-7xl mx-auto flex flex-col lg:grid grid-cols-3 items-center justify-start gap-4 lg:gap-12 px-8 mt-28"> + <Link + href="/worth" + className="flex w-fit gap-2 rounded border-2 border-[#f7f7f7] px-4 bg-gradient-to-r from-[#cb42b2] to-[#ecf576] bg-clip-text text-transparent p-2 font-sourcecodepro font-bold" + > + <Image + src="/assets/icons/money.svg" + alt="Leetcode Logo" + width={24} + height={24} + /> + LeetCode Worth + </Link> <select value={sortBy} onChange={handleSortChange} - className="mt-28 rounded border-2 border-[#f7f7f7] w-64 bg-[#0e0e0e] text-white p-2 font-sourcecodepro" + className=" rounded border-2 border-[#f7f7f7] w-64 bg-[#0e0e0e] text-white p-2 font-sourcecodepro" > <option value="default">Sort By Default</option> <option value="question-solved">Sort By Questions Solved</option> From f37f2724af3bf637bea7353e4c106fd10f799467 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:19:44 +0000 Subject: [PATCH 17/23] Bump axios from 1.7.3 to 1.7.4 in /client Bumps [axios](https://github.com/axios/axios) from 1.7.3 to 1.7.4. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.7.3...v1.7.4) --- updated-dependencies: - dependency-name: axios dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> --- client/package.json | 2 +- client/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/package.json b/client/package.json index 0bca9cf..7b5f84c 100644 --- a/client/package.json +++ b/client/package.json @@ -12,7 +12,7 @@ "@types/react-calendar-heatmap": "^1.6.7", "@vercel/analytics": "^1.3.1", "@vercel/speed-insights": "^1.0.12", - "axios": "^1.7.3", + "axios": "^1.7.4", "html2canvas": "^1.4.1", "mongoose": "^8.5.2", "next": "14.2.5", diff --git a/client/yarn.lock b/client/yarn.lock index 9f75c2c..e41d235 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -532,10 +532,10 @@ axe-core@^4.9.1: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== -axios@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.3.tgz#a1125f2faf702bc8e8f2104ec3a76fab40257d85" - integrity sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw== +axios@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2" + integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" From 4f7f59ce327c92e335718f5063099e5c2a1d337e Mon Sep 17 00:00:00 2001 From: Dhruv Kotwani <96691139+druvkotwani@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:01:52 +0530 Subject: [PATCH 18/23] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5782a6b..9e8e6f2 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ A platform to create your LeetCode statistics and showcase your LeetCode profile globally.  + + From 6a60690a9b552662c512ae7e56454145d546d68a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 05:09:32 +0000 Subject: [PATCH 19/23] Bump next from 14.2.5 to 14.2.10 in /client Bumps [next](https://github.com/vercel/next.js) from 14.2.5 to 14.2.10. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v14.2.5...v14.2.10) --- updated-dependencies: - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> --- client/package.json | 2 +- client/yarn.lock | 124 ++++++++++++++++++++++---------------------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/client/package.json b/client/package.json index 0bca9cf..b9e81b6 100644 --- a/client/package.json +++ b/client/package.json @@ -15,7 +15,7 @@ "axios": "^1.7.3", "html2canvas": "^1.4.1", "mongoose": "^8.5.2", - "next": "14.2.5", + "next": "14.2.10", "react": "^18", "react-calendar-heatmap": "^1.9.0", "react-dom": "^18", diff --git a/client/yarn.lock b/client/yarn.lock index 9f75c2c..ad3f431 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -129,10 +129,10 @@ dependencies: sparse-bitfield "^3.0.3" -"@next/env@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.5.tgz#1d9328ab828711d3517d0a1d505acb55e5ef7ad0" - integrity sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA== +"@next/env@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.10.tgz#1d3178340028ced2d679f84140877db4f420333c" + integrity sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw== "@next/eslint-plugin-next@14.2.5": version "14.2.5" @@ -141,50 +141,50 @@ dependencies: glob "10.3.10" -"@next/swc-darwin-arm64@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz#d0a160cf78c18731c51cc0bff131c706b3e9bb05" - integrity sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ== - -"@next/swc-darwin-x64@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz#eb832a992407f6e6352eed05a073379f1ce0589c" - integrity sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA== - -"@next/swc-linux-arm64-gnu@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz#098fdab57a4664969bc905f5801ef5a89582c689" - integrity sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA== - -"@next/swc-linux-arm64-musl@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz#243a1cc1087fb75481726dd289c7b219fa01f2b5" - integrity sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA== - -"@next/swc-linux-x64-gnu@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz#b8a2e436387ee4a52aa9719b718992e0330c4953" - integrity sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ== - -"@next/swc-linux-x64-musl@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz#cb8a9adad5fb8df86112cfbd363aab5c6d32757b" - integrity sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ== - -"@next/swc-win32-arm64-msvc@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz#81f996c1c38ea0900d4e7719cc8814be8a835da0" - integrity sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw== - -"@next/swc-win32-ia32-msvc@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz#f61c74ce823e10b2bc150e648fc192a7056422e0" - integrity sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg== - -"@next/swc-win32-x64-msvc@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz#ed199a920efb510cfe941cd75ed38a7be21e756f" - integrity sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g== +"@next/swc-darwin-arm64@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.10.tgz#49d10ca4086fbd59ee68e204f75d7136eda2aa80" + integrity sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ== + +"@next/swc-darwin-x64@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.10.tgz#0ebeae3afb8eac433882b79543295ab83624a1a8" + integrity sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA== + +"@next/swc-linux-arm64-gnu@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.10.tgz#7e602916d2fb55a3c532f74bed926a0137c16f20" + integrity sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA== + +"@next/swc-linux-arm64-musl@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.10.tgz#6b143f628ccee490b527562e934f8de578d4be47" + integrity sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ== + +"@next/swc-linux-x64-gnu@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.10.tgz#086f2f16a0678890a1eb46518c4dda381b046082" + integrity sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg== + +"@next/swc-linux-x64-musl@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.10.tgz#1befef10ed8dbcc5047b5d637a25ae3c30a0bfc3" + integrity sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA== + +"@next/swc-win32-arm64-msvc@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.10.tgz#731f52c3ae3c56a26cf21d474b11ae1529531209" + integrity sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ== + +"@next/swc-win32-ia32-msvc@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.10.tgz#32723ef7f04e25be12af357cc72ddfdd42fd1041" + integrity sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg== + +"@next/swc-win32-x64-msvc@14.2.10": + version "14.2.10" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.10.tgz#ee1d036cb5ec871816f96baee7991035bb242455" + integrity sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -2035,12 +2035,12 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -next@14.2.5: - version "14.2.5" - resolved "https://registry.yarnpkg.com/next/-/next-14.2.5.tgz#afe4022bb0b752962e2205836587a289270efbea" - integrity sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA== +next@14.2.10: + version "14.2.10" + resolved "https://registry.yarnpkg.com/next/-/next-14.2.10.tgz#331981a4fecb1ae8af1817d4db98fc9687ee1cb6" + integrity sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww== dependencies: - "@next/env" "14.2.5" + "@next/env" "14.2.10" "@swc/helpers" "0.5.5" busboy "1.6.0" caniuse-lite "^1.0.30001579" @@ -2048,15 +2048,15 @@ next@14.2.5: postcss "8.4.31" styled-jsx "5.1.1" optionalDependencies: - "@next/swc-darwin-arm64" "14.2.5" - "@next/swc-darwin-x64" "14.2.5" - "@next/swc-linux-arm64-gnu" "14.2.5" - "@next/swc-linux-arm64-musl" "14.2.5" - "@next/swc-linux-x64-gnu" "14.2.5" - "@next/swc-linux-x64-musl" "14.2.5" - "@next/swc-win32-arm64-msvc" "14.2.5" - "@next/swc-win32-ia32-msvc" "14.2.5" - "@next/swc-win32-x64-msvc" "14.2.5" + "@next/swc-darwin-arm64" "14.2.10" + "@next/swc-darwin-x64" "14.2.10" + "@next/swc-linux-arm64-gnu" "14.2.10" + "@next/swc-linux-arm64-musl" "14.2.10" + "@next/swc-linux-x64-gnu" "14.2.10" + "@next/swc-linux-x64-musl" "14.2.10" + "@next/swc-win32-arm64-msvc" "14.2.10" + "@next/swc-win32-ia32-msvc" "14.2.10" + "@next/swc-win32-x64-msvc" "14.2.10" normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" From b945faacb461ff341ecb30636a8a9114edb1dca3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 Jan 2025 11:09:46 +0000 Subject: [PATCH 20/23] Bump mongoose from 8.5.2 to 8.9.5 in /client Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.5.2 to 8.9.5. - [Release notes](https://github.com/Automattic/mongoose/releases) - [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md) - [Commits](https://github.com/Automattic/mongoose/compare/8.5.2...8.9.5) --- updated-dependencies: - dependency-name: mongoose dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> --- client/package.json | 2 +- client/yarn.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/client/package.json b/client/package.json index 094b348..28def9a 100644 --- a/client/package.json +++ b/client/package.json @@ -14,7 +14,7 @@ "@vercel/speed-insights": "^1.0.12", "axios": "^1.7.4", "html2canvas": "^1.4.1", - "mongoose": "^8.5.2", + "mongoose": "^8.9.5", "next": "14.2.10", "react": "^18", "react-calendar-heatmap": "^1.9.0", diff --git a/client/yarn.lock b/client/yarn.lock index 20ccf60..ffb3bf9 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -122,10 +122,10 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@mongodb-js/saslprep@^1.1.5": - version "1.1.8" - resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz#d39744540be8800d17749990b0da95b4271840d1" - integrity sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ== +"@mongodb-js/saslprep@^1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz#e974bab8eca9faa88677d4ea4da8d09a52069004" + integrity sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw== dependencies: sparse-bitfield "^3.0.3" @@ -585,10 +585,10 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -bson@^6.7.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/bson/-/bson-6.8.0.tgz#5063c41ba2437c2b8ff851b50d9e36cb7aaa7525" - integrity sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ== +bson@^6.10.1: + version "6.10.1" + resolved "https://registry.yarnpkg.com/bson/-/bson-6.10.1.tgz#dcd04703178f5ecf5b25de04edd2a95ec79385d3" + integrity sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA== busboy@1.6.0: version "1.6.0" @@ -1972,23 +1972,23 @@ mongodb-connection-string-url@^3.0.0: "@types/whatwg-url" "^11.0.2" whatwg-url "^13.0.0" -mongodb@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.7.0.tgz#f86e51e6530e6a2ca4a99d7cfdf6f409223ac199" - integrity sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA== +mongodb@~6.12.0: + version "6.12.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.12.0.tgz#8b0bda1b18cbb3f0aec8ab4119c5dc535a43c444" + integrity sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA== dependencies: - "@mongodb-js/saslprep" "^1.1.5" - bson "^6.7.0" + "@mongodb-js/saslprep" "^1.1.9" + bson "^6.10.1" mongodb-connection-string-url "^3.0.0" -mongoose@^8.5.2: - version "8.5.2" - resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.5.2.tgz#73b40ce778f3fc66407aba3c3157795cdd278543" - integrity sha512-GZB4rHMdYfGatV+23IpCrqFbyCOjCNOHXgWbirr92KRwTEncBrtW3kgU9vmpKjsGf7nMmnAy06SwWUv1vhDkSg== +mongoose@^8.9.5: + version "8.9.5" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.9.5.tgz#90a2d70b48a66022a61d7a170ab4fad106b83cba" + integrity sha512-SPhOrgBm0nKV3b+IIHGqpUTOmgVL5Z3OO9AwkFEmvOZznXTvplbomstCnPOGAyungtRXE5pJTgKpKcZTdjeESg== dependencies: - bson "^6.7.0" + bson "^6.10.1" kareem "2.6.3" - mongodb "6.7.0" + mongodb "~6.12.0" mpath "0.9.0" mquery "5.0.0" ms "2.1.3" From 574ccb2e078d7d11066fd85ffa742d8a3be112a0 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sat, 8 Mar 2025 16:48:26 +0530 Subject: [PATCH 21/23] Implement pagination and enhanced search functionality for user data --- client/src/app/components/Navbar.tsx | 10 +- client/src/app/page.tsx | 143 ++++++++++++++++++++++----- client/src/pages/api/fetchdata.ts | 46 ++++++++- 3 files changed, 168 insertions(+), 31 deletions(-) diff --git a/client/src/app/components/Navbar.tsx b/client/src/app/components/Navbar.tsx index 17c0eef..6be917a 100644 --- a/client/src/app/components/Navbar.tsx +++ b/client/src/app/components/Navbar.tsx @@ -35,21 +35,21 @@ const Navbar: React.FC<NavbarProps> = ({ {/* Search */} {searchBarPresent && ( - <form className=""> - <div className="relative flex "> + <form className="" onSubmit={(e) => e.preventDefault()}> + <div className="relative flex items-center"> <input value={search} onChange={(e) => setSearch && setSearch(e.target.value)} type="text" - placeholder="Search by username" - className="bg-[#1f1f1f] sm:w-[260px] w-[100px] font-sourcecodepro text-white rounded px-4 py-3 focus:outline-none pl-10" + placeholder="Search by name" + className="bg-[#1f1f1f] sm:w-[260px] w-[100px] font-sourcecodepro text-white rounded-l px-4 py-3 focus:outline-none pl-10" /> <Image src="/assets/icons/search.svg" alt="Search Icon" width={20} height={20} - className="absolute left-2 -translate-y-1/2 top-1/2 " + className="absolute left-2 -translate-y-1/2 top-1/2" /> </div> </form> diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 6763ff9..2ed9302 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -5,7 +5,7 @@ import Navbar from "./components/Navbar"; import Card from "./components/Card"; import Footer from "./components/Footer"; import GenerateStats from "./components/GenerateStats"; -import { useContext, useEffect, useState } from "react"; +import { useContext, useEffect, useState, useMemo } from "react"; import PromotionCard from "./components/PromotionCard"; import { DataContext } from "./context/DataContext"; import { ToastContainer } from "react-toastify"; @@ -18,21 +18,62 @@ export default function Home() { const [loading, setLoading] = useState(true); const [showStats, setShowStats] = useState(false); const [sortBy, setSortBy] = useState("default"); + const [pagination, setPagination] = useState({ + currentPage: 1, + totalPages: 1, + totalItems: 0, + limit: 11, + }); + const [searchTerm, setSearchTerm] = useState(""); + const [debouncedSearch, setDebouncedSearch] = useState(""); - const fetchData = async () => { + // Debounce search input to avoid too many requests + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearch(search); + }, 500); + + return () => clearTimeout(timer); + }, [search]); + + // Reset pagination when search changes + useEffect(() => { + setPagination((prev) => ({ ...prev, currentPage: 1 })); + }, [debouncedSearch]); + + const fetchData = async (page = 1) => { try { - const res = await fetch("/api/fetchdata"); - const data = await res.json(); - setDatas(data.data); + setLoading(true); + const queryParams = new URLSearchParams(); + + if (debouncedSearch) { + queryParams.append("search", debouncedSearch); + } + + queryParams.append("page", page.toString()); + queryParams.append("limit", pagination.limit.toString()); + queryParams.append("sortBy", sortBy); + + const res = await fetch(`/api/fetchdata?${queryParams.toString()}`); + const response = await res.json(); + + setDatas(response.data); + setPagination({ + currentPage: response.pagination.page, + totalPages: response.pagination.totalPages, + totalItems: response.pagination.total, + limit: response.pagination.limit, + }); setLoading(false); } catch (error) { console.error("Error fetching messages:", error); + setLoading(false); } }; useEffect(() => { - fetchData(); - }, [datas && datas.length]); + fetchData(pagination.currentPage); + }, [debouncedSearch, pagination.currentPage, pagination.limit, sortBy]); useEffect(() => { setTimeout(() => { @@ -42,22 +83,13 @@ export default function Home() { const handleSortChange = (e: React.ChangeEvent<HTMLSelectElement>) => { setSortBy(e.target.value); + // Reset to first page when sorting changes + setPagination((prev) => ({ ...prev, currentPage: 1 })); }; - const searchedData = datas?.filter((data: any) => - data.profileData.fullName.toLowerCase().includes(search.toLowerCase()) - ); - const sorting = (data: any[]) => { - switch (sortBy) { - case "question-solved": - return data.slice().sort((a, b) => b.totalSolved - a.totalSolved); - case "default": - default: - return data.slice().sort((a, b) => { - return ( - new Date(b.timeStamp).getTime() - new Date(a.timeStamp).getTime() - ); - }); + const handlePageChange = (newPage: number) => { + if (newPage > 0 && newPage <= pagination.totalPages) { + setPagination((prev) => ({ ...prev, currentPage: newPage })); } }; @@ -106,17 +138,80 @@ export default function Home() { <PromotionCard /> {loading ? ( <> - {Array.from({ length: 6 }, (_, i) => ( + {Array.from({ length: 8 }, (_, i) => ( <Skeleton key={i} /> ))} </> ) : ( - sorting(searchedData).map((userData: any, index: number) => ( - <Card userData={userData} index={index} key={index} /> + datas?.map((userData: any, index: number) => ( + <Card + userData={userData} + index={index} + key={userData._id || index} + /> )) )} </div> + {/* Pagination Controls */} + {!loading && pagination.totalPages > 1 && ( + <div className="flex justify-center mt-12 mb-16 max-w-7xl mx-auto"> + <div className="flex space-x-2 items-center font-sourcecodepro"> + <button + onClick={() => handlePageChange(pagination.currentPage - 1)} + disabled={pagination.currentPage === 1} + className="px-4 py-2 rounded-md border border-gray-600 bg-[#0e0e0e] text-white disabled:opacity-50 disabled:cursor-not-allowed" + > + Previous + </button> + + <div className="flex space-x-2"> + {Array.from( + { length: Math.min(5, pagination.totalPages) }, + (_, i) => { + // Show 5 pages around current page + let pageNum = 1; + if (pagination.totalPages <= 5) { + pageNum = i + 1; + } else if (pagination.currentPage <= 3) { + pageNum = i + 1; + } else if ( + pagination.currentPage >= + pagination.totalPages - 2 + ) { + pageNum = pagination.totalPages - 4 + i; + } else { + pageNum = pagination.currentPage - 2 + i; + } + + return ( + <button + key={pageNum} + onClick={() => handlePageChange(pageNum)} + className={`w-10 h-10 rounded-md ${ + pagination.currentPage === pageNum + ? "bg-gradient-to-r from-[#cb42b2] to-[#ecf576] text-black font-bold" + : "border border-gray-600 bg-[#0e0e0e] text-white" + }`} + > + {pageNum} + </button> + ); + } + )} + </div> + + <button + onClick={() => handlePageChange(pagination.currentPage + 1)} + disabled={pagination.currentPage === pagination.totalPages} + className="px-4 py-2 rounded-md border border-gray-600 bg-[#0e0e0e] text-white disabled:opacity-50 disabled:cursor-not-allowed" + > + Next + </button> + </div> + </div> + )} + <Footer /> <ToastContainer diff --git a/client/src/pages/api/fetchdata.ts b/client/src/pages/api/fetchdata.ts index 71e7717..29cd302 100644 --- a/client/src/pages/api/fetchdata.ts +++ b/client/src/pages/api/fetchdata.ts @@ -4,14 +4,56 @@ import UserData from "../../models/userdata"; export default async function handler(req: any, res: any) { const { method } = req; + const { search, page = 1, limit = 11, sortBy = "default" } = req.query; await connectToDatabase(); switch (method) { case "GET": try { - const messages = await UserData.find({}); - res.status(200).json({ success: true, data: messages }); + const pageNumber = parseInt(page as string); + const limitNumber = parseInt(limit as string); + const skip = (pageNumber - 1) * limitNumber; + + // Build the query based on search parameter + let query = {}; + if (search) { + query = { + "profileData.fullName": { $regex: search, $options: "i" }, + }; + } + + // Determine sort options based on sortBy parameter + let sortOptions = {}; + switch (sortBy) { + case "question-solved": + sortOptions = { totalSolved: -1 }; // -1 for descending order + break; + case "default": + default: + sortOptions = { timeStamp: -1 }; // Sort by timestamp descending (newest first) + break; + } + + // Get total count for pagination + const totalItems = await UserData.countDocuments(query); + + // Fetch data with pagination and sorting + const messages = await UserData.find(query) + .sort(sortOptions) + .skip(skip) + .limit(limitNumber); + + res.status(200).json({ + success: true, + data: messages, + pagination: { + total: totalItems, + page: pageNumber, + limit: limitNumber, + totalPages: Math.ceil(totalItems / limitNumber), + }, + }); } catch (error) { res.status(400).json({ success: false, error }); } From 79fc377350c7a2ea5bcca70871edb1d28bf7d9d4 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sat, 8 Mar 2025 17:25:59 +0530 Subject: [PATCH 22/23] Add LeetCode profile sync functionality with admin UI and scheduled workflows --- .github/workflows/weekly-sync.yml | 23 +++ client/public/assets/icons/refresh.svg | 1 + client/src/app/admin/sync/page.tsx | 153 +++++++++++++++++ client/src/app/api/cron/route.ts | 49 ++++++ client/src/app/page.tsx | 42 +++-- client/src/pages/api/syncAllProfiles.ts | 211 ++++++++++++++++++++++++ client/src/pages/api/syncProfile.ts | 199 ++++++++++++++++++++++ 7 files changed, 665 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/weekly-sync.yml create mode 100644 client/public/assets/icons/refresh.svg create mode 100644 client/src/app/admin/sync/page.tsx create mode 100644 client/src/app/api/cron/route.ts create mode 100644 client/src/pages/api/syncAllProfiles.ts create mode 100644 client/src/pages/api/syncProfile.ts diff --git a/.github/workflows/weekly-sync.yml b/.github/workflows/weekly-sync.yml new file mode 100644 index 0000000..2995f53 --- /dev/null +++ b/.github/workflows/weekly-sync.yml @@ -0,0 +1,23 @@ +name: Bi-Weekly LeetCode Profile Sync + +on: + schedule: + # Run every other Sunday at 00:00 UTC (every 2 weeks) + - cron: "0 0 */14 * *" + + # Allow manual triggering + workflow_dispatch: + +jobs: + sync-profiles: + runs-on: ubuntu-latest + + steps: + - name: Trigger Profile Sync + run: | + curl -X GET "${{ secrets.APP_URL }}/api/syncAllProfiles?secretKey=${{ secrets.SYNC_SECRET_KEY }}" \ + -H "Content-Type: application/json" \ + --fail + env: + APP_URL: ${{ secrets.APP_URL }} + SYNC_SECRET_KEY: ${{ secrets.SYNC_SECRET_KEY }} diff --git a/client/public/assets/icons/refresh.svg b/client/public/assets/icons/refresh.svg new file mode 100644 index 0000000..78c7f94 --- /dev/null +++ b/client/public/assets/icons/refresh.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path stroke-dasharray="16" stroke-dashoffset="16" d="M12 3c4.97 0 9 4.03 9 9"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="16;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path><path stroke-dasharray="64" stroke-dashoffset="64" stroke-opacity="0.3" d="M12 3c4.97 0 9 4.03 9 9c0 4.97 -4.03 9 -9 9c-4.97 0 -9 -4.03 -9 -9c0 -4.97 4.03 -9 9 -9Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.2s" values="64;0"/></path></g></svg> \ No newline at end of file diff --git a/client/src/app/admin/sync/page.tsx b/client/src/app/admin/sync/page.tsx new file mode 100644 index 0000000..6dcbb13 --- /dev/null +++ b/client/src/app/admin/sync/page.tsx @@ -0,0 +1,153 @@ +"use client"; + +import React, { useState } from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; + +export default function AdminSync() { + const [isLoading, setIsLoading] = useState(false); + const [results, setResults] = useState<any>(null); + const [error, setError] = useState<string | null>(null); + const [secretKey, setSecretKey] = useState(""); + const router = useRouter(); + + const handleSyncAll = async () => { + if (!secretKey) { + setError("Secret key is required"); + return; + } + + setIsLoading(true); + setError(null); + + try { + const response = await fetch( + `/api/syncAllProfiles?secretKey=${secretKey}` + ); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || "Failed to sync profiles"); + } + + setResults(data); + } catch (err: any) { + setError(err.message || "An error occurred while syncing profiles"); + } finally { + setIsLoading(false); + } + }; + + return ( + <section className="px-4 lg:px-24 py-24 min-h-screen bg-[#0e0e0e] text-white"> + <div className="max-w-4xl mx-auto"> + <div className="flex items-center mb-8"> + <Link href="/" className="mr-4"> + <Image + src="/assets/icons/leetcode.svg" + alt="Leetcode Logo" + width={36} + height={36} + /> + </Link> + <h1 className="text-3xl font-bold font-sourcecodepro"> + Admin: Profile Sync Tool + </h1> + </div> + + <div className="bg-[#1a1a1a] p-6 rounded-lg border border-gray-700 mb-8"> + <h2 className="text-xl font-bold mb-4 font-sourcecodepro"> + Sync All LeetCode Profiles + </h2> + <p className="mb-6 text-gray-300"> + This will update all profiles with the latest data from LeetCode. + This process may take several minutes depending on the number of + profiles. + </p> + + <div className="mb-4"> + <label className="block text-sm font-medium text-gray-300 mb-2"> + Secret Key + </label> + <input + type="password" + value={secretKey} + onChange={(e) => setSecretKey(e.target.value)} + className="w-full p-2 bg-[#0e0e0e] border border-gray-700 rounded text-white" + placeholder="Enter secret key" + /> + {error && <p className="mt-2 text-red-500">{error}</p>} + </div> + + <button + onClick={handleSyncAll} + disabled={isLoading} + className={`px-4 py-2 rounded-md font-sourcecodepro font-bold ${ + isLoading + ? "bg-gray-700 cursor-not-allowed" + : "bg-gradient-to-r from-[#cb42b2] to-[#ecf576] text-black hover:opacity-90" + } transition-all`} + > + {isLoading ? "Syncing..." : "Sync All Profiles"} + </button> + </div> + + {results && ( + <div className="bg-[#1a1a1a] p-6 rounded-lg border border-gray-700"> + <h2 className="text-xl font-bold mb-4 font-sourcecodepro"> + Sync Results + </h2> + + <div className="flex mb-4 gap-4"> + <div className="flex-1 p-4 bg-[#0e0e0e] rounded border border-gray-700"> + <div className="text-4xl font-bold mb-2 text-center"> + {results.results.total} + </div> + <div className="text-center text-gray-300">Total Profiles</div> + </div> + <div className="flex-1 p-4 bg-[#0e0e0e] rounded border border-gray-700"> + <div className="text-4xl font-bold mb-2 text-center text-green-500"> + {results.results.successful} + </div> + <div className="text-center text-gray-300"> + Successfully Updated + </div> + </div> + <div className="flex-1 p-4 bg-[#0e0e0e] rounded border border-gray-700"> + <div className="text-4xl font-bold mb-2 text-center text-red-500"> + {results.results.failed} + </div> + <div className="text-center text-gray-300">Failed Updates</div> + </div> + </div> + + {results.results.failures.length > 0 && ( + <div> + <h3 className="text-lg font-bold mb-2 font-sourcecodepro"> + Failed Profiles: + </h3> + <ul className="list-disc pl-5 text-red-400"> + {results.results.failures.map( + (username: string, idx: number) => ( + <li key={idx}>{username}</li> + ) + )} + </ul> + </div> + )} + + <div className="mt-6 flex justify-end"> + <button + onClick={() => router.push("/")} + className="px-4 py-2 bg-[#0e0e0e] text-white rounded-md border border-gray-700 hover:bg-[#1f1f1f] transition-all" + > + Back to Home + </button> + </div> + </div> + )} + </div> + </section> + ); +} diff --git a/client/src/app/api/cron/route.ts b/client/src/app/api/cron/route.ts new file mode 100644 index 0000000..de9f736 --- /dev/null +++ b/client/src/app/api/cron/route.ts @@ -0,0 +1,49 @@ +import { NextRequest, NextResponse } from "next/server"; + +export const config = { + runtime: "edge", + regions: ["iad1"], // specify a single region +}; + +// This defines a schedule for running once a week (Sunday at 00:00 UTC) +export const dynamic = "force-dynamic"; +export const revalidate = 0; + +// Set the cron schedule to run monthly (1st day of each month at midnight) +export const maxDuration = 300; // 5 minute maximum duration +export const schedule = { cron: "0 0 1 * *" }; // Monthly at midnight on the 1st day + +export async function GET(request: NextRequest) { + try { + const secretKey = process.env.SYNC_SECRET_KEY || "your-default-secret-key"; + + // Call our sync API + const syncResponse = await fetch( + `${ + process.env.NEXT_PUBLIC_BASE_URL || request.nextUrl.origin + }/api/syncAllProfiles?secretKey=${secretKey}`, + { method: "GET" } + ); + + const result = await syncResponse.json(); + + if (!result.success) { + console.error("Sync failed:", result); + return NextResponse.json( + { message: "Weekly sync failed", error: result }, + { status: 500 } + ); + } + + return NextResponse.json({ + message: "Weekly sync completed successfully", + result, + }); + } catch (error) { + console.error("Error during scheduled sync:", error); + return NextResponse.json( + { message: "Error during weekly sync", error }, + { status: 500 } + ); + } +} diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 2ed9302..89234dd 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -112,26 +112,42 @@ export default function Home() { <GenerateStats showStats={showStats} setShowStats={setShowStats} /> <div className="w-full max-w-7xl mx-auto flex flex-col lg:grid grid-cols-3 items-center justify-start gap-4 lg:gap-12 px-8 mt-28"> - <Link - href="/worth" - className="flex w-fit gap-2 rounded border-2 border-[#f7f7f7] px-4 bg-gradient-to-r from-[#cb42b2] to-[#ecf576] bg-clip-text text-transparent p-2 font-sourcecodepro font-bold" - > - <Image - src="/assets/icons/money.svg" - alt="Leetcode Logo" - width={24} - height={24} - /> - LeetCode Worth - </Link> + <div className="flex flex-wrap gap-4"> + <Link + href="/worth" + className="flex w-fit gap-2 rounded border-2 border-[#f7f7f7] px-4 bg-gradient-to-r from-[#cb42b2] to-[#ecf576] bg-clip-text text-transparent p-2 font-sourcecodepro font-bold" + > + <Image + src="/assets/icons/money.svg" + alt="Leetcode Logo" + width={24} + height={24} + /> + LeetCode Worth + </Link> + </div> <select value={sortBy} onChange={handleSortChange} - className=" rounded border-2 border-[#f7f7f7] w-64 bg-[#0e0e0e] text-white p-2 font-sourcecodepro" + className="rounded border-2 border-[#f7f7f7] w-64 bg-[#0e0e0e] text-white p-2 font-sourcecodepro" > <option value="default">Sort By Default</option> <option value="question-solved">Sort By Questions Solved</option> </select> + <div className="flex flex-wrap gap-4"> + <Link + href="/admin/sync" + className="flex w-fit gap-2 rounded border-2 border-[#f7f7f7] px-4 bg-gradient-to-r from-[#3b82f6] to-[#22d3ee] bg-clip-text text-transparent p-2 font-sourcecodepro font-bold" + > + <Image + src="/assets/icons/refresh.svg" + alt="Sync Icon" + width={24} + height={24} + /> + Sync Profiles + </Link> + </div> </div> <div className="mt-8 max-w-7xl mx-auto place-items-center grid grid-cols-1 md:grid-cols-2 gap-y-8 xl:grid-cols-3 font-sourcecodepro gap-x-4"> diff --git a/client/src/pages/api/syncAllProfiles.ts b/client/src/pages/api/syncAllProfiles.ts new file mode 100644 index 0000000..3412e03 --- /dev/null +++ b/client/src/pages/api/syncAllProfiles.ts @@ -0,0 +1,211 @@ +import connectToDatabase from "../../lib/mongodb"; +import UserData from "../../models/userdata"; +import axios from "axios"; + +// Function to fetch updated data from LeetCode for a user (same as in syncProfile.ts) +async function fetchLeetCodeData(username: string) { + try { + // LeetCode GraphQL API endpoint + const endpoint = "https://leetcode.com/graphql"; + + // GraphQL query to fetch user profile and stats + const query = ` + query userPublicProfile($username: String!) { + matchedUser(username: $username) { + username + profile { + realName + userAvatar + ranking + } + submissionCalendar + submitStats { + acSubmissionNum { + difficulty + count + submissions + } + totalSubmissionNum { + difficulty + count + submissions + } + } + socialAccounts { + provider + profileUrl + } + } + } + `; + + // Make the request to LeetCode API + const response = await axios.post(endpoint, { + query, + variables: { username }, + }); + + const userData = response.data.data.matchedUser; + + if (!userData) { + return null; + } + + // Extract social accounts + const github = userData.socialAccounts.find( + (account: any) => account.provider === "GITHUB" + ); + const twitter = userData.socialAccounts.find( + (account: any) => account.provider === "TWITTER" + ); + const linkedin = userData.socialAccounts.find( + (account: any) => account.provider === "LINKEDIN" + ); + const website = userData.socialAccounts.find( + (account: any) => account.provider === "WEBSITE" + ); + + // Extract submission stats + const totalSolved = userData.submitStats.acSubmissionNum.find( + (stat: any) => stat.difficulty === "All" + ).count; + const easySolved = userData.submitStats.acSubmissionNum.find( + (stat: any) => stat.difficulty === "Easy" + ).count; + const easyTotal = userData.submitStats.totalSubmissionNum.find( + (stat: any) => stat.difficulty === "Easy" + ).count; + const mediumSolved = userData.submitStats.acSubmissionNum.find( + (stat: any) => stat.difficulty === "Medium" + ).count; + const mediumTotal = userData.submitStats.totalSubmissionNum.find( + (stat: any) => stat.difficulty === "Medium" + ).count; + const hardSolved = userData.submitStats.acSubmissionNum.find( + (stat: any) => stat.difficulty === "Hard" + ).count; + const hardTotal = userData.submitStats.totalSubmissionNum.find( + (stat: any) => stat.difficulty === "Hard" + ).count; + + // Format the data to match our schema + return { + profileData: { + image: userData.profile.userAvatar, + fullName: userData.profile.realName, + username: userData.username, + rank: userData.profile.ranking.toString(), + }, + aboutData: { + github: { + link: github ? github.profileUrl : "", + text: github ? github.profileUrl.split("/").pop() : "", + }, + twitter: { + link: twitter ? twitter.profileUrl : "", + text: twitter ? twitter.profileUrl.split("/").pop() : "", + }, + linkedin: { + link: linkedin ? linkedin.profileUrl : "", + text: linkedin ? linkedin.profileUrl.split("/").pop() : "", + }, + website: { + link: website ? website.profileUrl : "", + text: website ? website.profileUrl.split("/").pop() : "", + }, + }, + totalSolved, + easySolved, + easyTotal, + mediumSolved, + mediumTotal, + hardSolved, + hardTotal, + timeStamp: new Date(), + }; + } catch (error) { + console.error(`Error fetching data for ${username}:`, error); + return null; + } +} + +export default async function handler(req: any, res: any) { + const { method } = req; + const { secretKey } = req.query; + + // Add a simple secret key check to prevent unauthorized access + const expectedSecretKey = + process.env.SYNC_SECRET_KEY || "your-default-secret-key"; + if (secretKey !== expectedSecretKey) { + return res.status(401).json({ success: false, message: "Unauthorized" }); + } + + await connectToDatabase(); + + switch (method) { + case "GET": + try { + // Get all users from the database + const users = await UserData.find({}); + + if (!users || users.length === 0) { + return res + .status(404) + .json({ success: false, message: "No users found" }); + } + + const results = { + total: users.length, + successful: 0, + failed: 0, + failures: [] as string[], + }; + + // Process users with a delay between requests to avoid rate limiting + for (const user of users) { + try { + const username = user.profileData.username; + // Add a small delay to avoid overwhelming the LeetCode API + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const updatedData = await fetchLeetCodeData(username); + + if (!updatedData) { + results.failed++; + results.failures.push(username); + continue; + } + + // Update the user data + await UserData.findOneAndUpdate( + { "profileData.username": username }, + updatedData, + { new: true } + ); + + results.successful++; + } catch (error) { + console.error("Error updating user:", error); + results.failed++; + results.failures.push(user.profileData.username); + } + } + + return res.status(200).json({ + success: true, + message: `Sync completed. Total: ${results.total}, Successful: ${results.successful}, Failed: ${results.failed}`, + results, + }); + } catch (error) { + console.error("Error syncing all profiles:", error); + return res + .status(500) + .json({ success: false, error: "Internal server error" }); + } + break; + + default: + res.status(405).json({ success: false, message: "Method not allowed" }); + break; + } +} diff --git a/client/src/pages/api/syncProfile.ts b/client/src/pages/api/syncProfile.ts new file mode 100644 index 0000000..80a29d0 --- /dev/null +++ b/client/src/pages/api/syncProfile.ts @@ -0,0 +1,199 @@ +import connectToDatabase from "../../lib/mongodb"; +import UserData from "../../models/userdata"; +import axios from "axios"; + +// Function to fetch updated data from LeetCode for a user +async function fetchLeetCodeData(username: string) { + try { + // LeetCode GraphQL API endpoint + const endpoint = "https://leetcode.com/graphql"; + + // GraphQL query to fetch user profile and stats + const query = ` + query userPublicProfile($username: String!) { + matchedUser(username: $username) { + username + profile { + realName + userAvatar + ranking + } + submissionCalendar + submitStats { + acSubmissionNum { + difficulty + count + submissions + } + totalSubmissionNum { + difficulty + count + submissions + } + } + socialAccounts { + provider + profileUrl + } + } + } + `; + + // Make the request to LeetCode API + const response = await axios.post(endpoint, { + query, + variables: { username }, + }); + + const userData = response.data.data.matchedUser; + + if (!userData) { + return null; + } + + // Extract social accounts + const github = userData.socialAccounts.find( + (account: any) => account.provider === "GITHUB" + ); + const twitter = userData.socialAccounts.find( + (account: any) => account.provider === "TWITTER" + ); + const linkedin = userData.socialAccounts.find( + (account: any) => account.provider === "LINKEDIN" + ); + const website = userData.socialAccounts.find( + (account: any) => account.provider === "WEBSITE" + ); + + // Extract submission stats + const totalSolved = userData.submitStats.acSubmissionNum.find( + (stat: any) => stat.difficulty === "All" + ).count; + const easySolved = userData.submitStats.acSubmissionNum.find( + (stat: any) => stat.difficulty === "Easy" + ).count; + const easyTotal = userData.submitStats.totalSubmissionNum.find( + (stat: any) => stat.difficulty === "Easy" + ).count; + const mediumSolved = userData.submitStats.acSubmissionNum.find( + (stat: any) => stat.difficulty === "Medium" + ).count; + const mediumTotal = userData.submitStats.totalSubmissionNum.find( + (stat: any) => stat.difficulty === "Medium" + ).count; + const hardSolved = userData.submitStats.acSubmissionNum.find( + (stat: any) => stat.difficulty === "Hard" + ).count; + const hardTotal = userData.submitStats.totalSubmissionNum.find( + (stat: any) => stat.difficulty === "Hard" + ).count; + + // Format the data to match our schema + return { + profileData: { + image: userData.profile.userAvatar, + fullName: userData.profile.realName, + username: userData.username, + rank: userData.profile.ranking.toString(), + }, + aboutData: { + github: { + link: github ? github.profileUrl : "", + text: github ? github.profileUrl.split("/").pop() : "", + }, + twitter: { + link: twitter ? twitter.profileUrl : "", + text: twitter ? twitter.profileUrl.split("/").pop() : "", + }, + linkedin: { + link: linkedin ? linkedin.profileUrl : "", + text: linkedin ? linkedin.profileUrl.split("/").pop() : "", + }, + website: { + link: website ? website.profileUrl : "", + text: website ? website.profileUrl.split("/").pop() : "", + }, + }, + totalSolved, + easySolved, + easyTotal, + mediumSolved, + mediumTotal, + hardSolved, + hardTotal, + timeStamp: new Date(), + }; + } catch (error) { + console.error(`Error fetching data for ${username}:`, error); + return null; + } +} + +export default async function handler(req: any, res: any) { + const { method } = req; + const { username, secretKey } = req.query; + + // Add a simple secret key check to prevent unauthorized access + const expectedSecretKey = + process.env.SYNC_SECRET_KEY || "your-default-secret-key"; + if (secretKey !== expectedSecretKey) { + return res.status(401).json({ success: false, message: "Unauthorized" }); + } + + await connectToDatabase(); + + switch (method) { + case "GET": + try { + // If username is provided, sync only that user + if (username) { + const user = await UserData.findOne({ + "profileData.username": username, + }); + + if (!user) { + return res + .status(404) + .json({ success: false, message: "User not found" }); + } + + const updatedData = await fetchLeetCodeData(username); + + if (!updatedData) { + return res.status(400).json({ + success: false, + message: "Failed to fetch updated data", + }); + } + + // Update the user data + await UserData.findOneAndUpdate( + { "profileData.username": username }, + updatedData, + { new: true } + ); + + return res.status(200).json({ + success: true, + message: `Successfully synced profile for ${username}`, + }); + } + // If no username is provided, return error + else { + return res + .status(400) + .json({ success: false, message: "Username is required" }); + } + } catch (error) { + console.error("Error syncing profile:", error); + return res + .status(500) + .json({ success: false, error: "Internal server error" }); + } + break; + + default: + res.status(405).json({ success: false, message: "Method not allowed" }); + break; + } +} From 33b1639d7fa7e76e681b59dc55f10f2c424bf3e3 Mon Sep 17 00:00:00 2001 From: druvkotwani <druvkotwani12@gmail.com> Date: Sat, 8 Mar 2025 17:32:47 +0530 Subject: [PATCH 23/23] Update cron route configuration for monthly sync and Next.js App Router --- client/src/app/api/cron/route.ts | 32 ++++++++++++-------------------- vercel.json | 8 ++++++++ 2 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 vercel.json diff --git a/client/src/app/api/cron/route.ts b/client/src/app/api/cron/route.ts index de9f736..6465c1a 100644 --- a/client/src/app/api/cron/route.ts +++ b/client/src/app/api/cron/route.ts @@ -1,26 +1,18 @@ -import { NextRequest, NextResponse } from "next/server"; - -export const config = { - runtime: "edge", - regions: ["iad1"], // specify a single region -}; - -// This defines a schedule for running once a week (Sunday at 00:00 UTC) -export const dynamic = "force-dynamic"; -export const revalidate = 0; - -// Set the cron schedule to run monthly (1st day of each month at midnight) -export const maxDuration = 300; // 5 minute maximum duration -export const schedule = { cron: "0 0 1 * *" }; // Monthly at midnight on the 1st day - -export async function GET(request: NextRequest) { +import { NextResponse } from "next/server"; + +/** + * This route is called by Vercel Cron Jobs + * Cron configuration is set in vercel.json at the project root + * Schedule: Monthly at midnight on the 1st day of each month + */ +export async function GET() { try { const secretKey = process.env.SYNC_SECRET_KEY || "your-default-secret-key"; // Call our sync API const syncResponse = await fetch( `${ - process.env.NEXT_PUBLIC_BASE_URL || request.nextUrl.origin + process.env.NEXT_PUBLIC_BASE_URL || "https://your-app-url.com" }/api/syncAllProfiles?secretKey=${secretKey}`, { method: "GET" } ); @@ -30,19 +22,19 @@ export async function GET(request: NextRequest) { if (!result.success) { console.error("Sync failed:", result); return NextResponse.json( - { message: "Weekly sync failed", error: result }, + { message: "Monthly sync failed", error: result }, { status: 500 } ); } return NextResponse.json({ - message: "Weekly sync completed successfully", + message: "Monthly sync completed successfully", result, }); } catch (error) { console.error("Error during scheduled sync:", error); return NextResponse.json( - { message: "Error during weekly sync", error }, + { message: "Error during monthly sync", error }, { status: 500 } ); } diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..d50050d --- /dev/null +++ b/vercel.json @@ -0,0 +1,8 @@ +{ + "crons": [ + { + "path": "/api/cron", + "schedule": "0 0 1 * *" + } + ] +}