diff --git a/interfaces/APIError.ts b/interfaces/APIError.ts new file mode 100644 index 0000000..5d2c52d --- /dev/null +++ b/interfaces/APIError.ts @@ -0,0 +1,6 @@ +export default interface APIError { + error: { + message: string; + code: number; + }; +} diff --git a/interfaces/UserAsset.ts b/interfaces/UserAsset.ts new file mode 100644 index 0000000..70ab4ec --- /dev/null +++ b/interfaces/UserAsset.ts @@ -0,0 +1,5 @@ +export default interface UserAsset { + name: string; + count: number; + provider: "7tv" | "bttv" | "ffz" | "twitch"; +} diff --git a/interfaces/UserBadge.ts b/interfaces/UserBadge.ts new file mode 100644 index 0000000..2f9f0bd --- /dev/null +++ b/interfaces/UserBadge.ts @@ -0,0 +1,5 @@ +export default interface UserBadge { + name: string; + color: string; + priority: number; +} diff --git a/interfaces/UserFakeDataEntry.ts b/interfaces/UserFakeDataEntry.ts new file mode 100644 index 0000000..c47a936 --- /dev/null +++ b/interfaces/UserFakeDataEntry.ts @@ -0,0 +1,12 @@ +import UserAsset from "./UserAsset"; +import UserBadge from "./UserBadge"; + +export default interface UserFakeDataEntry { + id: number; + name: string; + points: number; + daily_change: number; + daily_change_percent: number; + assets: UserAsset[]; + badges: UserBadge[]; +} diff --git a/interfaces/UserFakeDataJSON.ts b/interfaces/UserFakeDataJSON.ts new file mode 100644 index 0000000..610cb55 --- /dev/null +++ b/interfaces/UserFakeDataJSON.ts @@ -0,0 +1,5 @@ +import UserJSONEntry from "./UserJSONEntry"; + +export default interface UserFakeDataJSON { + data: UserJSONEntry[]; +} diff --git a/interfaces/UserJSONEntry.ts b/interfaces/UserJSONEntry.ts new file mode 100644 index 0000000..0cb67c1 --- /dev/null +++ b/interfaces/UserJSONEntry.ts @@ -0,0 +1,8 @@ +import UserFakeDataEntry from "./UserFakeDataEntry"; + +export default interface UserJSONEntry extends UserFakeDataEntry { + net_worth: number; + shares: number; + avatar_url: string; + rank: number; +} diff --git a/pages/api/fakeUsers.ts b/pages/api/fakeUsers.ts index 172c15c..70fe03a 100644 --- a/pages/api/fakeUsers.ts +++ b/pages/api/fakeUsers.ts @@ -1,4 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import UserBadge from "../../interfaces/UserBadge"; +import UserFakeDataEntry from "../../interfaces/UserFakeDataEntry"; +import UserJSONEntry from "../../interfaces/UserJSONEntry"; import { createRedisInstance } from "../../misc/redis"; import { getUserByName } from "../../misc/TwitchAPI"; import { fakePrices } from "./fakePrices"; @@ -23,43 +26,14 @@ export default async function handler( return; } - let data = fakeData; - // calculate all net worths - data = data.map((user) => { - return { - ...user, - net_worth: - user.points + - user.assets.reduce( - (a, b) => a + b.count * (fakePrices[b.name] ?? 0), - 0 - ), - }; - }); - // calculate ranking based on net worth - data = data.sort((a, b) => (b.net_worth ?? 0) - (a.net_worth ?? 0)); - data = data.map((user, i) => { - return { - ...user, - rank: i + 1, - // calculate total assets held (shares) - shares: user.assets.reduce((a, b) => a + b.count, 0), - // sort users badges by priority - badges: (user.badges ?? []).sort((a, b) => b.priority - a.priority ?? 0), - // sort users assets by total value - assets: user.assets.sort( - (a, b) => - (fakePrices[b.name] ?? 0) * b.count - - (fakePrices[a.name] ?? 0) * a.count - ), - }; - }); - + let userJSON: UserJSONEntry[]; + let userList: UserFakeDataEntry[] = fakeData; + let avatarURL = "/img/logo.webp"; // default avatar // if username is specified, only return that user if (username) { - // if user does not exist, return error - data = data.filter((u) => u.name === username); - if (data.length === 0) { + // filter for user, add required types + userList = userList.filter((u) => u.name === username); + if (userList.length === 0) { res .status(404) .json({ error: { message: "User not found", code: 20000 } }); @@ -80,92 +54,95 @@ export default async function handler( // temp who cares twitchData.data[0] = {}; twitchData.data[0].profile_image_url = "/img/logo.webp"; + } else { + avatarURL = twitchData.data[0].profile_image_url; } - // add users profile picture url - data = data.map((u) => { - return { - ...u, - avatar_url: twitchData.data[0].profile_image_url ?? "", - }; - }); - res.status(200).json({ data: data[0] }); - return; } + + userJSON = userList.map((user) => { + return { + ...user, + // calculate total assets held (shares) + shares: user.assets.reduce((a, b) => a + b.count, 0), + // sort users badges by priority + badges: (user.badges ?? []).sort((a, b) => b.priority - a.priority ?? 0), + avatar_url: avatarURL, + rank: 0, + // sort users assets by total value + assets: user.assets.sort( + (a, b) => + (fakePrices[b.name] ?? 0) * b.count - + (fakePrices[a.name] ?? 0) * a.count + ), + // calculate net worth + net_worth: + user.points + + user.assets.reduce( + (a, b) => a + b.count * (fakePrices[b.name] ?? 0), + 0 + ), + }; + }); + // calculate ranking based on net worth + userJSON = userJSON.sort((a, b) => (b.net_worth ?? 0) - (a.net_worth ?? 0)); + userJSON = userJSON.map((u, i) => { + return { + ...u, + rank: i + 1, + }; + }); + if (sortBy) { if (sortBy === "daily_change") { - data = data.sort((a, b) => b.daily_change - a.daily_change); + userJSON = userJSON.sort((a, b) => b.daily_change - a.daily_change); } else if (sortBy === "daily_change_percent") { - data = data.sort( + userJSON = userJSON.sort( (a, b) => b.daily_change_percent - a.daily_change_percent ); } else if (sortBy === "shares") { - data = data.sort((a, b) => (b.shares ?? 0) - (a.shares ?? 0)); + userJSON = userJSON.sort((a, b) => (b.shares ?? 0) - (a.shares ?? 0)); } else if (sortBy === "points") { - data = data.sort((a, b) => b.points - a.points); + userJSON = userJSON.sort((a, b) => b.points - a.points); } else if (sortBy === "name") { - data = data.sort((a, b) => a.name.localeCompare(b.name)); + userJSON = userJSON.sort((a, b) => a.name.localeCompare(b.name)); } if (sortAsc === "true") { // slow but only needed for temporary fake data anyway - data = data.reverse(); + userJSON = userJSON.reverse(); } } // fake loading time await new Promise((resolve) => setTimeout(resolve, 250 + Math.random() * 1000) ); - res.status(200).json({ data: data }); + res.status(200).json({ data: userJSON }); } -interface asset { - name: string; - count: number; - provider: "7tv" | "bttv" | "ffz" | "twitch"; -} -interface fakeDataEntry { - id: number; - name: string; - points: number; - daily_change: number; - daily_change_percent: number; - assets: asset[]; - net_worth?: number; - shares?: number; - avatar_url?: string; - badges?: badge[]; -} - -interface badge { - name: string; - color: string; - priority: number; -} - -const adminBadge: badge = { +const adminBadge: UserBadge = { name: "Admin", color: "#CC3333", priority: 99999, }; -const CEOBadge: badge = { +const CEOBadge: UserBadge = { name: "CEO", color: "#F97316", priority: 100000, }; -const webDevBadge: badge = { +const webDevBadge: UserBadge = { name: "Web Dev", color: "#a855f7", priority: 50000, }; -const botDevBadge: badge = { +const botDevBadge: UserBadge = { name: "Bot Dev", color: "#48b2f1", priority: 50001, }; -const fakeData: fakeDataEntry[] = [ +const fakeData: UserFakeDataEntry[] = [ { id: 4, name: "3zachm", @@ -384,6 +361,7 @@ const fakeData: fakeDataEntry[] = [ provider: "7tv", }, ], + badges: [], }, { id: 6, @@ -423,6 +401,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 7, @@ -462,6 +441,7 @@ const fakeData: fakeDataEntry[] = [ provider: "ffz", }, ], + badges: [], }, { id: 8, @@ -496,6 +476,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 9, @@ -535,6 +516,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 10, @@ -574,6 +556,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 11, @@ -613,6 +596,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 12, @@ -657,6 +641,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 13, @@ -686,6 +671,7 @@ const fakeData: fakeDataEntry[] = [ provider: "7tv", }, ], + badges: [], }, { id: 14, @@ -720,6 +706,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 15, @@ -754,6 +741,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 16, @@ -788,6 +776,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 17, @@ -822,6 +811,7 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, { id: 18, @@ -856,7 +846,6 @@ const fakeData: fakeDataEntry[] = [ provider: "twitch", }, ], + badges: [], }, ]; - -export type { fakeDataEntry }; diff --git a/pages/ranking/index.tsx b/pages/ranking/index.tsx index 915b22c..ed61365 100644 --- a/pages/ranking/index.tsx +++ b/pages/ranking/index.tsx @@ -2,6 +2,7 @@ import { m, Variants } from "framer-motion"; import Link from "next/link"; import { ReactElement, useEffect, useState } from "react"; import Loading from "../../components/common/Loading"; +import UserJSONEntry from "../../interfaces/UserJSONEntry"; import DashLayout from "../../layouts/DashLayout"; function Ranking() { @@ -158,47 +159,44 @@ function Ranking() { > { // generate table rows - fakeData.map( - (entry: { [key: string]: any }, index: number) => { - // if daily change is negative, make it red - let changeClass = " text-lime-500"; - if (entry.daily_change_percent < 0) { - changeClass = " text-red-500"; - } - return ( - -

