Merge pull request #10 from Invest-Bot/dev
Merge Dev: Dynamic metatags, redis error handling, userpage animations
This commit is contained in:
commit
c7766aeda3
17 changed files with 475 additions and 359 deletions
|
@ -11,11 +11,24 @@ import NavBar from "../components/dashboard/NavBar";
|
||||||
|
|
||||||
interface DashLayoutProps {
|
interface DashLayoutProps {
|
||||||
children: React.ReactNode;
|
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) {
|
function DashLayout(props: DashLayoutProps) {
|
||||||
// get the current route for animation purposes
|
// get the current route for animation purposes
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const title = props.metaTags.title ?? "Dashboard - toffee";
|
||||||
return (
|
return (
|
||||||
<m.div
|
<m.div
|
||||||
className="bg-gradient-to-t from-zinc-900 to-[#3015457b]"
|
className="bg-gradient-to-t from-zinc-900 to-[#3015457b]"
|
||||||
|
@ -26,18 +39,49 @@ function DashLayout(props: DashLayoutProps) {
|
||||||
>
|
>
|
||||||
<Head>
|
<Head>
|
||||||
<meta name="viewport" content="initial-scale=0.8" />
|
<meta name="viewport" content="initial-scale=0.8" />
|
||||||
<title>Dashboard - toffee</title>
|
<title>{title}</title>
|
||||||
<meta name="description" content="Dashboard statistics for toffee" />
|
<meta
|
||||||
|
name="description"
|
||||||
|
content={
|
||||||
|
props.metaTags.description ?? "Dashboard statistics for toffee"
|
||||||
|
}
|
||||||
|
/>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="theme-color" content="#c084fc" />
|
<meta
|
||||||
<meta property="og:title" content="toffee" />
|
name="theme-color"
|
||||||
|
content={props.metaTags.themeColor ?? "#c084fc"}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:title"
|
||||||
|
content={props.metaTags.ogTitle ?? props.metaTags.title ?? "toffee"}
|
||||||
|
/>
|
||||||
<meta
|
<meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="Serving anny's community est. 2022"
|
content={
|
||||||
|
props.metaTags.ogDescription ??
|
||||||
|
props.metaTags.description ??
|
||||||
|
"Dashboard statistics for toffee"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content={props.metaTags.imageUrl ?? "/img/logo.webp"}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:type"
|
||||||
|
content={props.metaTags.content ?? "website"}
|
||||||
/>
|
/>
|
||||||
<meta property="og:image" content="/img/logo.webp" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:site_name" content="toffee" />
|
<meta property="og:site_name" content="toffee" />
|
||||||
|
{props.metaTags.misc &&
|
||||||
|
Object.keys(props.metaTags.misc).map((key) => {
|
||||||
|
return (
|
||||||
|
<meta
|
||||||
|
key={key}
|
||||||
|
property={key}
|
||||||
|
content={props.metaTags.misc ? props.metaTags.misc[key] : ""}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<div className="flex h-screen w-screen flex-col overflow-hidden lg:flex-row">
|
<div className="flex h-screen w-screen flex-col overflow-hidden lg:flex-row">
|
||||||
|
|
|
@ -15,6 +15,18 @@ import { NavTemplate } from "./NavTemplates";
|
||||||
interface HomeLayoutProps {
|
interface HomeLayoutProps {
|
||||||
navOptions: NavTemplate[];
|
navOptions: NavTemplate[];
|
||||||
children: React.ReactNode;
|
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) {
|
function HomeLayout(props: HomeLayoutProps) {
|
||||||
|
@ -22,6 +34,7 @@ function HomeLayout(props: HomeLayoutProps) {
|
||||||
const navOptions = props.navOptions;
|
const navOptions = props.navOptions;
|
||||||
// get the current route for animation purposes
|
// get the current route for animation purposes
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const title = props.metaTags.title ?? "Dashboard - toffee";
|
||||||
return (
|
return (
|
||||||
<m.div
|
<m.div
|
||||||
initial="initial"
|
initial="initial"
|
||||||
|
@ -30,18 +43,49 @@ function HomeLayout(props: HomeLayoutProps) {
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
>
|
>
|
||||||
<Head>
|
<Head>
|
||||||
<title>toffee</title>
|
<title>{title}</title>
|
||||||
<meta name="description" content="Serving anny's community est. 2022" />
|
<meta
|
||||||
|
name="description"
|
||||||
|
content={
|
||||||
|
props.metaTags.description ?? "Serving anny's community est. 2022"
|
||||||
|
}
|
||||||
|
/>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="theme-color" content="#c084fc" />
|
<meta
|
||||||
<meta property="og:title" content="toffee" />
|
name="theme-color"
|
||||||
|
content={props.metaTags.themeColor ?? "#c084fc"}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:title"
|
||||||
|
content={props.metaTags.ogTitle ?? props.metaTags.title ?? "toffee"}
|
||||||
|
/>
|
||||||
<meta
|
<meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="Serving anny's community est. 2022"
|
content={
|
||||||
|
props.metaTags.ogDescription ??
|
||||||
|
props.metaTags.description ??
|
||||||
|
"Serving anny's community est. 2022"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content={props.metaTags.imageUrl ?? "/img/logo.webp"}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:type"
|
||||||
|
content={props.metaTags.content ?? "website"}
|
||||||
/>
|
/>
|
||||||
<meta property="og:image" content="/img/logo.webp" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:site_name" content="toffee" />
|
<meta property="og:site_name" content="toffee" />
|
||||||
|
{props.metaTags.misc &&
|
||||||
|
Object.keys(props.metaTags.misc).map((key) => {
|
||||||
|
return (
|
||||||
|
<meta
|
||||||
|
key={key}
|
||||||
|
property={key}
|
||||||
|
content={props.metaTags.misc ? props.metaTags.misc[key] : ""}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<LazyMotion features={domAnimation}>
|
<LazyMotion features={domAnimation}>
|
||||||
|
|
|
@ -24,10 +24,11 @@ export function createRedisInstance(config = getRedisConfiguration()) {
|
||||||
lazyConnect: true,
|
lazyConnect: true,
|
||||||
showFriendlyErrorStack: true,
|
showFriendlyErrorStack: true,
|
||||||
enableAutoPipelining: true,
|
enableAutoPipelining: true,
|
||||||
maxRetriesPerRequest: 0,
|
maxRetriesPerRequest: 3,
|
||||||
retryStrategy: (times: number) => {
|
retryStrategy: (times: number) => {
|
||||||
if (times > 3) {
|
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);
|
return Math.min(times * 200, 1000);
|
||||||
|
@ -45,11 +46,11 @@ export function createRedisInstance(config = getRedisConfiguration()) {
|
||||||
const redis = new Redis(options);
|
const redis = new Redis(options);
|
||||||
|
|
||||||
redis.on("error", (error: unknown) => {
|
redis.on("error", (error: unknown) => {
|
||||||
console.warn("[Redis] Error connecting", error);
|
console.warn("[Redis] ", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
return redis;
|
return redis;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[Redis] Could not create a Redis instance`);
|
console.log(`[Redis] Could not create a Redis instance`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>About - toffee</title>
|
|
||||||
</Head>
|
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center py-2">
|
|
||||||
<p>about</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
About.getLayout = function getLayout(page: ReactElement) {
|
|
||||||
return <HomeLayout navOptions={homeMain}>{page}</HomeLayout>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default About;
|
|
|
@ -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<Data>
|
|
||||||
) {
|
|
||||||
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 } });
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Data>
|
|
||||||
) {
|
|
||||||
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 },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
78
pages/api/emotes.ts
Normal file
78
pages/api/emotes.ts
Normal file
|
@ -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<Data>
|
||||||
|
) {
|
||||||
|
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 } });
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,12 @@ export default async function handler(
|
||||||
const sortAsc = req.query.a ? (req.query.a as string) : undefined;
|
const sortAsc = req.query.a ? (req.query.a as string) : undefined;
|
||||||
|
|
||||||
const redis = createRedisInstance();
|
const redis = createRedisInstance();
|
||||||
|
if (!redis) {
|
||||||
|
res.status(500).json({
|
||||||
|
error: { message: "Internal API is down", code: 50100 },
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let data = fakeData;
|
let data = fakeData;
|
||||||
// calculate all net worths
|
// calculate all net worths
|
||||||
|
@ -114,7 +120,7 @@ export default async function handler(
|
||||||
interface asset {
|
interface asset {
|
||||||
name: string;
|
name: string;
|
||||||
count: number;
|
count: number;
|
||||||
provider: "7tv" | "bttv" | "ffz" | "ttv";
|
provider: "7tv" | "bttv" | "ffz" | "twitch";
|
||||||
}
|
}
|
||||||
interface fakeDataEntry {
|
interface fakeDataEntry {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -185,7 +191,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfSigh",
|
name: "annytfSigh",
|
||||||
count: 1,
|
count: 1,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GabeN",
|
name: "GabeN",
|
||||||
|
@ -230,7 +236,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfHeart",
|
name: "annytfHeart",
|
||||||
count: 98,
|
count: 98,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Catge",
|
name: "Catge",
|
||||||
|
@ -275,7 +281,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfRave",
|
name: "annytfRave",
|
||||||
count: 5,
|
count: 5,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
badges: [adminBadge, botDevBadge],
|
badges: [adminBadge, botDevBadge],
|
||||||
|
@ -310,7 +316,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfMelt",
|
name: "annytfMelt",
|
||||||
count: 16,
|
count: 16,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
badges: [CEOBadge, adminBadge],
|
badges: [CEOBadge, adminBadge],
|
||||||
|
@ -350,7 +356,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfAngy",
|
name: "annytfAngy",
|
||||||
count: 90,
|
count: 90,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
badges: [adminBadge, botDevBadge],
|
badges: [adminBadge, botDevBadge],
|
||||||
|
@ -414,7 +420,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfHug",
|
name: "annytfHug",
|
||||||
count: 19,
|
count: 19,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -433,7 +439,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfLUL",
|
name: "annytfLUL",
|
||||||
count: 9,
|
count: 9,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "peepoSnow",
|
name: "peepoSnow",
|
||||||
|
@ -487,7 +493,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfPain",
|
name: "annytfPain",
|
||||||
count: 37,
|
count: 37,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -526,7 +532,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfKnuckles",
|
name: "annytfKnuckles",
|
||||||
count: 2,
|
count: 2,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -565,7 +571,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfCheer",
|
name: "annytfCheer",
|
||||||
count: 54,
|
count: 54,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -604,7 +610,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfBonk",
|
name: "annytfBonk",
|
||||||
count: 77,
|
count: 77,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -648,7 +654,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfSit",
|
name: "annytfSit",
|
||||||
count: 53,
|
count: 53,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -711,7 +717,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfGamba",
|
name: "annytfGamba",
|
||||||
count: 32,
|
count: 32,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -745,7 +751,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfFlower",
|
name: "annytfFlower",
|
||||||
count: 33,
|
count: 33,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -779,7 +785,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfFlower",
|
name: "annytfFlower",
|
||||||
count: 79,
|
count: 79,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -813,7 +819,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfSad",
|
name: "annytfSad",
|
||||||
count: 2,
|
count: 2,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -847,7 +853,7 @@ const fakeData: fakeDataEntry[] = [
|
||||||
{
|
{
|
||||||
name: "annytfHeart",
|
name: "annytfHeart",
|
||||||
count: 63,
|
count: 63,
|
||||||
provider: "ttv",
|
provider: "twitch",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -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<Data>
|
|
||||||
) {
|
|
||||||
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 } });
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Data>
|
|
||||||
) {
|
|
||||||
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 },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>Contact - toffee</title>
|
|
||||||
</Head>
|
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center py-2">
|
|
||||||
<p>contact</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
About.getLayout = function getLayout(page: ReactElement) {
|
|
||||||
return <HomeLayout navOptions={homeMain}>{page}</HomeLayout>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default About;
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { m, Variants } from "framer-motion";
|
import { m, Variants } from "framer-motion";
|
||||||
import Head from "next/head";
|
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import DashLayout from "../../layouts/DashLayout";
|
import DashLayout from "../../layouts/DashLayout";
|
||||||
|
|
||||||
|
@ -7,9 +6,6 @@ import DashLayout from "../../layouts/DashLayout";
|
||||||
function Dashboard() {
|
function Dashboard() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
|
||||||
<title>Dashboard - toffee</title>
|
|
||||||
</Head>
|
|
||||||
<m.div
|
<m.div
|
||||||
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"
|
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}
|
variants={gridContainerVariants}
|
||||||
|
@ -83,7 +79,8 @@ const gridItemVariants: Variants = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Dashboard.getLayout = function getLayout(page: ReactElement) {
|
Dashboard.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <DashLayout>{page}</DashLayout>;
|
const metaTags = {};
|
||||||
|
return <DashLayout metaTags={metaTags}>{page}</DashLayout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Dashboard;
|
export default Dashboard;
|
||||||
|
|
|
@ -3,15 +3,13 @@ import { ReactElement, useEffect, useState } from "react";
|
||||||
import HomeLayout from "../layouts/HomeLayout";
|
import HomeLayout from "../layouts/HomeLayout";
|
||||||
import { homeMain } from "../layouts/NavTemplates";
|
import { homeMain } from "../layouts/NavTemplates";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Head from "next/head";
|
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
let api7tvEmotes = `/api/7tv/emotes?c=61ad997effa9aba101bcfddf`;
|
|
||||||
const [emotesUrls, setEmotes] = useState([]);
|
const [emotesUrls, setEmotes] = useState([]);
|
||||||
const [currentEmote, setCurrentEmote] = useState(0);
|
const [currentEmote, setCurrentEmote] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(api7tvEmotes)
|
fetch("/api/emotes")
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// if error, return
|
// if error, return
|
||||||
|
@ -19,19 +17,16 @@ function Home() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// get all emote URLs
|
// get all emote URLs
|
||||||
let emoteUrls = data.channel.user.emote_sets[0].emotes.map(
|
let emoteUrls = data["7tv"].channel.map((emote: any) => {
|
||||||
(emote: any) => {
|
let base_url = emote.data.host.url;
|
||||||
let base_url = emote.data.host.url;
|
// get the largest emote size, append it to the base url
|
||||||
// get the largest emote size, append it to the base url
|
let largest = emote.data.host.files[emote.data.host.files.length - 1];
|
||||||
let largest =
|
// if width != height, skip it
|
||||||
emote.data.host.files[emote.data.host.files.length - 1];
|
if (largest.width !== largest.height) {
|
||||||
// if width != height, skip it
|
return null;
|
||||||
if (largest.width !== largest.height) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return `https:${base_url}/${largest.name}`;
|
|
||||||
}
|
}
|
||||||
);
|
return `https:${base_url}/${largest.name}`;
|
||||||
|
});
|
||||||
|
|
||||||
// remove null values
|
// remove null values
|
||||||
|
|
||||||
|
@ -78,9 +73,6 @@ function Home() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
|
||||||
<title>Home - toffee</title>
|
|
||||||
</Head>
|
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||||
<div className="inline-grid grid-cols-1 gap-20 text-white md:grid-cols-3">
|
<div className="inline-grid grid-cols-1 gap-20 text-white md:grid-cols-3">
|
||||||
<m.div
|
<m.div
|
||||||
|
@ -198,7 +190,14 @@ const slideShowVariants = {
|
||||||
|
|
||||||
// set the layout for the page, this is used to wrap the page in a layout
|
// set the layout for the page, this is used to wrap the page in a layout
|
||||||
Home.getLayout = function getLayout(page: ReactElement) {
|
Home.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <HomeLayout navOptions={homeMain}>{page}</HomeLayout>;
|
const metaTags = {
|
||||||
|
title: "Home - toffee",
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<HomeLayout navOptions={homeMain} metaTags={metaTags}>
|
||||||
|
{page}
|
||||||
|
</HomeLayout>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import { m, Variants } from "framer-motion";
|
import { m, Variants } from "framer-motion";
|
||||||
import Head from "next/head";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ReactElement, useEffect, useState } from "react";
|
import { ReactElement, useEffect, useState } from "react";
|
||||||
import Loading from "../../components/common/Loading";
|
import Loading from "../../components/common/Loading";
|
||||||
import DashLayout from "../../layouts/DashLayout";
|
import DashLayout from "../../layouts/DashLayout";
|
||||||
import { fakeDataEntry } from "../api/fakeUsers";
|
|
||||||
|
|
||||||
function Ranking() {
|
function Ranking() {
|
||||||
const [sortBy, setSortBy] = useState("netWorth");
|
const [sortBy, setSortBy] = useState("netWorth");
|
||||||
|
@ -75,9 +73,6 @@ function Ranking() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
|
||||||
<title>Ranking - toffee</title>
|
|
||||||
</Head>
|
|
||||||
<div className="flex w-full justify-center">
|
<div className="flex w-full justify-center">
|
||||||
<div className="ml-3 flex w-full max-w-7xl flex-col items-center justify-start font-plusJakarta font-semibold lg:ml-0">
|
<div className="ml-3 flex w-full max-w-7xl flex-col items-center justify-start font-plusJakarta font-semibold lg:ml-0">
|
||||||
{/* hidden if smaller than lg */}
|
{/* hidden if smaller than lg */}
|
||||||
|
@ -293,7 +288,11 @@ const rankingDataLineVariants: Variants = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ranking.getLayout = function getLayout(page: ReactElement) {
|
Ranking.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <DashLayout>{page}</DashLayout>;
|
const metaTags = {
|
||||||
|
title: "Ranking - toffee",
|
||||||
|
description: "Top investors on toffee",
|
||||||
|
};
|
||||||
|
return <DashLayout metaTags={metaTags}>{page}</DashLayout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Ranking;
|
export default Ranking;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { m, Variants } from "framer-motion";
|
import { m, Variants } from "framer-motion";
|
||||||
import Head from "next/head";
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import HomeLayout from "../layouts/HomeLayout";
|
import HomeLayout from "../layouts/HomeLayout";
|
||||||
|
@ -8,9 +7,6 @@ import { homeMain } from "../layouts/NavTemplates";
|
||||||
function Team() {
|
function Team() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
|
||||||
<title>Team - toffee</title>
|
|
||||||
</Head>
|
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center py-2">
|
<div className="flex min-h-screen flex-col items-center justify-center py-2">
|
||||||
<m.div
|
<m.div
|
||||||
className="grid w-[90vw] grid-cols-1 py-2 sm:grid-cols-2 md:grid-cols-4 lg:w-[75vw]"
|
className="grid w-[90vw] grid-cols-1 py-2 sm:grid-cols-2 md:grid-cols-4 lg:w-[75vw]"
|
||||||
|
@ -136,7 +132,15 @@ const headerVariants: Variants = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Team.getLayout = function getLayout(page: ReactElement) {
|
Team.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <HomeLayout navOptions={homeMain}>{page}</HomeLayout>;
|
const metaTags = {
|
||||||
|
title: "Team - toffee",
|
||||||
|
description: "Meet the team behind toffee",
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<HomeLayout navOptions={homeMain} metaTags={metaTags}>
|
||||||
|
{page}
|
||||||
|
</HomeLayout>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Team;
|
export default Team;
|
||||||
|
|
|
@ -1,26 +1,37 @@
|
||||||
import { m } from "framer-motion";
|
import { m, Variants } from "framer-motion";
|
||||||
import Head from "next/head";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { ReactElement, useEffect, useState } from "react";
|
import { ReactElement, useEffect, useState } from "react";
|
||||||
import DashLayout from "../../../layouts/DashLayout";
|
import DashLayout from "../../../layouts/DashLayout";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Loading from "../../../components/common/Loading";
|
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<{
|
const [channelEmotes, setChannelEmotes] = useState<{
|
||||||
[key: string]: { [key: string]: string };
|
[key: string]: { [key: string]: string };
|
||||||
}>({});
|
}>({});
|
||||||
const [userData, setUserData] = useState<{ [key: string]: any }>({});
|
|
||||||
const [errorCode, setErrorCode] = useState<number | null>(null);
|
const [errorCode, setErrorCode] = useState<number | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { username } = router.query;
|
const { username } = router.query;
|
||||||
const title = username ? `${username} - toffee` : "toffee";
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!router.isReady) return;
|
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((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// if error, return
|
// if error, return
|
||||||
|
@ -28,100 +39,65 @@ function UserPage() {
|
||||||
setErrorCode(data.error.code);
|
setErrorCode(data.error.code);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// construct js object with emote names as keys and emote urls as values
|
// construct js object with emote names as keys and emote urls for each provider
|
||||||
let emotes: { [key: string]: string } = {};
|
// 7tv
|
||||||
data.channel.user.emote_sets[0].emotes.forEach((emote: any) => {
|
let emotes: EmoteURLs = { "7tv": {}, bttv: {}, ffz: {}, twitch: {} };
|
||||||
|
data["7tv"].channel.forEach((emote: any) => {
|
||||||
let base_url = emote.data.host.url;
|
let base_url = emote.data.host.url;
|
||||||
// get the largest emote size, append it to the base url
|
// get the largest emote size, append it to the base url
|
||||||
let largest = emote.data.host.files[emote.data.host.files.length - 1];
|
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
|
// 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 base_url = emote.data.host.url;
|
||||||
let largest = emote.data.host.files[emote.data.host.files.length - 1];
|
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
|
// bttv
|
||||||
setChannelEmotes((prev) => ({ ...prev, "7tv": emotes }));
|
data["bttv"].channel.forEach((emote: any) => {
|
||||||
});
|
emotes["bttv"][
|
||||||
fetch("/api/bttv/emotes?c=56418014")
|
emote.code
|
||||||
.then((res) => res.json())
|
] = `https://cdn.betterttv.net/emote/${emote.id}/3x`;
|
||||||
.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`;
|
|
||||||
});
|
});
|
||||||
data.global.forEach((emote: any) => {
|
data["bttv"].global.forEach((emote: any) => {
|
||||||
emotes[emote.code] = `https://cdn.betterttv.net/emote/${emote.id}/3x`;
|
emotes["bttv"][
|
||||||
|
emote.code
|
||||||
|
] = `https://cdn.betterttv.net/emote/${emote.id}/3x`;
|
||||||
});
|
});
|
||||||
// add as bttv key to channelEmotes
|
// ffz
|
||||||
setChannelEmotes((prev) => ({ ...prev, bttv: emotes }));
|
data["ffz"].channel.forEach((emote: any) => {
|
||||||
});
|
|
||||||
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 emotes don't have all sizes available, so we need to get the largest one by taking the largest key in the urls object
|
// 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[
|
emote.urls[
|
||||||
Math.max(...Object.keys(emote.urls).map((k) => parseInt(k)))
|
Math.max(...Object.keys(emote.urls).map((k) => parseInt(k)))
|
||||||
]
|
]
|
||||||
}`;
|
}`;
|
||||||
});
|
});
|
||||||
data.global.forEach((emote: any) => {
|
data["ffz"].global.forEach((emote: any) => {
|
||||||
emotes[emote.name] = `https:${
|
emotes["ffz"][emote.name] = `https:${
|
||||||
emote.urls[
|
emote.urls[
|
||||||
Math.max(...Object.keys(emote.urls).map((k) => parseInt(k)))
|
Math.max(...Object.keys(emote.urls).map((k) => parseInt(k)))
|
||||||
]
|
]
|
||||||
}`;
|
}`;
|
||||||
});
|
});
|
||||||
// add as ffz key to channelEmotes
|
// twitch
|
||||||
setChannelEmotes((prev) => ({ ...prev, ffz: emotes }));
|
data["twitch"].channel.forEach((emote: any) => {
|
||||||
});
|
emotes["twitch"][emote.name] = emote.images["url_4x"];
|
||||||
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"];
|
|
||||||
});
|
});
|
||||||
data.global.forEach((emote: any) => {
|
data["twitch"].global.forEach((emote: any) => {
|
||||||
emotes[emote.name] = emote.images["url_4x"];
|
emotes["twitch"][emote.name] = emote.images["url_4x"];
|
||||||
});
|
});
|
||||||
// add as twitch key to channelEmotes
|
// set emotes to channelEmotes
|
||||||
setChannelEmotes((prev) => ({ ...prev, ttv: emotes }));
|
setChannelEmotes(emotes);
|
||||||
});
|
|
||||||
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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [router.isReady]);
|
}, [router.isReady]);
|
||||||
|
|
||||||
if (errorCode !== null) {
|
if (errorCode !== null) {
|
||||||
// 20000 = user not found
|
// 20000 = user not found
|
||||||
// 10000 = 7tv api error
|
// 10000 = emote api error
|
||||||
// 10100 = Twitch api error
|
// 10100 = twitch api error
|
||||||
// 10200 = BTTV api error
|
|
||||||
// 10300 = FFZ api error
|
|
||||||
const errorMsg = errorCode === 20000 ? "User not found" : "API error";
|
const errorMsg = errorCode === 20000 ? "User not found" : "API error";
|
||||||
return (
|
return (
|
||||||
<m.div
|
<m.div
|
||||||
|
@ -136,62 +112,57 @@ function UserPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if json is empty, and if channelEmotes is incomplete, show loading screen
|
// if json is empty, and if channelEmotes is incomplete, show loading screen
|
||||||
if (
|
if (Object.keys(channelEmotes).length < 4) {
|
||||||
Object.keys(channelEmotes).length < 4 ||
|
|
||||||
!userData ||
|
|
||||||
Object.keys(userData).length === 0
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-full items-center justify-center text-3xl">
|
<div className="flex h-screen w-full items-center justify-center text-3xl">
|
||||||
<Loading />
|
<Loading />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log(channelEmotes);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
|
||||||
<title>{title}</title>
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content={`${username}'s portfolio on toffee`}
|
|
||||||
/>
|
|
||||||
</Head>
|
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div className="mt-7 inline-grid w-[calc(100%-40px)] max-w-5xl grid-cols-10 gap-3 pl-2 font-plusJakarta lg:mt-12 lg:pl-0 lg:pr-2">
|
<m.div
|
||||||
|
className="mt-7 inline-grid w-[calc(100%-40px)] max-w-5xl grid-cols-10 gap-3 pl-2 font-plusJakarta lg:mt-12 lg:pl-0 lg:pr-2"
|
||||||
|
variants={containerVariants}
|
||||||
|
>
|
||||||
{/* User "banner" */}
|
{/* User "banner" */}
|
||||||
<div className="col-span-10 mb-2 rounded-2xl bg-zinc-800 bg-opacity-70 p-3">
|
<m.div
|
||||||
|
className="col-span-10 mb-2 rounded-2xl bg-zinc-800 bg-opacity-70 p-3"
|
||||||
|
variants={userBannerVariants}
|
||||||
|
>
|
||||||
<div className="flex items-center justify-between p-4">
|
<div className="flex items-center justify-between p-4">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<div className="relative bottom-[70px] w-[169px]">
|
<div className="relative bottom-[54px] -left-7 w-[110px] md:bottom-[70px] md:left-0 md:w-[169px]">
|
||||||
<Image
|
<Image
|
||||||
src={userData.avatar_url}
|
src={props.userData.avatar_url}
|
||||||
alt="User avatar"
|
alt="User avatar"
|
||||||
width={140}
|
width={140}
|
||||||
height={140}
|
height={140}
|
||||||
priority
|
priority
|
||||||
className="absolute rounded-lg border-4"
|
className="absolute rounded-lg border-4"
|
||||||
style={{
|
style={{
|
||||||
borderColor: userData.badges[0]
|
borderColor: props.userData.badges[0]
|
||||||
? userData.badges[0].color
|
? props.userData.badges[0].color
|
||||||
: "grey",
|
: "grey",
|
||||||
// "glow" effect
|
// "glow" effect
|
||||||
boxShadow: `0px 0px 20px 1px ${
|
boxShadow: `0px 0px 20px 1px ${
|
||||||
userData.badges[0]
|
props.userData.badges[0]
|
||||||
? userData.badges[0].color
|
? props.userData.badges[0].color
|
||||||
: "transparent"
|
: "transparent"
|
||||||
}`,
|
}`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-col">
|
<div className="flex-col overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||||
<h1 className="text-4xl font-semibold text-white">
|
<h1 className="w-full overflow-hidden overflow-ellipsis whitespace-nowrap text-2xl font-semibold text-white lg:text-4xl">
|
||||||
{userData.name}
|
{props.userData.name}
|
||||||
</h1>
|
</h1>
|
||||||
{/* User's badges */}
|
{/* User's badges */}
|
||||||
<div className="mt-1 flex flex-row text-sm">
|
<div className="mt-1 flex flex-row text-sm">
|
||||||
{userData.badges ? (
|
{props.userData.badges ? (
|
||||||
userData.badges.map(
|
props.userData.badges.map(
|
||||||
(badge: {
|
(badge: {
|
||||||
name: string;
|
name: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
@ -220,14 +191,17 @@ function UserPage() {
|
||||||
$
|
$
|
||||||
</span>
|
</span>
|
||||||
<span className="text-4xl text-white">
|
<span className="text-4xl text-white">
|
||||||
{userData.net_worth.toLocaleString("en-US")}
|
{props.userData.net_worth.toLocaleString("en-US")}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</m.div>
|
||||||
{/* Main Container */}
|
{/* Main Container */}
|
||||||
<div className="col-span-10 inline-grid grid-cols-7 gap-3 rounded-2xl lg:col-span-7">
|
<m.div
|
||||||
|
className="col-span-10 inline-grid grid-cols-7 gap-3 rounded-2xl lg:col-span-7"
|
||||||
|
variants={mainContainerVariants}
|
||||||
|
>
|
||||||
{/* User's Rank/Graph */}
|
{/* User's Rank/Graph */}
|
||||||
<div className="col-span-7 rounded-2xl bg-zinc-800 bg-opacity-70">
|
<div className="col-span-7 rounded-2xl bg-zinc-800 bg-opacity-70">
|
||||||
<div className="flex flex-row items-center justify-between p-5">
|
<div className="flex flex-row items-center justify-between p-5">
|
||||||
|
@ -238,7 +212,7 @@ function UserPage() {
|
||||||
<div className="flex items-center text-3xl font-bold">
|
<div className="flex items-center text-3xl font-bold">
|
||||||
<span className="text-zinc-400">#</span>
|
<span className="text-zinc-400">#</span>
|
||||||
<span className="text-white">
|
<span className="text-white">
|
||||||
{userData.rank.toLocaleString("en-US")}
|
{props.userData.rank.toLocaleString("en-US")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -256,7 +230,7 @@ function UserPage() {
|
||||||
$
|
$
|
||||||
</span>
|
</span>
|
||||||
<span className="text-3xl text-white sm:text-4xl">
|
<span className="text-3xl text-white sm:text-4xl">
|
||||||
{userData.net_worth.toLocaleString("en-US")}
|
{props.userData.net_worth.toLocaleString("en-US")}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
@ -275,7 +249,7 @@ function UserPage() {
|
||||||
{errorCode === 20000 ? (
|
{errorCode === 20000 ? (
|
||||||
<h1 className=" text-zinc-400">{`Could not load assets`}</h1>
|
<h1 className=" text-zinc-400">{`Could not load assets`}</h1>
|
||||||
) : (
|
) : (
|
||||||
userData.assets.map(
|
props.userData.assets.map(
|
||||||
(asset: {
|
(asset: {
|
||||||
name: string;
|
name: string;
|
||||||
count: number;
|
count: number;
|
||||||
|
@ -322,7 +296,7 @@ function UserPage() {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-row items-center justify-center">
|
<div className="flex w-full flex-row items-center justify-center">
|
||||||
{
|
{
|
||||||
// show provider logo (7tv, bttv, ffz, ttv)
|
// show provider logo (7tv, bttv, ffz, twitch)
|
||||||
asset.provider === "7tv" ? (
|
asset.provider === "7tv" ? (
|
||||||
<div className="mr-1 pt-[1px] text-7tv ">
|
<div className="mr-1 pt-[1px] text-7tv ">
|
||||||
<SevenTVLogo />
|
<SevenTVLogo />
|
||||||
|
@ -336,7 +310,7 @@ function UserPage() {
|
||||||
<FFZLogo />
|
<FFZLogo />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mr-1 w-4 pt-[1px] text-ttv">
|
<div className="mr-1 w-4 pt-[1px] text-twitch">
|
||||||
<TwitchLogo />
|
<TwitchLogo />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -352,29 +326,41 @@ function UserPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</m.div>
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="col-span-10 flex flex-col justify-start md:flex-row lg:col-span-3 lg:flex-col">
|
<m.div
|
||||||
<div className="center mb-3 mr-3 inline-grid grid-cols-2 gap-3 rounded-2xl bg-zinc-800 bg-opacity-70 p-5 text-xl font-medium lg:mr-0">
|
className="col-span-10 flex flex-col justify-start md:flex-row lg:col-span-3 lg:flex-col"
|
||||||
|
variants={sidebarVariants}
|
||||||
|
>
|
||||||
|
<m.div
|
||||||
|
className="center mb-3 mr-3 inline-grid grid-cols-2 gap-3 rounded-2xl bg-zinc-800 bg-opacity-70 p-5 text-xl font-medium lg:mr-0"
|
||||||
|
variants={sidebarItemVariants}
|
||||||
|
>
|
||||||
{/* User's Stats, left side is label, right side is value */}
|
{/* User's Stats, left side is label, right side is value */}
|
||||||
<h1>Points</h1>
|
<h1>Points</h1>
|
||||||
<h1>{userData.points.toLocaleString("en-US")}</h1>
|
<h1>{props.userData.points.toLocaleString("en-US")}</h1>
|
||||||
<h1>Shares</h1>
|
<h1>Shares</h1>
|
||||||
<h1>{userData.shares.toLocaleString("en-US")}</h1>
|
<h1>{props.userData.shares.toLocaleString("en-US")}</h1>
|
||||||
<h1>Trades</h1>
|
<h1>Trades</h1>
|
||||||
<h1>{(userData.trades ?? 0).toLocaleString("en-US")}</h1>
|
<h1>{(props.userData.trades ?? 0).toLocaleString("en-US")}</h1>
|
||||||
<h1>Peak rank</h1>
|
<h1>Peak rank</h1>
|
||||||
<h1>{(userData.peak_rank ?? 0).toLocaleString("en-US")}</h1>
|
<h1>{(props.userData.peak_rank ?? 0).toLocaleString("en-US")}</h1>
|
||||||
<h1>Joined</h1>
|
<h1>Joined</h1>
|
||||||
<h1>
|
<h1>
|
||||||
{new Date(userData.joined ?? 0).toLocaleDateString("en-US", {
|
{new Date(props.userData.joined ?? 0).toLocaleDateString(
|
||||||
year: "numeric",
|
"en-US",
|
||||||
month: "short",
|
{
|
||||||
})}
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
}
|
||||||
|
)}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</m.div>
|
||||||
{/* User's Favorite Emote */}
|
{/* User's Favorite Emote */}
|
||||||
<div className="flex flex-col rounded-2xl bg-zinc-800 bg-opacity-70">
|
<m.div
|
||||||
|
className="flex flex-col rounded-2xl bg-zinc-800 bg-opacity-70"
|
||||||
|
variants={sidebarItemVariants}
|
||||||
|
>
|
||||||
<div className="h-11 w-full rounded-t-2xl bg-pink-400">
|
<div className="h-11 w-full rounded-t-2xl bg-pink-400">
|
||||||
<h1 className="m-1 text-center text-2xl font-bold">
|
<h1 className="m-1 text-center text-2xl font-bold">
|
||||||
Favorite Emote
|
Favorite Emote
|
||||||
|
@ -385,9 +371,9 @@ function UserPage() {
|
||||||
This user has not yet set a favorite emote.
|
This user has not yet set a favorite emote.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</m.div>
|
||||||
</div>
|
</m.div>
|
||||||
</div>
|
</m.div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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<UserPageProps> = 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) {
|
UserPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <DashLayout>{page}</DashLayout>;
|
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 <DashLayout metaTags={metaTags}>{page}</DashLayout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserPage;
|
export default UserPage;
|
||||||
|
|
|
@ -16,7 +16,7 @@ module.exports = {
|
||||||
colors: {
|
colors: {
|
||||||
"7tv": "#4fc2bc",
|
"7tv": "#4fc2bc",
|
||||||
bttv: "#d50014",
|
bttv: "#d50014",
|
||||||
ttv: "#9146FF",
|
twitch: "#9146FF",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue