(feat): reducers and interfaces refactoring
This commit is contained in:
parent
49c262d5be
commit
9643070e86
@ -4,25 +4,26 @@ import { Route, Routes, useLocation } from 'react-router'
|
||||
|
||||
// Redux
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { actionCreators as settingsActionCreators } from './store/reducers/Settings'
|
||||
import { actionCreators as settingsActionCreators } from './store/reducers/Content'
|
||||
|
||||
// Components
|
||||
import { DynamicLayout } from './layouts'
|
||||
import { DynamicPage } from './pages'
|
||||
import { IReduxState, IRoute } from './interfaces'
|
||||
import { IRouteModel } from './models'
|
||||
import { ApplicationState } from './store'
|
||||
|
||||
|
||||
interface IRouteProp {
|
||||
path: string,
|
||||
element?: JSX.Element
|
||||
}
|
||||
|
||||
const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) => {
|
||||
|
||||
const NestedRoutes = (routes: IRouteModel[], tag: string | undefined = undefined) => {
|
||||
if(!Array.isArray(routes)) return
|
||||
|
||||
return routes.map((route: IRoute, index: number) => {
|
||||
return routes.map((route: IRouteModel, index: number) => {
|
||||
const routeProps: IRouteProp = {
|
||||
path: route.path
|
||||
path: route.target
|
||||
}
|
||||
|
||||
if (route.component) {
|
||||
@ -30,6 +31,8 @@ const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) =>
|
||||
routeProps.element = tag ? <DynamicLayout tag={tag}>{page}</DynamicLayout> : page
|
||||
}
|
||||
|
||||
|
||||
|
||||
return <Route key={index} { ...routeProps }>{Array.isArray(route.childRoutes) ? NestedRoutes(route.childRoutes, tag) : ''}</Route>
|
||||
})
|
||||
}
|
||||
@ -37,10 +40,10 @@ const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) =>
|
||||
const App: FC = () => {
|
||||
const { pathname } = useLocation()
|
||||
const dispatch = useDispatch()
|
||||
const { routes, adminRoutes, serviceRoutes } = useSelector((state: IReduxState) => state.settings)
|
||||
const state = useSelector((state: ApplicationState) => state.content)
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(settingsActionCreators.requestSettings())
|
||||
dispatch(settingsActionCreators.requestContent())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@ -52,9 +55,9 @@ const App: FC = () => {
|
||||
|
||||
return <>
|
||||
<Routes>
|
||||
{ NestedRoutes(routes, 'PublicLayout') }
|
||||
{ NestedRoutes(adminRoutes, 'AdminLayout') }
|
||||
{ NestedRoutes(serviceRoutes) }
|
||||
{state?.routes ? NestedRoutes(state.routes, 'PublicLayout') : ''}
|
||||
{state?.adminRoutes ? NestedRoutes(state.adminRoutes, 'AdminLayout') : ''}
|
||||
{state?.serviceRoutes ? NestedRoutes(state.serviceRoutes) : ''}
|
||||
</Routes>
|
||||
</>
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import { FeatherIcon } from '../FeatherIcons'
|
||||
import { ICreateIconProps, ICreateIconResponse, IFeatherRating } from './interfaces'
|
||||
|
||||
const FeatherRating: FC<IFeatherRating> = ({
|
||||
value,
|
||||
value = 0,
|
||||
icons = {
|
||||
complete: { icon: "star", color: '#ffbf00' },
|
||||
half: { icon: "star", color: '#ffdf80' },
|
||||
|
||||
24
clientapp/src/controllers/blogCatalog.ts
Normal file
24
clientapp/src/controllers/blogCatalog.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { IBlogItemsPaginationModel, IBlogItemModel, ICategoryModel } from "../models"
|
||||
import { Get } from "../restClient"
|
||||
|
||||
const apiUrl = 'https://localhost:59018/api/BlogCatalog'
|
||||
|
||||
export interface IGetBlogsRequest {
|
||||
[key: string]: string | undefined
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
export interface IGetBlogCatalogResponse {
|
||||
featuredBlog?: IBlogItemModel,
|
||||
blogItemsPagination?: IBlogItemsPaginationModel,
|
||||
categories?: ICategoryModel []
|
||||
}
|
||||
|
||||
const GetBlogCatalog = async (props?: IGetBlogsRequest): Promise<IGetBlogCatalogResponse> => await Get<Promise<IGetBlogCatalogResponse>>(apiUrl, props)
|
||||
|
||||
export {
|
||||
GetBlogCatalog
|
||||
}
|
||||
3
clientapp/src/controllers/blogItem.ts
Normal file
3
clientapp/src/controllers/blogItem.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { IBlogItemModel, ICategoryModel } from "../models"
|
||||
|
||||
const apiUrl = 'https://localhost:59018/api/Blog'
|
||||
22
clientapp/src/controllers/shopCatalog.ts
Normal file
22
clientapp/src/controllers/shopCatalog.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { IShopItemsPaginationModel } from "../models"
|
||||
import { Get } from "../restClient"
|
||||
|
||||
const apiUrl = 'https://localhost:59018/api/ShopCatalog'
|
||||
|
||||
export interface IGetShopCatalogRequest {
|
||||
[key: string]: string | undefined
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
export interface IGetShopCatalogResponse {
|
||||
shopItemsPagination?: IShopItemsPaginationModel,
|
||||
}
|
||||
|
||||
const GetShopCatalog = async (props?: IGetShopCatalogRequest): Promise<IGetShopCatalogResponse> => await Get<Promise<IGetShopCatalogResponse>>(apiUrl, props)
|
||||
|
||||
export {
|
||||
GetShopCatalog
|
||||
}
|
||||
27
clientapp/src/controllers/staticContent.ts
Normal file
27
clientapp/src/controllers/staticContent.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { IMenuItemModel, IPageModel, IRouteModel } from "../models"
|
||||
import { Get } from "../restClient"
|
||||
|
||||
const apiUrl = 'https://localhost:59018/api/StaticContent'
|
||||
|
||||
export interface IGetStaticContentRequest {
|
||||
[key: string]: string | undefined
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export interface IGetStaticContetnResponse {
|
||||
siteName: string,
|
||||
|
||||
routes: IRouteModel [],
|
||||
adminRoutes?: IRouteModel [],
|
||||
serviceRoutes?: IRouteModel [],
|
||||
|
||||
topMenu?: IMenuItemModel [],
|
||||
sideMenu?: IMenuItemModel [],
|
||||
pages?: IPageModel []
|
||||
}
|
||||
|
||||
const GetStaticContent = async (props?: IGetStaticContentRequest): Promise<IGetStaticContetnResponse> => await Get<Promise<IGetStaticContetnResponse>>(apiUrl, props)
|
||||
|
||||
export {
|
||||
GetStaticContent
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
import { IBlogItemModel, ICategoryModel, IFetchResult } from "./models"
|
||||
|
||||
const apiUrl = 'https://localhost:59018/api/Blog'
|
||||
@ -1,53 +0,0 @@
|
||||
import { IBlogItemsPaginationModel, IBlogItemModel, ICategoryModel, IFetchResult } from "./models"
|
||||
|
||||
const apiUrl = 'https://localhost:59018/api/Blogs'
|
||||
|
||||
export interface IGetBlogsRequest {
|
||||
[key: string]: string | undefined
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
export interface IGetBlogsResponse {
|
||||
featuredBlog?: IBlogItemModel,
|
||||
blogItemsPagination?: IBlogItemsPaginationModel,
|
||||
categories?: ICategoryModel []
|
||||
}
|
||||
|
||||
const GetBlogs = async (props?: IGetBlogsRequest): Promise<IGetBlogsResponse> => {
|
||||
const url = new URL(apiUrl)
|
||||
|
||||
if(props) {
|
||||
Object.keys(props).forEach(key => {
|
||||
if (typeof(props[key]) !== undefined) {
|
||||
url.searchParams.append(key, props[key] as string)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const requestParams = {
|
||||
method: 'GET',
|
||||
headers: { 'accept': 'application/json', 'content-type': 'application/json' },
|
||||
}
|
||||
|
||||
console.log(`invoke:`, url.toString())
|
||||
|
||||
const fetchData = await fetch(url.toString(), requestParams)
|
||||
.then(async fetchData => {
|
||||
return {
|
||||
status: fetchData.status,
|
||||
text: await fetchData.text()
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
return JSON.parse((fetchData as IFetchResult).text) as IGetBlogsResponse
|
||||
}
|
||||
|
||||
export {
|
||||
GetBlogs
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
const apiUrl = 'https://localhost:59018/api/Blogs'
|
||||
|
||||
export interface IGetBlogsRequest {
|
||||
[key: string]: string | undefined
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
export interface IBlogsResponse {
|
||||
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { ISettingsState } from "../store/reducers/Settings"
|
||||
|
||||
export interface IReduxState {
|
||||
settings: ISettingsState
|
||||
}
|
||||
|
||||
export interface IRoute {
|
||||
path: string,
|
||||
component?: string,
|
||||
childRoutes?: IRoute[]
|
||||
}
|
||||
|
||||
export interface ISubMenuItem {
|
||||
icon?: string,
|
||||
title: string,
|
||||
target?: string
|
||||
}
|
||||
|
||||
export interface IMenuItem extends ISubMenuItem {
|
||||
items?: ISubMenuItem []
|
||||
}
|
||||
|
||||
export interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
|
||||
@ -3,14 +3,13 @@ import { Link } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Button, Collapse, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'
|
||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||
import { IMenuItem, IReduxState } from '../../../interfaces'
|
||||
|
||||
interface INavMenu {
|
||||
toggleSidebar: () => void
|
||||
}
|
||||
|
||||
const NavMenu : FC<INavMenu> = (props: INavMenu) => {
|
||||
let { siteName, topMenu = [] } = useSelector((state: IReduxState) => state.settings)
|
||||
//let { siteName, topMenu = [] } = useSelector((state: IReduxState) => state.settings)
|
||||
|
||||
const { toggleSidebar } = props
|
||||
|
||||
@ -25,7 +24,7 @@ const NavMenu : FC<INavMenu> = (props: INavMenu) => {
|
||||
}
|
||||
|
||||
return <header>
|
||||
<Navbar className="navbar-expand-sm navbar-toggleable-sm fixed-top border-bottom box-shadow mb-3 bg-light">
|
||||
{/*<Navbar className="navbar-expand-sm navbar-toggleable-sm fixed-top border-bottom box-shadow mb-3 bg-light">
|
||||
<Button color="light" onClick={toggleSidebar}>
|
||||
<FeatherIcon icon="align-left" />
|
||||
</Button>
|
||||
@ -43,7 +42,7 @@ const NavMenu : FC<INavMenu> = (props: INavMenu) => {
|
||||
})}
|
||||
</ul>
|
||||
</Collapse>
|
||||
</Navbar>
|
||||
</Navbar>*/}
|
||||
</header>
|
||||
}
|
||||
|
||||
|
||||
@ -4,48 +4,45 @@ import { useSelector } from 'react-redux'
|
||||
import classNames from 'classnames'
|
||||
import { Collapse, Nav, NavItem, NavLink } from 'reactstrap'
|
||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||
import { IMenuItem, IReduxState, ISubMenuItem } from '../../../interfaces'
|
||||
import style from './scss/style.module.scss'
|
||||
|
||||
interface ISubMenu {
|
||||
icon?: string,
|
||||
title: string,
|
||||
items: ISubMenuItem []
|
||||
//items: ISubMenuItem []
|
||||
}
|
||||
|
||||
const SubMenu : FC<ISubMenu> = (props: ISubMenu) => {
|
||||
const { icon, title, items } = props
|
||||
//const { icon, title, items } = props
|
||||
|
||||
const [collapsed, setCollapsed] = useState(true)
|
||||
const toggle = () => setCollapsed(!collapsed)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NavItem onClick={toggle} className={classNames(style.navitem, !collapsed ? style.menuopen : '')}>
|
||||
<NavLink className={classNames("dropdown-toggle", `${style.navlink}`)}>
|
||||
{icon ? <FeatherIcon icon={icon}/> : ''}
|
||||
<span className={style.linktitle}>{title}</span>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<Collapse isOpen={!collapsed} navbar className={classNames(`${style.itemsmenu}`, { "mb-1": !collapsed })}>
|
||||
{items.map((item: ISubMenuItem, index: number) => (
|
||||
<NavItem key={index} className={classNames(style.navitem, "pl-4")}>
|
||||
<NavLink tag={Link} to={item.target} className={style.navlink}>
|
||||
{item.icon ? <FeatherIcon icon={item.icon}/> : ''}
|
||||
<span className={style.linktitle}>{item.title}</span>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
))}
|
||||
</Collapse>
|
||||
</div>
|
||||
)
|
||||
return <div>
|
||||
{/*<NavItem onClick={toggle} className={classNames(style.navitem, !collapsed ? style.menuopen : '')}>
|
||||
<NavLink className={classNames("dropdown-toggle", `${style.navlink}`)}>
|
||||
{icon ? <FeatherIcon icon={icon}/> : ''}
|
||||
<span className={style.linktitle}>{title}</span>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<Collapse isOpen={!collapsed} navbar className={classNames(`${style.itemsmenu}`, { "mb-1": !collapsed })}>
|
||||
{items.map((item: ISubMenuItem, index: number) => (
|
||||
<NavItem key={index} className={classNames(style.navitem, "pl-4")}>
|
||||
<NavLink tag={Link} to={item.target} className={style.navlink}>
|
||||
{item.icon ? <FeatherIcon icon={item.icon}/> : ''}
|
||||
<span className={style.linktitle}>{item.title}</span>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
))}
|
||||
</Collapse>*/}
|
||||
</div>
|
||||
}
|
||||
|
||||
const SideMenu : FC = () => {
|
||||
let { sideMenu = [] } = useSelector((state: IReduxState) => state.settings)
|
||||
//let { sideMenu = [] } = useSelector((state: IReduxState) => state.settings)
|
||||
|
||||
return <div className={style.sidemenu}>
|
||||
<Nav vertical className="list-unstyled pb-3">
|
||||
{/*<Nav vertical className="list-unstyled pb-3">
|
||||
{sideMenu.map((item: IMenuItem, index: number) => {
|
||||
if(item.items) {
|
||||
return <SubMenu key={index} icon={item.icon} title={item.title} items={item.items} />
|
||||
@ -58,7 +55,7 @@ const SideMenu : FC = () => {
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
})}
|
||||
</Nav>
|
||||
</Nav>*/}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { ISettingsState } from "../store/reducers/Settings"
|
||||
|
||||
export interface ILayout {
|
||||
children?: React.ReactNode
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Container } from 'reactstrap'
|
||||
import { IReduxState } from '../../../interfaces'
|
||||
|
||||
const Footer = () => {
|
||||
let { siteName } = useSelector((state: IReduxState) => state.settings)
|
||||
// let { siteName } = useSelector((state: IReduxState) => state.settings)
|
||||
|
||||
return <footer className="py-3 bg-dark">
|
||||
<Container fluid><p className="m-0 text-center text-white">Copyright © {siteName} {(new Date).getFullYear()}</p></Container>
|
||||
{/*<Container fluid><p className="m-0 text-center text-white">Copyright © {siteName} {(new Date).getFullYear()}</p></Container>*/}
|
||||
</footer>
|
||||
}
|
||||
|
||||
|
||||
@ -3,10 +3,13 @@ import { Link } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Collapse, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'
|
||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||
import { IMenuItem, IReduxState } from '../../../interfaces'
|
||||
|
||||
|
||||
const NavMenu : FC = () => {
|
||||
let { siteName, topMenu = [] } = useSelector((state: IReduxState) => state.settings)
|
||||
/*
|
||||
let { siteName, topMenu = [] } = useSelector((state: IReduxState) => {
|
||||
return state.settings
|
||||
})
|
||||
|
||||
const [state, hookState] = useState({
|
||||
isOpen: false
|
||||
@ -17,14 +20,16 @@ const NavMenu : FC = () => {
|
||||
isOpen: !state.isOpen
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
return <header>
|
||||
{/**
|
||||
<Navbar className="navbar-expand-sm navbar-toggleable-sm fixed-top border-bottom box-shadow mb-3 bg-light">
|
||||
<NavbarBrand href="/">{siteName}</ NavbarBrand>
|
||||
<NavbarToggler onClick={toggle} className="mr-2"/>
|
||||
<Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={state.isOpen} navbar>
|
||||
<ul className="navbar-nav flex-grow">
|
||||
{topMenu.map((item: IMenuItem, index: number) => {
|
||||
{topMenu.map((item: IMenuItemModel, index: number) => {
|
||||
return <NavItem key={index}>
|
||||
<NavLink tag={Link} className="text-dark" to={item.target}>
|
||||
{item.icon ? <FeatherIcon icon={item.icon}/> : ''}
|
||||
@ -42,6 +47,7 @@ const NavMenu : FC = () => {
|
||||
</button>
|
||||
</form>
|
||||
</Navbar>
|
||||
*/}
|
||||
</header>
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,3 @@
|
||||
export interface IFetchResult {
|
||||
status: number,
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface IImageModel {
|
||||
src: string,
|
||||
alt: string
|
||||
@ -29,8 +24,8 @@ interface IPostItemModel {
|
||||
}
|
||||
|
||||
export interface IBlogItemModel extends IPostItemModel {
|
||||
readTime: number,
|
||||
likes: number
|
||||
readTime?: number,
|
||||
likes?: number
|
||||
}
|
||||
|
||||
export interface IShopItemModel extends IPostItemModel {
|
||||
@ -59,4 +54,21 @@ export interface IShopItemsPaginationModel extends IPostPaginationModel {
|
||||
export interface ICategoryModel {
|
||||
id: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface IRouteModel {
|
||||
target: string
|
||||
component?: string
|
||||
childRoutes?: IRouteModel []
|
||||
}
|
||||
|
||||
export interface IMenuItemModel {
|
||||
icon?: string,
|
||||
title?: string,
|
||||
target?: string
|
||||
childItems?: IMenuItemModel []
|
||||
}
|
||||
|
||||
export interface IPageModel {
|
||||
id: string
|
||||
}
|
||||
@ -4,8 +4,8 @@ import { Card, CardBody, CardFooter, CardHeader, CardImg, Col, Container, Row }
|
||||
import { dateFormat } from '../../../functions'
|
||||
|
||||
|
||||
import { GetBlogs, IGetBlogsResponse } from '../../../httpQueries/blogs'
|
||||
import { IBlogItemModel, IBlogItemsPaginationModel } from '../../../httpQueries/models'
|
||||
import { GetBlogCatalog, IGetBlogCatalogResponse } from '../../../controllers/blogCatalog'
|
||||
import { IBlogItemModel, IBlogItemsPaginationModel } from '../../../models'
|
||||
|
||||
|
||||
import { Categories, Empty, Search } from '../SideWidgets'
|
||||
@ -38,8 +38,8 @@ const FeaturedBlog: FC<IBlogItemModel> = (props) => {
|
||||
|
||||
}
|
||||
|
||||
const BlogPagination: FC<IBlogItemsPaginationModel> = (props) => {
|
||||
const { items } = props
|
||||
const BlogItemsPagination: FC<IBlogItemsPaginationModel> = (props) => {
|
||||
const { items, currentPage, totalPages } = props
|
||||
|
||||
return <>
|
||||
{items.map((item, index) => <Col key={index} className="lg-6">
|
||||
@ -51,7 +51,7 @@ const BlogPagination: FC<IBlogItemsPaginationModel> = (props) => {
|
||||
<div className="small text-muted">{item.created}</div>
|
||||
<h2 className="card-title h4">{item.title}</h2>
|
||||
<p className="card-text">{item.shortText}</p>
|
||||
<Link to={`${item.slug}`} className="btn btn-primary">Read more →</Link>
|
||||
<Link to={`${currentPage}/${item.slug}`} className="btn btn-primary">Read more →</Link>
|
||||
</CardBody>
|
||||
|
||||
</Card>
|
||||
@ -72,13 +72,11 @@ const BlogPagination: FC<IBlogItemsPaginationModel> = (props) => {
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
|
||||
const BlogCatalog = () => {
|
||||
const [state, setState] = useState<IGetBlogsResponse>()
|
||||
const [state, setState] = useState<IGetBlogCatalogResponse>()
|
||||
|
||||
useEffect(() => {
|
||||
GetBlogs().then(response => {
|
||||
GetBlogCatalog().then(response => {
|
||||
setState(response)
|
||||
})
|
||||
}, [])
|
||||
@ -98,7 +96,7 @@ const BlogCatalog = () => {
|
||||
<Col>
|
||||
{state?.featuredBlog ? <FeaturedBlog {...state.featuredBlog} /> : ''}
|
||||
<Row>
|
||||
{state?.blogItemsPagination ? <BlogPagination {...state.blogItemsPagination} /> : '' }
|
||||
{state?.blogItemsPagination ? <BlogItemsPagination {...state.blogItemsPagination} /> : '' }
|
||||
</Row>
|
||||
</Col>
|
||||
<Col lg="4">
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
|
||||
import { ICategoryModel } from '../../../httpQueries/models'
|
||||
import { ICategoryModel } from '../../../models'
|
||||
|
||||
const Search = () => {
|
||||
return <Card className="mb-4">
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import React, { FC } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||
import { FeatherIcon } from '../../components/FeatherIcons'
|
||||
import { IImage } from '../../interfaces'
|
||||
import { IPageModel, IBlogItemModel, IImageModel } from '../../models'
|
||||
import { ApplicationState } from '../../store'
|
||||
import { IContentState } from '../../store/reducers/Content'
|
||||
|
||||
import style from './scss/style.module.scss'
|
||||
|
||||
@ -11,6 +14,42 @@ interface ITitleSection {
|
||||
text: string
|
||||
}
|
||||
|
||||
interface IFeaturesSectionItem {
|
||||
icon: string,
|
||||
title: string,
|
||||
text: string
|
||||
}
|
||||
interface IFeaturesSection {
|
||||
title: string,
|
||||
items: IFeaturesSectionItem [],
|
||||
}
|
||||
|
||||
|
||||
interface ITestimonialsSection {
|
||||
text: string,
|
||||
image: IImageModel
|
||||
}
|
||||
|
||||
interface IFeaturedBlogsSection {
|
||||
title: string,
|
||||
text: string,
|
||||
items: IBlogItemModel []
|
||||
}
|
||||
|
||||
interface ICallToActionSection {
|
||||
title: string,
|
||||
text: string,
|
||||
privacyDisclaimer: string
|
||||
}
|
||||
|
||||
interface IHomePage extends IPageModel {
|
||||
titleSection: ITitleSection,
|
||||
featuresSection: IFeaturesSection,
|
||||
testimonialsSection: ITestimonialsSection,
|
||||
featuredBlogsSection: IFeaturedBlogsSection,
|
||||
callToActionSection: ICallToActionSection
|
||||
}
|
||||
|
||||
const TitleSection : FC<ITitleSection> = (props) => {
|
||||
const { title, text } = props
|
||||
|
||||
@ -36,15 +75,7 @@ const TitleSection : FC<ITitleSection> = (props) => {
|
||||
|
||||
}
|
||||
|
||||
interface IFeaturesSectionItem {
|
||||
icon: string,
|
||||
title: string,
|
||||
text: string
|
||||
}
|
||||
interface IFeaturesSection {
|
||||
title: string,
|
||||
items: IFeaturesSectionItem [],
|
||||
}
|
||||
|
||||
|
||||
const FeaturesSection: FC<IFeaturesSection> = (props) => {
|
||||
const { title, items } = props
|
||||
@ -72,10 +103,7 @@ const FeaturesSection: FC<IFeaturesSection> = (props) => {
|
||||
|
||||
}
|
||||
|
||||
interface ITestimonialsSection {
|
||||
text: string,
|
||||
image: IImage
|
||||
}
|
||||
|
||||
|
||||
const TestimonialsSection: FC<ITestimonialsSection> = (props) => {
|
||||
const { text, image } = props
|
||||
@ -98,28 +126,12 @@ const TestimonialsSection: FC<ITestimonialsSection> = (props) => {
|
||||
</section>
|
||||
}
|
||||
|
||||
interface IBlogAuthor {
|
||||
name: string,
|
||||
image: IImage
|
||||
}
|
||||
|
||||
interface IBlogItem {
|
||||
image: IImage,
|
||||
badge: string,
|
||||
title: string,
|
||||
text: string,
|
||||
author: IBlogAuthor,
|
||||
date: string,
|
||||
readTime: string
|
||||
}
|
||||
|
||||
interface IFromOurBlogSection {
|
||||
title: string,
|
||||
text: string,
|
||||
items: IBlogItem []
|
||||
}
|
||||
|
||||
const FromOurBlogSection: FC<IFromOurBlogSection> = (props) => {
|
||||
|
||||
|
||||
const FromOurBlogSection: FC<IFeaturedBlogsSection> = (props) => {
|
||||
const { title, text, items } = props
|
||||
|
||||
return <section className="py-5">
|
||||
@ -148,8 +160,8 @@ const FromOurBlogSection: FC<IFromOurBlogSection> = (props) => {
|
||||
<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.name}</div>
|
||||
<div className="text-muted">{item.date} · {item.readTime}</div>
|
||||
<div className="fw-bold">{item.author.nickName}</div>
|
||||
<div className="text-muted">{item.created} · {item.readTime}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -161,11 +173,7 @@ const FromOurBlogSection: FC<IFromOurBlogSection> = (props) => {
|
||||
</section>
|
||||
}
|
||||
|
||||
interface ICallToActionSection {
|
||||
title: string,
|
||||
text: string,
|
||||
privacyDisclaimer: string
|
||||
}
|
||||
|
||||
|
||||
const CallToActionSection: FC<ICallToActionSection> = (props) => {
|
||||
const { title, text, privacyDisclaimer } = props
|
||||
@ -191,120 +199,16 @@ const CallToActionSection: FC<ICallToActionSection> = (props) => {
|
||||
}
|
||||
|
||||
const Home = () => {
|
||||
const titleSection = {
|
||||
title: "Hello, world!",
|
||||
text: `
|
||||
<p>Welcome to your new single-page application, built with:</p>
|
||||
<ul>
|
||||
<li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>
|
||||
<li><a href='https://facebook.github.io/react/'>React</a> and <a href='https://redux.js.org/'>Redux</a> for client-side code</li>
|
||||
<li><a href='https://getbootstrap.com/'>Bootstrap</a>, <a href='https://reactstrap.github.io/?path=/story/home-installation--page'>Reactstrap</a> and <a href="https://feathericons.com/">Feather icons</a> for layout and styling</li>
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
const state = useSelector((state: ApplicationState) => state.content)
|
||||
|
||||
const featuresSecton = {
|
||||
title: "To help you get started, we have also set up:",
|
||||
items: [
|
||||
{
|
||||
icon: "navigation",
|
||||
title: "Client-side navigation",
|
||||
text: "For example, click <em>Counter</em> then <em>Back</em> to return here."
|
||||
},
|
||||
{
|
||||
icon: "server",
|
||||
title: "Development server integration",
|
||||
text: "In development mode, the development server from <code>create-react-app</code> runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file."
|
||||
},
|
||||
{
|
||||
icon: "terminal",
|
||||
title: "Efficient production builds",
|
||||
text: "In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration produces minified, efficiently bundled JavaScript files."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const testimonialsSection = {
|
||||
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>.",
|
||||
image: {
|
||||
src: "https://dummyimage.com/40x40/ced4da/6c757d",
|
||||
alt: "..."
|
||||
}
|
||||
}
|
||||
|
||||
const fromOurBlogSection = {
|
||||
title: "From our blog",
|
||||
text: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad.",
|
||||
items: [
|
||||
{
|
||||
badge: "News",
|
||||
image: {
|
||||
src: "https://dummyimage.com/600x350/ced4da/6c757d",
|
||||
alt: "..."
|
||||
},
|
||||
title: "Blog post title",
|
||||
text: "Some quick example text to build on the card title and make up the bulk of the card's content.",
|
||||
author: {
|
||||
image: {
|
||||
src: "https://dummyimage.com/40x40/ced4da/6c757d",
|
||||
alt: "..."
|
||||
},
|
||||
name: "Kelly Rowan"
|
||||
},
|
||||
date: "March 12, 2022",
|
||||
readTime: "6 min read"
|
||||
},
|
||||
{
|
||||
badge: "News",
|
||||
image: {
|
||||
src: "https://dummyimage.com/600x350/ced4da/6c757d",
|
||||
alt: "..."
|
||||
},
|
||||
title: "Blog post title",
|
||||
text: "Some quick example text to build on the card title and make up the bulk of the card's content.",
|
||||
author: {
|
||||
image: {
|
||||
src: "https://dummyimage.com/40x40/ced4da/6c757d",
|
||||
alt: "..."
|
||||
},
|
||||
name: "Kelly Rowan"
|
||||
},
|
||||
date: "March 12, 2022",
|
||||
readTime: "6 min read"
|
||||
},
|
||||
{
|
||||
badge: "News",
|
||||
image: {
|
||||
src: "https://dummyimage.com/600x350/ced4da/6c757d",
|
||||
alt: "..."
|
||||
},
|
||||
title: "Blog post title",
|
||||
text: "Some quick example text to build on the card title and make up the bulk of the card's content.",
|
||||
author: {
|
||||
image: {
|
||||
src: "https://dummyimage.com/40x40/ced4da/6c757d",
|
||||
alt: "..."
|
||||
},
|
||||
name: "Kelly Rowan"
|
||||
},
|
||||
date: "March 12, 2022",
|
||||
readTime: "6 min read"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const callToActionSection = {
|
||||
title: "New products, delivered to you.",
|
||||
text: "Sign up for our newsletter for the latest updates.",
|
||||
privacyDisclaimer: "We care about privacy, and will never share your data."
|
||||
}
|
||||
const page = state?.pages?.filter(x => x.id == "HomePage").shift() as IHomePage
|
||||
|
||||
return <>
|
||||
<TitleSection {...titleSection}/>
|
||||
<FeaturesSection {...featuresSecton} />
|
||||
<TestimonialsSection {...testimonialsSection} />
|
||||
<FromOurBlogSection {...fromOurBlogSection} />
|
||||
<CallToActionSection {...callToActionSection} />
|
||||
{ page?.titleSection ? <TitleSection {...page.titleSection}/> : '' }
|
||||
{ page?.featuresSection ? <FeaturesSection {...page.featuresSection} /> : '' }
|
||||
{ page?.testimonialsSection ? <TestimonialsSection {...page.testimonialsSection} /> : '' }
|
||||
{ page?.featuredBlogsSection ? <FromOurBlogSection {...page.featuredBlogsSection} /> : '' }
|
||||
{ page?.callToActionSection ? <CallToActionSection {...page.callToActionSection} /> :'' }
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,54 @@
|
||||
import * as React from 'react'
|
||||
import React, { FC, useEffect, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||
|
||||
import { FeatherRating } from '../../../components/FeatherRating'
|
||||
|
||||
import { IShopItemsPaginationModel } from '../../../models'
|
||||
import { IGetShopCatalogResponse, GetShopCatalog } from '../../../controllers/shopCatalog'
|
||||
|
||||
|
||||
const ShopItemsPagination: FC<IShopItemsPaginationModel> = (props) => {
|
||||
const { items, currentPage, totalPages } = props
|
||||
|
||||
return <section className="py-5">
|
||||
<Container fluid className="px-4 px-lg-5 mt-5">
|
||||
<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>
|
||||
|
||||
<Link to={`${currentPage}/${item.slug}`}>
|
||||
<CardImg top {...item.image} />
|
||||
</Link>
|
||||
|
||||
<CardBody>
|
||||
<div className="text-center">
|
||||
<h5 className="fw-bolder">{item.title}</h5>
|
||||
|
||||
<FeatherRating {...{
|
||||
value: item?.rating ? item.rating : 0
|
||||
}} />
|
||||
|
||||
{item.newPrice
|
||||
? <><span className="text-muted text-decoration-line-through">{item.price}</span> {item.newPrice}</>
|
||||
: item.price}
|
||||
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="p-4 pt-0 border-top-0 bg-transparent">
|
||||
<div className="text-center"><a className="btn btn-outline-dark mt-auto" href="#">Add to cart</a></div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
)}
|
||||
</Row>
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
|
||||
|
||||
const ShopCatalog = () => {
|
||||
|
||||
const items = [
|
||||
@ -54,6 +99,14 @@ const ShopCatalog = () => {
|
||||
}
|
||||
]
|
||||
|
||||
const [state, setState] = useState<IGetShopCatalogResponse>()
|
||||
|
||||
useEffect(() => {
|
||||
GetShopCatalog().then(response => {
|
||||
setState(response)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return <>
|
||||
<header className="bg-dark py-5">
|
||||
<Container fluid className="px-4 px-lg-5 my-5">
|
||||
@ -65,39 +118,7 @@ const ShopCatalog = () => {
|
||||
</Container>
|
||||
</header>
|
||||
|
||||
<section className="py-5">
|
||||
<Container fluid className="px-4 px-lg-5 mt-5">
|
||||
<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"}}>Sale</div>
|
||||
|
||||
<Link to={`/shop/item/${item.id}`}><CardImg top src="https://dummyimage.com/450x300/dee2e6/6c757d.jpg" alt="..." /></Link>
|
||||
|
||||
<CardBody>
|
||||
<div className="text-center">
|
||||
<h5 className="fw-bolder">Fancy Product</h5>
|
||||
|
||||
<FeatherRating {...{
|
||||
value: item.rating
|
||||
}} />
|
||||
|
||||
{item.newPrice
|
||||
? <><span className="text-muted text-decoration-line-through">{item.price}</span> {item.newPrice}</>
|
||||
: item.price}
|
||||
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="p-4 pt-0 border-top-0 bg-transparent">
|
||||
<div className="text-center"><a className="btn btn-outline-dark mt-auto" href="#">Add to cart</a></div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
)}
|
||||
</Row>
|
||||
</Container>
|
||||
</section>
|
||||
{state?.shopItemsPagination ? <ShopItemsPagination {...state.shopItemsPagination} /> : ''}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ import { ShopCatalog, ShopItem } from './Shop'
|
||||
import { BlogCatalog, BlogItem } from './Blog'
|
||||
|
||||
interface IPages {
|
||||
[key: string]: React.FC<any>;
|
||||
[key: string]: FC<any>;
|
||||
}
|
||||
|
||||
const pages: IPages = {
|
||||
|
||||
62
clientapp/src/restClient.ts
Normal file
62
clientapp/src/restClient.ts
Normal file
@ -0,0 +1,62 @@
|
||||
|
||||
|
||||
export interface IRequest {
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
interface IFetchResult {
|
||||
status: number,
|
||||
text: string
|
||||
}
|
||||
|
||||
const Post = () => {
|
||||
|
||||
}
|
||||
|
||||
const Get = async <TResponse>(apiUrl: string, props?: IRequest): Promise<TResponse> => {
|
||||
const url = new URL(apiUrl)
|
||||
|
||||
if(props) {
|
||||
Object.keys(props).forEach(key => {
|
||||
if (typeof(props[key]) !== undefined) {
|
||||
url.searchParams.append(key, props[key] as string)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const requestParams = {
|
||||
method: 'GET',
|
||||
headers: { 'accept': 'application/json', 'content-type': 'application/json' },
|
||||
}
|
||||
|
||||
const fetchData = await fetch(url.toString(), requestParams)
|
||||
.then(async fetchData => {
|
||||
return {
|
||||
status: fetchData.status,
|
||||
text: await fetchData.text()
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
return JSON.parse((fetchData as IFetchResult).text) as TResponse
|
||||
}
|
||||
|
||||
const Put = () => {
|
||||
|
||||
}
|
||||
|
||||
const Delete = () => {
|
||||
|
||||
}
|
||||
|
||||
export {
|
||||
Post,
|
||||
Get,
|
||||
Put,
|
||||
Delete
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import * as WeatherForecasts from './reducers/WeatherForecasts'
|
||||
import * as Counter from './reducers/Counter'
|
||||
import * as Settings from './reducers/Settings'
|
||||
import * as Content from './reducers/Content'
|
||||
|
||||
// The top-level state object
|
||||
export interface ApplicationState {
|
||||
counter: Counter.CounterState | undefined
|
||||
weatherForecasts: WeatherForecasts.WeatherForecastsState | undefined
|
||||
settings: Settings.ISettingsState | undefined
|
||||
content: Content.IContentState | undefined
|
||||
}
|
||||
|
||||
// Whenever an action is dispatched, Redux will update each top-level application state property using
|
||||
@ -15,7 +15,7 @@ export interface ApplicationState {
|
||||
export const reducers = {
|
||||
counter: Counter.reducer,
|
||||
weatherForecasts: WeatherForecasts.reducer,
|
||||
settings: Settings.reducer
|
||||
content: Content.reducer
|
||||
}
|
||||
|
||||
// This type can be used as a hint on action creators so that its 'dispatch' and 'getState' params are
|
||||
|
||||
60
clientapp/src/store/reducers/Content.ts
Normal file
60
clientapp/src/store/reducers/Content.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '..'
|
||||
import { GetStaticContent, IGetStaticContentRequest, IGetStaticContetnResponse } from '../../controllers/staticContent'
|
||||
|
||||
export interface IContentState extends IGetStaticContetnResponse {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends IGetStaticContentRequest {
|
||||
type: 'REQUEST_CONTENT'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends IGetStaticContetnResponse {
|
||||
type: 'RECEIVE_CONTENT'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction;
|
||||
|
||||
export const actionCreators = {
|
||||
requestContent: (): AppThunkAction<KnownAction> => async (dispatch, getState) => {
|
||||
|
||||
dispatch({ type: 'REQUEST_CONTENT' })
|
||||
|
||||
var fetchData = await GetStaticContent()
|
||||
console.log(fetchData)
|
||||
|
||||
dispatch({ type: 'RECEIVE_CONTENT', ...fetchData })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: IContentState = {
|
||||
siteName: "MAKS-IT",
|
||||
routes: [
|
||||
{ target: "/", component: "Home" }
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
export const reducer: Reducer<IContentState> = (state: IContentState | undefined, incomingAction: Action): IContentState => {
|
||||
if (state === undefined) {
|
||||
return unloadedState
|
||||
}
|
||||
|
||||
const action = incomingAction as KnownAction
|
||||
switch (action.type) {
|
||||
case 'REQUEST_CONTENT':
|
||||
return {
|
||||
...state,
|
||||
isLoading: true
|
||||
}
|
||||
|
||||
case 'RECEIVE_CONTENT':
|
||||
return {
|
||||
...action,
|
||||
isLoading: false
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
@ -1,211 +0,0 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
import { IMenuItem, IRoute } from '../../interfaces'
|
||||
|
||||
export interface ISettingsState {
|
||||
siteName: string,
|
||||
routes: IRoute [],
|
||||
adminRoutes: IRoute [],
|
||||
serviceRoutes: IRoute [],
|
||||
sideMenu?: IMenuItem [],
|
||||
topMenu?: IMenuItem [],
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestSettingsAction {
|
||||
type: 'REQUEST_SETTINGS'
|
||||
}
|
||||
|
||||
interface ReceiveSettingsAction {
|
||||
type: 'RECEIVE_SETTINGS',
|
||||
siteName: string,
|
||||
routes: IRoute [],
|
||||
adminRoutes: IRoute [],
|
||||
serviceRoutes: IRoute [],
|
||||
sideMenu?: IMenuItem [],
|
||||
topMenu?: IMenuItem [],
|
||||
}
|
||||
|
||||
type KnownAction = RequestSettingsAction | ReceiveSettingsAction;
|
||||
|
||||
export const actionCreators = {
|
||||
requestSettings: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
dispatch({ type: 'REQUEST_SETTINGS' })
|
||||
|
||||
const appState = getState()
|
||||
|
||||
const siteName = "MAKS-IT"
|
||||
|
||||
const routes : IRoute[] = [
|
||||
{ path: "/", component: "Home" },
|
||||
{ path: "/home", component: "Home" },
|
||||
{ path: "/shop",
|
||||
childRoutes: [
|
||||
{
|
||||
path: "",
|
||||
component: "ShopCatalog"
|
||||
},
|
||||
{
|
||||
path: ":page",
|
||||
childRoutes: [
|
||||
{
|
||||
path: ":slug",
|
||||
component: "ShopItem"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: "/blog",
|
||||
childRoutes: [
|
||||
{
|
||||
path: "",
|
||||
component: "BlogCatalog"
|
||||
},
|
||||
{
|
||||
path: ":page",
|
||||
component: "BlogCatalog"
|
||||
},
|
||||
{
|
||||
path: ":page",
|
||||
childRoutes: [
|
||||
{
|
||||
path: ":slug",
|
||||
component: "BlogItem"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const adminRoutes : IRoute [] = [
|
||||
{ path: "/admin", component: "AdminHome" },
|
||||
|
||||
{ path: "/counter", component: "Counter" },
|
||||
{ path: "/fetch-data", component: "FetchData",
|
||||
childRoutes: [
|
||||
{
|
||||
path: ":startDateIndex",
|
||||
component: "FetchData"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const serviceRoutes : IRoute[] = [
|
||||
{ path: "/signin", component: "Signin" },
|
||||
{ path: "/signup", component: "Signup" }
|
||||
]
|
||||
|
||||
const sideMenu : IMenuItem [] = [
|
||||
{
|
||||
icon: "alert-triangle",
|
||||
title: "Home",
|
||||
target: "/admin"
|
||||
},
|
||||
{
|
||||
icon: "activity",
|
||||
title: "Page",
|
||||
items: [
|
||||
{
|
||||
icon: "activity",
|
||||
title: "Page 1",
|
||||
target: "/Page-1",
|
||||
},
|
||||
{
|
||||
icon: "activity",
|
||||
title: "Page 2",
|
||||
target: "/Page-2",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: "",
|
||||
title: "Counter",
|
||||
target: "/counter",
|
||||
},
|
||||
{
|
||||
icon: "",
|
||||
title: "Fetch data",
|
||||
target: "/fetch-data"
|
||||
},
|
||||
]
|
||||
|
||||
const topMenu : IMenuItem [] = [
|
||||
{
|
||||
icon: "",
|
||||
title: "Home",
|
||||
target: "/"
|
||||
},
|
||||
{
|
||||
icon: "",
|
||||
title: "Shop",
|
||||
target: "/shop",
|
||||
},
|
||||
{
|
||||
icon: "",
|
||||
title: "Blog",
|
||||
target: "/blog"
|
||||
},
|
||||
{
|
||||
icon: "",
|
||||
title: "Signin",
|
||||
target: "/signin"
|
||||
},
|
||||
{
|
||||
icon: "",
|
||||
title: "Signout",
|
||||
target: "/signout"
|
||||
}
|
||||
]
|
||||
|
||||
dispatch({ type: 'RECEIVE_SETTINGS', siteName, routes, adminRoutes, serviceRoutes, sideMenu, topMenu })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: ISettingsState = {
|
||||
siteName: "reactredux",
|
||||
routes: [],
|
||||
adminRoutes: [],
|
||||
serviceRoutes: [],
|
||||
sideMenu: [],
|
||||
topMenu: [],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
export const reducer: Reducer<ISettingsState> = (state: ISettingsState | undefined, incomingAction: Action): ISettingsState => {
|
||||
if (state === undefined) {
|
||||
return unloadedState
|
||||
}
|
||||
|
||||
const action = incomingAction as KnownAction
|
||||
switch (action.type) {
|
||||
case 'REQUEST_SETTINGS':
|
||||
return {
|
||||
siteName: state.siteName,
|
||||
routes: state.routes,
|
||||
adminRoutes: state.adminRoutes,
|
||||
serviceRoutes: state.serviceRoutes,
|
||||
sideMenu: state.sideMenu,
|
||||
topMenu: state.topMenu,
|
||||
isLoading: true
|
||||
}
|
||||
|
||||
case 'RECEIVE_SETTINGS':
|
||||
return {
|
||||
siteName: action.siteName,
|
||||
routes: action.routes,
|
||||
adminRoutes: action.adminRoutes,
|
||||
serviceRoutes: action.serviceRoutes,
|
||||
sideMenu: action.sideMenu,
|
||||
topMenu: action.topMenu,
|
||||
isLoading: false
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
@ -9,7 +9,7 @@ using Core.Abstractions.Models;
|
||||
namespace WeatherForecast.Controllers;
|
||||
|
||||
#region Input models
|
||||
public class GetBlogsResponse : ResponseModel {
|
||||
public class GetBlogCatalogResponse : ResponseModel {
|
||||
|
||||
public BlogItemModel FeaturedBlog { get; set; }
|
||||
|
||||
@ -23,11 +23,11 @@ public class GetBlogsResponse : ResponseModel {
|
||||
[AllowAnonymous]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class BlogsController : ControllerBase {
|
||||
public class BlogCatalogController : ControllerBase {
|
||||
|
||||
private readonly ILogger<LoginController> _logger;
|
||||
|
||||
public BlogsController(ILogger<LoginController> logger) {
|
||||
public BlogCatalogController(ILogger<LoginController> logger) {
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -48,15 +48,17 @@ public class BlogsController : ControllerBase {
|
||||
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,
|
||||
Tags = new List<string> { "react", "redux", "webapi" }
|
||||
};
|
||||
|
||||
var blogModels = new List<BlogItemModel>();
|
||||
@ -64,7 +66,7 @@ public class BlogsController : ControllerBase {
|
||||
blogModels.Add(blogItemModel);
|
||||
}
|
||||
|
||||
var blogResponse = new GetBlogsResponse {
|
||||
var blogCatalogResponse = new GetBlogCatalogResponse {
|
||||
FeaturedBlog = blogItemModel,
|
||||
BlogItemsPagination = new PaginationModel<BlogItemModel> {
|
||||
CurrentPage = currentPage,
|
||||
@ -101,7 +103,7 @@ public class BlogsController : ControllerBase {
|
||||
|
||||
|
||||
|
||||
return Ok(blogResponse);
|
||||
return Ok(blogCatalogResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
using Core.Abstractions.Models;
|
||||
using Core.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WeatherForecast.Models;
|
||||
|
||||
namespace WeatherForecast.Controllers;
|
||||
|
||||
#region Response models
|
||||
public class GetShopCatalogResponse : ResponseModel {
|
||||
|
||||
public PaginationModel<ShopItemModel> ShopItemsPagination { get; set; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
[AllowAnonymous]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class ShopCatalog : ControllerBase {
|
||||
|
||||
private readonly ILogger<LoginController> _logger;
|
||||
|
||||
public ShopCatalog(ILogger<LoginController> logger) {
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="category"></param>
|
||||
/// <param name="searchText"></param>
|
||||
/// <param name="currentPage"></param>
|
||||
/// <param name="itemsPerPage"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public IActionResult Get([FromQuery] Guid? category, [FromQuery] string? searchText, [FromQuery] int currentPage = 1, [FromQuery] int itemsPerPage = 4) {
|
||||
var shopItemModel = new ShopItemModel {
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
77
webapi/WeatherForecast/Controllers/ShopCatalogController.cs
Normal file
77
webapi/WeatherForecast/Controllers/ShopCatalogController.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using Core.Abstractions.Models;
|
||||
using Core.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WeatherForecast.Models;
|
||||
|
||||
namespace WeatherForecast.Controllers;
|
||||
|
||||
#region Response models
|
||||
public class GetShopCatalogResponse : ResponseModel {
|
||||
|
||||
public PaginationModel<ShopItemModel> ShopItemsPagination { get; set; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
[AllowAnonymous]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class ShopCatalogController : ControllerBase {
|
||||
|
||||
private readonly ILogger<LoginController> _logger;
|
||||
|
||||
public ShopCatalogController(ILogger<LoginController> logger) {
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="category"></param>
|
||||
/// <param name="searchText"></param>
|
||||
/// <param name="currentPage"></param>
|
||||
/// <param name="itemsPerPage"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public IActionResult Get([FromQuery] Guid? category, [FromQuery] string? searchText, [FromQuery] int currentPage = 1, [FromQuery] int itemsPerPage = 8) {
|
||||
|
||||
var shopModels = new List<ShopItemModel>();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
var shopItemModel = new ShopItemModel {
|
||||
Id = Guid.NewGuid(),
|
||||
Slug = "shop-catalog-item",
|
||||
Image = new ImageModel { Src = "https://dummyimage.com/450x300/dee2e6/6c757d.jpg", Alt = "..." },
|
||||
Badge = "sale",
|
||||
Title = "Shop item 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" },
|
||||
|
||||
Rating = 4.5,
|
||||
Price = 20,
|
||||
NewPrice = 10
|
||||
};
|
||||
|
||||
shopModels.Add(shopItemModel);
|
||||
}
|
||||
|
||||
var shopCatalogResponse = new GetShopCatalogResponse {
|
||||
ShopItemsPagination = new PaginationModel<ShopItemModel> {
|
||||
CurrentPage = currentPage,
|
||||
TotalPages = 100,
|
||||
Items = shopModels
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return Ok(shopCatalogResponse);
|
||||
}
|
||||
}
|
||||
215
webapi/WeatherForecast/Controllers/StaticContentController.cs
Normal file
215
webapi/WeatherForecast/Controllers/StaticContentController.cs
Normal file
@ -0,0 +1,215 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WeatherForecast.Models;
|
||||
|
||||
namespace WeatherForecast.Controllers;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[AllowAnonymous]
|
||||
[Route("api/[controller]")]
|
||||
public class StaticContentController : ControllerBase {
|
||||
|
||||
private readonly ILogger<StaticContentController> _logger;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
public StaticContentController(
|
||||
ILogger<StaticContentController> logger
|
||||
) {
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public IActionResult Get([FromQuery] string? locale = "en-US") {
|
||||
|
||||
var routes = new List<RouteModel> {
|
||||
new RouteModel ("/", "Home"),
|
||||
new RouteModel ("/home", "Home")
|
||||
};
|
||||
|
||||
var shopRoute = new RouteModel("/shop",
|
||||
new List<RouteModel> {
|
||||
new RouteModel ("", "ShopCatalog"),
|
||||
new RouteModel (":page", "ShopCatalog"),
|
||||
new RouteModel (":page", new List<RouteModel> {
|
||||
new RouteModel (":slug", "ShopItem")
|
||||
})
|
||||
});
|
||||
|
||||
var blogRoute = new RouteModel("/blog",
|
||||
new List<RouteModel> {
|
||||
new RouteModel ("", "BlogCatalog"),
|
||||
new RouteModel (":page", "BlogCatalog"),
|
||||
new RouteModel (":page", new List<RouteModel> {
|
||||
new RouteModel (":slug", "BlogItem")
|
||||
})
|
||||
});
|
||||
|
||||
routes.Add(shopRoute);
|
||||
routes.Add(blogRoute);
|
||||
|
||||
var demoRoutes = new List<RouteModel> {
|
||||
new RouteModel ("/counter", "Counter"),
|
||||
new RouteModel ("/fetch-data", new List<RouteModel> {
|
||||
new RouteModel ("", "FetchData"),
|
||||
new RouteModel (":startDateIndex", "FetchData")
|
||||
})
|
||||
};
|
||||
|
||||
routes = routes.Concat(demoRoutes).ToList();
|
||||
|
||||
var adminRoutes = new List<RouteModel> {
|
||||
new RouteModel ("/admin", "AdminHome")
|
||||
};
|
||||
|
||||
var serviceRoutes = new List<RouteModel> {
|
||||
new RouteModel ("/signin", "Signin"),
|
||||
new RouteModel ("/signup", "Signup"),
|
||||
new RouteModel ("*", "Error")
|
||||
};
|
||||
|
||||
var topMenu = new List<MenuItemModel> {
|
||||
new MenuItemModel ("Home", "/"),
|
||||
new MenuItemModel ("Shop", "/shop"),
|
||||
new MenuItemModel ("Blog", "/blog"),
|
||||
new MenuItemModel ("Signin", "/signin"),
|
||||
new MenuItemModel ("Sognout", "/signout")
|
||||
};
|
||||
|
||||
var sideMenu = new List<MenuItemModel> {
|
||||
new MenuItemModel ("alert-triangle", "Home", "/admin"),
|
||||
new MenuItemModel ("activity", "Page", new List<MenuItemModel> {
|
||||
new MenuItemModel ("activity", "Page-1", "Page-1"),
|
||||
new MenuItemModel ("activity", "Page-2", "Page-2"),
|
||||
new MenuItemModel ("activity", "Page-3", "Page-3")
|
||||
}),
|
||||
new MenuItemModel ("Counter", "/counter"),
|
||||
new MenuItemModel ("Fetch data", "/fetch-data")
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
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 pages = new List<object>();
|
||||
|
||||
pages.Add(new {
|
||||
Id = "HomePage",
|
||||
TitleSection = new {
|
||||
Title = "Hello, World!",
|
||||
Text = @"
|
||||
<p>Welcome to your new single-page application, built with:</p>
|
||||
<ul>
|
||||
<li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>
|
||||
<li><a href='https://facebook.github.io/react/'>React</a> and <a href='https://redux.js.org/'>Redux</a> for client-side code</li>
|
||||
<li><a href='https://getbootstrap.com/'>Bootstrap</a>, <a href='https://reactstrap.github.io/?path=/story/home-installation--page'>Reactstrap</a> and <a href=\""https://feathericons.com/\"">Feather icons</a> for layout and styling</li>
|
||||
</ul>",
|
||||
Image = new ImageModel { Src = "https://dummyimage.com/600x400/343a40/6c757d", Alt = "..." },
|
||||
PrimaryLink = new MenuItemModel("Get Started", "#features"),
|
||||
SecondaryLink = new MenuItemModel("Learn More", "#!")
|
||||
},
|
||||
FeaturesSection = new {
|
||||
Title = "To help you get started, we have also set up:",
|
||||
Items = new[] {
|
||||
new {
|
||||
Icon = "navigation",
|
||||
Title = "Client-side navigation",
|
||||
Text = "For example, click <em>Counter</em> then <em>Back</em> to return here."
|
||||
},
|
||||
new {
|
||||
Icon = "server",
|
||||
Title = "Development server integration",
|
||||
Text = "In development mode, the development server from <code>create-react-app</code> runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file."
|
||||
},
|
||||
new {
|
||||
Icon = "terminal",
|
||||
Title = "Efficient production builds",
|
||||
Text = "In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration produces minified, efficiently bundled JavaScript files."
|
||||
}
|
||||
}
|
||||
},
|
||||
TestimonialsSection = new {
|
||||
Items = new[] {
|
||||
new {
|
||||
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>.",
|
||||
Author = new AuthorModel {
|
||||
Image = new ImageModel { Src = "https://dummyimage.com/40x40/ced4da/6c757d", Alt = "..." },
|
||||
NickName = "Tom Ato/CEO, Pomodoro"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
FeaturedBlogsSection = new {
|
||||
Title = "From our blog",
|
||||
Items = blogItems
|
||||
},
|
||||
CallToActionSection = new {
|
||||
Title = "New products, delivered to you.",
|
||||
Text = "Sign up for our newsletter for the latest updates.",
|
||||
PrivacyDisclaimer = "We care about privacy, and will never share your data."
|
||||
}
|
||||
});
|
||||
|
||||
pages.Add(new {
|
||||
Id = "ShopCatalog",
|
||||
TitleSection = new {
|
||||
Title = "Shop in style",
|
||||
Text = "With this shop hompeage template"
|
||||
}
|
||||
});
|
||||
|
||||
pages.Add(new {
|
||||
Id = "BlogCatalog",
|
||||
TitleSection = new {
|
||||
Title = "Welcome to Blog Home!",
|
||||
Text = "A Bootstrap 5 starter layout for your next blog homepage"
|
||||
}
|
||||
});
|
||||
|
||||
return Ok(new {
|
||||
SiteName = "MAKS-IT",
|
||||
|
||||
Routes = routes,
|
||||
AdminRoutes = adminRoutes,
|
||||
ServiceRoutes = serviceRoutes,
|
||||
|
||||
TopMenu = topMenu,
|
||||
SideMenu = sideMenu,
|
||||
Pages = pages
|
||||
});
|
||||
}
|
||||
}
|
||||
27
webapi/WeatherForecast/Models/MenuItemModel.cs
Normal file
27
webapi/WeatherForecast/Models/MenuItemModel.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace WeatherForecast.Models {
|
||||
public class MenuItemModel {
|
||||
public string? Icon { get; set; }
|
||||
public string? Title { get; private set; }
|
||||
public string? Target { get; private set; }
|
||||
public List<MenuItemModel>? ChildItems { get; private set; }
|
||||
|
||||
public MenuItemModel(string title, string target) {
|
||||
Title = title;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public MenuItemModel(string title, string target, List<MenuItemModel> childItems) : this(title, target) {
|
||||
ChildItems = childItems;
|
||||
}
|
||||
|
||||
public MenuItemModel(string icon, string title, string target): this(title, target) {
|
||||
Icon = icon;
|
||||
}
|
||||
|
||||
public MenuItemModel(string icon, string title, string target, List<MenuItemModel> childItems) : this(icon, title, target) {
|
||||
ChildItems = childItems;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
namespace WeatherForecast.Models {
|
||||
public class PostItemModel {
|
||||
public abstract class PostItemModel {
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Slug { get; set; }
|
||||
|
||||
19
webapi/WeatherForecast/Models/RouteModel.cs
Normal file
19
webapi/WeatherForecast/Models/RouteModel.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace WeatherForecast.Models {
|
||||
public class RouteModel {
|
||||
public string Target { get; private set; }
|
||||
public string? Component { get; private set; }
|
||||
public List<RouteModel>? ChildRoutes { get; private set; }
|
||||
|
||||
private RouteModel(string target) {
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public RouteModel(string target, string component) : this(target) {
|
||||
Component = component;
|
||||
}
|
||||
|
||||
public RouteModel(string target, List<RouteModel> childRoutes) : this(target) {
|
||||
ChildRoutes = childRoutes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,9 @@
|
||||
public class ShopItemModel : PostItemModel {
|
||||
public List<ImageModel> Images { get; set; }
|
||||
public string Sku { get; set; }
|
||||
public int Rating { get; set; }
|
||||
public int Price { get; set; }
|
||||
public int NewPrice { get; set; }
|
||||
public double Rating { get; set; }
|
||||
public double Price { get; set; }
|
||||
public double NewPrice { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user