├── src ├── app │ ├── actions │ │ ├── index.js │ │ ├── types.js │ │ └── firebase_actions.js │ ├── bundle.scss │ ├── components │ │ ├── app.scss │ │ ├── index_home.jsx │ │ ├── user │ │ │ ├── logout.jsx │ │ │ ├── reset_password.jsx │ │ │ ├── change_password.jsx │ │ │ ├── profile.jsx │ │ │ ├── register.jsx │ │ │ └── login.jsx │ │ ├── helpers │ │ │ └── loading.jsx │ │ └── app.jsx │ ├── reducers │ │ ├── index.js │ │ └── firebase_user_reducer.js │ ├── utils │ │ ├── authenticated.js │ │ └── firebase.js │ ├── config.js │ ├── routes.jsx │ └── index.jsx └── index.html ├── .babelrc ├── .travis.yml ├── .firebaserc ├── jsconfig.json ├── webpack ├── webpack-prod.config.js ├── webpack-dev.config.js └── webpack.config.js ├── firebase.json ├── database.rules.json ├── .eslintrc ├── test ├── components │ └── app_test.js └── test_helper.js ├── .editorconfig ├── www.js ├── .gitignore ├── LICENSE ├── README.md └── package.json /src/app/actions/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/bundle.scss: -------------------------------------------------------------------------------- 1 | @import './components/app'; -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /src/app/components/app.scss: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 16px; 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "6" 5 | - "5" 6 | - "4" -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "react-redux-firebase-d6283" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/app/components/index_home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => { 4 | return
Home Page of our application!
; 5 | }; 6 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "allowSyntheticDefaultImports": true 5 | }, 6 | "exclude": [ 7 | "node_modules" 8 | ] 9 | } -------------------------------------------------------------------------------- /src/app/components/user/logout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | export default () => ( 5 |
6 |

You are logged out!

7 |
8 | ); 9 | -------------------------------------------------------------------------------- /webpack/webpack-prod.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./webpack.config.js')({ 2 | isProduction: true, 3 | devtool: 'source-map', 4 | jsFileName: 'app.[hash].js', 5 | cssFileName: 'app.[hash].css', 6 | }); 7 | -------------------------------------------------------------------------------- /webpack/webpack-dev.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./webpack.config.js')({ 2 | isProduction: false, 3 | devtool: 'cheap-module-source-map', 4 | jsFileName: 'app.js', 5 | cssFileName: 'app.css', 6 | port: 3000, 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/components/helpers/loading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | const Loading = () => { 5 | return ( 6 |
7 | Loading .... 8 |
9 | ); 10 | }; 11 | 12 | export default Loading; 13 | -------------------------------------------------------------------------------- /src/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import FireBaseUserReducer from './firebase_user_reducer'; 3 | 4 | const rootReducer = combineReducers({ 5 | currentUser: FireBaseUserReducer, 6 | }); 7 | 8 | export default rootReducer; 9 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "hosting": { 6 | "public": "dist", 7 | "rewrites": [ 8 | { 9 | "source": "**", 10 | "destination": "/index.html" 11 | } 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | ".read": false, 4 | ".write": false, 5 | "words": { 6 | ".read": true, 7 | ".write": true 8 | }, 9 | "dev": { 10 | ".read": true, 11 | ".write": true 12 | }, 13 | "staging": { 14 | ".read": true, 15 | ".write": true 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parserOptions": { 4 | "ecmaFeatures": { 5 | "experimentalObjectRestSpread": true 6 | } 7 | }, 8 | "rules": { 9 | "max-len": ["warn", 120], 10 | "indent": ["warn", 4], 11 | "react/jsx-indent": ["warn", 4] 12 | }, 13 | "globals": { 14 | "localStorage": true 15 | } 16 | } -------------------------------------------------------------------------------- /test/components/app_test.js: -------------------------------------------------------------------------------- 1 | import {renderComponent, expect} from '../test_helper'; 2 | import App from '../../src/app/components/app'; 3 | 4 | describe('App', () => { 5 | let component; 6 | 7 | beforeEach(() => { 8 | component = renderComponent(App); 9 | }); 10 | 11 | it('renders something', () => { 12 | expect(component).to.exist; 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | 17 | [COMMIT_EDITMSG] 18 | max_line_length = 0 -------------------------------------------------------------------------------- /www.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | 4 | app.use(express.static('./')); 5 | app.use(express.static('dist')); 6 | 7 | app.get('*', (req, res) => { 8 | res.sendFile(`${__dirname}/dist/index.html`); 9 | }); 10 | 11 | const port = process.env.PORT || 3000; 12 | 13 | app.listen(port, () => { 14 | console.log('app listening on', port); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/utils/authenticated.js: -------------------------------------------------------------------------------- 1 | function requireAuth(nextState, replace) { 2 | const key = Object.keys(localStorage).find(e => e.match(/firebase:authUser/)); 3 | const data = JSON.parse(localStorage.getItem(key)); 4 | if (data == null) { 5 | replace({ 6 | pathname: '/login', 7 | state: { 8 | nextPathname: nextState.location.pathname, 9 | }, 10 | }); 11 | } 12 | } 13 | 14 | module.exports = requireAuth; 15 | -------------------------------------------------------------------------------- /src/app/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | // Change this to your firebase configuration! (Add Firebase to your web app) 4 | FIREBASE_CONFIG: { 5 | apiKey: "AIzaSyBgD4q3YujiPOOt4sPAfBHzBtG6xENp-TE", 6 | authDomain: "adwebsite-928a9.firebaseapp.com", 7 | databaseURL: "https://adwebsite-928a9.firebaseio.com", 8 | projectId: "adwebsite-928a9", 9 | storageBucket: "adwebsite-928a9.appspot.com", 10 | messagingSenderId: "72060758788" 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/app/actions/types.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // / FIREBASE AUTH ACTIONS 4 | export const LOGIN_WITH_PROVIDER_FIREBASE = 'LOGIN_WITH_PROVIDER_FIREBASE'; 5 | export const REGISTER_FIREBASE_USER = 'REGISTER_FIREBASE_USER'; 6 | export const LOGIN_FIREBASE_USER = 'LOGIN_FIREBASE_USER'; 7 | export const FETCH_FIREBASE_USER = 'FETCH_FIREBASE_USER'; 8 | export const UPDATE_FIREBASE_USER = 'UPDATE_FIREBASE_USER'; 9 | export const CHANGE_FIREBASE_USER_PASSWORD = 'CHANGE_FIREBASE_USER_PASSWORD'; 10 | export const FIREBASE_PASSWORD_RESET_EMAIL = 'FIREBASE_PASSWORD_RESET_EMAIL'; 11 | export const LOGOUT_FIREBASE_USER = 'LOGOUT_FIREBASE_USER'; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # 7 | dist 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | .idea/ 42 | .vscode 43 | firebase-debug.log 44 | -------------------------------------------------------------------------------- /src/app/routes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | import App from './components/app'; 4 | 5 | import HomeIndex from './components/index_home'; 6 | import UserLogin from './components/user/login'; 7 | import UserLogout from './components/user/logout'; 8 | import UserRegister from './components/user/register'; 9 | import UserProfile from './components/user/profile'; 10 | import ResetPassword from './components/user/reset_password'; 11 | import requireAuth from './utils/authenticated'; 12 | 13 | export default ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /src/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore, applyMiddleware, compose } from 'redux'; 5 | import { Router, browserHistory } from 'react-router'; 6 | import { createLogger } from 'redux-logger'; 7 | import ReduxPromise from 'redux-promise'; 8 | 9 | 10 | import reducers from './reducers'; 11 | import routes from './routes'; 12 | 13 | import 'bootstrap-social'; 14 | 15 | // for bundling your styles 16 | import './bundle.scss'; 17 | 18 | const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore); 19 | 20 | // Redux Dev tools 21 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 22 | 23 | const logger = createLogger(); 24 | 25 | const enhancers = [applyMiddleware(ReduxPromise, logger)]; 26 | 27 | const store = createStore(reducers, composeEnhancers(...enhancers)); 28 | 29 | ReactDOM.render( 30 | 31 | 32 | , 33 | document.querySelector('.react-root') 34 | ); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 btomashvili 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 | -------------------------------------------------------------------------------- /test/test_helper.js: -------------------------------------------------------------------------------- 1 | import _$ from 'jquery'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import TestUtils from 'react-addons-test-utils'; 5 | import jsdom from 'jsdom'; 6 | import chai, { expect } from 'chai'; 7 | import chaiJquery from 'chai-jquery'; 8 | import { Provider } from 'react-redux'; 9 | import { createStore } from 'redux'; 10 | import reducers from '../src/app/reducers'; 11 | 12 | global.document = jsdom.jsdom(''); 13 | global.window = global.document.defaultView; 14 | global.navigator = global.window.navigator; 15 | const $ = _$(window); 16 | 17 | chaiJquery(chai, chai.util, $); 18 | 19 | function renderComponent(ComponentClass, props = {}, state = {}) { 20 | const componentInstance = TestUtils.renderIntoDocument( 21 | 22 | 23 | 24 | ); 25 | 26 | return $(ReactDOM.findDOMNode(componentInstance)); 27 | } 28 | 29 | $.fn.simulate = function(eventName, value) { 30 | if (value) { 31 | this.val(value); 32 | } 33 | TestUtils.Simulate[eventName](this[0]); 34 | }; 35 | 36 | export {renderComponent, expect}; 37 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | React, Redux & Firebase Boilerplate 10 | 12 | 14 | 16 | 17 | 18 |
19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/app/actions/firebase_actions.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOGIN_WITH_PROVIDER_FIREBASE, 3 | REGISTER_FIREBASE_USER, 4 | LOGIN_FIREBASE_USER, 5 | FETCH_FIREBASE_USER, 6 | UPDATE_FIREBASE_USER, 7 | CHANGE_FIREBASE_USER_PASSWORD, 8 | FIREBASE_PASSWORD_RESET_EMAIL, 9 | LOGOUT_FIREBASE_USER, 10 | } from './types'; 11 | 12 | 13 | export function loginWithProvider(provider) { 14 | return { 15 | type: LOGIN_WITH_PROVIDER_FIREBASE, 16 | provider, 17 | }; 18 | } 19 | 20 | export function registerUser(user) { 21 | return { 22 | type: REGISTER_FIREBASE_USER, 23 | user 24 | }; 25 | } 26 | 27 | export function loginUser(user) { 28 | return { 29 | type: LOGIN_FIREBASE_USER, 30 | user 31 | }; 32 | } 33 | 34 | export function fetchUser() { 35 | return { 36 | type: FETCH_FIREBASE_USER 37 | }; 38 | } 39 | 40 | export function updateUser(user) { 41 | return { 42 | type: UPDATE_FIREBASE_USER, 43 | user 44 | }; 45 | } 46 | 47 | export function changePassword(newPassword) { 48 | return { 49 | type: CHANGE_FIREBASE_USER_PASSWORD, 50 | newPassword 51 | }; 52 | } 53 | 54 | export function resetPasswordEmail(email) { 55 | return { 56 | type: FIREBASE_PASSWORD_RESET_EMAIL, 57 | email 58 | }; 59 | } 60 | 61 | export function logoutUser(user) { 62 | return { 63 | type: LOGOUT_FIREBASE_USER, 64 | user 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /src/app/components/user/reset_password.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | import { resetPasswordEmail } from '../../actions/firebase_actions'; 5 | 6 | class ResetPassword extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | message: '', 11 | }; 12 | this.onFormSubmit = this.onFormSubmit.bind(this); 13 | } 14 | 15 | onFormSubmit(event) { 16 | event.preventDefault(); 17 | const email = this.refs.email.value; 18 | this.props.resetPasswordEmail(email).then((data) => { 19 | if (data.payload.errorCode) { 20 | this.setState({ message: data.payload.errorMessage }); 21 | } else { 22 | this.setState({ message: 'Please see your email!' }); 23 | } 24 | }); 25 | } 26 | 27 | render() { 28 | return ( 29 | 30 |
31 |
32 |

{this.state.message}

33 |
34 | 35 | 39 |
40 | 41 |
42 |
43 | 44 | ); 45 | } 46 | } 47 | 48 | function mapDispatchToProps(dispatch) { 49 | return bindActionCreators({ 50 | resetPasswordEmail, 51 | }, dispatch); 52 | } 53 | 54 | export default connect(null, mapDispatchToProps)(ResetPassword); 55 | -------------------------------------------------------------------------------- /src/app/reducers/firebase_user_reducer.js: -------------------------------------------------------------------------------- 1 | import FireBaseTools from '../utils/firebase'; 2 | import { 3 | LOGIN_WITH_PROVIDER_FIREBASE, 4 | REGISTER_FIREBASE_USER, 5 | LOGIN_FIREBASE_USER, 6 | FETCH_FIREBASE_USER, 7 | UPDATE_FIREBASE_USER, 8 | CHANGE_FIREBASE_USER_PASSWORD, 9 | FIREBASE_PASSWORD_RESET_EMAIL, 10 | LOGOUT_FIREBASE_USER, 11 | } from '../actions/types'; 12 | 13 | 14 | export default function (state = null, action) { 15 | switch (action.type) { 16 | 17 | case FETCH_FIREBASE_USER: 18 | return fetchUser(); 19 | 20 | case LOGOUT_FIREBASE_USER: 21 | return logoutUser(action.user); 22 | 23 | case REGISTER_FIREBASE_USER: 24 | return registerUser(action.user); 25 | 26 | case LOGIN_FIREBASE_USER: 27 | return loginUser(action.user); 28 | 29 | case UPDATE_FIREBASE_USER: 30 | return updateUser(action.user); 31 | 32 | case CHANGE_FIREBASE_USER_PASSWORD: 33 | return changePassword(action.newPassword); 34 | 35 | case FIREBASE_PASSWORD_RESET_EMAIL: 36 | return resetPasswordEmail(action.email); 37 | 38 | case LOGIN_WITH_PROVIDER_FIREBASE: 39 | return loginWithProvider(action.provider); 40 | 41 | default: 42 | return state; 43 | 44 | } 45 | } 46 | 47 | function loginWithProvider(provider) { 48 | FireBaseTools.loginWithProvider(provider); 49 | } 50 | 51 | function registerUser(user) { 52 | FireBaseTools.registerUser(user); 53 | } 54 | 55 | function loginUser(user) { 56 | FireBaseTools.loginUser(user); 57 | } 58 | 59 | function fetchUser() { 60 | FireBaseTools.fetchUser(); 61 | } 62 | 63 | function updateUser(user) { 64 | FireBaseTools.updateUserProfile(user); 65 | } 66 | 67 | function changePassword(newPassword) { 68 | FireBaseTools.changePassword(newPassword); 69 | } 70 | 71 | function resetPasswordEmail(email) { 72 | FireBaseTools.resetPasswordEmail(email); 73 | } 74 | 75 | function logoutUser(user) { 76 | FireBaseTools.logoutUser(user); 77 | } 78 | -------------------------------------------------------------------------------- /src/app/components/user/change_password.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | import { changePassword } from '../../actions/firebase_actions'; 5 | 6 | class ChangePassword extends Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | this.onFormSubmit = this.onFormSubmit.bind(this); 11 | this.state = { 12 | message: '', 13 | }; 14 | } 15 | 16 | onFormSubmit(event) { 17 | event.preventDefault(); 18 | let password = this.refs.password.value; 19 | let repeatPassword = this.refs.repeatPassword.value; 20 | if (password !== repeatPassword) { 21 | this.setState({ 22 | message: 'Please password must match!', 23 | }); 24 | } else { 25 | this.props.changePassword(password).then((data) => { 26 | if (data.payload.errorCode) 27 | this.setState({ message: data.payload.errorMessage }); 28 | else 29 | this.setState({ message: 'Password was changed!' }); 30 | }); 31 | } 32 | } 33 | 34 | render() { 35 | return ( 36 |
37 |

Change Password

38 |
{this.state.message}
39 |
40 | 41 | 44 |
45 |
46 | 47 | 50 | 51 |
52 | 53 |
54 | ); 55 | } 56 | 57 | } 58 | 59 | 60 | function mapDispatchToProps(dispatch) { 61 | return bindActionCreators({ changePassword }, dispatch); 62 | } 63 | 64 | function mapStateToProps(state) { 65 | return { currentUser: state.currentUser }; 66 | } 67 | 68 | export default connect(mapStateToProps, mapDispatchToProps)(ChangePassword); 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PROJECT IS DEPRECATED IT'S NOT MAINTAINED ANYMORE! 2 | 3 | 4 | # React, Redux Firebase Boilerplate 5 | 6 | 7 | [![Build Status](https://travis-ci.org/awwong1/react-redux-firebase-boilerplate.svg?branch=master)](https://travis-ci.org/awwong1/react-redux-firebase-boilerplate) 8 | 9 | > [Firebase](https://www.firebase.com) is a powerful platform for your mobile and web applications that lets you build apps fast without managing servers. Firebase gives you the tools and infrastructure to build better apps and grow successful businesses. 10 | 11 | > [React](https://www.firebase.com) A javascript library for building user interfaces 12 | 13 | > [Redux](http://redux.js.org/) Redux is a predictable state container for JavaScript apps. 14 | 15 | ### Boilerplate Introduction 16 | Boilerplate is designed for quickly spin up your apps with Firebase, using bunch of awesome new front-end technologies includes webpack build system, hot reloading, routing & sass support. 17 | 18 | ## Features 19 | * [react](https://github.com/facebook/react) 20 | * [redux](https://github.com/rackt/redux) 21 | * [firebase](https://www.npmjs.com/package/firebase) 22 | * [react-router](https://github.com/rackt/react-router) 23 | * [redux-promise](https://github.com/acdlite/redux-promise) 24 | * [webpack](https://github.com/webpack/webpack) 25 | * [babel](https://github.com/babel/babel) 26 | 27 | Quick Start 28 | ----------- 29 | 30 | ```shell 31 | $ git clone https://github.com/btomashvili/react-redux-firebase-boilerplate.git 32 | $ cd react-redux-firebase-boilerplate 33 | $ npm install 34 | $ npm run dev 35 | ``` 36 | 37 | Firebase settings 38 | -------- 39 | First you need to create your firebase application to fetch settings for boilerplate. For more information how to add your web app check this [resource](https://firebase.google.com/docs/web/setup). After it copy your settings from firebase and fill config.js 40 | 41 | ```javascript 42 | module.exports = { 43 | 44 | FIREBASE_CONFIG: { 45 | 46 | apiKey: "", 47 | authDomain: "", 48 | databaseURL: "", 49 | storageBucket: "", 50 | 51 | } 52 | } 53 | ``` 54 | 55 | Commands 56 | -------- 57 | 58 | |Script|Description| 59 | |---|---| 60 | |`npm run dev`| Run development server with webpack-dev-server @ `localhost:3000`| 61 | |`npm run build`| Test, and build the application to `./dist`| 62 | |`npm start`| Start production ready app with pm2 from `./dist` @ `localhost:8080`| 63 | |`npm run lint`| Run ESLint on `./src`| 64 | 65 | 66 | What it looks like 67 | 68 | ### DEMO 69 | [https://react-redux-firebase-d6283.firebaseapp.com/](https://react-redux-firebase-d6283.firebaseapp.com/) 70 | 71 | -------- 72 | 73 | ![screen](https://www.dropbox.com/s/csufxlitjme8p3q/react_redux_firebase.gif?raw=1 "react_redux_firebase_boilerplate") 74 | -------------------------------------------------------------------------------- /webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const Webpack = require('webpack'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | module.exports = (options) => { 7 | const ExtractSASS = new ExtractTextPlugin(`/styles/${options.cssFileName}`); 8 | 9 | const webpackConfig = { 10 | devtool: options.devtool, 11 | entry: [ 12 | `webpack-dev-server/client?http://localhost:${+options.port}`, 13 | 'webpack/hot/dev-server', 14 | Path.join(__dirname, '../src/app/index'), 15 | ], 16 | output: { 17 | path: Path.join(__dirname, '../dist'), 18 | filename: `/scripts/${options.jsFileName}`, 19 | }, 20 | resolve: { 21 | extensions: ['', '.js', '.jsx'], 22 | }, 23 | module: { 24 | loaders: [ 25 | {test: /.jsx?$/, include: Path.join(__dirname, '../src/app'), loader: 'babel',}, 26 | {test: /\.jsx?$/, exclude: /(node_modules|bower_components)/, loader: 'babel'}, 27 | {test: /\.css$/, loader: 'style-loader!css-loader'}, 28 | {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file"}, 29 | {test: /\.(woff|woff2)$/, loader: "url?prefix=font/&limit=5000"}, 30 | {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream"}, 31 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml"} 32 | ], 33 | }, 34 | plugins: [ 35 | new Webpack.DefinePlugin({ 36 | 'process.env': { 37 | NODE_ENV: JSON.stringify(options.isProduction ? 'production' : 'development'), 38 | }, 39 | }), 40 | new HtmlWebpackPlugin({ 41 | template: Path.join(__dirname, '../src/index.html'), 42 | }), 43 | ], 44 | }; 45 | 46 | if (options.isProduction) { 47 | webpackConfig.entry = [Path.join(__dirname, '../src/app/index')]; 48 | 49 | webpackConfig.plugins.push( 50 | new Webpack.optimize.OccurenceOrderPlugin(), 51 | new Webpack.optimize.UglifyJsPlugin({ 52 | compressor: { 53 | warnings: false, 54 | }, 55 | }), 56 | ExtractSASS 57 | ); 58 | 59 | webpackConfig.module.loaders.push({ 60 | test: /\.scss$/, 61 | loader: ExtractSASS.extract(['css', 'sass']), 62 | }); 63 | } else { 64 | webpackConfig.plugins.push( 65 | new Webpack.HotModuleReplacementPlugin() 66 | ); 67 | 68 | webpackConfig.module.loaders.push({ 69 | test: /\.scss$/, 70 | loaders: ['style', 'css', 'sass'], 71 | }); 72 | 73 | webpackConfig.devServer = { 74 | contentBase: Path.join(__dirname, '../'), 75 | hot: true, 76 | port: options.port, 77 | inline: true, 78 | progress: true, 79 | historyApiFallback: true, 80 | }; 81 | } 82 | 83 | return webpackConfig; 84 | }; 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-firebase-boilerplate", 3 | "version": "1.0.0", 4 | "description": "Simple boilerplate for Reactjs with Redux and Firebase", 5 | "main": "index.jsx", 6 | "repository": "git@github.com:btomashvili/react-redux-firebase-boilerplate.git", 7 | "scripts": { 8 | "dev": "./node_modules/.bin/webpack-dev-server --config ./webpack/webpack-dev.config.js --watch --colors", 9 | "build": "rm -rf dist && ./node_modules/.bin/webpack --config ./webpack/webpack-prod.config.js --colors", 10 | "start": "PORT=8080 pm2 start ./www.js", 11 | "test": "./node_modules/.bin/eslint src && ./node_modules/.bin/mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test", 12 | "test:watch": "npm run test -- --watch", 13 | "lint": "./node_modules/.bin/eslint src" 14 | }, 15 | "author": "Beka Tomashvili", 16 | "contributors": [ 17 | { 18 | "name": "Alexander Wong", 19 | "email": "admin@alexander-wong.com", 20 | "url": "https://www.alexander-wong.com/" 21 | } 22 | ], 23 | "license": "MIT", 24 | "keywords": [ 25 | "ReactJS", 26 | "Redux", 27 | "Firebase", 28 | "React hot loader", 29 | "React Router", 30 | "ESLint" 31 | ], 32 | "devDependencies": { 33 | "babel-core": "^6.2.1", 34 | "babel-loader": "^6.2.0", 35 | "babel-preset-es2015": "^6.1.18", 36 | "babel-preset-react": "^6.1.18", 37 | "babel-preset-stage-1": "^6.5.0", 38 | "chai": "^3.5.0", 39 | "chai-jquery": "^2.0.0", 40 | "css-loader": "^0.23.1", 41 | "eslint": "^3.6.1", 42 | "eslint-config-airbnb": "^12.0.0", 43 | "eslint-plugin-import": "^1.16.0", 44 | "eslint-plugin-jsx-a11y": "^2.2.2", 45 | "eslint-plugin-react": "^6.3.0", 46 | "extract-text-webpack-plugin": "^1.0.1", 47 | "file-loader": "^0.9.0", 48 | "html-webpack-plugin": "^2.21.0", 49 | "imports-loader": "^0.6.5", 50 | "jquery": "^3.1.0", 51 | "jsdom": "^8.1.0", 52 | "mocha": "^2.4.5", 53 | "node-sass": "^3.8.0", 54 | "react-addons-test-utils": "^0.14.8", 55 | "react-hot-loader": "^1.3.0", 56 | "sass-loader": "^4.0.0", 57 | "style-loader": "^0.13.1", 58 | "url-loader": "^0.5.7", 59 | "webpack": "^1.12.9", 60 | "webpack-dev-server": "^1.14.0" 61 | }, 62 | "dependencies": { 63 | "bootstrap-social": "^5.0.0", 64 | "eslint-config-airbnb": "^12.0.0", 65 | "eslint-plugin-import": "^2.0.0", 66 | "express": "^4.14.0", 67 | "firebase": "^3.0.5", 68 | "font-awesome": "^4.6.3", 69 | "lodash": "^3.10.1", 70 | "react": "^0.14.8", 71 | "react-dom": "^0.14.3", 72 | "react-redux": "^4.0.0", 73 | "react-router": "^2.0.0-rc5", 74 | "redux": "^3.0.4", 75 | "redux-logger": "^3.0.6", 76 | "redux-promise": "^0.5.3", 77 | "redux-thunk": "^2.1.0", 78 | "reduxsauce": "^0.5.0", 79 | "seamless-immutable": "^7.1.2" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app/components/user/profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | import firebase from '../../utils/firebase'; 5 | 6 | 7 | import { fetchUser, updateUser } from '../../actions/firebase_actions'; 8 | import Loading from '../helpers/loading'; 9 | import ChangePassword from './change_password'; 10 | 11 | class UserProfile extends Component { 12 | 13 | constructor(props) { 14 | super(props); 15 | this.props.fetchUser(); 16 | this.state = { 17 | message: '', 18 | }; 19 | this.onFormSubmit = this.onFormSubmit.bind(this); 20 | } 21 | 22 | onFormSubmit(event) { 23 | event.preventDefault(); 24 | const email = this.refs.email.value; 25 | const displayName = this.refs.displayName.value; 26 | this.props.updateUser({ email, displayName }).then((data) => { 27 | if (data.payload.errorCode) { 28 | this.setState({ message: data.payload.errorMessage }); 29 | } else { 30 | this.setState({ 31 | message: 'Updated successfuly!', 32 | }); 33 | } 34 | } 35 | ); 36 | } 37 | 38 | render() { 39 | if (!this.props.currentUser) { 40 | return ; 41 | } 42 | 43 | return ( 44 |
45 |
46 |

User Profile Page

47 |

{this.state.message}

48 |
49 |
50 | 51 | 55 |
56 |
57 | 58 | 63 |
64 | 65 |
66 | 67 |
68 | ); 69 | } 70 | 71 | } 72 | 73 | 74 | function mapDispatchToProps(dispatch) { 75 | return bindActionCreators({ fetchUser, updateUser }, dispatch); 76 | } 77 | 78 | 79 | function mapStateToProps(state) { 80 | return { currentUser: state.currentUser }; 81 | } 82 | 83 | 84 | export default connect(mapStateToProps, mapDispatchToProps)(UserProfile); 85 | -------------------------------------------------------------------------------- /src/app/components/user/register.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { browserHistory } from 'react-router'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import { registerUser } from '../../actions/firebase_actions'; 6 | 7 | class UserRegister extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.onFormSubmit = this.onFormSubmit.bind(this); 11 | this.state = { 12 | message: '', 13 | }; 14 | } 15 | 16 | onFormSubmit(event) { 17 | event.preventDefault(); 18 | 19 | const email = this.refs.email.value; 20 | const password = this.refs.password.value; 21 | this.props.registerUser({ email, password }).then((data) => { 22 | if (data.payload.errorCode) { 23 | this.setState({ message: data.payload.errorMessage }) 24 | ; 25 | } else { 26 | browserHistory.push('/profile'); 27 | } 28 | } 29 | ); 30 | } 31 | 32 | render() { 33 | return ( 34 | 78 | ); 79 | } 80 | 81 | } 82 | 83 | function mapDispatchToProps(dispatch) { 84 | return bindActionCreators({ 85 | registerUser, 86 | }, dispatch); 87 | } 88 | 89 | function mapStateToProps(state) { 90 | return { currentUser: state.currentUser }; 91 | } 92 | 93 | export default connect(mapStateToProps, mapDispatchToProps)(UserRegister); 94 | -------------------------------------------------------------------------------- /src/app/components/app.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import { fetchUser, logoutUser } from '../actions/firebase_actions'; 6 | import FireBaseTools from '../utils/firebase' 7 | 8 | console.log(FireBaseTools.getDatabaseReference); 9 | 10 | class App extends Component { 11 | 12 | constructor(props) { 13 | super(props); 14 | 15 | this.props.fetchUser(); 16 | } 17 | 18 | _logOut() { 19 | this.props.logoutUser().then((data) => { 20 | // reload props from reducer 21 | this.props.fetchUser(); 22 | }); 23 | } 24 | 25 | _read() { 26 | FireBaseTools.getDatabaseReference('/DongerMaster').once('value').then(function(snapshot) { 27 | console.log(snapshot.val()); 28 | }); 29 | } 30 | 31 | _write() { 32 | FireBaseTools.getDatabaseReference('/').set({DongerMaster: 'lul'}); 33 | } 34 | 35 | _renderUserMenu(currentUser) { 36 | // if current user exists and user id exists than make user navigation 37 | if (currentUser && currentUser.uid) { 38 | return ( 39 |
  • 40 | 45 |
      46 |
    • Profile
    • 47 |
    • 48 |
    • this._logOut}>Logout
    • 49 |
    50 |
  • 51 | ); 52 | } else { 53 | return [ 54 |
  • Login
  • , 55 |
  • Register
  • , 56 | ]; 57 | } 58 | } 59 | 60 | render() { 61 | return ( 62 |
    63 | 64 | 65 | 87 | 88 |
    89 | {this.props.children} 90 |
    91 |
    92 | ); 93 | } 94 | } 95 | 96 | function mapDispatchToProps(dispatch) { 97 | return bindActionCreators({ fetchUser, logoutUser }, dispatch); 98 | } 99 | 100 | 101 | function mapStateToProps(state) { 102 | return { currentUser: state.currentUser }; 103 | } 104 | 105 | 106 | export default connect(mapStateToProps, mapDispatchToProps)(App); 107 | -------------------------------------------------------------------------------- /src/app/components/user/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import { loginUser, fetchUser, loginWithProvider } from '../../actions/firebase_actions'; 6 | 7 | 8 | class UserLogin extends Component { 9 | 10 | constructor(props) { 11 | super(props); 12 | this.onFormSubmit = this.onFormSubmit.bind(this); 13 | this.loginWithProvider = this.loginWithProvider.bind(this); 14 | this.state = { 15 | message: '', 16 | }; 17 | } 18 | 19 | onFormSubmit(event) { 20 | event.preventDefault(); 21 | 22 | const email = this.refs.email.value; 23 | const password = this.refs.password.value; 24 | this.props.loginUser({ email, password }).then((data) => { 25 | if (data.payload.errorCode) { 26 | this.setState({ message: data.payload.errorMessage }); 27 | } else { 28 | browserHistory.push('/profile'); 29 | } 30 | } 31 | ); 32 | } 33 | 34 | loginWithProvider(provider) { 35 | this.props.loginWithProvider(provider).then((data) => { 36 | if (data.payload.errorCode) { 37 | this.setState({ message: data.payload.errorMessage }); 38 | } else { 39 | browserHistory.push('/profile'); 40 | } 41 | }); 42 | } 43 | 44 | render() { 45 | return ( 46 | 97 | 98 | ); 99 | } 100 | 101 | } 102 | 103 | function mapDispatchToProps(dispatch) { 104 | return bindActionCreators({ 105 | loginUser, 106 | fetchUser, 107 | loginWithProvider, 108 | }, dispatch); 109 | } 110 | 111 | function mapStateToProps(state) { 112 | return { currentUser: state.currentUser }; 113 | } 114 | 115 | export default connect(mapStateToProps, mapDispatchToProps)(UserLogin); 116 | -------------------------------------------------------------------------------- /src/app/utils/firebase.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase'; 2 | import { FIREBASE_CONFIG } from '../config'; 3 | 4 | export const firebaseApp = firebase.initializeApp(FIREBASE_CONFIG); 5 | export const firebaseAuth = firebaseApp.auth(); 6 | export const firebaseDb = firebaseApp.database(); 7 | 8 | const FireBaseTools = { 9 | 10 | /** 11 | * Return an instance of a firebase auth provider based on the provider string. 12 | * 13 | * @param provider 14 | * @returns {firebase.auth.AuthProvider} 15 | */ 16 | getProvider: (provider) => { 17 | switch (provider) { 18 | case 'email': 19 | return new firebase.auth.EmailAuthProvider(); 20 | case 'facebook': 21 | return new firebase.auth.FacebookAuthProvider(); 22 | case 'github': 23 | return new firebase.auth.GithubAuthProvider(); 24 | case 'google': 25 | return new firebase.auth.GoogleAuthProvider(); 26 | case 'twitter': 27 | return new firebase.auth.TwitterAuthProvider(); 28 | default: 29 | throw new Error('Provider is not supported!!!'); 30 | } 31 | }, 32 | 33 | /** 34 | * Login with provider => p is provider "email", "facebook", "github", "google", or "twitter" 35 | * Uses Popup therefore provider must be an OAuth provider. EmailAuthProvider will throw an error 36 | * 37 | * @returns {any|!firebase.Thenable.<*>|firebase.Thenable} 38 | */ 39 | loginWithProvider: (p) => { 40 | const provider = FireBaseTools.getProvider(p); 41 | return firebaseAuth.signInWithPopup(provider).then(firebaseAuth.currentUser).catch(error => ({ 42 | errorCode: error.code, 43 | errorMessage: error.message, 44 | })); 45 | }, 46 | 47 | /** 48 | * Register a user with email and password 49 | * 50 | * @param user 51 | * @returns {any|!firebase.Thenable.<*>|firebase.Thenable} 52 | */ 53 | registerUser: user => firebaseAuth.createUserWithEmailAndPassword(user.email, user.password) 54 | .then(userInfo => userInfo) 55 | .catch(error => ({ 56 | errorCode: error.code, 57 | errorMessage: error.message, 58 | })), 59 | 60 | /** 61 | * Sign the user out 62 | * 63 | * @returns {!firebase.Promise.<*>|firebase.Thenable|firebase.Promise|!firebase.Thenable.<*>} 64 | */ 65 | logoutUser: () => firebaseAuth.signOut().then(() => ({ 66 | success: 1, 67 | message: 'logout', 68 | })), 69 | 70 | /** 71 | * Retrieve the current user (Promise) 72 | * @returns {Promise} 73 | */ 74 | fetchUser: () => new Promise((resolve, reject) => { 75 | const unsub = firebaseAuth.onAuthStateChanged((user) => { 76 | unsub(); 77 | resolve(user); 78 | }, (error) => { 79 | reject(error); 80 | }); 81 | }), 82 | 83 | /** 84 | * Log the user in using email and password 85 | * 86 | * @param user 87 | * @returns {any|!firebase.Thenable.<*>|firebase.Thenable} 88 | */ 89 | loginUser: user => firebaseAuth.signInWithEmailAndPassword(user.email, user.password) 90 | .then(userInfo => userInfo) 91 | .catch(error => ({ 92 | errorCode: error.code, 93 | errorMessage: error.message, 94 | })), 95 | 96 | /** 97 | * Update a user's profile data 98 | * 99 | * @param u 100 | * @returns {!firebase.Promise.<*>|firebase.Thenable|firebase.Promise|!firebase.Thenable.<*>} 101 | */ 102 | updateUserProfile: u => firebaseAuth.currentUser.updateProfile(u).then(() => firebaseAuth.currentUser, error => ({ 103 | errorCode: error.code, 104 | errorMessage: error.message, 105 | })), 106 | 107 | /** 108 | * Reset the password given the specified email 109 | * 110 | * @param email {string} 111 | * @returns {!firebase.Promise.<*>|firebase.Thenable|firebase.Promise|!firebase.Thenable.<*>} 112 | */ 113 | resetPasswordEmail: email => firebaseAuth.sendPasswordResetEmail(email).then(() => ({ 114 | message: 'Email sent', 115 | }), error => ({ 116 | errorCode: error.code, 117 | errorMessage: error.message, 118 | })), 119 | 120 | /** 121 | * Update the user's password with the given password 122 | * 123 | * @param newPassword {string} 124 | * @returns {!firebase.Promise.<*>|firebase.Thenable|firebase.Promise|!firebase.Thenable.<*>} 125 | */ 126 | changePassword: newPassword => firebaseAuth.currentUser.updatePassword(newPassword).then(user => user, error => ({ 127 | errorCode: error.code, 128 | errorMessage: error.message, 129 | })), 130 | 131 | /** 132 | * Send an account email verification message for the currently logged in user 133 | * 134 | * @returns {!firebase.Promise.<*>|firebase.Thenable|firebase.Promise|!firebase.Thenable.<*>} 135 | */ 136 | sendEmailVerification: () => firebaseAuth.currentUser.sendEmailVerification().then(() => ({ 137 | message: 'Email sent', 138 | }), error => ({ 139 | errorCode: error.code, 140 | errorMessage: error.message, 141 | })), 142 | 143 | /** 144 | * Get the firebase database reference. 145 | * 146 | * @param path {!string|string} 147 | * @returns {!firebase.database.Reference|firebase.database.Reference} 148 | */ 149 | getDatabaseReference: path => firebaseDb.ref(path), 150 | }; 151 | 152 | export default FireBaseTools; 153 | --------------------------------------------------------------------------------