Skip to content

Commit

Permalink
Prepare for release
Browse files Browse the repository at this point in the history
  • Loading branch information
slhmy committed Sep 30, 2024
1 parent 8567496 commit 0ab9b57
Show file tree
Hide file tree
Showing 19 changed files with 202 additions and 44 deletions.
4 changes: 2 additions & 2 deletions src/Router.tsx
Original file line number Diff line number Diff line change
@@ -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"));
Expand All @@ -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 = () => {
Expand All @@ -24,11 +24,11 @@ const Router: React.FC = () => {
<Route path="" element={<ProblemList />} />
<Route path=":slug" element={<Problem />} />
</Route>
<Route path="rank" element={<RankList />} />
<Route path="judges">
<Route path="" element={<JudgeList />} />
<Route path=":uid" element={<Judge />} />
</Route>
<Route path="rank" element={<RankList />} />
<Route path="login" element={<Login />} />
{/* Admin */}
<Route path="admin">
Expand Down
8 changes: 7 additions & 1 deletion src/apis/judge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +35,7 @@ export async function getJudgeList(limit?: number, offset?: number) {
params: {
limit,
offset,
self_only: selfOnly,
},
});
if (res.status !== 200) {
Expand Down
47 changes: 47 additions & 0 deletions src/apis/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
16 changes: 14 additions & 2 deletions src/components/display/JudgeTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import BrandCPPIcon from "@/components/display/icons/tabler/BrandCPPIcon";
import BrandPythonIcon from "@/components/display/icons/tabler/BrandPythonIcon";
import { shortenString } from "@/utils/string";
import UserAvatar from "./UserAvatar";
import { useSelector } from "react-redux";
import { userInfoSelector } from "@/store/selectors";

export interface JudgeTableProps {
data: JudgeServiceModel.JudgeInfo[];
Expand All @@ -17,6 +19,7 @@ export interface JudgeTableProps {
const JudgeTable: React.FC<JudgeTableProps> = (props) => {
const { t } = useTranslation();
const navigate = useNavigate();
const userInfo = useSelector(userInfoSelector);

return (
<div className={props.className}>
Expand All @@ -37,10 +40,19 @@ const JudgeTable: React.FC<JudgeTableProps> = (props) => {
key={idx}
className={joinClasses(
props.data.length > 1 ? "border-base-content/10" : "border-0",
props.enableRouting && "hover cursor-pointer",
((props.enableRouting && userInfo?.roles?.includes("admin")) ||
userInfo?.roles?.includes("super") ||
userInfo?.account === judge.user?.account) &&
"hover cursor-pointer",
)}
onClick={() => {
if (props.enableRouting) navigate(judge.UID);
if (
(props.enableRouting && userInfo?.roles?.includes("admin")) ||
userInfo?.roles?.includes("super") ||
userInfo?.account === judge.user?.account
) {
navigate(`/judges/${judge.UID}`);
}
}}
>
<th>{shortenString(judge.UID, 8, false)}</th>
Expand Down
4 changes: 2 additions & 2 deletions src/components/display/ProblemTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ const ProblemTable: React.FC<ProblemTableProps> = (props) => {

const ProblemTags: React.FC<{ tags: { name: string }[] }> = (props) => {
return (
<div className="space-x-2">
<div>
{props.tags.map((tag) => (
<div
key={tag.name}
className="badge border-0 bg-base-300 font-semibold text-base-content/80"
className="badge m-1 border-0 bg-base-300 font-semibold text-base-content/80"
>
{tag.name}
</div>
Expand Down
55 changes: 50 additions & 5 deletions src/components/display/UserTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import ConfirmDialog from "../control/ConfirmDialog";
import TrashIcon from "./icons/tabler/TrashIcon";
import { joinClasses } from "@/utils/common";
import UserCogIcon from "./icons/tabler/UserCogIcon";
import * as UserService from "@/apis/user";
import { useSelector } from "react-redux";
import { userInfoSelector } from "@/store/selectors";

export interface UserTableProps {
data: UserServiceModel.UserInfo[];
Expand All @@ -15,6 +18,9 @@ export interface UserTableProps {

const UserTable: React.FC<UserTableProps> = (props) => {
const { t } = useTranslation();
const [deletingAccount, setDeletingAccount] = React.useState<string | null>(
null,
);

return (
<>
Expand Down Expand Up @@ -47,7 +53,12 @@ const UserTable: React.FC<UserTableProps> = (props) => {
</td>
{props.showActions && (
<td className="p-2">
<UserActions userInfo={userInfo} onClickDelete={() => {}} />
<UserActions
userInfo={userInfo}
onClickDelete={() => {
setDeletingAccount(userInfo.account);
}}
/>
</td>
)}
</tr>
Expand All @@ -60,7 +71,15 @@ const UserTable: React.FC<UserTableProps> = (props) => {
title="Confirm"
message="Are you sure to delete this user?"
onClickConfirm={() => {
window.location.reload();
if (deletingAccount) {
UserService.deleteUser(deletingAccount)
.then(() => {
window.location.reload();
})
.catch((err) => {
console.error(err);
});
}
}}
/>
</>
Expand Down Expand Up @@ -92,24 +111,50 @@ interface ActionsProps {
}

const UserActions: React.FC<ActionsProps> = (props) => {
const userInfo = useSelector(userInfoSelector);

const onClickDelete = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();

props.onClickDelete();
const modal = document.getElementById(
"delete_confirm_modal",
"user_delete_confirm_modal",
) as HTMLDialogElement;
modal?.showModal();
};

return (
<div className="flex space-x-1">
<button className="btn btn-square btn-ghost btn-sm rounded">
<button
className={joinClasses(
"btn btn-square btn-ghost btn-sm rounded",
(props.userInfo.roles?.includes("super") ||
props.userInfo.account === userInfo?.account) &&
"btn-disabled",
)}
onClick={() => {
if (props.userInfo.roles?.includes("admin")) {
UserService.revokeUserRole(props.userInfo.account, "admin").catch(
(err) => {
console.error(err);
},
);
} else if (!props.userInfo.roles?.includes("super")) {
UserService.grantUserRole(props.userInfo.account, "admin").catch(
(err) => {
console.error(err);
},
);
}
window.location.reload();
}}
>
<UserCogIcon
className={joinClasses(
"h-5 w-5",
props.userInfo.roles?.includes("admin")
props.userInfo.roles?.includes("admin") ||
props.userInfo.roles?.includes("super")
? "text-success"
: "text-base-content",
)}
Expand Down
18 changes: 15 additions & 3 deletions src/hooks/judge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ export const useJudgeList = () => {
const [total, setTotal] = useState<number>(0);
const [limit, setLimit] = useState<number>(10);
const [offset, setOffset] = useState<number>(0);
const [selfOnly, setSelfOnly] = useState<boolean>(false);
const [judgeList, setJudgeList] = useState<JudgeServiceModel.JudgeInfo[]>([]);

const getJudgeListFromServer = () => {
JudgeService.getJudgeList()
JudgeService.getJudgeList(limit, offset, selfOnly)
.then((res) => {
setJudgeList(res.list);
setTotal(res.total);
Expand All @@ -86,7 +87,7 @@ export const useJudgeList = () => {
});
};

useEffect(getJudgeListFromServer, [dispatch, limit, offset, t]);
useEffect(getJudgeListFromServer, [dispatch, limit, offset, t, selfOnly]);

function getJudgeList() {
return judgeList;
Expand All @@ -105,5 +106,16 @@ export const useJudgeList = () => {
setOffset(offset);
}

return { getJudgeList, refreshJudgeList, getPageCount, setPagenation };
function getSelfOnly() {
return selfOnly;
}

return {
getJudgeList,
refreshJudgeList,
getPageCount,
setPagenation,
getSelfOnly,
setSelfOnly,
};
};
4 changes: 2 additions & 2 deletions src/hooks/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const useUserInfoList = () => {
useEffect(() => {
UserService.getUserInfoList(limit, offset)
.then((res) => {
setUserInfoList(res.list);
setTotal(res.total);
if (res.list) setUserInfoList(res.list);
if (res.total) setTotal(res.total);
})
.catch((err) => {
dispatch({
Expand Down
14 changes: 14 additions & 0 deletions src/i18n/resources/zh_CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,33 @@ const ZH_CN_TRANSLATIONS: Resource = {
Theme: "主题",
Create: "创建",
Cancel: "取消",
Confirm: "确认",
Internal: "内部",
Actions: "操作",
Avatar: "头像",
Name: "名称",
Account: "账号",
Roles: "角色",

"or Register": "或注册",
"Welcome!": "欢迎!",
"Welcome to OJ LAB!": "欢迎来到 OJ LAB!",
"input title": "输入标题",
"Problem Title": "题目标题",
"Submit Count": "提交数",
"Submit Time": "提交时间",
"Accept Count": "通过数",
"Accept Rate": "通过率",
"Problem Management": "题目管理",
"User Management": "用户管理",
"Your solution": "你的解答",
"Just look at yourself": "只看自己",
"Click and confirm submission": "点击并确认提交",
"Please login first": "请先登录",
"OAuth Login": "OAuth 登录",
"Password Login": "密码登录",
"Sign In with GitHub": "使用 GitHub 登录",
"Give us idea & feedback": "欢迎给我们想法和反馈",

"Failed to fetch rank list": "获取排名列表失败",
"Failed to fetch user list": "获取用户列表失败",
Expand Down
10 changes: 6 additions & 4 deletions src/layouts/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const APPNavigation: PageMenuItem[] = [

const AdminNavigation: PageMenuItem[] = [
{
name: "Problem Packages",
name: "Problem Management",
href: "/admin/problems",
icon: <PackageIcon className="h-6 w-6 shrink-0" />,
},
Expand Down Expand Up @@ -72,12 +72,14 @@ export const Drawer: React.FC<DrawerProps> = (props) => {
const dispatch = useDispatch();
const [open, setOpen] = React.useState<boolean>(true);
const userInfo = useSelector(userInfoSelector);
const isAdmin = userInfo?.roles?.includes("admin") ?? false;
const isAdmin =
(userInfo?.roles?.includes("admin") ||
userInfo?.roles?.includes("super")) ??
false;

useEffect(() => {
dispatch(setIsDrawerOpen(open));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
}, [dispatch, open]);

return (
<div
Expand Down
10 changes: 10 additions & 0 deletions src/layouts/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { useTranslation } from "react-i18next";

const Footer: React.FC = () => {
const { t } = useTranslation();

return (
<div className="footer flex w-full flex-col justify-center gap-1 text-xs text-base-content/80">
<p>©2024 OJ Lab</p>
<p className="flex flex-row">
Github:
<a href="https://github.com/oj-lab">https://github.com/oj-lab</a>
</p>
<p className="flex flex-row">
{t("Give us idea & feedback")}:
<a href="https://github.com/oj-lab/roadmap/discussions">
https://github.com/oj-lab/roadmap/discussions
</a>
</p>
</div>
);
};
Expand Down
Loading

0 comments on commit 0ab9b57

Please sign in to comment.