diff --git a/clientapp/package.json b/clientapp/package.json index e547ec3..1b123df 100644 --- a/clientapp/package.json +++ b/clientapp/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "bootstrap": "5.1.3", + "classnames": "^2.3.1", "history": "5.3.0", "merge": "^2.1.1", "react": "18.0.0", diff --git a/clientapp/src/App.tsx b/clientapp/src/App.tsx index 29da1e1..dde7cfb 100644 --- a/clientapp/src/App.tsx +++ b/clientapp/src/App.tsx @@ -3,48 +3,50 @@ import { Route, Routes } from 'react-router' //Redux import { useSelector, useDispatch } from 'react-redux' -import { actionCreators as settingsActionCreators, ISettingsState, IRoute } from './store/reducers/Settings' +import { actionCreators as settingsActionCreators } from './store/reducers/Settings' // Components import { DynamicLayout } from './layouts' import { DynamicPage } from './pages' - +import { IReduxState, IRoute } from './interfaces' interface IRouteProp { path: string, element?: JSX.Element } -const NestedRoutes = (routes: IRoute[], tag: string) => { +const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) => { + + if(!Array.isArray(routes)) return + return routes.map((route: IRoute, index: number) => { - const props: IRouteProp = { + const routeProps: IRouteProp = { path: route.path } if (route.component) { const page = - props.element = tag ? {page} : page + routeProps.element = tag ? {page} : page } - return {route.childRoutes ? NestedRoutes(route.childRoutes, tag) : ''} + return {Array.isArray(route.childRoutes) ? NestedRoutes(route.childRoutes, tag) : ''} }) } -interface IReduxState { - settings: ISettingsState -} + const App: FC = () => { const dispatch = useDispatch() - const { routes } = useSelector((state: IReduxState) => state.settings) + const { routes, serviceRoutes } = useSelector((state: IReduxState) => state.settings) useEffect(() => { dispatch(settingsActionCreators.requestSettings()) }, []) return <> - {routes.length > 0 ? - {NestedRoutes(routes, 'PublicLayout')} - : ''} + + {NestedRoutes(routes, 'AdminLayout')} + {NestedRoutes(serviceRoutes)} + } diff --git a/clientapp/src/components/FeatherIcons/FeatherIcon.tsx b/clientapp/src/components/FeatherIcons/FeatherIcon.tsx new file mode 100644 index 0000000..59aa0dc --- /dev/null +++ b/clientapp/src/components/FeatherIcons/FeatherIcon.tsx @@ -0,0 +1,46 @@ +import React, { FC } from 'react' +import { IconInner } from './IconInner' + +interface IFeatherIcon { + icon: string, + size?: string | number, + className?: string, + fill?: string, + otherProps?: any +} + +/** + * Feather icon + * otherProps spread will be removed in version 1. + * @param {icon} icon name that matches from feathericons + * @returns FeatherIcon react component + */ +const FeatherIcon : FC = (props : IFeatherIcon) => { + + const { icon, size = 24, className = '', fill = 'none', otherProps } = props + + if (!icon) { + return null + } + + return ( + + + + ) +} + +export { + FeatherIcon +} diff --git a/clientapp/src/components/FeatherIcons/IconInner.tsx b/clientapp/src/components/FeatherIcons/IconInner.tsx new file mode 100644 index 0000000..2193e36 --- /dev/null +++ b/clientapp/src/components/FeatherIcons/IconInner.tsx @@ -0,0 +1,37 @@ +import React, { FC } from 'react' +// for now this icons json is generated via the build script from latest feather +// TODO: automatically generate this JSON via this repo's build script +import icons from './icons.json' + +const createMarkup = (markup: string) => { + // we dont sanitize markup + // since icons.json is maintained within the package before build + return { __html: markup } +} + +interface IIconInner { + icon: string +} + +const IconInner : FC = (props : IIconInner) => { + const { icon } = props + + + // icons are based on generated icons.json from feather lib + interface IIcons { + [key: string]: string + } + + const iconMarkup = (icons as IIcons)[icon] + + if (iconMarkup) { + // i didnt want to use dangerouslySetInnerHTML + // but this way I can just use the JSON to spit out SVG. + return + } + return null +} + +export { + IconInner +} diff --git a/clientapp/src/components/FeatherIcons/icons.json b/clientapp/src/components/FeatherIcons/icons.json new file mode 100644 index 0000000..39fd4ec --- /dev/null +++ b/clientapp/src/components/FeatherIcons/icons.json @@ -0,0 +1 @@ +{"activity":"","airplay":"","alert-circle":"","alert-octagon":"","alert-triangle":"","align-center":"","align-justify":"","align-left":"","align-right":"","anchor":"","aperture":"","archive":"","arrow-down-circle":"","arrow-down-left":"","arrow-down-right":"","arrow-down":"","arrow-left-circle":"","arrow-left":"","arrow-right-circle":"","arrow-right":"","arrow-up-circle":"","arrow-up-left":"","arrow-up-right":"","arrow-up":"","at-sign":"","award":"","bar-chart-2":"","bar-chart":"","battery-charging":"","battery":"","bell-off":"","bell":"","bluetooth":"","bold":"","book-open":"","book":"","bookmark":"","box":"","briefcase":"","calendar":"","camera-off":"","camera":"","cast":"","check-circle":"","check-square":"","check":"","chevron-down":"","chevron-left":"","chevron-right":"","chevron-up":"","chevrons-down":"","chevrons-left":"","chevrons-right":"","chevrons-up":"","chrome":"","circle":"","clipboard":"","clock":"","cloud-drizzle":"","cloud-lightning":"","cloud-off":"","cloud-rain":"","cloud-snow":"","cloud":"","code":"","codepen":"","codesandbox":"","coffee":"","columns":"","command":"","compass":"","copy":"","corner-down-left":"","corner-down-right":"","corner-left-down":"","corner-left-up":"","corner-right-down":"","corner-right-up":"","corner-up-left":"","corner-up-right":"","cpu":"","credit-card":"","crop":"","crosshair":"","database":"","delete":"","disc":"","divide-circle":"","divide-square":"","divide":"","dollar-sign":"","download-cloud":"","download":"","dribbble":"","droplet":"","edit-2":"","edit-3":"","edit":"","external-link":"","eye-off":"","eye":"","facebook":"","fast-forward":"","feather":"","figma":"","file-minus":"","file-plus":"","file-text":"","file":"","film":"","filter":"","flag":"","folder-minus":"","folder-plus":"","folder":"","framer":"","frown":"","gift":"","git-branch":"","git-commit":"","git-merge":"","git-pull-request":"","github":"","gitlab":"","globe":"","grid":"","hard-drive":"","hash":"","headphones":"","heart":"","help-circle":"","hexagon":"","home":"","image":"","inbox":"","info":"","instagram":"","italic":"","key":"","layers":"","layout":"","life-buoy":"","link-2":"","link":"","linkedin":"","list":"","loader":"","lock":"","log-in":"","log-out":"","mail":"","map-pin":"","map":"","maximize-2":"","maximize":"","meh":"","menu":"","message-circle":"","message-square":"","mic-off":"","mic":"","minimize-2":"","minimize":"","minus-circle":"","minus-square":"","minus":"","monitor":"","moon":"","more-horizontal":"","more-vertical":"","mouse-pointer":"","move":"","music":"","navigation-2":"","navigation":"","octagon":"","package":"","paperclip":"","pause-circle":"","pause":"","pen-tool":"","percent":"","phone-call":"","phone-forwarded":"","phone-incoming":"","phone-missed":"","phone-off":"","phone-outgoing":"","phone":"","pie-chart":"","play-circle":"","play":"","plus-circle":"","plus-square":"","plus":"","pocket":"","power":"","printer":"","radio":"","refresh-ccw":"","refresh-cw":"","repeat":"","rewind":"","rotate-ccw":"","rotate-cw":"","rss":"","save":"","scissors":"","search":"","send":"","server":"","settings":"","share-2":"","share":"","shield-off":"","shield":"","shopping-bag":"","shopping-cart":"","shuffle":"","sidebar":"","skip-back":"","skip-forward":"","slack":"","slash":"","sliders":"","smartphone":"","smile":"","speaker":"","square":"","star":"","stop-circle":"","sun":"","sunrise":"","sunset":"","table":"","tablet":"","tag":"","target":"","terminal":"","thermometer":"","thumbs-down":"","thumbs-up":"","toggle-left":"","toggle-right":"","tool":"","trash-2":"","trash":"","trello":"","trending-down":"","trending-up":"","triangle":"","truck":"","tv":"","twitch":"","twitter":"","type":"","umbrella":"","underline":"","unlock":"","upload-cloud":"","upload":"","user-check":"","user-minus":"","user-plus":"","user-x":"","user":"","users":"","video-off":"","video":"","voicemail":"","volume-1":"","volume-2":"","volume-x":"","volume":"","watch":"","wifi-off":"","wifi":"","wind":"","x-circle":"","x-octagon":"","x-square":"","x":"","youtube":"","zap-off":"","zap":"","zoom-in":"","zoom-out":""} \ No newline at end of file diff --git a/clientapp/src/components/FeatherIcons/index.tsx b/clientapp/src/components/FeatherIcons/index.tsx new file mode 100644 index 0000000..59bafb6 --- /dev/null +++ b/clientapp/src/components/FeatherIcons/index.tsx @@ -0,0 +1 @@ +export { FeatherIcon } from './FeatherIcon' diff --git a/clientapp/src/interfaces/index.ts b/clientapp/src/interfaces/index.ts new file mode 100644 index 0000000..2c897da --- /dev/null +++ b/clientapp/src/interfaces/index.ts @@ -0,0 +1,23 @@ +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 [] +} + + diff --git a/clientapp/src/layouts/admin/NavMenu/index.tsx b/clientapp/src/layouts/admin/NavMenu/index.tsx new file mode 100644 index 0000000..1d62511 --- /dev/null +++ b/clientapp/src/layouts/admin/NavMenu/index.tsx @@ -0,0 +1,56 @@ +import React, { FC, useState } from 'react' +import { Button, Collapse, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap' +import { Link } from 'react-router-dom' +import { FeatherIcon } from '../../../components/FeatherIcons' +import { IMenuItem, IReduxState } from '../../../interfaces' +import { useSelector } from 'react-redux' + +interface INavMenu { + toggleSidebar: () => void +} + +const NavMenu : FC = (props: INavMenu) => { + let { topMenu = [] } = useSelector((state: IReduxState) => state.settings) + + const { toggleSidebar } = props + + const [state, hookState] = useState({ + isOpen: false + }) + + const toggle = () => { + hookState({ + isOpen: !state.isOpen + }) + } + + return + + + + reactstrap + + + + +
    + {topMenu.map((item: IMenuItem, index: number) => { + return + + {item.icon ? : ''} + {item.title} + + + })} +
