Skip to content

Commit

Permalink
Merge pull request #209 from Studio-Yandex-Practicum/api-140-insert-t…
Browse files Browse the repository at this point in the history
…o-BlogBlock

#140-api-inserted-to-BlogBlock
  • Loading branch information
JuliaAvramenko authored Feb 5, 2024
2 parents 4743ff9 + 8d4e6bb commit 5400102
Show file tree
Hide file tree
Showing 17 changed files with 173 additions and 33 deletions.
3 changes: 3 additions & 0 deletions src/app/providers/StoreProvider/config/StateSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { BrandSchema } from '@/widgets/BrandBlock/types/types'
import { ApiInstance } from '@/shared/api/api'
import { ShopNewsSchema } from '@/widgets/NewsBlock/model/types/types'
import { StoreReviewsSchema } from '@/widgets/ReviewsBlock/model/types/types'
import { IBlogPostsSchema } from '@/widgets/BlogBlock/model/types/types'
import { CoreBaseFooterSchema } from '@/widgets/Footer/model/types/types'


export interface StateSchema {
login: LoginSchema
storeReviews: StoreReviewsSchema
Expand All @@ -15,6 +17,7 @@ export interface StateSchema {
brand: BrandSchema
searchResult: SearchResultSchema
shopNews: ShopNewsSchema
blogPosts: IBlogPostsSchema
}

export interface ThunkExtraArg {
Expand Down
4 changes: 3 additions & 1 deletion src/app/providers/StoreProvider/config/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import searchProductSlice from '@/features/SearchProduct/slice/searchProductSlic
import { storeReviewsReducer } from '@/widgets/ReviewsBlock/model/slice/reviewsSlice'
import footerSlice from '@/widgets/Footer/model/slice/footerSlice'
import { shopNewsReducer } from '@/widgets/NewsBlock/model/slice/shopNewsSlice'
import { blogPostsReducer } from '@/widgets/BlogBlock/model/slice/blogPostsSlice'

export type RootState = StateSchema

Expand All @@ -18,7 +19,8 @@ const rootReducer: ReducersMapObject<RootState> = {
brand: brandSlice,
searchResult: searchProductSlice,
storeReviews: storeReviewsReducer,
shopNews: shopNewsReducer
shopNews: shopNewsReducer,
blogPosts: blogPostsReducer
}

export function createReduxStore(initialState: RootState) {
Expand Down
16 changes: 10 additions & 6 deletions src/entities/BlogCard/BlogCard.module.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@use '@/app/styles/index' as var;

.card {
max-width: 340px;
min-width: 340px;
position: relative;
transition: transform 0.3s ease-in-out;
Expand All @@ -14,12 +15,6 @@
transition: transform 0.3s ease-in-out;
}

img {
border-radius: 6px;
transition: transform 0.3s ease-in-out;
scroll-snap-align: start;
}

.heading
{
font-size: #{'min(max(14px, 1.2vw), 16px)'};
Expand All @@ -32,5 +27,14 @@
span {
color: var.$body-color-light-grey;
}

.img {
height: 462px;
width: 100%;
border-radius: 6px;
transition: transform 0.3s ease-in-out;
scroll-snap-align: start;
object-fit: cover;
}

}
11 changes: 4 additions & 7 deletions src/entities/BlogCard/BlogCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@ type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
card: {
id: 1,
src: Img1,
alt: 'Покупай и не жди. До -50% на весь электротранспорт!',
title: 'Покупай и не жди. До -50% на весь электротранспорт!',
date: '8 Мая, 2022'
}
id: 1,
image: Img1,
title: 'Покупай и не жди. До -50% на весь электротранспорт!',
date: '2022-07-8'
}
}
33 changes: 24 additions & 9 deletions src/entities/BlogCard/BlogCard.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
import { FC } from 'react'
import { TCard } from '@/models/CardModel'
import { FC, useMemo } from 'react'
import styles from './BlogCard.module.scss'
import Link from '@/shared/ui/Link/Link'
import Heading, { HeadingType } from '@/shared/ui/Heading/Heading'
import NoImage from '@/assets/icons/image-not-found-icon.svg'

export type Props = {
card: TCard
type Props = {
id: number
image: string
title: string
date: string
}

/**
* Карточка из блока блог
* @param {TCard} card - параметры карточки из блога
* @param {Props} card - параметры карточки из блога
*/

const BlogCard: FC<Props> = ({ card }) => {
const BlogCard: FC<Props> = ({ image, date, title }) => {
const newDate = useMemo(() => {
const _parsedDate = new Date(date)
const year = _parsedDate.getFullYear()
const formatter = new Intl.DateTimeFormat('ru', { month: 'long', day: 'numeric' }).format(_parsedDate)

return `${formatter}, ${year}`
}, [date])

return (
<Link to={''} className={styles.card}>
<img src={card.src} alt={card.alt} draggable="false" />
{image ? (
<img src={image} alt={'новость'} draggable="false" className={styles.img} />
) : (
<NoImage className={styles.img} />
)}
<Heading type={HeadingType.NORMAL} className={styles.heading}>
{card.title}
{title}
</Heading>
<span>{card.date}</span>
<span>{newDate}</span>
</Link>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/entities/NewsCard/NewsCard.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@
border-radius: 6px;
transition: transform 0.3s ease-in-out;
scroll-snap-align: start;
object-fit: cover;
}
}
4 changes: 2 additions & 2 deletions src/entities/NewsCard/NewsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Link from '@/shared/ui/Link/Link'
import Heading, { HeadingType } from '@/shared/ui/Heading/Heading'
import NoImage from '@/assets/icons/image-not-found-icon.svg'

export type Props = {
type Props = {
id: number
image: string
date: string
Expand All @@ -13,7 +13,7 @@ export type Props = {

/**
* Карточка из блока группы новостей
* @param {TCard} card - параметры карточки из группы новостей
* @param {Props} card - параметры карточки из группы новостей
*/

const NewsCard: FC<Props> = ({ image, date, title }) => {
Expand Down
3 changes: 2 additions & 1 deletion src/shared/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ export enum ApiRoutes {
SEARCH = 'search',
STORE_REVIEWS = 'store-reviews',
CATEGORIES = 'catalogue/category',
SHOP_NEWS = 'shopnews',
BLOG_POSTS = 'shopblog/posts',
CORE_BASE = 'core/base',
SHOP_NEWS = 'shopnews'
}

export enum ApiErrorTypes {
Expand Down
8 changes: 8 additions & 0 deletions src/shared/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ export const MAX_PRODUCTS_NUMBER: number = 99

//for BrandBlock component
export const BRANDS_FOR_MAIN_NUMBER: number = 6

// Actions
export const ACTION_GET_SHOP_NEWS = 'get-shop-news'
export const ACTION_GET_BLOG_POSTS = 'get-blog-posts'

// Reducers
export const REDUCER_SHOP_NEWS = 'shopNews'
export const REDUCER_BLOG_POSTS = 'shopBlogPosts'
5 changes: 5 additions & 0 deletions src/widgets/BlogBlock/model/selectors/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { StateSchema } from '@/app/providers/StoreProvider'

export const getBlogPostsSelector = (state: StateSchema) => {
return state.blogPosts.posts
}
23 changes: 23 additions & 0 deletions src/widgets/BlogBlock/model/services/getBlogPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema'
import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types'
import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify'
import { IBlogPostData } from '../types/types'
import { ACTION_GET_BLOG_POSTS } from '@/shared/constants/constants'

// export const getStoreReviews = createAsyncThunk<StoreReviewData[], void, ThunkConfig<ApiError>>(
export const getBlogPosts = createAsyncThunk<IBlogPostData[], void, ThunkConfig<ApiError>>(
//void1- выходные данные, void2- входные данные , thunkConfig- тип store
ACTION_GET_BLOG_POSTS, // action type, первый аргумент
async (_, thunkAPI) => {
// второй аргумент- асинхронная функция , кот вызовет dispatch в компоненте
const { rejectWithValue, extra } = thunkAPI
try {
const { data } = await extra.api.get(ApiRoutes.BLOG_POSTS)

return data.results
} catch (error) {
return rejectWithValue(apiErrorIdentify(error, ApiErrorTypes.DATA_EMPTY_ERROR))
}
}
)
35 changes: 35 additions & 0 deletions src/widgets/BlogBlock/model/slice/blogPostsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createSlice } from '@reduxjs/toolkit'
import { IBlogPostsSchema } from '../types/types'
import { getBlogPosts } from '../services/getBlogPosts'
import { REDUCER_BLOG_POSTS } from '@/shared/constants/constants'
import { rejectedPayloadHandle } from '@/shared/api/rejectedPayloadHandle'

const initialState: IBlogPostsSchema = {
isLoading: false,
posts: []
}

export const blogPostsSlice = createSlice({
name: REDUCER_BLOG_POSTS,
initialState,
reducers: {
errorReset: state => {
state.error = undefined
}
},
extraReducers: builder => {
builder
.addCase(getBlogPosts.pending, state => {
state.isLoading = true
})
.addCase(getBlogPosts.fulfilled, (state, { payload }) => {
state.isLoading = false
state.posts = payload
})
.addCase(getBlogPosts.rejected, (state, { payload }) => {
state.isLoading = false
state.error = rejectedPayloadHandle(payload)
})
}
})
export const { actions: blogPostsActions, reducer: blogPostsReducer } = blogPostsSlice
33 changes: 33 additions & 0 deletions src/widgets/BlogBlock/model/types/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export interface IPaginatedResponse<T> {
count: number
previous: string
next: string
results: T[]
}
export interface IBlogTagData {
name: string
}
export interface TBlogCategoryLight {
title: string
slug: string
}
export interface IBlogPostData {
id: number
title: string
text: string
pub_date: string
author: string
image: string
category: TBlogCategoryLight
tags: IBlogTagData[]
views: number
slug: string
meta_title: string
meta_description: string
}

export interface IBlogPostsSchema {
isLoading: boolean
posts: IBlogPostData[]
error?: string | string[]
}
3 changes: 2 additions & 1 deletion src/widgets/BlogBlock/ui/BlogBlock.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react'
import styles from './BlogBlock.module.scss'
import { FC } from 'react'
import styles from './BlogBlock.module.scss'

import BlogBlock from './BlogBlock'

const StorybookWrapper: FC = () => {
Expand Down
18 changes: 14 additions & 4 deletions src/widgets/BlogBlock/ui/BlogBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { FC } from 'react'
import { FC, useEffect } from 'react'
import { getBlogPosts } from '../model/services/getBlogPosts'
import { useAppDispatch } from '@/shared/libs/hooks/store'
import { getBlogPostsSelector } from '../model/selectors/selectors'
import { useSelector } from 'react-redux'
import IconLink from '@/assets/icons/IconLink'
import Heading, { HeadingType } from '@/shared/ui/Heading/Heading'
import Link from '@/shared/ui/Link/Link'
import styles from './BlogBlock.module.scss'
import Scroll from '@/shared/ui/Scroll/Scroll'
import { blogData } from '@/mockData/blogData'
import BlogCard from '@/entities/BlogCard/BlogCard'

/**
* Блок группы карточек блога
*/

const BlogBlock: FC = () => {
const dispatch = useAppDispatch()
const posts = useSelector(getBlogPostsSelector)

useEffect(() => {
dispatch(getBlogPosts())
}, [])

return (
<section className={styles.wrapper}>
<article>
Expand All @@ -22,8 +32,8 @@ const BlogBlock: FC = () => {
</Link>
</article>
<Scroll>
{blogData.map(item => (
<BlogCard key={item.id} card={item} />
{posts.map(item => (
<BlogCard key={item.id} id={item.id} image={item.image} date={item.pub_date} title={item.title} />
))}
</Scroll>
</section>
Expand Down
3 changes: 2 additions & 1 deletion src/widgets/NewsBlock/model/services/getShopNews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { ThunkConfig } from '@/app/providers/StoreProvider/config/StateSchema'
import { ApiError, ApiErrorTypes, ApiRoutes } from '@/shared/api/types'
import { apiErrorIdentify } from '@/shared/api/apiErrorIdentify'
import { ShopNewsData } from '../types/types'
import { ACTION_GET_SHOP_NEWS } from '@/shared/constants/constants'

// export const getStoreReviews = createAsyncThunk<StoreReviewData[], void, ThunkConfig<ApiError>>(
export const getShopNews = createAsyncThunk<ShopNewsData[], void, ThunkConfig<ApiError>>(
//void1- выходные данные, void2- входные данные , thunkConfig- тип store
'shop-news', // action type, первый аргумент
ACTION_GET_SHOP_NEWS, // action type, первый аргумент
async (_, thunkAPI) => {
// второй аргумент- асинхронная функция , кот вызовет dispatch в компоненте
const { rejectWithValue, extra } = thunkAPI
Expand Down
3 changes: 2 additions & 1 deletion src/widgets/NewsBlock/model/slice/shopNewsSlice.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { createSlice } from '@reduxjs/toolkit'
import { getShopNews } from '../services/getShopNews'
import { ShopNewsSchema } from '../types/types'
import { REDUCER_SHOP_NEWS } from '@/shared/constants/constants'

const initialState: ShopNewsSchema = {
isLoading: false,
news: []
}

export const shopNewsSlice = createSlice({
name: 'shopNews',
name: REDUCER_SHOP_NEWS,
initialState,
reducers: {},
extraReducers: builder => {
Expand Down

0 comments on commit 5400102

Please sign in to comment.