├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── Gruntfile.js ├── README.md ├── package.json ├── src ├── config.dev.json ├── config.json ├── data │ ├── generate.js │ └── user.schema.json ├── favicon.ico ├── images │ └── yeoman.png ├── index.html ├── scripts │ ├── components │ │ ├── App.js │ │ ├── Dashboard.js │ │ ├── Loading.js │ │ ├── auth │ │ │ ├── Login.js │ │ │ ├── Logout.js │ │ │ └── login.css │ │ ├── footer │ │ │ ├── PageFooter.js │ │ │ └── footer.css │ │ ├── grid │ │ │ ├── DateDisplay.js │ │ │ ├── EditingDisplay.js │ │ │ ├── Grid.js │ │ │ └── grid.css │ │ ├── inputs │ │ │ ├── Datepicker.js │ │ │ └── datepicker.css │ │ ├── navbar │ │ │ ├── Navbar.js │ │ │ ├── Sidebar.js │ │ │ └── navbar.css │ │ ├── popups │ │ │ ├── Modal.js │ │ │ └── modal.css │ │ ├── static │ │ │ ├── About.js │ │ │ └── NotFound.js │ │ └── users │ │ │ ├── Actions.js │ │ │ ├── Constants.js │ │ │ ├── Edit.js │ │ │ ├── EditingDisplay.js │ │ │ ├── List.js │ │ │ ├── RemoveModal.js │ │ │ ├── Store.js │ │ │ └── users.css │ ├── main.js │ ├── mixins │ │ └── Authentication.js │ ├── services │ │ └── auth.js │ └── utils │ │ ├── Dispatcher.js │ │ └── request.js └── styles │ ├── --media.css │ ├── --selectors.css │ ├── --variables.css │ └── main.css ├── webpack.config.js └── webpack.dist.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/data/ 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "airbnb"], 3 | "rules": { 4 | // Best Practices 5 | "no-new": 0, 6 | // Possible Errors 7 | "comma-dangle": [2, "never"], 8 | // Style 9 | "indent": [2, 2, {"SwitchCase": 1}], 10 | // ECMAScript 6 11 | "constructor-super": 2, 12 | "generator-star-spacing": [2, "after"], 13 | "no-this-before-super": 2, 14 | "object-shorthand": 2, 15 | // React rules 16 | "react/jsx-quotes": [2, "double", "avoid-escape"], 17 | "react/prop-types": 0, 18 | "react/sort-comp": 0, 19 | "react/no-did-mount-set-state": 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### SublimeText ### 2 | *.sublime-workspace 3 | 4 | ### OSX ### 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | Icon 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear on external disk 14 | .Spotlight-V100 15 | .Trashes 16 | 17 | ### Windows ### 18 | # Windows image file caches 19 | Thumbs.db 20 | ehthumbs.db 21 | 22 | # Folder config file 23 | Desktop.ini 24 | 25 | # Recycle Bin used on file shares 26 | $RECYCLE.BIN/ 27 | 28 | # App specific 29 | 30 | node_modules/ 31 | .tmp 32 | dist 33 | src/data/db.json -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var exec = require('child_process').exec; 4 | 5 | var mountFolder = function (connect, dir) { 6 | return connect.static(require('path').resolve(dir)); 7 | }; 8 | 9 | var webpackDistConfig = require('./webpack.dist.config.js'), 10 | webpackDevConfig = require('./webpack.config.js'); 11 | 12 | module.exports = function (grunt) { 13 | // Let *load-grunt-tasks* require everything 14 | require('load-grunt-tasks')(grunt); 15 | 16 | // Read configuration from package.json 17 | var pkgConfig = grunt.file.readJSON('package.json'); 18 | 19 | grunt.initConfig({ 20 | pkg: pkgConfig, 21 | 22 | webpack: { 23 | options: webpackDistConfig, 24 | 25 | dist: { 26 | cache: false 27 | } 28 | }, 29 | 30 | 'webpack-dev-server': { 31 | options: { 32 | hot: true, 33 | port: 8000, 34 | webpack: webpackDevConfig, 35 | publicPath: '/assets/', 36 | contentBase: './<%= pkg.src %>/', 37 | }, 38 | 39 | start: { 40 | keepAlive: true, 41 | } 42 | }, 43 | 44 | connect: { 45 | options: { 46 | port: 8000 47 | }, 48 | 49 | dist: { 50 | options: { 51 | keepalive: true, 52 | middleware: function (connect) { 53 | return [ 54 | mountFolder(connect, pkgConfig.dist) 55 | ]; 56 | } 57 | } 58 | } 59 | }, 60 | 61 | open: { 62 | options: { 63 | delay: 500 64 | }, 65 | dev: { 66 | path: 'http://localhost:<%= connect.options.port %>/webpack-dev-server/' 67 | }, 68 | dist: { 69 | path: 'http://localhost:<%= connect.options.port %>/' 70 | } 71 | }, 72 | 73 | copy: { 74 | dist: { 75 | files: [ 76 | // includes files within path 77 | { 78 | flatten: true, 79 | expand: true, 80 | src: ['<%= pkg.src %>/*'], 81 | dest: '<%= pkg.dist %>/', 82 | filter: 'isFile' 83 | }, 84 | { 85 | flatten: true, 86 | expand: true, 87 | src: ['<%= pkg.src %>/images/*'], 88 | dest: '<%= pkg.dist %>/images/' 89 | }, 90 | ] 91 | } 92 | }, 93 | 94 | clean: { 95 | dist: { 96 | files: [{ 97 | dot: true, 98 | src: [ 99 | '<%= pkg.dist %>' 100 | ] 101 | }] 102 | } 103 | } 104 | }); 105 | 106 | grunt.registerTask('serve', { timeout: 1000, }, function (target) { 107 | exec('npm run server'); 108 | 109 | if (target === 'dist') { 110 | grunt.task.run(['build', 'open:dist', 'connect:dist']); 111 | return; 112 | } 113 | 114 | grunt.task.run([ 115 | 'open:dev', 116 | 'webpack-dev-server' 117 | ]); 118 | }); 119 | 120 | grunt.registerTask('build', ['clean', 'copy', 'webpack']); 121 | 122 | grunt.registerTask('default', []); 123 | }; 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React Admin Panel Example 2 | === 3 | 4 | img 5 | 6 | ## Development 7 | 8 | Type `npm run init` for generate fake db data. 9 | 10 | Type `grunt serve` for start developing. 11 | 12 | 13 | ## Dependencies [![Dependency Status](https://david-dm.org/semigradsky/react-admin-example.svg)](https://david-dm.org/semigradsky/react-admin-example) 14 | 15 | - [bootstrap](https://github.com/twbs/bootstrap) 16 | - [console-polyfill](https://github.com/paulmillr/console-polyfill) - Browser console methods polyfill 17 | - [fecha](https://github.com/taylorhakes/fecha) - Date formatting and parsing 18 | - [flux](https://github.com/facebook/flux) - Application Architecture for Building User Interfaces 19 | - [font-awesome](https://github.com/FortAwesome/Font-Awesome) - The iconic font and CSS toolkit 20 | - [griddle-react](https://github.com/GriddleGriddle/Griddle) - Simple Grid Component written in React 21 | - [json-server](https://github.com/typicode/json-server) - a full fake REST API with zero coding 22 | - [pikaday](https://github.com/dbushell/Pikaday) - A refreshing JavaScript Datepicker — lightweight, no dependencies, modular CSS 23 | - react 24 | - [react-helmet](https://github.com/nfl/react-helmet) - Specify the page title, meta & link tags per component in a nested fashion 25 | - [react-modal](https://github.com/rackt/react-modal) - Accessible React Modal Dialog Component. 26 | - [react-router](https://github.com/rackt/react-router) - A complete routing solution for React.js 27 | - [superagent](https://github.com/visionmedia/superagent) - Ajax with less suck 28 | - [superagent-no-cache](https://github.com/johntron/superagent-no-cache) - Adds headers to all requests that prevents caching. 29 | 30 | ## Dev Dependencies [![devDependency Status](https://david-dm.org/semigradsky/react-admin-example/dev-status.svg)](https://david-dm.org/semigradsky/react-admin-example#info=devDependencies) 31 | 32 | - [eslint](https://github.com/eslint/eslint) - A fully pluggable tool for identifying and reporting on patterns in JavaScript 33 | - [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) - React specific linting rules for ESLint 34 | - [json-schema-faker](https://github.com/pateketrueke/json-schema-faker) - generate massive amounts of fake data 35 | - grunt 36 | - [load-grunt-tasks](https://github.com/sindresorhus/load-grunt-tasks) - Load multiple grunt tasks using globbing patterns 37 | - some grunt tasks: 38 | - grunt-contrib-clean, grunt-contrib-connect, grunt-contrib-copy, grunt-open, grunt-webpack 39 | - [webpack](https://github.com/webpack/webpack) - Packs CommonJs/AMD modules for the browser 40 | - [webpack-dev-server](https://github.com/webpack/webpack-dev-server) - Serves a webpack app. Updates the browser on changes 41 | - webpack loaders: 42 | - [babel-loader](https://github.com/babel/babel-loader), [css-loader](https://github.com/webpack/css-loader), [eslint-loader](https://github.com/MoOx/eslint-loader), [file-loader](https://github.com/webpack/file-loader), [react-hot-loader](https://github.com/gaearon/react-hot-loader), [style-loader](https://github.com/webpack/style-loader), [url-loader](https://github.com/webpack/url-loader), [json-loader](https://github.com/webpack/json-loader), [postcss-loader](https://github.com/postcss/postcss-loader) 43 | - PostCSS plugins: 44 | - [cssgrace](https://github.com/cssdream/cssgrace) - Adds IE hacks, missing properties, repairs common errors, etc. 45 | - [cssnext](https://github.com/cssnext/cssnext) - Adds variables, custom selectors, custom media queries, automatic vendor prefixes, inline @import rules, etc. 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-admin-example", 3 | "version": "0.0.1", 4 | "description": "React Admin Panel Example", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/semigradsky/react-admin-example.git" 8 | }, 9 | "private": true, 10 | "src": "src", 11 | "dist": "dist", 12 | "mainInput": "main", 13 | "mainOutput": "main", 14 | "scripts": { 15 | "init": "node src/data/generate.js", 16 | "start": "grunt serve", 17 | "server": "json-server src/data/db.json", 18 | "lint": "eslint src" 19 | }, 20 | "dependencies": { 21 | "bootstrap": "^3.3.5", 22 | "console-polyfill": "^0.2.1", 23 | "eslint-config-airbnb": "0.0.7", 24 | "faker": "^3.0.0", 25 | "fecha": "^0.2.1", 26 | "flux": "^2.0.3", 27 | "font-awesome": "^4.3.0", 28 | "formsy-react": "^0.14.1", 29 | "formsy-react-components": "^0.3.0", 30 | "griddle-react": "^0.2.13", 31 | "json-server": "^0.7.20", 32 | "pikaday": "^1.3.3", 33 | "react": "^0.13.3", 34 | "react-helmet": "^1.1.0", 35 | "react-modal": "^0.3.0", 36 | "react-router": "^0.13.3", 37 | "superagent": "^1.2.0", 38 | "superagent-no-cache": "^0.1.0" 39 | }, 40 | "devDependencies": { 41 | "babel-core": "^5.6.15", 42 | "babel-eslint": "^4.0.5", 43 | "babel-loader": "^5.3.1", 44 | "css-loader": "^0.15.2", 45 | "cssgrace": "^2.0.2", 46 | "cssnext": "^1.8.0", 47 | "eslint": "^1.0.0", 48 | "eslint-loader": "^0.14.2", 49 | "eslint-plugin-react": "^3.2.0", 50 | "file-loader": "^0.8.4", 51 | "grunt": "^0.4.5", 52 | "grunt-contrib-clean": "^0.6.0", 53 | "grunt-contrib-connect": "^0.11.2", 54 | "grunt-contrib-copy": "^0.8.0", 55 | "grunt-open": "^0.2.3", 56 | "grunt-webpack": "^1.0.11", 57 | "json-loader": "^0.5.2", 58 | "json-schema-faker": "^0.2.0", 59 | "load-grunt-tasks": "^3.2.0", 60 | "node-libs-browser": "^0.5.2", 61 | "postcss": "^4.1.16", 62 | "postcss-loader": "^0.5.1", 63 | "postcss-nested": "^0.3.2", 64 | "react-hot-loader": "^1.2.8", 65 | "style-loader": "^0.12.3", 66 | "url-loader": "^0.5.6", 67 | "webpack": "^1.10.1", 68 | "webpack-dev-server": "^1.10.1" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/config.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiUrl": "http://localhost:3000", 3 | "clientNoCache": false 4 | } 5 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiUrl": "http://react-admin-example.herokuapp.com", 3 | "clientNoCache": false 4 | } 5 | -------------------------------------------------------------------------------- /src/data/generate.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var jsf = require('json-schema-faker'); 4 | var userSchema = require('./user.schema.json'); 5 | 6 | var users = []; 7 | var countUsers = 2000; 8 | 9 | while (countUsers > 0) { 10 | countUsers--; 11 | // https://github.com/pateketrueke/json-schema-faker/issues/42 12 | users.push(jsf(userSchema)); 13 | } 14 | 15 | fs.writeFile(path.resolve(__dirname, 'db.json'), JSON.stringify({ users: users }, null, 4), function() { 16 | console.log('data set generated successfully!'); 17 | }); 18 | -------------------------------------------------------------------------------- /src/data/user.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "User", 3 | "type": "object", 4 | "properties": { 5 | "id": { 6 | "type": "string", 7 | "faker": "random.uuid" 8 | }, 9 | "name": { 10 | "type": "string", 11 | "faker": "name.findName" 12 | }, 13 | "login": { 14 | "type": "string", 15 | "faker": "internet.userName" 16 | }, 17 | "email": { 18 | "type": "string", 19 | "format": "email", 20 | "faker": "internet.email" 21 | }, 22 | "city": { 23 | "type": "string", 24 | "faker": "address.city" 25 | }, 26 | "country": { 27 | "type": "string", 28 | "faker": "address.country" 29 | }, 30 | "birthday": { 31 | "type": "string", 32 | "format": "date-time", 33 | "faker": { 34 | "date.between": [1940, 2015] 35 | } 36 | } 37 | }, 38 | "required": [ 39 | "id", 40 | "login", 41 | "email" 42 | ] 43 | } -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Semigradsky/react-admin-example/1c2746982fcc07398cd2c741bf90cd0264a8ef13/src/favicon.ico -------------------------------------------------------------------------------- /src/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Semigradsky/react-admin-example/1c2746982fcc07398cd2c741bf90cd0264a8ef13/src/images/yeoman.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 |
13 |
14 | 114 |
115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/scripts/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navigation, RouteHandler } from 'react-router'; 3 | import Helmet from 'react-helmet'; 4 | 5 | import Navbar from 'components/navbar/Navbar'; 6 | import PageFooter from 'components/footer/PageFooter'; 7 | 8 | import auth from 'services/auth'; 9 | 10 | const App = React.createClass({ 11 | mixins: [Navigation], 12 | 13 | getInitialState() { 14 | return { 15 | loggedIn: auth.loggedIn() 16 | }; 17 | }, 18 | 19 | componentWillMount() { 20 | auth.onChange = this.onAuthChange; 21 | auth.login(); 22 | }, 23 | 24 | onAuthChange(loggedIn) { 25 | this.setState({ loggedIn }); 26 | !loggedIn && this.transitionTo('login'); 27 | }, 28 | 29 | render() { 30 | const authState = this.state.loggedIn; 31 | const meta = [ 32 | { 33 | 'name': 'description', 34 | 'content': 'React Admin Example application' 35 | } 36 | ]; 37 | 38 | return ( 39 |
40 | 41 | 42 |
43 | 44 |
45 | 46 |
47 | ); 48 | } 49 | }); 50 | 51 | export default App; 52 | -------------------------------------------------------------------------------- /src/scripts/components/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import auth from 'services/auth'; 4 | import Authentication from 'mixins/Authentication'; 5 | 6 | import imageURL from 'images/yeoman.png'; 7 | 8 | const Dashboard = React.createClass({ 9 | mixins: [ Authentication ], 10 | 11 | render() { 12 | const token = auth.getToken(); 13 | return ( 14 |
15 |

Dashboard

16 | 17 |

You made it!

18 |

{token}

19 |
20 | ); 21 | } 22 | }); 23 | 24 | export default Dashboard; 25 | -------------------------------------------------------------------------------- /src/scripts/components/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loading = React.createClass({ 4 | render() { 5 | const spin = ; 6 | return !this.props.progress ? (
{this.props.children}
) : spin; 7 | } 8 | }); 9 | 10 | export default Loading; 11 | -------------------------------------------------------------------------------- /src/scripts/components/auth/Login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Router from 'react-router'; 3 | import Formsy from 'formsy-react'; 4 | import FRC from 'formsy-react-components'; 5 | 6 | import auth from 'services/auth'; 7 | 8 | import './login.css'; 9 | 10 | const Login = React.createClass({ 11 | mixins: [ Router.Navigation ], 12 | 13 | statics: { 14 | attemptedTransition: null 15 | }, 16 | 17 | getInitialState() { 18 | return { 19 | error: false 20 | }; 21 | }, 22 | 23 | onSubmit(data) { 24 | auth.login(data.email, data.pass, (loggedIn) => { 25 | if (!loggedIn) { 26 | return this.setState({ error: true }); 27 | } 28 | 29 | if (Login.attemptedTransition) { 30 | const transition = Login.attemptedTransition; 31 | Login.attemptedTransition = null; 32 | transition.retry(); 33 | } else { 34 | this.transitionTo('dashboard'); 35 | } 36 | }); 37 | }, 38 | 39 | render() { 40 | const errors = this.state.error ?

Bad login information

: ''; 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | {errors} 49 | 50 | ); 51 | } 52 | }); 53 | 54 | export default Login; 55 | -------------------------------------------------------------------------------- /src/scripts/components/auth/Logout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import auth from 'services/auth'; 4 | 5 | const Logout = React.createClass({ 6 | componentDidMount() { 7 | auth.logout(); 8 | }, 9 | 10 | render() { 11 | return

You are now logged out

; 12 | } 13 | }); 14 | 15 | export default Logout; 16 | -------------------------------------------------------------------------------- /src/scripts/components/auth/login.css: -------------------------------------------------------------------------------- 1 | @import '--media.css'; 2 | 3 | .login-form { 4 | min-width: 300px; 5 | width: 30%; 6 | margin: 100px auto 0; 7 | 8 | @media (--small-viewport) { 9 | width: 100%; 10 | min-width: initial; 11 | margin-top: 50px; 12 | 13 | .btn { 14 | width: 100%; 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/scripts/components/footer/PageFooter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | import './footer.css'; 5 | 6 | const PageFooter = React.createClass({ 7 | render() { 8 | return ( 9 | 13 | ); 14 | } 15 | }); 16 | 17 | export default PageFooter; 18 | -------------------------------------------------------------------------------- /src/scripts/components/footer/footer.css: -------------------------------------------------------------------------------- 1 | @import '--variables.css'; 2 | 3 | .page-footer { 4 | position: absolute; 5 | bottom: 0; 6 | left: 0; 7 | right: 0; 8 | padding-left: var(--mainLeftPadding); 9 | background-color: var(--footerBgColor); 10 | heigth: var(--footerHeight); 11 | line-height: var(--footerHeight); 12 | font-size: var(--footerFontSize); 13 | border: 1px solid var(--mainBorderColor); 14 | 15 | .about { 16 | padding-left: var(--mainLeftPadding); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/scripts/components/grid/DateDisplay.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import fecha from 'fecha'; 3 | 4 | const DateDisplay = React.createClass({ 5 | render() { 6 | return ( 7 | 8 | {this.props.data ? fecha.format(new Date(this.props.data), 'D MMM YYYY') : ''} 9 | 10 | ); 11 | } 12 | }); 13 | 14 | export default DateDisplay; 15 | -------------------------------------------------------------------------------- /src/scripts/components/grid/EditingDisplay.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | import Modal from 'components/popups/Modal'; 5 | 6 | const EditingDisplay = React.createClass({ 7 | getInitialState() { 8 | return { modalIsOpen: false }; 9 | }, 10 | 11 | openModal(e) { 12 | e && e.preventDefault(); 13 | this.setState({ modalIsOpen: true }); 14 | }, 15 | 16 | closeModal(e) { 17 | e && e.preventDefault(); 18 | this.setState({ modalIsOpen: false }); 19 | }, 20 | 21 | render() { 22 | const editLink = this.props.editRoute ? ( 23 | 24 | ) : ''; 25 | 26 | const child = this.props.remove ? 27 | React.Children.map(this.props.children, (el) => React.cloneElement(el, { close: this.closeModal })) : ''; 28 | 29 | return child ? ( 30 |
31 | {editLink} 32 | 33 | 36 | {child} 37 | 38 |
39 | ) : ( 40 |
41 | {editLink} 42 |
43 | ); 44 | } 45 | }); 46 | 47 | export default EditingDisplay; 48 | -------------------------------------------------------------------------------- /src/scripts/components/grid/Grid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Griddle from 'griddle-react'; 3 | 4 | import './grid.css'; 5 | 6 | const Grid = React.createClass({ 7 | render() { 8 | return ( 9 | 16 | ); 17 | } 18 | }); 19 | 20 | export default Grid; 21 | -------------------------------------------------------------------------------- /src/scripts/components/grid/grid.css: -------------------------------------------------------------------------------- 1 | .grid { 2 | width: 100%; 3 | table-layout: fixed; 4 | border: 1px solid #DDD; 5 | 6 | th { 7 | border-bottom: 1px solid #DDD; 8 | color: #222; 9 | padding: 5px; 10 | cursor: pointer; 11 | background-color: #EDEDEF; 12 | } 13 | 14 | .standard-row { 15 | &:hover td { 16 | background-color: #EEE; 17 | } 18 | 19 | td { 20 | margin: 0px; 21 | padding: 5px 5px 5px 5px; 22 | height: 15px; 23 | background-color: #FFF; 24 | border-top-color: #DDD; 25 | color: #222; 26 | } 27 | } 28 | 29 | .footer-container { 30 | padding: 0px; 31 | border: 0px; 32 | color: #222; 33 | background-color: #EDEDED; 34 | 35 | .griddle-footer { 36 | min-height: 35px; 37 | } 38 | } 39 | 40 | .next-data, 41 | .prev-data { 42 | width: 33%; 43 | float: left; 44 | min-height: 1px; 45 | margin-top: 5px; 46 | 47 | button { 48 | color: #222; 49 | border: none; 50 | background: none; 51 | } 52 | } 53 | 54 | .prev-data { 55 | button { 56 | margin-right: 10px; 57 | } 58 | } 59 | 60 | .next-data { 61 | text-align: right; 62 | 63 | button { 64 | margin-left: 10px; 65 | } 66 | } 67 | 68 | .griddle-page { 69 | text-align: center; 70 | width: 34%; 71 | float: left; 72 | min-height: 1px; 73 | margin-top: 5px; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/scripts/components/inputs/Datepicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import fecha from 'fecha'; 3 | import Pikaday from 'pikaday'; 4 | import Formsy from 'formsy-react'; 5 | import FRC from 'formsy-react-components'; 6 | 7 | import 'pikaday/css/pikaday.css'; 8 | import './datepicker.css'; 9 | 10 | const Datepicker = React.createClass({ 11 | mixins: [Formsy.Mixin], 12 | 13 | componentWillMount() { 14 | const value = this.getValue(); 15 | this.setState({ shownValue: value ? this.format(new Date(value)) : '' }); 16 | }, 17 | 18 | componentDidMount() { 19 | const value = this.getValue(); 20 | new Pikaday({ 21 | field: React.findDOMNode(this.refs.input), 22 | defaultDate: value ? new Date(value) : new Date(), 23 | setDefaultDate: true, 24 | onSelect: (date) => { 25 | const resettedDate = new Date(date - date.getTimezoneOffset() * 60 * 1000); 26 | this.setValue(resettedDate.toISOString()); 27 | this.setState({ shownValue: this.format(resettedDate) }); 28 | } 29 | }); 30 | }, 31 | 32 | format(date) { 33 | return fecha.format(date, 'D MMM YYYY'); 34 | }, 35 | 36 | render() { 37 | return ( 38 | 39 | 46 | 53 | 54 | ); 55 | } 56 | 57 | }); 58 | 59 | export default Datepicker; 60 | -------------------------------------------------------------------------------- /src/scripts/components/inputs/datepicker.css: -------------------------------------------------------------------------------- 1 | .datepicker.form-control[readonly] { 2 | background-color: #FFF; 3 | } 4 | -------------------------------------------------------------------------------- /src/scripts/components/navbar/Navbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | import Sidebar from 'components/navbar/Sidebar'; 5 | 6 | import './navbar.css'; 7 | 8 | const Navbar = React.createClass({ 9 | render() { 10 | const header = this.props.auth ? 11 |
12 | Admin Panel 13 |
: ''; 14 | 15 | const links = this.props.auth ? ( 16 | 17 | Log out 18 | 19 | ) : ( 20 | 21 | Log in 22 | 23 | ); 24 | 25 | return ( 26 | 33 | ); 34 | } 35 | }); 36 | 37 | export default Navbar; 38 | -------------------------------------------------------------------------------- /src/scripts/components/navbar/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Sidebar = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |
    9 |
  • 10 | Dashboard 11 |
  • 12 |
  • 13 | Users 14 |
  • 15 |
16 |
17 | ); 18 | } 19 | }); 20 | 21 | export default Sidebar; 22 | -------------------------------------------------------------------------------- /src/scripts/components/navbar/navbar.css: -------------------------------------------------------------------------------- 1 | @import '--variables.css'; 2 | 3 | .nav { 4 | padding-left: 0; 5 | margin-bottom: 0; 6 | list-style: none; 7 | 8 | > li { 9 | position: relative; 10 | display: block; 11 | 12 | > a { 13 | position: relative; 14 | display: block; 15 | padding: 10px 15px; 16 | 17 | &:focus, 18 | &:hover, 19 | &.active { 20 | text-decoration: none; 21 | background-color: var(--navbarActiveItemBgColor); 22 | } 23 | 24 | &.active { 25 | font-weight: bold; 26 | } 27 | } 28 | } 29 | } 30 | 31 | .navbar { 32 | border: 1px solid var(--mainBorderColor); 33 | border-radius: 0; 34 | z-index: 1000; 35 | position: relative; 36 | height: var(--navbarHeight); 37 | background-color: var(--navbarBgColor); 38 | } 39 | 40 | .navbar-header { 41 | float: left; 42 | height: var(--navbarHeight); 43 | padding: var(--mainLeftPadding); 44 | font-size: 1.3em; 45 | color: #777; 46 | } 47 | 48 | .navbar-links { 49 | float: right; 50 | padding: var(--mainLeftPadding); 51 | 52 | .link-text { 53 | margin-left: 4px; 54 | } 55 | } 56 | 57 | .sidebar { 58 | z-index: 1; 59 | position: absolute; 60 | width: var(--sidebarWidth); 61 | margin-top: var(--navbarHeight); 62 | } 63 | 64 | .side-menu .side-menu-item { 65 | border-bottom: 1px solid var(--mainBorderColor); 66 | } 67 | -------------------------------------------------------------------------------- /src/scripts/components/popups/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Modal from 'react-modal'; 3 | 4 | import './modal.css'; 5 | 6 | const appElement = document.getElementsByTagName('body')[0]; 7 | Modal.setAppElement(appElement); 8 | 9 | const CustomModal = React.createClass({ 10 | render() { 11 | return ; 12 | } 13 | }); 14 | 15 | export default CustomModal; 16 | -------------------------------------------------------------------------------- /src/scripts/components/popups/modal.css: -------------------------------------------------------------------------------- 1 | @import '--selectors.css'; 2 | 3 | :--modal { 4 | background-color: rgba(255, 255, 255, 0.75); 5 | z-index: 1000; 6 | } 7 | 8 | :--modal-content { 9 | max-height: 90%; 10 | margin: 30px auto 0; 11 | border: 1px solid #CCC; 12 | background: #FFF; 13 | overflow: auto; 14 | -webkit-overflow-scrolling: touch; 15 | border-radius: 4px; 16 | outline: none; 17 | padding: 20px; 18 | } 19 | -------------------------------------------------------------------------------- /src/scripts/components/static/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const About = React.createClass({ 4 | render() { 5 | return

About

; 6 | } 7 | }); 8 | 9 | export default About; 10 | -------------------------------------------------------------------------------- /src/scripts/components/static/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const NotFound = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

