├── .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 |
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 [](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 [](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 |
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 |
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 |
--------------------------------------------------------------------------------