diff --git a/package-lock.json b/package-lock.json index 3276904ec..b7919e815 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "rehype-react": "^8.0.0", "remark-math": "^6.0.0", "ts-md5": "^1.3.1", - "typescript": "^5.3.3", + "typescript": "^5.6.2", "uuid": "^9.0.1", "web-vitals": "^2.1.4" }, @@ -11324,9 +11324,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 676cb4604..90768d8ab 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "rehype-react": "^8.0.0", "remark-math": "^6.0.0", "ts-md5": "^1.3.1", - "typescript": "^5.3.3", + "typescript": "^5.6.2", "uuid": "^9.0.1", "web-vitals": "^2.1.4" }, diff --git a/src/Router.tsx b/src/Router.tsx index c3cc5b18a..56d1bebe4 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -1,7 +1,6 @@ import React, { Suspense, lazy } from "react"; import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import Layout from "@/layouts/Layout"; -import RankList from "./pages/RankList"; const Problem = lazy(() => import("@/pages/problem/Problem")); const AdminProblemList = lazy(() => import("@/pages/admin/ProblemList")); @@ -10,6 +9,7 @@ const AdminUserList = lazy(() => import("@/pages/admin/UserList")); const ProblemList = lazy(() => import("@/pages/problem/ProblemList")); const JudgeList = lazy(() => import("@/pages//judge/JudgeList")); const Judge = lazy(() => import("@/pages/judge/Judge")); +const RankList = lazy(() => import("@/pages/RankList")); const Login = lazy(() => import("@/pages/Login")); const Router: React.FC = () => { @@ -24,11 +24,11 @@ const Router: React.FC = () => { } /> } /> + } /> } /> } /> - } /> } /> {/* Admin */} diff --git a/src/apis/judge.ts b/src/apis/judge.ts index f64a8e468..b0ce31b2a 100644 --- a/src/apis/judge.ts +++ b/src/apis/judge.ts @@ -19,9 +19,14 @@ export async function postJudge(slug: string, code: string, language: string) { return res.data; } -export async function getJudgeList(limit?: number, offset?: number) { +export async function getJudgeList( + limit?: number, + offset?: number, + selfOnly?: boolean, +) { limit = limit || 10; offset = offset || 0; + selfOnly = selfOnly || false; let res = await axiosClient.get<{ total: number; @@ -30,6 +35,7 @@ export async function getJudgeList(limit?: number, offset?: number) { params: { limit, offset, + self_only: selfOnly, }, }); if (res.status !== 200) { diff --git a/src/apis/user.ts b/src/apis/user.ts index e32512407..127f28ec3 100644 --- a/src/apis/user.ts +++ b/src/apis/user.ts @@ -25,3 +25,50 @@ export async function getUserInfoList( } return res.data; } + +export async function deleteUser(account: string) { + let res = await axiosClient.delete(`/api/v1/user/${account}`); + if (res.status !== 200) { + throw Error("failed to delete user"); + } +} + +export async function grantUserRole( + account: string, + role: string, + domain?: string, +) { + if (!domain) domain = "system"; + + let body = { + role, + domain, + }; + let data = JSON.stringify(body); + + let res = await axiosClient.post(`/api/v1/user/${account}/role`, data); + if (res.status !== 200) { + throw Error("failed to grant user role"); + } +} + +export async function revokeUserRole( + account: string, + role: string, + domain?: string, +) { + if (!domain) domain = "system"; + + let body = { + role, + domain, + }; + let data = JSON.stringify(body); + + let res = await axiosClient.delete(`/api/v1/user/${account}/role`, { + data, + }); + if (res.status !== 200) { + throw Error("failed to revoke user role"); + } +} diff --git a/src/components/control/ConfirmDialog.tsx b/src/components/control/ConfirmDialog.tsx index 3fb8b0c11..9d0e897ef 100644 --- a/src/components/control/ConfirmDialog.tsx +++ b/src/components/control/ConfirmDialog.tsx @@ -13,14 +13,14 @@ export const ConfirmDialog: FC = (props) => { return ( -
+
{props.title &&

{t(props.title)}

}

{t(props.message)}

- + ) : ( <> @@ -40,7 +45,7 @@ const Login: React.FC = () => { setAccount(e.target.value)} /> @@ -48,7 +53,7 @@ const Login: React.FC = () => { setPassword(e.target.value)} autoComplete="current-password" /> @@ -67,13 +72,38 @@ const Login: React.FC = () => { className="btn btn-neutral btn-active btn-sm btn-block" type="submit" onClick={() => { - postPasswordLogin(account, password).then((res) => { - console.log(res); - window.location.href = import.meta.env.BASE_URL; - }); + if (!account) { + dispatch({ + type: AddMessageSagaPattern, + payload: { + id: "login-error", + content: `${t("Please input your account")}`, + duration: 3000, + level: "warning", + }, + }); + return; + } + postPasswordLogin(account, password) + .then((res) => { + console.log(res); + window.location.href = import.meta.env.BASE_URL; + }) + .catch((err) => { + dispatch({ + type: AddMessageSagaPattern, + payload: { + id: "login-error", + content: `${t("Failed login with password")}`, + duration: 3000, + level: "error", + err: err.toString(), + }, + }); + }); }} > - Login + {t("Login")} )} @@ -95,11 +125,10 @@ const Login: React.FC = () => {
- Password Login - + {t("Password Login")} + {" "} - {" "} - (Internal) + {`(${t("Internal")})`}
@@ -107,7 +136,7 @@ const Login: React.FC = () => { <> - OAuth Login + {t("OAuth Login")} )} diff --git a/src/pages/RankList.tsx b/src/pages/RankList.tsx index 8bab78303..1337283eb 100644 --- a/src/pages/RankList.tsx +++ b/src/pages/RankList.tsx @@ -1,14 +1,23 @@ import RankTable from "@/components/display/RankTable"; import { useRankList } from "@/hooks/rank"; +import { useTranslation } from "react-i18next"; const RankList: React.FC = () => { + const { t } = useTranslation(); const { getRankList } = useRankList(); const rankList = getRankList(); return ( -
-
-
+
+
+

+ {t( + "Once you passed the problem, the submission after will not be counted.", + )} +

+
+
+
diff --git a/src/pages/admin/ProblemList.tsx b/src/pages/admin/ProblemList.tsx index f14e410bd..b1016d2e2 100644 --- a/src/pages/admin/ProblemList.tsx +++ b/src/pages/admin/ProblemList.tsx @@ -29,7 +29,7 @@ const ProblemList: React.FC = () => { return (
-
+
{ setSearchingTitle(t); diff --git a/src/pages/admin/UserList.tsx b/src/pages/admin/UserList.tsx index 99cd75bcb..bbf598133 100644 --- a/src/pages/admin/UserList.tsx +++ b/src/pages/admin/UserList.tsx @@ -14,8 +14,7 @@ const UserList: React.FC = () => { useEffect(() => { setPagenation(countPerPage, page * countPerPage); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [countPerPage, page]); + }, [countPerPage, page, setPagenation]); return (
diff --git a/src/pages/judge/JudgeList.tsx b/src/pages/judge/JudgeList.tsx index 018dc8361..5ff679b15 100644 --- a/src/pages/judge/JudgeList.tsx +++ b/src/pages/judge/JudgeList.tsx @@ -7,8 +7,14 @@ import React, { useEffect } from "react"; const countPerPageSelections = [10, 25, 50]; const JudgeList: React.FC = () => { - const { getJudgeList, refreshJudgeList, getPageCount, setPagenation } = - useJudgeList(); + const { + getJudgeList, + refreshJudgeList, + getPageCount, + setPagenation, + getSelfOnly, + setSelfOnly, + } = useJudgeList(); const { t } = useTranslation(); // useEvent(); const refreshSeconds = 5; @@ -33,15 +39,26 @@ const JudgeList: React.FC = () => { useEffect(() => { setPagenation(countPerPage, page * countPerPage); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [countPerPage, page]); + }, [countPerPage, page, setPagenation]); return ( -
+
+
+

+ {t( + "If you finish more than 10 problems, an additional badge will be given.", + )} +

+
+ { + runJudge((judgeInfo) => { + dispatch({ + type: AddMessageSagaPattern, + payload: { + id: `judge-submitted-${judgeInfo.UID}`, + content: + t("Judge") + " " + judgeInfo.UID + " " + t("submitted"), + duration: 3000, + level: "success", + }, + }); + }); + }} + />
); }; diff --git a/src/pages/problem/ProblemList.tsx b/src/pages/problem/ProblemList.tsx index a3f79d81e..1cf9bd009 100644 --- a/src/pages/problem/ProblemList.tsx +++ b/src/pages/problem/ProblemList.tsx @@ -5,17 +5,14 @@ import Pagination from "@/components/control/Pagination"; import ProblemSearch, { DifficultySelection, } from "@/components/control/ProblemSearch"; +import { useTranslation } from "react-i18next"; const countPerPageSelections = [10, 25, 50]; const ProblemList: React.FC = () => { - const { - getProblemInfoList, - getPageCount, - setPagenation, - setSearch, - refreshProblemInfoList, - } = useProblemInfoList(); + const { t } = useTranslation(); + const { getProblemInfoList, getPageCount, setPagenation, setSearch } = + useProblemInfoList(); const [countPerPage, setCountPerPage] = React.useState( countPerPageSelections[0], ); @@ -29,9 +26,19 @@ const ProblemList: React.FC = () => { }, [countPerPage, page, setPagenation]); return ( -
-
-
+
+
+

+ {t("Select a problem to view the details and submit your solution.")} +

+

+ {t( + "We will give you badges for your achievements in later version of OJ LAB.", + )} +

+
+
+
{ setSearchingTitle(t); @@ -41,7 +48,7 @@ const ProblemList: React.FC = () => { }} onSearch={() => { setSearch(searchingTitle, searchingDifficulty); - refreshProblemInfoList(); + setPage(0); // Set page will trigger useEffect to refresh the list }} title={searchingTitle} difficulty={searchingDifficulty}