diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..74cdf3ee Binary files /dev/null and b/.DS_Store differ diff --git a/www/app/meme/layout.tsx b/www/app/meme/layout.tsx new file mode 100644 index 00000000..706c1ad3 --- /dev/null +++ b/www/app/meme/layout.tsx @@ -0,0 +1,81 @@ +import type { Metadata } from "next"; +import { Outfit, Space_Grotesk } from "next/font/google"; +import "./meme.css"; + +import Script from "next/script"; + + +const outfit = Outfit({ + subsets: ["latin"], + variable: "--font-outfit", + display: "swap", + weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], +}); + +const spaceGrotesk = Space_Grotesk({ + subsets: ["latin"], + variable: "--font-space-grotesk", + display: "swap", + weight: ["300", "400", "500", "600", "700"], +}); + +export const metadata: Metadata = { + title: "Devb.io - Effortless Portfolios for Developers", + description: "Effortless Portfolios for Developers", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + + {/* Google Analytics */} + + + + + + {children} + + + + ); +} diff --git a/www/app/meme/meme.css b/www/app/meme/meme.css new file mode 100644 index 00000000..8a4d2a5e --- /dev/null +++ b/www/app/meme/meme.css @@ -0,0 +1,14 @@ +body { + background-color: black; + height: 100%; + +} + +.body-container { + background-image: radial-gradient(circle at var(--x) var(--y), var(--color1) 0%, var(--color2) var(--position2)), url('/assets/gorilla-bg-min.jpg'); + background-position: center top; + background-size: cover; + height: 100vh; + + +} \ No newline at end of file diff --git a/www/app/meme/page.tsx b/www/app/meme/page.tsx new file mode 100644 index 00000000..5d9e7379 --- /dev/null +++ b/www/app/meme/page.tsx @@ -0,0 +1,385 @@ +"use client" +import { useEffect, useRef, useState } from "react"; +import "./meme.css"; +import { LinneChart } from "@/components/meme-comp/LinneChart"; +import MemeGenerator from "@/components/meme-comp/MemeGenerator"; +import { getGithubInfo, getMemeProfileData } from "@/lib/api"; +import { ContributionsData } from "@/types/types"; +import { Container } from "@/components/meme-comp/Container"; + +import { ButtonArrow } from "@/public/assets/buttonArrow"; +import { AnimatePresence, motion } from "framer-motion"; +import html2canvas from "html2canvas"; +import { Loader } from "@/components/meme-comp/Loader"; +import { AxiosError } from "axios"; + +function page(){ + const [myUserName, setMyUserName] = useState(""); + const [comparerUserInput, setComparerUserInput] = useState(""); + const [myData, setMyData] = useState(null); + const [comparerData, setComparerData] = useState(null); + const [myName, setMyName] = useState(""); + const [myTotalContribution, setMyTotalContribution] = useState(0); + const [comparertotalContri, setComparertotalContri] = useState(0); + const [comparerName, setComparerName] = useState(""); + const [memeIndex, setMemeIndex] = useState(0); + const [isLoading, setIsLoading] = useState(false); + const [winner, setWinner] = useState(null); + const [loser, setLoser] = useState(null); + const [haveData, setHaveData] = useState(false); + const [myError, setMyError] = useState(""); + const [comparerError, setComparerError] = useState(""); + const headingContainer = useRef(null); + const containerRef = useRef(null); + + const handleSubmit = async (e: React.FormEvent): Promise => { + e.preventDefault(); + + // Clear previous data + setMyData(null); + setComparerData(null); + + // Start loading + setIsLoading(true); + + if (myUserName === comparerUserInput) { + setMyError("You can't compare to yourself :')") + setIsLoading(false); + return; + } + try { + const [ + myProfileData, + myGitHubInfo, + comparerProfileData, + comparerGitHubInfo, + ] = await Promise.all([ + getMemeProfileData(myUserName), + getGithubInfo(myUserName), + getMemeProfileData(comparerUserInput), + getGithubInfo(comparerUserInput), + ]); + + setMyData(myProfileData); + setMyName(myGitHubInfo?.name || myUserName); + setComparerData(comparerProfileData); + setComparerName(comparerGitHubInfo?.name || comparerUserInput); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response && axiosError.response.status === 404) { + if (axiosError.config?.url?.includes(myUserName)) { + setMyError("Username not found"); + } else if (axiosError.config?.url?.includes(comparerUserInput)) { + setComparerError("Comparer username not found"); + } + } else { + console.error("Error fetching data:", error); + } + } finally { + setIsLoading(false); + } + }; + + const getMemeIndex = (myContribution: number, comparerContribution: number): number => { + if (myContribution === 0 && comparerContribution === 0) { + return 0; + } + const maxContribution = Math.max(myContribution, comparerContribution); + const difference = Math.abs(myContribution - comparerContribution); + const percentageDifference = (difference / maxContribution) * 100; + const index = Math.floor(percentageDifference / 10); + return Math.min(index, 9); + }; + + const downloadImage = (e: React.MouseEvent): void => { + if (!containerRef.current || !headingContainer.current) return; + + if (isMobile) { + headingContainer.current.style.display = 'none'; + } + e.currentTarget.style.display = "none"; + html2canvas(containerRef.current, { backgroundColor: "#2b3137" }) + .then((canvas) => { + const link = document.createElement("a"); + link.href = canvas.toDataURL("image/png"); + link.download = "github-compare.png"; + link.click(); + }) + .catch((error) => { + console.error("Error capturing the image:", error); + }) + .finally(() => { + e.currentTarget.style.display = "flex"; + if (headingContainer.current) { + headingContainer.current.style.display = 'flex'; + } + }); + }; + + useEffect(() => { + if (myData && comparerData) { + const arrOfMyContri = Object.values(myData.total); + const arrOfComparerContri = Object.values(comparerData.total); + + const myTotalContribution = arrOfMyContri.reduce( + (total, curVal) => total + curVal, + 0 + ); + setMyTotalContribution(myTotalContribution); + + const comparerTotalContribution = arrOfComparerContri.reduce( + (total, curVal) => total + curVal, + 0 + ); + setComparertotalContri(comparerTotalContribution); + + const memeIndex = getMemeIndex( + myTotalContribution, + comparerTotalContribution + ); + setMemeIndex(memeIndex); + const myfirstName = myName.split(" ")[0]; + const comparerfirstName = comparerName.split(" ")[0]; + + if (myTotalContribution > comparerTotalContribution) { + setWinner(myfirstName); + setLoser(comparerfirstName); + } else { + setWinner(comparerfirstName); + setLoser(myfirstName); + } + setHaveData(true); + } + }, [myData, comparerData, myName, comparerName]); + + const transition = { duration: 1, ease: "easeInOut" }; + + const [isMobile, setIsMobile] = useState(false); + useEffect(() => { + // Initialize mobile state after component mounts + const checkIsMobile = () => { + if (typeof window !== 'undefined') { + setIsMobile(window.innerWidth <= 768); + } + }; + + const handleResize = (): void => { + if (typeof window !== 'undefined') { + setIsMobile(window.innerWidth <= 768); + } + }; + + // Set initial value + checkIsMobile(); + + // Add event listener + if (typeof window !== 'undefined') { + window.addEventListener("resize", handleResize); + } + + return () => { + if (typeof window !== 'undefined') { + window.removeEventListener("resize", handleResize); + } + }; + }, []); + + const fontSize = haveData + ? isMobile + ? "34px" + : "52px" + : isMobile + ? "44px" + : "68px"; + + const clearDataCallBack = (): void => { + setMyData(null); + setMyName(''); + setComparerData(null); + setComparerName(''); + setHaveData(false); + setLoser(null); + setWinner(null); + setMyError(''); + setComparerError(''); + }; + + return ( + + + +
+ + + GitHub Profile
{" "} + + Comparison + +
+
+ + {!haveData && ( + +
+ {myError && ( +

+ {myError} +

+ )} + setMyUserName(e.currentTarget.value)} + placeholder="Your Username" + required + className={`border bg-white shadow-[0px_4px_50px_rgba(0,_255,_87,_0.5)] w-[294px] ${ + myError ? " outline outline-red-500" : "" + } py-3 px-4 text-[18px] rounded-full`} + /> +
+
+ {isLoading ? : VS} +
+
+ {comparerError && ( +

{comparerError}

+ )} + + setComparerUserInput(e.currentTarget.value) + } + placeholder="Enter other persons username" + required + className={`border bg-white shadow-[0px_4px_50px_rgba(0,_255,_87,_0.5)] w-[294px] ${ + comparerError ? " outline outline-red-500" : "" + } py-3 px-4 text-[18px] rounded-full`} + /> +
+
+ +
+
+ )} +
+ + + {myData && comparerData && ( + +
+ +
+
+
+
+

+ {myName} +

+

+ Total Contribution : {myTotalContribution} +

+
+
+
+
+
+

+ {comparerName} +

+

+ Total Contribution : {comparertotalContri} +

+
+
+
+ +
+
+
+ {winner && loser && ( + + )} +
+ +
+
+ )} +
+
+
+
+ ); +} + +export default page; diff --git a/www/app/page.tsx b/www/app/page.tsx index d8650ad9..3720414e 100644 --- a/www/app/page.tsx +++ b/www/app/page.tsx @@ -8,6 +8,7 @@ import ProfileCard from "@/components/profile-card/server"; import HowItWorksCard from "@/components/how-it-works-card/server"; import NextContributorCard from "@/components/next-contributor-card"; import { Compare } from "@/components/ui/compare"; +import CompareButton from "@/components/CompareButton"; // Types interface Profile { @@ -343,6 +344,9 @@ export default async function Home() { )} +
+ +