Repository: goemen/react-material-ui-typescript Branch: master Commit: c6620fe8e131 Files: 52 Total size: 65.4 KB Directory structure: gitextract_wgb9fbz0/ ├── .config/ │ └── tomahawkci.yml ├── .gitignore ├── Procfile ├── README.md ├── _config.yml ├── images.d.ts ├── package.json ├── public/ │ ├── index.html │ └── manifest.json ├── server.js ├── src/ │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── actions/ │ │ ├── App.Actions.ts │ │ └── Helpers.ts │ ├── alert/ │ │ └── Alert.tsx │ ├── components/ │ │ ├── MailList.tsx │ │ ├── TableHeader.tsx │ │ └── TableToolbar.tsx │ ├── data/ │ │ ├── mail.ts │ │ ├── material.ts │ │ └── users.ts │ ├── index.css │ ├── index.tsx │ ├── navigation/ │ │ ├── App.Bar.tsx │ │ ├── App.Drawer.tsx │ │ └── styles.ts │ ├── pages/ │ │ ├── Home.tsx │ │ ├── account/ │ │ │ ├── Account.tsx │ │ │ ├── Login.tsx │ │ │ └── Profile.tsx │ │ └── mail/ │ │ ├── Drafts.tsx │ │ ├── Inbox.tsx │ │ ├── Mail.tsx │ │ └── Sent.tsx │ ├── reducers/ │ │ ├── AuthenticationReducer.ts │ │ ├── CombinedReducers.ts │ │ └── UtilityReducer.ts │ ├── registerServiceWorker.ts │ ├── selectors/ │ │ └── index.ts │ ├── spinner/ │ │ └── Spinner.tsx │ ├── state/ │ │ ├── Alert.ts │ │ ├── AppState.ts │ │ ├── Helpers.ts │ │ ├── Spinner.ts │ │ ├── User.ts │ │ └── Utility.ts │ └── store/ │ └── Store.ts ├── tsconfig.json ├── tsconfig.prod.json ├── tsconfig.test.json └── tslint.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .config/tomahawkci.yml ================================================ version: '1.0.0' dependencies: - npm install --ignore-scripts tasks: test: - npm run test ================================================ FILE: .gitignore ================================================ # dependencies /node_modules # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* functions/ functions-src/ .vscode/settings.json ================================================ FILE: Procfile ================================================ web: npm run deploy ================================================ FILE: README.md ================================================ ## Description This is a boilerplate for React using Typescript, Material UI and Redux, React Router. ## Demo *Visit [Demo link](https://material-ui-admin.herokuapp.com/)* #### Login credentials * username/email: *anything* * password: *anything* ## Features ### Authentication The app uses redux to manage the authentication state, and uses redux-auth-wrapper library to guard the routes #### Pages * Login Page ### Admin dashboard The template comes with responsive modern charts, analytics, tables that are easily customizable to meet your data. ### Other pages * Inbox, Outbox, Drafts * Profile Page (coming soon) ## How to run ### Local development * Clone the project and cd into project * npm install * npm start and go to [link](*http://localhost:3000*) ### Deployment * npm install * npm run build * npm run deploy ## Key technologies & Libraries used * Material UI (1.1.0) - (for ui components) * React Router * Redux * Typescript * React (of course) ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-cayman ================================================ FILE: images.d.ts ================================================ declare module '*.svg' declare module '*.png' declare module '*.jpg' ================================================ FILE: package.json ================================================ { "name": "react-boilerplate", "version": "0.1.0", "private": true, "dependencies": { "@material-ui/core": "^1.5.1", "@material-ui/icons": "^1.1.1", "@types/immutable": "^3.8.7", "@types/lodash": "^4.14.119", "@types/moment": "^2.13.0", "@types/react-redux": "^6.0.13", "@types/react-router-dom": "^4.3.1", "@types/recharts": "^1.1.6", "@types/redux": "^3.6.31", "@types/redux-auth-wrapper": "^2.0.9", "@types/redux-devtools-extension": "^2.13.2", "@types/redux-thunk": "^2.1.32", "@types/reselect": "^2.2.0", "classnames": "^2.2.6", "express": "^4.16.4", "immutable": "^3.8.2", "lodash": "^4.17.11", "mobx": "^4.8.0", "mobx-react-devtools": "^5.0.1", "moment": "^2.23.0", "querystring": "^0.2.0", "randomcolor": "^0.5.3", "react": "^16.7.0", "react-dom": "^16.7.0", "react-mobx": "0.0.3", "react-redux": "^5.1.1", "react-router-dom": "^4.3.1", "react-scripts-ts": "2.16.0", "recharts": "^1.4.2", "redux": "^4.0.1", "redux-auth-wrapper": "^2.0.3", "redux-devtools-extension": "^2.13.7", "redux-rest-resource": "^0.18.0", "redux-thunk": "^2.3.0", "reselect": "^3.0.1" }, "scripts": { "start": "react-scripts-ts start", "build": "react-scripts-ts build", "test": "react-scripts-ts test --env=jsdom", "postinstall": "npm run build", "deploy": "node server.js", "eject": "react-scripts-ts eject" }, "devDependencies": { "@types/jest": "^22.2.3", "@types/node": "^10.12.18", "@types/react": "^16.7.18", "@types/react-dom": "^16.0.11", "babel-plugin-transform-decorators-legacy": "^1.3.4", "typescript": "^3.2.2" }, "babel": { "plugins": [ "transform-decorators-legacy" ], "presets": [ "react-app" ] } } ================================================ FILE: public/index.html ================================================ Tomahawk
================================================ FILE: public/manifest.json ================================================ { "short_name": "React App", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "start_url": "./index.html", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: server.js ================================================ const express = require('express'); const path = require('path'); const app = express(); app.use(express.static(path.join(__dirname, 'build'))); app.get('*', function (req, res) { res.sendFile(path.join(__dirname, 'build', 'index.html')); }); app.listen(process.env.PORT || 3000); ================================================ FILE: src/App.css ================================================ .App { text-align: center; } .App-logo { animation: App-logo-spin infinite 20s linear; height: 80px; } .App-header { background-color: #222; height: 150px; padding: 20px; color: white; } .App-title { font-size: 1.5em; } .App-intro { font-size: large; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ================================================ FILE: src/App.test.tsx ================================================ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import App from './App'; it('renders without crashing', () => { const div = document.createElement('div'); ReactDOM.render(, div); ReactDOM.unmountComponentAtNode(div); }); ================================================ FILE: src/App.tsx ================================================ import * as React from 'react'; import './App.css'; import AppNavBar from './navigation/App.Bar'; import { BrowserRouter as Router } from 'react-router-dom'; import { Provider } from 'react-redux'; import { store } from './store/Store'; import blue from '@material-ui/core/colors/blue'; import { createMuiTheme, MuiThemeProvider } from '@material-ui/core'; import { pink } from '@material-ui/core/colors'; const theme = createMuiTheme({ palette: { primary: blue, secondary: pink } }) class App extends React.Component { public render() { return ( ); } } export default App; ================================================ FILE: src/actions/App.Actions.ts ================================================ import { IAppAction, ActionType } from './Helpers'; import { match } from 'react-router'; import { Utility } from '../state/Utility'; import { Alert } from '../state/Alert'; import { Spinner } from '../state/Spinner'; import { User } from '../state/User'; export interface IApplicationProps { openDrawer: () => IAppAction; closeDrawer: () => IAppAction; showPopup: (alert: Alert) => IAppAction; closePopup: () => IAppAction; showSpinner: (message: string) => IAppAction; hideSpinner: () => IAppAction; login: (data: any) => IAppAction; logout: () => IAppAction; createUser: (content: any) => any; getUser: (id: any) => any; fetchUsers: (context?: any) => any; updateUser: (context: any) => any; deleteUser: (context: any) => any; createMaterial: (content: any) => any; getMaterial: (id: any) => any; fetchMaterials: (context?: any) => any; updateMaterial: (context: any) => any; deleteMaterial: (context: any) => any; getMail: (id: any) => any; fetchMails: (context?: any) => any; updateMail: (context: any) => any; deleteMail: (context: any) => any; match: match, location: any, history: any, utility: Utility; authentication: User; users: any; materials: any; mail: any[]; materialCharts: Array<{name: string, value: number, fill: string}>; } export const openDrawer = (): IAppAction => { return { type: ActionType.OPEN_DRAWER }; }; export const closeDrawer = (): IAppAction => { return { type: ActionType.CLOSE_DRAWER }; }; export const showPopup = (data: Alert): IAppAction => { return { type: ActionType.OPEN_ALERT, payload: data }; }; export const closePopup = (): IAppAction => { return { type: ActionType.CLOSE_ALERT }; }; export const showSpinner = (message: string): IAppAction => { return { type: ActionType.OPEN_SPINNER, payload: new Spinner({message}) }; }; export const hideSpinner = (): IAppAction => { return { type: ActionType.CLOSE_SPINNER }; }; export const login = (data: any): IAppAction => { return { type: ActionType.LOGIN_REQUEST, payload: data }; }; export const logout = (): IAppAction => { return { type: ActionType.LOGOUT_REQUEST }; }; ================================================ FILE: src/actions/Helpers.ts ================================================ import { Action } from "redux"; export enum ActionType { OPEN_DRAWER, CLOSE_DRAWER, OPEN_ALERT, CLOSE_ALERT, OPEN_SPINNER, CLOSE_SPINNER, LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT_REQUEST, LOGOUT_SUCCESS, LOGOUT_FAIL } export interface IAppAction extends Action { payload?: any; } ================================================ FILE: src/alert/Alert.tsx ================================================ import * as React from 'react'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; import { Alert } from '../state/Alert'; interface IAlertProps { data?: Alert; handleClose: () => void; children?: any; } export class AlertDialog extends React.Component { public handleClose = () => { this.props.handleClose(); }; public render() { return ( {this.props.data.title} { this.props.data.message } ); } } ================================================ FILE: src/components/MailList.tsx ================================================ import * as React from 'react'; import { withStyles, Theme } from '@material-ui/core/styles'; import ExpansionPanel from '@material-ui/core/ExpansionPanel'; import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; import ExpansionPanelActions from '@material-ui/core/ExpansionPanelActions'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { Avatar } from '@material-ui/core'; import Divider from '@material-ui/core/Divider'; import Button from '@material-ui/core/Button'; const styles = (theme: Theme) => ({ root: { width: '100%', }, avatar: { marginRight: 5 }, summary: { display: 'flex', alignContent: 'center', alignItems: 'center', }, heading: { fontSize: theme.typography.pxToRem(15), height: '100%', verticalAlign: 'middle', flexBasis: '33.33%', flexShrink: 0, color: theme.palette.text.secondary, }, secondaryHeading: { fontSize: theme.typography.pxToRem(15), }, }); interface IMailListProps { items: any[]; classes: any; } interface IState { expanded?: number; } class MailList extends React.Component { public state: IState = { expanded: null, }; private handleChange = (panel: any) => (event: any, expanded: any) => { this.setState({ expanded: expanded ? panel : false, }); }; public render() { const { classes } = this.props; const { expanded } = this.state; return (
{this.props.items.map((item: any) => { return ( }> {item.from} {item.subject} {item.content} ); })}
); } } export default withStyles(styles)(MailList); ================================================ FILE: src/components/TableHeader.tsx ================================================ import * as React from 'react'; import { TableHead, TableRow, TableCell, Checkbox, Tooltip, TableSortLabel } from '@material-ui/core'; export interface IColumnData { id?: string; numeric?: boolean; disablePadding?: boolean; label?: string; } interface IEnhancedTableHeadProps { onRequestSort?: (event: any, property: any) => any; onSelectAllClick?: any; order?: any; orderBy?: any; selected?: number; count?: number; columns?: IColumnData[]; } export class EnhancedTableHead extends React.Component { private createSortHandler = (property: any) => (event: any) => { this.props.onRequestSort(event, property); }; public render() { const { columns, onSelectAllClick, order, orderBy, selected, count } = this.props; return ( 0 && selected < count} checked={selected === count} onChange={onSelectAllClick} /> {columns.map(column => { return ( {column.label} ); }, this)} ); } } ================================================ FILE: src/components/TableToolbar.tsx ================================================ import * as React from 'react'; import { Toolbar, Typography, Tooltip, IconButton, Theme, withStyles } from '@material-ui/core'; const classNames = require('classnames'); import DeleteIcon from '@material-ui/icons/Delete'; import FilterListIcon from '@material-ui/icons/FilterList'; interface IEnhancedTableToolbarProps { classes?: any; selected?: number; } class EnhancedTableToolbar extends React.Component { public render(): JSX.Element { const { selected, classes } = this.props; return ( 0, })} >
{selected > 0 ? ( {selected} selected ) : ( Nutrition )}
{selected > 0 ? ( ) : ( )}
); } } const toolbarStyles = (theme: Theme) => ({ root: { paddingRight: theme.spacing.unit, }, highlight: theme.palette.type === 'light' ? { color: theme.palette.secondary.main, backgroundColor: `lighten(${theme.palette.secondary.light}, 0.85)`, } : { color: theme.palette.text.primary, backgroundColor: theme.palette.secondary.dark, }, spacer: { flex: '1 1 100%', }, actions: { color: theme.palette.text.secondary, }, title: { flex: '0 0 auto', }, }); export default withStyles(toolbarStyles)(EnhancedTableToolbar as any); ================================================ FILE: src/data/mail.ts ================================================ const Resourcer = require('redux-rest-resource'); export const { types, actions, rootReducer } = Resourcer.createResource({ name: 'mail', url: `https://5b1b0a966e0fd400146aaee2.mockapi.io/mail/:id` }); ================================================ FILE: src/data/material.ts ================================================ const Resourcer = require('redux-rest-resource'); export const { types, actions, rootReducer } = Resourcer.createResource({ name: 'material', url: `https://5b1b0a966e0fd400146aaee2.mockapi.io/materials/:id` }); ================================================ FILE: src/data/users.ts ================================================ const Resourcer = require('redux-rest-resource'); export const { types, actions, rootReducer } = Resourcer.createResource({ name: 'user', url: `https://5b1b0a966e0fd400146aaee2.mockapi.io/users/:id` }); ================================================ FILE: src/index.css ================================================ body { margin: 0; padding: 0; font-family: sans-serif; } ================================================ FILE: src/index.tsx ================================================ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import App from './App'; import './index.css'; import registerServiceWorker from './registerServiceWorker'; ReactDOM.render( , document.getElementById('root') as HTMLElement); registerServiceWorker(); ================================================ FILE: src/navigation/App.Bar.tsx ================================================ //#region import * as React from 'react'; const classNames = require('classnames'); import { withStyles } from '@material-ui/core/styles'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; import IconButton from '@material-ui/core/IconButton'; import MenuIcon from '@material-ui/icons/Menu'; import { ListItemText, Menu, MenuItem, Badge, Avatar } from '@material-ui/core'; import { Route, withRouter } from 'react-router-dom'; import Hidden from '@material-ui/core/Hidden'; import { styles } from './styles'; import { IApplicationProps } from '../actions/App.Actions'; import * as AppActionCreators from '../actions/App.Actions'; import { AppState, isAuthenticated } from '../state/AppState'; import { connect } from 'react-redux'; import * as _ from 'lodash'; import { bindActionCreators, Dispatch} from 'redux'; import { Alert } from '../state/Alert'; import { AlertDialog } from '../alert/Alert'; import SpinnerDialog from '../spinner/Spinner'; import { AccountPage } from '../pages/account/Account'; import { MailPage } from '../pages/mail/Mail'; import HomePage from '../pages/Home'; import AccountCircle from '@material-ui/icons/AccountCircle'; import { actions as UserActionCreators } from '../data/users'; import { actions as MailActionCreators } from '../data/mail'; import { actions as MaterialActionCreators } from '../data/material'; import { getMaterialChartItems, getMailitems } from '../selectors'; import AppDrawer from './App.Drawer'; import NotificationIcon from '@material-ui/icons/Notifications'; //#endregion interface IAppProps extends IApplicationProps { classes: any; theme?: any; } interface IState { anchorEl: any; notificationEl: any; } class MiniDrawer extends React.Component { public state: IState = { anchorEl: null, notificationEl: null }; public componentWillMount() { this.props.fetchUsers(); this.props.fetchMaterials(); this.props.fetchMails(); } private handleNotificationMenu = (event: any) => { this.setState({ notificationEl: event.currentTarget }); }; private handleNotificationMenuClose = () => { this.setState({ notificationEl: null }); }; private handleMenu = (event: any) => { this.setState({ anchorEl: event.currentTarget }); }; private handleMenuClose = (path?: string) => { this.setState({ anchorEl: null }); this.navigate(path); }; public handleLogout = () => { this.props.logout(); this.handleMenuClose(); }; private navigate = (path?: string) => { if (path) { this.props.history.push(path); } } public handleDrawerOpen = () => { this.props.openDrawer(); }; public handleDrawerClose = () => { this.props.closeDrawer(); }; public showPopup = () => { this.props.showPopup(new Alert({ title: "Testing title", message: "This is a very long message, expect alert to be very wide" })) } public showSpinner = () => { this.props.showSpinner("I am loading here please...") } private renderAlert(): JSX.Element { if (this.props.utility.alert) { return ( ); } return null } private renderSpinner(): JSX.Element { if (this.props.utility.spinner) { return ( ); } return null } private renderNotifications(notifications: any[]) { const { classes } = this.props; return ( {notifications.map((n: any) => ( ))} ); } private renderAppBar() { if (this.props.authentication) { const { classes, utility } = this.props; const { anchorEl, notificationEl } = this.state; const open = Boolean(anchorEl); const notificationsOpen = Boolean(notificationEl); const unreadMessages = this.props.mail.filter(x => x.seen === false); return ( Tomahawk
{this.renderNotifications(unreadMessages)} {this.props.authentication.name} Logout
); } return null; } private renderAccount = () => { return ( ); } private renderDrawer() { const { utility, authentication } = this.props; return ( ); } public render() { const { classes } = this.props; const Dashboard = isAuthenticated((props: any): any => { return ( ); }); const MailBoard = isAuthenticated((props: any): any => { return ( ); }); return (
{this.renderAppBar()} {this.renderDrawer()}
{this.renderAlert()} {this.renderSpinner()}
); } } const mapStateToProps = (state: AppState) => ({ utility: state.utility, authentication: state.authentication, users: state.users, materials: state.materials, materialCharts: getMaterialChartItems(state), mail: getMailitems(state) }); const mapDispatchtoProps = (dispatch: Dispatch) => bindActionCreators(_.assign({}, AppActionCreators, MailActionCreators, UserActionCreators, MaterialActionCreators), dispatch); export default withRouter(connect(mapStateToProps, mapDispatchtoProps)(withStyles(styles as any, { withTheme: true })(MiniDrawer as any)) as any); ================================================ FILE: src/navigation/App.Drawer.tsx ================================================ import * as React from 'react'; import InboxIcon from '@material-ui/icons/Inbox'; import DraftsIcon from '@material-ui/icons/Drafts'; import SendIcon from '@material-ui/icons/Send'; import AccountCircleIcon from '@material-ui/icons/AccountCircle'; import DashboardIcon from '@material-ui/icons/Dashboard'; import { Drawer, IconButton, Divider, Theme, ListItem, ListItemIcon, ListItemText, withStyles } from '@material-ui/core'; import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import { User } from '../state/User'; import { Utility } from '../state/Utility'; import { NavLink } from 'react-router-dom'; import { styles } from './styles'; const classNames = require('classnames'); interface IAppDrawer { authentication?: User; utility: Utility; classes?: any; theme?: Theme; handleDrawerClose?: () => void; } class AppDrawer extends React.Component { public routes = [ { path: '/', title: 'Dashboard', icon: () => }, { path: '/mail/inbox', title: 'Inbox', icon: () => }, { path: '/mail/sent', title: 'Sent', icon: () => }, { path: '/mail/drafts', title: 'Drafts', icon: () => }, { path: '/account', title: 'Profile', icon: () => } ] public render(): JSX.Element { const { authentication, classes, utility, theme } = this.props; return ( ); } } export default withStyles(styles as any, { withTheme: true })(AppDrawer as any) as any; ================================================ FILE: src/navigation/styles.ts ================================================ import { Theme } from "@material-ui/core"; const drawerWidth = 240; export const styles = (theme: Theme) => ({ root: { flexGrow: 1, height: '100vh', minHeight: '100%', zIndex: 1, overflow: 'hidden', position: 'relative', display: 'flex', width: '100%', backgroundColor: theme.palette.background.default, }, appBar: { zIndex: theme.zIndex.drawer + 1, transition: theme.transitions.create(['width', 'margin'], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), [theme.breakpoints.up('md')]: { width: `100%`, }, }, appBarShift: { marginLeft: drawerWidth, width: `calc(100% - ${drawerWidth}px)`, transition: theme.transitions.create(['width', 'margin'], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, }), }, menuButton: { marginLeft: 12, marginRight: 36, }, hide: { display: 'none', }, drawerPaper: { position: 'relative', top: 0, whiteSpace: 'nowrap', width: drawerWidth, transition: theme.transitions.create('width', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, }) }, drawerPaperClose: { overflowX: 'hidden', transition: theme.transitions.create('width', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), width: theme.spacing.unit * 7, [theme.breakpoints.up('sm')]: { width: theme.spacing.unit * 9, }, }, toolbar: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', padding: '0 8px', ...theme.mixins.toolbar, }, content: { flexGrow: 1, backgroundColor: theme.palette.background.default, padding: theme.spacing.unit * 3, minHeight: '100%', height: '100%', flex: '1 1 auto', overflowY: 'scroll' }, button: { margin: theme.spacing.unit, }, link: { textDecoration: 'none', }, current: { color: 'red !important', }, notifications: { overflowX: 'hidden' }, fillSpace: { flex: '1 1 auto' } }); ================================================ FILE: src/pages/Home.tsx ================================================ import * as React from 'react'; import { Theme, withStyles, Paper, Table, TableHead, TableRow, TableCell, TableBody, TablePagination, Grid, Typography } from '@material-ui/core'; import { BarChart, CartesianGrid, XAxis, YAxis, Bar, Tooltip, Legend, PieChart, Pie, ResponsiveContainer } from 'recharts'; const classNames = require('classnames'); import GroupIcon from '@material-ui/icons/Group'; import MailIcon from '@material-ui/icons/Mail'; import SettingsIcon from '@material-ui/icons/Settings'; import BusinessIcon from '@material-ui/icons/BusinessCenter'; interface IDashboardProps { fetchUsers: (context?: any) => void; users: any; materialChartData: any[]; classes?: any; theme?: any; children?: any; } interface IPageState { usersTablePage?: number; usersTableRowsPerPage: number; } class HomePage extends React.Component { public state: IPageState = { usersTablePage: 0, usersTableRowsPerPage: 5 }; private handleChangeUsersPage = (event: any, page: number) => { console.log(event); this.setState({ usersTablePage: page }); }; private handleChangeTableRowsPerPage = (event: any) => { this.setState({ usersTableRowsPerPage: event.target.value }); }; private renderUsers(): JSX.Element { const { users, classes } = this.props; if (!users) { return null; } return (

