mirror of
https://github.com/Telecominfraproject/wlan-cloud-ui-library.git
synced 2025-11-03 04:08:02 +00:00
init
This commit is contained in:
29
.babelrc
Normal file
29
.babelrc
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
"modules": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@babel/preset-react"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": true }] // `style: true` for less
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"production": {
|
||||||
|
"only": ["app"],
|
||||||
|
"plugins": [
|
||||||
|
"lodash",
|
||||||
|
"transform-react-remove-prop-types",
|
||||||
|
"@babel/plugin-transform-react-inline-elements",
|
||||||
|
"@babel/plugin-transform-react-constant-elements"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"plugins": ["@babel/plugin-transform-modules-commonjs", "dynamic-import-node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
.editorconfig
Normal file
19
.editorconfig
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 100
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
[*.md]
|
||||||
|
max_line_length = 0
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
[{Makefile,**.mk}]
|
||||||
|
# Use tabs for indentation (Makefiles require tabs)
|
||||||
|
indent_style = tab
|
||||||
|
[*.scss]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
40
.eslintrc
Normal file
40
.eslintrc
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"extends": ["airbnb", "prettier", "prettier/react"],
|
||||||
|
"plugins": ["prettier"],
|
||||||
|
"rules": {
|
||||||
|
"react/jsx-filename-extension": [
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
"extensions": [".js", ".jsx"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"react/prop-types": 0,
|
||||||
|
"no-underscore-dangle": 0,
|
||||||
|
"import/imports-first": ["error", "absolute-first"],
|
||||||
|
"import/newline-after-import": "error",
|
||||||
|
"import/prefer-default-export": 0,
|
||||||
|
"react/jsx-props-no-spreading": 0,
|
||||||
|
"semi": "error"
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"window": true,
|
||||||
|
"document": true,
|
||||||
|
"localStorage": true,
|
||||||
|
"FormData": true,
|
||||||
|
"FileReader": true,
|
||||||
|
"Blob": true,
|
||||||
|
"navigator": true,
|
||||||
|
"Headers": true,
|
||||||
|
"Request": true,
|
||||||
|
"fetch": true
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"node": {
|
||||||
|
"extensions": [".js", ".jsx", ".ts", ".tsx"],
|
||||||
|
"moduleDirectory": ["node_modules", "app"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
49
app/components/GlobalHeader/GlobalHeader.module.scss
Normal file
49
app/components/GlobalHeader/GlobalHeader.module.scss
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
@import 'styles/variables';
|
||||||
|
|
||||||
|
.GlobalHeader {
|
||||||
|
height: $header-height;
|
||||||
|
left: $sidebar-width;
|
||||||
|
padding: 0;
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
right: 0;
|
||||||
|
z-index: 2000;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
left: $sidebar-collapsed-width;
|
||||||
|
}
|
||||||
|
&.mobile {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-row) {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.LogoContainer {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 0 0 24px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.MenuIcon {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 64px;
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RightMenu {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
87
app/components/GlobalHeader/index.js
Normal file
87
app/components/GlobalHeader/index.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Layout, Popover, Row } from 'antd';
|
||||||
|
import { MenuUnfoldOutlined, MenuFoldOutlined, SettingOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import styles from './GlobalHeader.module.scss';
|
||||||
|
|
||||||
|
const { Header } = Layout;
|
||||||
|
|
||||||
|
const GlobalHeader = ({ collapsed, onMenuButtonClick, isMobile, logoMobile }) => {
|
||||||
|
const [popoverVisible, setPopoverVisible] = useState(false);
|
||||||
|
|
||||||
|
const hidePopover = () => {
|
||||||
|
setPopoverVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVisibleChange = visible => {
|
||||||
|
setPopoverVisible(visible);
|
||||||
|
};
|
||||||
|
|
||||||
|
const userOptions = (
|
||||||
|
<>
|
||||||
|
<Row>
|
||||||
|
<Link onClick={hidePopover} to="/accounts/customers/view">
|
||||||
|
Profile
|
||||||
|
</Link>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Link onClick={hidePopover} to="/account">
|
||||||
|
Users
|
||||||
|
</Link>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Link onClick={hidePopover} to="/accounts">
|
||||||
|
Advanced
|
||||||
|
</Link>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Link onClick={hidePopover} to="/accounts/customersxw">
|
||||||
|
Rules Preference
|
||||||
|
</Link>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Header
|
||||||
|
className={`${styles.GlobalHeader} ${collapsed ? styles.collapsed : ''} ${
|
||||||
|
isMobile ? styles.mobile : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isMobile && [
|
||||||
|
<Link className={styles.LogoContainer} to="/" key="mobileLogo">
|
||||||
|
<img src={logoMobile} alt="logo" width="32" />
|
||||||
|
</Link>,
|
||||||
|
]}
|
||||||
|
{collapsed ? (
|
||||||
|
<MenuUnfoldOutlined className={styles.MenuIcon} onClick={onMenuButtonClick} />
|
||||||
|
) : (
|
||||||
|
<MenuFoldOutlined className={styles.MenuIcon} onClick={onMenuButtonClick} />
|
||||||
|
)}
|
||||||
|
<div className={styles.RightMenu}>
|
||||||
|
<Popover
|
||||||
|
content={userOptions}
|
||||||
|
trigger="click"
|
||||||
|
getPopupContainer={e => e.parentElement}
|
||||||
|
visible={popoverVisible}
|
||||||
|
onVisibleChange={handleVisibleChange}
|
||||||
|
placement="bottomRight"
|
||||||
|
arrowPointAtCenter
|
||||||
|
>
|
||||||
|
<SettingOutlined className={styles.MenuIcon} />
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</Header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
GlobalHeader.propTypes = {
|
||||||
|
collapsed: PropTypes.bool.isRequired,
|
||||||
|
onMenuButtonClick: PropTypes.func.isRequired,
|
||||||
|
isMobile: PropTypes.bool.isRequired,
|
||||||
|
logoMobile: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlobalHeader;
|
||||||
37
app/components/SideMenu/Sider.module.scss
Normal file
37
app/components/SideMenu/Sider.module.scss
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
@import 'styles/variables';
|
||||||
|
|
||||||
|
.Sider {
|
||||||
|
height: 100vh;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
|
&.Mobile {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
.Logo {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.TopArea {
|
||||||
|
height: $header-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.LogoContainer {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Logo {
|
||||||
|
width: 200px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MenuIcon {
|
||||||
|
margin-right: 10px!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
271
app/components/SideMenu/index.js
Normal file
271
app/components/SideMenu/index.js
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Layout, Menu, Drawer } from 'antd';
|
||||||
|
import {
|
||||||
|
DashboardOutlined,
|
||||||
|
ProfileOutlined,
|
||||||
|
AreaChartOutlined,
|
||||||
|
MobileOutlined,
|
||||||
|
ApiOutlined,
|
||||||
|
NotificationOutlined,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
TeamOutlined,
|
||||||
|
LogoutOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
|
import styles from './Sider.module.scss';
|
||||||
|
|
||||||
|
const { Sider } = Layout;
|
||||||
|
const { SubMenu, Item } = Menu;
|
||||||
|
|
||||||
|
const ACCOUNTS = 'accounts';
|
||||||
|
const NETWORK = 'network';
|
||||||
|
const CONFIGURATION = 'configuration';
|
||||||
|
const INSIGHTS = 'insights';
|
||||||
|
const SYSTEM = 'system';
|
||||||
|
const HISTORY = 'history';
|
||||||
|
const rootSubmenuKeys = [ACCOUNTS, NETWORK, CONFIGURATION, INSIGHTS, SYSTEM, HISTORY];
|
||||||
|
|
||||||
|
const SideMenu = ({
|
||||||
|
locationState,
|
||||||
|
collapsed,
|
||||||
|
isMobile,
|
||||||
|
logo,
|
||||||
|
logoMobile,
|
||||||
|
onMenuButtonClick,
|
||||||
|
onMenuItemClick,
|
||||||
|
onLogout,
|
||||||
|
}) => {
|
||||||
|
const [openKeys, setOpenKeys] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpenKeys([]);
|
||||||
|
}, [collapsed]);
|
||||||
|
|
||||||
|
const enocMenuItems = [
|
||||||
|
{
|
||||||
|
key: 'dashboard',
|
||||||
|
icon: <DashboardOutlined className={styles.MenuIcon} />,
|
||||||
|
path: '/',
|
||||||
|
text: 'Dashboard',
|
||||||
|
onClick: onMenuItemClick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'profiles',
|
||||||
|
icon: <ProfileOutlined className={styles.MenuIcon} />,
|
||||||
|
path: '/profiles',
|
||||||
|
text: 'Profiles',
|
||||||
|
onClick: onMenuItemClick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'reports',
|
||||||
|
icon: <AreaChartOutlined className={styles.MenuIcon} />,
|
||||||
|
path: '/analytics/qoe',
|
||||||
|
text: 'Insights',
|
||||||
|
onClick: onMenuItemClick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'client-devices',
|
||||||
|
icon: <MobileOutlined className={styles.MenuIcon} />,
|
||||||
|
path: '/network/client-devices',
|
||||||
|
text: 'Client Devices',
|
||||||
|
onClick: onMenuItemClick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'network-elements',
|
||||||
|
icon: <ApiOutlined className={styles.MenuIcon} />,
|
||||||
|
path: '/network/elements',
|
||||||
|
text: 'Network Elements',
|
||||||
|
onClick: onMenuItemClick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'alarms',
|
||||||
|
icon: <NotificationOutlined className={styles.MenuIcon} />,
|
||||||
|
path: '/network/alarms',
|
||||||
|
text: 'Alarms',
|
||||||
|
onClick: onMenuItemClick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'recommendations',
|
||||||
|
icon: <CheckCircleOutlined className={styles.MenuIcon} />,
|
||||||
|
path: '/recommendations',
|
||||||
|
text: 'Recommendations',
|
||||||
|
onClick: onMenuItemClick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'settings',
|
||||||
|
icon: <SettingOutlined className={styles.MenuIcon} />,
|
||||||
|
path: '/settings',
|
||||||
|
text: 'Settings',
|
||||||
|
onClick: onMenuItemClick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: ACCOUNTS,
|
||||||
|
icon: <TeamOutlined className={styles.MenuIcon} />,
|
||||||
|
text: 'Customers',
|
||||||
|
path: '/accounts/customers',
|
||||||
|
onClick: onMenuItemClick,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const commonMenuItems = [
|
||||||
|
{
|
||||||
|
key: 'logout',
|
||||||
|
icon: <LogoutOutlined className={styles.MenuIcon} />,
|
||||||
|
path: '/signout',
|
||||||
|
text: 'Sign Out',
|
||||||
|
onClick: onLogout,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const onOpenChange = keys => {
|
||||||
|
const latestOpenKey = keys.find(key => !openKeys.includes(key));
|
||||||
|
|
||||||
|
if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
|
||||||
|
setOpenKeys(keys);
|
||||||
|
} else {
|
||||||
|
setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMenu = (config = { items: [] }, defaultSelectedKeys = []) => {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
let keys = [];
|
||||||
|
let selectedKeys = [...defaultSelectedKeys];
|
||||||
|
|
||||||
|
config.items.forEach(item => {
|
||||||
|
if (item && item.key) {
|
||||||
|
if (item.children) {
|
||||||
|
const subMenu = getMenu({ items: item.children, parent: item });
|
||||||
|
|
||||||
|
if (subMenu.selectedKeys && subMenu.selectedKeys.length) {
|
||||||
|
selectedKeys = [...selectedKeys, ...subMenu.selectedKeys];
|
||||||
|
}
|
||||||
|
if (subMenu.openKeys && subMenu.openKeys.length) {
|
||||||
|
keys = [...keys, ...subMenu.openKeys];
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
<SubMenu
|
||||||
|
key={item.key}
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
{item.icon}
|
||||||
|
<span>{item.text}</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{subMenu.items}
|
||||||
|
</SubMenu>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const ItemComponent = item.Component || Item;
|
||||||
|
|
||||||
|
let LinkComponent = ({ ...restP }) => (
|
||||||
|
<Link
|
||||||
|
// preserveParams={this.getPreservedParams(item.path, locationState)}
|
||||||
|
{...restP}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (item.LinkComponent) {
|
||||||
|
LinkComponent = item.LinkComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = locationState.pathname;
|
||||||
|
const pathAndHash = `${path}${locationState.hash}`; // for hash routing
|
||||||
|
|
||||||
|
if (path.startsWith(item.path) || pathAndHash.startsWith(item.path)) {
|
||||||
|
if (config.parent) {
|
||||||
|
keys.push(config.parent.key);
|
||||||
|
}
|
||||||
|
selectedKeys.push(item.key.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
<ItemComponent key={item.key} className="ant-menu-item">
|
||||||
|
<LinkComponent onClick={item.onClick} to={item.path}>
|
||||||
|
{item.icon}
|
||||||
|
<span>{item.text}</span>
|
||||||
|
</LinkComponent>
|
||||||
|
</ItemComponent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
items,
|
||||||
|
selectedKeys,
|
||||||
|
keys,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const menuConfig = {
|
||||||
|
items: [...enocMenuItems, ...commonMenuItems],
|
||||||
|
};
|
||||||
|
|
||||||
|
const menu = getMenu(menuConfig);
|
||||||
|
|
||||||
|
const sider = (
|
||||||
|
<Sider
|
||||||
|
collapsed={isMobile ? false : collapsed}
|
||||||
|
width="234px"
|
||||||
|
collapsedWidth="80px"
|
||||||
|
breakpoint="lg"
|
||||||
|
className={`${styles.Sider} ${collapsed ? styles.collapsed : ''} ${
|
||||||
|
isMobile ? styles.Mobile : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className={styles.TopArea}>
|
||||||
|
<Link className={styles.LogoContainer} to="/">
|
||||||
|
<img className={styles.Logo} alt="ConnectUs" src={collapsed ? logoMobile : logo} />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<Menu
|
||||||
|
className="sidemenu"
|
||||||
|
selectedKeys={menu.selectedKeys}
|
||||||
|
defaultOpenKeys={menu.openKeys}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
mode="inline"
|
||||||
|
theme="dark"
|
||||||
|
>
|
||||||
|
{menu.items}
|
||||||
|
</Menu>
|
||||||
|
</Sider>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
zIndex={9999}
|
||||||
|
placement="left"
|
||||||
|
closable={false}
|
||||||
|
visible={!collapsed}
|
||||||
|
onClose={onMenuButtonClick}
|
||||||
|
bodyStyle={{ padding: 0 }}
|
||||||
|
width={256}
|
||||||
|
>
|
||||||
|
{sider}
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sider;
|
||||||
|
};
|
||||||
|
|
||||||
|
SideMenu.propTypes = {
|
||||||
|
locationState: PropTypes.instanceOf(Object).isRequired,
|
||||||
|
collapsed: PropTypes.bool.isRequired,
|
||||||
|
isMobile: PropTypes.bool.isRequired,
|
||||||
|
logo: PropTypes.string.isRequired,
|
||||||
|
logoMobile: PropTypes.string.isRequired,
|
||||||
|
onMenuButtonClick: PropTypes.func.isRequired,
|
||||||
|
onMenuItemClick: PropTypes.func.isRequired,
|
||||||
|
onLogout: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SideMenu;
|
||||||
5
app/containers/Dashboard/index.js
Normal file
5
app/containers/Dashboard/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Dashboard = () => <h1>Dashboard</h1>;
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
22
app/containers/MasterLayout/MasterLayout.module.scss
Normal file
22
app/containers/MasterLayout/MasterLayout.module.scss
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@import 'styles/variables';
|
||||||
|
|
||||||
|
.MainLayout {
|
||||||
|
height: 100vh;
|
||||||
|
margin-left: $sidebar-width;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
margin-left: $sidebar-collapsed-width;
|
||||||
|
}
|
||||||
|
&.mobile {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Content {
|
||||||
|
margin-top: $header-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Footer {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
103
app/containers/MasterLayout/index.js
Normal file
103
app/containers/MasterLayout/index.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Layout } from 'antd';
|
||||||
|
|
||||||
|
import GlobalHeader from 'components/GlobalHeader';
|
||||||
|
import SideMenu from 'components/SideMenu';
|
||||||
|
|
||||||
|
import styles from './MasterLayout.module.scss';
|
||||||
|
|
||||||
|
const { Content, Footer } = Layout;
|
||||||
|
|
||||||
|
const MasterLayout = ({ children, logo, logoMobile, locationState, onLogout }) => {
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
const [screenSize, setScreenSize] = useState('lg');
|
||||||
|
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
const width = window.innerWidth;
|
||||||
|
|
||||||
|
if (width < 768 && screenSize !== 'sm') {
|
||||||
|
setIsCollapsed(true);
|
||||||
|
setIsMobile(true);
|
||||||
|
setScreenSize('sm');
|
||||||
|
} else if (width >= 768 && width < 992 && screenSize !== 'md') {
|
||||||
|
setIsCollapsed(true);
|
||||||
|
setIsMobile(false);
|
||||||
|
setScreenSize('md');
|
||||||
|
} else if (width >= 992 && screenSize !== 'lg') {
|
||||||
|
setIsCollapsed(false);
|
||||||
|
setIsMobile(false);
|
||||||
|
setScreenSize('lg');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuToggle = () => {
|
||||||
|
setIsCollapsed(!isCollapsed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuItemClick = () => {
|
||||||
|
if (isMobile === true) {
|
||||||
|
setIsCollapsed(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
onLogout();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, [screenSize]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<SideMenu
|
||||||
|
locationState={locationState}
|
||||||
|
collapsed={isCollapsed}
|
||||||
|
isMobile={isMobile}
|
||||||
|
logo={logo}
|
||||||
|
logoMobile={logoMobile}
|
||||||
|
onMenuButtonClick={handleMenuToggle}
|
||||||
|
onMenuItemClick={handleMenuItemClick}
|
||||||
|
onLogout={handleLogout}
|
||||||
|
/>
|
||||||
|
<Layout
|
||||||
|
className={`${styles.MainLayout} ${isCollapsed ? styles.collapsed : ''} ${
|
||||||
|
isMobile ? styles.mobile : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<GlobalHeader
|
||||||
|
collapsed={isCollapsed}
|
||||||
|
isMobile={isMobile}
|
||||||
|
logoMobile={logoMobile}
|
||||||
|
onMenuButtonClick={handleMenuToggle}
|
||||||
|
/>
|
||||||
|
<Content className={styles.Content}>{children}</Content>
|
||||||
|
<Footer className={styles.Footer}>
|
||||||
|
Copyright © {currentYear} ConnectUs Inc. All Rights Reserved.
|
||||||
|
</Footer>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MasterLayout.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
logo: PropTypes.string.isRequired,
|
||||||
|
logoMobile: PropTypes.string.isRequired,
|
||||||
|
onLogout: PropTypes.func.isRequired,
|
||||||
|
locationState: PropTypes.instanceOf(Object).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MasterLayout;
|
||||||
3
app/styles/index.scss
Normal file
3
app/styles/index.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
4
app/styles/variables.scss
Normal file
4
app/styles/variables.scss
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
$sidebar-width: 234px;
|
||||||
|
$sidebar-collapsed-width: 80px;
|
||||||
|
|
||||||
|
$header-height: 64px;
|
||||||
31
jest.config.js
Executable file
31
jest.config.js
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
module.exports = {
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'app/**/*.{js,jsx}',
|
||||||
|
'!app/**/*.test.{js,jsx}',
|
||||||
|
'!app/*/RbGenerated*/*.{js,jsx}',
|
||||||
|
'!app/app.js',
|
||||||
|
'!app/global-styles.js',
|
||||||
|
'!app/*/*/Loadable.{js,jsx}',
|
||||||
|
],
|
||||||
|
coverageThreshold: {
|
||||||
|
global: {
|
||||||
|
statements: 98,
|
||||||
|
branches: 91,
|
||||||
|
functions: 98,
|
||||||
|
lines: 98,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
moduleDirectories: ['node_modules', 'app'],
|
||||||
|
moduleNameMapper: {
|
||||||
|
'.*\\.(css|less|styl|scss|sass)$': '<rootDir>/internals/mocks/cssModule.js',
|
||||||
|
'.*\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||||
|
'<rootDir>/internals/mocks/image.js',
|
||||||
|
},
|
||||||
|
setupFilesAfterEnv: [
|
||||||
|
'<rootDir>/internals/testing/test-bundler.js',
|
||||||
|
'react-testing-library/cleanup-after-each',
|
||||||
|
],
|
||||||
|
setupFiles: ['raf/polyfill'],
|
||||||
|
testRegex: 'tests/.*\\.test\\.js$',
|
||||||
|
snapshotSerializers: [],
|
||||||
|
};
|
||||||
11
jsconfig.json
Normal file
11
jsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./app/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13625
package-lock.json
generated
Normal file
13625
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
80
package.json
Normal file
80
package.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"name": "cu-ui",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"author": "ConnectUs",
|
||||||
|
"description": "",
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=5",
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"format": "prettier --write \"app/**/*.js\"",
|
||||||
|
"eslint-fix": "eslint --fix \"app/**/*.js\"",
|
||||||
|
"build": "webpack --mode production"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^4.0.2",
|
||||||
|
"antd": "^4.0.2",
|
||||||
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
|
"connected-react-router": "^6.7.0",
|
||||||
|
"history": "^4.10.1",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react": "^16.13.0",
|
||||||
|
"react-dom": "^16.13.0",
|
||||||
|
"react-router-dom": "^5.1.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.8.7",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
|
"@babel/preset-env": "^7.8.7",
|
||||||
|
"@babel/preset-react": "^7.8.3",
|
||||||
|
"babel-core": "^6.26.3",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"babel-loader": "^8.0.6",
|
||||||
|
"babel-plugin-root-import": "^6.4.1",
|
||||||
|
"babel-polyfill": "^6.26.0",
|
||||||
|
"css-loader": "^3.4.2",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-config-airbnb": "^18.0.1",
|
||||||
|
"eslint-config-prettier": "^6.10.0",
|
||||||
|
"eslint-config-react": "^1.1.7",
|
||||||
|
"eslint-loader": "^3.0.3",
|
||||||
|
"eslint-plugin-import": "^2.20.1",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||||
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
|
"eslint-plugin-react": "^7.19.0",
|
||||||
|
"eslint-plugin-react-hooks": "^2.5.0",
|
||||||
|
"husky": "^4.2.3",
|
||||||
|
"less": "^3.11.1",
|
||||||
|
"less-loader": "^5.0.0",
|
||||||
|
"lint-staged": "^10.0.8",
|
||||||
|
"node-sass": "^4.13.1",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"pretty-quick": "^2.0.1",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"style-loader": "^1.1.3",
|
||||||
|
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||||
|
"webpack": "^4.42.0",
|
||||||
|
"webpack-cli": "^3.3.11",
|
||||||
|
"webpack-dev-server": "^3.10.3"
|
||||||
|
},
|
||||||
|
"precommit": "NODE_ENV=production lint-staged",
|
||||||
|
"browserslist": [
|
||||||
|
"last 2 versions",
|
||||||
|
"> 1%",
|
||||||
|
"IE 10"
|
||||||
|
],
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,jsx}": [
|
||||||
|
"pretty-quick --staged",
|
||||||
|
"eslint app/ --fix",
|
||||||
|
"git add"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "lint-staged"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
webpack.config.js
Normal file
80
webpack.config.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||||
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: process.env.NODE_ENV,
|
||||||
|
entry: ['babel-polyfill', './app/index.js'],
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
publicPath: '/',
|
||||||
|
filename: 'bundle.js',
|
||||||
|
},
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
devServer: {
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(js|jsx)$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: ['babel-loader', 'eslint-loader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Preprocess 3rd party .css files located in node_modules
|
||||||
|
test: /\.css$/,
|
||||||
|
include: /node_modules/,
|
||||||
|
use: ['style-loader', 'css-loader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.less$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'style-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'less-loader',
|
||||||
|
options: {
|
||||||
|
javascriptEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(css|scss)$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'style-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
modules: {
|
||||||
|
localIdentName: '[name]__[local]___[hash:base64:5]',
|
||||||
|
},
|
||||||
|
sourceMap: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'sass-loader',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimizer: [new UglifyJsPlugin()],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
modules: ['node_modules', path.resolve(`${__dirname}/app`)],
|
||||||
|
alias: {
|
||||||
|
app: path.resolve(`${__dirname}/app`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [new CleanWebpackPlugin()],
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user