- {index + 1} -

- -

- {entry.name} -

- -

{entry.net_worth.toLocaleString("en-US")}

-

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

-

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

-

- {( - Math.round(entry.daily_change_percent * 1000) / - 10 - ).toFixed(1) + "%"} -

-
- ); + fakeData.map((entry: UserJSONEntry, index: number) => { + // if daily change is negative, make it red + let changeClass = " text-lime-500"; + if (entry.daily_change_percent < 0) { + changeClass = " text-red-500"; } - ) + return ( + +

+ {index + 1} +

+ +

+ {entry.name} +

+ +

{entry.net_worth.toLocaleString("en-US")}

+

+ {entry.points.toLocaleString("en-US")} +

+

+ {entry.shares.toLocaleString("en-US")} +

+

+ {( + Math.round(entry.daily_change_percent * 1000) / 10 + ).toFixed(1) + "%"} +

+
+ ); + }) } ) diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index 61406e5..ab9c2be 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -5,6 +5,8 @@ import DashLayout from "../../../layouts/DashLayout"; import Image from "next/image"; import Loading from "../../../components/common/Loading"; import { GetServerSideProps } from "next"; +import UserJSONEntry from "../../../interfaces/UserJSONEntry"; +import APIError from "../../../interfaces/APIError"; interface EmoteURLs { "7tv": { [key: string]: string }; @@ -15,7 +17,8 @@ interface EmoteURLs { } interface UserPageProps { - userData: { [key: string]: any }; + userData: UserJSONEntry; + serverError: APIError | null; } function UserPage(props: UserPageProps) { @@ -28,8 +31,9 @@ function UserPage(props: UserPageProps) { useEffect(() => { if (!router.isReady) return; - if (props.userData.error) { - setErrorCode(props.userData.error.code); + // if it is of + if (props.serverError) { + setErrorCode(props.serverError.error.code); } fetch("/api/emotes") .then((res) => res.json()) @@ -342,18 +346,15 @@ function UserPage(props: UserPageProps) {

Shares

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

Trades

-

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

+

{(0).toLocaleString("en-US")}

Peak rank

-

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

+

{(0).toLocaleString("en-US")}

Joined

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

{/* User's Favorite Emote */} @@ -565,25 +566,30 @@ export const getServerSideProps: GetServerSideProps = async ( `/api/fakeUsers?u=${context.query.username}`, process.env.NEXT_PUBLIC_URL ); + // TODO: add error handling 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, + serverError: user, + }, + }; } - return { props: { userData: user.data } }; + return { props: { userData: user.data[0], serverError: null } }; }; UserPage.getLayout = function getLayout(page: ReactElement) { - const { userData } = page.props; + const { userData, serverError } = page.props; const metaTags = { - title: !userData.error + title: !serverError ? `${userData.name ?? "User 404"} - toffee` : "User 404 - toffee", - description: !userData.error + description: !serverError ? `${userData.name}'s portfolio on toffee` : "Couldn't find that user on toffee... :(", - imageUrl: !userData.error ? userData.avatar_url : undefined, + imageUrl: !serverError ? userData.avatar_url : undefined, misc: { "twitter:card": "summary", },