Ooops... Page not found!

9 |

10 | Go back to dashboard 11 |  or onward to source 12 |

13 |
14 | ); 15 | } 16 | }); 17 | 18 | export default NotFound; 19 | -------------------------------------------------------------------------------- /src/scripts/components/users/Actions.js: -------------------------------------------------------------------------------- 1 | import Dispatcher from 'utils/Dispatcher'; 2 | import UserConstants from 'components/users/Constants'; 3 | 4 | const UserActions = { 5 | create(data, callback) { 6 | Dispatcher.dispatch({ 7 | actionType: UserConstants.CREATE, 8 | data, 9 | callback 10 | }); 11 | }, 12 | 13 | update(id, data, callback) { 14 | Dispatcher.dispatch({ 15 | actionType: UserConstants.UPDATE, 16 | id, 17 | data, 18 | callback 19 | }); 20 | }, 21 | 22 | remove(id, callback) { 23 | Dispatcher.dispatch({ 24 | actionType: UserConstants.REMOVE, 25 | id, 26 | callback 27 | }); 28 | } 29 | 30 | }; 31 | 32 | export default UserActions; 33 | -------------------------------------------------------------------------------- /src/scripts/components/users/Constants.js: -------------------------------------------------------------------------------- 1 | import keyMirror from 'react/lib/keyMirror'; 2 | 3 | const UserConstants = keyMirror({ 4 | CREATE: null, 5 | REMOVE: null, 6 | UPDATE: null 7 | }); 8 | 9 | export default UserConstants; 10 | -------------------------------------------------------------------------------- /src/scripts/components/users/Edit.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navigation } from 'react-router'; 3 | import Formsy from 'formsy-react'; 4 | import FRC from 'formsy-react-components'; 5 | 6 | import UserActions from 'components/users/Actions'; 7 | import UserStore from 'components/users/Store'; 8 | import Authentication from 'mixins/Authentication'; 9 | import Loading from 'components/Loading'; 10 | import Datepicker from 'components/inputs/Datepicker'; 11 | 12 | import './users.css'; 13 | 14 | const UserEdit = React.createClass({ 15 | mixins: [ Authentication, Navigation ], 16 | 17 | getInitialState() { 18 | return { 19 | dataLoaded: false, 20 | dataSaved: true, 21 | user: {} 22 | }; 23 | }, 24 | 25 | componentDidMount() { 26 | const userId = this.props.params.userId; 27 | if (!userId) { 28 | this.setState({ dataLoaded: true, user: {} }); 29 | return; 30 | } 31 | 32 | UserStore.get(userId, (data) => { 33 | if (this.isMounted()) { 34 | this.setState({ dataLoaded: true, user: data }); 35 | } 36 | }); 37 | }, 38 | 39 | onSubmit(data) { 40 | const id = this.props.params.userId; 41 | this.setState({ dataSaved: false }); 42 | 43 | if (id) { 44 | UserActions.update(id, data, this.onSave); 45 | } else { 46 | UserActions.create(data, this.onSave); 47 | } 48 | this.transitionTo('users'); 49 | }, 50 | 51 | onSave(err, data) { 52 | if (err) { 53 | console.log(err); 54 | return; 55 | } 56 | this.setState({ dataSaved: true, user: data }); 57 | }, 58 | 59 | resetForm() { 60 | 61 | }, 62 | 63 | render() { 64 | const userId = this.props.params.userId; 65 | const user = this.state.user; 66 | 67 | return ( 68 |
69 |

{userId ? 'Edit' : 'Create'} User

70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
88 | ); 89 | } 90 | 91 | }); 92 | 93 | export default UserEdit; 94 | -------------------------------------------------------------------------------- /src/scripts/components/users/EditingDisplay.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import EditingDisplay from 'components/grid/EditingDisplay'; 4 | import RemoveModal from 'components/users/RemoveModal'; 5 | 6 | const UserEditingDisplay = React.createClass({ 7 | render() { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | }); 15 | 16 | export default UserEditingDisplay; 17 | -------------------------------------------------------------------------------- /src/scripts/components/users/List.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | import UserStore from 'components/users/Store'; 5 | import Authentication from 'mixins/Authentication'; 6 | import Grid from 'components/grid/Grid'; 7 | import DateDisplay from 'components/grid/DateDisplay'; 8 | import UserEditingDisplay from 'components/users/EditingDisplay'; 9 | import Loading from 'components/Loading'; 10 | 11 | import './users.css'; 12 | 13 | const metadata = [ 14 | { columnName: 'name', displayName: 'Name' }, 15 | { columnName: 'login', displayName: 'Login' }, 16 | { columnName: 'email', displayName: 'E-mail' }, 17 | { columnName: 'birthday', displayName: 'Birthday', customComponent: DateDisplay, cssClassName: 'birthday' }, 18 | { columnName: 'options', displayName: '', customComponent: UserEditingDisplay, cssClassName: 'options' } 19 | ]; 20 | 21 | const UserList = React.createClass({ 22 | mixins: [ Authentication ], 23 | 24 | update() { 25 | UserStore.getAll((data) => { 26 | this.setState({ 27 | dataLoaded: true, 28 | users: data 29 | }); 30 | }); 31 | }, 32 | 33 | getInitialState() { 34 | return { 35 | dataLoaded: false, 36 | users: [] 37 | }; 38 | }, 39 | 40 | componentDidMount() { 41 | this.update(); 42 | UserStore.addChangeListener(this.update); 43 | }, 44 | 45 | componentWillUnmount() { 46 | UserStore.removeChangeListener(this.update); 47 | }, 48 | 49 | render() { 50 | return ( 51 |
52 |

Users 53 | 54 |

55 | 56 | 65 | 66 |
67 | ); 68 | } 69 | 70 | }); 71 | 72 | export default UserList; 73 | -------------------------------------------------------------------------------- /src/scripts/components/users/RemoveModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FRC from 'formsy-react-components'; 3 | 4 | import UserActions from 'components/users/Actions'; 5 | 6 | const UserRemoveModal = React.createClass({ 7 | onSubmit(e) { 8 | e && e.preventDefault(); 9 | UserActions.remove(this.props.id, () => { 10 | this.props.close(); 11 | }); 12 | }, 13 | 14 | render() { 15 | return ( 16 |
17 |

Remove {this.props.login}

18 |

Are you sure?

19 | 20 | 21 | 22 | 23 | 24 |
25 | ); 26 | } 27 | }); 28 | 29 | export default UserRemoveModal; 30 | -------------------------------------------------------------------------------- /src/scripts/components/users/Store.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import faker from 'faker'; 3 | import assign from 'react/lib/Object.assign'; 4 | 5 | import request from 'utils/request'; 6 | import Dispatcher from 'utils/Dispatcher'; 7 | import UserConstants from 'components/users/Constants'; 8 | 9 | const CHANGE_EVENT = 'change'; 10 | 11 | const create = (data, fn) => { 12 | data.id = faker.random.uuid(); 13 | request('post', 'users/', data, (err, res) => fn(err, res.body)); 14 | }; 15 | 16 | const update = (id, data, fn) => { 17 | request('put', 'users/' + id, data, (err, res) => fn(err, res.body)); 18 | }; 19 | 20 | const remove = (id, fn) => { 21 | request('delete', 'users/' + id, (err, res) => fn(err, res.body)); 22 | }; 23 | 24 | const UserStore = assign({}, EventEmitter.prototype, { 25 | getAll(fn) { 26 | request('get', 'users', (err, res) => fn(err ? [] : res.body)); 27 | }, 28 | 29 | get(id, fn) { 30 | request('get', 'users/' + id, (err, res) => fn(err ? [] : res.body)); 31 | }, 32 | 33 | emitChange() { 34 | this.emit(CHANGE_EVENT); 35 | }, 36 | 37 | addChangeListener(callback) { 38 | this.on(CHANGE_EVENT, callback); 39 | }, 40 | 41 | removeChangeListener(callback) { 42 | this.removeListener(CHANGE_EVENT, callback); 43 | } 44 | }); 45 | 46 | Dispatcher.register((action) => { 47 | switch (action.actionType) { 48 | case UserConstants.CREATE: 49 | create(action.data, (err, data) => { 50 | action.callback && action.callback(err, data); 51 | !err && UserStore.emitChange(); 52 | }); 53 | break; 54 | 55 | case UserConstants.UPDATE: 56 | update(action.id, action.data, (err, data) => { 57 | action.callback && action.callback(err, data); 58 | !err && UserStore.emitChange(); 59 | }); 60 | break; 61 | 62 | case UserConstants.REMOVE: 63 | remove(action.id, (err, data) => { 64 | action.callback && action.callback(err, data); 65 | !err && UserStore.emitChange(); 66 | }); 67 | break; 68 | 69 | default: 70 | // do nothing 71 | } 72 | }); 73 | 74 | export default UserStore; 75 | -------------------------------------------------------------------------------- /src/scripts/components/users/users.css: -------------------------------------------------------------------------------- 1 | @import '--selectors.css'; 2 | 3 | .users-list { 4 | max-width: 900px; 5 | 6 | th.birthday { 7 | width: 100px; 8 | } 9 | 10 | td.birthday { 11 | text-align: right; 12 | } 13 | 14 | th.options { 15 | width: 60px; 16 | } 17 | 18 | td.options { 19 | text-align: center; 20 | 21 | .link { 22 | opacity: 0; 23 | 24 | } 25 | 26 | .remove { 27 | color: #9B3D3D; 28 | padding-left: 5px; 29 | } 30 | } 31 | 32 | tr:hover .link { 33 | opacity: 1; 34 | transition: opacity 1s; 35 | }; 36 | } 37 | 38 | .create-user-link { 39 | padding-left: 10px; 40 | vertical-align: middle; 41 | font-size: 0.7em; 42 | } 43 | 44 | :--modal-content.users { 45 | width: 400px; 46 | } 47 | -------------------------------------------------------------------------------- /src/scripts/main.js: -------------------------------------------------------------------------------- 1 | import 'console-polyfill'; 2 | 3 | import 'bootstrap/dist/css/bootstrap.css'; 4 | import 'styles/main.css'; 5 | import 'font-awesome/css/font-awesome.css'; 6 | 7 | import React from 'react'; 8 | import Router from 'react-router'; 9 | const { Route, DefaultRoute, NotFoundRoute } = Router; 10 | 11 | import App from 'components/App'; 12 | import Logout from 'components/auth/Logout'; 13 | import Login from 'components/auth/Login'; 14 | import About from 'components/static/About'; 15 | import Dashboard from 'components/Dashboard'; 16 | import Users from 'components/users/List'; 17 | import UserEdit from 'components/users/Edit'; 18 | import NotFound from 'components/static/NotFound'; 19 | 20 | const content = document.getElementsByTagName('body')[0]; 21 | 22 | const Routes = ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | 34 | Router.run(Routes, Router.HashLocation, (Handler) => { 35 | React.render(, content); 36 | }); 37 | -------------------------------------------------------------------------------- /src/scripts/mixins/Authentication.js: -------------------------------------------------------------------------------- 1 | import auth from 'services/auth'; 2 | import Login from 'components/auth/Login'; 3 | 4 | const Authentication = { 5 | statics: { 6 | willTransitionTo(transition) { 7 | if (!auth.loggedIn()) { 8 | Login.attemptedTransition = transition; 9 | transition.redirect('/login'); 10 | } 11 | } 12 | } 13 | }; 14 | 15 | export default Authentication; 16 | -------------------------------------------------------------------------------- /src/scripts/services/auth.js: -------------------------------------------------------------------------------- 1 | // Fake authentication lib 2 | 3 | const pretendRequest = (email, pass, cb) => { 4 | setTimeout(() => { 5 | if (email === 'joe@example.com' && pass === 'password1') { 6 | cb({ 7 | authenticated: true, 8 | token: Math.random().toString(36).substring(7) 9 | }); 10 | } else { 11 | cb({ authenticated: false }); 12 | } 13 | }, 0); 14 | }; 15 | 16 | const auth = { 17 | login(email, pass, cb) { 18 | if (localStorage.token) { 19 | cb && cb(true); 20 | this.onChange(true); 21 | return; 22 | } 23 | 24 | pretendRequest(email, pass, (res) => { 25 | if (res.authenticated) { 26 | localStorage.token = res.token; 27 | cb && cb(true); 28 | this.onChange(true); 29 | } else { 30 | cb && cb(false); 31 | this.onChange(false); 32 | } 33 | }); 34 | }, 35 | 36 | getToken() { 37 | return localStorage.token; 38 | }, 39 | 40 | logout(cb) { 41 | delete localStorage.token; 42 | cb && cb(); 43 | this.onChange(false); 44 | }, 45 | 46 | loggedIn() { 47 | return !!localStorage.token; 48 | }, 49 | 50 | onChange() {} 51 | }; 52 | 53 | export default auth; 54 | -------------------------------------------------------------------------------- /src/scripts/utils/Dispatcher.js: -------------------------------------------------------------------------------- 1 | import { Dispatcher } from 'flux'; 2 | 3 | export default new Dispatcher(); 4 | -------------------------------------------------------------------------------- /src/scripts/utils/request.js: -------------------------------------------------------------------------------- 1 | import superagent from 'superagent'; 2 | import noCache from 'superagent-no-cache'; 3 | import config from 'config.json'; 4 | 5 | function getRequester(type) { 6 | return type === 'delete' ? superagent.del : superagent[type]; 7 | } 8 | 9 | function request(type, url, data, fn) { 10 | const requester = getRequester(type); 11 | 12 | let agent = requester(config.apiUrl + '/' + url); 13 | 14 | if (config.clientNoCache) { 15 | agent = agent.use(noCache); 16 | } 17 | 18 | if (typeof data === 'object') { 19 | agent = agent.send(data); 20 | } 21 | 22 | agent.end(fn || data); 23 | } 24 | 25 | export default request; 26 | -------------------------------------------------------------------------------- /src/styles/--media.css: -------------------------------------------------------------------------------- 1 | @custom-media --small-viewport (width <= 767px); 2 | @custom-media --medium-viewport (width >= 768px); 3 | @custom-media --large-viewport (width >= 1200px); 4 | -------------------------------------------------------------------------------- /src/styles/--selectors.css: -------------------------------------------------------------------------------- 1 | @custom-selector :--modal .ReactModal__Overlay; 2 | @custom-selector :--modal-content .ReactModal__Content; 3 | -------------------------------------------------------------------------------- /src/styles/--variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Links */ 3 | --linkColor: #337AB7; 4 | --linkHoverColor: #23527C; 5 | 6 | /* Main */ 7 | --mainBorderColor: #E7E7E7; 8 | --mainTextColor: #333; 9 | --mainFontFamily: "Helvetica Neue",Helvetica,Arial,sans-serif; 10 | --mainFontSize: 14px; 11 | --mainLeftPadding: 15px; 12 | 13 | /* Navbar */ 14 | --navbarHeight: 50px; 15 | --navbarBgColor: #F8F8F8; 16 | --navbarActiveItemBgColor: #EEE; 17 | --sidebarWidth: 250px; 18 | 19 | /* Footer */ 20 | --footerBgColor: var(--navbarBgColor); 21 | --footerHeight: 30px; 22 | --footerFontSize: 0.857em; 23 | } 24 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | @import '--variables.css'; 2 | 3 | html, body { 4 | height: 100%; 5 | } 6 | 7 | body { 8 | font-family: var(--mainFontFamily); 9 | font-size: var(--mainFontSize); 10 | line-height: 1.42857143; 11 | color: var(--mainTextColor); 12 | } 13 | 14 | a { 15 | color: var(--linkColor); 16 | text-decoration: none; 17 | 18 | &:focus, &:hover { 19 | color: var(--linkHoverColor); 20 | text-decoration: none; 21 | } 22 | 23 | &:active, &:hover { 24 | outline: 0; 25 | } 26 | } 27 | 28 | #content { 29 | width: 100%; 30 | } 31 | 32 | #page-content { 33 | position: absolute; 34 | top: 0; 35 | bottom: 0; 36 | left: 0; 37 | right: 0; 38 | height: 100%; 39 | margin: 0; 40 | padding: var(--navbarHeight) 30px var(--footerHeight); 41 | background-color: #FFF; 42 | overflow: auto; 43 | 44 | &.auth { 45 | margin-left: var(--sidebarWidth); 46 | border-left: 1px solid var(--mainBorderColor); 47 | } 48 | } 49 | 50 | h1 { 51 | font-size: 2em; 52 | } 53 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Webpack development server configuration 3 | * 4 | * This file is set up for serving the webpack-dev-server, which will watch for changes and recompile as required if 5 | * the subfolder /webpack-dev-server/ is visited. Visiting the root will not automatically reload. 6 | */ 7 | 'use strict'; 8 | 9 | var webpack = require('webpack'); 10 | var cssnext = require('cssnext'); 11 | var cssgrace = require('cssgrace'); 12 | var nested = require('postcss-nested'); 13 | var patch = require('path'); 14 | 15 | module.exports = { 16 | 17 | output: { 18 | filename: 'main.js', 19 | publicPath: '/assets/' 20 | }, 21 | 22 | cache: true, 23 | debug: true, 24 | devtool: 'source-map', 25 | entry: [ 26 | 'webpack/hot/only-dev-server', 27 | './src/scripts/main.js' 28 | ], 29 | 30 | stats: { 31 | colors: true, 32 | reasons: true 33 | }, 34 | 35 | resolve: { 36 | root: patch.join(__dirname, 'src'), 37 | extensions: ['', '.js'], 38 | alias: { 39 | 'components': 'scripts/components/', 40 | 'mixins': 'scripts/mixins', 41 | 'services': 'scripts/services', 42 | 'utils': 'scripts/utils', 43 | 'ie': 'component-ie', 44 | 'config.json': 'config.dev.json' 45 | } 46 | }, 47 | 48 | module: { 49 | preLoaders: [ 50 | {test: /\.js$/, loader: 'eslint', exclude: /node_modules/} 51 | ], 52 | loaders: [ 53 | { test: /\.js$/, loaders: ['react-hot', 'babel'], exclude: /node_modules/ }, 54 | { test: /\.json$/, loader: 'json' }, 55 | { test: /\.css$/, loader: 'style!css!postcss' }, 56 | { test: /\.(png|jpg)$/, loader: 'url?limit=8192' }, 57 | { test: /\.(ttf|eot|svg)/, loader: 'file' }, 58 | { test: /\.woff(2)?/, loader: 'url?limit=10000&minetype=application/font-woff' } 59 | ] 60 | }, 61 | 62 | postcss: [ 63 | nested, 64 | cssnext({ 65 | browsers: ['last 1 version', '> 2%'], 66 | import: { 67 | root: 'styles' 68 | } 69 | }) 70 | // cssgrace // Wait fix https://github.com/cssdream/cssgrace/issues/29 71 | ], 72 | 73 | plugins: [ 74 | new webpack.HotModuleReplacementPlugin(), 75 | new webpack.NoErrorsPlugin() 76 | ] 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /webpack.dist.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Webpack distribution configuration 3 | * 4 | * This file is set up for serving the distribution version. It will be compiled to dist/ by default 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var webpack = require('webpack'); 10 | var cssnext = require('cssnext'); 11 | var cssgrace = require('cssgrace'); 12 | var nested = require('postcss-nested'); 13 | var patch = require('path'); 14 | 15 | module.exports = { 16 | 17 | output: { 18 | publicPath: '/react-admin-example/assets/', 19 | path: 'dist/assets/', 20 | filename: 'main.js' 21 | }, 22 | 23 | debug: false, 24 | devtool: 'source-map', 25 | entry: './src/scripts/main.js', 26 | 27 | stats: { 28 | colors: true, 29 | reasons: false 30 | }, 31 | 32 | plugins: [ 33 | new webpack.optimize.DedupePlugin(), 34 | new webpack.optimize.UglifyJsPlugin(), 35 | new webpack.optimize.OccurenceOrderPlugin(), 36 | new webpack.optimize.AggressiveMergingPlugin() 37 | ], 38 | 39 | resolve: { 40 | root: patch.join(__dirname, 'src'), 41 | extensions: ['', '.js'], 42 | alias: { 43 | 'components': 'scripts/components/', 44 | 'mixins': 'scripts/mixins', 45 | 'services': 'scripts/services', 46 | 'utils': 'scripts/utils', 47 | 'config.json': 'config.json' 48 | } 49 | }, 50 | 51 | module: { 52 | loaders: [ 53 | { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, 54 | { test: /\.json$/, loader: 'json-loader' }, 55 | { test: /\.css$/, loader: 'style-loader!css-loader!postcss-loader' }, 56 | { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' }, 57 | { test: /\.(ttf|eot|svg)/, loader: 'file-loader' }, 58 | { test: /\.woff(2)?/, loader: 'url-loader?limit=10000&minetype=application/font-woff' } 59 | ] 60 | }, 61 | 62 | postcss: [ 63 | nested, 64 | cssnext({ 65 | browsers: ['last 1 version', '> 2%'], 66 | import: { 67 | root: 'styles' 68 | } 69 | }) 70 | // cssgrace 71 | ] 72 | 73 | }; 74 | --------------------------------------------------------------------------------