├── .env
├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.test.js
├── components
│ ├── Home.js
│ ├── Login.js
│ ├── Register.js
│ ├── index.js
│ └── protected
│ │ └── Dashboard.js
├── config
│ └── constants.js
├── helpers
│ └── auth.js
├── index.css
├── index.js
└── registerServiceWorker.js
└── yarn.lock
/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_FIREBASE_KEY='## your firebase info here ##'
2 | REACT_APP_AUTH_DOMAIN='## your firebase info here ##'
3 | REACT_APP_DATABASE_URL='## your firebase info here ##'
4 | REACT_APP_PROJECT_ID='## your firebase info here ##'
5 | REACT_APP_STORAGE_BUCKET='## your firebase info here ##'
6 | REACT_APP_MESSAGING_SENDER_ID='## your firebase info here ##'
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 |
4 | # testing
5 | coverage
6 |
7 | # production
8 | build
9 |
10 | # misc
11 | .DS_Store
12 | .env
13 | npm-debug.log
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Auth with React Router V4 and Firebase V3
2 | This is an example repo for authenticating with Firebase and React Router using Material-UI.
3 |
4 | *Using React 16.1.1, React Router 4, and Firebase 4.6.0*
5 |
6 | CRA was used to setup this project so you might wanna go to [CRA's docs](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md) to check how to setup other tools or just change configuration.
7 |
8 | ### [Live Demo](https://guilherme6191.github.io/react-router-firebase-auth-material-ui/#/)
9 |
10 | #### Features:
11 | * Protected Routes with React Router
12 | * Register new users with Firebase
13 | * Add new users to ```/users``` in your Firebase Firestore database
14 | * Login/Logout Functionality
15 | * Material UI
16 | * Bootstrap v4 for some utilities via CDN ** should be included via yarn/npm or removed
17 |
18 | #### Instructions:
19 | * Swap out the firebase config in ```config/constants``` with your own
20 | * ```npm install```
21 | * ```npm start```
22 | * Visit ```localhost:3000```
23 |
24 | #### Others:
25 | It's basically [react-router-firebase-auth](https://github.com/tylermcginnis/react-router-firebase-auth) with [Material-UI](https://github.com/callemall/material-ui) and [Firebase/Firestore](https://firebase.google.com/products/firestore/)
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-router-firebase-auth-material-ui",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://guilherme6191.github.io/react-router-firebase-auth-material-ui",
6 | "dependencies": {
7 | "firebase": "4.6.0",
8 | "gh-pages": "^1.1.0",
9 | "material-ui": "^0.19.4",
10 | "react": "^16.1.1",
11 | "react-dom": "^16.1.1",
12 | "react-router-dom": "^4.2.2",
13 | "react-scripts": "1.0.17"
14 | },
15 | "scripts": {
16 | "predeploy": "npm run build",
17 | "deploy": "gh-pages -d build",
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test --env=jsdom --browser",
21 | "eject": "react-scripts eject"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guilherme6191/react-router-firebase-auth-material-ui/2282273d10ffa483dd308bb3fef11855893e9278/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
17 |
18 |
20 |
22 | react-router-firebase-auth-material-ui
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
4 | import App from './components';
5 |
6 | it('renders without crashing', () => {
7 | const div = document.createElement('div');
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | div
13 | );
14 | });
15 |
--------------------------------------------------------------------------------
/src/components/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class Home extends Component {
4 | render() {
5 | return Home. Not Protected. Anyone can see this.
;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { login, resetPassword } from '../helpers/auth';
3 |
4 | import RaisedButton from 'material-ui/RaisedButton';
5 | import TextField from 'material-ui/TextField';
6 |
7 | function setErrorMsg(error) {
8 | return {
9 | loginMessage: error
10 | };
11 | }
12 |
13 | export default class Login extends Component {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | email: '',
18 | password: '',
19 | loginMessage: null
20 | };
21 | }
22 |
23 | handleSubmit = e => {
24 | e.preventDefault();
25 | login(this.state.email, this.state.password).catch(error => {
26 | this.setState(setErrorMsg('Invalid username/password.'));
27 | });
28 | };
29 | resetPassword = () => {
30 | resetPassword(this.state.email)
31 | .then(() =>
32 | this.setState(
33 | setErrorMsg(`Password reset email sent to ${this.state.email}.`)
34 | )
35 | )
36 | .catch(error => this.setState(setErrorMsg(`Email address not found.`)));
37 | };
38 | render() {
39 | return (
40 |
78 | );
79 | }
80 | }
81 |
82 | const raisedBtn = {
83 | margin: 15
84 | };
85 |
86 | const container = {
87 | textAlign: 'center'
88 | };
89 |
90 | const style = {
91 | raisedBtn,
92 | container
93 | };
94 |
--------------------------------------------------------------------------------
/src/components/Register.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { auth } from '../helpers/auth';
3 |
4 | import RaisedButton from 'material-ui/RaisedButton';
5 | import TextField from 'material-ui/TextField';
6 |
7 | function setErrorMsg(error) {
8 | return {
9 | registerError: error.message
10 | };
11 | }
12 |
13 | export default class Register extends Component {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | registerError: null,
18 | email: '',
19 | password: ''
20 | };
21 | }
22 |
23 | handleSubmit = e => {
24 | e.preventDefault();
25 | auth(this.state.email, this.state.password).catch(e =>
26 | this.setState(setErrorMsg(e))
27 | );
28 | };
29 | render() {
30 | return (
31 |
63 | );
64 | }
65 | }
66 |
67 | const raisedBtn = {
68 | margin: 15
69 | };
70 |
71 | const container = {
72 | textAlign: 'center'
73 | };
74 |
75 | const style = {
76 | raisedBtn,
77 | container
78 | };
79 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Route, HashRouter, Link, Redirect, Switch } from 'react-router-dom';
3 | import Login from './Login';
4 | import Register from './Register';
5 | import Home from './Home';
6 | import Dashboard from './protected/Dashboard';
7 | import { logout } from '../helpers/auth';
8 | import { firebaseAuth } from '../config/constants';
9 | import AppBar from 'material-ui/AppBar';
10 | import FlatButton from 'material-ui/FlatButton';
11 |
12 | function PrivateRoute({ component: Component, authed, ...rest }) {
13 | return (
14 |
17 | authed === true ? (
18 |
19 | ) : (
20 |
23 | )}
24 | />
25 | );
26 | }
27 |
28 | function PublicRoute({ component: Component, authed, ...rest }) {
29 | return (
30 |
33 | authed === false ? (
34 |
35 | ) : (
36 |
37 | )}
38 | />
39 | );
40 | }
41 |
42 | export default class App extends Component {
43 | state = {
44 | authed: false,
45 | loading: true
46 | };
47 | componentDidMount() {
48 | this.removeListener = firebaseAuth().onAuthStateChanged(user => {
49 | if (user) {
50 | this.setState({
51 | authed: true,
52 | loading: false
53 | });
54 | } else {
55 | this.setState({
56 | authed: false,
57 | loading: false
58 | });
59 | }
60 | });
61 | }
62 | componentWillUnmount() {
63 | this.removeListener();
64 | }
65 | render() {
66 | const authButtons = this.state.authed ? (
67 | {
70 | logout();
71 | }}
72 | style={{ color: '#fff' }}
73 | />
74 | ) : (
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | );
84 |
85 | const topbarButtons = (
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | {authButtons}
94 |
95 | );
96 | return this.state.loading === true ? (
97 | Loading
98 | ) : (
99 |
100 |
101 |
110 |
111 |
112 |
113 |
114 |
119 |
124 |
129 | No Match
} />
130 |
131 |
132 |
133 |
134 |
135 | );
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/components/protected/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class Dashboard extends Component {
4 | render() {
5 | return (
6 |
7 | Dashboard. This is a protected route. You can only see this if you're
8 | authed.
9 |
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/config/constants.js:
--------------------------------------------------------------------------------
1 | import firebase from 'firebase';
2 | // Required for side-effects
3 | require('firebase/firestore');
4 |
5 | const config = {
6 | apiKey: process.env.REACT_APP_FIREBASE_KEY,
7 | authDomain: process.env.REACT_APP_AUTH_DOMAIN,
8 | databaseURL: process.env.REACT_APP_DATABASE_URL,
9 | projectId: process.env.REACT_APP_PROJECT_ID,
10 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
11 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID
12 | };
13 |
14 | firebase.initializeApp(config);
15 |
16 | export const db = firebase.firestore();
17 | export const firebaseAuth = firebase.auth;
18 |
--------------------------------------------------------------------------------
/src/helpers/auth.js:
--------------------------------------------------------------------------------
1 | import { db, firebaseAuth } from '../config/constants';
2 |
3 | export function auth(email, pw) {
4 | return firebaseAuth()
5 | .createUserWithEmailAndPassword(email, pw)
6 | .then(saveUser);
7 | }
8 |
9 | export function logout() {
10 | return firebaseAuth().signOut();
11 | }
12 |
13 | export function login(email, pw) {
14 | return firebaseAuth().signInWithEmailAndPassword(email, pw);
15 | }
16 |
17 | export function resetPassword(email) {
18 | return firebaseAuth().sendPasswordResetEmail(email);
19 | }
20 |
21 | export function saveUser(user) {
22 | return db
23 | .collection(`users`)
24 | .add({
25 | email: user.email,
26 | uid: user.uid
27 | })
28 | .then(docRef => docRef)
29 | .catch(function(error) {
30 | console.error('Error adding document: ', error);
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './components';
4 | import registerServiceWorker from './registerServiceWorker';
5 | import './index.css';
6 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById('root')
13 | );
14 | registerServiceWorker();
15 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 | } else {
39 | // Is not local host. Just register service worker
40 | registerValidSW(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------