diff --git a/client/package.json b/client/package.json
index 12c5682b6..f191f66ff 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "tachi-client",
- "version": "2.0.0-prerelease",
+ "version": "2.0.1",
"private": true,
"scripts": {
"start": "node scripts/start.js -o",
diff --git a/client/src/app/pages/ClientFileFlowPage.tsx b/client/src/app/pages/ClientFileFlowPage.tsx
index 3dc273461..80a29335b 100644
--- a/client/src/app/pages/ClientFileFlowPage.tsx
+++ b/client/src/app/pages/ClientFileFlowPage.tsx
@@ -1,6 +1,7 @@
import ApiError from "components/util/ApiError";
import Divider from "components/util/Divider";
import Loading from "components/util/Loading";
+import OAuthMoreInfo from "components/util/OAuthMoreInfo";
import useApiQuery from "components/util/query/useApiQuery";
import { UserContext } from "context/UserContext";
import { TachiConfig } from "lib/config";
@@ -89,6 +90,10 @@ export default function ClientFileFlowPage() {
Create the key.
+
>
) : (
<>
diff --git a/client/src/app/pages/OAuthRequestAuthPage.tsx b/client/src/app/pages/OAuthRequestAuthPage.tsx
new file mode 100644
index 000000000..f1ef08ac7
--- /dev/null
+++ b/client/src/app/pages/OAuthRequestAuthPage.tsx
@@ -0,0 +1,130 @@
+import CenterLayoutPage from "components/layout/CenterLayoutPage";
+import ApiError from "components/util/ApiError";
+import Divider from "components/util/Divider";
+import Loading from "components/util/Loading";
+import OAuthMoreInfo from "components/util/OAuthMoreInfo";
+import useApiQuery from "components/util/query/useApiQuery";
+import useQueryString from "components/util/useQueryString";
+import React from "react";
+import { Link } from "react-router-dom";
+import { integer, TachiAPIClientDocument } from "tachi-common";
+import { APIFetchV1 } from "util/api";
+
+export default function OAuthRequestAuthPage() {
+ const params = useQueryString();
+
+ const clientID = params.get("clientID");
+
+ if (!clientID) {
+ return (
+
+ No ClientID. Whoever gave you this link likely messed up somehow.
+ Go Home.
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
+
+function OAuthRequestAuthLoader({ clientID }: { clientID: string }) {
+ const { isLoading, error, data } = useApiQuery>(
+ `/clients/${clientID}`
+ );
+
+ if (error) {
+ return ;
+ }
+
+ if (isLoading || !data) {
+ return ;
+ }
+
+ if (!data.redirectUri) {
+ return (
+ <>
+ This client does not support OAuth Flows.
+
+ Whoever gave you this link likely messed up somehow.
+ Go Home.
+ >
+ );
+ }
+
+ return ;
+}
+
+function OAuthRequestAuthMain({
+ client,
+}: {
+ client: Omit;
+}) {
+ const params = useQueryString();
+
+ const context = params.get("context");
+
+ return (
+
+ <>
+
+ {client.name} is requesting you authenticate with them.
+
+
+
+ The key will have the following permissions.
+
+ {client.requestedPermissions.map(e => (
+ {e}
+ ))}
+
+
+
+
+ No thanks.
+
+ {
+ const tokenRes = await APIFetchV1<{
+ code: string;
+ userID: integer;
+ createdOn: number;
+ }>(
+ "/oauth/create-code",
+ {
+ method: "POST",
+ },
+ false,
+ true
+ );
+
+ if (!tokenRes.success) {
+ return;
+ }
+
+ const redirectURL = new URL(client.redirectUri!);
+
+ redirectURL.searchParams.set("code", tokenRes.body.code);
+
+ if (context) {
+ redirectURL.searchParams.set("context", context);
+ }
+
+ window.location.href = redirectURL.toString();
+ }}
+ >
+ I want to authenticate with {client.name}.
+
+
+
+ >
+
+ );
+}
diff --git a/client/src/app/pages/dashboard/import/BarbatosPage.tsx b/client/src/app/pages/dashboard/import/BarbatosPage.tsx
index 86f30872d..ca60555a5 100644
--- a/client/src/app/pages/dashboard/import/BarbatosPage.tsx
+++ b/client/src/app/pages/dashboard/import/BarbatosPage.tsx
@@ -21,10 +21,7 @@ export default function BarbatosPage() {
Download barbatos.dll from{" "}
-
- here (No download yet. Barbatos is not supported.)
-
- .
+ here .
Download your config file{" "}
@@ -32,8 +29,8 @@ export default function BarbatosPage() {
This file contains an API Key, which is meant to be kept secret!
- Place all of these files inside your SDVX folder, next to your .bat{" "}
- file.
+ Place barbatos.dll and the above config file inside your SDVX
+ folder, next to your .bat file.
diff --git a/client/src/app/pages/dashboard/import/BeatorajaIRPage.tsx b/client/src/app/pages/dashboard/import/BeatorajaIRPage.tsx
index f59004722..968cc6009 100644
--- a/client/src/app/pages/dashboard/import/BeatorajaIRPage.tsx
+++ b/client/src/app/pages/dashboard/import/BeatorajaIRPage.tsx
@@ -36,7 +36,14 @@ export default function BeatorajaIRPage() {
Place the IR .jar file in the ir/ folder.
Open the game options, and navigate to the IR section.
- Select {TachiConfig.name} IR.
+
+ Select {TachiConfig.name} IR.
+
+
+ If the IR isn't showing up, make sure you're launching the game with the{" "}
+ beatoraja-config.bat file. Otherwise, IRs will never load.
+
+
Get an API token for the IR by clicking{" "}
this link .
diff --git a/client/src/app/pages/dashboard/import/ImportPage.tsx b/client/src/app/pages/dashboard/import/ImportPage.tsx
index 023067e45..065e75a49 100644
--- a/client/src/app/pages/dashboard/import/ImportPage.tsx
+++ b/client/src/app/pages/dashboard/import/ImportPage.tsx
@@ -133,13 +133,13 @@ function ImportInfoDisplayer({ game }: { game: Game }) {
);
} else if (game === "sdvx") {
Content.unshift(
- // ,
+ ,
//
+
+ OAuth Flow Link:
+
+ {window.location.origin}/oauth/request-auth?clientID={client.clientID}
+
+
diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage.tsx
index e681a52e9..137bf1109 100644
--- a/client/src/app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage.tsx
+++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage.tsx
@@ -23,6 +23,7 @@ import { GPTLeaderboard, UGPTLeaderboardAdjacent } from "types/api-returns";
import { GamePT, SetState } from "types/react";
import { APIFetchV1, UnsuccessfulAPIFetchResponse } from "util/api";
import { ChangeOpacity } from "util/color-opacity";
+import { UppercaseFirst } from "util/misc";
interface LeaderboardsData {
stats: UGPTLeaderboardAdjacent;
@@ -161,7 +162,10 @@ function LeaderboardsPageContent({
}
>
-
+
<>
{bestNearbyUser >= 1 &&
leaderboard.gameStats
diff --git a/client/src/app/routes/AppRoutes.tsx b/client/src/app/routes/AppRoutes.tsx
index 2c420b633..cd95bf5f7 100644
--- a/client/src/app/routes/AppRoutes.tsx
+++ b/client/src/app/routes/AppRoutes.tsx
@@ -1,6 +1,7 @@
import { ErrorPage } from "app/pages/ErrorPage";
import ForgotPasswordPage from "app/pages/ForgotPasswordPage";
import LoginPage from "app/pages/LoginPage";
+import OAuthRequestAuthPage from "app/pages/OAuthRequestAuthPage";
import RegisterPage from "app/pages/RegisterPage";
import ResetPasswordPage from "app/pages/ResetPasswordPage";
import VerifyEmailPage from "app/pages/VerifyEmailPage";
@@ -51,6 +52,10 @@ export function Routes() {
+
+
+
+
diff --git a/client/src/components/imports/ARCImportPage.tsx b/client/src/components/imports/ARCImportPage.tsx
index 7ee75dd88..1d5981803 100644
--- a/client/src/components/imports/ARCImportPage.tsx
+++ b/client/src/components/imports/ARCImportPage.tsx
@@ -91,7 +91,7 @@ export default function ARCImportPage({ game }: { game: "sdvx" | "iidx" }) {
) : (
- Barbatos (Although it's not supported yet, sorry.)
+ Barbatos
)}{" "}
instead.
diff --git a/client/src/components/layout/footer/Footer.tsx b/client/src/components/layout/footer/Footer.tsx
index 5fe663f80..4dc2b059c 100644
--- a/client/src/components/layout/footer/Footer.tsx
+++ b/client/src/components/layout/footer/Footer.tsx
@@ -1,10 +1,24 @@
import Divider from "components/util/Divider";
import ExternalLink from "components/util/ExternalLink";
-import React from "react";
+import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
+import { ServerStatus } from "types/api-returns";
+import { APIFetchV1 } from "util/api";
import { FORMATTED_VERSION } from "util/constants/version";
export function Footer() {
+ const [serverVersion, setServerVersion] = useState("Loading...");
+
+ useEffect(() => {
+ APIFetchV1("/status").then(r => {
+ if (!r.success) {
+ setServerVersion("Error Fetching data!");
+ } else {
+ setServerVersion(r.body.version);
+ }
+ });
+ }, []);
+
return (
<>
@@ -14,12 +28,20 @@ export function Footer() {
{/* is there a better way to do this? mt-md-3 is the intent */}
+ Client:{" "}
{FORMATTED_VERSION}
+ , Server:{" "}
+
+ {serverVersion}
+
diff --git a/client/src/components/layout/header/HeaderMenu.tsx b/client/src/components/layout/header/HeaderMenu.tsx
index 7313587d6..b7ed9082b 100644
--- a/client/src/components/layout/header/HeaderMenu.tsx
+++ b/client/src/components/layout/header/HeaderMenu.tsx
@@ -14,6 +14,7 @@ import AllGames from "./AllGames";
import ImportScoresLink from "./ImportScoresLink";
import MenuDropdown from "./MenuDropdown";
import MenuLink from "./MenuLink";
+import SearchBar from "./SearchBar";
export function HeaderMenu() {
const { user } = useContext(UserContext);
@@ -95,6 +96,13 @@ export function HeaderMenu() {
+
+
+
+
+
+
+
{user && ugs && ugs.length !== 0 && (
{userProfileLinks}
)}
diff --git a/client/src/components/util/OAuthMoreInfo.tsx b/client/src/components/util/OAuthMoreInfo.tsx
new file mode 100644
index 000000000..e93e0f37b
--- /dev/null
+++ b/client/src/components/util/OAuthMoreInfo.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import { PublicUserDocument, TachiAPIClientDocument } from "tachi-common";
+import ApiError from "./ApiError";
+import Loading from "./Loading";
+import Muted from "./Muted";
+import useApiQuery from "./query/useApiQuery";
+
+export default function OAuthMoreInfo({
+ client,
+}: {
+ client: Omit;
+}) {
+ const { data, isLoading, error } = useApiQuery(`/users/${client.author}`);
+
+ if (error) {
+ return ;
+ }
+
+ if (isLoading || !data) {
+ return ;
+ }
+
+ return API Client Author: {data.username}. ;
+}
diff --git a/client/src/types/api-returns.ts b/client/src/types/api-returns.ts
index c3ecf0db7..071f38d6e 100644
--- a/client/src/types/api-returns.ts
+++ b/client/src/types/api-returns.ts
@@ -1,4 +1,5 @@
import {
+ APIPermissions,
ChartDocument,
ClassAchievementDocument,
FolderDocument,
@@ -162,3 +163,11 @@ export interface UserRecentSummary {
recentSessions: SessionDocument[];
recentFolders: FolderDocument[];
}
+
+export interface ServerStatus {
+ serverTime: number;
+ startTime: number;
+ version: string;
+ whoami: integer | null;
+ permissions: APIPermissions[];
+}
diff --git a/client/src/util/constants/version.ts b/client/src/util/constants/version.ts
index 847109716..477d5a065 100644
--- a/client/src/util/constants/version.ts
+++ b/client/src/util/constants/version.ts
@@ -1,8 +1,8 @@
const MAJOR = 2;
const MINOR = 0;
-const PATCH = 0;
+const PATCH = 1;
-// As is with all front-facing zkldi projects, the version names for tachi-server
+// As is with all front-facing zkldi projects, the version names for tachi-client
// are from an album I like. In this case, the album is The Cure - Disintegration.
const THE_CURE_DISINTEGRATION = [
diff --git a/client/src/util/splashes.ts b/client/src/util/splashes.ts
index 811dd1598..2bd06133c 100644
--- a/client/src/util/splashes.ts
+++ b/client/src/util/splashes.ts
@@ -97,6 +97,7 @@ export const loggedInSplashes = [
"I'm thinking this one's brown.",
"My god is the sun!",
"...Like Clockwork",
+ "brooooyyddg",
];
// Stuff that's bad for the kids, you know?