diff --git a/.cursor/skills/actions-and-pg-migration/SKILL.md b/.cursor/skills/actions-and-pg-migration/SKILL.md index 0fb977e9f..11a29962f 100644 --- a/.cursor/skills/actions-and-pg-migration/SKILL.md +++ b/.cursor/skills/actions-and-pg-migration/SKILL.md @@ -21,11 +21,11 @@ MY_ACTION: { ### Action files -Place the implementation in `src/actions/my-action.ts`. Always use `.js` extensions on imports (the package uses ESM): +Place the implementation in `src/actions/my-action.ts`: ```typescript -import { MakeAction } from "#lib/actions/actions.js"; -import DB from "#services/pg/db.js"; +import { MakeAction } from "#lib/actions/actions"; +import DB from "#services/pg/db"; import { ExpectedErr } from "bliss"; export const ACTION_MyAction = MakeAction( @@ -75,7 +75,6 @@ return res.status(200).json({ success: true, description: "...", body: result }) ```typescript import DB from "#services/pg/db"; // routers / utils -import DB from "#services/pg/db.js"; // action files (ESM) ``` `DB` is a typed `Kysely` instance. Types come from the generated `tachi-db` workspace package (`src/generated/public/Priv*.ts`). diff --git a/.cursor/skills/mongo-migration-constraints/SKILL.md b/.cursor/skills/mongo-migration-constraints/SKILL.md deleted file mode 100644 index 8a3f62c65..000000000 --- a/.cursor/skills/mongo-migration-constraints/SKILL.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: mongo-migration-constraints -description: Migrates server code from MongoDB to Postgres without modifying the legacy Mongo service. Never edits typescript/server/src/services/mongo/ (especially db.ts). Requires removing MONGODB_KILL imports and replacing usage with Postgres (Kysely) in migrated files. Use during Mongo-to-Postgres migration, when migrating routers/actions/utils off #services/mongo/db, or when the user says not to touch mongo/db.ts. ---- - -# Mongo → Postgres migration - off-limits Mongo layer - -## Hard rules - -1. **Do not edit** `typescript/server/src/services/mongo/db.ts` (or any file under `typescript/server/src/services/mongo/`). The Mongo service stays as-is until it is retired separately. - -2. **In every file you migrate** to Postgres: **remove** `import MONGODB_KILL from "#services/mongo/db"` (and any `MONGODB_KILL` usage). Replace reads/writes with Kysely against `#services/pg/db` (and helpers in `src/lib/db-formats/` as needed). - -3. **Do not** add new `MONGODB_KILL` imports to files that no longer need Mongo. - -## How to migrate call sites - -Follow [actions-and-pg-migration/SKILL.md](../actions-and-pg-migration/SKILL.md) for actions, routers, and the usual migration steps. Use [db-formats/SKILL.md](../db-formats/SKILL.md) for `SELECT_*` / `To*Document` shapes. - -If something still truly depends on Mongo and cannot move yet, **leave that file unchanged** rather than editing `db.ts` or other mongo internals to “help” the migration. - -## Quick checklist for a migrated file - -- [ ] No import from `#services/mongo/db` / `./db` under `services/mongo`. -- [ ] Uses `DB` from `#services/pg/db` (or `.js` in action files per project ESM rules). -- [ ] No edits under `typescript/server/src/services/mongo/`. diff --git a/.cursor/skills/no-oldtest-edits/SKILL.md b/.cursor/skills/no-oldtest-edits/SKILL.md deleted file mode 100644 index 411a27888..000000000 --- a/.cursor/skills/no-oldtest-edits/SKILL.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: no-oldtest-edits -description: Legacy. `*.oldtest.ts` snapshots were removed from the Tachi server after migration to Vitest `*.test.ts`. No action needed unless oldtest files are reintroduced. ---- - -# Legacy note - -Server tests live in `*.test.ts` under `typescript/server/src/`. The former Tap `*.oldtest.ts` tree has been deleted. - -If `*.oldtest.ts` files appear again, treat them as read-only snapshots and add or change behavior only in `*.test.ts` or source files. diff --git a/.cursor/skills/no-return-await/SKILL.md b/.cursor/skills/no-return-await/SKILL.md deleted file mode 100644 index 1eca7b160..000000000 --- a/.cursor/skills/no-return-await/SKILL.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -name: no-return-await -description: Prefer `return` over `return await` in async functions when the await is redundant. Use when writing or reviewing async TypeScript/JavaScript, simplifying control flow, or fixing redundant awaits on returned promises. ---- - -# Prefer `return` over `return await` - -## Default rule - -In an `async function`, **`return await x` and `return x` are equivalent** for callers when `x` is a Promise (or thenable): both return a Promise with the same fulfillment and rejection. - -Prefer **`return x`** - the extra `await` adds a microtask and obscures that you are simply forwarding the result. - -```typescript -// Prefer -async function load() { - return fetchData(); -} - -// Avoid (unless you need await for control flow - see below) -async function load() { - return await fetchData(); -} -``` - -## When you must `await` - -Use **`await`** (including `return await`) only when you need the async function to **suspend on that operation** for control flow: - -- **`try` / `catch` / `finally`**: To handle or finalize on **rejection** of the inner promise, you must `await` it inside `try`. A bare `return innerPromise()` does **not** route that rejection through `catch` / `finally` the same way. - -```typescript -async function safeLoad() { - try { - // `return await` is correct here so `catch` sees rejections from fetchData() - return await fetchData(); - } catch (e) { - return defaultData(); - } -} -``` - -- **`finally`** that must run after the inner work settles (same idea - often needs `await` in the `try`). - -If there is **no** `try`/`catch`/`finally` depending on that promise settling inside the function, **do not** use `return await`. - -## Review checklist - -- [ ] If the last statement is `return await expr` and nothing in the function uses `try`/`catch`/`finally` around that path for that promise → use `return expr`. -- [ ] If `catch` or `finally` must apply to failures of `expr` → keep `await` (often `return await expr` in the `try`). - -## Why - -- Clearer intent: forwarding a Promise vs. explicitly sequencing. -- Slightly leaner: no unnecessary `await` + re-wrapping. -- Aligns with common ESLint `no-return-await` guidance (with the try/catch exception above). diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e1301359c..34e87afe2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,7 @@ "TACHI_EMAIL_SECURE": "false" }, "postAttachCommand": "./dev/bootstrap.sh", - "forwardPorts": [3001, 3002], + "forwardPorts": [3000, 3001, 3002, 3003, 8080, 9779], "updateRemoteUserUID": true, "containerUser": "tachi", "customizations": { diff --git a/dev/functions.fish b/dev/functions.fish index 50d632b84..73f372b3d 100644 --- a/dev/functions.fish +++ b/dev/functions.fish @@ -56,7 +56,7 @@ function fish_greeting echo "" echo "Type $(cmd "just start") to start up tachi." echo " $(rgb "The site will start on http://localhost:3000." ffff00 000000)" - echo " $(rgb "The seeds web UI will start on http://localhost:3100." ffff00 000000)" + echo " $(rgb "The seeds web UI will start on http://localhost:3003." ffff00 000000)" echo " $(rgb "Use Ctrl+C to stop Tachi." ffff00 000000)" echo "" echo "You can also run:" diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 8f721ba52..609a85d47 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -153,11 +153,11 @@ services: build: dockerfile: Dockerfile.dev ports: - - "8080:8080" # server - "3000:3000" # client - "3001:3001" # docs - "3002:3002" # homepage - - "3100:3100" # seeds + - "3003:3003" # seeds + - "8080:8080" # server - "9779:9779" # metrics volumes: - ./:/tachi diff --git a/typescript/bot/src/utils/api-requests.ts b/typescript/bot/src/utils/api-requests.ts index 6c374358c..bfb30c782 100644 --- a/typescript/bot/src/utils/api-requests.ts +++ b/typescript/bot/src/utils/api-requests.ts @@ -14,7 +14,7 @@ import { type V3Game, } from "tachi-common"; -import type { ImportDeferred, ImportPollStatus, UGPTStats } from "./return-types"; +import type { ImportDeferred, ImportPollStatus, UserGameStatsReturn } from "./return-types"; import { RequestTypes, TachiServerV1Get, TachiServerV1Request } from "./fetch-tachi"; import { Sleep } from "./misc"; @@ -29,11 +29,11 @@ export async function GetUserInfo(userID: string | integer) { return res.body; } -export async function GetUGPTStats(userID: string | integer, game: V3Game) { - const res = await TachiServerV1Get(`/users/${userID}/games/${game}`, null); +export async function GetUserGameStats(userID: string | integer, game: V3Game) { + const res = await TachiServerV1Get(`/users/${userID}/games/${game}`, null); if (!res.success) { - throw new Error(`Failed to fetch UGPT stats for userID ${userID}, ${game}.`); + throw new Error(`Failed to fetch UserGame stats for userID ${userID}, ${game}.`); } return res.body; diff --git a/typescript/bot/src/utils/return-types.ts b/typescript/bot/src/utils/return-types.ts index eb2909ed0..143b70bd2 100644 --- a/typescript/bot/src/utils/return-types.ts +++ b/typescript/bot/src/utils/return-types.ts @@ -36,7 +36,7 @@ export type ImportPollStatus = }; }; -export interface UGPTStats { +export interface UserGameStatsReturn { gameStats: UserGameStats; firstScore: ScoreDocument; mostRecentScore: ScoreDocument; diff --git a/typescript/bot/src/webhook-handlers/class-update.ts b/typescript/bot/src/webhook-handlers/class-update.ts index 8f9f3ecc0..4484b6fff 100644 --- a/typescript/bot/src/webhook-handlers/class-update.ts +++ b/typescript/bot/src/webhook-handlers/class-update.ts @@ -10,7 +10,7 @@ import { import { Env } from "../config"; import { client } from "../main"; -import { GetUGPTStats, GetUserInfo } from "../utils/api-requests"; +import { GetUserGameStats, GetUserInfo } from "../utils/api-requests"; import { CreateEmbed } from "../utils/embeds"; import { PrependTachiUrl } from "../utils/fetch-tachi"; import { FormatClass, GetGameChannel, UppercaseFirst } from "../utils/misc"; @@ -45,7 +45,7 @@ export async function HandleClassUpdateV1( const minimumNecessaryScores = GetMinimumScores(game, event.set); if (minimumNecessaryScores !== null) { - const { totalScores } = await GetUGPTStats(userDoc.id, game); + const { totalScores } = await GetUserGameStats(userDoc.id, game); // Do not render if the user hasn't hit the score cap. if (totalScores < minimumNecessaryScores) { diff --git a/typescript/client/.env.example b/typescript/client/.env.example index 90eb78c5d..22b367612 100644 --- a/typescript/client/.env.example +++ b/typescript/client/.env.example @@ -9,7 +9,7 @@ VITE_FLO_CLIENT_ID="" VITE_RULES_READ_TIME=1 VITE_IS_LOCAL_DEV=true VITE_IS_DEVELOPMENT=true -VITE_SEEDS_URL="http://127.0.0.1:3100" +VITE_SEEDS_URL="http://127.0.0.1:3003" # Where routing should "base" from. # shouldn't be used, really. VITE_BASE_PATH="" diff --git a/typescript/client/src/app/App.tsx b/typescript/client/src/app/App.tsx index 75d37c6d9..59a3a55a7 100644 --- a/typescript/client/src/app/App.tsx +++ b/typescript/client/src/app/App.tsx @@ -1,7 +1,7 @@ import { CustomScrollbar } from "#components/layout/CustomScrollbar"; import { LocalDevMissingSeedsBanner } from "#components/layout/LocalDevMissingSeedsBanner"; import { LoadingScreen } from "#components/layout/screens/LoadingScreen"; -import { AllLUGPTStatsContextProvider } from "#context/AllLUGPTStatsContext"; +import { AllYourUGStatsContextProvider } from "#context/AllYourUGStatsContext"; import { BannedContextProvider } from "#context/BannedContext"; import { NotificationsContextProvider } from "#context/NotificationsContext"; import { SubheaderContextProvider } from "#context/SubheaderContext"; @@ -30,7 +30,7 @@ export default function App({ basename }: { basename: string }) { - + @@ -38,7 +38,7 @@ export default function App({ basename }: { basename: string }) { - + diff --git a/typescript/client/src/app/pages/ErrorPage.tsx b/typescript/client/src/app/pages/ErrorPage.tsx index 20802a555..52ea76a81 100644 --- a/typescript/client/src/app/pages/ErrorPage.tsx +++ b/typescript/client/src/app/pages/ErrorPage.tsx @@ -1,10 +1,9 @@ import useSetSubheader from "#components/layout/header/useSetSubheader"; import { ToCDNURL } from "#util/api"; import { HistorySafeGoBack } from "#util/misc"; -import React from "react"; import { useHistory } from "react-router-dom"; -export function ErrorPage({ +export default function ErrorPage({ statusCode, customMessage, }: { diff --git a/typescript/client/src/app/pages/OAuth2CallbackPage.tsx b/typescript/client/src/app/pages/OAuth2CallbackPage.tsx index beccbbaaa..7152d7625 100644 --- a/typescript/client/src/app/pages/OAuth2CallbackPage.tsx +++ b/typescript/client/src/app/pages/OAuth2CallbackPage.tsx @@ -1,10 +1,9 @@ import ApiError from "#components/util/ApiError"; import Loading from "#components/util/Loading"; import useApiQuery from "#components/util/query/useApiQuery"; -import React from "react"; import { Link } from "react-router-dom"; -import { ErrorPage } from "./ErrorPage"; +import ErrorPage from "./ErrorPage"; export default function OAuth2CallbackPage({ counterWeight, diff --git a/typescript/client/src/app/pages/OAuthRequestAuthPage.tsx b/typescript/client/src/app/pages/OAuthRequestAuthPage.tsx index 34ca7f52b..e5dbf3927 100644 --- a/typescript/client/src/app/pages/OAuthRequestAuthPage.tsx +++ b/typescript/client/src/app/pages/OAuthRequestAuthPage.tsx @@ -6,7 +6,6 @@ import OAuthMoreInfo from "#components/util/OAuthMoreInfo"; import useApiQuery from "#components/util/query/useApiQuery"; import useQueryString from "#components/util/useQueryString"; import { APIFetchV1 } from "#util/api"; -import React from "react"; import { Link } from "react-router-dom"; import { type integer, type TachiAPIClientDocument } from "tachi-common"; diff --git a/typescript/client/src/app/pages/PrivacyPolicyPage.tsx b/typescript/client/src/app/pages/PrivacyPolicyPage.tsx index 4235ffdab..2be1a17e9 100644 --- a/typescript/client/src/app/pages/PrivacyPolicyPage.tsx +++ b/typescript/client/src/app/pages/PrivacyPolicyPage.tsx @@ -1,7 +1,6 @@ import useSetSubheader from "#components/layout/header/useSetSubheader"; import ExternalLink from "#components/util/ExternalLink"; import { TachiConfig } from "#lib/config"; -import React from "react"; export default function PrivacyPolicyPage() { useSetSubheader(["Dashboard", "GDPR/Legal Stuff"]); diff --git a/typescript/client/src/app/pages/ResetPasswordPage.tsx b/typescript/client/src/app/pages/ResetPasswordPage.tsx index 4f289c800..bc2f70a17 100644 --- a/typescript/client/src/app/pages/ResetPasswordPage.tsx +++ b/typescript/client/src/app/pages/ResetPasswordPage.tsx @@ -2,10 +2,10 @@ import LoginPageLayout from "#components/layout/LoginPageLayout"; import MainPageTitleContainer from "#components/util/MainPageTitleContainer"; import { APIFetchV1 } from "#util/api"; import { ShortDelayify } from "#util/misc"; -import React, { useState } from "react"; +import { useState } from "react"; import { Button, Form } from "react-bootstrap"; -import { ErrorPage } from "./ErrorPage"; +import ErrorPage from "./ErrorPage"; export default function ResetPasswordPage() { const code = new URLSearchParams(window.location.search).get("code"); diff --git a/typescript/client/src/app/pages/VerifyEmailPage.tsx b/typescript/client/src/app/pages/VerifyEmailPage.tsx index 3d0f5b78a..4c9d98bca 100644 --- a/typescript/client/src/app/pages/VerifyEmailPage.tsx +++ b/typescript/client/src/app/pages/VerifyEmailPage.tsx @@ -6,9 +6,9 @@ import Loading from "#components/util/Loading"; import useApiQuery from "#components/util/query/useApiQuery"; import useQueryString from "#components/util/useQueryString"; import { UserContext } from "#context/UserContext"; -import React, { useContext } from "react"; +import { useContext } from "react"; -import { ErrorPage } from "./ErrorPage"; +import ErrorPage from "./ErrorPage"; import LoginPage from "./LoginPage"; export default function VerifyEmailPage() { diff --git a/typescript/client/src/app/pages/admin/AdminDestructivePage.tsx b/typescript/client/src/app/pages/admin/AdminDestructivePage.tsx index 24903f1da..044b8d4b2 100644 --- a/typescript/client/src/app/pages/admin/AdminDestructivePage.tsx +++ b/typescript/client/src/app/pages/admin/AdminDestructivePage.tsx @@ -11,10 +11,10 @@ export default function AdminDestructivePage() { const [deleteScoreId, setDeleteScoreId] = useState(""); const [deleteSessionId, setDeleteSessionId] = useState(""); - const [ugptUserId, setUgptUserId] = useState(""); - const [ugptGame, setUgptGame] = useState(TachiConfig.GAME_GROUPS[0]); - const ugptGameConfig = useMemo(() => GetGameGroupConfig(ugptGame), [ugptGame]); - const [ugptPlaytype, setUgptPlaytype] = useState( + const [userGameUserID, setUserGameUserID] = useState(""); + const [userGameGame, setUserGameGame] = useState(TachiConfig.GAME_GROUPS[0]); + const userGameConfig = useMemo(() => GetGameGroupConfig(userGameGame), [userGameGame]); + const [userGamePlaytype, setUserGamePlaytype] = useState( () => GetGameGroupConfig(TachiConfig.GAME_GROUPS[0]).playtypes[0], ); @@ -116,27 +116,27 @@ export default function AdminDestructivePage() { - Destroy user game profile (UGPT) + Destroy user game profile - + User ID setUgptUserId(e.target.value)} + onChange={(e) => setUserGameUserID(e.target.value)} type="number" - value={ugptUserId} + value={userGameUserID} /> - + Game { const g = e.target.value as GameGroup; - setUgptGame(g); + setUserGameGame(g); const cfg = GetGameGroupConfig(g); - setUgptPlaytype(cfg.playtypes[0]); + setUserGamePlaytype(cfg.playtypes[0]); }} - value={ugptGame} + value={userGameGame} > {TachiConfig.GAME_GROUPS.map((g) => ( - + Playtype setUgptPlaytype(e.target.value)} - value={ugptPlaytype} + onChange={(e) => setUserGamePlaytype(e.target.value)} + value={userGamePlaytype} > - {ugptGameConfig.playtypes.map((pt) => ( + {userGameConfig.playtypes.map((pt) => ( @@ -159,29 +159,29 @@ export default function AdminDestructivePage() { diff --git a/typescript/client/src/app/pages/admin/AdminJobQueuePage.tsx b/typescript/client/src/app/pages/admin/AdminJobQueuePage.tsx index bf2b2ab3f..2ee06280b 100644 --- a/typescript/client/src/app/pages/admin/AdminJobQueuePage.tsx +++ b/typescript/client/src/app/pages/admin/AdminJobQueuePage.tsx @@ -2,7 +2,6 @@ import useSetSubheader from "#components/layout/header/useSetSubheader"; import useApiQuery from "#components/util/query/useApiQuery"; import { ADMIN_PAGE_SIZE, ADMIN_RECENT_HOURS, JOB_STATUS } from "#lib/adminConstants"; import { MillisToSince } from "#util/time"; -import React from "react"; import { Button, Form, Table } from "react-bootstrap"; import { Link, useHistory, useLocation } from "react-router-dom"; diff --git a/typescript/client/src/app/pages/admin/AdminOperationsPage.tsx b/typescript/client/src/app/pages/admin/AdminOperationsPage.tsx index 9a8bea02b..76ab1e6d1 100644 --- a/typescript/client/src/app/pages/admin/AdminOperationsPage.tsx +++ b/typescript/client/src/app/pages/admin/AdminOperationsPage.tsx @@ -1,28 +1,19 @@ import useSetSubheader from "#components/layout/header/useSetSubheader"; -import { TachiConfig } from "#lib/config"; import { APIFetchV1 } from "#util/api"; -import React, { useState } from "react"; +import { useState } from "react"; import { Button, Card, Col, Form, Row } from "react-bootstrap"; -import { - FormatGame, - type GameGroup, - GetGameGroupConfig, - LEGACY_GameGroupPTToGame, -} from "tachi-common"; +import { ALL_GAMES, FormatGame, type V3Game } from "tachi-common"; export default function AdminOperationsPage() { useSetSubheader(["Admin", "Operations"]); const [announcementTitle, setAnnouncementTitle] = useState(""); - const [announcementGame, setAnnouncementGame] = useState<"" | GameGroup>(""); - const [announcementPlaytype, setAnnouncementPlaytype] = useState(""); + const [announcementGame, setAnnouncementPlaytype] = useState(null); const [folderId, setFolderId] = useState(""); const [supporterUser, setSupporterUser] = useState(""); - const announcementGameConfig = announcementGame ? GetGameGroupConfig(announcementGame) : null; - return ( @@ -38,44 +29,19 @@ export default function AdminOperationsPage() { /> - Game (optional) + Game { - const v = e.target.value; - setAnnouncementGame(v === "" ? "" : (v as GameGroup)); - setAnnouncementPlaytype(""); - }} - value={announcementGame === "" ? "" : announcementGame} + onChange={(e) => setAnnouncementPlaytype(e.target.value as V3Game)} + value={announcementGame ?? ""} > - - {TachiConfig.GAME_GROUPS.map((g) => ( - + {ALL_GAMES.map((game) => ( + ))} - {announcementGameConfig && ( - - Playtype (optional) - setAnnouncementPlaytype(e.target.value)} - value={announcementPlaytype} - > - - {announcementGameConfig.playtypes.map((pt) => ( - - ))} - - - )} @@ -520,7 +519,7 @@ function RenderCurrentStats({ ); } -function ManageAccount({ reqUser, game }: UGPT) { +function ManageAccount({ reqUser, game }: GameProfileProps) { const [password, setPassword] = useState(""); const [deleting, setDeleting] = useState(false); diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderComparePage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderComparePage.tsx index 44ba7b0b8..3c12be3bf 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderComparePage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderComparePage.tsx @@ -1,20 +1,20 @@ import Card from "#components/layout/page/Card"; import ComparePBsTable from "#components/tables/rivals/ComparePBsTable"; import ProfilePicture from "#components/user/ProfilePicture"; -import UGPTRatingsTable from "#components/user/UGPTStatsOverview"; +import UserGameRatingsTable from "#components/user/UserGameStatsOverview"; import ApiError from "#components/util/ApiError"; import Divider from "#components/util/Divider"; import Loading from "#components/util/Loading"; import UserSelectModal from "#components/util/modal/UserSelectModal"; import useApiQuery from "#components/util/query/useApiQuery"; -import useLUGPTSettings from "#components/util/useLUGPTSettings"; +import useLoggedInUserGameSettings from "#components/util/useLoggedInUserGameSettings"; import UserIcon from "#components/util/UserIcon"; import { UserContext } from "#context/UserContext"; -import { type UGPTFolderReturns, type UGPTStatsReturn } from "#types/api-returns"; -import { type GamePT } from "#types/react"; +import { type UserGameFolderReturns, type UserGameStatsReturn } from "#types/api-returns"; +import { type GameProps } from "#types/react"; import { type ComparePBsDataset } from "#types/tables"; import { CreateSongMap } from "#util/data"; -import React, { useContext, useMemo, useState } from "react"; +import { useContext, useMemo, useState } from "react"; import Button from "react-bootstrap/Button"; import Col from "react-bootstrap/Col"; import Form from "react-bootstrap/Form"; @@ -30,8 +30,8 @@ export default function RivalCompareFolderPage({ }: { folder: FolderDocument; reqUser: UserDocument; -} & GamePT) { - const { settings } = useLUGPTSettings(); +} & GameProps) { + const { settings } = useLoggedInUserGameSettings(); const { user } = useContext(UserContext); const [selectedUser, setSelectedUser] = useState(null); @@ -118,12 +118,12 @@ function FolderCompare({ folder: FolderDocument; reqUser: UserDocument; withUser: UserDocument; -} & GamePT) { - const { data: baseData, error: baseError } = useApiQuery( +} & GameProps) { + const { data: baseData, error: baseError } = useApiQuery( `/users/${reqUser.id}/games/${game}/folders/${folder.slug}`, ); - const { data: compareData, error: compareError } = useApiQuery( + const { data: compareData, error: compareError } = useApiQuery( `/users/${withUser.id}/games/${game}/folders/${folder.slug}`, ); @@ -186,8 +186,10 @@ function FolderCompare({ ); } -function UserCard({ user, game }: { user: UserDocument } & GamePT) { - const { data, error } = useApiQuery(`/users/${user.username}/games/${game}`); +function UserCard({ user, game }: { user: UserDocument } & GameProps) { + const { data, error } = useApiQuery( + `/users/${user.username}/games/${game}`, + ); if (error) { return ; @@ -206,7 +208,7 @@ function UserCard({ user, game }: { user: UserDocument } & GamePT) { - {data ? : } + {data ? : } diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumDistributionBreakdown.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumDistributionBreakdown.tsx index c4b2f03bc..4e40c86b4 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumDistributionBreakdown.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumDistributionBreakdown.tsx @@ -2,9 +2,9 @@ import type { FolderStatsInfo } from "#types/api-returns"; import { WindowContext } from "#context/WindowContext"; import { ChangeOpacity } from "#util/color-opacity"; -import { FormatGPTEnumMetric, ToFixedFloor, UppercaseFirst } from "#util/misc"; -import React, { useContext } from "react"; -import { type GameConfig, GetScoreMetricConf, V3Game } from "tachi-common"; +import { FormatGameEnumMetric, ToFixedFloor, UppercaseFirst } from "#util/misc"; +import { useContext } from "react"; +import { type GameConfig, GetScoreMetricConf, type V3Game } from "tachi-common"; import breakdownStyles from "./FolderEnumDistributionBreakdown.module.scss"; @@ -48,7 +48,7 @@ function distributionRowChrome(enumColour: string | undefined): { /** * Styled enum value × count breakdown (counts + % of folder charts). - * Rows use GPT enum colours; optional floor at `minimumRelevantValue`; remainder row for unmatched charts. + * Rows use game enum colours; optional floor at `minimumRelevantValue`; remainder row for unmatched charts. */ export default function FolderEnumDistributionBreakdown({ game, @@ -106,7 +106,7 @@ export default function FolderEnumDistributionBreakdown({ for (let vi = conf.values.length - 1; vi >= valueIndexFloor; vi--) { const label = conf.values[vi]; - const printedLabel = FormatGPTEnumMetric(game, enumMetric, label); + const printedLabel = FormatGameEnumMetric(game, enumMetric, label); const count = bucket[label] ?? 0; filled += count; diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumProgressBar.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumProgressBar.tsx index 759242854..b333be6f4 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumProgressBar.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumProgressBar.tsx @@ -3,7 +3,7 @@ import type { FolderStatsInfo } from "#types/api-returns"; import QuickTooltip from "#components/layout/misc/QuickTooltip"; import Muted from "#components/util/Muted"; import { ChangeOpacity } from "#util/color-opacity"; -import { FormatGPTEnumMetric, ToFixedFloor } from "#util/misc"; +import { FormatGameEnumMetric, ToFixedFloor } from "#util/misc"; import React, { useLayoutEffect, useRef, useState } from "react"; import { type GameConfig, GetScoreMetricConf, V3Game } from "tachi-common"; @@ -77,7 +77,7 @@ export default function FolderEnumProgressBar({ if (count > 0) { filled += count; segments.push({ - label: FormatGPTEnumMetric(game, enumMetric, v), + label: FormatGameEnumMetric(game, enumMetric, v), count, rawFill: colours?.[v] ?? "var(--bs-secondary-bg)", }); diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderQuestsPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderQuestsPage.tsx index 81f954d7f..0fb32ec0c 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderQuestsPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderQuestsPage.tsx @@ -6,7 +6,7 @@ import Loading from "#components/util/Loading"; import useApiQuery from "#components/util/query/useApiQuery"; import { TargetsContext } from "#context/TargetsContext"; import { type GoalsOnFolderReturn } from "#types/api-returns"; -import { type UGPT } from "#types/react"; +import { type GameProfileProps } from "#types/react"; import { CreateGoalSubDataset, CreateUserMap } from "#util/data"; import React, { useContext, useReducer, useState } from "react"; import { Button, Col } from "react-bootstrap"; @@ -18,7 +18,7 @@ export default function FolderQuestsPage({ reqUser, }: { folder: FolderDocument; -} & UGPT) { +} & GameProfileProps) { const [refresh, forceRefresh] = useReducer((x) => x + 1, 0); const { reloadTargets } = useContext(TargetsContext); @@ -63,7 +63,7 @@ function FolderQuestsInner({ }: { data: GoalsOnFolderReturn; folder: FolderDocument; -} & UGPT) { +} & GameProfileProps) { const userMap = CreateUserMap([reqUser]); return ; diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx index ed5c39096..16e534f6a 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx @@ -5,8 +5,8 @@ import Divider from "#components/util/Divider"; import Icon from "#components/util/Icon"; import Loading from "#components/util/Loading"; import useApiQuery from "#components/util/query/useApiQuery"; -import useLUGPTSettings from "#components/util/useLUGPTSettings"; -import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react"; +import useLoggedInUserGameSettings from "#components/util/useLoggedInUserGameSettings"; +import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react"; import Button from "react-bootstrap/Button"; import Collapse from "react-bootstrap/Collapse"; import Form from "react-bootstrap/Form"; @@ -22,7 +22,7 @@ const FOLDER_ROUTE_PATTERN = "/u/:userID/games/:game/folders/:folderSlug"; export default function FolderTablePage({ reqUser, game }: FolderTableScopedProps) { const { data, error } = useApiQuery(`/games/${game}/tables?showInactive=true`); - const { settings } = useLUGPTSettings(); + const { settings } = useLoggedInUserGameSettings(); const location = useLocation(); const history = useHistory(); diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FoldersMainPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FoldersMainPage.tsx index e058753e2..bd3ecc0ca 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FoldersMainPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FoldersMainPage.tsx @@ -1,14 +1,13 @@ import useSetSubheader from "#components/layout/header/useSetSubheader"; import Divider from "#components/util/Divider"; -import { type UGPT } from "#types/react"; -import React from "react"; +import { type GameProfileProps } from "#types/react"; import { Redirect, Route, Switch } from "react-router-dom"; import { FormatGame, GameToGameGroup, GetGameGroupConfig } from "tachi-common"; import FolderTablePage from "./FolderTablePage"; import SpecificFolderPage from "./SpecificFolderPage"; -export default function FoldersMainPage({ reqUser, game }: UGPT) { +export default function FoldersMainPage({ reqUser, game }: GameProfileProps) { useSetSubheader( [ "Users", diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx index c9411ae5f..df724a34d 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx @@ -1,4 +1,4 @@ -import { ErrorPage } from "#app/pages/ErrorPage"; +import ErrorPage from "#app/pages/ErrorPage"; import FolderInfoHeader from "#components/game/folder/FolderInfoHeader"; import QuickTooltip from "#components/layout/misc/QuickTooltip"; import DifficultyCell from "#components/tables/cells/DifficultyCell"; @@ -17,17 +17,17 @@ import useApiQuery from "#components/util/query/useApiQuery"; import ReferToUser from "#components/util/ReferToUser"; import SelectButton from "#components/util/SelectButton"; import SelectLinkButton from "#components/util/SelectLinkButton"; -import useUGPTBase from "#components/util/useUGPTBase"; +import useUserGameBase from "#components/util/useUserGameBase"; import { WindowContext } from "#context/WindowContext"; -import { GPT_CLIENT_IMPLEMENTATIONS } from "#lib/game-implementations"; -import { type GPTRatingSystem } from "#lib/types"; -import { type UGPTFolderReturns } from "#types/api-returns"; +import { GAME_CLIENT_IMPLEMENTATIONS } from "#lib/game-implementations"; +import { type GameRatingSystem } from "#lib/types"; +import { type UserGameFolderReturns } from "#types/api-returns"; import { type FolderDataset } from "#types/tables"; import { ChangeOpacity } from "#util/color-opacity"; import { CreateChartIDMap, CreateChartLink, CreateSongMap } from "#util/data"; import { DistinctArr, ToFixedFloor } from "#util/misc"; import { NumericSOV, StrSOV } from "#util/sorts"; -import React, { +import { type SetStateAction, useCallback, useContext, @@ -93,7 +93,7 @@ interface Props { export default function SpecificFolderPage({ reqUser, game }: Props) { const { folderSlug } = useParams<{ folderSlug: string }>(); - const { data, error } = useApiQuery( + const { data, error } = useApiQuery( `/users/${reqUser.id}/games/${game}/folders/${folderSlug}`, ); @@ -163,7 +163,7 @@ export default function SpecificFolderPage({ reqUser, game }: Props) { ); }, [data, folderSlug, folderDataset, game, onBreakdownEnumValueClick, reqUser]); - const base = `${useUGPTBase({ reqUser, game })}/folders/${folderSlug}`; + const base = `${useUserGameBase({ reqUser, game })}/folders/${folderSlug}`; if (error?.statusCode === 404) { return ( @@ -182,7 +182,7 @@ export default function SpecificFolderPage({ reqUser, game }: Props) { return ; } - const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[game]; + const gptImpl = GAME_CLIENT_IMPLEMENTATIONS[game]; return (
@@ -257,7 +257,7 @@ function FolderNormalView({ game, reqUser, }: { - data: UGPTFolderReturns; + data: UserGameFolderReturns; folderDataset: FolderDataset; folderTableEnumPreset: FolderEnumBreakdownTablePreset | null; } & Props) { @@ -292,12 +292,12 @@ function FolderNormalView({ // so type InfoProps = { - data: UGPTFolderReturns; + data: UserGameFolderReturns; folderDataset: FolderDataset; } & Props; function TierlistBreakdown({ game, folderDataset, reqUser }: InfoProps) { - const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[game]; + const gptImpl = GAME_CLIENT_IMPLEMENTATIONS[game]; const history = useHistory(); const location = useLocation(); @@ -305,7 +305,7 @@ function TierlistBreakdown({ game, folderDataset, reqUser }: InfoProps) { const canonicalFirstTier = gptImpl.ratingSystems[0]?.name ?? ""; const tierlist = useMemo((): string => { - const systems = gptImpl.ratingSystems as GPTRatingSystem[]; + const systems = gptImpl.ratingSystems as GameRatingSystem[]; const fromQs = new URLSearchParams(location.search).get("tierlist"); if (!fromQs) { return canonicalFirstTier; @@ -329,7 +329,7 @@ function TierlistBreakdown({ game, folderDataset, reqUser }: InfoProps) { ); useEffect(() => { - const systems = gptImpl.ratingSystems as GPTRatingSystem[]; + const systems = gptImpl.ratingSystems as GameRatingSystem[]; if (systems.length === 0) { return; } @@ -354,7 +354,7 @@ function TierlistBreakdown({ game, folderDataset, reqUser }: InfoProps) { [folderDataset, game, tierlist], ); - const tierlistImpl = (gptImpl.ratingSystems as GPTRatingSystem[]).find( + const tierlistImpl = (gptImpl.ratingSystems as GameRatingSystem[]).find( (rs) => rs.name === tierlist, ); @@ -449,7 +449,7 @@ function tierlistRowEnumBucketKey(row: TierlistInfo, game: V3Game, enumMetric: s try { const label = EnumIndexToValue( game, - // @ts-expect-error GPTRatingSystem.enumName matches score enums on this GPT + // @ts-expect-error GameRatingSystem.enumName matches score enums on this game enumMetric, idx, ); @@ -468,7 +468,7 @@ interface TierlistBucketBarSegment { function computeTierlistBucketBarModel( bucket: TierlistInfo[], game: V3Game, - tierlistImpl: GPTRatingSystem, + tierlistImpl: GameRatingSystem, useFancyColour: boolean, ): { achieved: number; segments: TierlistBucketBarSegment[]; total: number } { const total = bucket.length; @@ -530,7 +530,7 @@ function computeTierlistBucketBarModel( const enumMetric = tierlistImpl.enumName; const conf = GetScoreEnumConfs(GetGameConfig(game))[enumMetric]; - const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[game]; + const gptImpl = GAME_CLIENT_IMPLEMENTATIONS[game]; const counts = new Map(); for (const row of bucket) { @@ -749,7 +749,7 @@ function TierlistBucketsSummaryTable({ expandedBucketIndices: Set; game: V3Game; onTierActivate: (bucketIndex: number) => void; - tierlistImpl: GPTRatingSystem; + tierlistImpl: GameRatingSystem; useFancyColour: boolean; }) { if (buckets.length === 0) { @@ -873,7 +873,7 @@ function TierlistInfoLadder({ game: V3Game; playerStats: Record; reqUser: UserDocument; - tierlistImpl: GPTRatingSystem; + tierlistImpl: GameRatingSystem; useFancyColour: boolean; }) { const buckets: TierlistInfo[][] = useMemo(() => { @@ -1091,7 +1091,7 @@ function TierlistBucket({ forceGridView: boolean; game: V3Game; reqUser: UserDocument; - tierlistImpl: GPTRatingSystem; + tierlistImpl: GameRatingSystem; useFancyColour: boolean; }) { const { @@ -1150,7 +1150,7 @@ function TierlistInfoBucketValues({ game: V3Game; i: integer; reqUser: UserDocument; - tierlistImpl: GPTRatingSystem; + tierlistImpl: GameRatingSystem; tierlistInfo: TierlistInfo; useFancyColour: boolean; }) { @@ -1167,7 +1167,7 @@ function TierlistInfoBucketValues({ let colourCss: string | undefined; if (useFancyColour) { - const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[game]; + const gptImpl = GAME_CLIENT_IMPLEMENTATIONS[game]; // @ts-expect-error lol colourCss = gptImpl.enumColours[tierlistImpl.enumName][tierlistInfo.score]; @@ -1305,7 +1305,7 @@ enum AchievedStatuses { function FolderDatasetAchievedStatus(folderDataset: FolderDataset, game: V3Game, tierlist: string) { const tierlistInfo: Record = {}; - const fn = (GPT_CLIENT_IMPLEMENTATIONS[game].ratingSystems as GPTRatingSystem[]).find( + const fn = (GAME_CLIENT_IMPLEMENTATIONS[game].ratingSystems as GameRatingSystem[]).find( (e) => e.name === tierlist, )?.achievementFn; diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableEvolutionReplay.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableEvolutionReplay.tsx index 7f839aeb6..91de2642d 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableEvolutionReplay.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableEvolutionReplay.tsx @@ -1,4 +1,4 @@ -import type { FolderStatsInfo, UGPTEvolutionReplayReturns } from "#types/api-returns"; +import type { FolderStatsInfo, UserGameEvolutionReplayReturns } from "#types/api-returns"; import ApiError from "#components/util/ApiError"; import Icon from "#components/util/Icon"; @@ -7,12 +7,12 @@ import Muted from "#components/util/Muted"; import useApiQuery from "#components/util/query/useApiQuery"; import SelectButton from "#components/util/SelectButton"; import { useBucket } from "#components/util/useBucket"; -import { GPT_CLIENT_IMPLEMENTATIONS } from "#lib/game-implementations"; +import { GAME_CLIENT_IMPLEMENTATIONS } from "#lib/game-implementations"; import { ChangeOpacity } from "#util/color-opacity"; import { CreateChartIDMap, CreateSongMap } from "#util/data"; import { UppercaseFirst } from "#util/misc"; import { FormatDate, MillisToSince } from "#util/time"; -import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import Button from "react-bootstrap/Button"; import Collapse from "react-bootstrap/Collapse"; import Dropdown from "react-bootstrap/Dropdown"; @@ -141,7 +141,7 @@ export default function TableEvolutionReplay({ data: evo, error: evoError, isLoading: evolutionLoading, - } = useApiQuery(evolutionUrl, undefined, undefined, !open); + } = useApiQuery(evolutionUrl, undefined, undefined, !open); const events = useMemo(() => evo?.events ?? [], [evo?.events]); @@ -403,7 +403,7 @@ export default function TableEvolutionReplay({ const allEnumColours = useMemo( () => - GPT_CLIENT_IMPLEMENTATIONS[game].enumColours as + GAME_CLIENT_IMPLEMENTATIONS[game].enumColours as | Record> | undefined, [game], @@ -499,7 +499,7 @@ export default function TableEvolutionReplay({ {" "} {UppercaseFirst(metric)} diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderList.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderList.tsx index 82cc03292..f1fab9499 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderList.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderList.tsx @@ -1,8 +1,8 @@ import Icon from "#components/util/Icon"; import { UserContext } from "#context/UserContext"; -import { GPT_CLIENT_IMPLEMENTATIONS } from "#lib/game-implementations"; +import { GAME_CLIENT_IMPLEMENTATIONS } from "#lib/game-implementations"; import { APIFetchV1 } from "#util/api"; -import React, { useContext, useLayoutEffect, useMemo, useRef, useState } from "react"; +import { useContext, useLayoutEffect, useMemo, useRef, useState } from "react"; import { Link } from "react-router-dom"; import { GetGameConfig, type TableDocument } from "tachi-common"; @@ -11,7 +11,7 @@ import folderTableStyles from "./FolderTablePage.module.scss"; import { type FolderTableScopedProps, tableFolderSlugsDisplayOrder, - type UGPTFolderStats, + type UserGameFolderStats, } from "./folderTableShared"; export default function TableFolderList({ @@ -24,7 +24,7 @@ export default function TableFolderList({ reqUser, table, }: { - dataMap: Map; + dataMap: Map; enumMetric: string; highlightFolderSlug?: string; highlightRevealKey?: number; @@ -36,7 +36,7 @@ export default function TableFolderList({ const enumColours = useMemo( () => ( - GPT_CLIENT_IMPLEMENTATIONS[game].enumColours as + GAME_CLIENT_IMPLEMENTATIONS[game].enumColours as | Record> | undefined )?.[enumMetric], diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderViewer.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderViewer.tsx index 40ed1ad3f..1ef71b072 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderViewer.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderViewer.tsx @@ -1,14 +1,14 @@ -import type { UGPTTableReturns } from "#types/api-returns"; +import type { UserGameTableReturns } from "#types/api-returns"; import ApiError from "#components/util/ApiError"; import Divider from "#components/util/Divider"; import Loading from "#components/util/Loading"; import useApiQuery from "#components/util/query/useApiQuery"; import { useBucket } from "#components/util/useBucket"; -import React, { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { GetGameConfig, type TableDocument } from "tachi-common"; -import type { FolderTableScopedProps, UGPTFolderStats } from "./folderTableShared"; +import type { FolderTableScopedProps, UserGameFolderStats } from "./folderTableShared"; import TableEvolutionReplay from "./TableEvolutionReplay"; import TableFolderList from "./TableFolderList"; @@ -26,7 +26,7 @@ export default function TableFolderViewer({ onFolderRowNavigate?: () => void; table: TableDocument; } & FolderTableScopedProps) { - const { data, error } = useApiQuery( + const { data, error } = useApiQuery( `/users/${reqUser.id}/games/${game}/tables/${table.tableID}`, ); @@ -37,7 +37,7 @@ export default function TableFolderViewer({ setEnumMetric(bucket); }, [bucket, table.tableID]); - const [dataMap, setDataMap] = useState>(new Map()); + const [dataMap, setDataMap] = useState>(new Map()); const [hasLoadedFolderMap, setHasLoadedFolderMap] = useState(false); useEffect(() => { diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/folderTableShared.ts b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/folderTableShared.ts index 3e772155c..2316546ca 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/folderTableShared.ts +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/folderTableShared.ts @@ -1,6 +1,6 @@ import type { FolderStatsInfo, TableEvolutionEventAPI } from "#types/api-returns"; -import { GPT_CLIENT_IMPLEMENTATIONS } from "#lib/game-implementations"; +import { GAME_CLIENT_IMPLEMENTATIONS } from "#lib/game-implementations"; import { type FolderDocument, type GameConfig, @@ -16,7 +16,7 @@ export type FolderTableScopedProps = { reqUser: UserDocument; }; -export interface UGPTFolderStats { +export interface UserGameFolderStats { folder: FolderDocument; stats: FolderStatsInfo; } @@ -26,11 +26,10 @@ export interface UGPTFolderStats { * * `table.folders` is ascending (e.g. Level 1 … 12), and by default we reverse so the * highest folder / level renders first. Games can opt out via `reverseFolderOrder` on - * their GPT client impl, in which case we reverse again — i.e. render in the table's - * declared order (currently used for BMS/PMS). + * their game client impl. */ export function tableFolderSlugsDisplayOrder(table: TableDocument, game: V3Game): string[] { - const reverseAgain = GPT_CLIENT_IMPLEMENTATIONS[game].reverseFolderOrder ?? false; + const reverseAgain = GAME_CLIENT_IMPLEMENTATIONS[game].reverseFolderOrder ?? false; return reverseAgain ? [...table.folders] : [...table.folders].reverse(); } diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsActivityPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsActivityPage.tsx index f31910a39..2530dda92 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsActivityPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsActivityPage.tsx @@ -1,11 +1,10 @@ import Activity from "#components/activity/Activity"; -import useLUGPTSettings from "#components/util/useLUGPTSettings"; -import { type UGPT } from "#types/react"; -import React from "react"; +import useLoggedInUserGameSettings from "#components/util/useLoggedInUserGameSettings"; +import { type GameProfileProps } from "#types/react"; import { Link } from "react-router-dom"; -export default function RivalsActivityPage({ reqUser, game }: UGPT) { - const { settings } = useLUGPTSettings(); +export default function RivalsActivityPage({ reqUser, game }: GameProfileProps) { + const { settings } = useLoggedInUserGameSettings(); if (!settings) { return <>You have no settings. How did you get here?; diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsMainPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsMainPage.tsx index 9a8c3bb70..0c093519e 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsMainPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsMainPage.tsx @@ -3,9 +3,8 @@ import useSetSubheader from "#components/layout/header/useSetSubheader"; import Divider from "#components/util/Divider"; import Icon from "#components/util/Icon"; import SelectLinkButton from "#components/util/SelectLinkButton"; -import useLUGPTSettings from "#components/util/useLUGPTSettings"; -import useUGPTBase from "#components/util/useUGPTBase"; -import React from "react"; +import useLoggedInUserGameSettings from "#components/util/useLoggedInUserGameSettings"; +import useUserGameBase from "#components/util/useUserGameBase"; import { Col, Row } from "react-bootstrap"; import { Route, Switch } from "react-router-dom"; import { @@ -32,9 +31,9 @@ export default function RivalsMainPage({ reqUser, game }: { game: V3Game; reqUse `${reqUser.username}'s ${FormatGame(game)} Rivals`, ); - const base = useUGPTBase({ reqUser, game }); + const base = useUserGameBase({ reqUser, game }); - const { settings } = useLUGPTSettings(); + const { settings } = useLoggedInUserGameSettings(); if (!settings) { return
You have no settings set. How did you cause this?
; diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsManagePage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsManagePage.tsx index 452ad0b87..2f510a7c1 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsManagePage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsManagePage.tsx @@ -7,12 +7,12 @@ import Loading from "#components/util/Loading"; import UserSelectModal from "#components/util/modal/UserSelectModal"; import Muted from "#components/util/Muted"; import useApiQuery from "#components/util/query/useApiQuery"; -import useLUGPTSettings from "#components/util/useLUGPTSettings"; +import useLoggedInUserGameSettings from "#components/util/useLoggedInUserGameSettings"; import UserIcon from "#components/util/UserIcon"; import { UserContext } from "#context/UserContext"; import { APIFetchV1 } from "#util/api"; import { SendErrorToast } from "#util/toaster"; -import React, { useContext, useState } from "react"; +import { useContext, useState } from "react"; import { Alert, Button, Col } from "react-bootstrap"; import { Prompt } from "react-router-dom"; import { @@ -44,7 +44,7 @@ export default function RivalsManagePage({ `Managing ${reqUser.username}'s ${FormatGame(game)} Rivals`, ); - const { settings } = useLUGPTSettings(); + const { settings } = useLoggedInUserGameSettings(); const { data, error } = useApiQuery( `/users/${reqUser.id}/games/${game}/rivals`, @@ -101,7 +101,7 @@ function RivalsOverviewPage({ const [rivals, setRivals] = useState(initialRivals); const [show, setShow] = useState(false); - const { settings, setSettings } = useLUGPTSettings(); + const { settings, setSettings } = useLoggedInUserGameSettings(); const [currentRivals, setCurrentRivals] = useState(initialRivals); diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/TargetsPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/TargetsPage.tsx index ffa4e8de4..d764b38aa 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/TargetsPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/TargetsPage.tsx @@ -2,17 +2,16 @@ import useSetSubheader from "#components/layout/header/useSetSubheader"; import Divider from "#components/util/Divider"; import Icon from "#components/util/Icon"; import SelectLinkButton from "#components/util/SelectLinkButton"; -import useUGPTBase from "#components/util/useUGPTBase"; -import { type UGPT } from "#types/react"; -import React from "react"; +import useUserGameBase from "#components/util/useUserGameBase"; +import { type GameProfileProps } from "#types/react"; import { Col, Row } from "react-bootstrap"; import { Route, Switch } from "react-router-dom"; import { FormatGame, GameToGameGroup, GetGameGroupConfig } from "tachi-common"; -import UGPTGoalsPage from "./UGPTGoalsPage"; -import UGPTQuestsPage from "./UGPTQuestsPage"; +import UserGameGoalsPage from "./UserGameGoalsPage"; +import UserGameQuestsPage from "./UserGameQuestsPage"; -export default function TargetsPage({ reqUser, game }: UGPT) { +export default function TargetsPage({ reqUser, game }: GameProfileProps) { useSetSubheader( [ "Users", @@ -25,7 +24,7 @@ export default function TargetsPage({ reqUser, game }: UGPT) { `${reqUser.username}'s ${FormatGame(game)} Goals & Quests`, ); - const base = useUGPTBase({ reqUser, game }); + const base = useUserGameBase({ reqUser, game }); return ( @@ -43,10 +42,10 @@ export default function TargetsPage({ reqUser, game }: UGPT) { - + - + diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UGPTGoalsPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UserGameGoalsPage.tsx similarity index 92% rename from typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UGPTGoalsPage.tsx rename to typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UserGameGoalsPage.tsx index 12decb9ca..a35308a97 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UGPTGoalsPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UserGameGoalsPage.tsx @@ -7,23 +7,23 @@ import Icon from "#components/util/Icon"; import Loading from "#components/util/Loading"; import useApiQuery from "#components/util/query/useApiQuery"; import { TargetsContext } from "#context/TargetsContext"; -import { type AllUGPTGoalsReturn } from "#types/api-returns"; -import { type UGPT } from "#types/react"; +import { type AllUserGameGoalsReturn } from "#types/api-returns"; +import { type GameProfileProps } from "#types/react"; import { APIFetchV1 } from "#util/api"; import { CreateGoalSubDataset } from "#util/data"; -import React, { useContext, useReducer, useState } from "react"; +import { useContext, useReducer, useState } from "react"; import { Button, Col, Modal } from "react-bootstrap"; import { Link } from "react-router-dom"; import { FormatGame, type GoalDocument } from "tachi-common"; -export default function UGPTGoalsPage({ reqUser, game }: UGPT) { +export default function UserGameGoalsPage({ reqUser, game }: GameProfileProps) { const [showAdd, setShowAdd] = useState(false); const [showDelete, setShowDelete] = useState(false); const [editingGoal, setEditingGoal] = useState(null); const { reloadTargets } = useContext(TargetsContext); const [refresh, refetchGoals] = useReducer((x) => x + 1, 0); - const { data, error } = useApiQuery( + const { data, error } = useApiQuery( `/users/${reqUser.id}/games/${game}/targets/goals`, undefined, [refresh.toString()], diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UGPTQuestsPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UserGameQuestsPage.tsx similarity index 93% rename from typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UGPTQuestsPage.tsx rename to typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UserGameQuestsPage.tsx index a6339cb87..73f6fba66 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UGPTQuestsPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/targets/UserGameQuestsPage.tsx @@ -4,11 +4,11 @@ import Divider from "#components/util/Divider"; import Loading from "#components/util/Loading"; import useApiQuery from "#components/util/query/useApiQuery"; import Select from "#components/util/Select"; -import { type UGPT } from "#types/react"; +import { type GameProfileProps } from "#types/react"; import { CreateGoalMap, GetGoalIDsFromQuest } from "#util/data"; import { CreateQuestSubMap } from "#util/misc"; import { NumericSOV } from "#util/sorts"; -import React, { useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { Col, Row } from "react-bootstrap"; import { Link } from "react-router-dom"; import { @@ -17,7 +17,7 @@ import { type QuestSubscriptionDocument, } from "tachi-common"; -export default function UGPTQuestsPage({ reqUser, game }: UGPT) { +export default function UserGameQuestsPage({ reqUser, game }: GameProfileProps) { const [show, setShow] = useState<"achieved" | "all" | "unachieved">("all"); const { data, error } = useApiQuery<{ diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/utils/UGPTUtilsPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/utils/UserGameUtilsPage.tsx similarity index 84% rename from typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/utils/UGPTUtilsPage.tsx rename to typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/utils/UserGameUtilsPage.tsx index a9d9b63b0..4e5dce3a3 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/utils/UGPTUtilsPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/utils/UserGameUtilsPage.tsx @@ -1,22 +1,22 @@ -import { GetGPTUtils, GetGPTUtilsName } from "#components/gpt-utils/GPTUtils"; +import { GetGameUtils, GetGameUtilsName } from "#components/game-utils/GameUtils"; import useSetSubheader from "#components/layout/header/useSetSubheader"; import Card from "#components/layout/page/Card"; import Divider from "#components/util/Divider"; import LinkButton from "#components/util/LinkButton"; import { UserContext } from "#context/UserContext"; -import { type UGPT } from "#types/react"; -import React, { useContext } from "react"; +import { type GameProfileProps } from "#types/react"; +import { useContext } from "react"; import { Col, Row } from "react-bootstrap"; import { Link, Route, Switch } from "react-router-dom"; import { FormatGame, GameToGameGroup, GetGameGroupConfig } from "tachi-common"; -export default function UGPTUtilsPage({ reqUser, game }: UGPT) { +export default function UserGameUtilsPage({ reqUser, game }: GameProfileProps) { const { user } = useContext(UserContext); const isViewingOwnProfile = user?.id === reqUser.id; - const utils = GetGPTUtils(game); - const pageName = GetGPTUtilsName(game, isViewingOwnProfile); + const utils = GetGameUtils(game); + const pageName = GetGameUtilsName(game, isViewingOwnProfile); useSetSubheader( [ diff --git a/typescript/client/src/app/pages/dashboard/utils/QuestEditor.tsx b/typescript/client/src/app/pages/dashboard/utils/QuestEditor.tsx index 0ee2c1bc3..161eaccd6 100644 --- a/typescript/client/src/app/pages/dashboard/utils/QuestEditor.tsx +++ b/typescript/client/src/app/pages/dashboard/utils/QuestEditor.tsx @@ -21,17 +21,13 @@ import { type RawQuestDocument, type RawQuestlineDocument } from "#types/tachi"; import { APIFetchV1 } from "#util/api"; import { ChangeAtPosition, DeleteInPosition } from "#util/misc"; import { p, type PrudenceSchema } from "prudence"; -import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Alert, Badge, Button, Col, Form, Modal, Row, Spinner } from "react-bootstrap"; import { ALL_GAMES, FormatGame, FormatPrError, - type GameGroup, GetGameGroupConfig, - LEGACY_GameGroupPTToGame, - type LEGACY_GPTString, - type LEGACY_Playtype, type V3Game, } from "tachi-common"; @@ -205,12 +201,9 @@ export default function QuestEditor() { // ── Derived ──────────────────────────────────────────────────────────────── const selectedQuest = selectedQuestIdx !== null ? (quests[selectedQuestIdx] ?? null) : null; - const addQuest = (gptString: LEGACY_GPTString) => { - const [game, playtype] = gptString.split(":") as [GameGroup, LEGACY_Playtype]; - const v3Game: V3Game = LEGACY_GameGroupPTToGame(game, playtype); - + const addQuest = (game: V3Game) => { const newQuest: RawQuestDocument = { - game: v3Game, + game, name: "Untitled Quest", desc: "Please set a description.", rawQuestData: [], @@ -651,22 +644,23 @@ function QuestList({ onSelect, onAddQuest, }: { - onAddQuest: (gpt: LEGACY_GPTString) => void; + onAddQuest: (game: V3Game) => void; onSelect: (idx: number) => void; quests: Array; selectedIdx: number | null; }) { - const [gpt, setGpt] = useState(null); + const [game, setGame] = useState(null); - const allGpts: Array<{ label: string; value: LEGACY_GPTString }> = - TachiConfig.GAME_GROUPS.flatMap((gameGroup) => { + const allGames: Array<{ label: string; value: V3Game }> = TachiConfig.GAME_GROUPS.flatMap( + (gameGroup) => { const config = GetGameGroupConfig(gameGroup); - return config.playtypes.map((pt) => ({ - value: `${gameGroup}:${pt}` as LEGACY_GPTString, - label: FormatGame(LEGACY_GameGroupPTToGame(gameGroup, pt)), + return config.games.map((game) => ({ + value: game, + label: FormatGame(game), })); - }); + }, + ); return (
@@ -691,23 +685,23 @@ function QuestList({ {/* Inline new-quest form */}
setGpt(e.target.value as LEGACY_GPTString)} + onChange={(e) => setGame(e.target.value as V3Game)} size="sm" style={{ flex: 1 }} - value={gpt ?? ""} + value={game ?? ""} > - {allGpts.map((g) => ( + {allGames.map((g) => ( ))}
- - setCustomStat(stat)} reqUser={reqUser} @@ -353,7 +353,7 @@ export function FormatValue( export function GetStatName( stat: ShowcaseStatDetails, game: V3Game, - related: UGPTPreferenceStatsReturn["related"], + related: UserGamePreferenceStatsReturn["related"], ) { if (stat.mode === "folder") { return (related as { folder: FolderDocument }).folder.title; @@ -372,20 +372,20 @@ export function StatDisplay({ compareData, game, }: { - compareData?: UGPTPreferenceStatsReturn; + compareData?: UserGamePreferenceStatsReturn; reqUser: UserDocument; - statData: UGPTPreferenceStatsReturn; -} & GamePT) { + statData: UserGamePreferenceStatsReturn; +} & GameProps) { const { user } = useContext(UserContext); if (statData.stat.mode === "chart") { - const { stat, result, related } = statData as UGPTPreferenceChartStatsReturn; + const { stat, result, related } = statData as UserGamePreferenceChartStatsReturn; const { song, chart } = related; const { playcount, pb } = result; const compareChart = user && user.id !== reqUser.id && compareData?.stat.mode === "chart" - ? (compareData as UGPTPreferenceChartStatsReturn) + ? (compareData as UserGamePreferenceChartStatsReturn) : undefined; return ( @@ -463,7 +463,7 @@ export function StatDisplay({ } if (statData.stat.mode === "folder") { - const { stat, result, related } = statData as UGPTPreferenceFolderStatsReturn; + const { stat, result, related } = statData as UserGamePreferenceFolderStatsReturn; const { folder } = related; const headerStr = folder.title; @@ -506,7 +506,8 @@ export function StatDisplay({ v1={result.value} v2={ compareData?.stat.mode === "folder" - ? (compareData as UGPTPreferenceFolderStatsReturn).result.value + ? (compareData as UserGamePreferenceFolderStatsReturn).result + .value : undefined } /> diff --git a/typescript/client/src/components/user/UGPTStatsOverview.tsx b/typescript/client/src/components/user/UserGameStatsOverview.tsx similarity index 86% rename from typescript/client/src/components/user/UGPTStatsOverview.tsx rename to typescript/client/src/components/user/UserGameStatsOverview.tsx index 004c52178..619b0c1e1 100644 --- a/typescript/client/src/components/user/UGPTStatsOverview.tsx +++ b/typescript/client/src/components/user/UserGameStatsOverview.tsx @@ -3,18 +3,17 @@ import QuickTooltip from "#components/layout/misc/QuickTooltip"; import MiniTable from "#components/tables/components/MiniTable"; import Divider from "#components/util/Divider"; import { - FormatGPTProfileRating, - FormatGPTProfileRatingName, - FormatGPTScoreRatingName, + FormatGameProfileRating, + FormatGameProfileRatingName, + FormatGameScoreRatingName, getProfileRatingAlgRowStyle, sortProfileRatingEntries, UppercaseFirst, } from "#util/misc"; import { StrSOV } from "#util/sorts"; -import React from "react"; import { type Classes, GetGameConfig, type UserGameStats, type V3Game } from "tachi-common"; -export default function UGPTRatingsTable({ ugs }: { ugs: UserGameStats }) { +export default function UserGameRatingsTable({ ugs }: { ugs: UserGameStats }) { const game = ugs.game; const gameConfig = GetGameConfig(game); @@ -59,7 +58,7 @@ export default function UGPTRatingsTable({ ugs }: { ugs: UserGameStats }) { {gameConfig.profileRatingAlgs[k].associatedScoreAlgs?.map( (alg) => (
- ({FormatGPTScoreRatingName(game, alg)}:{" "} + ({FormatGameScoreRatingName(game, alg)}:{" "} {gameConfig.scoreRatingAlgs[alg].description})
), @@ -74,12 +73,12 @@ export default function UGPTRatingsTable({ ugs }: { ugs: UserGameStats }) { textDecorationStyle: "dotted", }} > - {FormatGPTProfileRatingName(game, k)} + {FormatGameProfileRatingName(game, k)}
- {FormatGPTProfileRating(game, k as any, v)} + {FormatGameProfileRating(game, k as any, v)} ))} diff --git a/typescript/client/src/components/util/AsyncLoader.tsx b/typescript/client/src/components/util/AsyncLoader.tsx index c09eca29c..25b06efce 100644 --- a/typescript/client/src/components/util/AsyncLoader.tsx +++ b/typescript/client/src/components/util/AsyncLoader.tsx @@ -1,4 +1,3 @@ -import React from "react"; import Async from "react-async"; import Loading from "./Loading"; diff --git a/typescript/client/src/components/util/CenterPage.tsx b/typescript/client/src/components/util/CenterPage.tsx index be2f97ba5..e9f667927 100644 --- a/typescript/client/src/components/util/CenterPage.tsx +++ b/typescript/client/src/components/util/CenterPage.tsx @@ -1,5 +1,4 @@ import { type JustChildren } from "#types/react"; -import React from "react"; export default function CenterPage({ children, diff --git a/typescript/client/src/components/util/CheckEdit.tsx b/typescript/client/src/components/util/CheckEdit.tsx index 9490f65a0..a7fb126c5 100644 --- a/typescript/client/src/components/util/CheckEdit.tsx +++ b/typescript/client/src/components/util/CheckEdit.tsx @@ -1,5 +1,4 @@ import { type JustChildren } from "#types/react"; -import React from "react"; import { Form } from "react-bootstrap"; export default function CheckEdit({ diff --git a/typescript/client/src/components/util/DebugContent.tsx b/typescript/client/src/components/util/DebugContent.tsx index 7f48f3d00..20db680c7 100644 --- a/typescript/client/src/components/util/DebugContent.tsx +++ b/typescript/client/src/components/util/DebugContent.tsx @@ -1,5 +1,3 @@ -import React from "react"; - export default function DebugContent({ data }: { data: unknown }) { return (