= (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}
-
+
+
>
}
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
+
+
+
+
+}
+
+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==