├── .babelrc
├── .eslintrc
├── .gitignore
├── LICENSE.md
├── README.md
├── __mocks__
└── parse.js
├── config
└── index.js
├── index.js
├── package.json
├── src
├── common
│ ├── components
│ │ ├── App
│ │ │ └── App.jsx
│ │ ├── ProtectedGateway
│ │ │ └── ProtectedGateway.jsx
│ │ ├── PublicGateway
│ │ │ └── PublicGateway.jsx
│ │ └── Routes
│ │ │ └── Routes.jsx
│ └── modules
│ │ ├── action-creators
│ │ └── index.js
│ │ ├── action-repository
│ │ └── index.js
│ │ ├── combined-reducer
│ │ └── index.js
│ │ ├── create-action
│ │ └── index.js
│ │ ├── get-current-user
│ │ └── index.js
│ │ ├── parse-client
│ │ ├── __tests__
│ │ │ └── index-test.js
│ │ └── index.js
│ │ ├── reducers
│ │ └── index.js
│ │ └── store
│ │ └── index.js
├── dashboard
│ └── components
│ │ └── DashboardPage
│ │ └── DashboardPage.jsx
└── public
│ ├── components
│ ├── LoginPage
│ │ └── LoginPage.jsx
│ └── RegistrationPage
│ │ └── RegistrationPage.jsx
│ └── modules
│ ├── action-creators
│ └── index.js
│ └── action-repository
│ └── index.js
├── webpack.config.js
└── webpack.config.production.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["transform-runtime"],
3 | "presets": ["es2015", "stage-0", "react"]
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "rules": {
4 | "indent": [
5 | 2,
6 | 4,
7 | {
8 | "SwitchCase": 1
9 | }
10 | ],
11 | "quotes": [
12 | 2,
13 | "single"
14 | ],
15 | "jsx-quotes": [
16 | 2,
17 | "prefer-double"
18 | ],
19 | "linebreak-style": [
20 | 2,
21 | "unix"
22 | ],
23 | "semi": [
24 | 2,
25 | "always"
26 | ],
27 | "object-curly-spacing": [
28 | 2,
29 | "never"
30 | ],
31 | "no-console": 0,
32 | "dot-notation": 2,
33 | "eol-last": 2,
34 | "constructor-super": 2,
35 | "no-class-assign": 2,
36 | "no-const-assign": 2,
37 | "no-dupe-class-members": 2,
38 | "no-this-before-super": 2,
39 | "no-var": 2,
40 | "prefer-const": 2,
41 | "object-shorthand": 2,
42 | "prefer-arrow-callback": 2,
43 | "prefer-template": 2,
44 | "arrow-parens": 2,
45 | "react/jsx-uses-react": 1,
46 | "react/forbid-prop-types": 1,
47 | "react/jsx-boolean-value": 1,
48 | "react/jsx-closing-bracket-location": 1,
49 | "react/jsx-curly-spacing": 1,
50 | "react/jsx-indent-props": 1,
51 | "react/jsx-max-props-per-line": [
52 | 1,
53 | {
54 | "maximum": 3
55 | }
56 | ],
57 | "react/jsx-no-duplicate-props": 1,
58 | "react/jsx-no-undef": 1,
59 | "react/jsx-uses-vars": 1,
60 | "react/no-danger": 1,
61 | "react/no-did-mount-set-state": 1,
62 | "react/no-did-update-set-state": 1,
63 | "react/no-direct-mutation-state": 1,
64 | "react/no-multi-comp": 1,
65 | "react/no-set-state": 1,
66 | "react/no-unknown-property": 1,
67 | "react/prefer-es6-class": 0,
68 | "react/react-in-jsx-scope": 1,
69 | "react/require-extension": 1,
70 | "react/self-closing-comp": 1,
71 | "react/sort-comp": 2,
72 | "react/wrap-multilines": 1
73 | },
74 | "env": {
75 | "es6": true,
76 | "node": true,
77 | "browser": true,
78 | "jasmine": true,
79 | "jest": true
80 | },
81 | "extends": "eslint:recommended",
82 | "ecmaFeatures": {
83 | "jsx": true,
84 | "experimentalObjectRestSpread": true,
85 | "modules": true
86 | },
87 | "plugins": [
88 | "react"
89 | ]
90 | }
91 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 | www
4 | !www/index.html
5 | npm-debug.log
6 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Maxim Potapov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATED
2 |
3 | Since Parse is about to shutdown, this package is no longer maintained.
4 |
5 | # Kickstart your application with React, Redux and Parse.
6 |
7 | ```npm run watch``` to start a webpack dev server.
8 |
9 | ```npm run build``` to build a production version.
10 |
11 | work is in progress...
12 |
13 | ## What is included
14 |
15 | * parse
16 | * react
17 | * redux
18 | * react-router
19 | * webpack
20 | * babel (ES6 Stage 0)
21 | * jest
22 | * eslint
23 |
24 |
25 | ## Todo
26 |
27 | * ~~remove axios and material-ui from deps~~
28 | * ~~add auth flow~~
29 | * add redux dev tools
30 | * add more tests
31 | * split config to dev/prod
32 | * add react hot reloading (https://github.com/gaearon/react-transform-boilerplate when it works with babel 6)
33 | * ~~add .babelrc~~
34 | * switch from Jest to something faster (several seconds to run a single test in [unacceptable](https://github.com/facebook/jest/issues/116))
35 |
--------------------------------------------------------------------------------
/__mocks__/parse.js:
--------------------------------------------------------------------------------
1 | jest.autoMockOff();
2 |
3 | const Parse = require.requireActual('parse');
4 | spyOn(Parse, 'initialize');
5 | module.exports = Parse;
6 |
7 | jest.autoMockOn();
8 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | PARSE_APP_ID: '',
3 | PARSE_JS_KEY: ''
4 | };
5 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import Routes from './src/common/components/Routes/Routes.jsx';
3 |
4 | ReactDOM.render(Routes, document.getElementById('content'));
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-parse-starter-kit",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "NODE_ENV='production' webpack --progress --profile --colors --config webpack.config.production.js",
8 | "analyze": "webpack --json | analyze-bundle-size",
9 | "watch": "webpack-dev-server --content-base www/ --hot --inline --progress --colors",
10 | "test": "BABEL_JEST_STAGE=0 jest",
11 | "lint": "eslint --ext .js --ext .jsx src"
12 | },
13 | "author": "Maxim Potapov",
14 | "license": "MIT",
15 | "dependencies": {
16 | "babel-polyfill": "^6.3.14",
17 | "babel-runtime": "^6.3.19",
18 | "history": "~1.13.0",
19 | "parse": "^1.6.8",
20 | "react": "^0.14.1",
21 | "react-addons-create-fragment": "^0.14.1",
22 | "react-addons-pure-render-mixin": "^0.14.1",
23 | "react-addons-transition-group": "^0.14.1",
24 | "react-addons-update": "^0.14.1",
25 | "react-dom": "^0.14.1",
26 | "react-redux": "^4.0.0",
27 | "react-router": "^1.0.0",
28 | "react-tap-event-plugin": "^0.2.1",
29 | "redux": "^3.0.4",
30 | "redux-thunk": "^1.0.0"
31 | },
32 | "devDependencies": {
33 | "babel-core": "^6.0.0",
34 | "babel-eslint": "^4.1.3",
35 | "babel-jest": "^6.0.0",
36 | "babel-loader": "^6.0.0",
37 | "babel-plugin-transform-runtime": "^6.3.13",
38 | "babel-preset-es2015": "^6.3.13",
39 | "babel-preset-react": "^6.3.13",
40 | "babel-preset-stage-0": "^6.3.13",
41 | "css-loader": "^0.21.0",
42 | "eslint": "^1.8.0",
43 | "eslint-loader": "^1.1.0",
44 | "eslint-plugin-react": "^3.6.3",
45 | "jest-cli": "^0.8.2",
46 | "react-addons-test-utils": "^0.14.1",
47 | "style-loader": "^0.13.0",
48 | "webpack": "^1.12.2",
49 | "webpack-bundle-size-analyzer": "^1.1.0",
50 | "webpack-dev-server": "^1.12.1"
51 | },
52 | "jest": {
53 | "scriptPreprocessor": "node_modules/babel-jest",
54 | "unmockedModulePathPatterns": [
55 | "node_modules/react",
56 | "node_modules/react-dom",
57 | "node_modules/react-addons-test-utils",
58 | "node_modules/fbjs"
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/common/components/App/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | class App extends React.Component {
5 |
6 | render() {
7 | return (
8 |
Welcome to App
9 | {this.props.children}
10 | );
11 | }
12 |
13 | }
14 |
15 | export default App;
16 |
--------------------------------------------------------------------------------
/src/common/components/ProtectedGateway/ProtectedGateway.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {connect} from 'react-redux';
3 | import {History} from 'react-router';
4 | import {signOut} from '../../modules/action-creators/index';
5 |
6 |
7 | const Protected = React.createClass({
8 |
9 | mixins: [History],
10 |
11 | componentWillMount() {
12 | if (!this.props.currentUser) {
13 | this.redirect();
14 | }
15 | },
16 |
17 | componentWillReceiveProps(nextProps) {
18 | if (!nextProps.currentUser) {
19 | this.redirect();
20 | }
21 | },
22 |
23 | redirect() {
24 | this.history.replaceState(null, '/signin');
25 | },
26 |
27 | handleSignOut() {
28 | this.props.dispatch(signOut());
29 | },
30 |
31 | render() {
32 | return (
33 |
34 |
35 | {this.props.children}
36 |
37 | );
38 | }
39 |
40 | });
41 |
42 | export default connect((state) => ({currentUser: state.currentUser}))(Protected);
43 |
--------------------------------------------------------------------------------
/src/common/components/PublicGateway/PublicGateway.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {connect} from 'react-redux';
3 | import {History} from 'react-router';
4 |
5 |
6 | const Public = React.createClass({
7 |
8 | mixins: [History],
9 |
10 | componentWillMount() {
11 | if (this.props.currentUser) {
12 | this.redirect();
13 | }
14 | },
15 |
16 | componentWillReceiveProps(nextProps) {
17 | if (nextProps.currentUser) {
18 | this.redirect();
19 | }
20 | },
21 |
22 | redirect() {
23 | this.history.replaceState(null, '/dashboard');
24 | },
25 |
26 | render() {
27 | return this.props.children;
28 | }
29 |
30 | });
31 |
32 | export default connect((state) => ({currentUser: state.currentUser}))(Public);
33 |
--------------------------------------------------------------------------------
/src/common/components/Routes/Routes.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Router, Route, Redirect} from 'react-router';
3 | import {Provider} from 'react-redux';
4 |
5 | import store from '../../modules/store/index';
6 |
7 | import App from '../App/App.jsx';
8 | import PublicGateway from '../PublicGateway/PublicGateway.jsx';
9 | import ProtectedGateway from '../ProtectedGateway/ProtectedGateway.jsx';
10 | import RegistrationPage from '../../../public/components/RegistrationPage/RegistrationPage.jsx';
11 | import LoginPage from '../../../public/components/LoginPage/LoginPage.jsx';
12 | import DashboardPage from '../../../dashboard/components/DashboardPage/DashboardPage.jsx';
13 |
14 | const Routes = (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 |
33 | export default Routes;
34 |
--------------------------------------------------------------------------------
/src/common/modules/action-creators/index.js:
--------------------------------------------------------------------------------
1 | import {signOut as actionSignOut} from '../action-repository/index';
2 | import Parse from 'parse';
3 |
4 | export const signOut = () => {
5 | Parse.User.logOut();
6 | return actionSignOut(Parse.User.current());
7 | };
8 |
--------------------------------------------------------------------------------
/src/common/modules/action-repository/index.js:
--------------------------------------------------------------------------------
1 | import createAction from '../create-action/index';
2 |
3 | export const signOut = createAction('SIGN_OUT');
4 |
--------------------------------------------------------------------------------
/src/common/modules/combined-reducer/index.js:
--------------------------------------------------------------------------------
1 | import {combineReducers} from 'redux';
2 | import {currentUser} from '../reducers/index';
3 |
4 | const reducer = combineReducers({
5 | currentUser
6 | });
7 |
8 | export default reducer;
9 |
--------------------------------------------------------------------------------
/src/common/modules/create-action/index.js:
--------------------------------------------------------------------------------
1 | export default (type) => {
2 | return (payload) => {
3 | return {
4 | type,
5 | payload
6 | };
7 | };
8 | };
9 |
--------------------------------------------------------------------------------
/src/common/modules/get-current-user/index.js:
--------------------------------------------------------------------------------
1 | import Parse from '../parse-client/index';
2 |
3 | export default () => {
4 | return Parse.User.current();
5 | };
6 |
--------------------------------------------------------------------------------
/src/common/modules/parse-client/__tests__/index-test.js:
--------------------------------------------------------------------------------
1 | const CONFIG_PATH = '../../../../../config/index';
2 |
3 | jest.dontMock('../index');
4 | jest.dontMock(CONFIG_PATH);
5 |
6 | describe('parse-client', () => {
7 |
8 | it('should initialize itself with keys from config', () => {
9 |
10 | const config = require(CONFIG_PATH).default;
11 | const Parse = require('../index').default;
12 |
13 | expect(Parse.initialize).toHaveBeenCalledWith(config.PARSE_APP_ID, config.PARSE_JS_KEY);
14 |
15 | });
16 |
17 | });
18 |
--------------------------------------------------------------------------------
/src/common/modules/parse-client/index.js:
--------------------------------------------------------------------------------
1 | import Parse from 'parse';
2 | import config from '../../../../config/index';
3 |
4 | Parse.initialize(config.PARSE_APP_ID, config.PARSE_JS_KEY);
5 |
6 | export default Parse;
7 |
--------------------------------------------------------------------------------
/src/common/modules/reducers/index.js:
--------------------------------------------------------------------------------
1 | import getCurrentUser from '../get-current-user/index';
2 |
3 |
4 | export const currentUser = function(state = getCurrentUser(), action) {
5 |
6 | switch (action.type) {
7 |
8 | case 'REGISTER':
9 | case 'SIGN_IN':
10 | case 'SIGN_OUT':
11 | return action.payload;
12 |
13 | default:
14 | return state;
15 |
16 | }
17 |
18 | };
19 |
--------------------------------------------------------------------------------
/src/common/modules/store/index.js:
--------------------------------------------------------------------------------
1 | import {createStore, applyMiddleware} from 'redux';
2 | import thunk from 'redux-thunk';
3 | import app from '../combined-reducer/index';
4 |
5 | const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
6 |
7 | const store = createStoreWithMiddleware(app);
8 |
9 | export default store;
10 |
--------------------------------------------------------------------------------
/src/dashboard/components/DashboardPage/DashboardPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | class DashboardPage extends React.Component {
5 |
6 | render() {
7 | return (
8 |
Dashboard
9 | );
10 | }
11 |
12 | }
13 |
14 | export default DashboardPage;
15 |
--------------------------------------------------------------------------------
/src/public/components/LoginPage/LoginPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router';
3 | import {signIn} from '../../modules/action-creators/index';
4 | import {connect} from 'react-redux';
5 |
6 |
7 | class LoginPage extends React.Component {
8 |
9 | handleSubmit(e) {
10 | e.preventDefault();
11 | this.props.dispatch(signIn(this.form.elements.username.value, this.form.elements.password.value));
12 | }
13 |
14 | render() {
15 | return (
16 |
Sign in
17 |
22 | Register
23 | );
24 | }
25 |
26 | }
27 |
28 | export default connect()(LoginPage);
29 |
--------------------------------------------------------------------------------
/src/public/components/RegistrationPage/RegistrationPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router';
3 | import {register} from '../../modules/action-creators/index';
4 | import {connect} from 'react-redux';
5 |
6 |
7 | class RegistrationPage extends React.Component {
8 |
9 | handleSubmit(e) {
10 | e.preventDefault();
11 | this.props.dispatch(register(this.form.elements.username.value, this.form.elements.password.value));
12 | }
13 |
14 | render() {
15 | return (
16 |
Register
17 |
22 | Sign in
23 | );
24 | }
25 |
26 | }
27 |
28 | export default connect()(RegistrationPage);
29 |
--------------------------------------------------------------------------------
/src/public/modules/action-creators/index.js:
--------------------------------------------------------------------------------
1 | import Parse from 'parse';
2 | import {register as actionRegister, signIn as actionSignIn} from '../action-repository/index';
3 |
4 |
5 | export const register = (username, password) => {
6 |
7 | return async function (dispatch) {
8 |
9 | const user = new Parse.User();
10 | user.set('username', username);
11 | user.set('password', password);
12 |
13 | dispatch(actionRegister(user));
14 |
15 | try {
16 |
17 | await user.signUp();
18 | dispatch(actionRegister(Parse.User.current()));
19 |
20 | } catch (exception) {
21 | console.error('REGISTER', exception);
22 | }
23 |
24 | };
25 | };
26 |
27 | export const signIn = (username, password) => {
28 |
29 | return async function (dispatch) {
30 |
31 | try {
32 |
33 | await Parse.User.logIn(username, password);
34 | dispatch(actionSignIn(Parse.User.current()));
35 |
36 | } catch (exception) {
37 | console.error('SIGN_IN', exception);
38 | }
39 |
40 | };
41 | };
42 |
--------------------------------------------------------------------------------
/src/public/modules/action-repository/index.js:
--------------------------------------------------------------------------------
1 | import createAction from '../../../common/modules/create-action/index';
2 |
3 |
4 | export const register = createAction('REGISTER');
5 | export const signIn = createAction('SIGN_IN');
6 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: [
5 | // Set up an ES6-ish environment
6 | 'babel-polyfill',
7 |
8 | // Add your application's scripts below
9 | './index.js',
10 | ],
11 | devtool: 'source-map',
12 | output: {
13 | path: path.join(__dirname, 'www'),
14 | filename: 'bundle.js'
15 | },
16 | module: {
17 | preLoaders: [
18 | {
19 | loader: 'eslint-loader',
20 | exclude: /node_modules/,
21 | test: /\.jsx?$/,
22 | }
23 | ],
24 | loaders: [
25 | {
26 | loader: 'babel',
27 | exclude: /node_modules/,
28 | test: /\.jsx?$/,
29 | },
30 | { test: /\.css$/, loader: 'style!css' },
31 |
32 | ]
33 | },
34 | eslint: {
35 | configFile: '.eslintrc'
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 |
5 | module.exports = {
6 | entry: [
7 | // Set up an ES6-ish environment
8 | 'babel-polyfill',
9 |
10 | // Add your application's scripts below
11 | './index.js',
12 | ],
13 | output: {
14 | path: path.join(__dirname, 'www'),
15 | filename: 'bundle.js'
16 | },
17 | module: {
18 | loaders: [
19 | {
20 | loader: 'babel',
21 | exclude: /node_modules/,
22 | test: /\.jsx?$/,
23 | },
24 | { test: /\.css$/, loader: 'style!css' },
25 |
26 | ]
27 | },
28 | plugins: [
29 | new webpack.optimize.DedupePlugin(),
30 | new webpack.optimize.UglifyJsPlugin()
31 | ]
32 | };
33 |
--------------------------------------------------------------------------------