From 48c3a9d23bd75243a677dd0bf79d009e7b085128 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 20 Dec 2022 06:04:13 +0000 Subject: [PATCH] feat: squish tools and insights together --- .../_game/_playtype/insights/InsightsPage.tsx | 80 ------------------- .../ToolsPage.tsx => utils/UGPTUtilsPage.tsx} | 75 ++++++++--------- client/src/app/routes/UserRoutes.tsx | 34 ++++---- client/src/components/gpt-utils/GPTUtils.tsx | 62 ++++++++++++++ .../insights/JubilityBreakdownInsight.tsx | 4 +- .../tools/BMSCustomTablesTool.tsx | 5 +- .../tools/IIDXEamusementExportTool.tsx | 6 +- .../tools/IIDXPlaylistsTool.tsx | 10 +-- .../tools-insights/ToolsAndInsights.tsx | 67 ---------------- client/src/components/user/UGPTHeader.tsx | 40 ++++------ client/src/types/ugpt.ts | 3 +- 11 files changed, 146 insertions(+), 240 deletions(-) delete mode 100644 client/src/app/pages/dashboard/users/games/_game/_playtype/insights/InsightsPage.tsx rename client/src/app/pages/dashboard/users/games/_game/_playtype/{tools/ToolsPage.tsx => utils/UGPTUtilsPage.tsx} (56%) create mode 100644 client/src/components/gpt-utils/GPTUtils.tsx rename client/src/components/{tools-insights => gpt-utils}/insights/JubilityBreakdownInsight.tsx (96%) rename client/src/components/{tools-insights => gpt-utils}/tools/BMSCustomTablesTool.tsx (94%) rename client/src/components/{tools-insights => gpt-utils}/tools/IIDXEamusementExportTool.tsx (91%) rename client/src/components/{tools-insights => gpt-utils}/tools/IIDXPlaylistsTool.tsx (87%) delete mode 100644 client/src/components/tools-insights/ToolsAndInsights.tsx diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/insights/InsightsPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/insights/InsightsPage.tsx deleted file mode 100644 index 2d542ef16..000000000 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/insights/InsightsPage.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import useSetSubheader from "components/layout/header/useSetSubheader"; -import Card from "components/layout/page/Card"; -import { GetGPTInsights } from "components/tools-insights/ToolsAndInsights"; -import Divider from "components/util/Divider"; -import LinkButton from "components/util/LinkButton"; -import React from "react"; -import { Col, Row } from "react-bootstrap"; -import { Link, Route, Switch } from "react-router-dom"; -import { FormatGame, GetGameConfig } from "tachi-common"; -import { UGPT } from "types/react"; - -export default function InsightsPage({ reqUser, game, playtype }: UGPT) { - const gameConfig = GetGameConfig(game); - useSetSubheader( - ["Users", reqUser.username, "Games", gameConfig.name, playtype, "Insights"], - [reqUser, game, playtype], - `${reqUser.username}'s ${FormatGame(game, playtype)} Insights` - ); - - const insights = GetGPTInsights(game, playtype); - - if (insights.length === 0) { - return ( -
- {FormatGame(game, playtype)} has no insights. How'd you get here! -
- ); - } - - return ( - - - {insights.map((tool) => ( - - - - < Back to all insights... - - } - > - {tool.description} - - - - {tool.component({ reqUser, game, playtype })} - - ))} - - - {insights.map((tool) => ( - - - - View - - - } - > - {tool.description} - - - ))} - - - - ); -} diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/tools/ToolsPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/utils/UGPTUtilsPage.tsx similarity index 56% rename from client/src/app/pages/dashboard/users/games/_game/_playtype/tools/ToolsPage.tsx rename to client/src/app/pages/dashboard/users/games/_game/_playtype/utils/UGPTUtilsPage.tsx index db29333a6..98f8d33c6 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/tools/ToolsPage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/utils/UGPTUtilsPage.tsx @@ -1,47 +1,67 @@ import useSetSubheader from "components/layout/header/useSetSubheader"; import Card from "components/layout/page/Card"; -import { GetGPTTools } from "components/tools-insights/ToolsAndInsights"; +import { GetGPTUtils, GetGPTUtilsName } from "components/gpt-utils/GPTUtils"; import Divider from "components/util/Divider"; import LinkButton from "components/util/LinkButton"; -import React from "react"; +import { UserContext } from "context/UserContext"; +import React, { useContext } from "react"; import { Col, Row } from "react-bootstrap"; import { Link, Route, Switch } from "react-router-dom"; import { FormatGame, GetGameConfig } from "tachi-common"; import { UGPT } from "types/react"; +import { GPTUtility } from "types/ugpt"; -export default function ToolsPage({ reqUser, game, playtype }: UGPT) { +export default function UGPTUtilsPage({ reqUser, game, playtype }: UGPT) { const gameConfig = GetGameConfig(game); + const { user } = useContext(UserContext); + + const isViewingOwnProfile = user?.id === reqUser.id; + + const utils = GetGPTUtils(game, playtype); + const pageName = GetGPTUtilsName(game, playtype, isViewingOwnProfile); + useSetSubheader( - ["Users", reqUser.username, "Games", gameConfig.name, playtype, "Tools"], + ["Users", reqUser.username, "Games", gameConfig.name, playtype, pageName ?? "Utils"], [reqUser, game, playtype], - `${reqUser.username}'s ${FormatGame(game, playtype)} Tools` + `${reqUser.username}'s ${FormatGame(game, playtype)} ${pageName ?? "Utils"}` ); - const tools = GetGPTTools(game, playtype); - - if (tools.length === 0) { - return ( -
- {FormatGame(game, playtype)} has no tools. How'd you get here! -
- ); - } - return ( - {tools.map((tool) => ( + + {utils.map((util) => ( + + + + View + + + } + > + {util.description} + + + ))} + + + {utils.map((tool) => ( < Back to all tools... @@ -55,25 +75,6 @@ export default function ToolsPage({ reqUser, game, playtype }: UGPT) { {tool.component({ reqUser, game, playtype })} ))} - - - {tools.map((tool) => ( - - - - Use Tool - - - } - > - {tool.description} - - - ))} - ); diff --git a/client/src/app/routes/UserRoutes.tsx b/client/src/app/routes/UserRoutes.tsx index c3005c20d..b6141db0f 100644 --- a/client/src/app/routes/UserRoutes.tsx +++ b/client/src/app/routes/UserRoutes.tsx @@ -1,19 +1,20 @@ import { APIFetchV1, APIFetchV1Return, ToAPIURL } from "util/api"; import { IsSupportedGame, IsSupportedPlaytype } from "util/asserts"; +import { ErrorPage } from "app/pages/ErrorPage"; import PlaytypeSelect from "app/pages/dashboard/games/_game/PlaytypeSelect"; -import FoldersMainPage from "app/pages/dashboard/users/games/_game/_playtype/folders/FoldersMainPage"; -import LeaderboardsPage from "app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage"; -import OverviewPage from "app/pages/dashboard/users/games/_game/_playtype/OverviewPage"; -import RivalsMainPage from "app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsMainPage"; -import SessionsPage from "app/pages/dashboard/users/games/_game/_playtype/SessionsPage"; -import SpecificSessionPage from "app/pages/dashboard/users/games/_game/_playtype/SpecificSessionPage"; -import TargetsPage from "app/pages/dashboard/users/games/_game/_playtype/targets/TargetsPage"; -import UGPTSettingsPage from "app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage"; import UserGamesPage from "app/pages/dashboard/users/UserGamesPage"; +import UserImportsPage from "app/pages/dashboard/users/UserImportsPage"; import UserIntegrationsPage from "app/pages/dashboard/users/UserIntegrationsPage"; import UserInvitesPage from "app/pages/dashboard/users/UserInvitesPage"; import UserSettingsPage from "app/pages/dashboard/users/UserSettingsPage"; -import { ErrorPage } from "app/pages/ErrorPage"; +import LeaderboardsPage from "app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage"; +import OverviewPage from "app/pages/dashboard/users/games/_game/_playtype/OverviewPage"; +import SessionsPage from "app/pages/dashboard/users/games/_game/_playtype/SessionsPage"; +import SpecificSessionPage from "app/pages/dashboard/users/games/_game/_playtype/SpecificSessionPage"; +import UGPTSettingsPage from "app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage"; +import FoldersMainPage from "app/pages/dashboard/users/games/_game/_playtype/folders/FoldersMainPage"; +import RivalsMainPage from "app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsMainPage"; +import TargetsPage from "app/pages/dashboard/users/games/_game/_playtype/targets/TargetsPage"; import RequireAuthAsUserParam from "components/auth/RequireAuthAsUserParam"; import LayoutHeaderContainer from "components/layout/LayoutHeaderContainer"; import { UGPTBottomNav, UGPTHeaderBody } from "components/user/UGPTHeader"; @@ -21,6 +22,7 @@ import { UserBottomNav, UserHeaderBody } from "components/user/UserHeader"; import Loading from "components/util/Loading"; import useApiQuery from "components/util/query/useApiQuery"; import { BackgroundContext } from "context/BackgroundContext"; +import { TargetsContextProvider } from "context/TargetsContext"; import { UGPTContextProvider } from "context/UGPTContext"; import { UserContext } from "context/UserContext"; import { UserSettingsContext } from "context/UserSettingsContext"; @@ -29,12 +31,9 @@ import { useQuery } from "react-query"; import { Redirect, Route, Switch, useHistory, useParams } from "react-router-dom"; import { FormatGame, Game, GetGameConfig, UserDocument, UserGameStats } from "tachi-common"; import { UGPTStatsReturn } from "types/api-returns"; -import UserImportsPage from "app/pages/dashboard/users/UserImportsPage"; -import { TargetsContextProvider } from "context/TargetsContext"; -import ToolsPage from "app/pages/dashboard/users/games/_game/_playtype/tools/ToolsPage"; -import InsightsPage from "app/pages/dashboard/users/games/_game/_playtype/insights/InsightsPage"; -import ScoresPage from "../pages/dashboard/users/games/_game/_playtype/ScoresPage"; +import UGPTUtilsPage from "app/pages/dashboard/users/games/_game/_playtype/utils/UGPTUtilsPage"; import UserPage from "../pages/dashboard/users/UserPage"; +import ScoresPage from "../pages/dashboard/users/games/_game/_playtype/ScoresPage"; export default function UserRoutes() { const params = useParams<{ userID: string }>(); @@ -265,11 +264,8 @@ function UserGamePlaytypeRoutes({ reqUser, game }: { reqUser: UserDocument; game - - - - - + + diff --git a/client/src/components/gpt-utils/GPTUtils.tsx b/client/src/components/gpt-utils/GPTUtils.tsx new file mode 100644 index 000000000..43d782a57 --- /dev/null +++ b/client/src/components/gpt-utils/GPTUtils.tsx @@ -0,0 +1,62 @@ +import { Game, IDStrings, Playtype } from "tachi-common"; +import { GPTUtility } from "types/ugpt"; +import { BMSCustomTablesTool } from "./tools/BMSCustomTablesTool"; +import { IIDXEamusementExportTool } from "./tools/IIDXEamusementExportTool"; +import { IIDXPlaylistsTool } from "./tools/IIDXPlaylistsTool"; +import { JubilityBreakdownInsight } from "./insights/JubilityBreakdownInsight"; + +// What utils does each game support? +const GPT_UTILS: Record> = { + "bms:7K": [BMSCustomTablesTool], + "bms:14K": [BMSCustomTablesTool], + "chunithm:Single": [], + "gitadora:Dora": [], + "gitadora:Gita": [], + "iidx:DP": [IIDXEamusementExportTool, IIDXPlaylistsTool], + "iidx:SP": [IIDXEamusementExportTool, IIDXPlaylistsTool], + "itg:Stamina": [], + "jubeat:Single": [JubilityBreakdownInsight], + "museca:Single": [], + "pms:Controller": [], + "pms:Keyboard": [], + "popn:9B": [], + "sdvx:Single": [], + "usc:Controller": [], + "usc:Keyboard": [], + "wacca:Single": [], +}; + +export function GetGPTUtils(game: Game, playtype: Playtype) { + const idString = `${game}:${playtype}` as IDStrings; + + return GPT_UTILS[idString]; +} + +export function GetGPTUtilsName(game: Game, playtype: Playtype, isViewingOwnProfile: boolean) { + const utils = GetGPTUtils(game, playtype); + + // things for personal use only are called "tools" + // things everyone might want to view about a person are called "insights". + // an example of an insight would be a jubility breakdown + // an example of a tool would be a eamusement csv export thing. + const tools = utils.filter((e) => e.personalUseOnly); + const insights = utils.filter((e) => e.personalUseOnly !== true); + + if (isViewingOwnProfile) { + if (tools.length > 0 && insights.length > 0) { + return "Tools & Insights"; + } else if (insights.length > 0) { + return "Insights"; + } else if (tools.length > 0) { + return "Tools"; + } + + return null; + } + + if (insights.length > 0) { + return "Insights"; + } + + return null; +} diff --git a/client/src/components/tools-insights/insights/JubilityBreakdownInsight.tsx b/client/src/components/gpt-utils/insights/JubilityBreakdownInsight.tsx similarity index 96% rename from client/src/components/tools-insights/insights/JubilityBreakdownInsight.tsx rename to client/src/components/gpt-utils/insights/JubilityBreakdownInsight.tsx index 7642c27aa..d3e31c826 100644 --- a/client/src/components/tools-insights/insights/JubilityBreakdownInsight.tsx +++ b/client/src/components/gpt-utils/insights/JubilityBreakdownInsight.tsx @@ -12,7 +12,7 @@ import { Col, Row } from "react-bootstrap"; import { ChartDocument, CreateSongMap, PBScoreDocument, SongDocument } from "tachi-common"; import { UGPT } from "types/react"; import { PBDataset } from "types/tables"; -import { GPTToolOrInsight } from "types/ugpt"; +import { GPTUtility } from "types/ugpt"; function Component({ game, playtype, reqUser }: UGPT) { const { data, error } = useApiQuery<{ @@ -115,7 +115,7 @@ function Component({ game, playtype, reqUser }: UGPT) { ); } -export const JubilityBreakdownInsight: GPTToolOrInsight = { +export const JubilityBreakdownInsight: GPTUtility = { name: "Jubility Breakdown", urlPath: "jubility", description: `See what PBs are going into profile jubility!`, diff --git a/client/src/components/tools-insights/tools/BMSCustomTablesTool.tsx b/client/src/components/gpt-utils/tools/BMSCustomTablesTool.tsx similarity index 94% rename from client/src/components/tools-insights/tools/BMSCustomTablesTool.tsx rename to client/src/components/gpt-utils/tools/BMSCustomTablesTool.tsx index f9ae732ce..43795f1a8 100644 --- a/client/src/components/tools-insights/tools/BMSCustomTablesTool.tsx +++ b/client/src/components/gpt-utils/tools/BMSCustomTablesTool.tsx @@ -8,7 +8,7 @@ import { TachiConfig } from "lib/config"; import React from "react"; import { Col, Row } from "react-bootstrap"; import { UGPT } from "types/react"; -import { GPTToolOrInsight } from "types/ugpt"; +import { GPTUtility } from "types/ugpt"; import Icon from "components/util/Icon"; import Divider from "components/util/Divider"; @@ -66,9 +66,10 @@ function Component({ game, playtype, reqUser }: UGPT) { ); } -export const BMSCustomTablesTool: GPTToolOrInsight = { +export const BMSCustomTablesTool: GPTUtility = { name: `${TachiConfig.name} BMS Tables`, urlPath: "custom-tables", description: `${TachiConfig.name} has its own BMS tables that you can use in-game!`, component: Component, + personalUseOnly: true, }; diff --git a/client/src/components/tools-insights/tools/IIDXEamusementExportTool.tsx b/client/src/components/gpt-utils/tools/IIDXEamusementExportTool.tsx similarity index 91% rename from client/src/components/tools-insights/tools/IIDXEamusementExportTool.tsx rename to client/src/components/gpt-utils/tools/IIDXEamusementExportTool.tsx index 38361564e..e8997615c 100644 --- a/client/src/components/tools-insights/tools/IIDXEamusementExportTool.tsx +++ b/client/src/components/gpt-utils/tools/IIDXEamusementExportTool.tsx @@ -1,6 +1,5 @@ import { CopyToClipboard } from "util/misc"; import ApiError from "components/util/ApiError"; -import DebugContent from "components/util/DebugContent"; import Divider from "components/util/Divider"; import Loading from "components/util/Loading"; import useApiQuery from "components/util/query/useApiQuery"; @@ -8,7 +7,7 @@ import { TachiConfig } from "lib/config"; import React from "react"; import { Alert, Button, Col, Row } from "react-bootstrap"; import { UGPT } from "types/react"; -import { GPTToolOrInsight } from "types/ugpt"; +import { GPTUtility } from "types/ugpt"; function Component({ game, playtype, reqUser }: UGPT) { const { data, error } = useApiQuery( @@ -60,9 +59,10 @@ function Component({ game, playtype, reqUser }: UGPT) { ); } -export const IIDXEamusementExportTool: GPTToolOrInsight = { +export const IIDXEamusementExportTool: GPTUtility = { name: `e-amusement CSV Export`, urlPath: "eam-csv-export", description: `Export your ${TachiConfig.name} scores into e-amusement format.`, component: Component, + personalUseOnly: true, }; diff --git a/client/src/components/tools-insights/tools/IIDXPlaylistsTool.tsx b/client/src/components/gpt-utils/tools/IIDXPlaylistsTool.tsx similarity index 87% rename from client/src/components/tools-insights/tools/IIDXPlaylistsTool.tsx rename to client/src/components/gpt-utils/tools/IIDXPlaylistsTool.tsx index bf71786e1..d0f3b9c59 100644 --- a/client/src/components/tools-insights/tools/IIDXPlaylistsTool.tsx +++ b/client/src/components/gpt-utils/tools/IIDXPlaylistsTool.tsx @@ -1,5 +1,4 @@ import { ToAPIURL } from "util/api"; -import { CopyToClipboard } from "util/misc"; import Card from "components/layout/page/Card"; import ApiError from "components/util/ApiError"; import Loading from "components/util/Loading"; @@ -8,9 +7,7 @@ import { TachiConfig } from "lib/config"; import React from "react"; import { Col, Row } from "react-bootstrap"; import { UGPT } from "types/react"; -import { GPTToolOrInsight } from "types/ugpt"; -import Icon from "components/util/Icon"; -import Divider from "components/util/Divider"; +import { GPTUtility } from "types/ugpt"; function Component({ game, playtype, reqUser }: UGPT) { const { data, error } = useApiQuery< @@ -76,9 +73,10 @@ function Component({ game, playtype, reqUser }: UGPT) { ); } -export const IIDXPlaylistsTool: GPTToolOrInsight = { +export const IIDXPlaylistsTool: GPTUtility = { name: `${TachiConfig.name} IIDX Playlists`, - urlPath: "custom-tables", + urlPath: "playlists", description: `${TachiConfig.name} has its own IIDX playlists that you can use in-game via playlister!`, component: Component, + personalUseOnly: true, }; diff --git a/client/src/components/tools-insights/ToolsAndInsights.tsx b/client/src/components/tools-insights/ToolsAndInsights.tsx deleted file mode 100644 index 9457e5a1d..000000000 --- a/client/src/components/tools-insights/ToolsAndInsights.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Game, GetIDString, IDStrings, Playtype } from "tachi-common"; -import { GPTToolOrInsight } from "types/ugpt"; -import { BMSCustomTablesTool } from "./tools/BMSCustomTablesTool"; -import { IIDXEamusementExportTool } from "./tools/IIDXEamusementExportTool"; -import { IIDXPlaylistsTool } from "./tools/IIDXPlaylistsTool"; -import { JubilityBreakdownInsight } from "./insights/JubilityBreakdownInsight"; - -// A tool is something that users can use, like exporting to a specific format -// or having things integrate with the game in a neat way. -// Tools are **NOT** displayed on someone elses profile. For that, you want -// Insights (Fancy score renderers, basically). - -// What tools does each game support? -const GPT_TOOLS: Record> = { - "bms:7K": [BMSCustomTablesTool], - "bms:14K": [BMSCustomTablesTool], - "chunithm:Single": [], - "gitadora:Dora": [], - "gitadora:Gita": [], - "iidx:DP": [IIDXEamusementExportTool, IIDXPlaylistsTool], - "iidx:SP": [IIDXEamusementExportTool, IIDXPlaylistsTool], - "itg:Stamina": [], - "jubeat:Single": [], - "museca:Single": [], - "pms:Controller": [], - "pms:Keyboard": [], - "popn:9B": [], - "sdvx:Single": [], - "usc:Controller": [], - "usc:Keyboard": [], - "wacca:Single": [], -}; - -// An insight is a page for a unique kind of data viz that tachi (generically) does not -// pick up. Good examples of this would be something like viewing what goes into your -// jubility. -const GPT_INSIGHTS: Record> = { - "bms:7K": [], - "bms:14K": [], - "chunithm:Single": [], - "gitadora:Dora": [], - "gitadora:Gita": [], - "iidx:DP": [], - "iidx:SP": [], - "itg:Stamina": [], - "jubeat:Single": [JubilityBreakdownInsight], - "museca:Single": [], - "pms:Controller": [], - "pms:Keyboard": [], - "popn:9B": [], - "sdvx:Single": [], - "usc:Controller": [], - "usc:Keyboard": [], - "wacca:Single": [], -}; - -export function GetGPTTools(game: Game, playtype: Playtype) { - const idString = GetIDString(game, playtype); - - return GPT_TOOLS[idString]; -} - -export function GetGPTInsights(game: Game, playtype: Playtype) { - const idString = GetIDString(game, playtype); - - return GPT_INSIGHTS[idString]; -} diff --git a/client/src/components/user/UGPTHeader.tsx b/client/src/components/user/UGPTHeader.tsx index 658d6111a..4c3aa4697 100644 --- a/client/src/components/user/UGPTHeader.tsx +++ b/client/src/components/user/UGPTHeader.tsx @@ -13,7 +13,7 @@ import { UGPTStatsReturn } from "types/api-returns"; import useLUGPTSettings from "components/util/useLUGPTSettings"; import { UserSettingsContext } from "context/UserSettingsContext"; import FollowUserButton from "components/util/FollowUserButton"; -import { GetGPTInsights, GetGPTTools } from "components/tools-insights/ToolsAndInsights"; +import { GetGPTUtilsName } from "components/gpt-utils/GPTUtils"; import ProfileBadges from "./ProfileBadges"; import ProfilePicture from "./ProfilePicture"; import RankingData from "./UGPTRankingData"; @@ -202,10 +202,23 @@ export function UGPTBottomNav({ , ]; - if (GetGPTInsights(game, playtype).length > 0) { + const utilsName = GetGPTUtilsName(game, playtype, isRequestedUser); + + if (utilsName) { navItems.push( - - Insights + + {utilsName} + + ); + } + + if (isRequestedUser) { + navItems.push( + + Rivals + , + + Goals & Quests ); } @@ -217,25 +230,6 @@ export function UGPTBottomNav({ ); if (isRequestedUser) { - navItems.push( - - Rivals - , - - Goals & Quests - - ); - - const tools = GetGPTTools(game, playtype); - - if (tools.length > 0) { - navItems.push( - - Tools - - ); - } - navItems.push( Settings diff --git a/client/src/types/ugpt.ts b/client/src/types/ugpt.ts index 85bbfc247..4b676ce84 100644 --- a/client/src/types/ugpt.ts +++ b/client/src/types/ugpt.ts @@ -1,9 +1,10 @@ import React from "react"; import { UGPT } from "./react"; -export interface GPTToolOrInsight { +export interface GPTUtility { urlPath: string; name: string; description: React.ReactChild; component: (ugpt: UGPT) => JSX.Element; + personalUseOnly?: boolean; }