(bugfix): models fixing

This commit is contained in:
Maksym Sadovnychyy 2022-06-19 22:05:58 +02:00
parent e1ae3f20e5
commit ddb13e4184
58 changed files with 1622 additions and 560 deletions

View File

@ -42,8 +42,7 @@ const App = () => {
const { pathname } = useLocation()
const dispatch = useDispatch()
const content = useSelector((state: ApplicationState) => state.content)
const loader = useSelector((state: ApplicationState) => state.loader)
const { content, loader } = useSelector((state: ApplicationState) => state)
useEffect(() => {
dispatch(settingsActionCreators.requestContent())

View File

@ -1,20 +1,28 @@
import React, { FC } from 'react'
import { Card, CardBody } from 'reactstrap'
import { CommentModel } from '../../models'
import { CommentsSectionModel } from '../../models/pageSections'
const Comments: FC<CommentsSectionModel> = ({
comments = []
interface Comments {
staticContent?: CommentsSectionModel,
items?: CommentModel []
}
const CommentsSection: FC<Comments> = ({
staticContent,
items = []
}) => {
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>
<textarea className="form-control" rows={3} placeholder={staticContent?.leaveComment}></textarea>
</form>
{comments.map((comment, index) => <div key={index} className={`d-flex ${index < comments.length - 1 ? 'mb-4' : ''}`}>
{items.map((comment, index) => <div key={index} className={`d-flex ${index < items.length - 1 ? 'mb-4' : ''}`}>
<div className="flex-shrink-0">
<img className="rounded-circle" {...comment.author.image} />
</div>
@ -42,5 +50,5 @@ const Comments: FC<CommentsSectionModel> = ({
}
export {
Comments
CommentsSection
}

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { FC } from 'react'
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
import { CategoryModel } from '../../models'
@ -15,20 +15,16 @@ const Search = () => {
}
interface ICategories {
categories?: CategoryModel []
items?: CategoryModel []
}
const Categories = (props: ICategories) => {
const { categories } = props
const Categories: FC<ICategories> = ({
items = []
}) => {
const middleIndex = Math.ceil(items.length / 2)
if(!categories) {
return <></>
}
const middleIndex = Math.ceil(categories.length / 2)
const firstHalf = categories.splice(0, middleIndex)
const secondHalf = categories.splice(-middleIndex)
const firstHalf = items.splice(0, middleIndex)
const secondHalf = items.splice(-middleIndex)
return <Card className="mb-4">
<CardHeader>Categories</CardHeader>

View File

@ -5,8 +5,11 @@ export interface RequestModel {
[key: string]: string | undefined
}
export interface ResponseModel {
export interface ResponseModel { }
export interface PageModel {
}
export interface PageSectionModel {
@ -16,22 +19,18 @@ export interface PageSectionModel {
export interface PersonModel {
id: string,
image: ImageModel
image?: ImageModel
}
export interface PostItemModel {
id: string,
slug: string,
image: ImageModel,
badge: string,
badges: string [],
title: string,
shortText: string,
text: string,
shortText?: string,
text?: string,
author: AuthorModel,
created: string,
tags: string []
}
export interface PageModel {
}

View File

@ -1,4 +1,4 @@
import { PageSectionModel, PersonModel, PostItemModel } from "./abstractions"
import { PersonModel, PostItemModel } from "./abstractions"
export interface AuthorModel extends PersonModel {
nickName: string
@ -14,12 +14,23 @@ export interface CategoryModel {
text: string
}
export interface CommentModel {
author: AuthorModel,
comment: string,
responses?: CommentModel []
}
export interface FeatureModel {
icon: string,
title: string,
text: string
}
export interface FormItemModel {
title?: string,
placeHolder?: string
}
export interface ImageModel {
src: string,
alt: string
@ -51,7 +62,7 @@ export interface ShopItemModel extends PostItemModel {
quantity?: number
}
export interface TestimonialsModel {
export interface TestimonialModel {
text: string,
reviewer: ReviewerModel
}
@ -61,14 +72,3 @@ export interface PaginationModel<T> {
currentPage: number,
items: T []
}
export interface FormItemModel {
title?: string,
placeHolder?: string
}
export interface CommentModel {
author: AuthorModel,
comment: string,
responses?: CommentModel []
}

View File

@ -1,21 +1,37 @@
import { BlogItemModel, CommentModel, FeatureModel, FormItemModel, ImageModel, MenuItemModel, TestimonialsModel } from "./"
import { FeatureModel, FormItemModel, ImageModel, MenuItemModel, TestimonialModel } from "./"
import { PageSectionModel } from "./abstractions"
export interface BlogTitleSectionModel extends PageSectionModel {
postedOnBy: string
}
export interface CallToActionSectionModel extends PageSectionModel {
privacyDisclaimer: string
email: FormItemModel
privacyDisclaimer?: string
email?: FormItemModel
}
export interface FeaturedBlogsSectionModel extends PageSectionModel {
items: BlogItemModel []
export interface CommentsSectionModel extends PageSectionModel {
leaveComment: string
}
export interface FeaturesSectionModel extends PageSectionModel {
items: FeatureModel []
}
export interface FeaturedBlogSectionModel extends PageSectionModel {
readTime: string
}
export interface FeaturedBlogsSectionModel extends PageSectionModel {}
export interface ProductSectionModel extends PageSectionModel {
availableQuantity: string,
addToCart: string
}
export interface RelatedProductsSectionModel extends PageSectionModel {}
export interface TestimonialsSectionModel extends PageSectionModel {
items: TestimonialsModel []
items: TestimonialModel []
}
export interface TitleSectionModel extends PageSectionModel {
@ -24,6 +40,18 @@ export interface TitleSectionModel extends PageSectionModel {
secondaryLink?: MenuItemModel
}
export interface CommentsSectionModel extends PageSectionModel {
comments?: CommentModel []
}

View File

@ -1,5 +1,15 @@
import { PageModel } from "./abstractions"
import { CallToActionSectionModel, FeaturedBlogsSectionModel, FeaturesSectionModel, TestimonialsSectionModel, TitleSectionModel } from "./pageSections"
import { BlogTitleSectionModel, CallToActionSectionModel, CommentsSectionModel, FeaturedBlogSectionModel, FeaturedBlogsSectionModel, FeaturesSectionModel, ProductSectionModel, RelatedProductsSectionModel, TestimonialsSectionModel, TitleSectionModel } from "./pageSections"
export interface BlogCatalogPageModel extends PageModel {
titleSection: TitleSectionModel,
featuredBlogSection: FeaturedBlogSectionModel
}
export interface BlogItemPageModel extends PageModel {
titleSection: BlogTitleSectionModel,
commentsSection: CommentsSectionModel
}
export interface HomePageModel extends PageModel {
titleSection: TitleSectionModel,
@ -13,10 +23,7 @@ export interface ShopCatalogPageModel extends PageModel {
titleSection: TitleSectionModel
}
export interface BlogCatalogPageModel extends PageModel {
titleSection: TitleSectionModel
}
export interface BlogPageModel extends PageModel {
export interface ShopItemPageModel extends PageModel {
productSection: ProductSectionModel
relatedProductsSection: RelatedProductsSectionModel
}

View File

@ -1,13 +1,7 @@
import { RequestModel } from "./abstractions"
export interface GetShopCatalogRequestModel extends RequestModel {
category?: string,
searchText?: string,
currentPage?: string,
itemsPerPage?: string
}
// Blog requests
export interface GetBlogCatalogRequestModel extends RequestModel {
category?: string,
searchText?: string,
@ -15,10 +9,33 @@ export interface GetBlogCatalogRequestModel extends RequestModel {
itemsPerPage?: string
}
export interface GetBlogCategoriesRequestModel extends RequestModel { }
export interface GetBlogItemRequestModel extends RequestModel {
slug: string
}
export interface GetStaticContentRequestModel extends RequestModel {
export interface GetBlogFeaturedRequestModel extends RequestModel { }
// Static content
export interface GetContentRequestModel extends RequestModel {
locale?: string
}
// Shop requests
export interface GetShopCatalogRequestModel extends RequestModel {
category?: string,
searchText?: string,
currentPage?: string,
itemsPerPage?: string
}
export interface GetShopCategoriesRequestModel extends RequestModel { }
export interface GetShopFeaturedRequestModel extends RequestModel { }
export interface GetShopItemRequestModel extends RequestModel {
slug: string
}
export interface GetShopRelatedRequestModel extends RequestModel { }

View File

@ -1,22 +1,34 @@
import { BlogItemModel, CategoryModel, CommentModel, MenuItemModel, PaginationModel, RouteModel, ShopItemModel } from "./"
import { ResponseModel } from "./abstractions"
import { BlogCatalogPageModel, HomePageModel, ShopCatalogPageModel } from "./pages"
import {
HomePageModel,
BlogCatalogPageModel,
BlogItemPageModel,
ShopCatalogPageModel,
ShopItemPageModel
} from "./pages"
export interface GetBlogCatalogResponseModel extends ResponseModel {
featuredBlog: BlogItemModel,
categories: CategoryModel [],
blogItemsPagination: PaginationModel<BlogItemModel>
// Shop response models
export interface GetShopCatalogResponseModel extends PaginationModel<ShopItemModel>, ResponseModel {}
export interface GetShopCategoriesResponseModel extends ResponseModel {
items: CategoryModel []
}
export interface GetBlogItemResponseModel extends ResponseModel {
export interface GetShopFeaturedResponseModel extends ResponseModel {
items: ShopItemModel []
}
export interface GetShopItemResponseModel extends ShopItemModel, ResponseModel {
comments: CommentModel []
}
export interface GetShopCatalogResponseModel extends ResponseModel {
shopItemsPagination: PaginationModel<ShopItemModel>
export interface GetShopRelatedResponseModel extends ResponseModel {
items: ShopItemModel []
}
export interface GetStaticContentResponseModel extends ResponseModel {
// Static content response model
export interface GetContentResponseModel extends ResponseModel {
siteName: string,
routes: RouteModel [],
@ -27,13 +39,32 @@ export interface GetStaticContentResponseModel extends ResponseModel {
sideMenu: MenuItemModel [],
homePage: HomePageModel,
shopCatalog: ShopCatalogPageModel,
blogCatalog: BlogCatalogPageModel
shopItem: ShopItemPageModel,
blogCatalog: BlogCatalogPageModel,
blogItem: BlogItemPageModel
}
// Blog response models
export interface GetBlogCatalogResponseModel extends PaginationModel<BlogItemModel>, ResponseModel { }
export interface GetBlogCategoriesResponseModel extends ResponseModel {
items: CategoryModel []
}
export interface GetBlogFeaturedResponseModel extends ResponseModel {
items: BlogItemModel []
}
export interface GetBlogItemResponseModel extends BlogItemModel, ResponseModel {
comments: CommentModel []
}
// Weather forecasts
export interface GetWeatherForecastResponseModel extends ResponseModel {
date: string,
temperatireC: number,
temperatureF: number,
summary?: string
}
}

View File

@ -1,20 +1,25 @@
import React, { FC, useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { ApplicationState } from '../../../store'
import { actionCreators as contentActionCreators } from '../../../store/reducers/Content'
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
import { actionCreators as blogCatalogActionCreators } from '../../../store/reducers/BlogCatalog'
import { actionCreators as blogFeaturedActionCreators } from '../../../store/reducers/BlogFeatured'
import { actionCreators as blogCategoriesActionCreators } from '../../../store/reducers/BlogCategories'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
import { dateFormat, findRoutes } from '../../../functions'
import { BlogItemModel, PaginationModel } from '../../../models'
import { ApplicationState } from '../../../store'
import { Categories, Empty, Search } from '../../../components/SideWidgets'
import { TitleSectionModel } from '../../../models/pageSections'
import { Pagination } from '../../../components/Pagination'
import { BlogItemModel } from '../../../models'
import { TitleSectionModel } from '../../../models/pageSections'
const TitleSection: FC<TitleSectionModel> = (props) => {
const { title, text } = props
return <header className="py-5 bg-light border-bottom mb-4">
@ -27,45 +32,80 @@ const TitleSection: FC<TitleSectionModel> = (props) => {
</header>
}
interface FeaturedBlogModel extends BlogItemModel {
currentPage: number
path: string,
interface FeaturedBlog {
path?: string,
currentPage?: number
item?: BlogItemModel,
readTime?: string
}
const FeaturedBlog: FC<FeaturedBlogModel> = (props) => {
const { id, slug, badge, image, title, shortText, author, created, readTime, likes, tags, currentPage, path } = props
const FeaturedBlogSection: FC<FeaturedBlog> = ({
currentPage = 1,
path = "",
item = {
id: "",
slug: "demo-post",
badges: [],
image: {
src: "https://dummyimage.com/850x350/dee2e6/6c757d.jpg",
alt: "..."
},
title: "",
shortText: "",
author: {
id: "",
nickName: "",
image: {
src: "https://dummyimage.com/40x40/ced4da/6c757d",
alt: "..."
}
},
created: new Date().toString(),
tags: [],
likes: 0
},
readTime = ""
}) => {
return <Card className="mb-4 shadow border-0">
<CardImg top {...image} />
<CardImg top {...item.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={`${path}/${currentPage}/${slug}`}>
<h5 className="card-title mb-3">{title}</h5>
{item.badges.map((badge, index) => <div key={index} className="badge bg-primary bg-gradient rounded-pill mb-2">{badge}</div>)}
<Link className="text-decoration-none link-dark stretched-link" to={`${path}/${currentPage}/${item.slug}`}>
<h5 className="card-title mb-3">{item.title}</h5>
</Link>
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: shortText }}></p>
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: item.shortText ? item.shortText : '' }}></p>
</CardBody>
<CardFooter className="p-4 pt-0 bg-transparent border-top-0">
<div className="d-flex align-items-end justify-content-between">
<div className="d-flex align-items-center">
<img className="rounded-circle me-3" {...author.image} />
<img className="rounded-circle me-3" {...item.author.image} />
<div className="small">
<div className="fw-bold">{author.nickName}</div>
<div className="text-muted">{dateFormat(created)} &middot; Time to read: {readTime} min</div>
<div className="fw-bold">{item.author.nickName}</div>
<div className="text-muted">{readTime}</div>
</div>
</div>
</div>
</CardFooter>
</Card>
}
interface BlogItemsPaginationModel extends PaginationModel<BlogItemModel> {
path: string
interface BlogItems {
path?: string
totalPages?: number,
currentPage?: number,
items?: BlogItemModel []
}
const BlogItemsPagination: FC<BlogItemsPaginationModel> = (props) => {
const { items, currentPage, totalPages, path } = props
const BlogItemsSection: FC<BlogItems> = ({
path = "",
totalPages = 1,
currentPage = 1,
items = []
}) => {
const dispatch = useDispatch()
const navigate = useNavigate()
@ -104,40 +144,52 @@ const BlogCatalog = () => {
const params = useParams()
const dispatch = useDispatch()
const content = useSelector((state: ApplicationState) => state.content)
const { content, blogCatalog, blogCategories, blogFeatured } = useSelector((state: ApplicationState) => state)
const page = content?.blogCatalog
const path = findRoutes(content?.routes, 'BlogCatalog')[0]?.targets[0]
const blogCatalog = useSelector((state: ApplicationState) => state.blogCatalog)
useEffect(() => {
dispatch(blogCatalogActionCreators.requestBlogCatalog({
currentPage: params?.page ? params.page : "1"
}))
// dispatch(blogCatalogActionCreators.requestBlogCatalog({
// currentPage: params?.page ? params.page : "1"
// }))
// dispatch(blogFeaturedActionCreators.requestBlogFeatured())
dispatch(blogCategoriesActionCreators.requestBlogCategories())
}, [])
useEffect(() => {
blogCatalog?.isLoading
? dispatch(loaderActionCreators.show())
: setTimeout(() => {
dispatch(loaderActionCreators.hide())
}, 1000)
// blogCatalog?.isLoading
// ? dispatch(loaderActionCreators.show())
// : setTimeout(() => {
// dispatch(loaderActionCreators.hide())
// }, 1000)
}, [blogCatalog?.isLoading])
const blogItem = blogFeatured?.items[0]
const featuredBlog: FeaturedBlog = {
path: path,
currentPage: blogCatalog?.currentPage,
item: blogItem,
readTime: page?.featuredBlogSection?.readTime
}
if(featuredBlog.readTime && blogItem?.created && blogItem?.readTime)
featuredBlog.readTime = featuredBlog.readTime?.replace('{date}', dateFormat(blogItem.created))
.replace('{readTime}', `${blogItem.readTime}`)
return <>
<TitleSection {...page?.titleSection} />
<Container fluid>
<Row>
<Col>
{blogCatalog?.featuredBlog ? <FeaturedBlog path={path} currentPage={blogCatalog.blogItemsPagination.currentPage} {...blogCatalog.featuredBlog} /> : ''}
<FeaturedBlogSection {...featuredBlog} />
<Row>
{blogCatalog?.blogItemsPagination ? <BlogItemsPagination path={path} {...blogCatalog.blogItemsPagination} /> : '' }
<BlogItemsSection path={path} {...blogCatalog} />
</Row>
</Col>
<Col lg="4">
<Search />
{blogCatalog?.categories ? <Categories {...{
categories: blogCatalog.categories
}} /> : '' }
<Categories {...blogCategories} />
<Empty/>
</Col>
</Row>

View File

@ -1,5 +1,5 @@
// React
import React, { useEffect } from 'react'
import React, { FC, useEffect } from 'react'
import { useParams } from 'react-router-dom'
// Redux
@ -7,123 +7,96 @@ 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 { CommentsSection } from '../../../components/Comments'
import { Categories, Empty, Search } from '../../../components/SideWidgets'
import { CommentModel } from '../../../models'
import { ApplicationState } from '../../../store'
import { dateFormat } from '../../../functions'
import { ImageModel } from '../../../models'
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."
interface BlogItemTitle {
title?: string,
postedOnBy? :string,
badges? : string[],
image?: ImageModel
}
const BlogTitleSection: FC<BlogItemTitle> = ({
title = "",
postedOnBy = "",
badges = [],
image = {
src: "",
alt: ""
}
}) => {
]
return <>
<header className="mb-4">
<h1 className="fw-bolder mb-1">{title}</h1>
<div className="text-muted fst-italic mb-2">{postedOnBy ? postedOnBy : ''}</div>
const badges : string [] = [
"Web Design",
"Freebies"
]
{badges ? 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" {...image} />
</figure>
</>
}
const BlogItem = () => {
const params = useParams()
const dispatch = useDispatch()
const blogItem = useSelector((state: ApplicationState) => state.blogItem)
const { content, blogItem } = useSelector((state: ApplicationState) => state)
const page = content?.blogItem
useEffect(() => {
if(params?.slug)
dispatch(blogItemActionCreators.requestBlogItem({
slug: params.slug
}))
// if(params?.slug)
// dispatch(blogItemActionCreators.requestBlogItem({
// slug: params.slug
// }))
}, [])
useEffect(() => {
blogItem?.isLoading
? dispatch(loaderActionCreators.show())
: setTimeout(() => {
dispatch(loaderActionCreators.hide())
}, 1000)
// blogItem?.isLoading
// ? dispatch(loaderActionCreators.show())
// : setTimeout(() => {
// dispatch(loaderActionCreators.hide())
// }, 1000)
}, [blogItem?.isLoading])
const blogItemTitle: BlogItemTitle = {
title: blogItem?.title,
postedOnBy: page?.titleSection?.postedOnBy,
badges: blogItem?.badges,
image: blogItem?.image
}
if(blogItemTitle.postedOnBy && blogItem?.created)
blogItemTitle.postedOnBy = blogItemTitle.postedOnBy?.replace('{date}', dateFormat(blogItem.created))
if(blogItemTitle.postedOnBy && blogItem?.author)
blogItemTitle.postedOnBy = blogItemTitle.postedOnBy?.replace('{nickName}', dateFormat(blogItem.author.nickName))
return <Container fluid mt="5">
<Row>
<Col lg="8">
<article>
<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>
{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>
<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>
<p className="fs-5 mb-4">The universe is large and old, and the ingredients for life as we know it are everywhere, so there's no reason to think that Earth would be unique in that regard. Whether of not the life became intelligent is a different question, and we'll see if we find that.</p>
<p className="fs-5 mb-4">If you get asteroids about a kilometer in size, those are large enough and carry enough energy into our system to disrupt transportation, communication, the food chains, and that can be a really bad day on Earth.</p>
<h2 className="fw-bolder mb-4 mt-5">I have odd cosmic thoughts every day</h2>
<p className="fs-5 mb-4">For me, the most fascinating interface is Twitter. I have odd cosmic thoughts every day and I realized I could hold them to myself or share them with people who might be interested.</p>
<p className="fs-5 mb-4">Venus has a runaway greenhouse effect. I kind of want to know what happened there because we're twirling knobs here on Earth without knowing the consequences of it. Mars once had running water. It's bone dry today. Something bad happened there as well.</p>
</section>
<BlogTitleSection {...blogItemTitle} />
<section className="mb-5" dangerouslySetInnerHTML={{ __html: blogItem?.text ? blogItem.text : '' }}></section>
</article>
<Comments comments={comments} />
<CommentsSection {...{
staticContent: page?.commentsSection,
items: blogItem?.comments
}} />
</Col>

View File

@ -13,7 +13,7 @@ const Counter = () => {
const counterState = useSelector((state: IReduxState) => state.counter)
const increment = () => {
dispatch(counterActionCreators.increment())
// dispatch(counterActionCreators.increment())
}
return <>

View File

@ -23,9 +23,9 @@ const FetchData = () => {
const { startDateIndex, forecasts, isLoading } = useSelector((state: IReduxState) => state.weatherForecasts)
const ensureDataFetched = () => {
dispatch(weatherForecastsActionCreators.requestWeatherForecasts(
parseInt(params.startDateIndex ? params.startDateIndex : '0', 10)
))
// dispatch(weatherForecastsActionCreators.requestWeatherForecasts(
// parseInt(params.startDateIndex ? params.startDateIndex : '0', 10)
// ))
}
// This method is called when the route parameters change

View File

@ -6,12 +6,14 @@ import { Link } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { ApplicationState } from '../../store'
import { actionCreators as loaderActionCreators } from '../../store/reducers/Loader'
import { actionCreators as blogFeaturedActionCreators } from '../../store/reducers/BlogFeatured'
// Reactstrap
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
// Models (interfaces)
import { CallToActionSectionModel, FeaturedBlogsSectionModel, FeaturesSectionModel, TestimonialsSectionModel, TitleSectionModel } from '../../models/pageSections'
import { BlogItemModel, FeatureModel, TestimonialModel } from '../../models'
// Custom components
import { FeatherIcon } from '../../components/FeatherIcons'
@ -19,18 +21,20 @@ import { FeatherIcon } from '../../components/FeatherIcons'
// Functions
import { dateFormat } from '../../functions'
// CSS Modules
import style from './scss/style.module.scss'
const TitleSection : FC<TitleSectionModel> = (props) => {
const { title, text } = props
const TitleSection : FC<TitleSectionModel> = ({
title = "",
text = ""
}) => {
return <header className="py-5 bg-dark">
<Container fluid className="px-5">
<Row className="gx-5 align-items-center justify-content-center">
<Col className="lg-8 xl-7 xxl-6">
<div className="my-5 text-center text-xl-start">
<h1 className="display-5 fw-bolder text-white mb-2">{title}</h1>
<span className="lead fw-normal text-white-50 mb-4" dangerouslySetInnerHTML={{ __html: text ? text : "" }}>
<span className="lead fw-normal text-white-50 mb-4" dangerouslySetInnerHTML={{ __html: text }}>
</span>
<div className="d-grid gap-3 d-sm-flex justify-content-sm-center justify-content-xl-start">
@ -43,14 +47,17 @@ const TitleSection : FC<TitleSectionModel> = (props) => {
</Row>
</Container>
</header>
}
interface Fetures {
title?: string,
items?: FeatureModel []
}
const FeaturesSection: FC<FeaturesSectionModel> = (props) => {
const { title, items } = props
const FeaturesSection: FC<Fetures> = ({
title = "",
items = []
}) => {
return <section className="py-5" id="features">
<Container fluid className="px-5 my-5">
<Row className="gx-5">
@ -73,73 +80,89 @@ const FeaturesSection: FC<FeaturesSectionModel> = (props) => {
</section>
}
const TestimonialsSection: FC<TestimonialsSectionModel> = (props) => {
const item = props?.items ? props?.items.shift() : undefined
interface Testimonials {
items?: TestimonialModel []
}
if(!item) return <></>
const TestimonialsSection: FC<Testimonials> = ({
items = []
}) => {
const item = items[0]
return <section className="py-5 bg-light">
<Container fluid className="px-5 my-5">
<Row className="gx-5 justify-content-center">
<Col className="lg-10 xl-7">
<div className="text-center">
<div className="fs-4 mb-4 fst-italic" dangerouslySetInnerHTML={{ __html: item.text }}></div>
<div className="d-flex align-items-center justify-content-center">
<img className="rounded-circle me-3" {...item.reviewer.image} />
<div className="fw-bold">{item.reviewer.fullName}<span className="fw-bold text-primary mx-1">/</span>{item.reviewer.position}
</div>
</div>
</div>
</Col>
</Row>
</Container>
</section>
}
const FromOurBlogSection: FC<FeaturedBlogsSectionModel> = (props) => {
const { title, text, items } = props
return <section className="py-5">
<Container fluid className="px-5 my-5">
<Row className="gx-5 justify-content-center">
<Col className="lg-8 xl-6">
<div className="text-center">
<h2 className="fw-bolder">{title ? title : ''}</h2>
<p className="lead fw-normal text-muted mb-5" dangerouslySetInnerHTML={{ __html: text ? text : '' }}></p>
</div>
</Col>
</Row>
<Row className="gx-5">
{items ? items.map((item, index) => <Col key={index} className="lg-4 mb-5">
<Card className="h-100 shadow border-0">
<CardImg top {...item.image} />
<CardBody className="p-4">
<div className="badge bg-primary bg-gradient rounded-pill mb-2">{item.badge}</div>
<Link className="text-decoration-none link-dark stretched-link" to="#!">
<h5 className="card-title mb-3">{item.title}</h5>
</Link>
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: text ? text : '' }}></p>
</CardBody>
<CardFooter className="p-4 pt-0 bg-transparent border-top-0">
<div className="d-flex align-items-end justify-content-between">
<div className="d-flex align-items-center">
<img className="rounded-circle me-3" {...item.author.image} />
<div className="small">
<div className="fw-bold">{item.author.nickName}</div>
<div className="text-muted">{dateFormat(item.created)} &middot; {item.readTime}</div>
</div>
{ item
? <div className="text-center">
<div className="fs-4 mb-4 fst-italic" dangerouslySetInnerHTML={{ __html: item.text }}></div>
<div className="d-flex align-items-center justify-content-center">
<img className="rounded-circle me-3" {...item.reviewer.image} />
<div className="fw-bold">{item.reviewer.fullName}<span className="fw-bold text-primary mx-1">/</span>{item.reviewer.position}
</div>
</div>
</CardFooter>
</Card>
</Col>) : ''}
</div>
: '' }
</Col>
</Row>
</Container>
</section>
}
const CallToActionSection: FC<CallToActionSectionModel> = (props) => {
const { title, text, privacyDisclaimer, email } = props
interface FeaturedBlogs extends FeaturedBlogsSectionModel {
items?: BlogItemModel []
}
const FeaturedBlogsSection: FC<FeaturedBlogs> = ({
title = "",
text = "",
items = [] }) => <section className="py-5">
<Container fluid className="px-5 my-5">
<Row className="gx-5 justify-content-center">
<Col className="lg-8 xl-6">
<div className="text-center">
<h2 className="fw-bolder">{title}</h2>
<p className="lead fw-normal text-muted mb-5" dangerouslySetInnerHTML={{ __html: text }}></p>
</div>
</Col>
</Row>
<Row className="gx-5">
{items.map((item, index) => <Col key={index} className="lg-4 mb-5">
<Card className="h-100 shadow border-0">
<CardImg top {...item.image} />
<CardBody className="p-4">
<div className="badge bg-primary bg-gradient rounded-pill mb-2">{item.badges}</div>
<Link className="text-decoration-none link-dark stretched-link" to="#!">
<h5 className="card-title mb-3">{item.title}</h5>
</Link>
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: text ? text : '' }}></p>
</CardBody>
<CardFooter className="p-4 pt-0 bg-transparent border-top-0">
<div className="d-flex align-items-end justify-content-between">
<div className="d-flex align-items-center">
<img className="rounded-circle me-3" {...item.author.image} />
<div className="small">
<div className="fw-bold">{item.author.nickName}</div>
<div className="text-muted">{dateFormat(item.created)} &middot; {item.readTime}</div>
</div>
</div>
</div>
</CardFooter>
</Card>
</Col>)}
</Row>
</Container>
</section>
const CallToActionSection: FC<CallToActionSectionModel> = ({
title = "",
text = "",
privacyDisclaimer = "",
email = {
placeHolder: "",
title: ""
}
}) => {
return <section className="py-5">
<Container fluid className="px-5 my-5">
@ -164,26 +187,27 @@ const CallToActionSection: FC<CallToActionSectionModel> = (props) => {
const Home = () => {
const dispatch = useDispatch()
const content = useSelector((state: ApplicationState) => state.content)
const { content, blogFeatured } = useSelector((state: ApplicationState) => state)
const page = content?.homePage
useEffect(() => {
content?.isLoading
dispatch(blogFeaturedActionCreators.requestBlogFeatured())
}, [])
useEffect(() => {
content?.isLoading || content?.isLoading
? dispatch(loaderActionCreators.show())
: setTimeout(() => {
dispatch(loaderActionCreators.hide())
}, 1000)
}, [content?.isLoading])
const page = content?.homePage
if(!page) return <></>
}, [content?.isLoading, blogFeatured?.isLoading])
return <>
<TitleSection {...page.titleSection} />
<FeaturesSection {...page.featuresSection} />
<TestimonialsSection {...page.testimonialsSection} />
<FromOurBlogSection {...page.featuredBlogsSection} />
<CallToActionSection {...page.callToActionSection} />
<TitleSection {...page?.titleSection} />
<FeaturesSection {...page?.featuresSection} />
<TestimonialsSection {...page?.testimonialsSection} />
<FeaturedBlogsSection items={blogFeatured?.items} {...page?.featuredBlogsSection} />
<CallToActionSection {...page?.callToActionSection} />
</>
}

View File

@ -12,7 +12,7 @@ import { actionCreators as shopCatalogActionCreators } from '../../../store/redu
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
// Models (interfaces)
import { PaginationModel, ShopItemModel } from '../../../models'
import { ShopItemModel } from '../../../models'
import { TitleSectionModel } from '../../../models/pageSections'
// Custom components
@ -22,26 +22,32 @@ import { Pagination } from '../../../components/Pagination'
// Functions
import { findRoutes } from '../../../functions'
const TitleSection: FC<TitleSectionModel> = (props) => {
const { title, text } = props
const TitleSection: FC<TitleSectionModel> = ({
title = "",
text = ""
}) => <header className="bg-dark py-5">
<Container fluid className="px-4 px-lg-5 my-5">
<div className="text-center text-white">
<h1 className="display-4 fw-bolder">{title}</h1>
<p className="lead fw-normal text-white-50 mb-0">{text}</p>
</div>
</Container>
</header>
return <header className="bg-dark py-5">
<Container fluid className="px-4 px-lg-5 my-5">
<div className="text-center text-white">
<h1 className="display-4 fw-bolder">{title ? title : ''}</h1>
<p className="lead fw-normal text-white-50 mb-0">{text ? text : ''}</p>
</div>
</Container>
</header>
interface ShopItems {
path?: string
totalPages?: number,
currentPage?: number,
items?: ShopItemModel []
}
interface ShopItemsPaginationModel extends PaginationModel<ShopItemModel> {
path: string
}
const ShopItemsPagination: FC<ShopItemsPaginationModel> = (props) => {
const { items, currentPage, totalPages, path } = props
const ShopItemsSection: FC<ShopItems> = ({
path = "",
totalPages = 1,
currentPage = 1,
items = []
}) => {
const dispatch = useDispatch()
const navigate = useNavigate()
@ -51,7 +57,7 @@ const ShopItemsPagination: FC<ShopItemsPaginationModel> = (props) => {
<Row className="gx-4 gx-lg-5 row-cols-2 row-cols-md-3 row-cols-xl-4 justify-content-center">
{items.map((item, index) => <Col key={index} className="mb-5">
<Card className="h-100">
<div className="badge bg-dark text-white position-absolute" style={{top: "0.5rem", right: "0.5rem"}}>{item.badge}</div>
<div className="badge bg-dark text-white position-absolute" style={{top: "0.5rem", right: "0.5rem"}}>{item.badges}</div>
<Link to={`${path}/${currentPage}/${item.slug}`}>
<CardImg top {...item.image} />
@ -66,8 +72,8 @@ const ShopItemsPagination: FC<ShopItemsPaginationModel> = (props) => {
}} />
{item.newPrice
? <><span className="text-muted text-decoration-line-through">{item.price}</span> {item.newPrice}</>
: item.price}
? <><span className="text-muted text-decoration-line-through">{item.price}</span> <span>{item.newPrice}</span></>
: <span>{item.price}</span>}
</div>
</CardBody>
@ -75,9 +81,7 @@ const ShopItemsPagination: FC<ShopItemsPaginationModel> = (props) => {
<div className="text-center"><a className="btn btn-outline-dark mt-auto" href="#">Add to cart</a></div>
</CardFooter>
</Card>
</Col>
)}
</Col>)}
</Row>
@ -102,28 +106,32 @@ const ShopCatalog = () => {
const params = useParams()
const dispatch = useDispatch()
const content = useSelector((state: ApplicationState) => state.content)
const { content, shopCatalog } = useSelector((state: ApplicationState) => state)
const page = content?.shopCatalog
const path = findRoutes(content?.routes, 'ShopCatalog')[0]?.targets[0]
const shopCatalog = useSelector((state: ApplicationState) => state.shopCatalog)
useEffect(() => {
dispatch(shopCatalogActionCreators.requestShopCatalog({
currentPage: params?.page ? params.page : "1"
}))
// dispatch(shopCatalogActionCreators.requestShopCatalog({
// currentPage: params?.page ? params.page : "1"
// }))
}, [])
useEffect(() => {
shopCatalog?.isLoading
? dispatch(loaderActionCreators.show())
: setTimeout(() => {
dispatch(loaderActionCreators.hide())
}, 1000)
// shopCatalog?.isLoading
// ? dispatch(loaderActionCreators.show())
// : setTimeout(() => {
// dispatch(loaderActionCreators.hide())
// }, 1000)
}, [shopCatalog?.isLoading])
const shopItems: ShopItems = {
path,
...shopCatalog
}
return <>
<TitleSection {...page?.titleSection} />
{shopCatalog?.shopItemsPagination ? <ShopItemsPagination path={path} {...shopCatalog.shopItemsPagination} /> : ''}
<ShopItemsSection {...shopItems} />
</>
}

View File

@ -1,23 +1,54 @@
import React, { FC } from 'react'
import React, { FC, useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
import { actionCreators as shopItemActionCreators } from '../../../store/reducers/ShopItem'
import { Container } from 'reactstrap'
import { FeatherIcon } from '../../../components/FeatherIcons'
import { RelatedProducts } from '../RelatedProducts'
import { ApplicationState } from '../../../store'
const ShopItem = () => {
const params = useParams()
const dispatch = useDispatch()
const { content, shopItem } = useSelector((state: ApplicationState) => state)
const page = content?.shopItem
useEffect(() => {
// if(params?.slug)
// dispatch(shopItemActionCreators.requestShopItem({
// slug: params.slug
// }))
}, [])
useEffect(() => {
// shopItem?.isLoading
// ? dispatch(loaderActionCreators.show())
// : setTimeout(() => {
// dispatch(loaderActionCreators.hide())
// }, 1000)
}, [shopItem?.isLoading])
return <>
<section className="py-5">
<Container fluid className="px-4 px-lg-5 my-5">
<div className="row gx-4 gx-lg-5 align-items-center">
<div className="col-md-6"><img className="card-img-top mb-5 mb-md-0" src="https://dummyimage.com/600x700/dee2e6/6c757d.jpg" alt="..." /></div>
<div className="col-md-6"><img className="card-img-top mb-5 mb-md-0" {...shopItem?.image} /></div>
<div className="col-md-6">
<div className="small mb-1">SKU: BST-498</div>
<h1 className="display-5 fw-bolder">Shop item template</h1>
<div className="small mb-1">{`SKU: ${shopItem?.sku}`}</div>
<h1 className="display-5 fw-bolder">{shopItem?.title}</h1>
<div className="fs-5 mb-5">
<span className="text-decoration-line-through">$45.00</span>
<span>$40.00</span>
{shopItem?.newPrice
? <><span className="text-decoration-line-through">{shopItem?.price}</span> <span>{shopItem?.newPrice}</span></>
: <span>{shopItem?.price}</span>}
</div>
<p className="lead">Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium at dolorem quidem modi. Nam sequi consequatur obcaecati excepturi alias magni, accusamus eius blanditiis delectus ipsam minima ea iste laborum vero?</p>
<section dangerouslySetInnerHTML={{ __html: shopItem?.text ? shopItem.text : '' }}></section>
<div className="d-flex">
<input className="form-control text-center me-3" id="inputQuantity" type="num" value="1" style={{maxWidth: "3rem"}} />
<button className="btn btn-outline-dark flex-shrink-0" type="button">

View File

@ -3,7 +3,7 @@ import { RequestModel } from "./models/abstractions"
interface IFetchResult {
interface FetchData {
status: number,
text: string
}
@ -40,7 +40,7 @@ const Get = async <T>(apiUrl: string, props?: RequestModel): Promise<T | null> =
})
if (fetchData?.text)
return JSON.parse((fetchData as IFetchResult).text) as T
return JSON.parse((fetchData as FetchData).text) as T
return null
}

View File

@ -1,45 +1,63 @@
import * as WeatherForecasts from './reducers/WeatherForecasts'
import * as Counter from './reducers/Counter'
import * as BlogCatalog from './reducers/BlogCatalog'
import * as BlogCategories from './reducers/BlogCategories'
import * as BlogFeatured from './reducers/BlogFeatured'
import * as BlogItem from './reducers/BlogItem'
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 BlogItem from './reducers/BlogItem'
import * as ShopCatalog from './reducers/ShopCatalog'
import * as ShopCategories from './reducers/ShopCategories'
import * as ShopFeatured from './reducers/ShopFeatured'
import * as ShopItem from './reducers/ShopItem'
import * as ShopRelated from './reducers/ShopRelated'
import * as WeatherForecasts from './reducers/WeatherForecasts'
// The top-level state object
export interface ApplicationState {
counter: Counter.CounterState | undefined
weatherForecasts: WeatherForecasts.WeatherForecastsState | undefined
loader: Loader.LoaderState | undefined
blogCatalog: BlogCatalog.BlogCatalogState | undefined
blogCategories: BlogCategories.BlogCategoriesState | undefined
blogFeatured: BlogFeatured.BlogFeaturedState | undefined
blogItem: BlogItem.BlogItemState | undefined
content: Content.ContentState | undefined
blogCatalog: BlogCatalog.BlogCatalogState | undefined
blogItem: BlogItem.BlogItemState | undefined
counter: Counter.CounterState | undefined
loader: Loader.LoaderState | undefined
shopCatalog: ShopCatalog.ShopCatalogState | undefined
shopCategories: ShopCategories.ShopCategoriesState | undefined
shopFeatured: ShopFeatured.ShopFeaturedState | undefined
shopItem: ShopItem.ShopItemState | undefined
shopRelated: ShopRelated.ShopRelatedState | undefined
weatherForecasts: WeatherForecasts.WeatherForecastsState | undefined
}
// Whenever an action is dispatched, Redux will update each top-level application state property using
// the reducer with the matching name. It's important that the names match exactly, and that the reducer
// acts on the corresponding ApplicationState property type.
export const reducers = {
counter: Counter.reducer,
weatherForecasts: WeatherForecasts.reducer,
loader: Loader.reducer,
content: Content.reducer,
blogCatalog: BlogCatalog.reducer,
blogCategories: BlogCategories.reducer,
blogFeatured: BlogFeatured.reducer,
blogItem: BlogItem.reducer,
shopCatalog: ShopCatalog.reducer
content: Content.reducer,
counter: Counter.reducer,
loader: Loader.reducer,
shopCatalog: ShopCatalog.reducer,
shopCategories: ShopCategories.reducer,
shopFeatured: ShopFeatured.reducer,
shopItem: ShopItem.reducer,
shopRelated: ShopRelated.reducer,
weatherForecasts: WeatherForecasts.reducer
}
// This type can be used as a hint on action creators so that its 'dispatch' and 'getState' params are

View File

@ -22,81 +22,46 @@ type KnownAction = RequestAction | ReceiveAction
export const actionCreators = {
requestBlogCatalog: (props?: GetBlogCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
const apiUrl = 'https://localhost:7151/api/BlogCatalog'
Get<Promise<GetBlogCatalogResponseModel>>(apiUrl, props)
Get<Promise<GetBlogCatalogResponseModel>>('https://localhost:7151/api/BlogCatalog', props)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...data })
})
console.log(getState().blogCatalog)
dispatch({ type: 'REQUEST_BLOG_CATALOG' })
}
}
const unloadedState: BlogCatalogState = {
featuredBlog: {
id: "",
slug: "demo-post",
badge: "demo",
image: {
src: "https://dummyimage.com/850x350/dee2e6/6c757d.jpg",
alt: "..."
},
title: "Lorem ipsum",
shortText: "",
text: "",
author: {
totalPages: 1,
currentPage: 1,
items: [
{
id: "",
nickName: "Admin",
slug: "demo-post",
badges: [ "demo" ],
image: {
src: "https://dummyimage.com/40x40/ced4da/6c757d",
src: "https://dummyimage.com/850x350/dee2e6/6c757d.jpg",
alt: "..."
}
},
created: new Date().toString(),
tags: [],
likes: 0
},
categories: [
{ id: "", text: "" }
],
blogItemsPagination: {
totalPages: 1,
currentPage: 1,
items: [
{
id: "",
slug: "demo-post",
badge: "demo",
image: {
src: "https://dummyimage.com/850x350/dee2e6/6c757d.jpg",
alt: "..."
},
title: "Lorem ipsum",
shortText: "",
text: "",
author: {
id: "",
nickName: "Admin",
image: {
src: "https://dummyimage.com/40x40/ced4da/6c757d",
alt: "..."
}
},
created: new Date().toString(),
tags: [],
likes: 0
},
]
},
title: "Lorem ipsum",
shortText: "",
text: "",
author: {
id: "",
nickName: "Admin",
image: {
src: "https://dummyimage.com/40x40/ced4da/6c757d",
alt: "..."
}
},
created: new Date().toString(),
tags: [],
likes: 0
},
],
isLoading: false
}

View File

@ -0,0 +1,65 @@
import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../'
import { GetBlogCategoriesRequestModel } from '../../models/requests'
import { GetBlogCategoriesResponseModel } from '../../models/responses'
import { Get } from '../../restClient'
export interface BlogCategoriesState extends GetBlogCategoriesResponseModel {
isLoading: boolean
}
interface RequestAction extends GetBlogCategoriesRequestModel {
type: 'REQUEST_BLOG_CATEGORIES'
}
interface ReceiveAction extends GetBlogCategoriesResponseModel {
type: 'RECEIVE_BLOG_CATEGORIES'
}
type KnownAction = RequestAction | ReceiveAction
export const actionCreators = {
requestBlogCategories: (props?: GetBlogCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
Get<Promise<GetBlogCategoriesResponseModel>>('https://localhost:7151/api/BlogCategories', props)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_CATEGORIES', ...data })
})
dispatch({ type: 'REQUEST_BLOG_CATEGORIES' })
}
}
const unloadedState: BlogCategoriesState = {
items: [
{ id: "", text: "Software" },
{ id: "", text: "Hardware" }
],
isLoading: false
}
export const reducer: Reducer<BlogCategoriesState> = (state: BlogCategoriesState | undefined, incomingAction: Action): BlogCategoriesState => {
if (state === undefined) {
return unloadedState
}
const action = incomingAction as KnownAction
switch (action.type) {
case 'REQUEST_BLOG_CATEGORIES':
return {
...state,
isLoading: true
}
case 'RECEIVE_BLOG_CATEGORIES':
return {
...action,
isLoading: false
}
}
return state
}

View File

@ -0,0 +1,135 @@
import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../'
import { GetBlogFeaturedRequestModel } from '../../models/requests'
import { GetBlogFeaturedResponseModel } from '../../models/responses'
import { Get } from '../../restClient'
export interface BlogFeaturedState extends GetBlogFeaturedResponseModel {
isLoading: boolean
}
interface RequestAction extends GetBlogFeaturedRequestModel {
type: 'REQUEST_BLOG_FEATURED'
}
interface ReceiveAction extends GetBlogFeaturedResponseModel {
type: 'RECEIVE_BLOG_FEATURED'
}
type KnownAction = RequestAction | ReceiveAction
export const actionCreators = {
requestBlogFeatured: (props?: GetBlogFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
Get<Promise<GetBlogFeaturedResponseModel>>('https://localhost:7151/api/BlogFeatured', props)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_FEATURED', ...data })
})
dispatch({ type: 'REQUEST_BLOG_FEATURED' })
}
}
const unloadedState: BlogFeaturedState = {
items: [
{
id: "",
slug: "demo-post",
badges: [ "demo" ],
image: {
src: "https://dummyimage.com/850x350/dee2e6/6c757d.jpg",
alt: "..."
},
title: "Lorem ipsum",
shortText: "",
author: {
id: "",
nickName: "Admin",
image: {
src: "https://dummyimage.com/40x40/ced4da/6c757d",
alt: "..."
}
},
created: new Date().toString(),
tags: [],
readTime: 10,
likes: 0
},
{
id: "",
slug: "demo-post",
badges: [ "demo" ],
image: {
src: "https://dummyimage.com/850x350/dee2e6/6c757d.jpg",
alt: "..."
},
title: "Lorem ipsum",
shortText: "",
author: {
id: "",
nickName: "Admin",
image: {
src: "https://dummyimage.com/40x40/ced4da/6c757d",
alt: "..."
}
},
created: new Date().toString(),
tags: [],
readTime: 10,
likes: 0
},
{
id: "",
slug: "demo-post",
badges: [ "demo" ],
image: {
src: "https://dummyimage.com/850x350/dee2e6/6c757d.jpg",
alt: "..."
},
title: "Lorem ipsum",
shortText: "",
author: {
id: "",
nickName: "Admin",
image: {
src: "https://dummyimage.com/40x40/ced4da/6c757d",
alt: "..."
}
},
created: new Date().toString(),
tags: [],
readTime: 10,
likes: 0
}
],
isLoading: false
}
export const reducer: Reducer<BlogFeaturedState> = (state: BlogFeaturedState | undefined, incomingAction: Action): BlogFeaturedState => {
if (state === undefined) {
return unloadedState
}
const action = incomingAction as KnownAction
switch (action.type) {
case 'REQUEST_BLOG_FEATURED':
return {
...state,
isLoading: true
}
case 'RECEIVE_BLOG_FEATURED':
return {
...action,
isLoading: false
}
}
return state
}

View File

@ -21,23 +21,47 @@ 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)
Get<Promise<GetBlogItemResponseModel>>('https://localhost:7151/api/BlogItem', 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 = {
id: "",
slug: "demo-post",
image: {
src: "https://dummyimage.com/900x400/ced4da/6c757d.jpg",
alt: "..."
},
badges: [
"Web Design",
"Freebies"
],
title: "Welcome to Blog Post!",
text: `<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>
<p className="fs-5 mb-4">The universe is large and old, and the ingredients for life as we know it are everywhere, so there's no reason to think that Earth would be unique in that regard. Whether of not the life became intelligent is a different question, and we'll see if we find that.</p>
<p className="fs-5 mb-4">If you get asteroids about a kilometer in size, those are large enough and carry enough energy into our system to disrupt transportation, communication, the food chains, and that can be a really bad day on Earth.</p>
<h2 className="fw-bolder mb-4 mt-5">I have odd cosmic thoughts every day</h2>
<p className="fs-5 mb-4">For me, the most fascinating interface is Twitter. I have odd cosmic thoughts every day and I realized I could hold them to myself or share them with people who might be interested.</p>
<p className="fs-5 mb-4">Venus has a runaway greenhouse effect. I kind of want to know what happened there because we're twirling knobs here on Earth without knowing the consequences of it. Mars once had running water. It's bone dry today. Something bad happened there as well.</p>`,
author: {
id: "",
nickName: "Admin",
image: {
src: "https://dummyimage.com/40x40/ced4da/6c757d",
alt: "..."
}
},
created: new Date().toString(),
tags: [ "react", "redux", "webapi" ],
comments: [
{
author: {

View File

@ -1,30 +1,28 @@
import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../'
import { GetStaticContentRequestModel } from '../../models/requests'
import { GetStaticContentResponseModel } from '../../models/responses'
import { GetContentRequestModel } from '../../models/requests'
import { GetContentResponseModel } from '../../models/responses'
import { Get } from '../../restClient'
export interface ContentState extends GetStaticContentResponseModel {
export interface ContentState extends GetContentResponseModel {
isLoading: boolean
}
interface RequestAction extends GetStaticContentRequestModel {
interface RequestAction extends GetContentRequestModel {
type: 'REQUEST_CONTENT'
}
interface ReceiveAction extends GetStaticContentResponseModel {
interface ReceiveAction extends GetContentResponseModel {
type: 'RECEIVE_CONTENT'
}
type KnownAction = RequestAction | ReceiveAction;
export const actionCreators = {
requestContent: (props?: GetStaticContentRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
requestContent: (props?: GetContentRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
const apiUrl = 'https://localhost:7151/api/StaticContent'
Get<Promise<GetStaticContentResponseModel>>(apiUrl, props)
Get<Promise<GetContentResponseModel>>('https://localhost:7151/api/Content', props)
.then(response => response)
.then((data) => {
if(data) {
@ -32,8 +30,6 @@ export const actionCreators = {
}
})
console.log(getState().content)
dispatch({ type: 'REQUEST_CONTENT' })
}
}
@ -99,7 +95,7 @@ const unloadedState: ContentState = {
]
},
testimonialsSection: {
items : [
items: [
{
text: "The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>yarn</code> commands such as <code>yarn test</code> or <code>yarn install</code>.",
reviewer: {
@ -112,66 +108,7 @@ const unloadedState: ContentState = {
]
},
featuredBlogsSection: {
title: "From our blog",
items: [
{
id: "",
slug: "blog-post-title",
image: { src: "https://dummyimage.com/600x350/ced4da/6c757d", alt: "..." },
badge: "news",
title: "Blog post title",
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
text: "",
author: {
id: "",
image: { src: "https://dummyimage.com/40x40/ced4da/6c757d", alt: "..." },
nickName: "Admin"
},
created: (new Date).toString(),
tags: [ "react", "redux", "webapi" ],
readTime: 10,
likes: 200,
},
{
id: "",
slug: "blog-post-title",
image: { src: "https://dummyimage.com/600x350/ced4da/6c757d", alt: "..." },
badge: "news",
title: "Blog post title",
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
text: "",
author: {
id: "",
image: { src: "https://dummyimage.com/40x40/ced4da/6c757d", alt: "..." },
nickName: "Admin"
},
created: (new Date).toString(),
tags: [ "react", "redux", "webapi" ],
readTime: 10,
likes: 200,
},
{
id: "",
slug: "blog-post-title",
image: { src: "https://dummyimage.com/600x350/ced4da/6c757d", alt: "..." },
badge: "news",
title: "Blog post title",
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
text: "",
author: {
id: "",
image: { src: "https://dummyimage.com/40x40/ced4da/6c757d", alt: "..." },
nickName: "Admin"
},
created: (new Date).toString(),
tags: [ "react", "redux", "webapi" ],
readTime: 10,
likes: 200,
}
]
title: "Featured blogs"
},
callToActionSection: {
title: "New products, delivered to you.",
@ -191,11 +128,34 @@ const unloadedState: ContentState = {
}
},
shopItem: {
productSection: {
availableQuantity: "Available Qty.",
addToCart: "Add to cart"
},
relatedProductsSection: {
title: "Related products"
}
},
blogCatalog: {
titleSection: {
title: "Welcome to Blog Home!",
text: "A Bootstrap 5 starter layout for your next blog homepage"
},
featuredBlogSection: {
readTime: "{date} Time to read: {readTime} min"
},
},
blogItem: {
titleSection: {
postedOnBy: "Posted on {date} by {nickName}"
},
commentsSection: {
leaveComment: "Join the discussion and leave a comment!"
}
},
isLoading: false

View File

@ -22,9 +22,7 @@ type KnownAction = RequestAction | ReceiveAction
export const actionCreators = {
requestShopCatalog: (props?: GetShopCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
const apiUrl = 'https://localhost:7151/api/ShopCatalog'
Get<Promise<GetShopCatalogResponseModel>>(apiUrl, props)
Get<Promise<GetShopCatalogResponseModel>>('https://localhost:7151/api/ShopCatalog', props)
.then(response => response)
.then(data => {
if(data)
@ -36,37 +34,33 @@ export const actionCreators = {
}
const unloadedState: ShopCatalogState = {
shopItemsPagination: {
totalPages: 1,
currentPage: 1,
totalPages: 1,
currentPage: 1,
items: [
{
id: '',
slug: "shop-catalog-item",
sku: "SKU-0",
image: { src: "https://dummyimage.com/450x300/dee2e6/6c757d.jpg", alt: "..." },
badges: [ "sale" ],
title: "Shop item title",
items: [
{
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
text: "",
author: {
id: '',
slug: "shop-catalog-item",
sku: "SKU-0",
image: { src: "https://dummyimage.com/450x300/dee2e6/6c757d.jpg", alt: "..." },
badge: "sale",
title: "Shop item title",
image: { src: "https://dummyimage.com/40x40/ced4da/6c757d", alt: "..." },
nickName: "Admin"
},
created: (new Date).toString(),
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
text: "",
author: {
id: '',
image: { src: "https://dummyimage.com/40x40/ced4da/6c757d", alt: "..." },
nickName: "Admin"
},
created: (new Date).toString(),
tags: [ "react", "redux", "webapi" ],
rating: 4.5,
price: 20,
newPrice: 10
}
]
},
tags: [ "react", "redux", "webapi" ],
rating: 4.5,
price: 20,
newPrice: 10
}
],
isLoading: false
}

View File

@ -0,0 +1,65 @@
import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../'
import { GetShopCategoriesRequestModel } from '../../models/requests'
import { GetShopCategoriesResponseModel } from '../../models/responses'
import { Get } from '../../restClient'
export interface ShopCategoriesState extends GetShopCategoriesResponseModel {
isLoading: boolean
}
interface RequestAction extends GetShopCategoriesRequestModel {
type: 'REQUEST_SHOP_CATEGORIES'
}
interface ReceiveAction extends GetShopCategoriesResponseModel {
type: 'RECEIVE_SHOP_CATEGORIES'
}
type KnownAction = RequestAction | ReceiveAction
export const actionCreators = {
requestShopCategories: (props?: GetShopCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
Get<Promise<GetShopCategoriesResponseModel>>('https://localhost:7151/api/ShopCategories', props)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_CATEGORIES', ...data })
})
dispatch({ type: 'REQUEST_SHOP_CATEGORIES' })
}
}
const unloadedState: ShopCategoriesState = {
items: [
{ id: "", text: "Software" },
{ id: "", text: "Hardware" }
],
isLoading: false
}
export const reducer: Reducer<ShopCategoriesState> = (state: ShopCategoriesState | undefined, incomingAction: Action): ShopCategoriesState => {
if (state === undefined) {
return unloadedState
}
const action = incomingAction as KnownAction
switch (action.type) {
case 'REQUEST_SHOP_CATEGORIES':
return {
...state,
isLoading: true
}
case 'RECEIVE_SHOP_CATEGORIES':
return {
...action,
isLoading: false
}
}
return state
}

View File

@ -0,0 +1,86 @@
import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../'
import { GetShopFeaturedRequestModel, } from '../../models/requests'
import { GetShopFeaturedResponseModel } from '../../models/responses'
import { Get } from '../../restClient'
export interface ShopFeaturedState extends GetShopFeaturedResponseModel {
isLoading: boolean
}
interface RequestAction extends GetShopFeaturedRequestModel {
type: 'REQUEST_SHOP_FEATURED'
}
interface ReceiveAction extends GetShopFeaturedResponseModel {
type: 'RECEIVE_SHOP_FEATURED'
}
type KnownAction = RequestAction | ReceiveAction
export const actionCreators = {
requestShopFeatured: (props?: GetShopFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
Get<Promise<GetShopFeaturedResponseModel>>('https://localhost:7151/api/ShopFeatured', props)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_FEATURED', ...data })
})
dispatch({ type: 'REQUEST_SHOP_FEATURED' })
}
}
const unloadedState: ShopFeaturedState = {
items: [
{
id: '',
slug: "shop-catalog-item",
sku: "SKU-0",
image: { src: "https://dummyimage.com/450x300/dee2e6/6c757d.jpg", alt: "..." },
badges: [ "sale" ],
title: "Shop item title",
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
text: "",
author: {
id: '',
image: { src: "https://dummyimage.com/40x40/ced4da/6c757d", alt: "..." },
nickName: "Admin"
},
created: (new Date).toString(),
tags: [ "react", "redux", "webapi" ],
rating: 4.5,
price: 20,
newPrice: 10
}
],
isLoading: false
}
export const reducer: Reducer<ShopFeaturedState> = (state: ShopFeaturedState | undefined, incomingAction: Action): ShopFeaturedState => {
if (state === undefined) {
return unloadedState
}
const action = incomingAction as KnownAction
switch (action.type) {
case 'REQUEST_SHOP_FEATURED':
return {
...state,
isLoading: true
}
case 'RECEIVE_SHOP_FEATURED':
return {
...action,
isLoading: false
}
}
return state
}

View File

@ -0,0 +1,143 @@
import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../'
import { GetShopItemRequestModel } from '../../models/requests'
import { GetShopItemResponseModel } from '../../models/responses'
import { Get } from '../../restClient'
export interface ShopItemState extends GetShopItemResponseModel {
isLoading: boolean
}
interface RequestAction extends GetShopItemRequestModel {
type: 'REQUEST_SHOP_ITEM'
}
interface ReceiveAction extends GetShopItemResponseModel {
type: 'RECEIVE_SHOP_ITEM'
}
type KnownAction = RequestAction | ReceiveAction
export const actionCreators = {
requestShopItem: (props: GetShopItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
Get<Promise<GetShopItemResponseModel>>('https://localhost:7151/api/ShopItem', props)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_ITEM', ...data })
})
dispatch({ type: 'REQUEST_SHOP_ITEM', slug: props.slug })
}
}
const unloadedState: ShopItemState = {
id: "",
slug: "demo-post",
image: {
src: "https://dummyimage.com/600x700/dee2e6/6c757d.jpg",
alt: "..."
},
sku: "BST-498",
badges: [
"Sale"
],
title: "Shop item template",
text: `<p className="lead">Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium at dolorem quidem modi. Nam sequi consequatur obcaecati excepturi alias magni, accusamus eius blanditiis delectus ipsam minima ea iste laborum vero?</p>`,
author: {
id: "",
nickName: "Admin",
image: {
src: "https://dummyimage.com/40x40/ced4da/6c757d",
alt: "..."
}
},
created: new Date().toString(),
tags: [ "react", "redux", "webapi" ],
price: 20,
newPrice: 10,
quantity: 10,
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<ShopItemState> = (state: ShopItemState | undefined, incomingAction: Action): ShopItemState => {
if (state === undefined) {
return unloadedState
}
const action = incomingAction as KnownAction
switch (action.type) {
case 'REQUEST_SHOP_ITEM':
return {
...state,
isLoading: true
}
case 'RECEIVE_SHOP_ITEM':
return {
...action,
isLoading: false
}
}
return state
}

View File

@ -0,0 +1,88 @@
import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../'
import { GetShopCatalogRequestModel } from '../../models/requests'
import { GetShopCatalogResponseModel } from '../../models/responses'
import { Get } from '../../restClient'
export interface ShopRelatedState extends GetShopCatalogResponseModel {
isLoading: boolean
}
interface RequestAction extends GetShopCatalogRequestModel {
type: 'REQUEST_SHOP_RELATED'
}
interface ReceiveAction extends GetShopCatalogResponseModel {
type: 'RECEIVE_SHOP_RELATED'
}
type KnownAction = RequestAction | ReceiveAction
export const actionCreators = {
requestShopRelated: (props?: GetShopCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
Get<Promise<GetShopCatalogResponseModel>>('https://localhost:7151/api/ShopRelated', props)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_RELATED', ...data })
})
dispatch({ type: 'REQUEST_SHOP_RELATED' })
}
}
const unloadedState: ShopRelatedState = {
totalPages: 1,
currentPage: 1,
items: [
{
id: '',
slug: "shop-catalog-item",
sku: "SKU-0",
image: { src: "https://dummyimage.com/450x300/dee2e6/6c757d.jpg", alt: "..." },
badges: [ "sale" ],
title: "Shop item title",
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
text: "",
author: {
id: '',
image: { src: "https://dummyimage.com/40x40/ced4da/6c757d", alt: "..." },
nickName: "Admin"
},
created: (new Date).toString(),
tags: [ "react", "redux", "webapi" ],
rating: 4.5,
price: 20,
newPrice: 10
}
],
isLoading: false
}
export const reducer: Reducer<ShopRelatedState> = (state: ShopRelatedState | undefined, incomingAction: Action): ShopRelatedState => {
if (state === undefined) {
return unloadedState
}
const action = incomingAction as KnownAction
switch (action.type) {
case 'REQUEST_SHOP_RELATED':
return {
...state,
isLoading: true
}
case 'RECEIVE_SHOP_RELATED':
return {
...action,
isLoading: false
}
}
return state
}

View File

@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks;
namespace Core.Models {
public class PaginationModel<T> {
public class PaginationModel<T> : ResponseModel {
public int TotalPages { get; set; }
public int CurrentPage { get; set; }
public List<T> Items { get; set; }

View File

@ -14,9 +14,9 @@ namespace WeatherForecast.Controllers;
[Route("api/[controller]")]
public class BlogCatalogController : ControllerBase {
private readonly ILogger<LoginController> _logger;
private readonly ILogger<BlogCatalogController> _logger;
public BlogCatalogController(ILogger<LoginController> logger) {
public BlogCatalogController(ILogger<BlogCatalogController> logger) {
_logger = logger;
}

View File

@ -0,0 +1,44 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using WeatherForecast.Models;
using WeatherForecast.Models.Responses;
namespace WeatherForecast.Controllers {
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class BlogCategoriesController : ControllerBase {
private readonly ILogger<BlogCategoriesController> _logger;
public BlogCategoriesController(ILogger<BlogCategoriesController> logger) {
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Get() {
var categories = new List<CategoryModel> {
new CategoryModel {
Id = Guid.NewGuid(),
Text = "Software"
},
new CategoryModel {
Id = Guid.NewGuid(),
Text = "Hardware"
},
};
var response = new GetBlogCategoriesResponseModel {
Items = categories
};
return Ok(response);
}
}
}

View File

@ -0,0 +1,57 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using WeatherForecast.Models;
using WeatherForecast.Models.Responses;
namespace WeatherForecast.Controllers {
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class BlogFeaturedController : ControllerBase {
private readonly ILogger<BlogFeaturedController> _logger;
public BlogFeaturedController(ILogger<BlogFeaturedController> logger) {
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Get() {
#if MOCK_SERVER
var blogItems = new List<BlogItemModel>();
for (int i = 0; i < 3; i++) {
blogItems.Add(new BlogItemModel {
Id = Guid.NewGuid(),
Slug = "blog-post-title",
Image = new ImageModel { Src = "https://dummyimage.com/600x350/ced4da/6c757d", Alt = "..." },
Badge = "news",
Title = "C# Blog post title",
ShortText = "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
Text = "",
Author = new AuthorModel {
Id = Guid.NewGuid(),
Image = new ImageModel { Src = "https://dummyimage.com/40x40/ced4da/6c757d", Alt = "..." },
NickName = "Admin"
},
Created = DateTime.UtcNow,
Tags = new List<string> { "react", "redux", "webapi" },
ReadTime = 10,
Likes = 200,
});
}
var result = new GetBlogFeaturedResponseModel(blogItems);
return Ok(result);
#else
return Ok();
#endif
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace WeatherForecast.Controllers {
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class BlogItemController : ControllerBase {
private readonly ILogger<BlogItemController> _logger;
public BlogItemController(ILogger<BlogItemController> logger) {
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Get() {
return Ok();
}
}
}

View File

@ -14,16 +14,16 @@ namespace WeatherForecast.Controllers;
[ApiController]
[AllowAnonymous]
[Route("api/[controller]")]
public class StaticContentController : ControllerBase {
public class ContentController : ControllerBase {
private readonly ILogger<StaticContentController> _logger;
private readonly ILogger<ContentController> _logger;
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
public StaticContentController(
ILogger<StaticContentController> logger
public ContentController(
ILogger<ContentController> logger
) {
_logger = logger;
}
@ -103,30 +103,7 @@ public class StaticContentController : ControllerBase {
var blogItems = new List<BlogItemModel>();
for (int i = 0; i < 3; i++) {
var blogItemModel = new BlogItemModel {
Id = Guid.NewGuid(),
Slug = "blog-post-title",
Image = new ImageModel { Src = "https://dummyimage.com/600x350/ced4da/6c757d", Alt = "..." },
Badge = "news",
Title = "Blog post title",
ShortText = "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
Text = "",
Author = new AuthorModel {
Id = Guid.NewGuid(),
Image = new ImageModel { Src = "https://dummyimage.com/40x40/ced4da/6c757d", Alt = "..." },
NickName = "Admin"
},
Created = DateTime.UtcNow,
Tags = new List<string> { "react", "redux", "webapi" },
ReadTime = 10,
Likes = 200,
};
blogItems.Add(blogItemModel);
}
var homePage = new HomePageModel {
@ -169,6 +146,7 @@ public class StaticContentController : ControllerBase {
new TestimonialModel {
Text = "The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>yarn</code> commands such as <code>yarn test</code> or <code>yarn install</code>.",
Reviewer = new ReviewerModel {
Id = Guid.NewGuid(),
Image = new ImageModel { Src = "https://dummyimage.com/40x40/ced4da/6c757d", Alt = "..." },
FullName = "Admin",
Position = "CEO, MAKS-IT"
@ -177,8 +155,7 @@ public class StaticContentController : ControllerBase {
}
},
FeaturedBlogsSection = new FeaturedBologsSectionModel {
Title = "From our blog",
Items = blogItems
Title = "From our blog"
},
CallToActionSection = new CallToActionSectionModel {
@ -199,14 +176,36 @@ public class StaticContentController : ControllerBase {
}
};
var shopItem = new ShopItemPageModel {
ProductSection = new ProductSectionModel {
AvailableQuantity = "Available Qty.",
AddToCart = "Add to cart"
},
RelatedProductsSection = new RelatedProductsSectionModel {
Title = "Related products"
}
};
var blogCatalogPage = new BlogCatalogPageModel {
TitleSection = new TitleSectionModel {
Title = "Welcome to Blog Home!",
Text = "A Bootstrap 5 starter layout for your next blog homepage"
},
FeaturedBlogSection = new FeaturedBlogSectionModel {
ReadTime = "{date} Time to read: {readTime} min"
}
};
return Ok(new GetStaticContentResponseModel {
var blogItem = new BlogItemPageModel {
TitleSection = new BlogTitleSectionModel {
PostedOnBy = "Posted on {date} by {nickName}"
},
CommentsSection = new CommentsSectionModel {
LeaveComment = "Join the discussion and leave a comment!"
}
};
return Ok(new GetContentResponseModel {
SiteName = "MAKS-IT",
Routes = routes,
@ -215,9 +214,14 @@ public class StaticContentController : ControllerBase {
TopMenu = topMenu,
SideMenu = sideMenu,
HomePage = homePage,
ShopCatalog = shopCatalogPage,
BlogCatalog = blogCatalogPage
ShopItem = shopItem,
BlogCatalog = blogCatalogPage,
Blogitem = blogItem
});
}
}

View File

@ -12,9 +12,9 @@ namespace WeatherForecast.Controllers;
[Route("api/[controller]")]
public class ShopCatalogController : ControllerBase {
private readonly ILogger<LoginController> _logger;
private readonly ILogger<ShopCatalogController> _logger;
public ShopCatalogController(ILogger<LoginController> logger) {
public ShopCatalogController(ILogger<ShopCatalogController> logger) {
_logger = logger;
}

View File

@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using WeatherForecast.Models;
namespace WeatherForecast.Controllers {
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class ShopCategoriesController : ControllerBase {
private readonly ILogger<ShopCategoriesController> _logger;
public ShopCategoriesController(ILogger<ShopCategoriesController> logger) {
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Get() {
var categories = new List<CategoryModel> {
new CategoryModel {
Id = Guid.NewGuid(),
Text = "Software"
},
new CategoryModel {
Id = Guid.NewGuid(),
Text = "Hardware"
},
};
return Ok(categories);
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace WeatherForecast.Controllers {
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class ShopFeaturedController : ControllerBase {
private readonly ILogger<ShopFeaturedController> _logger;
public ShopFeaturedController(ILogger<ShopFeaturedController> logger) {
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Get() {
return Ok();
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace WeatherForecast.Controllers {
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class ShopItemController : ControllerBase {
private readonly ILogger<ShopItemController> _logger;
public ShopItemController(ILogger<ShopItemController> logger) {
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Get() {
return Ok();
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace WeatherForecast.Controllers {
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class ShopRelatedController : ControllerBase {
private readonly ILogger<ShopRelatedController> _logger;
public ShopRelatedController(ILogger<ShopRelatedController> logger) {
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Get() {
return Ok();
}
}
}

View File

@ -1,6 +1,6 @@
namespace WeatherForecast.Models.Abstractions {
public abstract class PersonModel {
public Guid Id { get; set; }
public ImageModel Image { get; set; }
public ImageModel? Image { get; set; }
}
}

View File

@ -10,9 +10,9 @@
public string Title { get; set; }
public string ShortText { get; set; }
public string? ShortText { get; set; }
public string Text { get; set; }
public string? Text { get; set; }
public AuthorModel Author { get; set; }

View File

@ -4,9 +4,9 @@ namespace WeatherForecast.Models {
public class BlogItemModel : PostItemModel {
public int ReadTime { get; set; }
public int? ReadTime { get; set; }
public int Likes { get; set; }
public int? Likes { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace WeatherForecast.Models {
public class CommentModel {
public AuthorModel Author { get; set; }
public string Comment { get; set; }
public List<CommentModel>? Responses { get; set; }
}
}

View File

@ -0,0 +1,7 @@
using WeatherForecast.Models.Abstractions;
namespace WeatherForecast.Models.PageSections {
public class BlogTitleSectionModel : PageSectionModel {
public string PostedOnBy { get; set; }
}
}

View File

@ -0,0 +1,7 @@
using WeatherForecast.Models.Abstractions;
namespace WeatherForecast.Models.PageSections {
public class CommentsSectionModel : PageSectionModel {
public string LeaveComment { get; set; }
}
}

View File

@ -0,0 +1,7 @@
using WeatherForecast.Models.Abstractions;
namespace WeatherForecast.Models.PageSections {
public class FeaturedBlogSectionModel : PageSectionModel {
public string ReadTime { get; set; }
}
}

View File

@ -1,7 +1,5 @@
using WeatherForecast.Models.Abstractions;
namespace WeatherForecast.Models.PageSections {
public class FeaturedBologsSectionModel : PageSectionModel {
public List<BlogItemModel> Items { get; set; }
}
public class FeaturedBologsSectionModel : PageSectionModel { }
}

View File

@ -0,0 +1,8 @@
using WeatherForecast.Models.Abstractions;
namespace WeatherForecast.Models.PageSections {
public class ProductSectionModel : PageSectionModel {
public string AvailableQuantity { get; set; }
public string AddToCart { get; set; }
}
}

View File

@ -0,0 +1,7 @@
using WeatherForecast.Models.Abstractions;
namespace WeatherForecast.Models.PageSections {
public class RelatedProductsSectionModel : PageSectionModel {
}
}

View File

@ -4,5 +4,6 @@ using WeatherForecast.Models.PageSections;
namespace WeatherForecast.Models.Pages {
public class BlogCatalogPageModel : PageModel {
public TitleSectionModel TitleSection { get; set; }
public FeaturedBlogSectionModel FeaturedBlogSection { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using WeatherForecast.Models.Abstractions;
using WeatherForecast.Models.PageSections;
namespace WeatherForecast.Models.Pages {
public class BlogItemPageModel : PageModel {
public BlogTitleSectionModel TitleSection { get; set; }
public CommentsSectionModel CommentsSection { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using WeatherForecast.Models.Abstractions;
using WeatherForecast.Models.PageSections;
namespace WeatherForecast.Models.Pages {
public class ShopItemPageModel : PageModel {
public ProductSectionModel ProductSection { get; set; }
public RelatedProductsSectionModel RelatedProductsSection { get; set; }
}
}

View File

@ -0,0 +1,7 @@
using Core.Abstractions.Models;
namespace WeatherForecast.Models.Responses {
public class GetBlogCategoriesResponseModel : ResponseModel {
public List<CategoryModel> Items { get; set; }
}
}

View File

@ -0,0 +1,23 @@
using Core.Abstractions.Models;
namespace WeatherForecast.Models.Responses {
/// <summary>
///
/// </summary>
public class GetBlogFeaturedResponseModel : ResponseModel {
/// <summary>
///
/// </summary>
public List<BlogItemModel> Items { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="items"></param>
public GetBlogFeaturedResponseModel(List<BlogItemModel> items) {
Items = items;
}
}
}

View File

@ -2,7 +2,7 @@
using WeatherForecast.Models.Pages;
namespace WeatherForecast.Models.Responses {
public class GetStaticContentResponseModel : ResponseModel {
public class GetContentResponseModel : ResponseModel {
public string SiteName { get; set; }
public List<RouteModel> Routes { get; set; }
@ -13,7 +13,11 @@ namespace WeatherForecast.Models.Responses {
public List<MenuItemModel> SideMenu { get; set; }
public HomePageModel HomePage { get; set; }
public ShopCatalogPageModel ShopCatalog { get; set; }
public ShopItemPageModel ShopItem { get; set; }
public BlogCatalogPageModel BlogCatalog { get; set; }
public BlogItemPageModel Blogitem { get; set; }
}
}

View File

@ -2,11 +2,11 @@
namespace WeatherForecast.Models {
public class ShopItemModel : PostItemModel {
public List<ImageModel> Images { get; set; }
public List<ImageModel>? Images { get; set; }
public string Sku { get; set; }
public double Rating { get; set; }
public double? Rating { get; set; }
public double Price { get; set; }
public double NewPrice { get; set; }
public int Quantity { get; set; }
public double? NewPrice { get; set; }
public int? Quantity { get; set; }
}
}

View File

@ -31,6 +31,9 @@
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"publishAllPorts": true,
"useSSL": true
}

View File

@ -6,6 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>WeatherForecast</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DefineConstants>TRACE;DEBUG;MOCK_SERVER</DefineConstants>
</PropertyGroup>
<ItemGroup>