Customers

Id Name Email {users.items.slice(this.state.usersTablePage * this.state.usersTableRowsPerPage, this.state.usersTablePage * this.state.usersTableRowsPerPage + this.state.usersTableRowsPerPage).map((n: any) => { return ( {n.id} {n.name} {n.email} ); })}
); } private renderRadialBarChart(): JSX.Element { return (

Material Inventory

); } private renderBarChart(): JSX.Element { return (

Material Sales

); } public render(): JSX.Element { const { classes } = this.props; return (
{this.props.users.items.length} Customers Inbox Purchases Settings {this.renderBarChart()} {this.renderRadialBarChart()} {this.renderUsers()}
); } } const styles = (theme: Theme) => ({ root: { flexGrow: 1, marginBottom: 24, }, paper: { padding: theme.spacing.unit * 2, textAlign: 'center', color: theme.palette.text.secondary, }, headerTiles: { overflowX: 'hidden', display: 'flex', justifyContent: 'center', alignItems: 'center', borderRight: `5px solid ${theme.palette.secondary.main}`, }, headerTileIcon: { fontSize: 40, color: theme.palette.primary.main, paddingRight: 5 }, tileText: { fontSize: 20, color: theme.palette.grey["400"], }, sectionTitle: { paddingLeft: theme.spacing.unit * 2, }, users: { marginBottom: 24, overflowX: 'scroll' }, chart: { width: '100%' }, }); export default withStyles(styles as any)(HomePage as any) as any; ================================================ FILE: src/pages/account/Account.tsx ================================================ import * as React from 'react'; import { User } from '../../state/User'; import LoginPage from './Login'; import { Route, Switch } from 'react-router'; import { ProfilePage } from './Profile'; import { isAuthenticated } from '../../state/AppState'; interface IAccountProps { login?: (data: any) => void; match?: any; location?: any; classes?: any; user: User; } export class AccountPage extends React.Component { private renderLogin = () => { return ( ); } public render(): JSX.Element { return ( ); } } ================================================ FILE: src/pages/account/Login.tsx ================================================ import * as React from 'react'; import { Theme, withStyles, FormControl, InputLabel, Input, InputAdornment, Button, Icon } from '@material-ui/core'; import Paper from '@material-ui/core/Paper'; import * as querystring from 'querystring'; import { User } from '../../state/User'; import { Redirect } from 'react-router'; interface ILoginProps { login?: (data: any) => void; match?: any; location?: any; classes?: any; user: User; } interface ILoginState { email: string; password: string; } class LoginPage extends React.Component { public state = { email: "", password: "" }; private handleEmailAddressChange = (event: any) => { this.setState({ email: event.target.value }) } private handlePasswordChange = (event: any) => { this.setState({ password: event.target.value }) } private handleLogin = () => { this.props.login(this.state); } public render(): JSX.Element { const classes = this.props.classes; if (this.props.user) { const path: string = querystring. parse((this.props.location.search as string).substr(1)).redirect as any || '/inbox'; return } return (

{'Login'}

Email Address email } /> Password lock } />
); } } const styles = (theme: Theme) => ({ container: { display: 'flex', justifyContent: 'center' }, paper: theme.mixins.gutters({ paddingTop: 16, paddingBottom: 16, marginTop: theme.spacing.unit * 3, width: '30%', display: 'flex', flexDirection: 'column', alignContent: 'center', [theme.breakpoints.down('md')]: { width: '100%', }, }), field: { marginTop: theme.spacing.unit * 3 }, actions: theme.mixins.gutters({ paddingTop: 16, paddingBottom: 16, marginTop: theme.spacing.unit * 3, display: 'flex', flexDirection: 'row', alignContent: 'center' }), button: { marginRight: theme.spacing.unit }, }); export default withStyles(styles, { withTheme: true })(LoginPage as any) as any; ================================================ FILE: src/pages/account/Profile.tsx ================================================ import * as React from 'react'; import { Typography } from '@material-ui/core'; export class ProfilePage extends React.Component<{}, {}> { public render(): JSX.Element { return ({"Profile Page"}) } } ================================================ FILE: src/pages/mail/Drafts.tsx ================================================ import * as React from 'react' import MailList from '../../components/MailList'; import { Paper, Typography, Theme, withStyles } from '@material-ui/core'; import { Button } from '@material-ui/core'; interface IDraftsProps { items: any[]; classes: any; } class DraftsPage extends React.Component { public render(): JSX.Element { const { classes } = this.props; return (
Drafts
); } } const styles = (theme: Theme) => ({ root: { width: '100%', }, boxHeader: { width: '100%', display: 'flex', [theme.breakpoints.down('md')]: { flexDirection: 'column', }, }, boxHeaderTitle: { padding: '5px 10px', fontSize: 35, }, fillRemainingSpace: { flex: '1 1 auto', [theme.breakpoints.down('md')]: { display: 'none', }, }, }); export default withStyles(styles as any)(DraftsPage as any) as any; ================================================ FILE: src/pages/mail/Inbox.tsx ================================================ import * as React from 'react' import MailList from '../../components/MailList'; import { Paper, Typography, Theme, withStyles } from '@material-ui/core'; import { Button } from '@material-ui/core'; interface InboxProps { items: any[]; classes: any; } class InboxPage extends React.Component { public render(): JSX.Element { const { classes } = this.props; return (
Inbox
); } } const styles = (theme: Theme) => ({ root: { width: '100%', }, boxHeader: { width: '100%', display: 'flex', [theme.breakpoints.down('md')]: { flexDirection: 'column', }, }, boxHeaderTitle: { padding: '5px 10px', fontSize: 35, }, fillRemainingSpace: { flex: '1 1 auto', [theme.breakpoints.down('md')]: { display: 'none', }, }, }); export default withStyles(styles as any)(InboxPage as any) as any; ================================================ FILE: src/pages/mail/Mail.tsx ================================================ import * as React from 'react'; import { Route, Switch } from 'react-router'; import InboxPage from './Inbox'; import SentPage from './Sent'; import DraftsPage from './Drafts'; interface IMailProps { match?: any; location?: any; classes?: any; mail: any[]; } export class MailPage extends React.Component { private renderInbox = () => { return (); } private renderSent = () => { return (); } private renderDrafts = () => { return (); } public render(): JSX.Element { return ( ); } } ================================================ FILE: src/pages/mail/Sent.tsx ================================================ import * as React from 'react' import MailList from '../../components/MailList'; import { Paper, Typography, Theme, withStyles } from '@material-ui/core'; import { Button } from '@material-ui/core'; interface ISentProps { items: any[]; classes: any; } class SentPage extends React.Component { public render(): JSX.Element { const { classes } = this.props; return (
Sent
); } } const styles = (theme: Theme) => ({ root: { width: '100%', }, boxHeader: { width: '100%', display: 'flex', [theme.breakpoints.down('md')]: { flexDirection: 'column', }, }, boxHeaderTitle: { padding: '5px 10px', fontSize: 35, }, fillRemainingSpace: { flex: '1 1 auto', [theme.breakpoints.down('md')]: { display: 'none', }, }, }); export default withStyles(styles as any)(SentPage as any) as any; ================================================ FILE: src/reducers/AuthenticationReducer.ts ================================================ import { IAppAction, ActionType } from './../actions/Helpers'; import { User } from '../state/User'; export const AuthenticationReducer = (state: User = null, action: IAppAction): User => { switch (action.type) { case ActionType.LOGIN_REQUEST: return new User({email: action.payload.email, name: 'Goeme Nthomiwa', roles: ['Admin']}); case ActionType.LOGOUT_REQUEST: return null; default: return state; } }; ================================================ FILE: src/reducers/CombinedReducers.ts ================================================ import { combineReducers } from "redux"; import { UtilityReducer } from './UtilityReducer'; import { AuthenticationReducer } from "./AuthenticationReducer"; import { rootReducer as usersReducers } from "../data/users"; import { rootReducer as materialsReducers } from "../data/material"; import { rootReducer as mailReducers } from "../data/mail"; export const reducers = combineReducers({ utility: UtilityReducer, authentication: AuthenticationReducer, users: usersReducers, materials: materialsReducers, mail: mailReducers }); ================================================ FILE: src/reducers/UtilityReducer.ts ================================================ import { IAppAction, ActionType } from './../actions/Helpers'; import { Utility } from '../state/Utility'; export const UtilityReducer = (state: Utility = new Utility(), action: IAppAction): Utility => { switch (action.type) { case ActionType.OPEN_DRAWER: return state.set(Utility.DRAWER_OPEN, true) as Utility; case ActionType.CLOSE_DRAWER: return state.set(Utility.DRAWER_OPEN, false) as Utility; case ActionType.OPEN_ALERT: return state.set(Utility.ALERT, action.payload) as Utility; case ActionType.CLOSE_ALERT: return state.set(Utility.ALERT, null) as Utility; case ActionType.OPEN_SPINNER: return state.set(Utility.SPINNER, action.payload) as Utility; case ActionType.CLOSE_SPINNER: return state.set(Utility.SPINNER, null) as Utility; default: return state; } }; ================================================ FILE: src/registerServiceWorker.ts ================================================ // tslint:disable:no-console // In production, we register a service worker to serve assets from local cache. // This lets the app load faster on subsequent visits in production, and gives // it offline capabilities. However, it also means that developers (and users) // will only see deployed updates on the 'N+1' visit to a page, since previously // cached resources are updated in the background. // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. // This link also includes instructions on opting out of this behavior. const isLocalhost = Boolean( window.location.hostname === 'localhost' || // [::1] is the IPv6 localhost address. window.location.hostname === '[::1]' || // 127.0.0.1/8 is considered localhost for IPv4. window.location.hostname.match( /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ ) ); export default function register() { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. const publicUrl = new URL( process.env.PUBLIC_URL!, window.location.toString() ); if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 return; } window.addEventListener('load', () => { const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; if (isLocalhost) { // This is running on localhost. Lets check if a service worker still exists or not. checkValidServiceWorker(swUrl); // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + 'worker. To learn more, visit https://goo.gl/SC7cgQ' ); }); } else { // Is not local host. Just register service worker registerValidSW(swUrl); } }); } } function registerValidSW(swUrl: string) { navigator.serviceWorker .register(swUrl) .then(registration => { registration.onupdatefound = () => { const installingWorker = registration.installing; if (installingWorker) { installingWorker.onstatechange = () => { if (installingWorker.state === 'installed') { if (navigator.serviceWorker.controller) { // At this point, the old content will have been purged and // the fresh content will have been added to the cache. // It's the perfect time to display a 'New content is // available; please refresh.' message in your web app. console.log('New content is available; please refresh.'); } else { // At this point, everything has been precached. // It's the perfect time to display a // 'Content is cached for offline use.' message. console.log('Content is cached for offline use.'); } } }; } }; }) .catch(error => { console.error('Error during service worker registration:', error); }); } function checkValidServiceWorker(swUrl: string) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl) .then(response => { // Ensure service worker exists, and that we really are getting a JS file. if ( response.status === 404 || response.headers.get('content-type')!.indexOf('javascript') === -1 ) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then(registration => { registration.unregister().then(() => { window.location.reload(); }); }); } else { // Service worker found. Proceed as normal. registerValidSW(swUrl); } }) .catch(() => { console.log( 'No internet connection found. App is running in offline mode.' ); }); } export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { registration.unregister(); }); } } ================================================ FILE: src/selectors/index.ts ================================================ import { AppState } from '../state/AppState'; import { createSelector } from 'reselect'; const randomColor = require('randomcolor'); import * as _ from 'lodash'; import * as moment from 'moment'; const materialItemsSelector = (state: AppState) => state.materials.items; const mailSelector = (state: AppState) => state.mail; export const getMaterialChartItems = createSelector(materialItemsSelector, (items: any[]) => { const categories = _.groupBy(items, x => x.category); const data = _.keys(categories).map(category => ({ name: category, value: categories[category].length, fill: randomColor() })); return data; }); export const getMailitems = createSelector(mailSelector, (mail: any) => { return _.sortBy(mail.items.map((item: any) => _.assign({}, item, {createdAt: moment(item.createdAt)}), (i: any) => i.createdAt)); }); ================================================ FILE: src/spinner/Spinner.tsx ================================================ import * as React from 'react'; import Dialog from '@material-ui/core/Dialog'; import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import { withStyles, CircularProgress, WithStyles } from '@material-ui/core'; const styles = (theme: any) => ({ progress: { margin: theme.spacing.unit * 2, }, content: { display: 'flex', alignItems: 'center' } }); interface ISpinnerProps { message?: string; classes?: any; } class SpinnerDialog extends React.Component, {}> { public render() { return ( {this.props.message} ); } } export default withStyles(styles)(SpinnerDialog); ================================================ FILE: src/state/Alert.ts ================================================ import { Model } from "./Helpers"; interface IAlertButtonOptions { label: string; handler: () => void; } export interface IAlert { title?: string; message: string; buttons?: IAlertButtonOptions[]; } export const AlertModel = Model({ title: null, message: null, buttons: null }); export class Alert extends AlertModel { public title: string; public message: string; public buttons: IAlertButtonOptions[]; constructor(data: IAlert) { super(data); } } ================================================ FILE: src/state/AppState.ts ================================================ import { connectedRouterRedirect } from 'redux-auth-wrapper/history4/redirect'; import { Utility } from './Utility'; import { Model } from "./Helpers"; import { User } from './User'; export interface IAppState { utility?: Utility; authentication?: User; users?: any; materials?: any; mail?: any; } export const AppStateModel = Model({ utility: new Utility(), authentication: null, users: null, materials: null, mail: null }); export class AppState extends AppStateModel { public static UTILITY = 'utility'; public static AUTHENTICATION = "authentication"; public utility: Utility; public authentication: User; public users: any; public materials: any; public mail: any; } export const isAuthenticated = connectedRouterRedirect({ redirectPath: '/account/login', authenticatedSelector: (state: AppState) => state.authentication !== null, wrapperDisplayName: 'Authenticated' }) as any; ================================================ FILE: src/state/Helpers.ts ================================================ import { Record } from 'immutable'; export const Model = (data: T): Record.Class => { return Record(data); }; ================================================ FILE: src/state/Spinner.ts ================================================ import { Model } from "./Helpers"; export interface ISpinner { message: string; } export const SpinnerModel = Model({ message: null }); export class Spinner extends SpinnerModel { public static MESSAGE = 'message'; public message: string; } ================================================ FILE: src/state/User.ts ================================================ import { Model } from "./Helpers"; import * as _ from 'lodash'; export interface IUser { email?: string; name?: string; roles?: string[]; } const UserModel = Model({ email: null, name: null, roles: null }); export class User extends UserModel { public static EMAIL = 'email'; public static NAME = 'name'; public static ROLES = 'roles'; public email: string; public name: string; public roles: string[]; public isInRole(candidate: string) { return _.intersection(this.roles, [candidate]).length > 0; } } ================================================ FILE: src/state/Utility.ts ================================================ import { Model } from "./Helpers"; import { Alert } from './Alert'; import { Spinner } from "./Spinner"; export interface IUtility { drawerOpen?: boolean; alert?: Alert; spinner?: Spinner; } export const UtilityModel = Model({ drawerOpen: false, alert: null, spinner: null }); export class Utility extends UtilityModel { public static DRAWER_OPEN = 'drawerOpen'; public static ALERT = 'alert'; public static SPINNER = 'spinner'; public drawerOpen: boolean; public alert: Alert; public spinner: Spinner; } ================================================ FILE: src/store/Store.ts ================================================ import { reducers } from '../reducers/CombinedReducers'; import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk'; export const store = createStore(reducers, applyMiddleware(thunk)); ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "outDir": "build/dist", "module": "esnext", "target": "es5", "lib": ["es6", "dom"], "sourceMap": true, "allowJs": true, "jsx": "react", "moduleResolution": "node", "rootDir": "src", "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, "noImplicitAny": true, "strictNullChecks": false, "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true, "experimentalDecorators": true, "emitDecoratorMetadata": true }, "exclude": [ "node_modules", "build", "scripts", "acceptance-tests", "webpack", "jest", "src/setupTests.ts" ] } ================================================ FILE: tsconfig.prod.json ================================================ { "extends": "./tsconfig.json" } ================================================ FILE: tsconfig.test.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs" } } ================================================ FILE: tslint.json ================================================ { "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "linterOptions": { "exclude": [ "config/**/*.js", "node_modules/**/*.ts" ] }, "rules": { "ordered-imports": false, "member-ordering": false, "object-literal-sort-keys": false, "no-var-requires": false, "no-console": false, "jsx-no-lambda": false } }