feat(tester): add Contest&Contests pages
This commit is contained in:
parent
b515ae3e67
commit
78e61899cd
22 changed files with 542 additions and 261 deletions
|
@ -7,7 +7,7 @@
|
|||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"gen": "openapi-generator-cli generate -g typescript-axios -i ./proto/user/v1/openapi.yaml -o ./proto/user/v1/api"
|
||||
"gen": "openapi-generator-cli generate -g typescript-axios -i ./proto/user/v1/openapi.yaml -o ./proto/user/v1/api && openapi-generator-cli generate -g typescript-axios -i ./proto/tester/v1/openapi.yaml -o ./proto/tester/v1/api"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/core": "^7.17.0",
|
||||
|
|
2
proto
2
proto
|
@ -1 +1 @@
|
|||
Subproject commit c1b7fd7a2d32678641ebd3acfe3d5b2eca5d0c72
|
||||
Subproject commit ea2a76c1f4001bba8405a5a447f085831dc5cf18
|
|
@ -1,27 +1 @@
|
|||
import React from 'react';
|
||||
import {Anchor, AppShell, AppShellHeader, AppShellMain} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import Link from "next/link";
|
||||
|
||||
type PageProps = {
|
||||
params: {
|
||||
contest_id: number
|
||||
}
|
||||
}
|
||||
|
||||
const Page = ({params}: PageProps) => {
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain>
|
||||
<Anchor component={Link} href="/contests/1/A">
|
||||
Просмотр {params.contest_id}
|
||||
</Anchor>
|
||||
</AppShellMain>
|
||||
</AppShell>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export {ContestPage as default, generateMetadata} from "@/plain-pages/contest"
|
||||
|
|
|
@ -1,108 +1 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
AppShell,
|
||||
AppShellAside,
|
||||
AppShellHeader,
|
||||
AppShellMain,
|
||||
Button,
|
||||
Checkbox,
|
||||
Pagination,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata = {
|
||||
title: 'Контесты',
|
||||
description: '',
|
||||
};
|
||||
|
||||
const contests = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Соревнование 1",
|
||||
startsAt: (new Date()).toISOString(),
|
||||
duration: "02:00",
|
||||
started: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Соревнование 2",
|
||||
startsAt: (new Date()).toISOString(),
|
||||
duration: "02:00",
|
||||
started: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Соревнование 3",
|
||||
startsAt: (new Date()).toISOString(),
|
||||
duration: "02:00",
|
||||
started: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Соревнование 4",
|
||||
startsAt: (new Date()).toISOString(),
|
||||
duration: "02:00",
|
||||
started: false
|
||||
}
|
||||
]
|
||||
|
||||
const Page = () => {
|
||||
const rows = contests.map((contest) => (
|
||||
<TableTr key={contest.id}>
|
||||
<TableTd>{contest.title}</TableTd>
|
||||
<TableTd>{contest.startsAt}</TableTd>
|
||||
<TableTd>{contest.duration}</TableTd>
|
||||
<TableTd>{<Button size="xs"
|
||||
disabled={!contest.started}
|
||||
component={Link}
|
||||
href={`/contests/${contest.id}`}
|
||||
>
|
||||
{contest.started ? "Войти в контест" : "Не началось"}
|
||||
</Button>}
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
));
|
||||
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Title>Контесты</Title>
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Название</TableTh>
|
||||
<TableTh>Начало</TableTh>
|
||||
<TableTh>Длительность</TableTh>
|
||||
<TableTh></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Pagination total={10} />
|
||||
</Stack>
|
||||
</AppShellMain>
|
||||
<AppShellAside withBorder={false} px="16" >
|
||||
<Stack pt="16">
|
||||
<TextInput placeholder="Поиск"/>
|
||||
<Checkbox label="Завершенные"/>
|
||||
</Stack>
|
||||
</AppShellAside>
|
||||
</AppShell>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export {ContestsPage as default, generateMetadata} from "@/plain-pages/contests"
|
||||
|
|
|
@ -1,6 +1 @@
|
|||
export const metadata = {
|
||||
title: 'Вход в аккаунт',
|
||||
description: '',
|
||||
};
|
||||
|
||||
export {LoginPage as default} from "@/plain-pages/login"
|
||||
export {LoginPage as default, generateMetadata} from "@/plain-pages/login"
|
||||
|
|
|
@ -99,7 +99,7 @@ const Header = () => {
|
|||
visibleFrom="xs">
|
||||
Пользователи
|
||||
</Anchor>
|
||||
<Anchor component={Link} href="/workshop" className={classes.link} underline="never"
|
||||
<Anchor component={Link} href="/problems" className={classes.link} underline="never"
|
||||
visibleFrom="xs">
|
||||
Мастерская
|
||||
</Anchor>
|
||||
|
|
1
src/plain-pages/contest/index.ts
Normal file
1
src/plain-pages/contest/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./page";
|
33
src/plain-pages/contest/page.tsx
Normal file
33
src/plain-pages/contest/page.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
"use server";
|
||||
|
||||
import {ClientPage} from "./ui";
|
||||
import {Metadata} from "next";
|
||||
import {GetContest} from "@/shared/api";
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ contest_id: number }>
|
||||
}
|
||||
|
||||
const generateMetadata = async (props: Props): Promise<Metadata> => {
|
||||
const contest_id = (await props.params).contest_id;
|
||||
|
||||
const response = await GetContest(contest_id);
|
||||
|
||||
return {
|
||||
title: response.contest.title,
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const Page = async (props: Props) => {
|
||||
const contest_id = (await props.params).contest_id;
|
||||
|
||||
const response = await GetContest(contest_id);
|
||||
|
||||
return (
|
||||
<ClientPage contest={response.contest} tasks={response.tasks}/>
|
||||
)
|
||||
}
|
||||
|
||||
export {Page as ContestPage, generateMetadata};
|
123
src/plain-pages/contest/ui.tsx
Normal file
123
src/plain-pages/contest/ui.tsx
Normal file
|
@ -0,0 +1,123 @@
|
|||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import {Header} from "@/components/header";
|
||||
import {Contest, GetContestResponseTasksInner} from "../../../proto/tester/v1/api";
|
||||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import {IconMail, IconTrash} from "@tabler/icons-react";
|
||||
import {DeleteTask} from "@/shared/api";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {useRouter} from "next/navigation";
|
||||
|
||||
type PageProps = {
|
||||
contest: Contest,
|
||||
tasks: Array<GetContestResponseTasksInner>
|
||||
}
|
||||
|
||||
const Page = (props: PageProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: DeleteTask,
|
||||
onSuccess: async () => {
|
||||
await router.refresh();
|
||||
},
|
||||
retry: false
|
||||
});
|
||||
|
||||
const rows = props.tasks.map((task) => (
|
||||
<TableTr key={task.task.id}>
|
||||
<TableTd>
|
||||
<Anchor component={Link}
|
||||
href={`/contests/${props.contest.id}/tasks/${task.task.id}`}
|
||||
c="var(--mantine-link-color)"
|
||||
underline="always"
|
||||
>
|
||||
{`${task.task.position + 1}. ${task.task.title}`}
|
||||
</Anchor>
|
||||
</TableTd>
|
||||
<TableTd>{task.best_solution.total_score}</TableTd>
|
||||
<TableTd>
|
||||
<Button component={Link} href={`/`} size="xs">
|
||||
Мои посылки
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<ActionIcon onClick={() => mutation.mutate(task.task.id)}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
|
||||
return (
|
||||
<Stack gap="xl">
|
||||
<header>
|
||||
<Header/>
|
||||
</header>
|
||||
<main>
|
||||
<Stack gap="xl" align="center">
|
||||
<Title order={1}>{props.contest.title}</Title>
|
||||
<Table horizontalSpacing="xl" align="center" w="fit-content">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Название</TableTh>
|
||||
<TableTh>Баллы</TableTh>
|
||||
<TableTh>
|
||||
<Center>
|
||||
<IconMail/>
|
||||
</Center>
|
||||
</TableTh>
|
||||
<TableTh>
|
||||
<Center>
|
||||
<IconTrash/>
|
||||
</Center>
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Stack>
|
||||
<Center>
|
||||
<Group>
|
||||
<Button size="md">
|
||||
Все посылки
|
||||
</Button>
|
||||
<Button size="md">
|
||||
Редактировать контест
|
||||
</Button>
|
||||
</Group>
|
||||
</Center>
|
||||
<Center>
|
||||
<Group>
|
||||
<Button size="md">
|
||||
Добавить пользователя
|
||||
</Button>
|
||||
<Button size="md">
|
||||
Добавить задачу
|
||||
</Button>
|
||||
</Group>
|
||||
</Center>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</main>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export {Page as ClientPage};
|
1
src/plain-pages/contests/index.ts
Normal file
1
src/plain-pages/contests/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./page";
|
28
src/plain-pages/contests/page.tsx
Normal file
28
src/plain-pages/contests/page.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
"use server";
|
||||
|
||||
import {ClientPage} from "./ui";
|
||||
import {Metadata} from "next";
|
||||
import {ListContests} from "@/shared/api";
|
||||
|
||||
const generateMetadata = async (): Promise<Metadata> => {
|
||||
return {
|
||||
title: 'Контесты',
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
type Props = {
|
||||
searchParams: Promise<{ page: number }>
|
||||
}
|
||||
|
||||
const Page = async (props: Props) => {
|
||||
const page = (await props.searchParams).page || 1;
|
||||
|
||||
const response = await ListContests(page, 10);
|
||||
|
||||
return (
|
||||
<ClientPage contests={response.contests} max_page={response.max_page} page={response.page}/>
|
||||
)
|
||||
}
|
||||
|
||||
export {Page as ContestsPage, generateMetadata};
|
144
src/plain-pages/contests/ui.tsx
Normal file
144
src/plain-pages/contests/ui.tsx
Normal file
|
@ -0,0 +1,144 @@
|
|||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
AppShell,
|
||||
AppShellAside,
|
||||
AppShellHeader,
|
||||
AppShellMain,
|
||||
Button,
|
||||
Checkbox,
|
||||
Pagination,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import Link from "next/link";
|
||||
import {ContestsListItem} from "../../../proto/tester/v1/api";
|
||||
|
||||
type Props = {
|
||||
contests: ContestsListItem[],
|
||||
page: number,
|
||||
max_page: number
|
||||
}
|
||||
|
||||
// const contests = [
|
||||
// {
|
||||
// id: 1,
|
||||
// title: "Соревнование 1",
|
||||
// startsAt: (new Date()).toISOString(),
|
||||
// duration: "02:00",
|
||||
// started: true
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// title: "Соревнование 2",
|
||||
// startsAt: (new Date()).toISOString(),
|
||||
// duration: "02:00",
|
||||
// started: false
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// title: "Соревнование 3",
|
||||
// startsAt: (new Date()).toISOString(),
|
||||
// duration: "02:00",
|
||||
// started: false
|
||||
// },
|
||||
// {
|
||||
// id: 4,
|
||||
// title: "Соревнование 4",
|
||||
// startsAt: (new Date()).toISOString(),
|
||||
// duration: "02:00",
|
||||
// started: false
|
||||
// }
|
||||
// ]
|
||||
|
||||
const Page = (props: Props) => {
|
||||
const rows = props.contests.map((contest) => (
|
||||
<TableTr key={contest.id}>
|
||||
<TableTd>{contest.title}</TableTd>
|
||||
<TableTd>
|
||||
<Button size="xs"
|
||||
component={Link}
|
||||
href={`/contests/${contest.id}`}
|
||||
>
|
||||
{"Войти в контест"}
|
||||
</Button>
|
||||
</TableTd>
|
||||
{/*<TableTd>{contest.startsAt}</TableTd>*/}
|
||||
{/*<TableTd>{contest.duration}</TableTd>*/}
|
||||
{/*<TableTd>{<Button size="xs"*/}
|
||||
{/* disabled={!contest.started}*/}
|
||||
{/* component={Link}*/}
|
||||
{/* href={`/contests/${contest.id}`}*/}
|
||||
{/*>*/}
|
||||
{/* {contest.started ? "Войти в контест" : "Не началось"}*/}
|
||||
{/*</Button>}*/}
|
||||
{/*</TableTd>*/}
|
||||
</TableTr>
|
||||
));
|
||||
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Title>Контесты</Title>
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Название</TableTh>
|
||||
{/*<TableTh>Начало</TableTh>*/}
|
||||
{/*<TableTh>Длительность</TableTh>*/}
|
||||
<TableTh></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Pagination total={props.max_page}
|
||||
value={props.page}
|
||||
getItemProps={(page) => ({
|
||||
component: Link,
|
||||
href: `/contests?page=${page}`,
|
||||
})}
|
||||
getControlProps={(control) => {
|
||||
if (control === 'next') {
|
||||
if (props.page === props.max_page) {
|
||||
return {component: Link, href: `/contests?page=${props.page}`};
|
||||
}
|
||||
|
||||
return {component: Link, href: `/contests?page=${+props.page + 1}`};
|
||||
}
|
||||
|
||||
if (control === 'previous') {
|
||||
if (props.page === 1) {
|
||||
return {component: Link, href: `/contests?page=${props.page}`};
|
||||
}
|
||||
return {component: Link, href: `/contests?page=${+props.page - 1}`};
|
||||
}
|
||||
|
||||
return {};
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</AppShellMain>
|
||||
<AppShellAside withBorder={false} px="16">
|
||||
<Stack pt="16">
|
||||
<TextInput placeholder="Поиск"/>
|
||||
<Checkbox label="Завершенные"/>
|
||||
</Stack>
|
||||
</AppShellAside>
|
||||
</AppShell>
|
||||
);
|
||||
};
|
||||
|
||||
export {Page as ClientPage};
|
|
@ -1 +1 @@
|
|||
export {Page as LoginPage} from "./page";
|
||||
export {LoginPage, generateMetadata} from "./page";
|
||||
|
|
|
@ -1,110 +1,19 @@
|
|||
"use client";
|
||||
"use server";
|
||||
|
||||
import {
|
||||
AppShell,
|
||||
AppShellHeader,
|
||||
AppShellMain,
|
||||
Button,
|
||||
Image,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import React from "react";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import NextImage from "next/image";
|
||||
import {Login} from "@/shared/api";
|
||||
import {ClientPage} from "./ui";
|
||||
import {Metadata} from "next";
|
||||
|
||||
const Page = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
username: "",
|
||||
password: ""
|
||||
},
|
||||
});
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: Login,
|
||||
onSuccess: async () => {
|
||||
await router.push("/")
|
||||
},
|
||||
onError: (error) => {
|
||||
form.clearErrors();
|
||||
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.response?.status === 404) {
|
||||
form.setFieldError("username", "Неверный юзернейм или пароль")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
form.setFieldError("username", "Что-то пошло не так. Попробуйте позже.")
|
||||
},
|
||||
retry: false
|
||||
});
|
||||
|
||||
const onSubmit = (event) => {
|
||||
event.preventDefault()
|
||||
mutation.mutate(form.getValues())
|
||||
}
|
||||
const generateMetadata = async (): Promise<Metadata> => {
|
||||
return {
|
||||
title: 'Вход в аккаунт',
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
const Page = async () => {
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain>
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Stack
|
||||
align="center"
|
||||
justify="center"
|
||||
w="fit-content"
|
||||
m="auto"
|
||||
mt="5%"
|
||||
p="md"
|
||||
style={{color: "var(--mantine-color-bright)"}}
|
||||
>
|
||||
<Image
|
||||
component={NextImage}
|
||||
src="/gate_logo.svg"
|
||||
alt="Gate logo"
|
||||
width="40"
|
||||
height="40"
|
||||
maw="40"
|
||||
mah="40"
|
||||
/>
|
||||
<Title>Войти в Gate</Title>
|
||||
<Stack w="100%" gap="0">
|
||||
<TextInput
|
||||
label="Username"
|
||||
placeholder="Username"
|
||||
key={form.key('username')}
|
||||
w="250"
|
||||
{...form.getInputProps('username')}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Пароль"
|
||||
placeholder="Пароль"
|
||||
w="250"
|
||||
key={form.key('password')}
|
||||
{...form.getInputProps('password')}
|
||||
/>
|
||||
</Stack>
|
||||
<Button type="submit" loading={mutation.isPending}>Войти</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</AppShellMain>
|
||||
</AppShell>
|
||||
);
|
||||
};
|
||||
<ClientPage/>
|
||||
)
|
||||
}
|
||||
|
||||
export {Page};
|
||||
export {Page as LoginPage, generateMetadata};
|
110
src/plain-pages/login/ui.tsx
Normal file
110
src/plain-pages/login/ui.tsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
AppShell,
|
||||
AppShellHeader,
|
||||
AppShellMain,
|
||||
Button,
|
||||
Image,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import React from "react";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import NextImage from "next/image";
|
||||
import {Login} from "@/shared/api";
|
||||
|
||||
const Page = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
username: "",
|
||||
password: ""
|
||||
},
|
||||
});
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: Login,
|
||||
onSuccess: async () => {
|
||||
await router.push("/")
|
||||
},
|
||||
onError: (error) => {
|
||||
form.clearErrors();
|
||||
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.response?.status === 404) {
|
||||
form.setFieldError("username", "Неверный юзернейм или пароль")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
form.setFieldError("username", "Что-то пошло не так. Попробуйте позже.")
|
||||
},
|
||||
retry: false
|
||||
});
|
||||
|
||||
const onSubmit = (event) => {
|
||||
event.preventDefault()
|
||||
mutation.mutate(form.getValues())
|
||||
}
|
||||
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain>
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Stack
|
||||
align="center"
|
||||
justify="center"
|
||||
w="fit-content"
|
||||
m="auto"
|
||||
mt="5%"
|
||||
p="md"
|
||||
style={{color: "var(--mantine-color-bright)"}}
|
||||
>
|
||||
<Image
|
||||
component={NextImage}
|
||||
src="/gate_logo.svg"
|
||||
alt="Gate logo"
|
||||
width="40"
|
||||
height="40"
|
||||
maw="40"
|
||||
mah="40"
|
||||
/>
|
||||
<Title>Войти в Gate</Title>
|
||||
<Stack w="100%" gap="0">
|
||||
<TextInput
|
||||
label="Username"
|
||||
placeholder="Username"
|
||||
key={form.key('username')}
|
||||
w="250"
|
||||
{...form.getInputProps('username')}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Пароль"
|
||||
placeholder="Пароль"
|
||||
w="250"
|
||||
key={form.key('password')}
|
||||
{...form.getInputProps('password')}
|
||||
/>
|
||||
</Stack>
|
||||
<Button type="submit" loading={mutation.isPending}>Войти</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</AppShellMain>
|
||||
</AppShell>
|
||||
);
|
||||
};
|
||||
|
||||
export {Page as ClientPage};
|
|
@ -1,9 +0,0 @@
|
|||
import {Configuration, DefaultApi} from "../../../proto/user/v1/api";
|
||||
|
||||
const configuration = new Configuration({
|
||||
basePath: "http://localhost:60005",
|
||||
});
|
||||
|
||||
const authApi = new DefaultApi(configuration);
|
||||
|
||||
export {authApi};
|
|
@ -1 +1,2 @@
|
|||
export * from "./ms-auth";
|
||||
export * from "./ms-auth";
|
||||
export * from "./ms-tester";
|
|
@ -3,7 +3,13 @@
|
|||
import {AxiosRequestConfig} from "axios";
|
||||
import {decode} from "jsonwebtoken";
|
||||
import {cookies} from "next/headers";
|
||||
import {authApi} from "./config";
|
||||
import {Configuration, DefaultApi} from "../../../proto/user/v1/api";
|
||||
|
||||
const configuration = new Configuration({
|
||||
basePath: "http://localhost:60005",
|
||||
});
|
||||
|
||||
const authApi = new DefaultApi(configuration);
|
||||
|
||||
export type Credentials = {
|
||||
username: string,
|
||||
|
|
72
src/shared/api/ms-tester.ts
Normal file
72
src/shared/api/ms-tester.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
"use server";
|
||||
|
||||
import {Configuration, DefaultApi} from "../../../proto/tester/v1/api";
|
||||
import {cookies} from "next/headers";
|
||||
import {AxiosRequestConfig} from "axios";
|
||||
|
||||
const configuration = new Configuration({
|
||||
basePath: "http://localhost:60060",
|
||||
});
|
||||
|
||||
const testerApi = new DefaultApi(configuration);
|
||||
|
||||
const CookieName: any = "SESSIONID";
|
||||
|
||||
export const ListContests = async (page: number, pageSize: number) => {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const session = cookieStore.get(CookieName);
|
||||
|
||||
if (session === undefined) {
|
||||
throw new Error("Session id not found");
|
||||
}
|
||||
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Authorization': "Bearer " + session.value
|
||||
}
|
||||
};
|
||||
|
||||
const response = await testerApi.listContests(page, pageSize, options);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const GetContest = async (id: number) => {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const session = cookieStore.get(CookieName);
|
||||
|
||||
if (session === undefined) {
|
||||
throw new Error("Session id not found");
|
||||
}
|
||||
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Authorization': "Bearer " + session.value
|
||||
}
|
||||
}
|
||||
|
||||
const response = await testerApi.getContest(id, options);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const DeleteTask = async (taskId: number) => {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const session = cookieStore.get(CookieName);
|
||||
|
||||
if (session === undefined) {
|
||||
throw new Error("Session id not found");
|
||||
}
|
||||
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Authorization': "Bearer " + session.value
|
||||
}
|
||||
};
|
||||
|
||||
const response = await testerApi.deleteTask(taskId, options);
|
||||
return response.data;
|
||||
}
|
Loading…
Add table
Reference in a new issue