+
+ + +
+} + +export { + NavMenu +} \ No newline at end of file diff --git a/clientapp/src/layouts/admin/SideMenu/index.tsx b/clientapp/src/layouts/admin/SideMenu/index.tsx new file mode 100644 index 0000000..cfeb007 --- /dev/null +++ b/clientapp/src/layouts/admin/SideMenu/index.tsx @@ -0,0 +1,69 @@ +import React, { FC, useState } from 'react' + +import classNames from 'classnames' +import { Collapse, Nav, NavItem, NavLink } from 'reactstrap' +import { Link } from 'react-router-dom' + +import { FeatherIcon } from '../../../components/FeatherIcons' +import { IMenuItem, IReduxState, ISubMenuItem } from '../../../interfaces' +import { useSelector } from 'react-redux' + + +interface ISubMenu { + icon?: string, + title: string, + items: ISubMenuItem [] +} + +const SubMenu : FC = (props: ISubMenu) => { + const { icon, title, items } = props + + const [collapsed, setCollapsed] = useState(true) + const toggle = () => setCollapsed(!collapsed) + + return ( +
+ + + {icon ? : ''} + {title} + + + + {items.map((item: ISubMenuItem, index: number) => ( + + + {item.icon ? : ''} + {item.title} + + + ))} + +
+ ) +} + +const SideMenu : FC = () => { + let { sideMenu = [] } = useSelector((state: IReduxState) => state.settings) + + return
+ +
+} + +export { + SideMenu +} diff --git a/clientapp/src/layouts/admin/index.tsx b/clientapp/src/layouts/admin/index.tsx index bfdfcc0..ef4fa7b 100644 --- a/clientapp/src/layouts/admin/index.tsx +++ b/clientapp/src/layouts/admin/index.tsx @@ -1,15 +1,34 @@ -import React, { FC } from 'react' +import React, { FC, useState } from 'react' +import classNames from 'classnames' import { Container } from 'reactstrap' +import { NavMenu } from './NavMenu' +import { SideMenu } from './SideMenu' + import { ILayout } from '../interfaces' import 'bootstrap/dist/css/bootstrap.min.css' +import './scss/style.scss' + + + + const AdminLayout: FC = ({ children = null }) => { + + const [sidebarIsOpen, setSidebar] = useState(true) + const toggleSidebar = () => setSidebar(!sidebarIsOpen) + return <> - - {children} - + +
+
+ +
+ + {children} + +
} diff --git a/clientapp/src/layouts/admin/scss/style.scss b/clientapp/src/layouts/admin/scss/style.scss new file mode 100644 index 0000000..a43f3c5 --- /dev/null +++ b/clientapp/src/layouts/admin/scss/style.scss @@ -0,0 +1,170 @@ +//colors +$color_science_blue_approx: #0366d6; +$color_cerise_approx: #e01a76; +$white: #fff; +$color_denim_approx: #1b6ec2; +$color_fun_blue_approx: #1861ac; +$black_5: rgba(0, 0, 0, .05); + +html { + font-size: 14px; +} + +a { + color: $color_science_blue_approx; + &.navbar-brand { + white-space: normal; + text-align: center; + //Instead of the line below you could use @include word-break($value) + word-break: break-all; + } +} + +code { + color: $color_cerise_approx; +} + +.btn-primary { + color: $white; + background-color: $color_denim_approx; + border-color: $color_fun_blue_approx; +} + +.box-shadow { + //Instead of the line below you could use @include box-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10) + box-shadow: 0 .25rem .75rem $black_5; +} + +@media(min-width: 768px) { + html { + font-size: 16px; + } +} + + + + + +.wrapper { + padding-top: 57px; + + .sidebar { + position: fixed; + + background: #7386d5; + color: #fff; + + min-width: 250px; + max-width: 250px; + height: 100%; + + margin-left: -250px; + transition: all 0.5s; + &.is-open { + margin-left: 0; + transition: 0.5s; + } + + a, + a:hover, + a:focus { + cursor: pointer; + color: inherit; + text-decoration: none; + transition: all 0.3s; + + .link-title { + padding-left: 10px; + } + } + + .side-menu { + + .menu-title { + color: #fff; + padding: 10px; + } + + .nav-item { + &:hover { + color: #7386d5; + background: #fff; + } + + .dropdown-toggle { + &::after { + + } + } + } + + /* Collabsable menu nav item */ + .menu-open { + background: #6d7fcc; + } + + /* Collapsable menu items container */ + .items-menu { + color: #fff; + background: #6d7fcc; + } + } + } + + .content { + transition: all 0.5s; + + &.is-open { + padding-left: 255px; + } + } +} + + + + + + + + + +@media(max-width: 768px) { + .wrapper { + padding-top: 51px; + + .sidebar { + &.is-open { + min-width: 60px; + max-width: 60px; + + margin-left: 0; + transition: all 0.5s, height 0s; + } + + .side-menu { + .menu-title { + display: none; + } + + .nav-item { + .link-title { + display: none; + } + .dropdown-toggle { + &::after { + + } + } + } + } + } + + .content { + transition: all 0.5s; + + &.is-open { + padding-left: 65px; + } + } + } +} diff --git a/clientapp/src/layouts/index.tsx b/clientapp/src/layouts/index.tsx index 6996c16..6f7c717 100644 --- a/clientapp/src/layouts/index.tsx +++ b/clientapp/src/layouts/index.tsx @@ -1,10 +1,10 @@ import React, { FC } from 'react' -import { ILayout } from './interfaces' - import { PublicLayout } from './public' import { AdminLayout } from './admin' +import { ILayout } from './interfaces' + interface ILayouts { [key: string]: React.FC; } @@ -14,7 +14,7 @@ const layouts: ILayouts = { AdminLayout } -export interface IDynamicLayout { +interface IDynamicLayout { tag: string, children: React.ReactNode } diff --git a/clientapp/src/layouts/interfaces.tsx b/clientapp/src/layouts/interfaces.tsx index bb59ddf..6f16a3b 100644 --- a/clientapp/src/layouts/interfaces.tsx +++ b/clientapp/src/layouts/interfaces.tsx @@ -1,3 +1,5 @@ +import { ISettingsState } from "../store/reducers/Settings" + export interface ILayout { children?: React.ReactNode } \ No newline at end of file diff --git a/clientapp/src/layouts/public/NavMenu/index.tsx b/clientapp/src/layouts/public/NavMenu/index.tsx index 8a2a509..c16e759 100644 --- a/clientapp/src/layouts/public/NavMenu/index.tsx +++ b/clientapp/src/layouts/public/NavMenu/index.tsx @@ -15,7 +15,7 @@ const NavMenu = () => { } return
- + react-redux-template diff --git a/clientapp/src/pages/Signin/index.tsx b/clientapp/src/pages/Signin/index.tsx new file mode 100644 index 0000000..774f4f5 --- /dev/null +++ b/clientapp/src/pages/Signin/index.tsx @@ -0,0 +1,70 @@ +import React, { useState } from "react" +import { Link } from "react-router-dom" +import { Button, Container, Form, FormGroup, Input, Label } from "reactstrap" + +import './scss/style.scss' + +interface IStateProp { + [key: string]: string; +} + +interface IState extends IStateProp { + username: string, + password: string +} + +const Signin = () => { + + const [state, hookState] = useState({ + username: '', + password: '' + }) + + const setState = (props: IStateProp) => { + const newState = { ...state } + Object.keys(props).forEach(key => newState[key] = props[key]) + hookState(newState) + return newState + } + + const onChange = (e: React.ChangeEvent) => { + const { name, value } = e.target + setState({ [name]: value }) + } + + return +

Sign In

+
+ + + + + + + + + + Dont have an account yet? Please Signup. + + +
+ + +
+} + +export { + Signin +} \ No newline at end of file diff --git a/clientapp/src/pages/Signin/scss/style.scss b/clientapp/src/pages/Signin/scss/style.scss new file mode 100644 index 0000000..0d759a7 --- /dev/null +++ b/clientapp/src/pages/Signin/scss/style.scss @@ -0,0 +1,19 @@ +.container { + border: 2px solid #d3d3d3; + border-radius: .5em; + margin-bottom: 1em; + margin-left: auto; + margin-right: auto; + margin-top: 100px; + padding: 1em; + text-align: left; + width: 600px; +} + +.form { + padding: 1em; +} + +label { + font-weight: 600; +} \ No newline at end of file diff --git a/clientapp/src/pages/Signup/index.tsx b/clientapp/src/pages/Signup/index.tsx new file mode 100644 index 0000000..7414c12 --- /dev/null +++ b/clientapp/src/pages/Signup/index.tsx @@ -0,0 +1,117 @@ +import React, { useState } from "react" +import { Button, Container, Form, FormGroup, Input, Label } from "reactstrap" + +import './scss/style.scss' + +interface IStateProp { + [key: string]: string | boolean; +} + +interface IState extends IStateProp { + username: string, + + email: string, + reEmail: string, + + password: string, + rePassword: string, + + tnc: boolean +} + +const Signup = () => { + + const [state, hookState] = useState({ + username: '', + + email: '', + reEmail : '', + + password: '', + rePassword: '', + + tnc: false + }) + + const setState = (props: IStateProp) => { + const newState = { ...state } + Object.keys(props).forEach(key => newState[key] = props[key]) + hookState(newState) + return newState + } + + const onChange = (e: React.ChangeEvent) => { + const { name, value } = e.target + setState({ [name]: value }) + } + + const onTogle = (e: React.ChangeEvent) => { + const { name } = e.target + setState({ [name]: !state[name] }) + } + + return +

Sign Up

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+} + +export { + Signup +} \ No newline at end of file diff --git a/clientapp/src/pages/Signup/scss/style.scss b/clientapp/src/pages/Signup/scss/style.scss new file mode 100644 index 0000000..0d759a7 --- /dev/null +++ b/clientapp/src/pages/Signup/scss/style.scss @@ -0,0 +1,19 @@ +.container { + border: 2px solid #d3d3d3; + border-radius: .5em; + margin-bottom: 1em; + margin-left: auto; + margin-right: auto; + margin-top: 100px; + padding: 1em; + text-align: left; + width: 600px; +} + +.form { + padding: 1em; +} + +label { + font-weight: 600; +} \ No newline at end of file diff --git a/clientapp/src/pages/index.tsx b/clientapp/src/pages/index.tsx index 1fb56b1..e8dfe89 100644 --- a/clientapp/src/pages/index.tsx +++ b/clientapp/src/pages/index.tsx @@ -3,15 +3,19 @@ import React, { FC } from 'react' import { Home } from './Home' import { Counter } from './Counter' import { FetchData } from './FetchData' +import { Signin } from './Signin' +import { Signup } from './Signup' interface IPages { [key: string]: React.FC; } const pages: IPages = { - Home: Home, - Counter: Counter, - FetchData: FetchData + Home, + Counter, + FetchData, + Signin, + Signup } export interface IDynamicPage { diff --git a/clientapp/src/store/reducers/Settings.ts b/clientapp/src/store/reducers/Settings.ts index 4b680ba..1ddcd72 100644 --- a/clientapp/src/store/reducers/Settings.ts +++ b/clientapp/src/store/reducers/Settings.ts @@ -1,14 +1,12 @@ import { Action, Reducer } from 'redux' import { AppThunkAction } from '../' - -export interface IRoute { - path: string, - component?: string, - childRoutes?: IRoute[] -} +import { IMenuItem, IRoute } from '../../interfaces' export interface ISettingsState { - routes: IRoute[], + routes: IRoute [], + serviceRoutes: IRoute [], + sideMenu?: IMenuItem [], + topMenu?: IMenuItem [], isLoading: boolean } @@ -18,38 +16,137 @@ interface RequestSettingsAction { interface ReceiveSettingsAction { type: 'RECEIVE_SETTINGS', - routes: IRoute[] + routes: IRoute [], + serviceRoutes: IRoute [], + sideMenu?: IMenuItem [], + topMenu?: IMenuItem [], } type KnownAction = RequestSettingsAction | ReceiveSettingsAction; export const actionCreators = { requestSettings: (): AppThunkAction => (dispatch, getState) => { - const appState = getState() - console.log(appState) + + dispatch({ type: 'REQUEST_SETTINGS' }) - const routes = [ + const appState = getState() + + const routes : IRoute[] = [ { path: "/", component: "Home" }, + { path: "/home", component: "Home" }, { path: "/counter", component: "Counter" }, - { path: "/fetch-data", - component: "FetchData", + { 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: "activity", + title: "Home", + items: [ + { + icon: "activity", + title: "Home 1", + target: "/Home-1", + }, + { + icon: "activity", + title: "Home 2", + target: "/Home-2", + }, + { + icon: "activity", + title: "Home 3", + target: "/Home-3", + }, + ] + }, + + { + icon: "info", + title: "About", + target: "/about" + }, + + { + icon: "activity", + title: "Page", + items: [ + { + icon: "activity", + title: "Page 1", + target: "/Page-1", + }, + { + icon: "activity", + title: "Page 2", + target: "/Page-2", + }, + ] + }, + + { + icon: "alert-triangle", + title: "Faq", + target: "/faq" + }, + + { + icon: "phone-call", + title: "Contact", + target: "/contact" + }, + ] + + const topMenu : IMenuItem [] = [ + { + icon: "", + title: "Home", + target: "/" + }, + { + icon: "", + title: "Counter", + target: "/counter", + }, + { + icon: "", + title: "Fetch data", + target: "fetch-data" + }, + { + icon: "", + title: "Signin", + target: "/signin" + }, + { + icon: "", + title: "Signout", + target: "/signout" } ] - dispatch({ type: 'RECEIVE_SETTINGS', routes }) - - dispatch({ type: 'REQUEST_SETTINGS' }) + dispatch({ type: 'RECEIVE_SETTINGS', routes, serviceRoutes, sideMenu, topMenu }) } } const unloadedState: ISettingsState = { routes: [], + serviceRoutes: [], + sideMenu: [], + topMenu: [], isLoading: false } @@ -63,12 +160,18 @@ export const reducer: Reducer = (state: ISettingsState | undefin case 'REQUEST_SETTINGS': return { routes: state.routes, + serviceRoutes: state.serviceRoutes, + sideMenu: state.sideMenu, + topMenu: state.topMenu, isLoading: true } case 'RECEIVE_SETTINGS': return { routes: action.routes, + serviceRoutes: action.serviceRoutes, + sideMenu: action.sideMenu, + topMenu: action.topMenu, isLoading: false } } diff --git a/clientapp/yarn.lock b/clientapp/yarn.lock index 5c6fe94..68f72c3 100644 --- a/clientapp/yarn.lock +++ b/clientapp/yarn.lock @@ -2820,7 +2820,7 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== -classnames@^2.2.3: +classnames@^2.2.3, classnames@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==