(feature): clone objects and refactoring

This commit is contained in:
Maksym Sadovnychyy 2023-04-14 23:17:48 +02:00
parent eb5334917d
commit 740a5d2d75
48 changed files with 652 additions and 377 deletions

View File

@ -1,5 +1,5 @@
{ {
"name": "react-redux-template", "name": "react-redux",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {

View File

@ -9,12 +9,17 @@ export interface ICategory {
} }
interface ICategories { interface ICategories {
totalPages: number,
currentPage: number,
items?: ICategory [] items?: ICategory []
} }
const Categories: FC<ICategories> = ({ export interface ICategoriesComponent extends ICategories {}
items = []
}) => { const Categories: FC<ICategoriesComponent> = (props) => {
const { items = [] } = props
const middleIndex = Math.ceil(items.length / 2) const middleIndex = Math.ceil(items.length / 2)
const firstHalf = items.splice(0, middleIndex) const firstHalf = items.splice(0, middleIndex)

View File

@ -0,0 +1,10 @@
const cloneObject = <T>(obj : T) => {
const json = JSON.stringify(obj)
const newObj: T = JSON.parse(json)
return newObj
}
export {
cloneObject
}

View File

@ -1,10 +1,14 @@
import { dateFormat, timeFormat } from './dateTimeFormat' import { dateFormat, timeFormat } from './dateTimeFormat'
import { findRoutes } from './findRoutes' import { findRoutes } from './findRoutes'
import { getKeyValue } from './getKeyValue' import { getKeyValue } from './getKeyValue'
import { cloneObject } from './cloneObject'
import { isSuccessStatusCode } from './isSuccessStatusCode'
export { export {
getKeyValue, getKeyValue,
dateFormat, dateFormat,
timeFormat, timeFormat,
findRoutes findRoutes,
cloneObject,
isSuccessStatusCode
} }

View File

@ -0,0 +1,7 @@
const isSuccessStatusCode = (statusCode: number) => {
return statusCode >= 200 && statusCode <= 299
}
export {
isSuccessStatusCode
}

View File

@ -12,7 +12,7 @@ import { actionCreators as blogCatalogActionCreators } from '../../../store/redu
import { Card, CardBody, CardImg, Col } from 'reactstrap' import { Card, CardBody, CardImg, Col } from 'reactstrap'
// Components // Components
import { IPaginationComponent, Pagination } from '../../../components/Pagination' import { ISSRPaginationComponent, SSRPagination } from '../../../components/Pagination'
// Functions // Functions
import { dateFormat } from '../../../functions' import { dateFormat } from '../../../functions'
@ -46,7 +46,6 @@ export interface IBlogItem {
} }
export interface IBlogItems { export interface IBlogItems {
path?: string
totalPages?: number, totalPages?: number,
currentPage?: number, currentPage?: number,
items?: IBlogItem [] items?: IBlogItem []
@ -56,7 +55,9 @@ export interface IBlogItemsSection {
readMore: string readMore: string
} }
export interface IBlogItemsComponent extends IBlogItemsSection, IBlogItems { } export interface IBlogItemsComponent extends IBlogItemsSection, IBlogItems {
path: string
}
const BlogItemsComponent: FC<IBlogItemsComponent> = (props) => { const BlogItemsComponent: FC<IBlogItemsComponent> = (props) => {
@ -65,6 +66,9 @@ const BlogItemsComponent: FC<IBlogItemsComponent> = (props) => {
const dispatch = useDispatch() const dispatch = useDispatch()
const navigate = useNavigate() const navigate = useNavigate()
return <> return <>
{items.map((item, index) => <Col key={index} className="lg-6 mb-3"> {items.map((item, index) => <Col key={index} className="lg-6 mb-3">
<Card> <Card>
@ -82,17 +86,20 @@ const BlogItemsComponent: FC<IBlogItemsComponent> = (props) => {
</Card> </Card>
</Col>)} </Col>)}
<Pagination {...{ <SSRPagination {...{
totalPages: totalPages, totalPages: totalPages,
currentPage: currentPage, currentPage: currentPage,
onClick: (nextPage) => { // onClick: (nextPage) => {
dispatch(blogCatalogActionCreators.requestBlogCatalog({ // dispatch(blogCatalogActionCreators.requestBlogCatalog({
currentPage: nextPage + "" // searchParams: {
})) // currentPage: nextPage + ""
// }
// }))
navigate(`${path}/${nextPage}`) // navigate(`${path}/${nextPage}`)
} // }
} as IPaginationComponent} /> linksPath: path
} as ISSRPaginationComponent} />
</> </>
} }

View File

@ -22,8 +22,7 @@ export interface IFeaturedBlogItem {
image: IImage, image: IImage,
badges: string [], badges: string [],
title: string, title: string,
shortText?: string, shortText: string,
text?: string,
author: IAuthor, author: IAuthor,
created: string, created: string,
tags: string [] tags: string []
@ -58,10 +57,11 @@ const FeaturedBlogComponent: FC<IFeaturedBlogComponent> = (props) => {
<CardBody className="p-4"> <CardBody className="p-4">
{item.badges.map((badge, index) => <div key={index} className="badge bg-primary bg-gradient rounded-pill mb-2">{badge}</div>)} {item.badges.map((badge, index) => <div key={index} className="badge bg-primary bg-gradient rounded-pill mb-2">{badge}</div>)}
<div className="small text-muted">{dateFormat(item.created)}</div>
<Link className="text-decoration-none link-dark stretched-link" to={`${path}/${currentPage}/${item.slug}`}> <Link className="text-decoration-none link-dark stretched-link" to={`${path}/${currentPage}/${item.slug}`}>
<h5 className="card-title mb-3">{item.title}</h5> <h5 className="card-title mb-3">{item.title}</h5>
</Link> </Link>
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: item.shortText ? item.shortText : '' }}></p> <p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: item.shortText }}></p>
</CardBody> </CardBody>
<CardFooter className="p-4 pt-0 bg-transparent border-top-0"> <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-end justify-content-between">

View File

@ -8,8 +8,8 @@ import { ApplicationState } from '../../../store'
// Reducers // Reducers
import { actionCreators as blogCatalogActionCreators } from '../../../store/reducers/BlogCatalog' 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 { actionCreators as blogCategoriesActionCreators } from '../../../store/reducers/BlogCategories'
import { actionCreators as blogFeaturedActionCreators } from '../../../store/reducers/BlogFeatured'
// Reactstrap // Reactstrap
import { Col, Container, Row } from 'reactstrap' import { Col, Container, Row } from 'reactstrap'
@ -17,11 +17,12 @@ import { Col, Container, Row } from 'reactstrap'
// Components // Components
import { TitleSection, ITitleSection } from './TitleComponent' import { TitleSection, ITitleSection } from './TitleComponent'
import { FeaturedBlogComponent, IFeaturedBlogComponent, IFeaturedBlogSection } from './FeaturedBlogComponent' import { FeaturedBlogComponent, IFeaturedBlogComponent, IFeaturedBlogSection } from './FeaturedBlogComponent'
import { BlogItemsComponent, IBlogItemsSection } from './BlogItemsComponent' import { BlogItemsComponent, IBlogItemsComponent, IBlogItemsSection } from './BlogItemsComponent'
import { Categories, Empty, Search } from '../../../components/SideWidgets' import { Categories, Empty, Search } from '../../../components/SideWidgets'
// Interfaces // Interfaces
import { IHeader } from '../../../interfaces' import { IHeader } from '../../../interfaces'
import { cloneObject } from '../../../functions'
export interface IBlogCatalogPage { export interface IBlogCatalogPage {
header: IHeader, header: IHeader,
@ -38,23 +39,40 @@ const BlogCatalog : FC<IBlogCatalogComponent> = () => {
const dispatch = useDispatch() const dispatch = useDispatch()
const { content, blogCatalog, blogCategories, blogFeatured } = useSelector((state: ApplicationState) => state) const { content, blogCatalog, blogCategories, blogFeatured } = useSelector((state: ApplicationState) => state)
const {header, titleSection, featuredBlogSection } = content.blogCatalog
// update categories slugs const { dateFormat, timeFormat } = content.localization
blogCategories.items = blogCategories.items.map(item => {
item.href = `${location.pathname.split('/').slice(0, 2).join('/')}/${item.href}` const { header, titleSection, blogItemsSection, featuredBlogSection } = content.blogCatalog
return item
}) useEffect(() => {
dispatch(blogFeaturedActionCreators.requestBlogFeatured())
dispatch(blogCategoriesActionCreators.requestBlogCategories())
}, [])
useEffect(() => { useEffect(() => {
dispatch(blogCatalogActionCreators.requestBlogCatalog({ dispatch(blogCatalogActionCreators.requestBlogCatalog({
searchParams: { searchParams: {
currentPage: params?.page ? params.page : "1" category: params?.category,
currentPage: params?.page
} }
})) }))
dispatch(blogFeaturedActionCreators.requestBlogFeatured()) }, [params.category, params.page])
dispatch(blogCategoriesActionCreators.requestBlogCategories())
}, []) const updateBlogCategories = () => {
const newBlogCategoies = cloneObject(blogCategories)
newBlogCategoies.items = newBlogCategoies.items.map(item => {
item.href = `${location.pathname.split('/').slice(0, 2).join('/')}/${item.href}`
return item
})
return newBlogCategoies
}
const updateBlogLinks = () => {
return location.pathname.split('/').slice(0, 3).join('/')
}
const blogItem = blogFeatured?.items[0] const blogItem = blogFeatured?.items[0]
@ -70,12 +88,16 @@ const BlogCatalog : FC<IBlogCatalogComponent> = () => {
featuredBlogSection featuredBlogSection
} as IFeaturedBlogComponent} /> } as IFeaturedBlogComponent} />
<Row> <Row>
<BlogItemsComponent path={location.pathname} {...blogCatalog} readMore={''} /> <BlogItemsComponent {...{
path: updateBlogLinks(),
...blogCatalog,
...blogItemsSection
} as IBlogItemsComponent }/>
</Row> </Row>
</Col> </Col>
<Col lg="3"> <Col lg="3">
<Search /> <Search />
<Categories {...blogCategories} /> <Categories {...updateBlogCategories()} />
<Empty/> <Empty/>
</Col> </Col>
</Row> </Row>

View File

@ -21,13 +21,18 @@ export interface IComment {
responses?: IComment [] responses?: IComment []
} }
export interface IComments {
path?: string
totalPages?: number,
currentPage?: number,
items?: IComment []
}
export interface ICommentsSection { export interface ICommentsSection {
leaveComment: string leaveComment: string
} }
export interface ICommentsComponent extends ICommentsSection { export interface ICommentsComponent extends ICommentsSection, IComments { }
items?: IComment []
}
const CommentsComponent: FC<ICommentsComponent> = (props) => { const CommentsComponent: FC<ICommentsComponent> = (props) => {

View File

@ -59,8 +59,8 @@ const BlogItem : FC<IBlogItemComponent> = () => {
const params = useParams() const params = useParams()
const dispatch = useDispatch() const dispatch = useDispatch()
const { content, blogItem } = useSelector((state: ApplicationState) => state) const { content, blogItem, comments } = useSelector((state: ApplicationState) => state)
const page = content?.blogItem const { titleSection, commentsSection } = content.blogItem
useEffect(() => { useEffect(() => {
if(params?.slug) if(params?.slug)
@ -73,7 +73,7 @@ const BlogItem : FC<IBlogItemComponent> = () => {
const blogItemTitle: ITitleComponent = { const blogItemTitle: ITitleComponent = {
title: blogItem?.title, title: blogItem?.title,
text: page?.titleSection?.text, text: titleSection?.text,
badges: blogItem?.badges, badges: blogItem?.badges,
image: blogItem?.image image: blogItem?.image
} }
@ -94,8 +94,8 @@ const BlogItem : FC<IBlogItemComponent> = () => {
</article> </article>
<CommentsComponent {...{ <CommentsComponent {...{
...page?.commentsSection, ...commentsSection,
//items: blogItem?.comments ...comments
} as ICommentsComponent} /> } as ICommentsComponent} />
</Col> </Col>

View File

@ -23,28 +23,38 @@ export interface IFeaturedBlogItem {
image: IImage, image: IImage,
badges: string [], badges: string [],
title: string, title: string,
shortText?: string, shortText: string,
text?: string,
author: IAuthor, author: IAuthor,
created: string, created: string,
tags: string [] tags: string []
readTime?: number, readTime: number,
likes?: number likes?: number
} }
export interface IFeaturedBlogsSection { export interface IFeaturedBlogsSection {
title: string, title: string,
text?: string text: string,
readTime: string
} }
interface IFeaturedBlogsFull extends IFeaturedBlogsSection { export interface IFeaturedBlogsFull extends IFeaturedBlogsSection {
items?: IFeaturedBlogItem [] items?: IFeaturedBlogItem []
} }
const FeaturedBlogsSection: FC<IFeaturedBlogsFull> = (props) => { const FeaturedBlogsSection: FC<IFeaturedBlogsFull> = (props) => {
const { title, text = "", items = [] } = props const { title, text, items = [] } = props
const readTimeString = (itemCreated: string, itemReadTime: number) : string => {
let { readTime } = props
if(readTime && itemCreated && itemReadTime)
readTime = readTime?.replace('{date}', dateFormat(itemCreated))
.replace('{readTime}', `${itemReadTime}`)
return readTime
}
return <section className="py-5"> return <section className="py-5">
<Container fluid className="px-5 my-5"> <Container fluid className="px-5 my-5">
@ -65,7 +75,7 @@ const FeaturedBlogsSection: FC<IFeaturedBlogsFull> = (props) => {
<Link className="text-decoration-none link-dark stretched-link" to="#!"> <Link className="text-decoration-none link-dark stretched-link" to="#!">
<h5 className="card-title mb-3">{item.title}</h5> <h5 className="card-title mb-3">{item.title}</h5>
</Link> </Link>
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: text ? text : '' }}></p> <p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: item.shortText }}></p>
</CardBody> </CardBody>
<CardFooter className="p-4 pt-0 bg-transparent border-top-0"> <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-end justify-content-between">
@ -73,7 +83,7 @@ const FeaturedBlogsSection: FC<IFeaturedBlogsFull> = (props) => {
<img className="rounded-circle me-3" {...item.author.image} /> <img className="rounded-circle me-3" {...item.author.image} />
<div className="small"> <div className="small">
<div className="fw-bold">{item.author.nickName}</div> <div className="fw-bold">{item.author.nickName}</div>
<div className="text-muted">{dateFormat(item.created)} &middot; {item.readTime}</div> <div className="text-muted" dangerouslySetInnerHTML={{ __html: readTimeString(item.created, item.readTime)}}></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,7 +13,7 @@ import { IHeader } from '../../interfaces'
import { TitleSection, ITitleSection } from './TitleSection' import { TitleSection, ITitleSection } from './TitleSection'
import { FeaturesSection, IFeaturesSection } from './FeaturesSection' import { FeaturesSection, IFeaturesSection } from './FeaturesSection'
import { TestimonialsSection, ITestimonialsSection } from './TestimonialsSection' import { TestimonialsSection, ITestimonialsSection } from './TestimonialsSection'
import { FeaturedBlogsSection, IFeaturedBlogsSection } from './FeaturedBlogsSection' import { FeaturedBlogsSection, IFeaturedBlogsFull, IFeaturedBlogsSection } from './FeaturedBlogsSection'
import { CallToActionSection, ICallToActionSection } from './CallToActionSection' import { CallToActionSection, ICallToActionSection } from './CallToActionSection'
@ -47,7 +47,9 @@ const Home : FC<IHomePageComponent> = () => {
<TitleSection {...titleSection} /> <TitleSection {...titleSection} />
<FeaturesSection {...featuresSection} /> <FeaturesSection {...featuresSection} />
<TestimonialsSection {...testimonialsSection} /> <TestimonialsSection {...testimonialsSection} />
<FeaturedBlogsSection items={blogFeatured?.items} {...featuredBlogsSection} /> <FeaturedBlogsSection {...{
items: blogFeatured?.items,
...featuredBlogsSection} as IFeaturedBlogsFull} />
<CallToActionSection {...callToActionSection} /> <CallToActionSection {...callToActionSection} />
</> </>
} }

View File

@ -113,7 +113,7 @@ const ShopItemsSection: FC<IShopItemComponent> = (props) => {
// navigate(`${path}/${nextPage}`) // navigate(`${path}/${nextPage}`)
// } // }
linksPath: path.split('/').slice(0, 3).join('/') linksPath: path
} as ISSRPaginationComponent} /> } as ISSRPaginationComponent} />
</> </>
} }

View File

@ -9,6 +9,7 @@ import { ApplicationState } from '../../../store'
// Reducers // Reducers
import { actionCreators as shopCatalogActionCreators } from '../../../store/reducers/ShopCatalog' import { actionCreators as shopCatalogActionCreators } from '../../../store/reducers/ShopCatalog'
import { actionCreators as shopCategoriesActionCreators } from '../../../store/reducers/ShopCategories' import { actionCreators as shopCategoriesActionCreators } from '../../../store/reducers/ShopCategories'
import { actionCreators as shopFeaturedActionCreators } from '../../../store/reducers/ShopFeatured'
// Reactstrap // Reactstrap
import { Col, Container, Row } from 'reactstrap' import { Col, Container, Row } from 'reactstrap'
@ -20,6 +21,7 @@ import { Categories, Empty, Search } from '../../../components/SideWidgets'
// Interfaces // Interfaces
import { IHeader } from '../../../interfaces' import { IHeader } from '../../../interfaces'
import { cloneObject } from '../../../functions'
export interface IShopCatalogPage { export interface IShopCatalogPage {
header: IHeader, header: IHeader,
@ -36,14 +38,14 @@ const ShopCatalog : FC<IShopCatalogComponent> = () => {
const { content, shopCatalog, shopCategories } = useSelector((state: ApplicationState) => state) const { content, shopCatalog, shopCategories } = useSelector((state: ApplicationState) => state)
const { currencySymbol } = content.localization const { dateFormat, timeFormat, currencySymbol } = content.localization
const { header, titleSection, shopItemsSection } = content.shopCatalog const { header, titleSection, shopItemsSection } = content.shopCatalog
// update categories slugs useEffect(() => {
shopCategories.items = shopCategories.items.map(item => {
item.href = `${location.pathname.split('/').slice(0, 2).join('/')}/${item.href}` dispatch(shopCategoriesActionCreators.requestShopCategories())
return item dispatch(shopFeaturedActionCreators.requestShopFeatured())
}) }, [])
useEffect(() => { useEffect(() => {
dispatch(shopCatalogActionCreators.requestShopCatalog({ dispatch(shopCatalogActionCreators.requestShopCatalog({
@ -52,8 +54,22 @@ const ShopCatalog : FC<IShopCatalogComponent> = () => {
currentPage: params?.page currentPage: params?.page
} }
})) }))
dispatch(shopCategoriesActionCreators.requestShopCategories()) }, [params.category, params.page])
}, [])
const updateShopCategories = () => {
const newShopCategoies = cloneObject(shopCategories)
newShopCategoies.items = newShopCategoies.items.map(item => {
item.href = `${location.pathname.split('/').slice(0, 2).join('/')}/${item.href}`
return item
})
return newShopCategoies
}
const updateShopLinks = () => {
return location.pathname.split('/').slice(0, 3).join('/')
}
return <> return <>
<TitleSection {...titleSection} /> <TitleSection {...titleSection} />
@ -64,7 +80,7 @@ const ShopCatalog : FC<IShopCatalogComponent> = () => {
<Row> <Row>
<ShopItemsSection {...{ <ShopItemsSection {...{
currencySymbol, currencySymbol,
path: location.pathname, path: updateShopLinks(),
...shopItemsSection, ...shopItemsSection,
...shopCatalog ...shopCatalog
} as IShopItemComponent} /> } as IShopItemComponent} />
@ -72,7 +88,7 @@ const ShopCatalog : FC<IShopCatalogComponent> = () => {
</Col> </Col>
<Col lg="3"> <Col lg="3">
<Search /> <Search />
<Categories {...shopCategories} /> <Categories {...updateShopCategories()} />
<Empty/> <Empty/>
</Col> </Col>
</Row> </Row>

View File

@ -0,0 +1,77 @@
import React, { FC } from 'react'
import { Card, CardBody } from 'reactstrap'
interface IImage {
src: string,
alt: string
}
interface IAuthor {
id: string,
image?: IImage
nickName: string
}
export interface IComment {
author: IAuthor,
comment: string,
responses?: IComment []
}
export interface IComments {
path?: string
totalPages?: number,
currentPage?: number,
items?: IComment []
}
export interface ICommentsSection {
leaveComment: string
}
export interface ICommentsComponent extends ICommentsSection, IComments { }
const CommentsComponent: FC<ICommentsComponent> = (props) => {
const { leaveComment, items = [] } = props
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={leaveComment}></textarea>
</form>
{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>
<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 {
CommentsComponent
}

View File

@ -25,7 +25,7 @@ interface IAuthor {
} }
interface IRelatedProduct { export interface IRelatedProduct {
id: string, id: string,
slug: string, slug: string,
image: IImage, image: IImage,

View File

@ -16,6 +16,7 @@ import { IRelatedProductsComponent, IRelatedProductsSection, RelatedProducts } f
import { IHeader } from '../../../interfaces' import { IHeader } from '../../../interfaces'
import { CommentsComponent, ICommentsComponent, ICommentsSection } from './CommentsComponent'
interface IImage { interface IImage {
src: string, src: string,
@ -59,14 +60,11 @@ interface IProductSection {
addToCart: string addToCart: string
} }
export interface IShopItemPage { export interface IShopItemPage {
header: IHeader, header: IHeader,
productSection: IProductSection productSection: IProductSection
relatedProductsSection: IRelatedProductsSection relatedProductsSection: IRelatedProductsSection,
commentsSection: ICommentsSection
} }
export interface IShopItemComponent extends IShopItemPage {} export interface IShopItemComponent extends IShopItemPage {}
@ -75,7 +73,10 @@ const ShopItem : FC<IShopItemComponent> = () => {
const params = useParams() const params = useParams()
const dispatch = useDispatch() const dispatch = useDispatch()
const { content, shopItem } = useSelector((state: ApplicationState) => state) const { content, shopItem, comments } = useSelector((state: ApplicationState) => state)
const { commentsSection } = content.shopItem
const { const {
currencySymbol = "" currencySymbol = ""
@ -129,6 +130,12 @@ const ShopItem : FC<IShopItemComponent> = () => {
<RelatedProducts {...{ <RelatedProducts {...{
} as IRelatedProductsComponent} /> } as IRelatedProductsComponent} />
<CommentsComponent {...{
...commentsSection,
...comments
} as ICommentsComponent} />
</> </>
} }

View File

@ -9,6 +9,7 @@ import { ApplicationState } from "../../store"
import './scss/style.scss' import './scss/style.scss'
import { IHeader } from "../../interfaces" import { IHeader } from "../../interfaces"
import { Post } from "../../restClient"
interface IStateProp { interface IStateProp {
[key: string]: string; [key: string]: string;
@ -28,6 +29,7 @@ interface ILink {
target: string, target: string,
anchorText: string anchorText: string
} }
export interface ISigninPage { export interface ISigninPage {
header: IHeader, header: IHeader,
@ -47,14 +49,7 @@ const Signin : FC<ISigninComponent> = () => {
const dispatch = useDispatch() const dispatch = useDispatch()
const { content } = useSelector((state: ApplicationState) => state) const { content } = useSelector((state: ApplicationState) => state)
const { const { title, email, password, dontHaveAnAccount, signUpLink, submit } = content.signIn
title,
email,
password,
dontHaveAnAccount,
signUpLink,
submit
} = content.signIn
const [state, hookState] = useState<IState>({ const [state, hookState] = useState<IState>({
username: '', username: '',
@ -74,7 +69,15 @@ const Signin : FC<ISigninComponent> = () => {
} }
const postSignIn = () => { const postSignIn = () => {
Post<Promise<any>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_ACCOUNT}`, { account: 'Authenticate' }, {
username: state.username,
password: state.password
}).then(response => response)
.then((data) => {
if(data) {
console.log(data)
}
})
} }
return <Container className="container"> return <Container className="container">

View File

@ -1,16 +1,44 @@
import axios from "axios" import axios from "axios"
import { IParams } from "./interfaces" import { IParams } from "./interfaces"
export interface FetchResult<T> {
interface FetchData {
status: number, status: number,
text: any data?: T
} }
const Post = () => { const Post = async <T>(apiUrl: string, pathParams?: IParams, data?: any) : Promise<FetchResult<T>> => {
const url = new URL(apiUrl)
if(pathParams) {
Object.keys(pathParams).forEach(key => {
if (typeof(pathParams[key]) !== undefined) {
url.pathname += `/${pathParams[key]}`
}
})
}
const requestParams = {
method: 'POST',
headers: { 'accept': 'application/json', 'content-type': 'application/json' },
data
}
const fetchData = await axios(url.toString(), requestParams)
.then(async fetchData => {
return {
status: fetchData.status,
data: fetchData.data as T
}
})
.catch(err => {
console.log(err)
return {
status: err.status
}
})
return fetchData
} }
const Get = async <T>(apiUrl: string, pathParams?: IParams, searchParams?: IParams): Promise<T | null> => { const Get = async <T>(apiUrl: string, pathParams?: IParams, searchParams?: IParams): Promise<T | null> => {

View File

@ -6,6 +6,7 @@ import * as BlogItem from './reducers/BlogItem'
import * as Counter from './reducers/Counter' import * as Counter from './reducers/Counter'
import * as Header from './reducers/Header' import * as Header from './reducers/Header'
import * as Comments from './reducers/Comments'
import * as Content from './reducers/Content' import * as Content from './reducers/Content'
import * as ShopCatalog from './reducers/ShopCatalog' import * as ShopCatalog from './reducers/ShopCatalog'
@ -24,6 +25,7 @@ export interface ApplicationState {
blogFeatured: BlogFeatured.BlogFeaturedState blogFeatured: BlogFeatured.BlogFeaturedState
blogItem: BlogItem.BlogItemState blogItem: BlogItem.BlogItemState
comments: Comments.CommentsState
content: Content.ContentState content: Content.ContentState
counter: Counter.CounterState counter: Counter.CounterState
@ -48,6 +50,7 @@ export const reducers = {
blogFeatured: BlogFeatured.reducer, blogFeatured: BlogFeatured.reducer,
blogItem: BlogItem.reducer, blogItem: BlogItem.reducer,
comments: Comments.reducer,
content: Content.reducer, content: Content.reducer,
counter: Counter.reducer, counter: Counter.reducer,

View File

@ -6,20 +6,21 @@ import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
import { IBlogItem } from '../../pages/Blog/Catalog/BlogItemsComponent' import { IBlogItem } from '../../pages/Blog/Catalog/BlogItemsComponent'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Request // Request
interface IGetBlogcatalogPathParams extends IParams {} interface IGetBlogcatalogPathParams extends IParams {}
interface IGeBlogCatalogSearchParams extends IParams {} interface IGeBlogCatalogSearchParams extends IParams {
interface IGetBlogCatalogRequestModel extends IRequest<IGetBlogcatalogPathParams, IGeBlogCatalogSearchParams> {
category?: string, category?: string,
searchText?: string, searchText?: string,
currentPage?: string, currentPage?: string,
itemsPerPage?: string itemsPerPage?: string
} }
interface IGetBlogCatalogRequestModel extends IRequest<IGetBlogcatalogPathParams, IGeBlogCatalogSearchParams> { }
// Response // Response
interface IGetBlogCatalogResponseModel extends IPagination<IBlogItem>, IResponse { } interface IGetBlogCatalogResponseModel extends IPagination<IBlogItem>, IResponse { }
@ -38,28 +39,7 @@ interface ReceiveAction extends IGetBlogCatalogResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
export const actionCreators = { const mockData: IGetBlogCatalogResponseModel = {
requestBlogCatalog: (props?: IGetBlogCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
const locale = process.env.REACT_APP_LOCALE
const searchParams = {...props?.searchParams, locale}
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
return
Get<Promise<IGetBlogCatalogResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...data })
})
dispatch({ type: 'REQUEST_BLOG_CATALOG' })
}
}
const unloadedState: BlogCatalogState = {
totalPages: 100, totalPages: 100,
currentPage: 1, currentPage: 1,
items: [ items: [
@ -72,8 +52,7 @@ const unloadedState: BlogCatalogState = {
alt: "..." alt: "..."
}, },
title: "Lorem ipsum", title: "Lorem ipsum",
shortText: "", shortText: "This is a blog short text...",
text: "",
author: { author: {
id: "", id: "",
nickName: "Admin", nickName: "Admin",
@ -96,8 +75,7 @@ const unloadedState: BlogCatalogState = {
alt: "..." alt: "..."
}, },
title: "Lorem ipsum", title: "Lorem ipsum",
shortText: "", shortText: "This is a blog short text...",
text: "",
author: { author: {
id: "", id: "",
nickName: "Admin", nickName: "Admin",
@ -120,8 +98,7 @@ const unloadedState: BlogCatalogState = {
alt: "..." alt: "..."
}, },
title: "Lorem ipsum", title: "Lorem ipsum",
shortText: "", shortText: "This is a blog short text...",
text: "",
author: { author: {
id: "", id: "",
nickName: "Admin", nickName: "Admin",
@ -144,8 +121,7 @@ const unloadedState: BlogCatalogState = {
alt: "..." alt: "..."
}, },
title: "Lorem ipsum", title: "Lorem ipsum",
shortText: "", shortText: "This is a blog short text...",
text: "",
author: { author: {
id: "", id: "",
nickName: "Admin", nickName: "Admin",
@ -159,7 +135,32 @@ const unloadedState: BlogCatalogState = {
likes: 0 likes: 0
} }
], ]
}
export const actionCreators = {
requestBlogCatalog: (props?: IGetBlogCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
dispatch({ type: 'REQUEST_BLOG_CATALOG' })
const locale = process.env.REACT_APP_LOCALE
const searchParams = {...props?.searchParams, locale}
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...cloneObject(mockData) })
return
}
Get<Promise<IGetBlogCatalogResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...data })
})
}
}
const unloadedState: BlogCatalogState = {
...cloneObject(mockData),
isLoading: false isLoading: false
} }

View File

@ -6,6 +6,7 @@ import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
import { ICategory } from '../../components/SideWidgets/Categories' import { ICategory } from '../../components/SideWidgets/Categories'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Request // Request
@ -33,15 +34,28 @@ interface ReceiveAction extends IGetBlogCategoriesResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
const mockData: IGetBlogCategoriesResponseModel = {
totalPages: 1,
currentPage: 1,
items: [
{ href: 'default', anchorText: "Default" },
{ href: 'software', anchorText: "Software" },
{ href: 'hardware', anchorText: "Hardware" }
]
}
export const actionCreators = { export const actionCreators = {
requestBlogCategories: (props?: IGetBlogCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => { requestBlogCategories: (props?: IGetBlogCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
dispatch({ type: 'REQUEST_BLOG_CATEGORIES' })
const locale = process.env.REACT_APP_LOCALE const locale = process.env.REACT_APP_LOCALE
const searchParams = {...props?.searchParams, locale} const searchParams = {...props?.searchParams, locale}
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
console.log(mockData)
dispatch({ type: 'RECEIVE_BLOG_CATEGORIES', ...cloneObject(mockData) })
return return
}
Get<Promise<IGetBlogCategoriesResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams) Get<Promise<IGetBlogCategoriesResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
.then(response => response) .then(response => response)
@ -49,19 +63,11 @@ export const actionCreators = {
if(data) if(data)
dispatch({ type: 'RECEIVE_BLOG_CATEGORIES', ...data }) dispatch({ type: 'RECEIVE_BLOG_CATEGORIES', ...data })
}) })
dispatch({ type: 'REQUEST_BLOG_CATEGORIES' })
} }
} }
const unloadedState: BlogCategoriesState = { const unloadedState: BlogCategoriesState = {
totalPages: 1, ...cloneObject(mockData),
currentPage: 1,
items: [
{ href: 'default', anchorText: "Default" },
{ href: 'software', anchorText: "Software" },
{ href: 'hardware', anchorText: "Hardware" }
],
isLoading: false isLoading: false
} }

View File

@ -6,6 +6,7 @@ import { IParams, IRequest, IResponse } from '../../interfaces'
import { IFeaturedBlogItem } from '../../pages/Blog/Catalog/FeaturedBlogComponent' import { IFeaturedBlogItem } from '../../pages/Blog/Catalog/FeaturedBlogComponent'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Request // Request
@ -36,28 +37,7 @@ interface ReceiveAction extends IGetBlogFeaturedResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
export const actionCreators = { const mockData: IGetBlogFeaturedResponseModel = {
requestBlogFeatured: (props?: IGetBlogFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
const locale = process.env.REACT_APP_LOCALE
const searchParams = {...props?.searchParams, locale}
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
return
Get<Promise<IGetBlogFeaturedResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEMS_FEAUTERED}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_FEATURED', ...data })
})
dispatch({ type: 'REQUEST_BLOG_FEATURED' })
}
}
const unloadedState: BlogFeaturedState = {
items: [ items: [
{ {
id: "", id: "",
@ -68,7 +48,7 @@ const unloadedState: BlogFeaturedState = {
alt: "..." alt: "..."
}, },
title: "Lorem ipsum", title: "Lorem ipsum",
shortText: "", shortText: "This is a blog short text...",
author: { author: {
id: "", id: "",
nickName: "Admin", nickName: "Admin",
@ -92,7 +72,7 @@ const unloadedState: BlogFeaturedState = {
alt: "..." alt: "..."
}, },
title: "Lorem ipsum", title: "Lorem ipsum",
shortText: "", shortText: "This is a blog short text...",
author: { author: {
id: "", id: "",
nickName: "Admin", nickName: "Admin",
@ -116,7 +96,7 @@ const unloadedState: BlogFeaturedState = {
alt: "..." alt: "..."
}, },
title: "Lorem ipsum", title: "Lorem ipsum",
shortText: "", shortText: "This is a blog short text...",
author: { author: {
id: "", id: "",
nickName: "Admin", nickName: "Admin",
@ -131,7 +111,32 @@ const unloadedState: BlogFeaturedState = {
readTime: 10, readTime: 10,
likes: 0 likes: 0
} }
], ]
}
export const actionCreators = {
requestBlogFeatured: (props?: IGetBlogFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
dispatch({ type: 'REQUEST_BLOG_FEATURED' })
const locale = process.env.REACT_APP_LOCALE
const searchParams = {...props?.searchParams, locale}
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
dispatch({ type: 'RECEIVE_BLOG_FEATURED', ...cloneObject(mockData) })
return
}
Get<Promise<IGetBlogFeaturedResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEMS_FEAUTERED}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_FEATURED', ...data })
})
}
}
const unloadedState: BlogFeaturedState = {
...cloneObject(mockData),
isLoading: false isLoading: false
} }

View File

@ -6,6 +6,7 @@ import { IParams, IRequest, IResponse } from '../../interfaces'
import { IBlogItem } from '../../pages/Blog/Item' import { IBlogItem } from '../../pages/Blog/Item'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Request // Request
@ -35,28 +36,7 @@ interface ReceiveAction extends IGetBlogItemResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
export const actionCreators = { const mockData: IGetBlogItemResponseModel = {
requestBlogItem: (props?: IGetBlogItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
const locale = process.env.REACT_APP_LOCALE
const searchParams = {...props?.searchParams, locale}
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
return
Get<Promise<IGetBlogItemResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEM}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_ITEM', ...data })
})
// dispatch({ type: 'REQUEST_BLOG_ITEM', slug: props.slug })
}
}
const unloadedState: BlogItemState = {
id: "", id: "",
slug: "demo-post", slug: "demo-post",
image: { image: {
@ -84,8 +64,32 @@ const unloadedState: BlogItemState = {
} }
}, },
created: new Date().toString(), created: new Date().toString(),
tags: [ "react", "redux", "webapi" ], tags: [ "react", "redux", "webapi" ]
}
export const actionCreators = {
requestBlogItem: (props?: IGetBlogItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
dispatch({ type: 'REQUEST_BLOG_ITEM' })
const locale = process.env.REACT_APP_LOCALE
const searchParams = {...props?.searchParams, locale}
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
dispatch({ type: 'RECEIVE_BLOG_ITEM', ...cloneObject(mockData) })
return
}
Get<Promise<IGetBlogItemResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEM}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_ITEM', ...data })
})
}
}
const unloadedState: BlogItemState = {
...cloneObject(mockData),
isLoading: false isLoading: false
} }

View File

@ -7,6 +7,7 @@ import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
import { IComment } from '../../pages/Blog/Item/CommentsComponent' import { IComment } from '../../pages/Blog/Item/CommentsComponent'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Request // Request
interface IGetCommentsPathParams extends IParams { } interface IGetCommentsPathParams extends IParams { }
@ -32,26 +33,7 @@ interface ReceiveAction extends IGetCommentsResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
export const actionCreators = { const mockData: IGetCommentsResponseModel = {
requestComments: (props?: IGetCommentsRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
return
/*
Get<Promise<IGetBlogCatalogResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...data })
})
dispatch({ type: 'REQUEST_BLOG_CATALOG' })
*/
}
}
const unloadedState: CommentsState = {
totalPages: 100, totalPages: 100,
currentPage: 1, currentPage: 1,
items: [ items: [
@ -102,9 +84,32 @@ const unloadedState: CommentsState = {
}, },
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." 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."
} }
]
}
], export const actionCreators = {
requestComments: (props?: IGetCommentsRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
dispatch({ type: 'RECEIVE_COMMENTS', ...cloneObject(mockData) })
return
}
/*
Get<Promise<IGetBlogCatalogResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...data })
})
dispatch({ type: 'REQUEST_BLOG_CATALOG' })
*/
}
}
const unloadedState: CommentsState = {
...cloneObject(mockData),
isLoading: false isLoading: false
} }

View File

@ -27,6 +27,7 @@ import { IShopCheckoutPage } from '../../pages/Shop/Checkout'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Request // Request
@ -92,25 +93,7 @@ interface ReceiveAction extends IGetContentResponseModel {
type KnownAction = RequestAction | ReceiveAction; type KnownAction = RequestAction | ReceiveAction;
export const actionCreators = { const mockData: IGetContentResponseModel = {
requestContent: (props?: IGetContentRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
return
Get<Promise<IGetContentResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CONTENT}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
.then(response => response)
.then((data) => {
if(data) {
dispatch({ type: 'RECEIVE_CONTENT', ...data })
}
})
dispatch({ type: 'REQUEST_CONTENT' })
}
}
const unloadedState: ContentState = {
siteName: "Contoso", siteName: "Contoso",
siteUrl: "https://contoso.com", siteUrl: "https://contoso.com",
@ -238,7 +221,9 @@ const unloadedState: ContentState = {
] ]
}, },
featuredBlogsSection: { featuredBlogsSection: {
title: "Featured blogs" title: "Featured blogs",
text: "Chek our best blog posts",
readTime: `${ReservedWords.date} &middot; Time to read: ${ReservedWords.readTime} min`
}, },
callToActionSection: { callToActionSection: {
title: "New products, delivered to you.", title: "New products, delivered to you.",
@ -287,6 +272,9 @@ const unloadedState: ContentState = {
relatedProductsSection: { relatedProductsSection: {
title: "Related products", title: "Related products",
addToCart: "Add to cart" addToCart: "Add to cart"
},
commentsSection: {
leaveComment: "Join the discussion and leave a comment!"
} }
}, },
@ -552,8 +540,30 @@ const unloadedState: ContentState = {
submit: { submit: {
title: "Sing up" title: "Sing up"
} }
}, }
}
export const actionCreators = {
requestContent: (props?: IGetContentRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
dispatch({ type: 'REQUEST_CONTENT' })
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
dispatch({ type: 'RECEIVE_CONTENT', ...cloneObject(mockData) })
return
}
Get<Promise<IGetContentResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CONTENT}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
.then(response => response)
.then((data) => {
if(data) {
dispatch({ type: 'RECEIVE_CONTENT', ...data })
}
})
}
}
const unloadedState: ContentState = {
...cloneObject(mockData),
isLoading: false isLoading: false
} }

View File

@ -6,6 +6,7 @@ import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
import { IShopItem } from '../../pages/Shop/Catalog/ShopItemsSection' import { IShopItem } from '../../pages/Shop/Catalog/ShopItemsSection'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Request // Request
@ -23,7 +24,6 @@ interface GetShopCatalogRequestModel extends IRequest<IGetShopcatalogPathParams,
// Response // Response
interface GetShopCatalogResponseModel extends IPagination<IShopItem>, IResponse {} interface GetShopCatalogResponseModel extends IPagination<IShopItem>, IResponse {}
export interface ShopCatalogState extends GetShopCatalogResponseModel { export interface ShopCatalogState extends GetShopCatalogResponseModel {
isLoading: boolean isLoading: boolean
} }
@ -38,28 +38,7 @@ interface ReceiveAction extends GetShopCatalogResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
export const actionCreators = { const mockData: GetShopCatalogResponseModel = {
requestShopCatalog: (props?: GetShopCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
const locale = process.env.REACT_APP_LOCALE
const searchParams = { ...props?.searchParams, locale }
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
return
Get<Promise<GetShopCatalogResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_SITEID}/${process.env.REACT_APP_SHOPITEMS}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_CATALOG', ...data })
})
dispatch({ type: 'REQUEST_SHOP_CATALOG' })
}
}
const unloadedState: ShopCatalogState = {
totalPages: 100, totalPages: 100,
currentPage: 1, currentPage: 1,
items: [ items: [
@ -159,7 +138,32 @@ const unloadedState: ShopCatalogState = {
price: 20, price: 20,
newPrice: 10 newPrice: 10
} }
], ]
}
export const actionCreators = {
requestShopCatalog: (props?: GetShopCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
dispatch({ type: 'REQUEST_SHOP_CATALOG' })
const locale = process.env.REACT_APP_LOCALE
const searchParams = { ...props?.searchParams, locale }
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
dispatch({ type: 'RECEIVE_SHOP_CATALOG', ...cloneObject(mockData) })
return
}
Get<Promise<GetShopCatalogResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_SITEID}/${process.env.REACT_APP_SHOPITEMS}`, props?.pathParams, searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_CATALOG', ...data })
})
}
}
const unloadedState: ShopCatalogState = {
...cloneObject(mockData),
isLoading: false isLoading: false
} }

View File

@ -6,6 +6,7 @@ import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
import { ICategory } from '../../components/SideWidgets/Categories' import { ICategory } from '../../components/SideWidgets/Categories'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Request // Request
interface GetShopCategoriesPathParams extends IParams { } interface GetShopCategoriesPathParams extends IParams { }
@ -33,11 +34,24 @@ interface ReceiveAction extends GetShopCategoriesResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
const mockData: GetShopCategoriesResponseModel = {
totalPages: 1,
currentPage: 1,
items: [
{ href: 'default', anchorText: "Default" },
{ href: 'software', anchorText: "Software" },
{ href: 'hardware', anchorText: "Hardware" }
]
}
export const actionCreators = { export const actionCreators = {
requestShopCategories: (props?: GetShopCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => { requestShopCategories: (props?: GetShopCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
dispatch({ type: 'REQUEST_SHOP_CATEGORIES' })
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
dispatch({ type: 'RECEIVE_SHOP_CATEGORIES', ...cloneObject(mockData) })
return return
}
Get<Promise<GetShopCategoriesResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams) Get<Promise<GetShopCategoriesResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
.then(response => response) .then(response => response)
@ -45,19 +59,11 @@ export const actionCreators = {
if(data) if(data)
dispatch({ type: 'RECEIVE_SHOP_CATEGORIES', ...data }) dispatch({ type: 'RECEIVE_SHOP_CATEGORIES', ...data })
}) })
dispatch({ type: 'REQUEST_SHOP_CATEGORIES' })
} }
} }
const unloadedState: ShopCategoriesState = { const unloadedState: ShopCategoriesState = {
totalPages: 1, ...cloneObject(mockData),
currentPage: 1,
items: [
{ href: 'default', anchorText: "Default" },
{ href: 'software', anchorText: "Software" },
{ href: 'hardware', anchorText: "Hardware" }
],
isLoading: false isLoading: false
} }

View File

@ -3,10 +3,11 @@ import { AppThunkAction } from '../'
// Interfaces // Interfaces
import { IParams, IRequest, IResponse } from '../../interfaces' import { IParams, IRequest, IResponse } from '../../interfaces'
import { IRelatedProduct } from '../../pages/Shop/Item/RelatedProducts' import { IRelatedProduct, IRelatedProducts } from '../../pages/Shop/Item/RelatedProducts'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Request // Request
interface IGetShopFeaturedSearchParams extends IParams { } interface IGetShopFeaturedSearchParams extends IParams { }
@ -35,24 +36,7 @@ interface ReceiveAction extends IGetShopFeaturedResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
export const actionCreators = { const mockData: IGetShopFeaturedResponseModel = {
requestShopFeatured: (props?: IGetShopFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
return
Get<Promise<IGetShopFeaturedResponseModel>>('https://localhost:7151/api/ShopFeatured', props?.pathParams, props?.searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_FEATURED', ...data })
})
dispatch({ type: 'REQUEST_SHOP_FEATURED' })
}
}
const unloadedState: ShopFeaturedState = {
items: [ items: [
{ {
id: '', id: '',
@ -78,7 +62,30 @@ const unloadedState: ShopFeaturedState = {
price: 20, price: 20,
newPrice: 10 newPrice: 10
} }
], ]
}
export const actionCreators = {
requestShopFeatured: (props?: IGetShopFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
dispatch({ type: 'RECEIVE_SHOP_FEATURED', ...cloneObject(mockData) })
return
}
Get<Promise<IGetShopFeaturedResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_SHOPITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_FEATURED', ...data })
})
dispatch({ type: 'REQUEST_SHOP_FEATURED' })
}
}
const unloadedState: ShopFeaturedState = {
...cloneObject(mockData),
isLoading: false isLoading: false
} }

View File

@ -6,6 +6,7 @@ import { IParams, IRequest, IResponse } from '../../interfaces'
import { IShopItem } from '../../pages/Shop/Item' import { IShopItem } from '../../pages/Shop/Item'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Reuest // Reuest
@ -36,24 +37,7 @@ interface ReceiveAction extends GetShopItemResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
export const actionCreators = { const mockData: GetShopItemResponseModel = {
requestShopItem: (props?: GetShopItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
return
Get<Promise<GetShopItemResponseModel>>('https://localhost:7151/api/ShopItem', props?.pathParams, props?.searchParams)
.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: "", id: "",
slug: "demo-post", slug: "demo-post",
image: { image: {
@ -85,58 +69,29 @@ const unloadedState: ShopItemState = {
price: 20, price: 20,
newPrice: 10, newPrice: 10,
quantity: 10, quantity: 10
}
// comments: [ export const actionCreators = {
// { requestShopItem: (props?: GetShopItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
// author: { dispatch({ type: 'REQUEST_SHOP_ITEM' })
// id: "",
// nickName: "Commenter Name 1",
// image: {
// src: `${process.env.REACT_APP_FRONTEND}/Image/50x50/ced4da/6c757d`,
// 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: [ if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
// { dispatch({ type: 'RECEIVE_SHOP_ITEM', ...cloneObject(mockData) })
// author: { return
// id: "", }
// nickName: "Commenter Name 4",
// image: {
// src: `${process.env.REACT_APP_FRONTEND}/Image/50x50/ced4da/6c757d`,
// 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: `${process.env.REACT_APP_FRONTEND}/Image/50x50/ced4da/6c757d`,
// alt: "..."
// }
// },
// comment: "When you put money directly to a problem, it makes a good headline."
// }
// ]
// },
// {
// author: {
// id: "",
// nickName: "Commenter Name 2",
// image: {
// src: `${process.env.REACT_APP_FRONTEND}/Image/50x50/ced4da/6c757d`,
// 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."
// }
// ], Get<Promise<GetShopItemResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_SHOPITEM}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_ITEM', ...data })
})
}
}
const unloadedState: ShopItemState = {
...cloneObject(mockData),
isLoading: false isLoading: false
} }

View File

@ -2,9 +2,10 @@ import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../' import { AppThunkAction } from '../'
import { IParams, IRequest, IResponse } from '../../interfaces' import { IParams, IRequest, IResponse } from '../../interfaces'
import { IRelatedProduct } from '../../pages/Shop/Item/RelatedProducts' import { IRelatedProduct, IRelatedProducts } from '../../pages/Shop/Item/RelatedProducts'
import { Get } from '../../restClient' import { Get } from '../../restClient'
import { cloneObject } from '../../functions'
// Resquest // Resquest
interface IGetShopRelatedPathParams extends IParams {} interface IGetShopRelatedPathParams extends IParams {}
@ -33,24 +34,7 @@ interface ReceiveAction extends IGetShopRelatedResponseModel {
type KnownAction = RequestAction | ReceiveAction type KnownAction = RequestAction | ReceiveAction
export const actionCreators = { const mockData: IGetShopRelatedResponseModel = {
requestShopRelated: (props?: IGetShopRelatedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
return
Get<Promise<IGetShopRelatedResponseModel>>('https://localhost:7151/api/ShopRelated', props?.pathParams, props?.searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_RELATED', ...data })
})
dispatch({ type: 'REQUEST_SHOP_RELATED' })
}
}
const unloadedState: ShopRelatedState = {
items: [ items: [
{ {
id: '', id: '',
@ -148,7 +132,29 @@ const unloadedState: ShopRelatedState = {
price: 20, price: 20,
newPrice: 10 newPrice: 10
} }
], ]
}
export const actionCreators = {
requestShopRelated: (props?: IGetShopRelatedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
dispatch({ type: 'REQUEST_SHOP_RELATED' })
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
dispatch({ type: 'RECEIVE_SHOP_RELATED', ...cloneObject(mockData) })
return
}
Get<Promise<IGetShopRelatedResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_SITEID}/${process.env.REACT_APP_SHOPITEMS}`, props?.pathParams, props?.searchParams)
.then(response => response)
.then(data => {
if(data)
dispatch({ type: 'RECEIVE_SHOP_RELATED', ...data })
})
}
}
const unloadedState: ShopRelatedState = {
...cloneObject(mockData),
isLoading: false isLoading: false
} }

View File

@ -15,6 +15,8 @@ services:
volumes: volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
depends_on:
- weatherforecast
networks: networks:
- "my-network" - "my-network"
@ -28,10 +30,65 @@ services:
volumes: volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
depends_on:
- placement
- redis
- mongo
networks: networks:
- "my-network" - "my-network"
weatherforecast-dapr:
image: "daprio/daprd:edge"
command: [
"./daprd",
"-app-id", "weatherforecast",
"-placement-host-address", "placement:50006",
"-components-path", "/components"
]
volumes:
- "./docker-compose/dapr/components/:/components"
depends_on:
- weatherforecast
network_mode: "service:weatherforecast"
############################
# Dapr dashboard
# https://github.com/dapr/dashboard/pull/217
############################
dashboard:
image: daprio/dashboard:latest
ports:
- "8080:8080"
depends_on:
- placement
- redis
networks:
- "my-network"
############################
# Dapr placement service
############################
placement:
image: "daprio/dapr"
command: ["./placement", "-port", "50006"]
ports:
- "50006:50006"
networks:
- "my-network"
############################
# Redis state store
############################
redis:
image: "redis:alpine"
ports:
- "6380:6379"
networks:
- "my-network"
############################
# MongoDB
############################
mongo: mongo:
image: mongo image: mongo
restart: always restart: always
@ -45,6 +102,9 @@ services:
networks: networks:
- "my-network" - "my-network"
############################
# Mongo Express
############################
mongo-express: mongo-express:
image: mongo-express image: mongo-express
restart: always restart: always

View File

@ -3,4 +3,4 @@ WiredTiger 10.0.2: (December 21, 2021)
WiredTiger version WiredTiger version
major=10,minor=0,patch=2 major=10,minor=0,patch=2
file:WiredTiger.wt file:WiredTiger.wt
access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.126190=(addr="018381e452c7a52b8481e4d7b9659f8581e46348db2e808080e3023fc0e3010fc0",order=126190,time=1680809094,size=81920,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=1524,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=379335,run_write_gen=377166)),checkpoint_backup_info=,checkpoint_lsn=(78,597248) access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.127717=(addr="019681e4fb9f7e379f81e4f489d9b8a081e4070e71ea808080e301ffc0e3010fc0",order=127717,time=1681378661,size=81920,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=188,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=383947,run_write_gen=383679)),checkpoint_backup_info=,checkpoint_lsn=(82,75904)