Commit 276daafc authored by Ken's avatar Ken

feat: add news filter by category and abort signal for api

parent 6cf25b38
Pipeline #19395 canceled with stages
import { IPagination } from "pages/interface";
export interface BaseAPI {
signal?: AbortSignal;
}
export interface NewsAPI extends BaseAPI {
params: IPagination;
}
import { IPagination } from "pages/interface";
import axiosClient from "./axiosClient";
import { NewsAPI } from "./model";
const newsApi = {
getNews: (params: IPagination) => {
getNews: ({ params, signal }: NewsAPI) => {
const url = "/api/news";
return axiosClient.get(url, { params });
return axiosClient.get(url, { params, signal });
},
getNewsDetail: (id: string) => {
const url = `/api/news/${id}`;
......
......@@ -10,15 +10,14 @@ import { useAppDispatch, useAppSelector } from "app/hooks";
import { getCategories } from "./headerSlice";
import { handleLoading } from "app/globalSlice";
import { headerSelector } from "app/selectors";
import { useNavigate } from "react-router-dom";
import HeaderLogo from "./HeaderLogo";
import { handleSetFilter } from "pages/home/homePageSlice";
export default function Header() {
const dispatch = useAppDispatch();
const headerRef = useRef<HTMLElement>(null);
const [mobileOpen, setMobileOpen] = useState<boolean>(false);
const { data: categoryData } = useAppSelector(headerSelector);
const navigate = useNavigate();
useEffect(() => {
dispatch(handleLoading(true));
......@@ -89,7 +88,7 @@ export default function Header() {
>
{categoryData.map((item) => (
<Box
onClick={() => navigate(item.link)}
onClick={() => dispatch(handleSetFilter(item.link))}
component="li"
key={item.link}
sx={{
......
......@@ -20,7 +20,8 @@
width: 50%;
min-height: 288px;
img {
img,
video {
border: 0;
display: block;
}
......@@ -55,7 +56,8 @@
&-img {
width: 100%;
img {
img,
video {
width: 100%;
height: 100%;
object-fit: cover;
......
......@@ -4,6 +4,7 @@ import { INewspaper } from "pages/interface";
import React from "react";
import { useNavigate } from "react-router-dom";
import { useWindowSize } from "usehooks-ts";
import { checkUrlLink } from "utils/helpers/checkUrlLink";
type Props = {
data: INewspaper;
......@@ -32,7 +33,13 @@ const Newspaper = (props: Props) => {
onClick={() => navigate(`${PageUrl.NEWS_ROOT}/${id}`)}
>
<div className="newspaper-img">
{checkUrlLink(image) === "image" ? (
<img src={image} alt={title} />
) : (
<video autoPlay muted loop>
<source src={image} type="video/mp4" />
</video>
)}
</div>
<div className="newspaper-content">
......
......@@ -10,7 +10,10 @@ import {
Typography,
} from "@mui/material";
import { ICategory } from "components/interface";
import { handleSetFilter } from "pages/home/homePageSlice";
import { useAppDispatch } from "app/hooks";
import { useNavigate } from "react-router-dom";
import { PageUrl } from "configuration/enum";
type Props = {
handleDrawerToggle: () => void;
......@@ -20,8 +23,13 @@ type Props = {
const drawerWidth = 240;
const Sidenav = (props: Props) => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const { navItems, handleDrawerToggle, mobileOpen } = props;
const handleItemClick = (link: string) => {
navigate(PageUrl.HOMEPAGE);
dispatch(handleSetFilter(link));
};
return (
<Box component="nav">
......@@ -49,7 +57,7 @@ const Sidenav = (props: Props) => {
<ListItem
key={item.link}
disablePadding
onClick={() => navigate(item.link)}
onClick={() => handleItemClick(item.link)}
>
<ListItemButton sx={{ textAlign: "center" }}>
<ListItemText primary={item.label} />
......
......@@ -5,3 +5,7 @@ export const enum PageUrl {
NEWS_ROOT = "/newspaper",
ALL = "*",
}
export const enum PageSearchParams {
FILTER = "filterBy",
}
import React from "react";
const NewsByCategory = () => {
return <div>NewsByCategory</div>;
};
export default NewsByCategory;
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { NewsAPI } from "api/model";
import newsApi from "api/newsApi";
import { INewspaper, IPagination } from "pages/interface";
import { INewspaper } from "pages/interface";
const initialState: {
newsData: INewspaper[];
currentPage: number;
isMaxPage: boolean;
currentFilter: string;
} = {
newsData: [],
currentPage: 1,
isMaxPage: false,
currentFilter: "",
};
export const getNews = createAsyncThunk(
"home/news",
async (params: IPagination) => {
const res = await newsApi.getNews(params);
export const getNews = createAsyncThunk("home/news", async (data: NewsAPI) => {
const res = await newsApi.getNews(data);
return res;
}
);
});
const home = createSlice({
name: "home",
initialState,
reducers: {
handleResetNews: (state) => {
return initialState;
return {
...initialState,
currentFilter: state.currentFilter,
};
},
handlePage: (state, action) => {
state.currentPage += action.payload;
......@@ -33,6 +36,13 @@ const home = createSlice({
handleMaxPage: (state) => {
state.isMaxPage = true;
},
handleSetFilter: (state, action) => {
const filterValue = action.payload;
if (filterValue === "/") {
state.currentFilter = "";
} else state.currentFilter = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(getNews.fulfilled, (state, action: PayloadAction<any>) => {
......@@ -46,5 +56,6 @@ const home = createSlice({
});
const { reducer, actions } = home;
export const { handleResetNews, handlePage, handleMaxPage } = actions;
export const { handleResetNews, handlePage, handleMaxPage, handleSetFilter } =
actions;
export default reducer;
......@@ -15,7 +15,8 @@ import { CircularProgress } from "@mui/material";
const HomePage = () => {
const dispatch = useAppDispatch();
const { newsData, currentPage, isMaxPage } = useAppSelector(homeSelector);
const { newsData, currentPage, isMaxPage, currentFilter } =
useAppSelector(homeSelector);
const { ref, inView } = useInView({
threshold: 0.7,
initialInView: false,
......@@ -24,18 +25,23 @@ const HomePage = () => {
const [isLocalLoading, setIsLocalLoading] = useState<boolean>(false);
useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
try {
if (!isMaxPage) {
setIsLocalLoading(true);
setTimeout(() => {
const newsParams: IPagination = {
Filters: "",
Sorts: "",
Filters: currentFilter ? `categorylink==${currentFilter}` : "",
Sorts: "-CreatedAt",
Page: currentPage,
PageSize: 10,
};
const fetchData = async () => {
const res: any = await dispatch(getNews(newsParams)).unwrap();
const res: any = await dispatch(
getNews({ params: newsParams, signal })
).unwrap();
if (res.data.collection.length === 0) {
dispatch(handleMaxPage());
......@@ -51,8 +57,10 @@ const HomePage = () => {
setIsLocalLoading(false);
}
return () => controller.abort();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage]);
}, [currentPage, currentFilter]);
useEffect(() => {
if (inView && !isMaxPage) {
......@@ -64,7 +72,7 @@ const HomePage = () => {
useEffect(() => {
dispatch(handleResetNews());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [currentFilter]);
return (
<main className="homePage">
......
export const checkUrlLink = (url: string) => {
if (url.endsWith("mp4")) return "video";
return "image";
};
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