├── .flowconfig ├── .gitignore ├── .jshintrc ├── .babelrc ├── src ├── components │ ├── List │ │ └── index.js │ ├── ListItem │ │ └── index.js │ ├── Nav │ │ └── index.js │ ├── Title │ │ └── index.js │ ├── Header │ │ ├── LogoutLink.js │ │ ├── LoginLink.js │ │ ├── index.js │ │ └── Nav.js │ ├── J │ │ └── index.js │ ├── SlickButton │ │ └── index.js │ ├── requireAuth.js │ └── Button │ │ └── index.js ├── config │ ├── index.js │ ├── firebase.js │ └── constants.js ├── index.test.js ├── store │ ├── configure.js │ ├── configure.prod.js │ └── configure.dev.js ├── reducers │ ├── routesPermissions.js │ ├── initialState.js │ ├── user.js │ ├── index.js │ ├── ajaxStatus.js │ ├── notifications.js │ └── auth.js ├── actions │ ├── ajaxStatus.js │ ├── notifications.js │ ├── user.js │ └── auth.js ├── containers │ ├── NotFoundPage │ │ └── index.js │ ├── AccountPage │ │ └── index.js │ ├── HomePage │ │ └── index.js │ ├── AboutPage │ │ └── index.js │ ├── App │ │ └── index.js │ ├── Notifications │ │ └── index.js │ ├── RepoList │ │ └── index.js │ └── Layout.js ├── selectors │ └── index.js ├── index.html ├── routes.js ├── styles │ └── global.js ├── api │ └── firebase.js ├── index.js └── logo.svg ├── esdoc.json ├── tools ├── startMessage.js ├── srcServer.js └── testSetup.js ├── .editorconfig ├── .vscode └── launch.json ├── LICENSE ├── webpack.config.dev.js ├── .eslintrc ├── package.json └── README.md /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | dist 5 | .idea 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "newcap": false 6 | } 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["react-hot-loader/babel"] 4 | } 5 | -------------------------------------------------------------------------------- /src/components/List/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.ul` 4 | list-style: none; 5 | `; 6 | -------------------------------------------------------------------------------- /src/components/ListItem/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.li` 4 | margin: 0; 5 | `; 6 | -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs", 4 | "plugins": [ 5 | {"name": "esdoc-es7-plugin"} 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | import firebase from './firebase'; 2 | import constants from './constants'; 3 | 4 | export const firebaseConfig = firebase; 5 | -------------------------------------------------------------------------------- /tools/startMessage.js: -------------------------------------------------------------------------------- 1 | import colors from 'colors'; 2 | 3 | /*eslint-disable no-console */ 4 | 5 | console.log('Starting app in dev mode...'.green); 6 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | describe('Our first test', () => { 4 | it('should pass', () => { 5 | expect(true).toEqual(true); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/store/configure.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configure.prod'); 3 | } else { 4 | module.exports = require('./configure.dev'); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Nav/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.nav` 4 | > * { 5 | margin: 0 5px 5px 0; 6 | } 7 | > *:last-child { 8 | margin-right: inherit; 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /src/reducers/routesPermissions.js: -------------------------------------------------------------------------------- 1 | import initialState from './initialState'; 2 | 3 | export default function routesPermissions(state = initialState.auth, action) { 4 | switch (action.type) { 5 | default: 6 | return state; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/actions/ajaxStatus.js: -------------------------------------------------------------------------------- 1 | import * as types from '../config/constants'; 2 | 3 | export function beginAjaxCall() { 4 | return { type: types.BEGIN_AJAX_CALL }; 5 | } 6 | 7 | export function ajaxCallError() { 8 | return { type: types.AJAX_CALL_ERROR }; 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /src/components/Title/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Title = styled.h2` 4 | border-bottom: 1px solid rgba(0, 0, 0, .1); 5 | margin: 35px 0 15px 0; 6 | &:first-child { 7 | margin-top: 0; 8 | } 9 | `; 10 | 11 | export default Title; 12 | -------------------------------------------------------------------------------- /src/config/firebase.js: -------------------------------------------------------------------------------- 1 | export default { 2 | apiKey: 'PUT_FIREBASE_API_KEY_HERE', 3 | authDomain: 'PUT_FIREBASE_AUTH_DOMAIN_HERE', 4 | databaseURL: 'PUT_FIREBASE_DATABASE_URL_HERE', 5 | storageBucket: 'PUT_FIREBASE_STORAGE_BUCKET_HERE', 6 | messagingSenderId: 'PUT_FIREBASE_MESSAGING_SENDER_ID_HERE', 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/Header/LogoutLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '../Button'; 3 | 4 | const LogoutLink = ({ signOut }) => { 5 | return ; 6 | }; 7 | 8 | LogoutLink.propTypes = { 9 | signOut: React.PropTypes.func.isRequired 10 | }; 11 | 12 | export default LogoutLink; 13 | -------------------------------------------------------------------------------- /src/store/configure.prod.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import rootReducer from '../reducers'; 3 | import thunk from 'redux-thunk'; 4 | 5 | export default function configureStore(initialState) { 6 | return createStore( 7 | rootReducer, 8 | initialState, 9 | applyMiddleware(thunk) 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/actions/notifications.js: -------------------------------------------------------------------------------- 1 | export const DISMISS_NOTIFICATION = 'DISMISS_NOTIFICATION'; 2 | export const PUSH_NOTIFICATION = 'PUSH_NOTIFICATION'; 3 | 4 | export function dismiss (notification) { 5 | return { 6 | type: DISMISS_NOTIFICATION, 7 | notification 8 | }; 9 | } 10 | 11 | export function notify (message) { 12 | return { 13 | type: PUSH_NOTIFICATION, 14 | message 15 | }; 16 | } -------------------------------------------------------------------------------- /src/reducers/initialState.js: -------------------------------------------------------------------------------- 1 | export default { 2 | ajaxCallsInProgress: 0, 3 | auth: { 4 | currentUserUID: null, 5 | initialized: false, 6 | isLogged: false, 7 | }, 8 | routesPermissions: { 9 | requireAuth: [ 10 | '/admin', 11 | ], 12 | routesRequireAdmin: [ 13 | '/admin', 14 | ], 15 | }, 16 | routing: {}, 17 | user: { 18 | isAdmin: undefined, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/containers/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import styled from 'styled-components'; 4 | 5 | import { Row, Column } from 'hedron'; 6 | 7 | const HomePage = () => { 8 | return ( 9 | 10 | 11 |

