+
+ {/* Mobile menu button */}
+
+
+
+
+ Grasscutter Tools
-
-
- Grasscutter Tools
-
-
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/RouteMenu.tsx b/src/components/RouteMenu.tsx
index 6df7131..19078d8 100644
--- a/src/components/RouteMenu.tsx
+++ b/src/components/RouteMenu.tsx
@@ -1,6 +1,7 @@
import {Routes, Route, RouteProps} from "react-router-dom";
import React from "react";
import Artifacts from "./pages/Artifacts";
+import GCAuth from "./pages/GCAuth";
interface RouteMenuProps extends RouteProps{
handleHeaderTitleChange: (title: string) => void;
@@ -17,6 +18,9 @@ export default function RouteMenu(props: RouteMenuProps) {
}/>
+
+ }/>
);
}
\ No newline at end of file
diff --git a/src/components/pages/GCAuth.tsx b/src/components/pages/GCAuth.tsx
new file mode 100644
index 0000000..da176f0
--- /dev/null
+++ b/src/components/pages/GCAuth.tsx
@@ -0,0 +1,322 @@
+import {FormEvent, useEffect, useState} from "react";
+import Swal from "sweetalert2";
+
+interface IGCAuthResponse {
+ status: string;
+ message: string;
+ jwt: string;
+}
+
+interface IJWTPayload {
+ token: string;
+ username: string;
+ uid: string;
+}
+
+interface IGCAuthLogin {
+ username: string;
+ password: string;
+}
+
+interface IGCAuthRegister {
+ username: string;
+ password: string;
+ password_confirmation: string;
+}
+
+interface IGCAuthChangePassword {
+ username: string;
+ new_password: string;
+ new_password_confirmation: string;
+ old_password: string;
+}
+
+export default function GCAuth() {
+ const [jwt, setJwt] = useState("");
+ const [dispatchServer, setDispatchServer] = useState(localStorage.getItem("dispatchServer") ?? "");
+ const [useSSl, setUseSSl] = useState(localStorage.getItem("useSSl")==="true" ?? true);
+ const [baseUrl, setBaseUrl] = useState("");
+
+ const [loginUsername, setLoginUsername] = useState("");
+ const [loginPassword, setLoginPassword] = useState("");
+ const [token, setToken] = useState("");
+
+ const [registerUsername, setRegisterUsername] = useState("");
+ const [registerPassword, setRegisterPassword] = useState("");
+ const [registerPasswordConfirmation, setRegisterPasswordConfirmation] = useState("");
+
+ const [changePasswordUsername, setChangePasswordUsername] = useState("");
+ const [changePasswordNewPassword, setChangePasswordNewPassword] = useState("");
+ const [changePasswordNewPasswordConfirmation, setChangePasswordNewPasswordConfirmation] = useState("");
+ const [changePasswordOldPassword, setChangePasswordOldPassword] = useState("");
+
+ const checkGCAuth = async () => {
+ fetch(baseUrl + "/authentication/type")
+ .then(res => res.text())
+ .then(res => {
+ if (res === "me.exzork.gcauth.handler.GCAuthAuthenticationHandler") {
+ Swal.fire({
+ toast: true,
+ position: "top-end",
+ showConfirmButton: false,
+ timer: 3000,
+ timerProgressBar: true,
+ title: "GCAuth is installed",
+ icon: "success"
+ });
+ } else {
+ Swal.fire({
+ toast: true,
+ position: "top-end",
+ showConfirmButton: false,
+ timer: 3000,
+ timerProgressBar: true,
+ title: "GCAuth is not installed",
+ icon: "error"
+ });
+ }
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ }
+
+ useEffect(() => {
+ setBaseUrl(`http${useSSl ? "s" : ""}://${dispatchServer}`);
+ localStorage.setItem("dispatchServer", dispatchServer);
+ localStorage.setItem("useSSl", useSSl.toString());
+ }, [useSSl, dispatchServer]);
+
+ const handleLogin = async (e: FormEvent) => {
+ e.preventDefault();
+ const data: IGCAuthLogin = {
+ username: loginUsername,
+ password: loginPassword
+ }
+ fetch(baseUrl + "/authentication/login", {method: "POST", body: JSON.stringify(data)})
+ .then(async (res) => {
+ const resText = await res.text();
+ try {
+ let resJson = JSON.parse(resText);
+ if (resJson.success) {
+ await Swal.fire({
+ toast: true,
+ position: "top-end",
+ showConfirmButton: false,
+ timer: 3000,
+ timerProgressBar: true,
+ title: "Login success",
+ icon: "success"
+ });
+ setJwt(resJson.jwt);
+ const splitToken = resJson.jwt.split(".");
+ const payload = JSON.parse(atob(splitToken[1]));
+ setToken(payload.token);
+ } else {
+ await Swal.fire({
+ title: "Login failed",
+ text: resJson.message ?? resJson,
+ icon: "error"
+ });
+ }
+ } catch (e) {
+ await Swal.fire({
+ title: "Login failed",
+ text: resText,
+ icon: "error"
+ });
+ }
+ }).catch((err) => {
+ console.log(err);
+ Swal.fire({
+ title: "Login failed",
+ text: err,
+ icon: "error"
+ });
+ });
+ }
+
+ const handleRegister = async (e: FormEvent) => {
+ e.preventDefault();
+ const data: IGCAuthRegister = {
+ username: registerUsername,
+ password: registerPassword,
+ password_confirmation: registerPasswordConfirmation
+ }
+ fetch(baseUrl + "/authentication/register", {method: "POST", body: JSON.stringify(data)})
+ .then(async (res) => {
+ const resText = await res.text();
+ try {
+ let resJson = JSON.parse(resText);
+ if (resJson.success) {
+ await Swal.fire({
+ toast: true,
+ position: "top-end",
+ showConfirmButton: false,
+ timer: 3000,
+ timerProgressBar: true,
+ title: "Register success",
+ icon: "success"
+ });
+ setJwt(resJson.jwt);
+ } else {
+ await Swal.fire({
+ title: "Register failed",
+ text: resJson.message ?? resJson,
+ icon: "error"
+ });
+ }
+ } catch (e) {
+ await Swal.fire({
+ title: "Register failed",
+ text: resText,
+ icon: "error"
+ });
+ }
+ }).catch((err) => {
+ console.log(err);
+ Swal.fire({
+ title: "Register failed",
+ text: err,
+ icon: "error"
+ });
+ });
+ }
+
+ const handleChangePassword = async (e: FormEvent) => {
+ e.preventDefault();
+ const data: IGCAuthChangePassword = {
+ username: changePasswordUsername,
+ new_password: changePasswordNewPassword,
+ new_password_confirmation: changePasswordNewPasswordConfirmation,
+ old_password: changePasswordOldPassword
+ }
+
+ fetch(baseUrl + "/authentication/change_password", {method: "POST", body: JSON.stringify(data)})
+ .then(async (res) => {
+ const resText = await res.text();
+ try {
+ let resJson = JSON.parse(resText);
+ if (resJson.success) {
+ await Swal.fire({
+ toast: true,
+ position: "top-end",
+ showConfirmButton: false,
+ timer: 3000,
+ timerProgressBar: true,
+ title: "Change password success",
+ icon: "success"
+ });
+ } else {
+ await Swal.fire({
+ title: "Change password failed",
+ text: resJson.message ?? resJson,
+ icon: "error"
+ });
+ }
+ } catch (e) {
+ await Swal.fire({
+ title: "Change password failed",
+ text: resText,
+ icon: "error"
+ });
+ }
+ }).catch((err) => {
+ console.log(err);
+ Swal.fire({
+ title: "Change password failed",
+ text: err,
+ icon: "error"
+ });
+ });
+ }
+
+ const handleCopyToken = (e:FormEvent) => {
+ e.preventDefault();
+ if (token!==""){
+ navigator.clipboard.writeText(token);
+ Swal.fire({toast: true, position: "top-end", showConfirmButton: false, timer: 3000, timerProgressBar: true, title: "Token copied", icon: "success"});
+ }else{
+ Swal.fire({title: "No token", text: "You need to login first", icon: "error"});
+ }
+ }
+
+ return (
+ <>
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/i18n.ts b/src/i18n.ts
index 3c5a4cd..2e6d058 100644
--- a/src/i18n.ts
+++ b/src/i18n.ts
@@ -13,7 +13,7 @@ i18n
interpolation: {
escapeValue: false, // not needed for react!!
},
- ns: ["artifact"],
+ ns: ["artifact","gcauth"],
defaultNS: "",
});
diff --git a/yarn.lock b/yarn.lock
index ca3b6d9..dfe7806 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8341,6 +8341,11 @@ svgo@^2.7.0:
picocolors "^1.0.0"
stable "^0.1.8"
+sweetalert2@^11.4.10:
+ version "11.4.10"
+ resolved "https://registry.yarnpkg.com/sweetalert2/-/sweetalert2-11.4.10.tgz#d0f7733a0997aad18b81c6f6b39a01c3a6cb4e21"
+ integrity sha512-Ms3mryLA/c5+llHNY0I9KXU8bv0N1a874gZr7xMPOiSnLD9veHZA92nZvJOkuN1hrdcR8kyj4IN8tBxlAIjVww==
+
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz"