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 (
);
}
}
================================================
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 (
);
}
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)}
);
}
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 (
{theme.direction === 'rtl' ? : }
{this.routes.map((route, index) => {
return (
{route.icon()}
);
})}
);
}
}
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 (
);
}
}
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 (
);
}
}
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
}
}