├── 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 |
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 |
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 |
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 | [](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 | 
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 |
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 | {currentUser.email}
44 |
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 |
--------------------------------------------------------------------------------