Compare commits
No commits in common. "766074c6c34b7229aae6b62fd23bd74f374adb12" and "a723b02f19693323c565cbea64cf65b88add436d" have entirely different histories.
766074c6c3
...
a723b02f19
44
package.json
44
package.json
@ -10,40 +10,38 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/pro-components": "^2.6.43",
|
||||
"@sentry/react": "^7.90.0",
|
||||
"@sentry/vite-plugin": "^2.10.2",
|
||||
"ace-builds": "^1.32.2",
|
||||
"antd": "^5.12.4",
|
||||
"axios": "^1.6.2",
|
||||
"github-markdown-css": "^5.5.0",
|
||||
"@ant-design/icons": "^5.1.4",
|
||||
"@ant-design/pro-components": "^2.6.7",
|
||||
"@sentry/react": "^7.58.1",
|
||||
"@sentry/vite-plugin": "^2.4.0",
|
||||
"antd": "^5.7.0",
|
||||
"axios": "^1.4.0",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-router-dom": "^6.21.0",
|
||||
"react-router-dom": "^6.14.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"rehype-mathjax": "^4.0.3",
|
||||
"rehype-mathjax": "^4.0.2",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"remark-emoji": "^3.1.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.5",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-syntax-highlighter": "^15.5.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"@types/node": "^20.4.2",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@types/react-syntax-highlighter": "^15.5.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
||||
"@typescript-eslint/parser": "^5.61.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"eslint-plugin-react-refresh": "^0.4.1",
|
||||
"prettier": "3.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^4.5.1"
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
2132
pnpm-lock.yaml
2132
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
3
src/api/api.ts
Normal file
3
src/api/api.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { send } from "./base";
|
||||
|
||||
export { UserApi } from "./user.ts";
|
@ -25,7 +25,7 @@ export async function send<D, T>(
|
||||
): Promise<T> {
|
||||
try {
|
||||
const resp = await axios.post<ResponseWrap<T>>(api, data, {
|
||||
headers: { Authorization: token },
|
||||
headers: { token: token },
|
||||
});
|
||||
if (resp.data.code !== 0) return Promise.reject(resp.data.msg);
|
||||
return resp.data.body;
|
||||
|
@ -1,9 +0,0 @@
|
||||
import { ProblemApi } from "./problem.ts";
|
||||
|
||||
export async function ProblemLoader({ params }: { params: { id: string } }) {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id)) {
|
||||
throw new Error("invalid problem id");
|
||||
}
|
||||
return await ProblemApi.Details({ pid: id });
|
||||
}
|
@ -7,11 +7,6 @@ export interface UserReq {
|
||||
uid?: number;
|
||||
}
|
||||
|
||||
export interface UserLoginResp {
|
||||
nickname: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
meta: Meta;
|
||||
user_name: string;
|
||||
@ -28,7 +23,7 @@ export class UserApi {
|
||||
return send("/user/create", data);
|
||||
}
|
||||
|
||||
static async Login(data: UserReq): Promise<UserLoginResp> {
|
||||
static async Login(data: UserReq): Promise<string> {
|
||||
if (!data.username || !data.password) {
|
||||
throw new Error("Missing required fields");
|
||||
}
|
||||
|
@ -1,116 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { UserApi } from "../api/user.ts";
|
||||
|
||||
interface AuthContextType {
|
||||
token: string;
|
||||
nickname: string;
|
||||
isLoggedIn: boolean;
|
||||
login: (
|
||||
username: string,
|
||||
password: string,
|
||||
onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => void;
|
||||
logout: (
|
||||
onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => void;
|
||||
}
|
||||
|
||||
const AuthContext = React.createContext<AuthContextType>({
|
||||
token: "",
|
||||
nickname: "guest",
|
||||
isLoggedIn: false,
|
||||
login: (
|
||||
_username: string,
|
||||
_password: string,
|
||||
_onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => {
|
||||
onFailed("not implemented");
|
||||
},
|
||||
logout: (_onSuccess: VoidFunction, onFailed: (error: string) => void) => {
|
||||
onFailed("not implemented");
|
||||
},
|
||||
});
|
||||
|
||||
function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [token, setToken] = useState("");
|
||||
const [nickname, setNickname] = useState("guest");
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
setToken(token);
|
||||
setIsLoggedIn(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const login = (
|
||||
username: string,
|
||||
password: string,
|
||||
onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => {
|
||||
UserApi.Login({ username: username, password: password })
|
||||
.then((resp) => {
|
||||
localStorage.setItem("token", resp.token);
|
||||
setToken(resp.token);
|
||||
setNickname(resp.nickname);
|
||||
setIsLoggedIn(true);
|
||||
onSuccess();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("[user] userLogin", err);
|
||||
setIsLoggedIn(false);
|
||||
onFailed(err as string);
|
||||
});
|
||||
};
|
||||
|
||||
const logout = (
|
||||
onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => {
|
||||
localStorage.removeItem("token");
|
||||
setToken("");
|
||||
setNickname("guest");
|
||||
setIsLoggedIn(false);
|
||||
|
||||
if (!isLoggedIn) {
|
||||
onFailed("not logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
UserApi.Logout(token)
|
||||
.then(() => {
|
||||
onSuccess();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("[user] userLogout", err);
|
||||
onFailed(err as string);
|
||||
});
|
||||
};
|
||||
|
||||
const value = { token, nickname, isLoggedIn, login, logout };
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}> {children} </AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function RequireAuth({ children }: { children: React.ReactNode }) {
|
||||
const auth = React.useContext(AuthContext);
|
||||
const location = useLocation();
|
||||
|
||||
if (!auth.token) {
|
||||
return <Navigate to="/login" state={{ from: location }} replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export { AuthProvider, RequireAuth, AuthContext };
|
@ -1,40 +0,0 @@
|
||||
import AceEditor from "react-ace";
|
||||
|
||||
import "ace-builds/src-noconflict/mode-c_cpp";
|
||||
import "ace-builds/src-noconflict/mode-python";
|
||||
import "ace-builds/src-noconflict/snippets/c_cpp";
|
||||
import "ace-builds/src-noconflict/snippets/python";
|
||||
|
||||
import "ace-builds/src-noconflict/theme-github";
|
||||
import "ace-builds/src-noconflict/ext-language_tools";
|
||||
|
||||
interface EditorProps {
|
||||
mode: string;
|
||||
onChange: (code: string) => void;
|
||||
}
|
||||
|
||||
export default function Editor(props: EditorProps) {
|
||||
return (
|
||||
<>
|
||||
<AceEditor
|
||||
name="code_editor"
|
||||
fontSize={16}
|
||||
height="75vh"
|
||||
width="100%"
|
||||
mode={props.mode}
|
||||
theme="github"
|
||||
onChange={(code: string) => props.onChange(code)} // force converts to string
|
||||
editorProps={{
|
||||
$blockScrolling: false,
|
||||
$enableMultiselect: true,
|
||||
}}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
wrap: true,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import React from "react";
|
||||
import { AuthContext } from "./auth.tsx";
|
||||
|
||||
export function useAuth() {
|
||||
return React.useContext(AuthContext);
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
import React from "react";
|
||||
import { Link, useLoaderData } from "react-router-dom";
|
||||
import { Collapse, CollapseProps, Descriptions, Space, Tag } from "antd";
|
||||
|
||||
import { DetailsResp } from "../api/problem.ts";
|
||||
|
||||
interface ProblemDetailsProps {
|
||||
action: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function ProblemDetails(props: ProblemDetailsProps) {
|
||||
const details = useLoaderData() as DetailsResp;
|
||||
|
||||
const problemInfo = (
|
||||
<Descriptions bordered column={1} size="small">
|
||||
<Descriptions.Item label="Provider">
|
||||
<Link to={`/user/${details.problem.provider.meta.ID}`}>
|
||||
{details.problem.provider.nick_name}
|
||||
</Link>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Supported Languages">
|
||||
<Space size={[0, 8]} wrap>
|
||||
{details.context.Languages.map((l) => (
|
||||
<Tag key={l.Lang}>{l.Lang}</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Task Nums">
|
||||
{details.context.Tasks.length}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
);
|
||||
|
||||
const runtimeLimit = (
|
||||
<Descriptions bordered column={1} size="small">
|
||||
<Descriptions.Item label="Time Limit">
|
||||
{details.context.Runtime.TimeLimit} ms
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Memory Limit">
|
||||
{details.context.Runtime.MemoryLimit} MB
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Process Limit">
|
||||
{details.context.Runtime.NProcLimit}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
);
|
||||
|
||||
const miscItems: CollapseProps["items"] = [
|
||||
{
|
||||
key: "1",
|
||||
label: "Problem Info",
|
||||
children: problemInfo,
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
label: "Runtime Limit",
|
||||
children: runtimeLimit,
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
label: "Action",
|
||||
children: props.action,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Collapse items={miscItems} defaultActiveKey={["1", "2", "3"]} />
|
||||
</>
|
||||
);
|
||||
}
|
@ -10,15 +10,13 @@ import {
|
||||
} from "react-router-dom";
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
import { AuthProvider } from "./components/auth.tsx";
|
||||
import { Root, HomePage, ErrorPage } from "./pages/pages.tsx";
|
||||
import { RouteConfigs } from "./routes.tsx";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://903205e5bdddd0bb8f7625a63b161764@o4506423190355968.ingest.sentry.io/4506428723757056",
|
||||
environment: import.meta.env.MODE,
|
||||
dsn: "https://4f90ea95bb8b462c8d8432ddbabac9b8@o354675.ingest.sentry.io/4505537167491072",
|
||||
integrations: [
|
||||
new Sentry.BrowserTracing({
|
||||
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
|
||||
@ -72,8 +70,6 @@ const router = sentryCreateBrowserRouter([
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<AuthProvider>
|
||||
<RouterProvider router={router} />
|
||||
</AuthProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ const ErrorPage = () => {
|
||||
|
||||
const convertError = (error: unknown): string => {
|
||||
if (isRouteErrorResponse(error)) {
|
||||
return `${error.status} ${error.statusText}`;
|
||||
return error.error?.message || error.statusText;
|
||||
} else if (error instanceof Error) {
|
||||
return error.message;
|
||||
} else if (typeof error === "string") {
|
||||
|
@ -1,86 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { message } from "antd";
|
||||
import { LoginForm, ProFormText } from "@ant-design/pro-components";
|
||||
import { LockOutlined, UserOutlined } from "@ant-design/icons";
|
||||
|
||||
import { useAuth } from "../components/hook.ts";
|
||||
|
||||
export function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
const auth = useAuth();
|
||||
const [msg, msgContextHolder] = message.useMessage();
|
||||
const [errMsg, setErrMsg] = useState("");
|
||||
|
||||
const onSuccess = async () => {
|
||||
await msg.open({
|
||||
type: "success",
|
||||
content: "登录成功",
|
||||
duration: 1,
|
||||
});
|
||||
if (window.history?.length) navigate(-1);
|
||||
else navigate("/");
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async function onFinish(values: { username: string; password: string }) {
|
||||
const username = values.username;
|
||||
const password = values.password;
|
||||
|
||||
auth.login(
|
||||
username,
|
||||
password,
|
||||
() => {
|
||||
void onSuccess();
|
||||
},
|
||||
(err) => setErrMsg(err),
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (errMsg) {
|
||||
void msg.error(errMsg).then(() => setErrMsg(""));
|
||||
}
|
||||
}, [errMsg, msg]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{msgContextHolder}
|
||||
<LoginForm
|
||||
title="WOJ"
|
||||
subTitle="WHU Online Judge"
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<ProFormText
|
||||
name="username"
|
||||
fieldProps={{
|
||||
size: "large",
|
||||
prefix: <UserOutlined className={"prefixIcon"} />,
|
||||
}}
|
||||
placeholder={"用户名"}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "请输入用户名!",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormText.Password
|
||||
name="password"
|
||||
fieldProps={{
|
||||
size: "large",
|
||||
prefix: <LockOutlined className={"prefixIcon"} />,
|
||||
}}
|
||||
placeholder={"密码"}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "请输入密码!",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</LoginForm>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Result } from "antd";
|
||||
import { SmileOutlined } from "@ant-design/icons";
|
||||
|
||||
import { useAuth } from "../components/hook.ts";
|
||||
|
||||
export function LogoutPage() {
|
||||
const navigate = useNavigate();
|
||||
const auth = useAuth();
|
||||
|
||||
const goHome = () => setTimeout(() => navigate("/"), 1500);
|
||||
useEffect(
|
||||
() => {
|
||||
auth.logout(goHome, goHome);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Result icon={<SmileOutlined />} title={"See you next time!"} />
|
||||
</>
|
||||
);
|
||||
}
|
@ -2,9 +2,5 @@ export { Root } from "./root";
|
||||
export { ErrorPage } from "./error-page";
|
||||
|
||||
export { HomePage } from "./home";
|
||||
export { ProblemPage } from "./problem";
|
||||
export { ProblemLoader, ProblemPage } from "./problem";
|
||||
export { SearchPage } from "./search";
|
||||
export { SubmitPage } from "./submit";
|
||||
|
||||
export { LoginPage } from "./login.tsx";
|
||||
export { LogoutPage } from "./logout.tsx";
|
||||
|
@ -1,15 +1,63 @@
|
||||
import { Row, Col, Space, Button } from "antd";
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Collapse,
|
||||
CollapseProps,
|
||||
Descriptions,
|
||||
Tag,
|
||||
Space,
|
||||
Button,
|
||||
} from "antd";
|
||||
import { PlayCircleOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import { useLoaderData, useNavigate } from "react-router-dom";
|
||||
import { Link, useLoaderData, useNavigate } from "react-router-dom";
|
||||
|
||||
import Markdown from "../components/markdown.tsx";
|
||||
import { DetailsResp } from "../api/problem.ts";
|
||||
import ProblemDetails from "../components/problem-details.tsx";
|
||||
import { DetailsResp, ProblemApi } from "../api/problem.ts";
|
||||
|
||||
export async function ProblemLoader({ params }: { params: { id: string } }) {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id)) {
|
||||
throw new Error("invalid problem id");
|
||||
}
|
||||
return await ProblemApi.Details({ pid: id });
|
||||
}
|
||||
|
||||
export function ProblemPage() {
|
||||
const details = useLoaderData() as DetailsResp;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const problemInfo = (
|
||||
<Descriptions bordered column={1} size="small">
|
||||
<Descriptions.Item label="Provider">
|
||||
<Link to={`/user/${details.problem.provider.meta.ID}`}>
|
||||
{details.problem.provider.nick_name}
|
||||
</Link>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Supported Languages">
|
||||
<Space size={[0, 8]} wrap>
|
||||
{details.context.Languages.map((l) => (
|
||||
<Tag key={l.Lang}>{l.Lang}</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Task Nums">
|
||||
{details.context.Tasks.length}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
);
|
||||
const runtimeLimit = (
|
||||
<Descriptions bordered column={1} size="small">
|
||||
<Descriptions.Item label="Time Limit">
|
||||
{details.context.Runtime.TimeLimit} ms
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Memory Limit">
|
||||
{details.context.Runtime.MemoryLimit} MB
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Process Limit">
|
||||
{details.context.Runtime.NProcLimit}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
);
|
||||
const actionBtn = (
|
||||
<Space wrap>
|
||||
<Button
|
||||
@ -31,8 +79,28 @@ export function ProblemPage() {
|
||||
</Space>
|
||||
);
|
||||
|
||||
const miscItems: CollapseProps["items"] = [
|
||||
{
|
||||
key: "1",
|
||||
label: "Problem Info",
|
||||
children: problemInfo,
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
label: "Runtime Limit",
|
||||
children: runtimeLimit,
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
label: "Action",
|
||||
children: actionBtn,
|
||||
},
|
||||
];
|
||||
|
||||
const ProblemStatement = <Markdown markdown={details.problem.statement} />;
|
||||
const MiscPanel = <ProblemDetails action={actionBtn} />;
|
||||
const MiscPanel = (
|
||||
<Collapse items={miscItems} defaultActiveKey={["1", "2", "3"]} />
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,84 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { useLoaderData } from "react-router-dom";
|
||||
|
||||
import { Button, Col, message, Row, Select, Space } from "antd";
|
||||
import { PlayCircleOutlined } from "@ant-design/icons";
|
||||
|
||||
import Editor from "../components/editor.tsx";
|
||||
import ProblemDetails from "../components/problem-details.tsx";
|
||||
import { useAuth } from "../components/hook.ts";
|
||||
import { DetailsResp } from "../api/problem.ts";
|
||||
import { SubmissionApi } from "../api/submission.ts";
|
||||
|
||||
const AvailLang = [
|
||||
{ value: "cpp", label: "C++" },
|
||||
{ value: "c", label: "C" },
|
||||
{ value: "python", label: "Python" },
|
||||
];
|
||||
|
||||
const LangToMode: { [lang: string]: string } = {
|
||||
cpp: "c_cpp",
|
||||
c: "c_cpp",
|
||||
python: "python",
|
||||
};
|
||||
|
||||
export function SubmitPage() {
|
||||
const details = useLoaderData() as DetailsResp;
|
||||
const auth = useAuth();
|
||||
const [msg, msgContextHolder] = message.useMessage();
|
||||
|
||||
const langOptions = AvailLang.filter((l) =>
|
||||
details.context.Languages.some((x) => x.Lang === l.value),
|
||||
);
|
||||
|
||||
const [lang, setLang] = useState(langOptions[0].value);
|
||||
const [code, setCode] = useState("");
|
||||
|
||||
const submitCode = () => {
|
||||
console.debug(lang, code);
|
||||
SubmissionApi.Create(
|
||||
{
|
||||
pid: details.problem.meta.ID,
|
||||
language: lang,
|
||||
code: code,
|
||||
},
|
||||
auth.token,
|
||||
).then(
|
||||
// TODO: onSuccess: jump to status page
|
||||
() => msg.success("Submit success"),
|
||||
(err: string) => msg.error("Failed to submit: " + err),
|
||||
);
|
||||
};
|
||||
|
||||
const funcMenu = (
|
||||
<Space wrap>
|
||||
<Select
|
||||
placeholder="Select a person"
|
||||
optionFilterProp="children"
|
||||
value={lang}
|
||||
onChange={setLang}
|
||||
options={langOptions}
|
||||
/>
|
||||
<Button icon={<PlayCircleOutlined />} onClick={submitCode}>
|
||||
Submit
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
|
||||
const codeEditor = Editor({
|
||||
mode: LangToMode[lang],
|
||||
onChange: setCode,
|
||||
});
|
||||
|
||||
const MiscPanel = <ProblemDetails action={funcMenu} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
{msgContextHolder}
|
||||
<Row justify="center" align="top" gutter={[16, 16]}>
|
||||
<Col span={18}>{codeEditor}</Col>
|
||||
<Col span={6}>{MiscPanel}</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,28 +1,13 @@
|
||||
import {
|
||||
HomePage,
|
||||
LoginPage,
|
||||
LogoutPage,
|
||||
ProblemPage,
|
||||
SearchPage,
|
||||
SubmitPage,
|
||||
} from "./pages/pages.tsx";
|
||||
import { RequireAuth } from "./components/auth.tsx";
|
||||
import { HomePage } from "./pages/home.tsx";
|
||||
|
||||
import { ProblemLoader } from "./api/loader.ts";
|
||||
import { ProblemLoader, ProblemPage } from "./pages/pages.tsx";
|
||||
import { SearchPage } from "./pages/pages.tsx";
|
||||
|
||||
const RouteConfigs = [
|
||||
{
|
||||
path: "home",
|
||||
element: <HomePage />,
|
||||
},
|
||||
{
|
||||
path: "login",
|
||||
element: <LoginPage />,
|
||||
},
|
||||
{
|
||||
path: "logout",
|
||||
element: <LogoutPage />,
|
||||
},
|
||||
{
|
||||
path: "search",
|
||||
element: <SearchPage />,
|
||||
@ -34,12 +19,7 @@ const RouteConfigs = [
|
||||
},
|
||||
{
|
||||
path: "problem/:id/submit",
|
||||
element: (
|
||||
<RequireAuth>
|
||||
<SubmitPage />
|
||||
</RequireAuth>
|
||||
),
|
||||
loader: ProblemLoader,
|
||||
element: <div>problem submit</div>,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -9,7 +9,7 @@ export default defineConfig({
|
||||
react(),
|
||||
sentryVitePlugin({
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
org: "0x7f",
|
||||
org: "ldcraft",
|
||||
project: "woj-ui",
|
||||
}),
|
||||
],
|
||||
|
Reference in New Issue
Block a user