From d601659289fc61b6b8656e6ced6bcaa53227ce05 Mon Sep 17 00:00:00 2001 From: ariari04 Date: Thu, 5 Sep 2024 12:08:57 +0600 Subject: [PATCH] Add news page --- next.config.mjs | 4 +-- src/app/[locale]/news/page.tsx | 22 ++++++++++++ src/entities/NewsCard.tsx | 50 +++++++++++++++++++++++++++ src/shared/types/news-type.ts | 13 +++++++ src/widgets/NewsList/NewsList.tsx | 56 +++++++++++++++++++++++++++++++ src/widgets/NewsList/newsStore.ts | 38 +++++++++++++++++++++ 6 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 src/app/[locale]/news/page.tsx create mode 100644 src/entities/NewsCard.tsx create mode 100644 src/shared/types/news-type.ts create mode 100644 src/widgets/NewsList/NewsList.tsx create mode 100644 src/widgets/NewsList/newsStore.ts diff --git a/next.config.mjs b/next.config.mjs index 9e682c8..0104334 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -6,8 +6,8 @@ const nextConfig = { images: { remotePatterns: [ { - protocol: "https", - hostname: "**", + protocol: "http", + hostname: "***", }, ], }, diff --git a/src/app/[locale]/news/page.tsx b/src/app/[locale]/news/page.tsx new file mode 100644 index 0000000..6078f56 --- /dev/null +++ b/src/app/[locale]/news/page.tsx @@ -0,0 +1,22 @@ +import { Container, Title } from "@/shared/ui"; +import NewsList from "@/widgets/NewsList/NewsList"; +import React from "react"; + +const News = ({ + searchParams, +}: { + searchParams: { + ["страница-новостей"]: string; + }; +}) => { + return ( +
+ + + <NewsList searchParams={searchParams} /> + </Container> + </div> + ); +}; + +export default News; diff --git a/src/entities/NewsCard.tsx b/src/entities/NewsCard.tsx new file mode 100644 index 0000000..b8ac8f7 --- /dev/null +++ b/src/entities/NewsCard.tsx @@ -0,0 +1,50 @@ +import { Title } from "@/shared/ui"; +import Image, { StaticImageData } from "next/image"; + +interface INewsCard { + id: number; + image: StaticImageData | string; + title: string; + content: string; + date: string; +} +const NewsCard = ({ id, image, title, content, date }: INewsCard) => { + const sliceTitle = (title: string) => { + if (title.length > 35) { + return `${title.slice(0, 35)}...`; + } + + return title; + }; + const sliceDescription = (content: string) => { + if (content.length > 65) { + return `${content.slice(0, 65)}...`; + } + + return content; + }; + const sliceDate = (date: string) => { + return `${date.slice(8, 10)}/${date.slice(5, 7)}/${date.slice(0, 4)}`; + }; + return ( + <div className="w-[393px] h-[508px] bg-white shadow-sm p-6 flex flex-col"> + <Image + src={image} + alt="Card image" + width={345} + height={240} + className="mb-[32px]" + /> + <Title + text={sliceTitle(title)} + className="text-[24px] font-semibold mb-[12px]" + /> + <p className="leading-6 text-gray-600 mb-[46px]"> + {sliceDescription(content)} + </p> + <p className="text-[14px] text-gray-600">{sliceDate(date)}</p> + </div> + ); +}; + +export default NewsCard; diff --git a/src/shared/types/news-type.ts b/src/shared/types/news-type.ts new file mode 100644 index 0000000..ca44e80 --- /dev/null +++ b/src/shared/types/news-type.ts @@ -0,0 +1,13 @@ +import { IList } from "./list-type"; + +export interface INews { + id: number; + title: string; + content: string; + image: string; + created_at: string; +} + +export interface INewsList extends IList { + results: INews[]; +} diff --git a/src/widgets/NewsList/NewsList.tsx b/src/widgets/NewsList/NewsList.tsx new file mode 100644 index 0000000..a51a633 --- /dev/null +++ b/src/widgets/NewsList/NewsList.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useNewsStore } from "./newsStore"; +import NewsCard from "@/entities/NewsCard"; +import Pagination from "@/features/Pagination/Pagination"; + +interface INewsListProps { + searchParams: { + ["страница-новостей"]: string; + }; +} + +const NewsList: React.FC<INewsListProps> = ({ + searchParams, +}: INewsListProps) => { + const [activePage, setActivePage] = useState<number>( + +searchParams["страница-новостей"] || 1 + ); + const { data: news, getNews, isLoading, error } = useNewsStore(); + + useEffect(() => { + getNews(activePage); + }, []); + return ( + <div className="flex flex-col gap-4"> + <ul className="flex gap-[30px] flex-wrap mb-[96px]"> + {news.results.map((news) => ( + <li key={news.id}> + <NewsCard + id={news.id} + title={news.title} + content={news.content} + image={news.image} + date={news.created_at} + /> + </li> + ))} + </ul> + <hr></hr> + <div className="mx-auto mb-[96px]"> + <Pagination + activePage={activePage} + setActivePage={setActivePage} + prev={news.previous} + next={news.next} + count={news.count as number} + current_count={news.results.length} + limit={20} + /> + </div> + </div> + ); +}; + +export default NewsList; diff --git a/src/widgets/NewsList/newsStore.ts b/src/widgets/NewsList/newsStore.ts new file mode 100644 index 0000000..c39828f --- /dev/null +++ b/src/widgets/NewsList/newsStore.ts @@ -0,0 +1,38 @@ +import { apiInstance } from "@/shared/config/apiConfig"; +import { IFetch } from "@/shared/types/fetch-type"; +import { INewsList } from "@/shared/types/news-type"; +import { AxiosError } from "axios"; +import { create } from "zustand"; + +interface useNewsStore extends IFetch { + data: INewsList; + getNews: (page: number) => void; +} + +export const useNewsStore = create<useNewsStore>((set) => ({ + data: { + count: 0, + previous: null, + next: null, + results: [], + }, + error: "", + isLoading: false, + getNews: async (page: number) => { + try { + set({ isLoading: true }); + + const res = await apiInstance.get(`/news/`); + + set({ data: res.data }); + } catch (error: unknown) { + if (error instanceof AxiosError) { + set({ error: error.message }); + } else { + set({ error: "An error ocured" }); + } + } finally { + set({ isLoading: false }); + } + }, +}));