├── .eslintrc
├── .gitignore
├── README.md
├── package.json
├── screenshots
└── run_through.gif
├── src
├── actions
│ └── authentication.js
├── assets
│ ├── favicon.ico
│ └── images
│ │ └── cc.png
├── components
│ ├── Pages
│ │ └── NotFound404.jsx
│ ├── auth
│ │ ├── LoginForm.jsx
│ │ └── LoginModal.jsx
│ ├── nav
│ │ ├── TopNav.jsx
│ │ └── UserMenu.jsx
│ └── shared
│ │ ├── Heading.jsx
│ │ └── Icon.jsx
├── config
│ └── routes.js
├── constants
│ └── authentication.js
├── containers
│ ├── Main.jsx
│ ├── Page1.jsx
│ └── Page2.jsx
├── index.html
├── index.jsx
├── reducers
│ ├── authentication.js
│ └── index.js
├── store
│ └── index.js
├── styles
│ ├── Auth.scss
│ ├── Main.scss
│ └── colors.scss
└── utils
│ ├── fakeApi.js
│ ├── storage.js
│ └── sum.js
└── webpack.config.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | "jsx": true
8 | }
9 | },
10 | "rules": {
11 | "semi": 1
12 | }
13 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | dist
4 | .idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-webpack-scss-quickstart
2 | A minimal web application with [React](https://facebook.github.io/react/), [Router](https://github.com/reactjs/react-router), [Async Redux](http://redux.js.org/docs/advanced/AsyncActions.html), and [Webpack](https://webpack.github.io/). ES6/ES2015 support using [Babel](https://babeljs.io/). SASS/SCSS styling with [PostCss](https://github.com/postcss/postcss) and [Bootstrap](http://getbootstrap.com/).
3 |
4 | ## Demo
5 | [Click here](http://react-webpack-scss-quickstart.s3-website-us-west-2.amazonaws.com/)
6 |
7 | Log in with any username/password combination. The app will remember you between sessions.
8 |
9 | 
10 |
Logging in simulates an async authorization request. Use a library such as [Fetch](https://github.com/matthew-andrews/isomorphic-fetch) to implement your own authorization endpoint [here](https://github.com/jogleasonjr/react-webpack-scss-quickstart/blob/master/src/actions/authentication.js#L39).
11 |
12 | ## To Build and Run via CLI
13 |
14 | Dependencies:
15 |
16 | * [Python 2.7](https://www.python.org/downloads/). I haven't tested Python 3.
17 | * [Visual Studio 2015](https://www.visualstudio.com/downloads/). I used the Enterprise Edition, the Community Edition (free) will probably work.
18 |
19 | ```bash
20 | # Clone this repository
21 | git clone https://github.com/jogleasonjr/react-webpack-scss-quickstart
22 | # Go into the repository
23 | cd react-webpack-scss-quickstart
24 | # Install dependencies and run the app with Hot Reloading
25 | npm install && npm run start
26 | ```
27 | Now navigate to [http://localhost:8182/webpack-dev-server/](http://localhost:8182/webpack-dev-server/) in your browser.
28 |
29 | ## Next Steps
30 | * Learn about managing Redux state [here](https://github.com/reactjs/redux)
31 | * Learn more about React and JSX components [here](https://facebook.github.io/react/docs/getting-started.html).
32 |
33 | ## License
34 |
35 | [MIT](https://tldrlegal.com/license/mit-license)
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-webpack-scss-quickstart",
3 | "version": "0.0.1",
4 | "description": "A minimal, cross-platform web application with React and Webpack. ES6/ES2015 support using Babel. SCSS support using PostCss.",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/jogleasonjr/react-webpack-scss-quickstart"
9 | },
10 | "scripts": {
11 | "start": "./node_modules/.bin/webpack-dev-server --port 8182",
12 | "build": "./node_modules/.bin/webpack -p",
13 | "deploy": "webpack && cp dist/* \\\\20.20.20.31\\c$\\tfx",
14 | "test": "jest"
15 | },
16 | "author": "",
17 | "license": "MIT",
18 | "devDependencies": {
19 | "autoprefixer": "^6.3.3",
20 | "babel-core": "^6.5.2",
21 | "babel-eslint": "^6.0.0-beta.6",
22 | "babel-loader": "^6.2.2",
23 | "babel-plugin-transform-object-rest-spread": "^6.6.5",
24 | "babel-polyfill": "^6.7.4",
25 | "babel-preset-es2015": "^6.5.0",
26 | "babel-preset-react": "^6.5.0",
27 | "css-loader": "^0.23.1",
28 | "eslint": "^2.4.0",
29 | "eslint-loader": "^1.2.1",
30 | "file-loader": "^0.8.5",
31 | "font-awesome": "^4.5.0",
32 | "html-webpack-plugin": "2.10.0",
33 | "node-sass": "3.4.2",
34 | "postcss-import": "^8.0.2",
35 | "postcss-loader": "0.8.2",
36 | "postcss-url": "^5.1.1",
37 | "precss": "^1.4.0",
38 | "redux-devtools": "^3.1.1",
39 | "sass-loader": "^3.2.0",
40 | "style-loader": "^0.13.0",
41 | "webpack": "^1.12.13",
42 | "webpack-dev-server": "^1.14.1"
43 | },
44 | "dependencies": {
45 | "bootstrap": "^3.3.6",
46 | "es6-promise": "^3.1.2",
47 | "isomorphic-fetch": "^2.2.1",
48 | "react": "^15.0.1",
49 | "react-bootstrap": "^0.29.2",
50 | "react-dom": "^15.0.1",
51 | "react-redux": "^4.4.1",
52 | "react-router": "^2.0.1",
53 | "react-router-bootstrap": "^0.20.1",
54 | "redux": "^3.3.1",
55 | "redux-form": "^4.2.2",
56 | "redux-thunk": "^2.0.1",
57 | "url-loader": "^0.5.7"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/screenshots/run_through.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jogleasonjr/react-webpack-scss-quickstart/4883137c71a55813c297423fe510cf27dc8a14c7/screenshots/run_through.gif
--------------------------------------------------------------------------------
/src/actions/authentication.js:
--------------------------------------------------------------------------------
1 | // for actions, see: http://redux.js.org/docs/basics/Actions.html
2 | // for some conventions, see: https://github.com/acdlite/flux-standard-action
3 |
4 | import AuthConstants from '../constants/authentication';
5 | import {Promise} from 'es6-promise';
6 | import Storage from '../utils/storage';
7 | import api from '../utils/fakeApi.js';
8 |
9 | const PROFILE_STORAGE_KEY = 'PROFILE_STORAGE_KEY';
10 |
11 | export const loginPrompt = () => {
12 | var profile = Storage.getJSON(PROFILE_STORAGE_KEY);
13 | if (profile) {
14 | return loginSuccess(profile);
15 | }
16 | else {
17 | return {
18 | type: AuthConstants.LOG_IN_PROMPT,
19 | payload: {}
20 | };
21 | }
22 | };
23 |
24 | export const loginCancel = () => ({
25 | type: AuthConstants.LOG_IN_CANCEL,
26 | payload: {}
27 | });
28 |
29 | export const loginRequest = () => ({
30 | type: AuthConstants.LOG_IN_REQUEST
31 | });
32 |
33 | export const loginSuccess = (user) => ({
34 | type: AuthConstants.LOG_IN_SUCCESS,
35 | payload: {
36 | user
37 | }
38 | });
39 |
40 | export const loginError = (status) => ({
41 | type: AuthConstants.LOG_IN_ERROR,
42 | payload: new Error(status),
43 | error: true
44 | });
45 |
46 | export const logout = () => {
47 | localStorage.removeItem(PROFILE_STORAGE_KEY);
48 |
49 | return {
50 | type: AuthConstants.LOG_OUT
51 | };
52 | };
53 |
54 | export const login = (formData) => {
55 |
56 | const {username, password} = formData;
57 | return (dispatch) => {
58 | dispatch(loginRequest());
59 |
60 | api.getToken(username, password)
61 | .then(authResponse => {
62 | var token = authResponse.access_token;
63 | return api.getUserProfile(token);
64 | })
65 | .then(profile => {
66 | Storage.setJSON(PROFILE_STORAGE_KEY, profile);
67 | dispatch(loginSuccess(profile));
68 | })
69 | .catch(errorStatusCode => {
70 | switch (errorStatusCode) {
71 | case 400:
72 | dispatch(loginError("Username or password is incorrect."));
73 | break;
74 | default:
75 | dispatch(loginError("Unknown error: " + errorStatusCode));
76 | break;
77 | }
78 | });
79 | };
80 | };
81 |
82 |
--------------------------------------------------------------------------------
/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jogleasonjr/react-webpack-scss-quickstart/4883137c71a55813c297423fe510cf27dc8a14c7/src/assets/favicon.ico
--------------------------------------------------------------------------------
/src/assets/images/cc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jogleasonjr/react-webpack-scss-quickstart/4883137c71a55813c297423fe510cf27dc8a14c7/src/assets/images/cc.png
--------------------------------------------------------------------------------
/src/components/Pages/NotFound404.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Heading from './../shared/Heading';
3 | //import '../../styles/main.scss';
4 |
5 | export default class Main extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | return (
);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/components/auth/LoginForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {reduxForm} from 'redux-form';
3 | import {Input} from 'react-bootstrap';
4 |
5 | const LoginForm = (props) => {
6 |
7 | const {fields: {username, password}, onSubmit, isLoggingIn} = props;
8 |
9 | return (
10 |
19 | );
20 | };
21 |
22 | const validate = (values) => {
23 | const errors = {};
24 |
25 | if (!values.username) {
26 | errors.username = 'Required';
27 | } else if (values.username.length < 3) {
28 | errors.username = 'Must be at least 3 characters';
29 | }
30 |
31 | return errors;
32 | };
33 |
34 | export default reduxForm({
35 | form: 'login',
36 | fields: ['username', 'password'],
37 | validate
38 | },
39 | state => ({
40 | initialValues: {
41 | username: state.global.storedUserName,
42 | password: state.global.storedPassword
43 | }
44 | })
45 | )(LoginForm);
--------------------------------------------------------------------------------
/src/components/auth/LoginModal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Modal, Button} from 'react-bootstrap';
3 | import Icon from '../shared/Icon';
4 | import LoginForm from './LoginForm';
5 |
6 |
7 | export default class LoginModal extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.handleKeyPress = this.handleKeyPress.bind(this);
11 | this.handleSubmit = this.handleSubmit.bind(this);
12 | this.cancelClicked = this.cancelClicked.bind(this);
13 | }
14 |
15 | handleKeyPress(e) {
16 | if (e.key === 'Enter') {
17 | this.handleSubmit();
18 | }
19 | }
20 |
21 | handleSubmit() {
22 | this.refs.loginForm.submit();
23 | }
24 |
25 | cancelClicked(e) {
26 | e.preventDefault();
27 | this.props.loginCancel();
28 | };
29 |
30 | render() {
31 |
32 | const {loginRequired, login, isLoggingIn, applicationName, environmentName, loginError} = this.props;
33 | if (loginError) console.log(loginError);
34 | const loginText = isLoggingIn ? "Logging in..." : "Login";
35 | return (
36 |
37 |
38 |
39 | ×
41 | Log in to {applicationName} {environmentName}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | { loginError ?
50 | {loginError.toString()}
51 |
: null }
52 | { isLoggingIn ? : null }
53 | Cancel
54 | {loginText}
56 |
57 |
58 |
59 | );
60 | }
61 | }
--------------------------------------------------------------------------------
/src/components/nav/TopNav.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {LinkContainer, IndexLinkContainer} from 'react-router-bootstrap';
3 | import {Nav, Navbar, NavItem} from 'react-bootstrap';
4 | import Icon from './../shared/Icon';
5 | import UserMenu from './UserMenu';
6 |
7 | export default (props) => (
8 |
9 |
10 |
11 | {props.applicationName}
12 |
13 |
14 |
15 |
16 |
17 | Page 1
18 | Page 2
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
--------------------------------------------------------------------------------
/src/components/nav/UserMenu.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {NavItem, NavDropdown, MenuItem} from 'react-bootstrap';
3 | import Icon from '../shared/Icon';
4 |
5 | export default ({user, loginPrompt, logout, isLoggingIn, loginError, environmentName}) => {
6 |
7 | const logoutClicked = (e) => {
8 | e.preventDefault();
9 | logout();
10 | };
11 |
12 | const loginClicked = (e) => {
13 | e.preventDefault();
14 | loginPrompt();
15 | };
16 |
17 | if (user) {
18 | return (
19 | {user.name}} id="basic-nav-dropdown">
20 | Action
21 | Another action
22 | Something else here
23 |
24 |
25 | Logout
26 |
27 |
28 | );
29 | }
30 | else {
31 | const navIcon = isLoggingIn ? 'spinner fa-spin' : loginError? 'exclamation-triangle' : 'sign-in';
32 | const navText = isLoggingIn ? 'Logging in' : loginError? 'Error logging in!' :'Login';
33 | return (
34 |
35 | {navText} {environmentName}
36 |
37 | );
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/src/components/shared/Heading.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default (props) => (
4 | {props.text}
5 | );
6 |
--------------------------------------------------------------------------------
/src/components/shared/Icon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default (props) => (
4 |
5 |
6 |
7 | );
--------------------------------------------------------------------------------
/src/config/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Router, Route, IndexRoute } from 'react-router';
3 | import Main from '../containers/Main';
4 | import Page1 from '../containers/Page1';
5 | import Page2 from '../containers/Page2';
6 | import NotFound404 from '../components/pages/NotFound404';
7 |
8 | export default (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
--------------------------------------------------------------------------------
/src/constants/authentication.js:
--------------------------------------------------------------------------------
1 | import keyMirror from 'fbjs/lib/keyMirror';
2 |
3 | // use keyMirror here so we can cleanly refactor action names
4 | // throughout the whole app.
5 | const actionTypes = keyMirror({
6 | LOG_IN_PROMPT: null,
7 | LOG_IN_CANCEL: null,
8 | LOG_IN_REQUEST: null,
9 | LOG_IN_SUCCESS: null,
10 | LOG_IN_ERROR: null,
11 | LOG_OUT: null
12 | });
13 |
14 | export default {
15 | ...actionTypes
16 | };
--------------------------------------------------------------------------------
/src/containers/Main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {connect} from 'react-redux';
3 | import TopNav from './../components/nav/TopNav';
4 | import {login, logout, loginPrompt, loginCancel} from '../actions/authentication';
5 | import LoginModal from '../components/auth/LoginModal';
6 |
7 | class Main extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | componentDidMount() {
13 | this.props.loginPrompt();
14 | }
15 |
16 |
17 | render() {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 | {this.props.children}
26 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 | const mapDispatchToProps = (dispatch) => {
34 | return {
35 | loginCancel: () => {
36 | dispatch(loginCancel());
37 | },
38 |
39 | loginPrompt: () => {
40 | dispatch(loginPrompt());
41 | },
42 |
43 | login: (username, password) => {
44 | dispatch(login(username, password));
45 | },
46 |
47 | logout: () => {
48 | dispatch(logout());
49 | }
50 | };
51 | };
52 |
53 | const mapStateToProps = (state) => {
54 | return {
55 | applicationName: state.global.applicationName,
56 | environmentName: state.global.environmentName,
57 |
58 | //everything from auth
59 | ...state.authentication
60 | };
61 | };
62 |
63 | export default connect(mapStateToProps, mapDispatchToProps)(Main);
64 |
--------------------------------------------------------------------------------
/src/containers/Page1.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {connect} from 'react-redux';
3 | import Heading from '../components/shared/Heading';
4 | import Icon from './../components/shared/Icon';
5 |
6 | class Page1 extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | render() {
12 | const {user} = this.props;
13 | const username = user ? user.name : 'World';
14 |
15 | return (
16 |
17 |
18 |
);
19 | }
20 | }
21 |
22 | const mapStateToProps = (state) => {
23 | return { user: state.authentication.user};
24 | };
25 |
26 | export default connect(mapStateToProps)(Page1);
27 |
--------------------------------------------------------------------------------
/src/containers/Page2.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Heading from '../components/shared/Heading';
3 | import Icon from './../components/shared/Icon';
4 | import {connect} from 'react-redux';
5 |
6 | class Page2 extends React.Component {
7 |
8 | render() {
9 | const {user} = this.props;
10 | const username = user ? user.name : 'cruel world';
11 |
12 | return (
13 |
14 |
15 |
);
16 | }
17 | }
18 |
19 | const mapStateToProps = (state) => {
20 | return {user: state.authentication.user};
21 | };
22 |
23 | export default connect(mapStateToProps)(Page2);
24 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Hello World ❤ React & Webpack
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import React from 'react';
3 | import {Router, hashHistory} from 'react-router';
4 | import routes from './config/routes';
5 | import {Provider} from 'react-redux';
6 | import "!style!css!sass!./styles/main.scss";
7 |
8 | import store from './store';
9 |
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById('react-root')
15 | );
16 |
--------------------------------------------------------------------------------
/src/reducers/authentication.js:
--------------------------------------------------------------------------------
1 | import AuthConstants from '../constants/authentication';
2 |
3 | const initialState = {
4 | isLoggingIn: false,
5 | loginRequired: false
6 | };
7 |
8 | const authentication = (state = initialState, action) => {
9 | switch (action.type) {
10 |
11 | case AuthConstants.LOG_IN_PROMPT:
12 | {
13 | return {
14 | ...state,
15 | loginRequired: true
16 | };
17 | }
18 |
19 | case AuthConstants.LOG_IN_CANCEL:
20 | {
21 | return {
22 | ...state,
23 | loginRequired: false,
24 | loginError: false
25 | };
26 | }
27 |
28 |
29 | case AuthConstants.LOG_IN_REQUEST:
30 | {
31 | return {
32 | ...state,
33 | isLoggingIn: true,
34 | loginError: false
35 | };
36 | }
37 |
38 | case AuthConstants.LOG_IN_SUCCESS:
39 | {
40 | return {
41 | ...state,
42 | isLoggingIn: false,
43 | loginRequired: false,
44 | user: action.payload.user
45 | };
46 | }
47 |
48 | case AuthConstants.LOG_IN_ERROR:
49 | {
50 | return {
51 | ...state,
52 | isLoggingIn: false,
53 | loginError: action.payload
54 | };
55 | }
56 |
57 | case AuthConstants.LOG_OUT:
58 | {
59 | return {
60 | ...state,
61 | isLoggingIn: false,
62 | user: null
63 | };
64 | }
65 |
66 | default:
67 | {
68 | return state;
69 | }
70 | }
71 | };
72 |
73 | export default authentication;
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | // combine reducers here
2 | import {combineReducers} from 'redux';
3 | import {reducer as formReducer} from 'redux-form';
4 | import authentication from './authentication';
5 |
6 | export default combineReducers(
7 | {
8 | authentication,
9 | form: formReducer,
10 |
11 | // this is just for the initial state
12 | // in the store.js
13 | // see: http://stackoverflow.com/a/33678198/5906146
14 | global: (state = {}) => state
15 | }
16 | );
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import {createStore, applyMiddleware, compose} from 'redux';
2 | import reducers from '../reducers';
3 | import thunkMiddleware from 'redux-thunk';
4 |
5 |
6 | const initialState = {
7 | global: {
8 | applicationName: "Redux App Template",
9 | environmentName: "v1.0"
10 | }
11 | };
12 |
13 | const reduxDevToolsExtension = window.devToolsExtension ? window.devToolsExtension() : f => f;
14 |
15 | const store = createStore(
16 | reducers,
17 | initialState,
18 | compose(
19 | applyMiddleware(thunkMiddleware),
20 | reduxDevToolsExtension
21 | )
22 | );
23 |
24 | export default store;
--------------------------------------------------------------------------------
/src/styles/Auth.scss:
--------------------------------------------------------------------------------
1 | @import './colors';
2 |
3 | .loginModal {
4 |
5 | //width: 400px;
6 | //left: 50%;
7 | //margin-top: 50px;
8 | //margin-left: -200px;
9 |
10 |
11 | .modal-content {
12 | //width: 350px;
13 | //text-align: center
14 | }
15 |
16 | .btn-primary {
17 | background-color: $accent;
18 | border-color: $accent-dark;
19 | }
20 |
21 | .icon .fa {
22 | margin-right: 0.5em;
23 | font-size: 1.5em;
24 | vertical-align: middle;
25 | }
26 |
27 | .loginError {
28 | color: red;
29 | }
30 |
31 | .input {
32 | }
33 | }
--------------------------------------------------------------------------------
/src/styles/Main.scss:
--------------------------------------------------------------------------------
1 | @import './../../node_modules/font-awesome/css/font-awesome.min.css';
2 | @import './../../node_modules/bootstrap/dist/css/bootstrap.min.css';
3 | @import 'colors';
4 |
5 | @import './Auth';
6 |
7 | .fa {
8 | color: $accent;
9 | }
10 |
11 | body {
12 | background-color: $white;
13 | padding-top: 20px;
14 | padding-bottom: 20px;
15 | }
16 |
17 | .jumbotron {
18 | background-color: transparent;
19 | h1 {
20 | font-size: 1.8em;
21 | }
22 | p {
23 | font-size: 1.2em;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/styles/colors.scss:
--------------------------------------------------------------------------------
1 | $accent: #d20c37;
2 | $accent-dark: #ab0f30;
3 |
4 | $white: #f5f5f5;
5 | $black: #121212;
--------------------------------------------------------------------------------
/src/utils/fakeApi.js:
--------------------------------------------------------------------------------
1 | import {Promise} from 'es6-promise';
2 |
3 | export default {
4 |
5 | status(response) {
6 | if (response.status >= 200 && response.status < 300) {
7 | return Promise.resolve(response);
8 | } else {
9 | return Promise.reject(response.status);
10 | }
11 | },
12 |
13 | json(response) {
14 | return response.json();
15 | },
16 |
17 | // fake a token using the username
18 | getToken(username, password) {
19 |
20 | var p = new Promise((resolve) =>
21 |
22 | setTimeout(() => {
23 | resolve({
24 | access_token: username
25 | });
26 | }, 500));
27 |
28 | return p;
29 | },
30 |
31 | getUserProfile(token) {
32 | var p = new Promise((resolve) =>
33 |
34 | setTimeout(() => {
35 | resolve({
36 | accessToken: token,
37 | name: token
38 | });
39 | }, 500));
40 |
41 | return p;
42 | }
43 | };
--------------------------------------------------------------------------------
/src/utils/storage.js:
--------------------------------------------------------------------------------
1 | export default {
2 |
3 | get(key) {
4 | return localStorage[key];
5 | },
6 |
7 | set(key, value) {
8 | localStorage[key] = value;
9 | },
10 |
11 | remove(key) {
12 | localStorage.removeItem(key);
13 | },
14 |
15 | getJSON(key) {
16 |
17 | var value = this.get(key);
18 | try {
19 | return value != undefined ? JSON.parse(value) : null;
20 | } catch (e) {
21 | throw `unable to convert key "${key}" with value "${value}" to JSON`;
22 | }
23 | },
24 |
25 | setJSON(key, json) {
26 | var savedValue = JSON.stringify(json);
27 | this.set(key, savedValue);
28 | }
29 | };
--------------------------------------------------------------------------------
/src/utils/sum.js:
--------------------------------------------------------------------------------
1 | // calm down. this class is just for testing the test runner :)
2 | export default (a, b) => a + b;
3 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const postcssImport = require('postcss-import');
5 | const postcssUrl = require('postcss-url');
6 | const autoprefixer = require('autoprefixer');
7 | const precss = require('precss');
8 |
9 | const PATHS = {
10 | src: path.join(__dirname, 'src'),
11 | dist: path.join(__dirname, 'dist')
12 | }
13 |
14 |
15 | module.exports = {
16 |
17 | // This enables the creation of source maps,
18 | // which improve the debuggability of the application
19 | // by allowing you to see where an error was raised.
20 | devtool: "source-map",
21 |
22 | entry: PATHS.src,
23 |
24 | // Location and filename pattern of the
25 | // final build output files.
26 | output: {
27 | path: PATHS.dist,
28 | filename: "bundle.js"
29 | },
30 |
31 | devServer: {
32 | //content from here will be automatically served from here
33 | contentBase: "dist/",
34 | // and appears to come relative to this path
35 | publicPath: "/",
36 |
37 | historyApiFallback: true
38 | },
39 |
40 |
41 | module: {
42 |
43 | // Performs linting on code for quality checks
44 | preLoaders: [
45 | {
46 | test: /(\.js$|\.jsx$)/,
47 | include: PATHS.src,
48 | loader: "eslint"
49 | }
50 | ],
51 |
52 | // Performs transformations
53 | loaders: [
54 | {
55 | // Post-css loader and its plugins.
56 | test: /\.scss$/,
57 | include: PATHS.src,
58 | loaders: [
59 | 'style',// inserts raw css into styles elements.
60 | 'css', // css-loader parses css files resolves url() expressions.
61 | 'sass', // sass-loader for sass compilation
62 | 'postcss' // for whatever we have defined in postcss( ) below
63 | ]
64 | },
65 | {
66 | // for jsx
67 | test: /\.jsx?$/,
68 | include: PATHS.src,
69 | loader: 'babel',
70 | query: {
71 | plugins: ['transform-object-rest-spread'],
72 | presets: ['react', 'es2015']
73 | }
74 | },
75 |
76 | // for font-awesome
77 | { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&minetype=application/font-woff" },
78 | { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" }
79 | ]
80 | },
81 | postcss: function(webpack) {
82 | return [autoprefixer, precss, postcssImport({ addDependencyTo: webpack }), postcssUrl({})];
83 | },
84 | eslint: {
85 | configFile: '.eslintrc'
86 | },
87 |
88 | // Defines where we can load modules from,
89 | // and the extensions we care about. The ''
90 | // empty string allows the requiring of arbitrary
91 | // extensions, e.g. require('./somefile.ext').
92 | // Specifiying extensions such as '.js' allows
93 | // requiring without extensions,
94 | // e.g. require('underscore')
95 | resolve: {
96 | modulesDirectories: ['node_modules'],
97 | extensions: ['', '.js', '.jsx']
98 | },
99 |
100 | plugins: [
101 | new webpack.HotModuleReplacementPlugin(),
102 | new HtmlWebpackPlugin({
103 | favicon: 'src/assets/favicon.ico',
104 | template: 'src/index.html',
105 | inject: true
106 | })
107 | ]
108 | }
--------------------------------------------------------------------------------