diff --git a/components/common/NavBar.tsx b/components/common/NavBar.tsx index 97f2906..5432c45 100644 --- a/components/common/NavBar.tsx +++ b/components/common/NavBar.tsx @@ -169,7 +169,7 @@ const logoContainerVariants: Variants = { transition: { duration: 4, type: "spring", - stiffness: 20, + stiffness: 15, }, }, }; diff --git a/components/dashboard/NavBar.tsx b/components/dashboard/NavBar.tsx index b8f4a90..27b106f 100644 --- a/components/dashboard/NavBar.tsx +++ b/components/dashboard/NavBar.tsx @@ -1,35 +1,54 @@ import { m, Variants } from "framer-motion"; +import { useRouter } from "next/router"; import Link from "next/link"; function NavBar() { return ( - <m.div - className="mr-2 flex h-full w-24 flex-col items-center justify-between bg-zinc-800 p-1" - variants={navContainerVariants} - initial="initial" - animate="animate" - > - <m.div className="flex flex-col pt-5" variants={navStripVariants}> - <m.div variants={navIconVariants} className="pb-5"> - <Link href="/dashboard"> - <DashIcon /> - </Link> - </m.div> - <m.div variants={navIconVariants} className="pb-5"> - <Link href="/dashboard/ranking"> - <RankingIcon /> - </Link> - </m.div> - </m.div> + <div className="m-3"> <m.div - className="flex w-full flex-col items-center justify-center pb-5" - variants={navStripVariants} + className="flex min-h-[5rem] w-full flex-row items-center justify-between rounded-2xl bg-zinc-800 p-1 lg:h-full lg:w-24 lg:flex-col" + variants={navContainerVariants} + initial="initial" + animate="animate" > - <Link href="/"> - <ExitIcon /> - </Link> + <m.div + className="flex flex-row items-center justify-center pl-5 lg:flex-col lg:pl-0 lg:pt-5" + variants={navStripVariants} + > + <m.div variants={navIconVariants} className="pr-5 lg:pr-0 lg:pb-3"> + <ActiveLink href="/dashboard"> + <DashIcon /> + </ActiveLink> + </m.div> + <m.div + variants={navIconVariants} + className="pr-5 lg:pr-0 lg:pt-3 lg:pb-3" + > + <ActiveLink href="/dashboard/ranking"> + <RankingIcon /> + </ActiveLink> + </m.div> + </m.div> + <m.div + className="flex flex-row items-center justify-center pr-5 lg:w-full lg:flex-col lg:pr-0 lg:pb-5" + variants={navStripVariants} + > + <m.div + className="fill-white stroke-white" + whileHover={{ + color: "#fca311", + }} + whileTap={{ + color: "#dd4444", + }} + > + <Link href="/"> + <ExitIcon /> + </Link> + </m.div> + </m.div> </m.div> - </m.div> + </div> ); } @@ -43,6 +62,12 @@ const NavSvgWrap = (props: { children: React.ReactNode }) => { x={0} y={0} origin="center" + whileHover={{ + scale: 1.1, + }} + whileTap={{ + scale: 0.9, + }} > {props.children} </m.svg> @@ -54,9 +79,9 @@ const DashIcon = () => { <NavSvgWrap> <m.path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z" - fill="white" - stroke="white" strokeWidth="1" + stroke="currentColor" + fill="currentColor" /> </NavSvgWrap> ); @@ -67,9 +92,9 @@ const ExitIcon = () => { <NavSvgWrap> <m.path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z" - fill="white" - stroke="white" strokeWidth="1" + stroke="currentColor" + fill="currentColor" /> </NavSvgWrap> ); @@ -80,14 +105,29 @@ const RankingIcon = () => { <NavSvgWrap> <m.path d="M7.5 21H2V9h5.5v12zm7.25-18h-5.5v18h5.5V3zM22 11h-5.5v10H22V11z" - fill="white" - stroke="white" strokeWidth="1" + stroke="currentColor" + fill="currentColor" /> </NavSvgWrap> ); }; +const ActiveLink = (props: { href: string; children: React.ReactNode }) => { + const router = useRouter(); + let styling = "text-white"; + console.log(router.pathname); + console.log(props.href); + if (router.pathname === props.href) { + styling = "text-[#a855f7]"; + } + return ( + <Link href={props.href} className={styling}> + {props.children} + </Link> + ); +}; + const navContainerVariants: Variants = { initial: { opacity: 0, diff --git a/layouts/DashLayout.tsx b/layouts/DashLayout.tsx index 58d6344..d212598 100644 --- a/layouts/DashLayout.tsx +++ b/layouts/DashLayout.tsx @@ -34,7 +34,7 @@ function DashLayout(props: DashLayoutProps) { <meta property="og:site_name" content="InvestBot" /> </Head> - <div className="flex h-screen w-screen flex-row overflow-hidden"> + <div className="flex h-screen w-screen flex-col overflow-hidden lg:flex-row"> {/* dashboard nav bar */} <LazyMotion features={domAnimation}> <AnimatePresence mode="wait"> @@ -46,7 +46,7 @@ function DashLayout(props: DashLayoutProps) { <AnimatePresence mode="wait"> <m.div key={router.route.concat("layout-fade")} - className="h-screen w-screen" + className="w-screen overflow-y-scroll" variants={containerVariants} initial="initial" animate="animate" diff --git a/pages/api/7tv/emotes.ts b/pages/api/7tv/emotes.ts index 3d49d13..ab4694e 100644 --- a/pages/api/7tv/emotes.ts +++ b/pages/api/7tv/emotes.ts @@ -5,8 +5,6 @@ type Data = { [key: string]: any; }; -const secret = process.env.NEXTAUTH_SECRET; - export default async function handler( req: NextApiRequest, res: NextApiResponse<Data> diff --git a/pages/api/fakeRanking.ts b/pages/api/fakeRanking.ts new file mode 100644 index 0000000..0b5731d --- /dev/null +++ b/pages/api/fakeRanking.ts @@ -0,0 +1,208 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +type Data = { + [key: string]: any; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse<Data> +) { + const sortBy = req.query.s ? (req.query.s as string) : undefined; + + let data = fakeData; + if (sortBy) { + if (sortBy === "netWorth") { + data = data.sort((a, b) => b.netWorth - a.netWorth); + } else if (sortBy === "dailyChange") { + data = data.sort((a, b) => b.dailyChange - a.dailyChange); + } else if (sortBy === "dailyChangePercent") { + data = data.sort((a, b) => b.dailyChangePercent - a.dailyChangePercent); + } else if (sortBy === "shares") { + data = data.sort((a, b) => b.shares - a.shares); + } else if (sortBy === "points") { + data = data.sort((a, b) => b.points - a.points); + } else if (sortBy === "name") { + data = data.sort((a, b) => a.name.localeCompare(b.name)); + } + } + + res.status(200).json({ data }); +} + +interface fakeDataEntry { + id: number; + name: string; + netWorth: number; + points: number; + shares: number; + dailyChange: number; + dailyChangePercent: number; +} + +const fakeData: fakeDataEntry[] = [ + { + id: 4, + name: "3zachm", + netWorth: 10030, // stocks + points + points: 70, /// uninvested points + shares: 20, + dailyChange: -500, + dailyChangePercent: -0.523, + }, + { + id: 1, + name: "ModulatingForce", + netWorth: 142910, + points: 10020, + shares: 200, + dailyChange: 5420, + dailyChangePercent: 0.14, + }, + { + id: 2, + name: "notohh", + netWorth: 153495392, + points: 10020, + shares: 2432, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 3, + name: "SecondSock", + netWorth: 153495, + points: 15020, + shares: 20, + dailyChange: 5432, + dailyChangePercent: 0.104, + }, + { + id: 0, + name: "Ente", + netWorth: 429481824, + points: 1002022, + shares: 94214, + dailyChange: 3294444224, + dailyChangePercent: 0.94, + }, + { + id: 5, + name: "ObnoxiouslyLongNameWICKED", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 6, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 7, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 8, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 9, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 10, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 11, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 12, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 13, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 14, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 15, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 16, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, + { + id: 17, + name: "User", + netWorth: 0, + points: 100, + shares: 0, + dailyChange: 0, + dailyChangePercent: 0, + }, +]; + +export type { fakeDataEntry }; diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index 112747d..6595e0a 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -11,37 +11,37 @@ function Dashboard() { <title>Dashboard - InvestBot</title> </Head> <m.div - className="inline-grid h-full w-full grid-cols-2 pt-2 pr-2 xl:grid-cols-5" + className="inline-grid w-full grid-cols-1 pt-2 pl-2 lg:h-full lg:grid-cols-5 lg:pl-0 lg:pr-2" variants={gridContainerVariants} initial="initial" animate="animate" > <m.div - className="col-span-1 m-2 bg-zinc-800" + className="col-span-1 m-2 rounded-2xl bg-zinc-800 p-3" variants={gridItemVariants} > 1 </m.div> <m.div - className="col-span-3 m-2 bg-zinc-800" + className="col-span-1 row-span-3 m-2 rounded-2xl bg-zinc-800 p-3 lg:col-span-3 lg:row-span-1" variants={gridItemVariants} > 2 </m.div> <m.div - className="col-span-1 m-2 bg-zinc-800" + className="col-span-1 m-2 rounded-2xl bg-zinc-800 p-3" variants={gridItemVariants} > 3 </m.div> <m.div - className="col-span-4 m-2 bg-zinc-800" + className="col-span-1 row-span-4 m-2 rounded-2xl bg-zinc-800 p-3 lg:col-span-4 lg:row-span-1" variants={gridItemVariants} > 4 </m.div> <m.div - className="col-span-1 m-2 bg-zinc-800" + className="col-span-1 m-2 rounded-2xl bg-zinc-800 p-3" variants={gridItemVariants} > 5 diff --git a/pages/dashboard/ranking.tsx b/pages/dashboard/ranking.tsx index 1c35eed..52b041a 100644 --- a/pages/dashboard/ranking.tsx +++ b/pages/dashboard/ranking.tsx @@ -1,26 +1,101 @@ import { m, Variants } from "framer-motion"; import Head from "next/head"; -import { ReactElement } from "react"; +import { ReactElement, useEffect, useState } from "react"; import DashLayout from "../../layouts/DashLayout"; +import { fakeDataEntry } from "../api/fakeRanking"; + +function Ranking() { + const [sortBy, setSortBy] = useState("netWorth"); + const [fakeData, setFakeData] = useState([]); + + useEffect(() => { + fetch(`/api/fakeRanking?s=${sortBy}`) + .then((res) => res.json()) + .then((data) => { + setFakeData(data.data); + }); + }, [sortBy]); -function Dashboard() { return ( <> <Head> <title>Ranking - InvestBot</title> </Head> - <div className="flex min-h-screen flex-col items-center justify-start py-2"> + <div className="flex w-full justify-center"> <m.div - className="grid w-[90vw] grid-cols-1 py-2 sm:grid-cols-2 md:grid-cols-4 lg:w-[75vw]" + className="ml-3 flex w-full flex-col items-center justify-start font-spaceMono font-semibold lg:ml-0" variants={containerVariants} initial="initial" animate="animate" > - <m.div - className="col-span-1 flex w-full items-center justify-center bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text pt-[200px] pb-[100px] font-plusJakarta text-transparent sm:col-span-2 md:col-span-4" + <m.h1 + className="hidden bg-gradient-to-tr from-purple-500 to-purple-100 bg-clip-text py-10 text-center font-plusJakarta text-5xl font-bold text-white text-transparent lg:block lg:text-6xl" variants={headerVariants} > - <m.h1 className="text-6xl">rankings</m.h1> + Top Investors + </m.h1> + <m.div + className="inline-grid w-full rounded-t-2xl bg-zinc-800 bg-opacity-70 p-3 pt-4 text-xl backdrop-blur lg:text-2xl" + variants={rankingCardVariants} + initial="initial" + animate="animate" + > + <m.div className="inline-grid w-full grid-flow-col grid-cols-[0.75fr_4fr_3fr_2fr] gap-2 border-b-2 border-zinc-400 px-5 pb-3 text-right text-gray-300 md:grid-cols-[0.5fr_4fr_repeat(3,_2fr)_1.5fr]"> + <m.h1 className="text-left md:text-center">#</m.h1> + <m.h1 + className="overflow-hidden overflow-ellipsis whitespace-nowrap text-left" + onClick={() => setSortBy("name")} + > + Name + </m.h1> + <m.h1 onClick={() => setSortBy("netWorth")}>Assets</m.h1> + <m.h1 + className="hidden md:block" + onClick={() => setSortBy("points")} + > + Points + </m.h1> + <m.h1 + className="hidden md:block" + onClick={() => setSortBy("shares")} + > + Shares + </m.h1> + <m.h1 onClick={() => setSortBy("dailyChange")}>Daily</m.h1> + </m.div> + { + // TODO: add arrow to show which column is being sorted by and which direction + fakeData.map((entry: fakeDataEntry, index) => { + let changeClass = " text-lime-500"; + if (entry.dailyChangePercent < 0) { + changeClass = " text-red-500"; + } + return ( + <m.div key={entry.id}> + <m.div className="inline-grid w-full grid-flow-col grid-cols-[1fr_4fr_3fr_2fr] gap-2 border-b-2 border-zinc-700 px-5 py-2 text-right md:grid-cols-[0.5fr_4fr_repeat(3,_2fr)_1.5fr]"> + <m.h1 className="text-left md:text-center"> + {index + 1} + </m.h1> + <m.h1 className="overflow-hidden overflow-ellipsis whitespace-nowrap text-left"> + {entry.name} + </m.h1> + <m.h1>{entry.netWorth.toLocaleString("en-US")}</m.h1> + <m.h1 className="hidden md:block"> + {entry.points.toLocaleString("en-US")} + </m.h1> + <m.h1 className="hidden md:block"> + {entry.shares.toLocaleString("en-US")} + </m.h1> + <m.h1 className={changeClass}> + {( + Math.round(entry.dailyChangePercent * 1000) / 10 + ).toFixed(1) + "%"} + </m.h1> + </m.div> + </m.div> + ); + }) + } </m.div> </m.div> </div> @@ -48,23 +123,42 @@ const containerVariants: Variants = { const headerVariants: Variants = { initial: { opacity: 0, - y: 100, + y: -100, }, animate: { opacity: 1, y: 0, transition: { - delay: 0.5, + delay: 1.0, duration: 1.0, type: "spring", bounce: 0.5, - stiffness: 80, + stiffness: 60, }, }, }; -Dashboard.getLayout = function getLayout(page: ReactElement) { +const rankingCardVariants: Variants = { + initial: { + opacity: 0, + y: 300, + }, + animate: { + opacity: 1, + y: 0, + transition: { + duration: 3, + delayChildren: 0.5, + staggerChildren: 0.25, + type: "spring", + bounce: 0.5, + stiffness: 40, + }, + }, +}; + +Ranking.getLayout = function getLayout(page: ReactElement) { return <DashLayout>{page}</DashLayout>; }; -export default Dashboard; +export default Ranking; diff --git a/styles/globals.css b/styles/globals.css index 800f679..52fd3d1 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -3,6 +3,7 @@ @tailwind utilities; @import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"); @import url("https://fonts.googleapis.com/css?family=Plus+Jakarta+Sans:300,400,500,700&display=swap"); +@import url("https://fonts.googleapis.com/css?family=Space+Mono:300,400,500,700&display=swap"); html, body { diff --git a/tailwind.config.js b/tailwind.config.js index 68e017a..42ea415 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -10,6 +10,7 @@ module.exports = { fontFamily: { roboto: ["Roboto", "sans-serif"], plusJakarta: ["Plus Jakarta Sans", "sans-serif"], + spaceMono: ["Space Mono", "monospace"], }, }, },