Commit a513873f by asranov0003

feat: add news

parent ec458499
......@@ -7,7 +7,7 @@ import {
} from "react-icons/io5";
import { GrMenu } from "react-icons/gr";
import { Link, NavLink } from "react-router-dom";
import { MdLogout } from "react-icons/md";
import { MdLogout, MdNewspaper } from "react-icons/md";
import { RiHome3Line } from "react-icons/ri";
import { BsCreditCard } from "react-icons/bs";
import { useTranslation } from "react-i18next";
......@@ -168,6 +168,15 @@ const Header: React.FC = () => {
</NavLink>
<NavLink
to={"/news"}
className={({ isActive }) => (isActive ? "active" : "")}
onClick={() => setIsSidebarOpen(false)}
>
<MdNewspaper className="sidebard__content__navs__icon" />
<span>{t("navs.news")}</span>
</NavLink>
<NavLink
to={"/subscription"}
className={({ isActive }) => (isActive ? "active" : "")}
onClick={() => setIsSidebarOpen(false)}
......
......@@ -41,6 +41,7 @@
"navs": {
"home": "Home",
"notifications": "Notifications",
"news": "News",
"subscriptions": "Subscriptions",
"settings": "Settings",
"logout": "Logout"
......
......@@ -41,6 +41,7 @@
"navs": {
"home": "Главная",
"notifications": "Уведомления",
"news": "Новости",
"subscriptions": "Подписки",
"settings": "Настройки",
"logout": "Выйти"
......
.news {
padding-top: 60px;
}
.news__list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.news__item {
padding: 1rem;
background: var(--content-bg-color);
border-radius: 10px;
}
.news__item__head {
margin-bottom: 1rem;
font-weight: bold;
}
.news__item ul {
margin-left: 1.5rem;
}
\ No newline at end of file
import React, { useEffect } from "react";
import "./News.css";
import CLoading from "../../components/CLoading";
import {
useAppDispatch,
useAppSelector,
type RootState,
} from "../../stores/store";
import { fetchAllNews } from "../../stores/slices/newsSlice";
const News: React.FC = () => {
const { news, isNewsLoading } = useAppSelector(
(state: RootState) => state.news
);
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchAllNews());
}, [dispatch]);
return (
<div className="news wrapper">
{isNewsLoading && news.length === 0 && (
<div className="cloading__center">
<CLoading />
</div>
)}
<div className="news__list">
{news.map((item) => {
return (
<div className="news__item" key={item.id}>
<div
className="news__item__head"
dangerouslySetInnerHTML={{ __html: item.head }}
></div>
<div
className="news__item__text"
dangerouslySetInnerHTML={{ __html: item.text }}
/>
</div>
);
})}
</div>
</div>
);
};
export default News;
export { default } from "./News";
......@@ -29,6 +29,7 @@ import UsageLimits from "../pages/UsageLimits";
import Terms from "../pages/Auth/Terms";
import Privacy from "../pages/Auth/Privacy";
import RecoverPassword from "../pages/RecoverPassword";
import News from "../pages/News";
const Router: React.FC = () => {
const isAuthenticated = !!localStorage.getItem(`token-${TG_USER_ID}`);
......@@ -73,6 +74,10 @@ const Router: React.FC = () => {
element: <Notifications />,
},
{
path: "news",
element: <News />,
},
{
path: "subscription",
element: <Subscription />,
},
......
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { sendRpcRequest } from "../../services/apiClient";
import type { INews } from "../../types/news.types";
interface INewsState {
news: INews[];
isNewsLoading: boolean;
newsError: string | null;
}
const initialState: INewsState = {
news: [],
isNewsLoading: false,
newsError: null,
};
export const fetchAllNews = createAsyncThunk(
"news/fetchAllNews",
async (_, { rejectWithValue }) => {
try {
const response = await sendRpcRequest<{ list: INews[] }>("news.getAll");
return response.list;
} catch (error: unknown) {
if (typeof error === "object" && error !== null && "message" in error) {
return rejectWithValue(error.message);
}
return rejectWithValue("Unknown error occurred");
}
}
);
const newsSlice = createSlice({
name: "news",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchAllNews.pending, (state) => {
state.isNewsLoading = true;
})
.addCase(fetchAllNews.fulfilled, (state, action) => {
state.isNewsLoading = false;
state.news = action.payload;
state.newsError = null;
})
.addCase(fetchAllNews.rejected, (state, action) => {
state.isNewsLoading = false;
state.newsError = action.payload as string;
});
},
});
export default newsSlice.reducer;
......@@ -11,6 +11,7 @@ import messengerHistorySlice from "./slices/messengerHistorySlice";
import usageLimitSlice from "./slices/usageLimitSlice";
import dataSlice from "./slices/dataSlice";
import billingSlice from "./slices/billingSlice";
import newsSlice from "./slices/newsSlice";
import {
useDispatch,
useSelector,
......@@ -31,6 +32,7 @@ export const store = configureStore({
usageLimit: usageLimitSlice,
data: dataSlice,
billing: billingSlice,
news: newsSlice,
},
});
......
export interface INews {
id: string;
head: string;
text: string;
readed: boolean;
w_date: string;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment