(feat): BlogItem reducer
This commit is contained in:
parent
268c1d0060
commit
e1ae3f20e5
46
clientapp/src/components/Comments/index.tsx
Normal file
46
clientapp/src/components/Comments/index.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Card, CardBody } from 'reactstrap'
|
||||
import { CommentsSectionModel } from '../../models/pageSections'
|
||||
|
||||
const Comments: FC<CommentsSectionModel> = ({
|
||||
comments = []
|
||||
}) => {
|
||||
|
||||
|
||||
return <section className="mb-5">
|
||||
<Card className="card bg-light">
|
||||
<CardBody className="card-body">
|
||||
<form className="mb-4">
|
||||
<textarea className="form-control" rows={3} placeholder="Join the discussion and leave a comment!"></textarea>
|
||||
</form>
|
||||
|
||||
{comments.map((comment, index) => <div key={index} className={`d-flex ${index < comments.length - 1 ? 'mb-4' : ''}`}>
|
||||
<div className="flex-shrink-0">
|
||||
<img className="rounded-circle" {...comment.author.image} />
|
||||
</div>
|
||||
<div className="ms-3">
|
||||
<div className="fw-bold">{comment.author.nickName}</div>
|
||||
{comment.comment}
|
||||
|
||||
{comment.responses? comment.responses.map((response, index) => <div key={index} className="d-flex mt-4">
|
||||
<div className="flex-shrink-0">
|
||||
<img className="rounded-circle" {...response.author.image} />
|
||||
</div>
|
||||
<div className="ms-3">
|
||||
<div className="fw-bold">{response.author.nickName}</div>
|
||||
{response.comment}
|
||||
</div>
|
||||
</div>) : ''}
|
||||
</div>
|
||||
</div>)}
|
||||
|
||||
|
||||
</CardBody>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
}
|
||||
|
||||
export {
|
||||
Comments
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
|
||||
import { CategoryModel } from '../../../models'
|
||||
import { CategoryModel } from '../../models'
|
||||
|
||||
const Search = () => {
|
||||
return <Card className="mb-4">
|
||||
@ -2,7 +2,7 @@ import { AuthorModel, ImageModel } from "./"
|
||||
|
||||
|
||||
export interface RequestModel {
|
||||
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
export interface ResponseModel {
|
||||
|
||||
@ -67,3 +67,8 @@ export interface FormItemModel {
|
||||
placeHolder?: string
|
||||
}
|
||||
|
||||
export interface CommentModel {
|
||||
author: AuthorModel,
|
||||
comment: string,
|
||||
responses?: CommentModel []
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { BlogItemModel, FeatureModel, FormItemModel, ImageModel, MenuItemModel, TestimonialsModel } from "./"
|
||||
import { BlogItemModel, CommentModel, FeatureModel, FormItemModel, ImageModel, MenuItemModel, TestimonialsModel } from "./"
|
||||
import { PageSectionModel } from "./abstractions"
|
||||
|
||||
export interface CallToActionSectionModel extends PageSectionModel {
|
||||
@ -23,3 +23,7 @@ export interface TitleSectionModel extends PageSectionModel {
|
||||
primaryLink?: MenuItemModel,
|
||||
secondaryLink?: MenuItemModel
|
||||
}
|
||||
|
||||
export interface CommentsSectionModel extends PageSectionModel {
|
||||
comments?: CommentModel []
|
||||
}
|
||||
@ -16,3 +16,7 @@ export interface ShopCatalogPageModel extends PageModel {
|
||||
export interface BlogCatalogPageModel extends PageModel {
|
||||
titleSection: TitleSectionModel
|
||||
}
|
||||
|
||||
export interface BlogPageModel extends PageModel {
|
||||
|
||||
}
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
import { RequestModel } from "./abstractions"
|
||||
|
||||
|
||||
export interface GetShopCatalogRequestModel {
|
||||
[key: string]: string | undefined
|
||||
export interface GetShopCatalogRequestModel extends RequestModel {
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
export interface GetBlogCatalogRequestModel {
|
||||
[key: string]: string | undefined
|
||||
export interface GetBlogCatalogRequestModel extends RequestModel {
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
export interface GetStaticContentRequestModel {
|
||||
[key: string]: string | undefined
|
||||
export interface GetBlogItemRequestModel extends RequestModel {
|
||||
slug: string
|
||||
}
|
||||
|
||||
export interface GetStaticContentRequestModel extends RequestModel {
|
||||
locale?: string
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { BlogItemModel, CategoryModel, MenuItemModel, PaginationModel, RouteModel, ShopItemModel } from "./"
|
||||
import { BlogItemModel, CategoryModel, CommentModel, MenuItemModel, PaginationModel, RouteModel, ShopItemModel } from "./"
|
||||
import { ResponseModel } from "./abstractions"
|
||||
import { BlogCatalogPageModel, HomePageModel, ShopCatalogPageModel } from "./pages"
|
||||
|
||||
@ -8,6 +8,10 @@ export interface GetBlogCatalogResponseModel extends ResponseModel {
|
||||
blogItemsPagination: PaginationModel<BlogItemModel>
|
||||
}
|
||||
|
||||
export interface GetBlogItemResponseModel extends ResponseModel {
|
||||
comments: CommentModel []
|
||||
}
|
||||
|
||||
export interface GetShopCatalogResponseModel extends ResponseModel {
|
||||
shopItemsPagination: PaginationModel<ShopItemModel>
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import { dateFormat, findRoutes } from '../../../functions'
|
||||
import { BlogItemModel, PaginationModel } from '../../../models'
|
||||
import { ApplicationState } from '../../../store'
|
||||
|
||||
import { Categories, Empty, Search } from '../SideWidgets'
|
||||
import { Categories, Empty, Search } from '../../../components/SideWidgets'
|
||||
import { TitleSectionModel } from '../../../models/pageSections'
|
||||
import { Pagination } from '../../../components/Pagination'
|
||||
|
||||
@ -27,14 +27,20 @@ const TitleSection: FC<TitleSectionModel> = (props) => {
|
||||
</header>
|
||||
}
|
||||
|
||||
const FeaturedBlog: FC<BlogItemModel> = (props) => {
|
||||
const { id, slug, badge, image, title, shortText, author, created, readTime, likes, tags } = props
|
||||
interface FeaturedBlogModel extends BlogItemModel {
|
||||
currentPage: number
|
||||
path: string,
|
||||
|
||||
}
|
||||
|
||||
const FeaturedBlog: FC<FeaturedBlogModel> = (props) => {
|
||||
const { id, slug, badge, image, title, shortText, author, created, readTime, likes, tags, currentPage, path } = props
|
||||
|
||||
return <Card className="mb-4 shadow border-0">
|
||||
<CardImg top {...image} />
|
||||
<CardBody className="p-4">
|
||||
<div className="badge bg-primary bg-gradient rounded-pill mb-2">{badge}</div>
|
||||
<Link className="text-decoration-none link-dark stretched-link" to={`/blog/${slug}`}>
|
||||
<Link className="text-decoration-none link-dark stretched-link" to={`${path}/${currentPage}/${slug}`}>
|
||||
<h5 className="card-title mb-3">{title}</h5>
|
||||
</Link>
|
||||
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: shortText }}></p>
|
||||
@ -122,7 +128,7 @@ const BlogCatalog = () => {
|
||||
<Container fluid>
|
||||
<Row>
|
||||
<Col>
|
||||
{blogCatalog?.featuredBlog ? <FeaturedBlog {...blogCatalog.featuredBlog} /> : ''}
|
||||
{blogCatalog?.featuredBlog ? <FeaturedBlog path={path} currentPage={blogCatalog.blogItemsPagination.currentPage} {...blogCatalog.featuredBlog} /> : ''}
|
||||
<Row>
|
||||
{blogCatalog?.blogItemsPagination ? <BlogItemsPagination path={path} {...blogCatalog.blogItemsPagination} /> : '' }
|
||||
</Row>
|
||||
|
||||
@ -1,13 +1,101 @@
|
||||
import React from 'react'
|
||||
import { Card, CardBody, CardHeader, Col, Container, Row } from 'reactstrap'
|
||||
import { Categories, Empty, Search } from '../SideWidgets'
|
||||
// React
|
||||
import React, { useEffect } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
|
||||
import { actionCreators as blogItemActionCreators } from '../../../store/reducers/BlogItem'
|
||||
|
||||
|
||||
|
||||
|
||||
import { Col, Container, Row } from 'reactstrap'
|
||||
|
||||
import { Comments } from '../../../components/Comments'
|
||||
import { Categories, Empty, Search } from '../../../components/SideWidgets'
|
||||
|
||||
import { CommentModel } from '../../../models'
|
||||
import { ApplicationState } from '../../../store'
|
||||
|
||||
const comments : CommentModel [] = [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 1",
|
||||
image: {
|
||||
src: "https://dummyimage.com/50x50/ced4da/6c757d.jpg",
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.",
|
||||
|
||||
responses: [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 4",
|
||||
image: {
|
||||
src: "https://dummyimage.com/50x50/ced4da/6c757d.jpg",
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors."
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 3",
|
||||
image: {
|
||||
src: "https://dummyimage.com/50x50/ced4da/6c757d.jpg",
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When you put money directly to a problem, it makes a good headline."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 2",
|
||||
image: {
|
||||
src: "https://dummyimage.com/50x50/ced4da/6c757d.jpg",
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence."
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
const badges : string [] = [
|
||||
"Web Design",
|
||||
"Freebies"
|
||||
]
|
||||
|
||||
|
||||
const BlogItem = () => {
|
||||
const params = useParams()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const blogItem = useSelector((state: ApplicationState) => state.blogItem)
|
||||
useEffect(() => {
|
||||
if(params?.slug)
|
||||
dispatch(blogItemActionCreators.requestBlogItem({
|
||||
slug: params.slug
|
||||
}))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
blogItem?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [blogItem?.isLoading])
|
||||
|
||||
|
||||
const postItem = {
|
||||
comments: []
|
||||
}
|
||||
|
||||
return <Container fluid mt="5">
|
||||
<Row>
|
||||
@ -17,11 +105,13 @@ const BlogItem = () => {
|
||||
<header className="mb-4">
|
||||
<h1 className="fw-bolder mb-1">Welcome to Blog Post!</h1>
|
||||
<div className="text-muted fst-italic mb-2">Posted on January 1, 2022 by Start Bootstrap</div>
|
||||
<a className="badge bg-secondary text-decoration-none link-light" href="#!">Web Design</a>
|
||||
<a className="badge bg-secondary text-decoration-none link-light" href="#!">Freebies</a>
|
||||
|
||||
{badges.map((badge, index) => <a key={index} className="badge bg-secondary text-decoration-none link-light" href="#!">{badge}</a>)}
|
||||
</header>
|
||||
|
||||
<figure className="mb-4"><img className="img-fluid rounded" src="https://dummyimage.com/900x400/ced4da/6c757d.jpg" alt="..." /></figure>
|
||||
<figure className="mb-4">
|
||||
<img className="img-fluid rounded" src="https://dummyimage.com/900x400/ced4da/6c757d.jpg" alt="..." />
|
||||
</figure>
|
||||
|
||||
<section className="mb-5">
|
||||
<p className="fs-5 mb-4">Science is an enterprise that should be cherished as an activity of the free human mind. Because it transforms who we are, how we live, and it gives us an understanding of our place in the universe.</p>
|
||||
@ -33,53 +123,7 @@ const BlogItem = () => {
|
||||
</section>
|
||||
</article>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section className="mb-5">
|
||||
<div className="card bg-light">
|
||||
<div className="card-body">
|
||||
<form className="mb-4">
|
||||
<textarea className="form-control" rows={3} placeholder="Join the discussion and leave a comment!"></textarea>
|
||||
</form>
|
||||
<div className="d-flex mb-4">
|
||||
|
||||
<div className="flex-shrink-0"><img className="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
|
||||
<div className="ms-3">
|
||||
<div className="fw-bold">Commenter Name</div>
|
||||
If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.
|
||||
|
||||
<div className="d-flex mt-4">
|
||||
<div className="flex-shrink-0"><img className="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
|
||||
<div className="ms-3">
|
||||
<div className="fw-bold">Commenter Name</div>
|
||||
And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex mt-4">
|
||||
<div className="flex-shrink-0"><img className="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
|
||||
<div className="ms-3">
|
||||
<div className="fw-bold">Commenter Name</div>
|
||||
When you put money directly to a problem, it makes a good headline.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex">
|
||||
<div className="flex-shrink-0"><img className="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
|
||||
<div className="ms-3">
|
||||
<div className="fw-bold">Commenter Name</div>
|
||||
When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Comments comments={comments} />
|
||||
</Col>
|
||||
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { RequestModel } from "./models/abstractions"
|
||||
|
||||
|
||||
|
||||
export interface IRequest {
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
interface IFetchResult {
|
||||
status: number,
|
||||
@ -13,7 +12,7 @@ const Post = () => {
|
||||
|
||||
}
|
||||
|
||||
const Get = async <T>(apiUrl: string, props?: IRequest): Promise<T | null> => {
|
||||
const Get = async <T>(apiUrl: string, props?: RequestModel): Promise<T | null> => {
|
||||
const url = new URL(apiUrl)
|
||||
|
||||
if(props) {
|
||||
|
||||
@ -4,9 +4,11 @@ import * as Counter from './reducers/Counter'
|
||||
import * as Loader from './reducers/Loader'
|
||||
|
||||
import * as Content from './reducers/Content'
|
||||
import * as BlogCatalog from './reducers/BlogCatalog'
|
||||
import * as ShopCatalog from './reducers/ShopCatalog'
|
||||
|
||||
import * as BlogCatalog from './reducers/BlogCatalog'
|
||||
import * as BlogItem from './reducers/BlogItem'
|
||||
|
||||
import * as ShopCatalog from './reducers/ShopCatalog'
|
||||
|
||||
// The top-level state object
|
||||
export interface ApplicationState {
|
||||
@ -16,7 +18,10 @@ export interface ApplicationState {
|
||||
loader: Loader.LoaderState | undefined
|
||||
|
||||
content: Content.ContentState | undefined
|
||||
|
||||
blogCatalog: BlogCatalog.BlogCatalogState | undefined
|
||||
blogItem: BlogItem.BlogItemState | undefined
|
||||
|
||||
shopCatalog: ShopCatalog.ShopCatalogState | undefined
|
||||
}
|
||||
|
||||
@ -30,7 +35,10 @@ export const reducers = {
|
||||
loader: Loader.reducer,
|
||||
|
||||
content: Content.reducer,
|
||||
|
||||
blogCatalog: BlogCatalog.reducer,
|
||||
blogItem: BlogItem.reducer,
|
||||
|
||||
shopCatalog: ShopCatalog.reducer
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ interface ReceiveAction extends GetBlogCatalogResponseModel {
|
||||
type: 'RECEIVE_BLOG_CATALOG'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction;
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogCatalog: (props?: GetBlogCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
116
clientapp/src/store/reducers/BlogItem.ts
Normal file
116
clientapp/src/store/reducers/BlogItem.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetBlogItemRequestModel } from '../../models/requests'
|
||||
import { GetBlogItemResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
export interface BlogItemState extends GetBlogItemResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends GetBlogItemRequestModel {
|
||||
type: 'REQUEST_BLOG_ITEM'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends GetBlogItemResponseModel {
|
||||
type: 'RECEIVE_BLOG_ITEM'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogItem: (props: GetBlogItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
const apiUrl = 'https://localhost:7151/api/BlogItem'
|
||||
|
||||
Get<Promise<GetBlogItemResponseModel>>(apiUrl, props)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_BLOG_ITEM', ...data })
|
||||
})
|
||||
|
||||
console.log(getState().blogItem)
|
||||
|
||||
dispatch({ type: 'REQUEST_BLOG_ITEM', slug: props.slug })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: BlogItemState = {
|
||||
|
||||
comments: [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 1",
|
||||
image: {
|
||||
src: "https://dummyimage.com/50x50/ced4da/6c757d.jpg",
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.",
|
||||
|
||||
responses: [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 4",
|
||||
image: {
|
||||
src: "https://dummyimage.com/50x50/ced4da/6c757d.jpg",
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors."
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 3",
|
||||
image: {
|
||||
src: "https://dummyimage.com/50x50/ced4da/6c757d.jpg",
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When you put money directly to a problem, it makes a good headline."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 2",
|
||||
image: {
|
||||
src: "https://dummyimage.com/50x50/ced4da/6c757d.jpg",
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence."
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
export const reducer: Reducer<BlogItemState> = (state: BlogItemState | undefined, incomingAction: Action): BlogItemState => {
|
||||
if (state === undefined) {
|
||||
return unloadedState
|
||||
}
|
||||
|
||||
const action = incomingAction as KnownAction
|
||||
switch (action.type) {
|
||||
case 'REQUEST_BLOG_ITEM':
|
||||
return {
|
||||
...state,
|
||||
isLoading: true
|
||||
}
|
||||
|
||||
case 'RECEIVE_BLOG_ITEM':
|
||||
return {
|
||||
...action,
|
||||
isLoading: false
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user