news & things

This commit is contained in:
2024-04-13 14:37:30 +03:00
parent 8904d3c2b6
commit 89f59dabb1
17 changed files with 334 additions and 5 deletions

View File

@ -19,6 +19,7 @@ import { useNavigate } from "react-router-dom";
const AuthModal = (props: {
open: boolean;
setOpen: (arg0: boolean) => void;
setDrawerOpen: (arg0: boolean) => void;
}) => {
const navigate = useNavigate();
const messageApi = useContext(MessageContext);
@ -58,6 +59,7 @@ const AuthModal = (props: {
.then(() => refetch())
.then(() => props.setOpen(false))
.then(() => navigate("/dashboard"))
.then(() => props.setDrawerOpen(false))
.catch(() => messageApi.error(tr("Login failed!")));
};

View File

@ -3,6 +3,7 @@ import {
GlobalOutlined,
LogoutOutlined,
MenuOutlined,
PicCenterOutlined,
SettingOutlined,
UserOutlined,
} from "@ant-design/icons";
@ -100,6 +101,11 @@ const HeaderComponent = () => {
icon: <DesktopOutlined />,
disabled: !user,
},
{
label: <Link to="/news">{tr("News")}</Link>,
key: "news",
icon: <PicCenterOutlined />,
},
{
label: tr("Language"),
key: "language",
@ -111,7 +117,7 @@ const HeaderComponent = () => {
label: user ? user.username : tr("Log in"),
key: "login",
icon: <UserOutlined />,
onClick: () => setAuthModalOpen(true),
onClick: () => !user && setAuthModalOpen(true),
...(user ? { children: userMenuItems } : {}),
},
];
@ -139,6 +145,13 @@ const HeaderComponent = () => {
key: "dashboard",
icon: <DesktopOutlined />,
disabled: !user,
onClick: () => setDrawerOpen(false),
},
{
label: <Link to="/news">{tr("News")}</Link>,
key: "news",
icon: <PicCenterOutlined />,
onClick: () => setDrawerOpen(false),
},
{
label: tr("Language"),
@ -151,14 +164,18 @@ const HeaderComponent = () => {
label: user ? user.username : tr("Log in"),
key: "login",
icon: <UserOutlined />,
onClick: () => setAuthModalOpen(true),
onClick: () => !user && setAuthModalOpen(true),
...(user ? { children: userMenuItems } : {}),
},
];
return (
<>
<AuthModal open={authModalOpen} setOpen={setAuthModalOpen} />
<AuthModal
open={authModalOpen}
setOpen={setAuthModalOpen}
setDrawerOpen={setDrawerOpen}
/>
<Header className="header">
<Menu
theme="dark"

View File

@ -0,0 +1,73 @@
import React, { useContext } from "react";
import "../styles.css";
import { Button, Form, Input, Spin } from "antd";
import Title from "antd/es/typography/Title";
import tr from "../../config/translation";
import { MessageContext } from "../../App";
import { useNavigate } from "react-router-dom";
import { PlusCircleOutlined } from "@ant-design/icons";
import { CreateNewsRequest, useCreateNewsMutation } from "../../slice/NewsApi";
const CreateNewsCard = (): JSX.Element => {
const messageApi = useContext(MessageContext);
const navigate = useNavigate();
const [form] = Form.useForm();
const [createNews, { isLoading }] = useCreateNewsMutation();
const submit = (formData: CreateNewsRequest) => {
createNews(formData)
.unwrap()
.then(() => navigate(`/news`))
.then(() => messageApi.success(tr("News created")))
.catch(() => messageApi.error(tr("Failed to create news")));
};
return (
<div className="card">
<Spin spinning={isLoading}>
<Title level={2}>{tr("New news")}</Title>
<Form
form={form}
layout="vertical"
requiredMark={false}
onFinish={(formData: CreateNewsRequest) => submit(formData)}
>
<Form.Item
name={"title"}
label={tr("Title")}
rules={[
{
required: true,
message: tr("Please input news title!"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
name={"content"}
label={tr("Content")}
rules={[
{
required: true,
message: tr("Please input news content!"),
},
]}
>
<Input />
</Form.Item>
<Button
style={{ width: "100%" }}
icon={<PlusCircleOutlined />}
type="primary"
onClick={() => form.submit()}
>
{tr("Publish")}
</Button>
</Form>
</Spin>
</div>
);
};
export default CreateNewsCard;

View File

@ -0,0 +1,40 @@
import React from "react";
import "../styles.css";
import { Card, Spin } from "antd";
import { News, useGetNewsQuery } from "../../slice/NewsApi";
import { ClockCircleOutlined } from "@ant-design/icons";
const formatTime = (s: string) => {
const d = new Date(s);
return d.toLocaleString();
};
const NewsListCard = (): JSX.Element => {
const { data, isLoading } = useGetNewsQuery({});
return (
<div className="card">
<Spin spinning={isLoading}>
{data &&
data.map((news: News) => (
<Card
title={news.title}
key={news.id}
style={{ width: "100%", marginBottom: "1rem" }}
bordered={false}
>
<p style={{ textAlign: "left", color: "white" }}>
{news.content}
</p>
<br />
<div className="news-footer">
<ClockCircleOutlined />
<br />
<p style={{ marginLeft: "1rem" }}>{formatTime(news.created)}</p>
</div>
</Card>
))}
</Spin>
</div>
);
};
export default NewsListCard;

View File

@ -91,3 +91,9 @@
transform: rotate(0.05turn);
transition-duration: 0.2s;
}
.news-footer {
color: grey;
display: flex;
flex-flow: row;
}

View File

@ -7,6 +7,7 @@ import {
import { setupListeners } from "@reduxjs/toolkit/query";
import { AuthApi, User } from "../slice/AuthApi";
import { QueueApi } from "../slice/QueueApi";
import { NewsApi } from "../slice/NewsApi";
export type AuthDataType = {
token: string | null;
@ -45,6 +46,7 @@ export const store = configureStore({
// Add the generated reducer as a specific top-level slice
[AuthApi.reducerPath]: AuthApi.reducer,
[QueueApi.reducerPath]: QueueApi.reducer,
[NewsApi.reducerPath]: NewsApi.reducer,
auth: createReducer(initialAuthDataState, (builder) => {
builder.addCase(updateToken, (state, action) => {
state.token = action.payload;
@ -85,7 +87,8 @@ export const store = configureStore({
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.concat(AuthApi.middleware)
.concat(QueueApi.middleware),
.concat(QueueApi.middleware)
.concat(NewsApi.middleware),
});
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors

View File

@ -94,5 +94,8 @@
},
"Failed to create queue": {
"ru": "Не удалось создать очередь"
},
"News": {
"ru": "Новости"
}
}

View File

@ -6,6 +6,8 @@ import DashboardPage from "./DashboardPage";
import PropTypes from "prop-types";
import NotFoundPage from "./NotFoundPage";
import NewQueuePage from "./NewQueuePage";
import NewsPage from "./NewsPage";
import CreateNewsPage from "./CreateNewsPage";
const AppRoutes = ({ children }: { children: ReactNode }) => {
store.dispatch(getLocalToken());
@ -18,6 +20,8 @@ const AppRoutes = ({ children }: { children: ReactNode }) => {
<Route path="/" element={<MainPage />} />
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/dashboard/new" element={<NewQueuePage />} />
<Route path="/news" element={<NewsPage />} />
<Route path="/news/new" element={<CreateNewsPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>

View File

@ -0,0 +1,17 @@
import React from "react";
import "./styles.css";
import { useSelector } from "react-redux";
import { StorePrototype } from "../config/store";
import CreateNewsCard from "../components/news/CreateNewsCard";
import NotFoundPage from "./NotFoundPage";
const CreateNewsPage = () => {
const user = useSelector((state: StorePrototype) => state.auth.user);
return user && user.username === "admin" ? (
<CreateNewsCard />
) : (
<NotFoundPage />
);
};
export default CreateNewsPage;

View File

@ -0,0 +1,9 @@
import React from "react";
import "./styles.css";
import NewsListCard from "../components/news/NewsListCard";
const NewsPage = () => {
return <NewsListCard />;
};
export default NewsPage;

View File

@ -0,0 +1,43 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { baseUrl } from "../config/baseUrl";
import { RootState } from "../config/store";
export type CreateNewsRequest = {
title: string;
content: string | null;
};
export type News = {
id: string;
title: string;
content: string;
created: string;
};
export const NewsApi = createApi({
reducerPath: "NewsApi",
baseQuery: fetchBaseQuery({
baseUrl: `${baseUrl}/news`,
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.token;
if (token) {
headers.set("authorization", `Bearer ${token}`);
}
return headers;
},
}),
endpoints: (builder) => ({
getNews: builder.query({
query: () => "/",
}),
createNews: builder.mutation({
query: (data: CreateNewsRequest) => ({
url: "/",
method: "POST",
body: data,
}),
}),
}),
});
export const { useGetNewsQuery, useCreateNewsMutation } = NewsApi;