diff --git a/layouts/DashLayout.tsx b/layouts/DashLayout.tsx index 612004c..0ad3018 100644 --- a/layouts/DashLayout.tsx +++ b/layouts/DashLayout.tsx @@ -11,11 +11,24 @@ import NavBar from "../components/dashboard/NavBar"; interface DashLayoutProps { children: React.ReactNode; + metaTags: { + title?: string; + ogTitle?: string; + description?: string; + ogDescription?: string; + content?: string; + imageUrl?: string; + themeColor?: string; + misc?: { + [key: string]: string; + }; + }; } function DashLayout(props: DashLayoutProps) { // get the current route for animation purposes const router = useRouter(); + const title = props.metaTags.title ?? "Dashboard - toffee"; return ( - Dashboard - toffee - + {title} + - - + + + + - - + {props.metaTags.misc && + Object.keys(props.metaTags.misc).map((key) => { + return ( + + ); + })}
diff --git a/layouts/HomeLayout.tsx b/layouts/HomeLayout.tsx index 3606082..0b7c24b 100644 --- a/layouts/HomeLayout.tsx +++ b/layouts/HomeLayout.tsx @@ -15,6 +15,18 @@ import { NavTemplate } from "./NavTemplates"; interface HomeLayoutProps { navOptions: NavTemplate[]; children: React.ReactNode; + metaTags: { + title?: string; + ogTitle?: string; + description?: string; + ogDescription?: string; + content?: string; + imageUrl?: string; + themeColor?: string; + misc?: { + [key: string]: string; + }; + }; } function HomeLayout(props: HomeLayoutProps) { @@ -22,6 +34,7 @@ function HomeLayout(props: HomeLayoutProps) { const navOptions = props.navOptions; // get the current route for animation purposes const router = useRouter(); + const title = props.metaTags.title ?? "Dashboard - toffee"; return ( - toffee - + {title} + - - + + + + - - + {props.metaTags.misc && + Object.keys(props.metaTags.misc).map((key) => { + return ( + + ); + })} diff --git a/misc/redis.ts b/misc/redis.ts index d118502..282ee1f 100644 --- a/misc/redis.ts +++ b/misc/redis.ts @@ -24,10 +24,11 @@ export function createRedisInstance(config = getRedisConfiguration()) { lazyConnect: true, showFriendlyErrorStack: true, enableAutoPipelining: true, - maxRetriesPerRequest: 0, + maxRetriesPerRequest: 3, retryStrategy: (times: number) => { if (times > 3) { - throw new Error(`[Redis] Could not connect after ${times} attempts`); + console.log(`[Redis] Could not connect after ${times} attempts`); + return undefined; } return Math.min(times * 200, 1000); @@ -45,11 +46,11 @@ export function createRedisInstance(config = getRedisConfiguration()) { const redis = new Redis(options); redis.on("error", (error: unknown) => { - console.warn("[Redis] Error connecting", error); + console.warn("[Redis] ", error); }); return redis; } catch (e) { - throw new Error(`[Redis] Could not create a Redis instance`); + console.log(`[Redis] Could not create a Redis instance`); } } diff --git a/pages/about.tsx b/pages/about.tsx deleted file mode 100644 index c3cc05e..0000000 --- a/pages/about.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Head from "next/head"; -import { ReactElement } from "react"; -import HomeLayout from "../layouts/HomeLayout"; -import { homeMain } from "../layouts/NavTemplates"; - -function About() { - return ( - <> - - About - toffee - -
-

about

-
- - ); -} - -About.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default About; diff --git a/pages/api/7tv/emotes.ts b/pages/api/7tv/emotes.ts deleted file mode 100644 index 4718558..0000000 --- a/pages/api/7tv/emotes.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { createRedisInstance } from "../../../misc/redis"; -import { getChannelEmotes, getGlobalEmotes } from "../../../misc/7TVAPI"; - -type Data = { - [key: string]: any; -}; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const redis = createRedisInstance(); - - try { - const channel = req.query.c - ? await getChannelEmotes(redis, req.query.c as string) - : undefined; - const global = await getGlobalEmotes(redis); - redis.quit(); - res.status(200).json({ channel, global }); - } catch (e) { - console.log(e); - res - .status(500) - .json({ error: { message: "7TV or internal API is down", code: 10000 } }); - } -} diff --git a/pages/api/bttv/emotes.ts b/pages/api/bttv/emotes.ts deleted file mode 100644 index fbdf506..0000000 --- a/pages/api/bttv/emotes.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { createRedisInstance } from "../../../misc/redis"; -import { getUserByID, getGlobalEmotes } from "../../../misc/BTTVAPI"; - -type Data = { - [key: string]: any; -}; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const redis = createRedisInstance(); - - try { - const channel = req.query.c - ? (await getUserByID(redis, req.query.c as string)).channelEmotes - : undefined; - const global = await getGlobalEmotes(redis); - redis.quit(); - res.status(200).json({ channel, global }); - } catch (e) { - console.log(e); - res - .status(500) - .json({ - error: { message: "BTTV or internal API is down", code: 10200 }, - }); - } -} diff --git a/pages/api/emotes.ts b/pages/api/emotes.ts new file mode 100644 index 0000000..00b2353 --- /dev/null +++ b/pages/api/emotes.ts @@ -0,0 +1,78 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { createRedisInstance } from "../../misc/redis"; +import { + getGlobalEmotes as get7TVGlobalEmotes, + getChannelEmotes as get7TVChannelEmotes, +} from "../../misc/7TVAPI"; +import { + getGlobalEmotes as getBTTVGlobalEmotes, + getUserByID as getBTTVUser, +} from "../../misc/BTTVAPI"; +import { + getGlobalEmotes as getFFZGlobalEmotes, + getEmoteSet as getFFZEmoteSet, +} from "../../misc/FFZAPI"; +import { + getGlobalEmotes as getTwitchGlobalEmotes, + getChannelEmotes as getTwitchChannelEmotes, +} from "../../misc/TwitchAPI"; + +type Data = { + [key: string]: any; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const redis = createRedisInstance(); + + if (!redis) { + res + .status(500) + .json({ error: { message: "Internal API is down", code: 50000 } }); + return; + } + + try { + const cachedJSON = await redis.get("ALL_EMOTES"); + if (cachedJSON) { + const jsonRes = JSON.parse(cachedJSON); + redis.quit(); + res.status(200).json(jsonRes); + return; + } + const ffzGlobal = await getFFZGlobalEmotes(redis); + const jsonRes = { + "7tv": { + global: (await get7TVGlobalEmotes(redis)).namedEmoteSet.emotes, + channel: (await get7TVChannelEmotes(redis, "61ad997effa9aba101bcfddf")) + .user.emote_sets[0].emotes, + }, + bttv: { + global: await getBTTVGlobalEmotes(redis), + channel: (await getBTTVUser(redis, "56418014")).channelEmotes, + }, + ffz: { + global: ffzGlobal.sets["3"].emoticons.concat( + ffzGlobal.sets["4330"].emoticons + ), + channel: (await getFFZEmoteSet(redis, "341402")).set.emoticons, + }, + twitch: { + global: (await getTwitchGlobalEmotes(redis)).data, + channel: (await getTwitchChannelEmotes(redis, "56418014")).data, + }, + }; + // cache emotelist for 20 minutes + await redis.set("ALL_EMOTES", JSON.stringify(jsonRes), "EX", 1200); + redis.quit(); + + res.status(200).json(jsonRes); + } catch (e) { + console.log(e); + res + .status(500) + .json({ error: { message: "Internal Emote API error", code: 10000 } }); + } +} diff --git a/pages/api/fakeUsers.ts b/pages/api/fakeUsers.ts index 052e9dc..172c15c 100644 --- a/pages/api/fakeUsers.ts +++ b/pages/api/fakeUsers.ts @@ -16,6 +16,12 @@ export default async function handler( const sortAsc = req.query.a ? (req.query.a as string) : undefined; const redis = createRedisInstance(); + if (!redis) { + res.status(500).json({ + error: { message: "Internal API is down", code: 50100 }, + }); + return; + } let data = fakeData; // calculate all net worths @@ -114,7 +120,7 @@ export default async function handler( interface asset { name: string; count: number; - provider: "7tv" | "bttv" | "ffz" | "ttv"; + provider: "7tv" | "bttv" | "ffz" | "twitch"; } interface fakeDataEntry { id: number; @@ -185,7 +191,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfSigh", count: 1, - provider: "ttv", + provider: "twitch", }, { name: "GabeN", @@ -230,7 +236,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfHeart", count: 98, - provider: "ttv", + provider: "twitch", }, { name: "Catge", @@ -275,7 +281,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfRave", count: 5, - provider: "ttv", + provider: "twitch", }, ], badges: [adminBadge, botDevBadge], @@ -310,7 +316,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfMelt", count: 16, - provider: "ttv", + provider: "twitch", }, ], badges: [CEOBadge, adminBadge], @@ -350,7 +356,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfAngy", count: 90, - provider: "ttv", + provider: "twitch", }, ], badges: [adminBadge, botDevBadge], @@ -414,7 +420,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfHug", count: 19, - provider: "ttv", + provider: "twitch", }, ], }, @@ -433,7 +439,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfLUL", count: 9, - provider: "ttv", + provider: "twitch", }, { name: "peepoSnow", @@ -487,7 +493,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfPain", count: 37, - provider: "ttv", + provider: "twitch", }, ], }, @@ -526,7 +532,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfKnuckles", count: 2, - provider: "ttv", + provider: "twitch", }, ], }, @@ -565,7 +571,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfCheer", count: 54, - provider: "ttv", + provider: "twitch", }, ], }, @@ -604,7 +610,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfBonk", count: 77, - provider: "ttv", + provider: "twitch", }, ], }, @@ -648,7 +654,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfSit", count: 53, - provider: "ttv", + provider: "twitch", }, ], }, @@ -711,7 +717,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfGamba", count: 32, - provider: "ttv", + provider: "twitch", }, ], }, @@ -745,7 +751,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfFlower", count: 33, - provider: "ttv", + provider: "twitch", }, ], }, @@ -779,7 +785,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfFlower", count: 79, - provider: "ttv", + provider: "twitch", }, ], }, @@ -813,7 +819,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfSad", count: 2, - provider: "ttv", + provider: "twitch", }, ], }, @@ -847,7 +853,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfHeart", count: 63, - provider: "ttv", + provider: "twitch", }, ], }, diff --git a/pages/api/ffz/emotes.ts b/pages/api/ffz/emotes.ts deleted file mode 100644 index dbc58c9..0000000 --- a/pages/api/ffz/emotes.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { createRedisInstance } from "../../../misc/redis"; -import { getEmoteSet, getGlobalEmotes } from "../../../misc/FFZAPI"; - -type Data = { - [key: string]: any; -}; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const redis = createRedisInstance(); - - try { - const channel = req.query.s - ? (await getEmoteSet(redis, req.query.s as string)).set.emoticons - : undefined; - let global = await getGlobalEmotes(redis); - // set global emotes to be the three sets within the global object ("3", "4330") - global = global.sets["3"].emoticons.concat(global.sets["4330"].emoticons); - redis.quit(); - res.status(200).json({ channel, global }); - } catch (e) { - console.log(e); - res - .status(500) - .json({ error: { message: "FFZ or internal API is down", code: 10300 } }); - } -} diff --git a/pages/api/twitch/emotes.ts b/pages/api/twitch/emotes.ts deleted file mode 100644 index 9452544..0000000 --- a/pages/api/twitch/emotes.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { createRedisInstance } from "../../../misc/redis"; -import { getChannelEmotes, getGlobalEmotes } from "../../../misc/TwitchAPI"; - -type Data = { - [key: string]: any; -}; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const redis = createRedisInstance(); - - try { - const channel = req.query.c - ? (await getChannelEmotes(redis, req.query.c as string)).data - : undefined; - const global = (await getGlobalEmotes(redis)).data; - redis.quit(); - res.status(200).json({ channel, global }); - } catch (e) { - console.log(e); - res - .status(500) - .json({ - error: { message: "Twitch or internal API is down", code: 10100 }, - }); - } -} diff --git a/pages/contact.tsx b/pages/contact.tsx deleted file mode 100644 index 24a1fe9..0000000 --- a/pages/contact.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Head from "next/head"; -import { ReactElement } from "react"; -import HomeLayout from "../layouts/HomeLayout"; -import { homeMain } from "../layouts/NavTemplates"; - -function About() { - return ( - <> - - Contact - toffee - -
-

contact

-
- - ); -} - -About.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default About; diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index 92e64e3..0c88f80 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -1,5 +1,4 @@ import { m, Variants } from "framer-motion"; -import Head from "next/head"; import { ReactElement } from "react"; import DashLayout from "../../layouts/DashLayout"; @@ -7,9 +6,6 @@ import DashLayout from "../../layouts/DashLayout"; function Dashboard() { return ( <> - - Dashboard - toffee - {page}; + const metaTags = {}; + return {page}; }; export default Dashboard; diff --git a/pages/index.tsx b/pages/index.tsx index 25bd22e..3c6802a 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -3,15 +3,13 @@ import { ReactElement, useEffect, useState } from "react"; import HomeLayout from "../layouts/HomeLayout"; import { homeMain } from "../layouts/NavTemplates"; import Image from "next/image"; -import Head from "next/head"; function Home() { - let api7tvEmotes = `/api/7tv/emotes?c=61ad997effa9aba101bcfddf`; const [emotesUrls, setEmotes] = useState([]); const [currentEmote, setCurrentEmote] = useState(0); useEffect(() => { - fetch(api7tvEmotes) + fetch("/api/emotes") .then((res) => res.json()) .then((data) => { // if error, return @@ -19,19 +17,16 @@ function Home() { return; } // get all emote URLs - let emoteUrls = data.channel.user.emote_sets[0].emotes.map( - (emote: any) => { - let base_url = emote.data.host.url; - // get the largest emote size, append it to the base url - let largest = - emote.data.host.files[emote.data.host.files.length - 1]; - // if width != height, skip it - if (largest.width !== largest.height) { - return null; - } - return `https:${base_url}/${largest.name}`; + let emoteUrls = data["7tv"].channel.map((emote: any) => { + let base_url = emote.data.host.url; + // get the largest emote size, append it to the base url + let largest = emote.data.host.files[emote.data.host.files.length - 1]; + // if width != height, skip it + if (largest.width !== largest.height) { + return null; } - ); + return `https:${base_url}/${largest.name}`; + }); // remove null values @@ -78,9 +73,6 @@ function Home() { return ( <> - - Home - toffee -
{page}; + const metaTags = { + title: "Home - toffee", + }; + return ( + + {page} + + ); }; export default Home; diff --git a/pages/ranking/index.tsx b/pages/ranking/index.tsx index 5c6bb14..915b22c 100644 --- a/pages/ranking/index.tsx +++ b/pages/ranking/index.tsx @@ -1,10 +1,8 @@ import { m, Variants } from "framer-motion"; -import Head from "next/head"; import Link from "next/link"; import { ReactElement, useEffect, useState } from "react"; import Loading from "../../components/common/Loading"; import DashLayout from "../../layouts/DashLayout"; -import { fakeDataEntry } from "../api/fakeUsers"; function Ranking() { const [sortBy, setSortBy] = useState("netWorth"); @@ -75,9 +73,6 @@ function Ranking() { return ( <> - - Ranking - toffee -
{/* hidden if smaller than lg */} @@ -293,7 +288,11 @@ const rankingDataLineVariants: Variants = { }; Ranking.getLayout = function getLayout(page: ReactElement) { - return {page}; + const metaTags = { + title: "Ranking - toffee", + description: "Top investors on toffee", + }; + return {page}; }; export default Ranking; diff --git a/pages/team.tsx b/pages/team.tsx index 935bb55..a21afb7 100644 --- a/pages/team.tsx +++ b/pages/team.tsx @@ -1,5 +1,4 @@ import { m, Variants } from "framer-motion"; -import Head from "next/head"; import Image from "next/image"; import { ReactElement } from "react"; import HomeLayout from "../layouts/HomeLayout"; @@ -8,9 +7,6 @@ import { homeMain } from "../layouts/NavTemplates"; function Team() { return ( <> - - Team - toffee -
{page}; + const metaTags = { + title: "Team - toffee", + description: "Meet the team behind toffee", + }; + return ( + + {page} + + ); }; export default Team; diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index c0057c8..61406e5 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -1,26 +1,37 @@ -import { m } from "framer-motion"; -import Head from "next/head"; +import { m, Variants } from "framer-motion"; import { useRouter } from "next/router"; import { ReactElement, useEffect, useState } from "react"; import DashLayout from "../../../layouts/DashLayout"; import Image from "next/image"; import Loading from "../../../components/common/Loading"; +import { GetServerSideProps } from "next"; -// TODO: Animations +interface EmoteURLs { + "7tv": { [key: string]: string }; + bttv: { [key: string]: string }; + ffz: { [key: string]: string }; + twitch: { [key: string]: string }; + [key: string]: { [key: string]: string }; +} -function UserPage() { +interface UserPageProps { + userData: { [key: string]: any }; +} + +function UserPage(props: UserPageProps) { const [channelEmotes, setChannelEmotes] = useState<{ [key: string]: { [key: string]: string }; }>({}); - const [userData, setUserData] = useState<{ [key: string]: any }>({}); const [errorCode, setErrorCode] = useState(null); const router = useRouter(); const { username } = router.query; - const title = username ? `${username} - toffee` : "toffee"; useEffect(() => { if (!router.isReady) return; - fetch("/api/7tv/emotes?c=61ad997effa9aba101bcfddf") + if (props.userData.error) { + setErrorCode(props.userData.error.code); + } + fetch("/api/emotes") .then((res) => res.json()) .then((data) => { // if error, return @@ -28,100 +39,65 @@ function UserPage() { setErrorCode(data.error.code); return; } - // construct js object with emote names as keys and emote urls as values - let emotes: { [key: string]: string } = {}; - data.channel.user.emote_sets[0].emotes.forEach((emote: any) => { + // construct js object with emote names as keys and emote urls for each provider + // 7tv + let emotes: EmoteURLs = { "7tv": {}, bttv: {}, ffz: {}, twitch: {} }; + data["7tv"].channel.forEach((emote: any) => { let base_url = emote.data.host.url; // get the largest emote size, append it to the base url let largest = emote.data.host.files[emote.data.host.files.length - 1]; - emotes[emote.data.name] = `https:${base_url}/${largest.name}`; + emotes["7tv"][emote.data.name] = `https:${base_url}/${largest.name}`; }); // same for global emotes - data.global.namedEmoteSet.emotes.forEach((emote: any) => { + data["7tv"].global.forEach((emote: any) => { let base_url = emote.data.host.url; let largest = emote.data.host.files[emote.data.host.files.length - 1]; - emotes[emote.data.name] = `https:${base_url}/${largest.name}`; + emotes["7tv"][emote.data.name] = `https:${base_url}/${largest.name}`; }); - // set 7tv key to channelEmotes - setChannelEmotes((prev) => ({ ...prev, "7tv": emotes })); - }); - fetch("/api/bttv/emotes?c=56418014") - .then((res) => res.json()) - .then((data) => { - if (data.error) { - setErrorCode(data.error.code); - return; - } - let emotes: { [key: string]: string } = {}; - data.channel.forEach((emote: any) => { - emotes[emote.code] = `https://cdn.betterttv.net/emote/${emote.id}/3x`; + // bttv + data["bttv"].channel.forEach((emote: any) => { + emotes["bttv"][ + emote.code + ] = `https://cdn.betterttv.net/emote/${emote.id}/3x`; }); - data.global.forEach((emote: any) => { - emotes[emote.code] = `https://cdn.betterttv.net/emote/${emote.id}/3x`; + data["bttv"].global.forEach((emote: any) => { + emotes["bttv"][ + emote.code + ] = `https://cdn.betterttv.net/emote/${emote.id}/3x`; }); - // add as bttv key to channelEmotes - setChannelEmotes((prev) => ({ ...prev, bttv: emotes })); - }); - fetch("/api/ffz/emotes?s=341402") - .then((res) => res.json()) - .then((data) => { - if (data.error) { - setErrorCode(data.error.code); - return; - } - let emotes: { [key: string]: string } = {}; - data.channel.forEach((emote: any) => { + // ffz + data["ffz"].channel.forEach((emote: any) => { // ffz emotes don't have all sizes available, so we need to get the largest one by taking the largest key in the urls object - emotes[emote.name] = `https:${ + emotes["ffz"][emote.name] = `https:${ emote.urls[ Math.max(...Object.keys(emote.urls).map((k) => parseInt(k))) ] }`; }); - data.global.forEach((emote: any) => { - emotes[emote.name] = `https:${ + data["ffz"].global.forEach((emote: any) => { + emotes["ffz"][emote.name] = `https:${ emote.urls[ Math.max(...Object.keys(emote.urls).map((k) => parseInt(k))) ] }`; }); - // add as ffz key to channelEmotes - setChannelEmotes((prev) => ({ ...prev, ffz: emotes })); - }); - fetch("/api/twitch/emotes?c=56418014") - .then((res) => res.json()) - .then((data) => { - if (data.error) { - setErrorCode(data.error.code); - return; - } - let emotes: { [key: string]: string } = {}; - data.channel.forEach((emote: any) => { - emotes[emote.name] = emote.images["url_4x"]; + // twitch + data["twitch"].channel.forEach((emote: any) => { + emotes["twitch"][emote.name] = emote.images["url_4x"]; }); - data.global.forEach((emote: any) => { - emotes[emote.name] = emote.images["url_4x"]; + data["twitch"].global.forEach((emote: any) => { + emotes["twitch"][emote.name] = emote.images["url_4x"]; }); - // add as twitch key to channelEmotes - setChannelEmotes((prev) => ({ ...prev, ttv: emotes })); - }); - fetch(`/api/fakeUsers?u=${username}`) - .then((res) => res.json()) - .then((data) => { - if (data.error) { - setErrorCode(data.error.code); - } - setUserData(data.data); + // set emotes to channelEmotes + setChannelEmotes(emotes); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady]); if (errorCode !== null) { // 20000 = user not found - // 10000 = 7tv api error - // 10100 = Twitch api error - // 10200 = BTTV api error - // 10300 = FFZ api error + // 10000 = emote api error + // 10100 = twitch api error const errorMsg = errorCode === 20000 ? "User not found" : "API error"; return (
); } - console.log(channelEmotes); + return ( <> - - {title} - -
-
+ {/* User "banner" */} -
+
-
+
User avatar
-
-

- {userData.name} +
+

+ {props.userData.name}

{/* User's badges */}
- {userData.badges ? ( - userData.badges.map( + {props.userData.badges ? ( + props.userData.badges.map( (badge: { name: string; color: string; @@ -220,14 +191,17 @@ function UserPage() { $ - {userData.net_worth.toLocaleString("en-US")} + {props.userData.net_worth.toLocaleString("en-US")}

-
+ {/* Main Container */} -
+ {/* User's Rank/Graph */}
@@ -238,7 +212,7 @@ function UserPage() {
# - {userData.rank.toLocaleString("en-US")} + {props.userData.rank.toLocaleString("en-US")}
@@ -256,7 +230,7 @@ function UserPage() { $ - {userData.net_worth.toLocaleString("en-US")} + {props.userData.net_worth.toLocaleString("en-US")}
@@ -275,7 +249,7 @@ function UserPage() { {errorCode === 20000 ? (

{`Could not load assets`}

) : ( - userData.assets.map( + props.userData.assets.map( (asset: { name: string; count: number; @@ -322,7 +296,7 @@ function UserPage() {
{ - // show provider logo (7tv, bttv, ffz, ttv) + // show provider logo (7tv, bttv, ffz, twitch) asset.provider === "7tv" ? (
@@ -336,7 +310,7 @@ function UserPage() {
) : ( -
+
) @@ -352,29 +326,41 @@ function UserPage() { )}
-
+
{/* Sidebar */} -
-
+ + {/* User's Stats, left side is label, right side is value */}

Points

-

{userData.points.toLocaleString("en-US")}

+

{props.userData.points.toLocaleString("en-US")}

Shares

-

{userData.shares.toLocaleString("en-US")}

+

{props.userData.shares.toLocaleString("en-US")}

Trades

-

{(userData.trades ?? 0).toLocaleString("en-US")}

+

{(props.userData.trades ?? 0).toLocaleString("en-US")}

Peak rank

-

{(userData.peak_rank ?? 0).toLocaleString("en-US")}

+

{(props.userData.peak_rank ?? 0).toLocaleString("en-US")}

Joined

- {new Date(userData.joined ?? 0).toLocaleDateString("en-US", { - year: "numeric", - month: "short", - })} + {new Date(props.userData.joined ?? 0).toLocaleDateString( + "en-US", + { + year: "numeric", + month: "short", + } + )}

-
+ {/* User's Favorite Emote */} -
+

Favorite Emote @@ -385,9 +371,9 @@ function UserPage() { This user has not yet set a favorite emote.

-
-
-
+
+ +
); @@ -479,8 +465,130 @@ const TwitchLogo = () => { ); }; +const containerVariants: Variants = { + initial: { + opacity: 0, + y: 20, + }, + animate: { + opacity: 1, + y: 0, + transition: { + duration: 0.75, + ease: "easeOut", + delayChildren: 0.3, + staggerChildren: 0.25, + }, + }, + exit: { + opacity: 0, + y: 20, + transition: { + duration: 0.5, + ease: "easeOut", + }, + }, +}; + +const userBannerVariants: Variants = { + initial: { + opacity: 0, + x: 20, + }, + animate: { + opacity: 1, + x: 0, + transition: { + duration: 0.75, + type: "spring", + }, + }, +}; + +const mainContainerVariants: Variants = { + initial: { + opacity: 0, + y: 20, + }, + animate: { + opacity: 1, + y: 0, + transition: { + duration: 0.75, + ease: "easeOut", + }, + }, +}; + +const sidebarVariants: Variants = { + initial: { + opacity: 0, + x: 20, + }, + animate: { + opacity: 1, + x: 0, + transition: { + duration: 0.75, + ease: "easeOut", + delayChildren: 0.3, + staggerChildren: 0.25, + }, + }, +}; + +const sidebarItemVariants: Variants = { + initial: { + opacity: 0, + x: 20, + }, + animate: { + opacity: 1, + x: 0, + transition: { + duration: 0.75, + ease: "easeOut", + }, + }, +}; + +export const getServerSideProps: GetServerSideProps = async ( + context +) => { + // cache, currently 30s till stale + context.res.setHeader( + "Cache-Control", + "public, s-maxage=45, stale-while-revalidate=30" + ); + // data fetch + const url = new URL( + `/api/fakeUsers?u=${context.query.username}`, + process.env.NEXT_PUBLIC_URL + ); + const res = await fetch(url); + let user = await res.json(); + // return error in user.data if user not found + if (user.error) { + user = { data: user }; + } + return { props: { userData: user.data } }; +}; + UserPage.getLayout = function getLayout(page: ReactElement) { - return {page}; + const { userData } = page.props; + const metaTags = { + title: !userData.error + ? `${userData.name ?? "User 404"} - toffee` + : "User 404 - toffee", + description: !userData.error + ? `${userData.name}'s portfolio on toffee` + : "Couldn't find that user on toffee... :(", + imageUrl: !userData.error ? userData.avatar_url : undefined, + misc: { + "twitter:card": "summary", + }, + }; + return {page}; }; export default UserPage; diff --git a/tailwind.config.js b/tailwind.config.js index 0685455..35d5e36 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -16,7 +16,7 @@ module.exports = { colors: { "7tv": "#4fc2bc", bttv: "#d50014", - ttv: "#9146FF", + twitch: "#9146FF", }, }, },