Page Not Found

12 |
13 |
14 | ); 15 | }; 16 | 17 | export default HomePage; 18 | -------------------------------------------------------------------------------- /src/selectors/index.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | const getAlertsBase = state => state.get('alerts'); 4 | 5 | export const getAlerts = createSelector([getAlertsBase], (base) => { 6 | let arr = []; 7 | 8 | base.forEach(item => { 9 | arr.push({ 10 | message: item.get('message'), 11 | title: item.get('title'), 12 | key: item.get('key'), 13 | dismissAfter: 5000 14 | }); 15 | }); 16 | 17 | return arr; 18 | }); -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React Hot Redux Firebase Starter 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/Header/LoginLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import FontAwesome from 'react-fontawesome'; 4 | import Button from '../Button'; 5 | 6 | const LoginLink = (props) => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | LoginLink.propTypes = { 15 | action: React.PropTypes.func.isRequired, 16 | }; 17 | 18 | export default LoginLink; 19 | -------------------------------------------------------------------------------- /src/reducers/user.js: -------------------------------------------------------------------------------- 1 | import * as types from '../config/constants'; 2 | import initialState from './initialState'; 3 | 4 | export default function userReducer(state = initialState.user, action) { 5 | switch (action.type) { 6 | case types.USER_LOADED_SUCCESS: 7 | return Object.assign({}, state, action.user); 8 | case types.PROVIDER_LOGIN_SUCCESS: 9 | return Object.assign({}, state, action.token); 10 | case types.AUTH_LOGGED_OUT_SUCCESS: 11 | return initialState.user; 12 | default: 13 | return state; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/containers/AccountPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { Row, Column } from 'hedron'; 4 | 5 | import checkAuth from '../../components/requireAuth'; 6 | import Title from '../../components/Title'; 7 | 8 | const AccountPage = () => { 9 | return ( 10 | 11 | 12 | You will only see this page if you are logged in 13 |

GitHub Token: CLICK TO SHOW

14 |
15 |
16 | ); 17 | }; 18 | 19 | export default checkAuth(AccountPage); 20 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import ajaxCallsInProgress from './ajaxStatus'; 5 | import auth from './auth'; 6 | import notifications from './notifications'; 7 | import routesPermissions from './routesPermissions'; 8 | import user from './user'; 9 | 10 | 11 | const rootReducer = combineReducers({ 12 | ajaxCallsInProgress, 13 | auth, 14 | notifications, 15 | routesPermissions, 16 | routing: routerReducer, 17 | user, 18 | }); 19 | 20 | export default rootReducer; 21 | -------------------------------------------------------------------------------- /src/containers/HomePage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Link } from 'react-router'; 4 | import { Row, Column } from 'hedron'; 5 | 6 | import J from '../../components/J'; 7 | import Title from '../../components/Title'; 8 | 9 | const HomePage = () => { 10 | return ( 11 | 12 | 13 | Welcome to the React Firebase Boilerplate! 14 |

Time to start putting in your own content

15 |
16 |
17 | ); 18 | }; 19 | 20 | export default HomePage; 21 | -------------------------------------------------------------------------------- /src/components/J/index.js: -------------------------------------------------------------------------------- 1 | import emojione from 'emojione'; 2 | import React from 'react'; 3 | import styled from 'styled-components'; 4 | 5 | const Emoji = styled.img` 6 | width: ${props => props.s ? props.s : 22}px; 7 | `; 8 | 9 | const J = ({ id, s }) => { 10 | const shortname = `:${id}:`; 11 | const unicode = emojione.emojioneList[shortname].unicode; 12 | return ; 13 | }; 14 | 15 | J.propTypes = { 16 | id: React.PropTypes.string, 17 | s: React.PropTypes.number, 18 | }; 19 | 20 | export default J; -------------------------------------------------------------------------------- /src/reducers/ajaxStatus.js: -------------------------------------------------------------------------------- 1 | import * as types from '../config/constants'; 2 | import initialState from './initialState'; 3 | 4 | function actionTypeEndsInSuccess(type) { 5 | return type.substring(type.length - 8) == '_SUCCESS'; 6 | } 7 | 8 | export default function ajaxStatusReducer(state = initialState.ajaxCallsInProgress, action) { 9 | if (action.type == types.BEGIN_AJAX_CALL) { 10 | return state + 1; 11 | } else if (action.type == types.AJAX_CALL_ERROR || 12 | actionTypeEndsInSuccess(action.type)) { 13 | return state - 1; 14 | } 15 | 16 | 17 | return state; 18 | } 19 | -------------------------------------------------------------------------------- /src/reducers/notifications.js: -------------------------------------------------------------------------------- 1 | import { DISMISS_NOTIFICATION, PUSH_NOTIFICATION } from '../actions/notifications'; 2 | import { OrderedSet } from 'immutable'; 3 | 4 | let _currentKey = 0; 5 | export default function notifications (state = OrderedSet(), action) { 6 | 7 | switch (action.type) { 8 | case DISMISS_NOTIFICATION: 9 | return OrderedSet(state).delete(action.notification); 10 | 11 | case PUSH_NOTIFICATION: 12 | return OrderedSet(state).add({ 13 | message: action.message, 14 | key: _currentKey++, 15 | }); 16 | } 17 | 18 | return OrderedSet(state); 19 | } 20 | -------------------------------------------------------------------------------- /src/store/configure.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import { routerMiddleware } from 'react-router-redux'; 3 | import rootReducer from '../reducers'; 4 | import reduxImmutableStateInvariant from 'redux-immutable-state-invariant'; 5 | import thunk from 'redux-thunk'; 6 | import { browserHistory } from "react-router"; 7 | 8 | export default function configureStore(initialState) { 9 | return createStore( 10 | rootReducer, 11 | initialState, 12 | compose( 13 | applyMiddleware(thunk, reduxImmutableStateInvariant(), routerMiddleware(browserHistory)), 14 | window.devToolsExtension ? window.devToolsExtension() : f => f 15 | ) 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceRoot}/lint:watch", 12 | "cwd": "${workspaceRoot}" 13 | }, 14 | { 15 | "type": "node", 16 | "request": "attach", 17 | "name": "Attach to Process", 18 | "port": 5858 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /src/containers/AboutPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { Row, Column } from 'hedron'; 4 | 5 | import Title from '../../components/Title'; 6 | 7 | const AboutPage = () => { 8 | return ( 9 | 10 | 11 | About 12 |

Created by @garetmckinley

13 | Links 14 |
    15 |
  • 16 | GitHub Repo 17 |
  • 18 |
19 |
20 |
21 | ); 22 | }; 23 | 24 | export default AboutPage; 25 | -------------------------------------------------------------------------------- /src/reducers/auth.js: -------------------------------------------------------------------------------- 1 | import * as types from '../config/constants'; 2 | import initialState from './initialState'; 3 | 4 | export default function authReducer(state = initialState.auth, action) { 5 | switch (action.type) { 6 | case types.AUTH_INITIALIZATION_DONE: 7 | return Object.assign({}, state, { initialized: true }); 8 | 9 | case types.AUTH_LOGGED_IN_SUCCESS: 10 | return Object.assign({}, state, { 11 | isLogged: true, 12 | currentUserUID: action.userUID 13 | }); 14 | 15 | case types.AUTH_LOGGED_OUT_SUCCESS: 16 | return Object.assign({}, state, { 17 | isLogged: false, 18 | currentUserUID: null 19 | }); 20 | default: 21 | return state; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | 4 | import AboutPage from './containers/AboutPage'; 5 | import HomePage from './containers/HomePage'; 6 | import NotFound from './containers/NotFoundPage'; 7 | import Layout from './containers/Layout'; 8 | import ProtectedPage from './containers/AccountPage'; 9 | 10 | export default function Routes(store) { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /tools/srcServer.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import webpack from 'webpack'; 3 | import path from 'path'; 4 | import config from '../webpack.config.dev'; 5 | import open from 'open'; 6 | 7 | /* eslint-disable no-console */ 8 | 9 | const port = 3000; 10 | const app = express(); 11 | const compiler = webpack(config); 12 | 13 | app.use(require('webpack-dev-middleware')(compiler, { 14 | noInfo: true, 15 | publicPath: config.output.publicPath 16 | })); 17 | 18 | app.use(require('webpack-hot-middleware')(compiler)); 19 | 20 | app.get('*', function(req, res) { 21 | res.sendFile(path.join( __dirname, '../src/index.html')); 22 | }); 23 | 24 | app.listen(port, function(err) { 25 | if (err) { 26 | console.log(err); 27 | } else { 28 | open(`http://localhost:${port}`); 29 | } 30 | }); 31 | 32 | export default app; 33 | -------------------------------------------------------------------------------- /src/containers/App/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Router } from 'react-router'; 3 | import routes from '../../routes'; 4 | 5 | // If you use React Router, make this component 6 | // render with your routes. Currently, 7 | // only synchronous routes are hot reloaded, and 8 | // you will see a warning from on every reload. 9 | // You can ignore this warning. For details, see: 10 | // https://github.com/reactjs/react-router/issues/2182 11 | 12 | class App extends Component { 13 | render() { 14 | const { history, store } = this.props; 15 | return ( 16 | 17 | ); 18 | } 19 | } 20 | 21 | App.propTypes = { 22 | history: React.PropTypes.object.isRequired, 23 | store: React.PropTypes.object.isRequired, 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /src/containers/Notifications/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { NotificationStack } from 'react-notification'; 4 | import { dismiss } from '../../actions/notifications'; 5 | 6 | class Notifications extends React.Component { 7 | render() { 8 | return ( 9 | this.props.dispatch(dismiss(notification))} 13 | /> 14 | ); 15 | } 16 | } 17 | 18 | Notifications.propTypes = { 19 | dispatch: React.PropTypes.func.isRequired, 20 | notifications: React.PropTypes.object.isRequired, 21 | }; 22 | 23 | function mapStateToProps(state, ownProps) { 24 | return { 25 | notifications: state.notifications 26 | }; 27 | } 28 | 29 | export default connect(mapStateToProps)(Notifications); 30 | -------------------------------------------------------------------------------- /src/containers/RepoList/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | 6 | class RepoList extends React.PureComponent { 7 | constructor(props, context) { 8 | super(props, context); 9 | } 10 | render() { 11 | const { actions, user, loading, ...props } = this.props; 12 | return ( 13 |
{user.username}
14 | ); 15 | } 16 | } 17 | 18 | RepoList.propTypes = { 19 | actions: React.PropTypes.object.isRequired, 20 | loading: React.PropTypes.bool.isRequired, 21 | user: React.PropTypes.object.isRequired, 22 | }; 23 | 24 | 25 | function mapStateToProps(state, ownProps) { 26 | return { 27 | loading: state.ajaxCallsInProgress > 0, 28 | user: state.user, 29 | }; 30 | } 31 | 32 | function mapDispatchToProps(dispatch) { 33 | return { 34 | actions: bindActionCreators({ }, dispatch) 35 | }; 36 | } 37 | 38 | export default connect(mapStateToProps, mapDispatchToProps)(RepoList); 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Garet McKinley 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 | -------------------------------------------------------------------------------- /src/components/SlickButton/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Link, browserHistory } from 'react-router'; 4 | 5 | const Wrapper = styled.span` 6 | display: inline-block; 7 | position: relative; 8 | overflow: hidden; 9 | `; 10 | 11 | const StyledButton = styled.button` 12 | padding: 4px 8px; 13 | background: transparent; 14 | border: 1px solid deepskyblue; 15 | border-radius: 3px; 16 | color: deepskyblue; 17 | font-size: 22px; 18 | font-weight: 200; 19 | letter-spacing: 1px; 20 | &::before { 21 | content: '${props => props.children}'; 22 | position: absolute; 23 | margin-top: -50px; 24 | } 25 | &:hover{ 26 | background: deepskyblue; 27 | color: white; 28 | } 29 | &:focus { 30 | outline: 2px solid deepskyblue; 31 | } 32 | `; 33 | 34 | function Button({ to, ...props }) { 35 | function navigate() { 36 | browserHistory.push(to); 37 | } 38 | return ( 39 | 40 | 41 | 42 | ); 43 | } 44 | 45 | Button.propTypes = { 46 | to: React.PropTypes.string, 47 | }; 48 | 49 | export default Button; 50 | -------------------------------------------------------------------------------- /src/components/requireAuth.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { notify } from '../actions/notifications'; 5 | 6 | import { store } from '../index'; 7 | 8 | export default function (ComposedComponent){ 9 | class Authentication extends Component { 10 | componentWillMount(){ 11 | if(!this.props.authenticated) { 12 | this.context.router.push('/'); 13 | store.dispatch(notify('You need to be logged to access this page')); 14 | } 15 | } 16 | componentWillUpdate(nextProps){ 17 | if(!nextProps.authenticated) { 18 | this.context.router.push('/'); 19 | store.dispatch(notify('You need to be logged to access this page')); 20 | } 21 | } 22 | render(){ 23 | return ; 24 | } 25 | } 26 | Authentication.contextTypes = { 27 | router : PropTypes.object 28 | }; 29 | Authentication.propTypes = { 30 | authenticated : PropTypes.bool, 31 | }; 32 | const mapStateToProps = (state) => ({ 33 | authenticated : state.auth.isLogged, 34 | }); 35 | return connect(mapStateToProps)(Authentication); 36 | } 37 | -------------------------------------------------------------------------------- /src/config/constants.js: -------------------------------------------------------------------------------- 1 | // ajax and loading actions 2 | export const BEGIN_AJAX_CALL = 'BEGIN_AJAX_CALL'; 3 | export const AJAX_CALL_ERROR = 'AJAX_CALL_ERROR'; 4 | 5 | // Auth actions 6 | export const AUTH_INITIALIZATION_DONE = 'AUTH_INITIALIZATION_DONE'; 7 | export const AUTH_LOGGED_IN_SUCCESS = 'AUTH_LOGGED_IN_SUCCESS'; 8 | export const AUTH_LOGGED_OUT_SUCCESS = 'AUTH_LOGGED_OUT_SUCCESS'; 9 | 10 | // User actions 11 | export const USER_CREATED_SUCCESS = 'USER_CREATED_SUCCESS'; 12 | export const USER_LOADED_SUCCESS = 'USER_LOADED_SUCCESS'; 13 | export const USER_IS_ADMIN_SUCCESS = 'USER_IS_ADMIN_SUCCESS'; 14 | export const PROVIDER_LOGIN_SUCCESS = 'PROVIDER_LOGIN_SUCCESS'; 15 | 16 | export const NOTIFICATION_ADDED_SUCCESS = 'NOTIFICATION_ADDED_SUCCESS'; 17 | export const NOTIFICATION_REMOVED_SUCCESS = 'NOTIFICATION_REMOVED_SUCCESS'; 18 | 19 | export default { 20 | BEGIN_AJAX_CALL, 21 | AJAX_CALL_ERROR, 22 | AUTH_INITIALIZATION_DONE, 23 | AUTH_LOGGED_IN_SUCCESS, 24 | AUTH_LOGGED_OUT_SUCCESS, 25 | USER_CREATED_SUCCESS, 26 | USER_LOADED_SUCCESS, 27 | USER_IS_ADMIN_SUCCESS, 28 | PROVIDER_LOGIN_SUCCESS, 29 | NOTIFICATION_ADDED_SUCCESS, 30 | NOTIFICATION_REMOVED_SUCCESS, 31 | }; -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | let path = require('path'); 2 | let webpack = require('webpack'); 3 | 4 | const config = { 5 | devtool: 'source-map', 6 | entry: [ 7 | 'react-hot-loader/patch', 8 | 'webpack-hot-middleware/client?reload=false', 9 | './src/index' 10 | ], 11 | output: { 12 | path: path.join(__dirname, 'dist'), 13 | filename: 'bundle.js', 14 | publicPath: '/static/' 15 | }, 16 | plugins: [ 17 | new webpack.HotModuleReplacementPlugin() 18 | ], 19 | module: { 20 | loaders: [ 21 | { test: /\.js$/, include: path.join(__dirname, 'src'), loaders: ['babel'] }, 22 | { test: /(\.css)$/, loaders: ['style', 'css'] }, 23 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 24 | { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 25 | { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 26 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 27 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" } 28 | ] 29 | } 30 | }; 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import emojione from 'emojione'; 2 | import React, { PropTypes } from 'react'; 3 | import ReactTooltip from 'react-tooltip'; 4 | import styled from 'styled-components'; 5 | import { bindActionCreators } from 'redux'; 6 | import { connect } from 'react-redux'; 7 | import { Link, IndexLink } from 'react-router'; 8 | import { Row, Column } from 'hedron'; 9 | 10 | import Button from '../Button'; 11 | import J from '../J'; 12 | import LoginLink from './LoginLink'; 13 | import LogoutLink from './LogoutLink'; 14 | import Nav from './Nav'; 15 | 16 | const PageHeader = styled.section` 17 | text-align: center; 18 | `; 19 | 20 | const svg = styled.img` 21 | width: 250px; 22 | `; 23 | 24 | const Header = (props) => { 25 | return ( 26 | 27 | 28 | 29 |

30 | React Firebase Boilerplate 31 |

32 |

The perfect starting point for react apps with firebase backends.

33 |