From e77843c749d4beb56be5cdb13e5720b1e5685c9d Mon Sep 17 00:00:00 2001 From: iiTzArcur Date: Fri, 13 Sep 2024 09:33:58 +0200 Subject: [PATCH] add a admin panel in the stats results --- src/api/GametoolsApi.tsx | 43 ++- src/api/ReturnTypes.tsx | 8 + src/assets/scss/App.scss | 23 ++ .../routes/Stats/Player/AdminPanel.tsx | 284 ++++++++++++++++++ src/components/routes/Stats/Player/Main.tsx | 19 ++ .../routes/Stats/Player/OverviewStats.tsx | 46 ++- src/locales/languages/en-US.json | 15 + 7 files changed, 427 insertions(+), 11 deletions(-) create mode 100644 src/components/routes/Stats/Player/AdminPanel.tsx diff --git a/src/api/GametoolsApi.tsx b/src/api/GametoolsApi.tsx index a8d5906..cbd77fb 100644 --- a/src/api/GametoolsApi.tsx +++ b/src/api/GametoolsApi.tsx @@ -11,6 +11,7 @@ import { ServerPlayersReturn, ServerSearch, StatsGraph, + SusStats, UserGames, } from "./ReturnTypes"; import { battlebitApi } from "./battlebitApi"; @@ -110,6 +111,23 @@ interface ServerGraphInfo { platform?: string; } +interface managerPlayerInfo { + playerId: number; +} + +interface managerPlayer { + id: number; + avatar: string; + name: string; + vban: { [Key: string]: { bannedUntil: string; reason: string } }; + ingame: string[]; + otherNames: { + updateTimestamp: string; + usedNames: string[]; + }; + bfban: bfbanPlayers; +} + export interface bfbanPlayer { apiUrl: string; names: { [name: string]: bfbanPlayers }; @@ -123,7 +141,7 @@ export interface bfeacPlayer { } export interface bfbanPlayers { - personaId: string; + personaId?: string; url: string; status: string; hacker: boolean; @@ -206,6 +224,21 @@ export class ApiProvider extends JsonClient { }); } + async sus({ + game, + playerId, + platform = "pc", + }: { + game: string; + playerId: number; + platform: string; + }): Promise { + return await this.getJsonMethod(`/${game}/sus/`, { + platform: platform, + playerid: playerId.toString(), + }); + } + async statsarray({ game, getter, @@ -528,6 +561,14 @@ export class ApiProvider extends JsonClient { return await this.getJsonMethod(`/bfglobal/totalstatusarray/`, {}); } + async managerCheckPlayer({ + playerId, + }: managerPlayerInfo): Promise { + return await this.getJsonMethod("/manager/checkban", { + playerid: playerId.toString(), + }); + } + async bfbanCheckPlayers({ getter, usernames, diff --git a/src/api/ReturnTypes.tsx b/src/api/ReturnTypes.tsx index 8b6fde1..84ba1ef 100644 --- a/src/api/ReturnTypes.tsx +++ b/src/api/ReturnTypes.tsx @@ -40,6 +40,10 @@ export type PlatoonStats = { servers: ServerList[]; }; +export type SusStats = { + weapons: MainStatsWeapon & SusWeapon[]; +}; + export type MainStats = { apiUrl: string; accuracy: number; @@ -183,6 +187,10 @@ export type MainStatsVehicle = { vehicleName: string; }; +export type SusWeapon = { + susReason: string; +}; + export type MainStatsWeapon = { accuracy: number; headshots: number; diff --git a/src/assets/scss/App.scss b/src/assets/scss/App.scss index eaf93a4..e80833b 100644 --- a/src/assets/scss/App.scss +++ b/src/assets/scss/App.scss @@ -116,6 +116,20 @@ body { @extend %font-shared; } + th { + color: var(--color-text); + font-weight: bold; + font-size: 12px; + @extend %font-shared; + } + + td { + color: var(--color-alt-text); + font-weight: 500; + font-size: 13px; + @extend %font-shared; + } + span { color: var(--color-text); } @@ -203,10 +217,19 @@ body { transition: 0.4s; } +.darkSlider { + @extend .slider; + background-color: var(--color-m96-base); +} + input:checked + .slider { background-color: #2196f3; } +input:checked + .darkSlider { + background-color: var(--color-m96-alt-base); +} + input:focus + .slider { box-shadow: 0 0 1px #2196f3; } diff --git a/src/components/routes/Stats/Player/AdminPanel.tsx b/src/components/routes/Stats/Player/AdminPanel.tsx new file mode 100644 index 0000000..5fa33ea --- /dev/null +++ b/src/components/routes/Stats/Player/AdminPanel.tsx @@ -0,0 +1,284 @@ +import { useQuery } from "@tanstack/react-query"; +import * as React from "react"; +import { useTranslation } from "react-i18next"; +import { GametoolsApi } from "../../../../api/GametoolsApi"; +import { MainStatsWeapon, SusWeapon } from "../../../../api/ReturnTypes"; +import { newTitles } from "../../../../api/static"; +import "../../../../locales/config"; +import sslFix from "../../../functions/fixEaAssets"; +import { Box } from "../../../Materials"; +import { PlatformViews } from "./Main"; +import * as Mainstyles from "./Main.module.scss"; +import * as styles from "./Main.module.scss"; + +function SusWeapon(props: Readonly): React.ReactElement { + const { t } = useTranslation(); + + if (!newTitles.includes(props.game)) { + return <>; + } + + const getLanguage = () => window.localStorage.i18nextLng; + const numberFormat = new Intl.NumberFormat(getLanguage()); + + const { + isLoading, + isError, + error, + data: stats, + } = useQuery({ + queryKey: ["susStats" + props.game + props?.stats?.id, props.platform], + queryFn: () => + GametoolsApi.sus({ + game: props.game, + playerId: props?.stats?.id, + platform: props.platform, + }), + }); + + if (isError) { + return ( + <> +

{t("stats.adminPanel.susWeapons")}

+

{t("stats.error", { error: error })}

+ + ); + } + + if (isLoading || stats === undefined) { + return ( + <> +

{t("stats.adminPanel.susWeapons")}

+

{t("loading")}

+ + ); + } + + const weapons = stats?.weapons || []; + + if (weapons?.length === 0) { + return ( + <> +

{t("stats.adminPanel.susWeapons")}

+

{t("resultNotFound")}

+ + ); + } + + return ( + <> +

{t("stats.adminPanel.susWeapons")}

+ {weapons.map((key: SusWeapon & MainStatsWeapon, index: number) => { + return ( +
+
+

{key?.weaponName}

+ +
+ {key?.type && ( +
+

{key?.type}

+

{t("stats.rows.type")}

+
+ )} +
+

{numberFormat.format(key?.kills)}

+

{t("stats.rows.kills")}

+
+
+

+ {numberFormat.format(key?.killsPerMinute)} +

+

+ {t("stats.rows.killsPerMinute")} +

+
+ {key?.accuracy !== undefined && ( +
+

+ {numberFormat.format(key?.accuracy)}% +

+

+ {t("stats.rows.accuracy")} +

+
+ )} + {key?.damagePerMinute !== undefined && ( +
+

{numberFormat.format(key?.damagePerMinute)}

+

+ {t("stats.rows.damagePerMinute")} +

+
+ )} +
+

+ {numberFormat.format(key?.headshots)}% +

+

+ {t("stats.rows.headshots")} +

+
+ {key?.hitVKills !== undefined && ( +
+

{numberFormat.format(key?.hitVKills)}

+

+ {t("stats.rows.hitVKills")} +

+
+ )} +
+ ); + })} + + ); +} + +export function AdminPanel(props: Readonly): React.ReactElement { + const { t } = useTranslation(); + + const { + isLoading, + isError, + error, + data: stats, + } = useQuery({ + queryKey: ["managerCheckPlayer" + props?.stats?.id], + queryFn: () => + GametoolsApi.managerCheckPlayer({ + playerId: props?.stats?.id, + }), + }); + + // stats = { + // "id": 1699922734, + // "avatar": "https://secure.download.dm.origin.com/production/avatar/prod/userAvatar/37268238/208x208.JPEG", + // "name": "ABARN1998", + // "vban": { + // "BoB": { + // "bannedUntil": "2022-07-22T11:15:01.350000", + // "reason": "raciscm", + // } + // }, + // "ingame": [ + // "[BoB]#3 EU Sinai 24/7 join US discord.gg/BoBofficial", + // "[BoB]#1 EU All CQ Maps! JOIN US:discord.gg/BoB", + // "[BoB]#4 EU Frontlines JOIN US: discord.gg/BoB", + // ], + // "otherNames": { + // "updateTimestamp": "2022-07-03T20:03:46.395000", + // "usedNames": ["iiTzArcur"], + // }, + // "bfban": { + // "url": "https://bfban.com/#/cheaters/1007506226959", + // "status": "1", + // "hacker": true, + // "originId": "unturned6646", + // "originPersonaId": "1004008026959", + // "originUserId": "1007506226959", + // "cheatMethods": "aimbot", + // }, + // } + + if (isError) { + return ( +
+ +

{t("stats.adminPanel.main")}

+

{t("stats.error", { error: error })}

+
+
+ ); + } + + if (isLoading || stats === undefined) { + return ( +
+ +

{t("stats.adminPanel.main")}

+

{t("loading")}

+
+
+ ); + } + + return ( +
+ +

{t("stats.adminPanel.main")}

+ {/*

{t("stats.adminPanel.vban.main")}

*/} + + + + + + + + {Array.from( + Array( + Math.max( + Object.keys(stats?.vban)?.length, + stats?.ingame?.length, + stats?.otherNames?.usedNames?.length, + ), + ), + (_, i) => { + // Fill with N/A if not used + const vbanItem = Object.entries(stats?.vban)[i] || []; + return ( + + {i === 0 && vbanItem.length === 0 ? ( + <> + + + + ) : ( + <> + + + + )} + {i === 0 && stats?.otherNames?.usedNames?.length === 0 ? ( + + ) : ( + + )} + {i === 0 && stats?.ingame?.length === 0 ? ( + + ) : ( + + )} + + ); + }, + )} +
{t("stats.adminPanel.vban.group")}{t("stats.adminPanel.vban.reason")}{t("stats.adminPanel.otherNames.main")}{t("stats.adminPanel.bannedServer.main")}
{t("notApplicable")}{t("notApplicable")}{vbanItem[0]}{vbanItem[1]?.reason}{t("notApplicable")}{stats?.otherNames?.usedNames[i]}{t("notApplicable")}{stats?.ingame[i]}
+
+ + +
+ ); +} diff --git a/src/components/routes/Stats/Player/Main.tsx b/src/components/routes/Stats/Player/Main.tsx index 068f41d..8e7167b 100644 --- a/src/components/routes/Stats/Player/Main.tsx +++ b/src/components/routes/Stats/Player/Main.tsx @@ -17,6 +17,7 @@ import { import "../../../../locales/config"; import { getLanguage } from "../../../../locales/config"; import { BackButton, RightArrow } from "../../../Materials"; +import { AdminPanel } from "./AdminPanel"; import { ViewClasses } from "./Classes"; import { DetailedStats } from "./DetailedStats"; import { GadgetGraph, ViewGadgets } from "./Gadgets"; @@ -288,6 +289,11 @@ interface GameStatsItems { function GameStats(props: Readonly): React.ReactElement { const { game, name, type, platform } = props; + const [showAdminPanel, setShowAdminPanel] = useLocalStorage( + "stats_showAdminPanel", + false, + ); + const { isLoading, isError, @@ -318,9 +324,22 @@ function GameStats(props: Readonly): React.ReactElement { errors={error} isError={isError} name={name} + showAdminPanel={showAdminPanel} + setShowAdminPanel={setShowAdminPanel} > {props.children} + {showAdminPanel && ( + + )} >; } export function ViewStats(props: Readonly): React.ReactElement { @@ -172,18 +174,42 @@ export function ViewStats(props: Readonly): React.ReactElement {

- {!!stats?.secondsPlayed && ( -
-

- {t("stats.main.timePlayed")}{" "} - - {t("change", { - change: addSeconds(new Date(), stats?.secondsPlayed), - })} - +

+ {!!stats?.secondsPlayed && ( +
+

+ {t("stats.main.timePlayed")}{" "} + + {t("change", { + change: addSeconds(new Date(), stats?.secondsPlayed), + })} + +

+
+ )} +
+ +

+ {t("stats.toggleAdminPanel")}

- )} +