Skip to content

Commit

Permalink
Prepare for release
Browse files Browse the repository at this point in the history
  • Loading branch information
slhmy committed Oct 1, 2024
1 parent 8567496 commit 6f6afb1
Show file tree
Hide file tree
Showing 24 changed files with 350 additions and 91 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");
}
}
6 changes: 3 additions & 3 deletions src/components/control/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export const ConfirmDialog: FC<ConfirmDialogProps> = (props) => {

return (
<dialog id={props.id} className="modal">
<div className="modal-box">
<div className="modal-box rounded">
{props.title && <h3 className="text-lg font-bold">{t(props.title)}</h3>}
<p className="py-4">{t(props.message)}</p>
<div className="modal-action">
<form method="dialog" className="flex gap-2">
<button className="btn btn-sm">{t("Cancel")}</button>
<button className="btn btn-sm rounded">{t("Cancel")}</button>
<button
className="btn btn-error btn-sm"
className="btn btn-primary btn-sm rounded"
onClick={props.onClickConfirm}
>
{t("Confirm")}
Expand Down
2 changes: 1 addition & 1 deletion src/components/control/ProblemSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const ProblemSearch: React.FC<ProblemSearchProps> = (props): JSX.Element => {
)
}
>
<option value="all">{t("all")}</option>
<option value="all">{t("all difficulty")}</option>
<option value="easy">{t("easy")}</option>
<option value="medium">{t("medium")}</option>
<option value="hard">{t("hard")}</option>
Expand Down
22 changes: 17 additions & 5 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 Expand Up @@ -94,12 +106,12 @@ function getStatusBadageClass(status: string, verdict: string): string {
if (status === "finished" && verdict === "Accepted") {
return "bg-success/10 text-success";
}
if (status === "finished" && verdict === "WrongAnswer") {
return "bg-error/10 text-error";
}
if (status === "finished" && verdict === "CompileError") {
return "bg-warning/10 text-warning";
}
if (status === "finished") {
return "bg-error/10 text-error";
}
if (status === "pending") {
return "bg-primary/10 text-primary";
}
Expand Down
14 changes: 9 additions & 5 deletions src/components/display/ProblemTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const ProblemTable: React.FC<ProblemTableProps> = (props) => {
<ConfirmDialog
id="problem_delete_confirm_modal"
title="Confirm"
message="Are you sure to delete this problem?"
message={t("Are you sure to delete this problem?")}
onClickConfirm={() => {
ProblemService.deleteProblem(deletingSlug).then((_) => {
window.location.reload();
Expand All @@ -100,21 +100,25 @@ const ProblemTable: React.FC<ProblemTableProps> = (props) => {
};

const ProblemTags: React.FC<{ tags: { name: string }[] }> = (props) => {
const { t } = useTranslation();

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}
{t(tag.name)}
</div>
))}
</div>
);
};

const DifficultyBadge: React.FC<{ difficulty: string }> = (props) => {
const { t } = useTranslation();

return (
<div
className={joinClasses(
Expand All @@ -124,7 +128,7 @@ const DifficultyBadge: React.FC<{ difficulty: string }> = (props) => {
props.difficulty === "hard" && "bg-error/10 text-error",
)}
>
{props.difficulty}
{t(props.difficulty)}
</div>
);
};
Expand Down
57 changes: 51 additions & 6 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 @@ -58,9 +69,17 @@ const UserTable: React.FC<UserTableProps> = (props) => {
<ConfirmDialog
id="user_delete_confirm_modal"
title="Confirm"
message="Are you sure to delete this user?"
message={t("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
2 changes: 1 addition & 1 deletion src/components/navigation/PageBreadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const PageBreadcrumbs: React.FC = () => {
let paths = location.pathname.split("/").filter((x) => x);

return (
<div className="breadcrumbs overflow-visible text-sm">
<div className="breadcrumbs overflow-visible p-0 text-sm">
<ul>
{paths.map((path, index) => {
const url = `/${paths.slice(0, index + 1).join("/")}`;
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,
};
};
Loading

0 comments on commit 6f6afb1

Please sign in to comment.