(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
|
// Redux
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import { actionCreators as settingsActionCreators } from './store/reducers/Settings'
|
import { actionCreators as settingsActionCreators } from './store/reducers/Content'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { DynamicLayout } from './layouts'
|
import { DynamicLayout } from './layouts'
|
||||||
import { DynamicPage } from './pages'
|
import { DynamicPage } from './pages'
|
||||||
import { IReduxState, IRoute } from './interfaces'
|
import { IRouteModel } from './models'
|
||||||
|
import { ApplicationState } from './store'
|
||||||
|
|
||||||
|
|
||||||
interface IRouteProp {
|
interface IRouteProp {
|
||||||
path: string,
|
path: string,
|
||||||
element?: JSX.Element
|
element?: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) => {
|
const NestedRoutes = (routes: IRouteModel[], tag: string | undefined = undefined) => {
|
||||||
|
|
||||||
if(!Array.isArray(routes)) return
|
if(!Array.isArray(routes)) return
|
||||||
|
|
||||||
return routes.map((route: IRoute, index: number) => {
|
return routes.map((route: IRouteModel, index: number) => {
|
||||||
const routeProps: IRouteProp = {
|
const routeProps: IRouteProp = {
|
||||||
path: route.path
|
path: route.target
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.component) {
|
if (route.component) {
|
||||||
@ -30,6 +31,8 @@ const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) =>
|
|||||||
routeProps.element = tag ? <DynamicLayout tag={tag}>{page}</DynamicLayout> : page
|
routeProps.element = tag ? <DynamicLayout tag={tag}>{page}</DynamicLayout> : page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return <Route key={index} { ...routeProps }>{Array.isArray(route.childRoutes) ? NestedRoutes(route.childRoutes, tag) : ''}</Route>
|
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 App: FC = () => {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { routes, adminRoutes, serviceRoutes } = useSelector((state: IReduxState) => state.settings)
|
const state = useSelector((state: ApplicationState) => state.content)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(settingsActionCreators.requestSettings())
|
dispatch(settingsActionCreators.requestContent())
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -52,9 +55,9 @@ const App: FC = () => {
|
|||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Routes>
|
<Routes>
|
||||||
{ NestedRoutes(routes, 'PublicLayout') }
|
{state?.routes ? NestedRoutes(state.routes, 'PublicLayout') : ''}
|
||||||
{ NestedRoutes(adminRoutes, 'AdminLayout') }
|
{state?.adminRoutes ? NestedRoutes(state.adminRoutes, 'AdminLayout') : ''}
|
||||||
{ NestedRoutes(serviceRoutes) }
|
{state?.serviceRoutes ? NestedRoutes(state.serviceRoutes) : ''}
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { FeatherIcon } from '../FeatherIcons'
|
|||||||
import { ICreateIconProps, ICreateIconResponse, IFeatherRating } from './interfaces'
|
import { ICreateIconProps, ICreateIconResponse, IFeatherRating } from './interfaces'
|
||||||
|
|
||||||
const FeatherRating: FC<IFeatherRating> = ({
|
const FeatherRating: FC<IFeatherRating> = ({
|
||||||
value,
|
value = 0,
|
||||||
icons = {
|
icons = {
|
||||||
complete: { icon: "star", color: '#ffbf00' },
|
complete: { icon: "star", color: '#ffbf00' },
|
||||||
half: { icon: "star", color: '#ffdf80' },
|
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 { useSelector } from 'react-redux'
|
||||||
import { Button, Collapse, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'
|
import { Button, Collapse, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'
|
||||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||||
import { IMenuItem, IReduxState } from '../../../interfaces'
|
|
||||||
|
|
||||||
interface INavMenu {
|
interface INavMenu {
|
||||||
toggleSidebar: () => void
|
toggleSidebar: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavMenu : FC<INavMenu> = (props: INavMenu) => {
|
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
|
const { toggleSidebar } = props
|
||||||
|
|
||||||
@ -25,7 +24,7 @@ const NavMenu : FC<INavMenu> = (props: INavMenu) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <header>
|
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}>
|
<Button color="light" onClick={toggleSidebar}>
|
||||||
<FeatherIcon icon="align-left" />
|
<FeatherIcon icon="align-left" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -43,7 +42,7 @@ const NavMenu : FC<INavMenu> = (props: INavMenu) => {
|
|||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Navbar>
|
</Navbar>*/}
|
||||||
</header>
|
</header>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,48 +4,45 @@ import { useSelector } from 'react-redux'
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { Collapse, Nav, NavItem, NavLink } from 'reactstrap'
|
import { Collapse, Nav, NavItem, NavLink } from 'reactstrap'
|
||||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||||
import { IMenuItem, IReduxState, ISubMenuItem } from '../../../interfaces'
|
|
||||||
import style from './scss/style.module.scss'
|
import style from './scss/style.module.scss'
|
||||||
|
|
||||||
interface ISubMenu {
|
interface ISubMenu {
|
||||||
icon?: string,
|
icon?: string,
|
||||||
title: string,
|
title: string,
|
||||||
items: ISubMenuItem []
|
//items: ISubMenuItem []
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubMenu : FC<ISubMenu> = (props: ISubMenu) => {
|
const SubMenu : FC<ISubMenu> = (props: ISubMenu) => {
|
||||||
const { icon, title, items } = props
|
//const { icon, title, items } = props
|
||||||
|
|
||||||
const [collapsed, setCollapsed] = useState(true)
|
const [collapsed, setCollapsed] = useState(true)
|
||||||
const toggle = () => setCollapsed(!collapsed)
|
const toggle = () => setCollapsed(!collapsed)
|
||||||
|
|
||||||
return (
|
return <div>
|
||||||
<div>
|
{/*<NavItem onClick={toggle} className={classNames(style.navitem, !collapsed ? style.menuopen : '')}>
|
||||||
<NavItem onClick={toggle} className={classNames(style.navitem, !collapsed ? style.menuopen : '')}>
|
<NavLink className={classNames("dropdown-toggle", `${style.navlink}`)}>
|
||||||
<NavLink className={classNames("dropdown-toggle", `${style.navlink}`)}>
|
{icon ? <FeatherIcon icon={icon}/> : ''}
|
||||||
{icon ? <FeatherIcon icon={icon}/> : ''}
|
<span className={style.linktitle}>{title}</span>
|
||||||
<span className={style.linktitle}>{title}</span>
|
</NavLink>
|
||||||
</NavLink>
|
</NavItem>
|
||||||
</NavItem>
|
<Collapse isOpen={!collapsed} navbar className={classNames(`${style.itemsmenu}`, { "mb-1": !collapsed })}>
|
||||||
<Collapse isOpen={!collapsed} navbar className={classNames(`${style.itemsmenu}`, { "mb-1": !collapsed })}>
|
{items.map((item: ISubMenuItem, index: number) => (
|
||||||
{items.map((item: ISubMenuItem, index: number) => (
|
<NavItem key={index} className={classNames(style.navitem, "pl-4")}>
|
||||||
<NavItem key={index} className={classNames(style.navitem, "pl-4")}>
|
<NavLink tag={Link} to={item.target} className={style.navlink}>
|
||||||
<NavLink tag={Link} to={item.target} className={style.navlink}>
|
{item.icon ? <FeatherIcon icon={item.icon}/> : ''}
|
||||||
{item.icon ? <FeatherIcon icon={item.icon}/> : ''}
|
<span className={style.linktitle}>{item.title}</span>
|
||||||
<span className={style.linktitle}>{item.title}</span>
|
</NavLink>
|
||||||
</NavLink>
|
</NavItem>
|
||||||
</NavItem>
|
))}
|
||||||
))}
|
</Collapse>*/}
|
||||||
</Collapse>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SideMenu : FC = () => {
|
const SideMenu : FC = () => {
|
||||||
let { sideMenu = [] } = useSelector((state: IReduxState) => state.settings)
|
//let { sideMenu = [] } = useSelector((state: IReduxState) => state.settings)
|
||||||
|
|
||||||
return <div className={style.sidemenu}>
|
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) => {
|
{sideMenu.map((item: IMenuItem, index: number) => {
|
||||||
if(item.items) {
|
if(item.items) {
|
||||||
return <SubMenu key={index} icon={item.icon} title={item.title} items={item.items} />
|
return <SubMenu key={index} icon={item.icon} title={item.title} items={item.items} />
|
||||||
@ -58,7 +55,7 @@ const SideMenu : FC = () => {
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
})}
|
})}
|
||||||
</Nav>
|
</Nav>*/}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { ISettingsState } from "../store/reducers/Settings"
|
|
||||||
|
|
||||||
export interface ILayout {
|
export interface ILayout {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { Container } from 'reactstrap'
|
import { Container } from 'reactstrap'
|
||||||
import { IReduxState } from '../../../interfaces'
|
|
||||||
|
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
let { siteName } = useSelector((state: IReduxState) => state.settings)
|
// let { siteName } = useSelector((state: IReduxState) => state.settings)
|
||||||
|
|
||||||
return <footer className="py-3 bg-dark">
|
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>
|
</footer>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,13 @@ import { Link } from 'react-router-dom'
|
|||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { Collapse, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'
|
import { Collapse, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'
|
||||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||||
import { IMenuItem, IReduxState } from '../../../interfaces'
|
|
||||||
|
|
||||||
const NavMenu : FC = () => {
|
const NavMenu : FC = () => {
|
||||||
let { siteName, topMenu = [] } = useSelector((state: IReduxState) => state.settings)
|
/*
|
||||||
|
let { siteName, topMenu = [] } = useSelector((state: IReduxState) => {
|
||||||
|
return state.settings
|
||||||
|
})
|
||||||
|
|
||||||
const [state, hookState] = useState({
|
const [state, hookState] = useState({
|
||||||
isOpen: false
|
isOpen: false
|
||||||
@ -17,14 +20,16 @@ const NavMenu : FC = () => {
|
|||||||
isOpen: !state.isOpen
|
isOpen: !state.isOpen
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
return <header>
|
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">
|
||||||
<NavbarBrand href="/">{siteName}</ NavbarBrand>
|
<NavbarBrand href="/">{siteName}</ NavbarBrand>
|
||||||
<NavbarToggler onClick={toggle} className="mr-2"/>
|
<NavbarToggler onClick={toggle} className="mr-2"/>
|
||||||
<Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={state.isOpen} navbar>
|
<Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={state.isOpen} navbar>
|
||||||
<ul className="navbar-nav flex-grow">
|
<ul className="navbar-nav flex-grow">
|
||||||
{topMenu.map((item: IMenuItem, index: number) => {
|
{topMenu.map((item: IMenuItemModel, index: number) => {
|
||||||
return <NavItem key={index}>
|
return <NavItem key={index}>
|
||||||
<NavLink tag={Link} className="text-dark" to={item.target}>
|
<NavLink tag={Link} className="text-dark" to={item.target}>
|
||||||
{item.icon ? <FeatherIcon icon={item.icon}/> : ''}
|
{item.icon ? <FeatherIcon icon={item.icon}/> : ''}
|
||||||
@ -42,6 +47,7 @@ const NavMenu : FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
*/}
|
||||||
</header>
|
</header>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,3 @@
|
|||||||
export interface IFetchResult {
|
|
||||||
status: number,
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IImageModel {
|
export interface IImageModel {
|
||||||
src: string,
|
src: string,
|
||||||
alt: string
|
alt: string
|
||||||
@ -29,8 +24,8 @@ interface IPostItemModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IBlogItemModel extends IPostItemModel {
|
export interface IBlogItemModel extends IPostItemModel {
|
||||||
readTime: number,
|
readTime?: number,
|
||||||
likes: number
|
likes?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IShopItemModel extends IPostItemModel {
|
export interface IShopItemModel extends IPostItemModel {
|
||||||
@ -59,4 +54,21 @@ export interface IShopItemsPaginationModel extends IPostPaginationModel {
|
|||||||
export interface ICategoryModel {
|
export interface ICategoryModel {
|
||||||
id: string,
|
id: string,
|
||||||
text: 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 { dateFormat } from '../../../functions'
|
||||||
|
|
||||||
|
|
||||||
import { GetBlogs, IGetBlogsResponse } from '../../../httpQueries/blogs'
|
import { GetBlogCatalog, IGetBlogCatalogResponse } from '../../../controllers/blogCatalog'
|
||||||
import { IBlogItemModel, IBlogItemsPaginationModel } from '../../../httpQueries/models'
|
import { IBlogItemModel, IBlogItemsPaginationModel } from '../../../models'
|
||||||
|
|
||||||
|
|
||||||
import { Categories, Empty, Search } from '../SideWidgets'
|
import { Categories, Empty, Search } from '../SideWidgets'
|
||||||
@ -38,8 +38,8 @@ const FeaturedBlog: FC<IBlogItemModel> = (props) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const BlogPagination: FC<IBlogItemsPaginationModel> = (props) => {
|
const BlogItemsPagination: FC<IBlogItemsPaginationModel> = (props) => {
|
||||||
const { items } = props
|
const { items, currentPage, totalPages } = props
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
{items.map((item, index) => <Col key={index} className="lg-6">
|
{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>
|
<div className="small text-muted">{item.created}</div>
|
||||||
<h2 className="card-title h4">{item.title}</h2>
|
<h2 className="card-title h4">{item.title}</h2>
|
||||||
<p className="card-text">{item.shortText}</p>
|
<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>
|
</CardBody>
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
@ -72,13 +72,11 @@ const BlogPagination: FC<IBlogItemsPaginationModel> = (props) => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const BlogCatalog = () => {
|
const BlogCatalog = () => {
|
||||||
const [state, setState] = useState<IGetBlogsResponse>()
|
const [state, setState] = useState<IGetBlogCatalogResponse>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
GetBlogs().then(response => {
|
GetBlogCatalog().then(response => {
|
||||||
setState(response)
|
setState(response)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
@ -98,7 +96,7 @@ const BlogCatalog = () => {
|
|||||||
<Col>
|
<Col>
|
||||||
{state?.featuredBlog ? <FeaturedBlog {...state.featuredBlog} /> : ''}
|
{state?.featuredBlog ? <FeaturedBlog {...state.featuredBlog} /> : ''}
|
||||||
<Row>
|
<Row>
|
||||||
{state?.blogItemsPagination ? <BlogPagination {...state.blogItemsPagination} /> : '' }
|
{state?.blogItemsPagination ? <BlogItemsPagination {...state.blogItemsPagination} /> : '' }
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<Col lg="4">
|
<Col lg="4">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
|
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
|
||||||
import { ICategoryModel } from '../../../httpQueries/models'
|
import { ICategoryModel } from '../../../models'
|
||||||
|
|
||||||
const Search = () => {
|
const Search = () => {
|
||||||
return <Card className="mb-4">
|
return <Card className="mb-4">
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
import React, { FC } from 'react'
|
import React, { FC } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||||
import { FeatherIcon } from '../../components/FeatherIcons'
|
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'
|
import style from './scss/style.module.scss'
|
||||||
|
|
||||||
@ -11,6 +14,42 @@ interface ITitleSection {
|
|||||||
text: string
|
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 TitleSection : FC<ITitleSection> = (props) => {
|
||||||
const { title, text } = 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 FeaturesSection: FC<IFeaturesSection> = (props) => {
|
||||||
const { title, items } = 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 TestimonialsSection: FC<ITestimonialsSection> = (props) => {
|
||||||
const { text, image } = props
|
const { text, image } = props
|
||||||
@ -98,28 +126,12 @@ const TestimonialsSection: FC<ITestimonialsSection> = (props) => {
|
|||||||
</section>
|
</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
|
const { title, text, items } = props
|
||||||
|
|
||||||
return <section className="py-5">
|
return <section className="py-5">
|
||||||
@ -148,8 +160,8 @@ const FromOurBlogSection: FC<IFromOurBlogSection> = (props) => {
|
|||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<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.name}</div>
|
<div className="fw-bold">{item.author.nickName}</div>
|
||||||
<div className="text-muted">{item.date} · {item.readTime}</div>
|
<div className="text-muted">{item.created} · {item.readTime}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -161,11 +173,7 @@ const FromOurBlogSection: FC<IFromOurBlogSection> = (props) => {
|
|||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICallToActionSection {
|
|
||||||
title: string,
|
|
||||||
text: string,
|
|
||||||
privacyDisclaimer: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const CallToActionSection: FC<ICallToActionSection> = (props) => {
|
const CallToActionSection: FC<ICallToActionSection> = (props) => {
|
||||||
const { title, text, privacyDisclaimer } = props
|
const { title, text, privacyDisclaimer } = props
|
||||||
@ -191,120 +199,16 @@ const CallToActionSection: FC<ICallToActionSection> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const titleSection = {
|
const state = useSelector((state: ApplicationState) => state.content)
|
||||||
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 featuresSecton = {
|
const page = state?.pages?.filter(x => x.id == "HomePage").shift() as IHomePage
|
||||||
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."
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<TitleSection {...titleSection}/>
|
{ page?.titleSection ? <TitleSection {...page.titleSection}/> : '' }
|
||||||
<FeaturesSection {...featuresSecton} />
|
{ page?.featuresSection ? <FeaturesSection {...page.featuresSection} /> : '' }
|
||||||
<TestimonialsSection {...testimonialsSection} />
|
{ page?.testimonialsSection ? <TestimonialsSection {...page.testimonialsSection} /> : '' }
|
||||||
<FromOurBlogSection {...fromOurBlogSection} />
|
{ page?.featuredBlogsSection ? <FromOurBlogSection {...page.featuredBlogsSection} /> : '' }
|
||||||
<CallToActionSection {...callToActionSection} />
|
{ 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 { Link } from 'react-router-dom'
|
||||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||||
|
|
||||||
import { FeatherRating } from '../../../components/FeatherRating'
|
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 ShopCatalog = () => {
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
@ -54,6 +99,14 @@ const ShopCatalog = () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const [state, setState] = useState<IGetShopCatalogResponse>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
GetShopCatalog().then(response => {
|
||||||
|
setState(response)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<header className="bg-dark py-5">
|
<header className="bg-dark py-5">
|
||||||
<Container fluid className="px-4 px-lg-5 my-5">
|
<Container fluid className="px-4 px-lg-5 my-5">
|
||||||
@ -65,39 +118,7 @@ const ShopCatalog = () => {
|
|||||||
</Container>
|
</Container>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section className="py-5">
|
{state?.shopItemsPagination ? <ShopItemsPagination {...state.shopItemsPagination} /> : ''}
|
||||||
<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>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { ShopCatalog, ShopItem } from './Shop'
|
|||||||
import { BlogCatalog, BlogItem } from './Blog'
|
import { BlogCatalog, BlogItem } from './Blog'
|
||||||
|
|
||||||
interface IPages {
|
interface IPages {
|
||||||
[key: string]: React.FC<any>;
|
[key: string]: FC<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pages: IPages = {
|
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 WeatherForecasts from './reducers/WeatherForecasts'
|
||||||
import * as Counter from './reducers/Counter'
|
import * as Counter from './reducers/Counter'
|
||||||
import * as Settings from './reducers/Settings'
|
import * as Content from './reducers/Content'
|
||||||
|
|
||||||
// The top-level state object
|
// The top-level state object
|
||||||
export interface ApplicationState {
|
export interface ApplicationState {
|
||||||
counter: Counter.CounterState | undefined
|
counter: Counter.CounterState | undefined
|
||||||
weatherForecasts: WeatherForecasts.WeatherForecastsState | 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
|
// 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 = {
|
export const reducers = {
|
||||||
counter: Counter.reducer,
|
counter: Counter.reducer,
|
||||||
weatherForecasts: WeatherForecasts.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
|
// 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;
|
namespace WeatherForecast.Controllers;
|
||||||
|
|
||||||
#region Input models
|
#region Input models
|
||||||
public class GetBlogsResponse : ResponseModel {
|
public class GetBlogCatalogResponse : ResponseModel {
|
||||||
|
|
||||||
public BlogItemModel FeaturedBlog { get; set; }
|
public BlogItemModel FeaturedBlog { get; set; }
|
||||||
|
|
||||||
@ -23,11 +23,11 @@ public class GetBlogsResponse : ResponseModel {
|
|||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
public class BlogsController : ControllerBase {
|
public class BlogCatalogController : ControllerBase {
|
||||||
|
|
||||||
private readonly ILogger<LoginController> _logger;
|
private readonly ILogger<LoginController> _logger;
|
||||||
|
|
||||||
public BlogsController(ILogger<LoginController> logger) {
|
public BlogCatalogController(ILogger<LoginController> logger) {
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,15 +48,17 @@ public class BlogsController : ControllerBase {
|
|||||||
Badge = "news",
|
Badge = "news",
|
||||||
Title = "Blog post title",
|
Title = "Blog post title",
|
||||||
ShortText = "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
ShortText = "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||||
|
Text = "",
|
||||||
Author = new AuthorModel {
|
Author = new AuthorModel {
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
Image = new ImageModel { Src = "https://dummyimage.com/40x40/ced4da/6c757d", Alt = "..." },
|
Image = new ImageModel { Src = "https://dummyimage.com/40x40/ced4da/6c757d", Alt = "..." },
|
||||||
NickName = "Admin"
|
NickName = "Admin"
|
||||||
},
|
},
|
||||||
Created = DateTime.UtcNow,
|
Created = DateTime.UtcNow,
|
||||||
|
Tags = new List<string> { "react", "redux", "webapi" },
|
||||||
|
|
||||||
ReadTime = 10,
|
ReadTime = 10,
|
||||||
Likes = 200,
|
Likes = 200,
|
||||||
Tags = new List<string> { "react", "redux", "webapi" }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var blogModels = new List<BlogItemModel>();
|
var blogModels = new List<BlogItemModel>();
|
||||||
@ -64,7 +66,7 @@ public class BlogsController : ControllerBase {
|
|||||||
blogModels.Add(blogItemModel);
|
blogModels.Add(blogItemModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
var blogResponse = new GetBlogsResponse {
|
var blogCatalogResponse = new GetBlogCatalogResponse {
|
||||||
FeaturedBlog = blogItemModel,
|
FeaturedBlog = blogItemModel,
|
||||||
BlogItemsPagination = new PaginationModel<BlogItemModel> {
|
BlogItemsPagination = new PaginationModel<BlogItemModel> {
|
||||||
CurrentPage = currentPage,
|
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 {
|
namespace WeatherForecast.Models {
|
||||||
public class PostItemModel {
|
public abstract class PostItemModel {
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
public string Slug { 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 class ShopItemModel : PostItemModel {
|
||||||
public List<ImageModel> Images { get; set; }
|
public List<ImageModel> Images { get; set; }
|
||||||
public string Sku { get; set; }
|
public string Sku { get; set; }
|
||||||
public int Rating { get; set; }
|
public double Rating { get; set; }
|
||||||
public int Price { get; set; }
|
public double Price { get; set; }
|
||||||
public int NewPrice { get; set; }
|
public double NewPrice { get; set; }
|
||||||
public int Quantity { get; set; }
|
public int Quantity { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user