From 45b0b59b994f73f2f84206d101a79613feae0849 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Sat, 21 Jan 2023 03:32:25 -0800 Subject: [PATCH 1/7] basic animations and responsive fixes --- pages/user/[username]/index.tsx | 137 ++++++++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 16 deletions(-) diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index c0057c8..9dc5757 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -1,4 +1,4 @@ -import { m } from "framer-motion"; +import { m, Variants } from "framer-motion"; import Head from "next/head"; import { useRouter } from "next/router"; import { ReactElement, useEffect, useState } from "react"; @@ -158,12 +158,18 @@ function UserPage() { />
-
+ {/* User "banner" */} -
+
-
+
User avatar
-
-

+
+

{userData.name}

{/* User's badges */} @@ -225,9 +231,12 @@ function UserPage() {

-
+ {/* Main Container */} -
+ {/* User's Rank/Graph */}
@@ -352,10 +361,16 @@ function UserPage() { )}
-
+ {/* Sidebar */} -
-
+ + {/* User's Stats, left side is label, right side is value */}

Points

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

@@ -372,9 +387,12 @@ function UserPage() { month: "short", })} -
+ {/* User's Favorite Emote */} -
+

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

-
-
-
+
+ +
); @@ -479,6 +497,93 @@ 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", + }, + }, +}; + UserPage.getLayout = function getLayout(page: ReactElement) { return {page}; }; From c31db440db9877a2a52507cce6ee4b6c1ee5297d Mon Sep 17 00:00:00 2001 From: 3zachm <3zachn4@gmail.com> Date: Sun, 22 Jan 2023 17:25:04 -0800 Subject: [PATCH 2/7] redis error handling fixes --- misc/redis.ts | 9 +++++---- pages/api/7tv/emotes.ts | 7 ++++++- pages/api/bttv/emotes.ts | 14 +++++++++----- pages/api/fakeUsers.ts | 6 ++++++ pages/api/ffz/emotes.ts | 6 ++++++ pages/api/twitch/emotes.ts | 14 +++++++++----- 6 files changed, 41 insertions(+), 15 deletions(-) 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/api/7tv/emotes.ts b/pages/api/7tv/emotes.ts index 4718558..9251d7d 100644 --- a/pages/api/7tv/emotes.ts +++ b/pages/api/7tv/emotes.ts @@ -11,7 +11,12 @@ export default async function handler( res: NextApiResponse ) { const redis = createRedisInstance(); - + if (!redis) { + res + .status(500) + .json({ error: { message: "Internal API is down", code: 50000 } }); + return; + } try { const channel = req.query.c ? await getChannelEmotes(redis, req.query.c as string) diff --git a/pages/api/bttv/emotes.ts b/pages/api/bttv/emotes.ts index fbdf506..7fdaa5d 100644 --- a/pages/api/bttv/emotes.ts +++ b/pages/api/bttv/emotes.ts @@ -11,6 +11,12 @@ export default async function handler( res: NextApiResponse ) { const redis = createRedisInstance(); + if (!redis) { + res.status(500).json({ + error: { message: "Internal API is down", code: 50200 }, + }); + return; + } try { const channel = req.query.c @@ -21,10 +27,8 @@ export default async function handler( 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 }, - }); + res.status(500).json({ + error: { message: "BTTV or internal API is down", code: 10200 }, + }); } } diff --git a/pages/api/fakeUsers.ts b/pages/api/fakeUsers.ts index 052e9dc..4461736 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 diff --git a/pages/api/ffz/emotes.ts b/pages/api/ffz/emotes.ts index dbc58c9..8fcb2a5 100644 --- a/pages/api/ffz/emotes.ts +++ b/pages/api/ffz/emotes.ts @@ -11,6 +11,12 @@ export default async function handler( res: NextApiResponse ) { const redis = createRedisInstance(); + if (!redis) { + res.status(500).json({ + error: { message: "Internal API is down", code: 50300 }, + }); + return; + } try { const channel = req.query.s diff --git a/pages/api/twitch/emotes.ts b/pages/api/twitch/emotes.ts index 9452544..9927a92 100644 --- a/pages/api/twitch/emotes.ts +++ b/pages/api/twitch/emotes.ts @@ -11,6 +11,12 @@ export default async function handler( res: NextApiResponse ) { const redis = createRedisInstance(); + if (!redis) { + res.status(500).json({ + error: { message: "Internal API is down", code: 50100 }, + }); + return; + } try { const channel = req.query.c @@ -21,10 +27,8 @@ export default async function handler( 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 }, - }); + res.status(500).json({ + error: { message: "Twitch or internal API is down", code: 10100 }, + }); } } From 69389834443f4e49256d4f2645f30d178d07c772 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Mon, 23 Jan 2023 00:36:04 -0800 Subject: [PATCH 3/7] condense API routes in meantime --- pages/api/7tv/emotes.ts | 33 ----------- pages/api/bttv/emotes.ts | 34 ----------- pages/api/emotes.ts | 78 ++++++++++++++++++++++++ pages/api/fakeUsers.ts | 36 +++++------ pages/api/ffz/emotes.ts | 36 ----------- pages/api/twitch/emotes.ts | 34 ----------- pages/index.tsx | 24 ++++---- pages/user/[username]/index.tsx | 102 +++++++++++++------------------- tailwind.config.js | 2 +- 9 files changed, 148 insertions(+), 231 deletions(-) delete mode 100644 pages/api/7tv/emotes.ts delete mode 100644 pages/api/bttv/emotes.ts create mode 100644 pages/api/emotes.ts delete mode 100644 pages/api/ffz/emotes.ts delete mode 100644 pages/api/twitch/emotes.ts diff --git a/pages/api/7tv/emotes.ts b/pages/api/7tv/emotes.ts deleted file mode 100644 index 9251d7d..0000000 --- a/pages/api/7tv/emotes.ts +++ /dev/null @@ -1,33 +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(); - if (!redis) { - res - .status(500) - .json({ error: { message: "Internal API is down", code: 50000 } }); - return; - } - 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 7fdaa5d..0000000 --- a/pages/api/bttv/emotes.ts +++ /dev/null @@ -1,34 +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(); - if (!redis) { - res.status(500).json({ - error: { message: "Internal API is down", code: 50200 }, - }); - return; - } - - 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 4461736..172c15c 100644 --- a/pages/api/fakeUsers.ts +++ b/pages/api/fakeUsers.ts @@ -120,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; @@ -191,7 +191,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfSigh", count: 1, - provider: "ttv", + provider: "twitch", }, { name: "GabeN", @@ -236,7 +236,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfHeart", count: 98, - provider: "ttv", + provider: "twitch", }, { name: "Catge", @@ -281,7 +281,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfRave", count: 5, - provider: "ttv", + provider: "twitch", }, ], badges: [adminBadge, botDevBadge], @@ -316,7 +316,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfMelt", count: 16, - provider: "ttv", + provider: "twitch", }, ], badges: [CEOBadge, adminBadge], @@ -356,7 +356,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfAngy", count: 90, - provider: "ttv", + provider: "twitch", }, ], badges: [adminBadge, botDevBadge], @@ -420,7 +420,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfHug", count: 19, - provider: "ttv", + provider: "twitch", }, ], }, @@ -439,7 +439,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfLUL", count: 9, - provider: "ttv", + provider: "twitch", }, { name: "peepoSnow", @@ -493,7 +493,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfPain", count: 37, - provider: "ttv", + provider: "twitch", }, ], }, @@ -532,7 +532,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfKnuckles", count: 2, - provider: "ttv", + provider: "twitch", }, ], }, @@ -571,7 +571,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfCheer", count: 54, - provider: "ttv", + provider: "twitch", }, ], }, @@ -610,7 +610,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfBonk", count: 77, - provider: "ttv", + provider: "twitch", }, ], }, @@ -654,7 +654,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfSit", count: 53, - provider: "ttv", + provider: "twitch", }, ], }, @@ -717,7 +717,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfGamba", count: 32, - provider: "ttv", + provider: "twitch", }, ], }, @@ -751,7 +751,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfFlower", count: 33, - provider: "ttv", + provider: "twitch", }, ], }, @@ -785,7 +785,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfFlower", count: 79, - provider: "ttv", + provider: "twitch", }, ], }, @@ -819,7 +819,7 @@ const fakeData: fakeDataEntry[] = [ { name: "annytfSad", count: 2, - provider: "ttv", + provider: "twitch", }, ], }, @@ -853,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 8fcb2a5..0000000 --- a/pages/api/ffz/emotes.ts +++ /dev/null @@ -1,36 +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(); - if (!redis) { - res.status(500).json({ - error: { message: "Internal API is down", code: 50300 }, - }); - return; - } - - 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 9927a92..0000000 --- a/pages/api/twitch/emotes.ts +++ /dev/null @@ -1,34 +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(); - if (!redis) { - res.status(500).json({ - error: { message: "Internal API is down", code: 50100 }, - }); - return; - } - - 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/index.tsx b/pages/index.tsx index 25bd22e..2640de9 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -6,12 +6,11 @@ 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 +18,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 diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index 9dc5757..959b1bc 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -6,7 +6,13 @@ import DashLayout from "../../../layouts/DashLayout"; import Image from "next/image"; import Loading from "../../../components/common/Loading"; -// 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() { const [channelEmotes, setChannelEmotes] = useState<{ @@ -20,7 +26,7 @@ function UserPage() { useEffect(() => { if (!router.isReady) return; - fetch("/api/7tv/emotes?c=61ad997effa9aba101bcfddf") + fetch("/api/emotes") .then((res) => res.json()) .then((data) => { // if error, return @@ -28,83 +34,59 @@ 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 })); + // set emotes to channelEmotes + setChannelEmotes(emotes); }); + // fetch user data fetch(`/api/fakeUsers?u=${username}`) .then((res) => res.json()) .then((data) => { @@ -118,10 +100,8 @@ function UserPage() { 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 (
{ - // show provider logo (7tv, bttv, ffz, ttv) + // show provider logo (7tv, bttv, ffz, twitch) asset.provider === "7tv" ? (
@@ -345,7 +325,7 @@ function 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", }, }, }, From ffdf91baf67fa05ae9dd18cb299246b0d0621ded Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Thu, 26 Jan 2023 02:20:07 -0800 Subject: [PATCH 4/7] dynamic head at layout level for SEO --- layouts/DashLayout.tsx | 58 +++++++++++++++--- layouts/HomeLayout.tsx | 58 +++++++++++++++--- pages/about.tsx | 23 ------- pages/contact.tsx | 23 ------- pages/dashboard/index.tsx | 7 +-- pages/index.tsx | 13 ++-- pages/ranking/index.tsx | 11 ++-- pages/team.tsx | 14 +++-- pages/user/[username]/index.tsx | 105 ++++++++++++++++++-------------- 9 files changed, 184 insertions(+), 128 deletions(-) delete mode 100644 pages/about.tsx delete mode 100644 pages/contact.tsx 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/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/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 2640de9..3c6802a 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -3,7 +3,6 @@ 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() { const [emotesUrls, setEmotes] = useState([]); @@ -74,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 959b1bc..7a46772 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -1,5 +1,4 @@ import { m, Variants } from "framer-motion"; -import Head from "next/head"; import { useRouter } from "next/router"; import { ReactElement, useEffect, useState } from "react"; import DashLayout from "../../../layouts/DashLayout"; @@ -14,18 +13,23 @@ interface EmoteURLs { [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; + if (props.userData.error) { + setErrorCode(props.userData.error.code); + } fetch("/api/emotes") .then((res) => res.json()) .then((data) => { @@ -86,15 +90,6 @@ function UserPage() { // set emotes to channelEmotes setChannelEmotes(emotes); }); - // fetch user data - fetch(`/api/fakeUsers?u=${username}`) - .then((res) => res.json()) - .then((data) => { - if (data.error) { - setErrorCode(data.error.code); - } - setUserData(data.data); - }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady]); @@ -116,27 +111,16 @@ function UserPage() { } // if json is empty, and if channelEmotes is incomplete, show loading screen - if ( - Object.keys(channelEmotes).length < 4 || - !userData || - Object.keys(userData).length === 0 - ) { + if (Object.keys(channelEmotes).length < 4) { return (
); } - console.log(channelEmotes); + return ( <> - - {title} - -
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; @@ -206,7 +190,7 @@ function UserPage() { $ - {userData.net_worth.toLocaleString("en-US")} + {props.userData.net_worth.toLocaleString("en-US")}
@@ -227,7 +211,7 @@ function UserPage() {
# - {userData.rank.toLocaleString("en-US")} + {props.userData.rank.toLocaleString("en-US")}
@@ -245,7 +229,7 @@ function UserPage() { $ - {userData.net_worth.toLocaleString("en-US")} + {props.userData.net_worth.toLocaleString("en-US")}
@@ -264,7 +248,7 @@ function UserPage() { {errorCode === 20000 ? (

{`Could not load assets`}

) : ( - userData.assets.map( + props.userData.assets.map( (asset: { name: string; count: number; @@ -353,19 +337,22 @@ function UserPage() { > {/* 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 */} @@ -564,8 +551,32 @@ const sidebarItemVariants: Variants = { }, }; +UserPage.getInitialProps = async (context: { query: { username: string } }) => { + const res = await fetch( + `${process.env.NEXT_PUBLIC_URL}/api/fakeUsers?u=${context.query.username}` + ); + let user = await res.json(); + if (user.error) { + user = { data: user }; + } + return { 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... :(", + image: !userData.error ? userData.avatar_url : undefined, + misc: { + "twitter:card": "summary", + }, + }; + return {page}; }; export default UserPage; From 635fa9d5c599bad96f04f6e3db0cacdf4b530a93 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Thu, 26 Jan 2023 02:52:52 -0800 Subject: [PATCH 5/7] maybe fix? --- pages/user/[username]/index.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index 7a46772..c0ee448 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -551,10 +551,18 @@ const sidebarItemVariants: Variants = { }, }; -UserPage.getInitialProps = async (context: { query: { username: string } }) => { - const res = await fetch( - `${process.env.NEXT_PUBLIC_URL}/api/fakeUsers?u=${context.query.username}` - ); +UserPage.getInitialProps = async (context: { + query: { username: string }; + req: any; +}) => { + let host = process.env.NEXT_PUBLIC_URL; + console.log(host); + if (context.req) { + // if env breaks ??? + let host = context.req.headers.host; + } + const url = new URL(`${host}/api/fakeUsers?u=${context.query.username}`); + const res = await fetch(url); let user = await res.json(); if (user.error) { user = { data: user }; From 8bb40539900fb9c55529d5e5b584d3f538efb21e Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Thu, 26 Jan 2023 03:00:10 -0800 Subject: [PATCH 6/7] nope, fix tmrw --- pages/user/[username]/index.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index c0ee448..6d69f4c 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -555,13 +555,10 @@ UserPage.getInitialProps = async (context: { query: { username: string }; req: any; }) => { - let host = process.env.NEXT_PUBLIC_URL; - console.log(host); - if (context.req) { - // if env breaks ??? - let host = context.req.headers.host; - } - const url = new URL(`${host}/api/fakeUsers?u=${context.query.username}`); + // fix weird bug where host env was undefined on layout render, not direct page render + const url = new URL( + `https://invest.3zachm.dev/api/fakeUsers?u=${context.query.username}` + ); const res = await fetch(url); let user = await res.json(); if (user.error) { From e34aa1583ec787d92aa9cb519e724f06800fc664 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Thu, 26 Jan 2023 18:54:42 -0800 Subject: [PATCH 7/7] replace initial with serverside --- pages/user/[username]/index.tsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index 6d69f4c..61406e5 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -4,6 +4,7 @@ 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"; interface EmoteURLs { "7tv": { [key: string]: string }; @@ -551,20 +552,26 @@ const sidebarItemVariants: Variants = { }, }; -UserPage.getInitialProps = async (context: { - query: { username: string }; - req: any; -}) => { - // fix weird bug where host env was undefined on layout render, not direct page render +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( - `https://invest.3zachm.dev/api/fakeUsers?u=${context.query.username}` + `/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 { userData: user.data }; + return { props: { userData: user.data } }; }; UserPage.getLayout = function getLayout(page: ReactElement) { @@ -576,7 +583,7 @@ UserPage.getLayout = function getLayout(page: ReactElement) { description: !userData.error ? `${userData.name}'s portfolio on toffee` : "Couldn't find that user on toffee... :(", - image: !userData.error ? userData.avatar_url : undefined, + imageUrl: !userData.error ? userData.avatar_url : undefined, misc: { "twitter:card": "summary", },