├── .gitignore ├── LICENSE.md ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── components ├── Account.js ├── App.css ├── App.js ├── App.test.js ├── AuthUserContext.js ├── Home.js ├── Landing.js ├── Navigation.js ├── PasswordChange.js ├── PasswordForget.js ├── SignIn.js ├── SignOut.js ├── SignUp.js ├── logo.svg ├── withAuthentication.js └── withAuthorization.js ├── constants └── routes.js ├── firebase ├── auth.js ├── db.js ├── firebase.js └── index.js ├── index.css ├── index.js └── serviceWorker.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2018 Pablo Satler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reactjs with Firebase Authentication 2 | 3 | > A live demo of the project is found [here](https://react-firebase-authentication.herokuapp.com/). 4 | 5 | This a simple Reactjs project showcasing use of firebase authentication. It has both public and private endpoints. Private endpoints are protected using session handling. 6 | 7 | For users that signs in through email/password in the website, it is also possible to make password modifications (like reset or change password). For users that signs in using Facebook, this feature is disabled. 8 | 9 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 10 | 11 | ## Some Endpoints 12 | 13 | - `/`: it's the root route, also called the landing page. 14 | - `/signin`: where the user is able to login either through Facebook or via email/password 15 | - `/signup`: where the user can signup using email/password. 16 | - `/account`: protected endpoint, available only for users registered using email/password. At this endpoint, they can modify/reset their password. 17 | - `/home`: protected endpoint, reachable only for authenticated users. 18 | 19 | ## How to run 20 | 21 | Do the following: 22 | 23 | ``` 24 | git clone https://github.com/psatler/react-firebase-authentication.git 25 | cd react-firebase-authentication 26 | npm start 27 | ``` 28 | 29 | A live demo of the project can be found [here](https://react-firebase-authentication.herokuapp.com/) 30 | 31 | ## How to Deploy 32 | 33 | Make sure you have the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) installed. Then, just follow [this](https://github.com/mars/create-react-app-buildpack#quick-start) instructions. 34 | 35 | Remember to add your new domain to the Firebase Console: firebase console -> Authentication -> Sign-in Method Tab -> Authorized Domains Section (at the bottom of the page) 36 | 37 | NOTE: for the Facebook Login button, you need to create an app at [Facebook Developers](https://developers.facebook.com/). 38 | 39 | ## Main Dependecies 40 | 41 | - [React Router 4](https://reacttraining.com/react-router/core/guides/philosophy): Declarative Routing for React.js 42 | - [React Context API](https://reactjs.org/docs/context.html): Context provides a way to pass data through the component tree without having to pass props down manually at every level 43 | - [Firebase](https://firebase.google.com/): Build apps fast, without managing infrastructure 44 | - [React Social Login Buttons](https://www.npmjs.com/package/react-social-login-buttons): A simple package to display social login buttons using React. 45 | - [Reactstrap](https://reactstrap.github.io/): Easy to use React Bootstrap 4 components. 46 | - [Bootstrap](https://getbootstrap.com/): Free and open-source front-end framework for designing websites. 47 | 48 | ## License 49 | 50 | This project is licensed under the terms of the [MIT License](https://opensource.org/licenses/MIT) © Pablo Satler 2018 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-authentication", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bootstrap": "^4.1.3", 7 | "firebase": "^5.5.4", 8 | "react": "^16.5.2", 9 | "react-dom": "^16.5.2", 10 | "react-router-dom": "^4.3.1", 11 | "react-scripts": "2.0.4", 12 | "react-social-login-buttons": "^2.1.2", 13 | "reactstrap": "^6.5.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": "react-app" 23 | }, 24 | "browserslist": [ 25 | ">0.2%", 26 | "not dead", 27 | "not ie <= 11", 28 | "not op_mini all" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psatler/react-firebase-authentication/d12650de743525e296d7bbb2b94ac7947da9b350/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Account.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import AuthUserContext from "./AuthUserContext"; 4 | import { PasswordForgetForm } from "./PasswordForget"; 5 | import PasswordChangeForm from "./PasswordChange"; 6 | import withAuthorization from "./withAuthorization"; //redirects to sign in if user not signed in 7 | 8 | const AccountPage = () => ( 9 | //authUser is passed down via Context API (It is set at withAuthentication.js file) 10 | 11 | {authUser => ( 12 |
13 |
14 |
15 | Change/Reset Password for : {authUser.email} 16 |
17 | {/* disabling password changes/resets if user is logged in through facebook */} 18 | {authUser.providerData[0].providerId === "facebook.com" ? null : ( 19 |
20 | 21 | 22 |
23 | )} 24 |
25 |
26 | )} 27 |
28 | ); 29 | 30 | const authCondition = authUser => 31 | !!authUser && authUser.providerData[0].providerId !== "facebook.com"; //true and false 32 | 33 | export default withAuthorization(authCondition)(AccountPage); 34 | -------------------------------------------------------------------------------- /src/components/App.css: -------------------------------------------------------------------------------- 1 | /* .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #282c34; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: white; 19 | } 20 | 21 | .App-link { 22 | color: #61dafb; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } */ 33 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { BrowserRouter, Route } from "react-router-dom"; 3 | import { Container } from "reactstrap"; 4 | import "./App.css"; 5 | 6 | import { firebase } from "../firebase"; 7 | import * as routes from "../constants/routes"; 8 | 9 | //nav stuff 10 | import Navigation from "./Navigation"; 11 | import LandingPage from "./Landing"; 12 | import SignUpPage from "./SignUp"; 13 | import SignInPage from "./SignIn"; 14 | import PasswordForgetPage from "./PasswordForget"; 15 | import HomePage from "./Home"; 16 | import AccountPage from "./Account"; 17 | 18 | import withAuthentication from "./withAuthentication"; 19 | 20 | const App = () => ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | ); 38 | 39 | // class App extends Component { 40 | // //holds info about if an user is signed in or not 41 | // state = { 42 | // authUser: null 43 | // }; 44 | 45 | // componentDidMount() { 46 | // //a listener for the authenticated user 47 | // //if the user signs out, the authUser becomes null 48 | // firebase.auth.onAuthStateChanged(authUser => { 49 | // authUser 50 | // ? this.setState({ authUser }) 51 | // : this.setState({ authUser: null }); 52 | // }); 53 | // } 54 | 55 | // render() { 56 | // return ( 57 | // //
58 | // 59 | //
60 | // 61 | 62 | //
63 | 64 | // 65 | // 66 | // 67 | // 72 | // 73 | // 74 | //
75 | //
76 | // //
77 | // ); 78 | // } 79 | // } 80 | 81 | // export default App; 82 | export default withAuthentication(App); //using HoC to handle session 83 | -------------------------------------------------------------------------------- /src/components/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/AuthUserContext.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const AuthUserContext = React.createContext(null); //using React's Context API 4 | 5 | export default AuthUserContext; 6 | -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import withAuthorization from "./withAuthorization"; 4 | import { db } from "../firebase"; 5 | 6 | class HomePage extends Component { 7 | state = { 8 | users: null, 9 | username: "", 10 | loading: true 11 | }; 12 | 13 | componentDidMount() { 14 | // db.onceGetUsers().then(res => { 15 | // this.setState({ 16 | // users: res.val() 17 | // }); 18 | // }); 19 | 20 | const { loggedUser } = this.props; 21 | db.doGetAnUnser(loggedUser.uid).then(res => { 22 | this.setState({ 23 | username: res.val().username, 24 | loading: false 25 | }); 26 | }); 27 | } 28 | 29 | render() { 30 | const { users, username, loading } = this.state; 31 | // console.log("dasdf", this.props.loggedUser); 32 | return ( 33 |
34 |

Home

35 | {!loading &&

Hello {username}!

} 36 | 37 |

38 | NOTE: This page is only accessible by signed in users. 39 |

40 | {/* {!!users && } */} 41 |
42 | ); 43 | } 44 | } 45 | 46 | // const UserList = ({ users }) => ( 47 | //
48 | // {console.log("users", users)} 49 | //

List of Usernames of Users

50 | //

(Saved on Sign Up in Firebase Database)

51 | 52 | // {Object.keys(users).map(key => ( 53 | //
{users[key].username}
54 | // ))} 55 | //
56 | // ); 57 | 58 | const authCondition = authUser => !!authUser; 59 | 60 | export default withAuthorization(authCondition)(HomePage); //grants authorization to open endpoint if an user is signed in 61 | -------------------------------------------------------------------------------- /src/components/Landing.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const LandingPage = () => ( 4 |
5 |

Landing Page

6 | 7 |

This page is public, accessible by everyone

8 | 9 |

10 | Est cillum sunt qui nulla esse mollit quis magna enim non non laborum 11 | culpa nisi. Amet do nisi minim amet dolor quis veniam fugiat exercitation 12 | duis anim occaecat. Mollit pariatur minim aute eiusmod est ad dolore 13 | labore fugiat deserunt quis. Aliquip dolor ex irure sunt voluptate 14 | exercitation voluptate incididunt. 15 |

16 |
17 | ); 18 | 19 | export default LandingPage; 20 | -------------------------------------------------------------------------------- /src/components/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Navbar, NavbarBrand, Nav, NavItem, NavLink } from "reactstrap"; 3 | import { Link } from "react-router-dom"; 4 | 5 | import SignOutButton from "./SignOut"; 6 | import * as routes from "../constants/routes"; 7 | 8 | import AuthUserContext from "./AuthUserContext"; 9 | 10 | const Navigation = () => ( 11 | 12 | {authUser => 13 | authUser ? : 14 | } 15 | 16 | ); 17 | 18 | const NavigationNonAuth = () => ( 19 | 20 | 21 | Landing 22 | 23 | 30 | 31 | ); 32 | 33 | export default Navigation; 34 | 35 | const NavigationAuth = ({ userInfo }) => ( 36 | 37 | 38 | Landing 39 | 40 | 57 | 58 | ); 59 | 60 | // const NavigationAuth = ({ userInfo }) => ( 61 | // 79 | // ); 80 | 81 | // const NavigationNonAuth = () => ( 82 | // 90 | // ); 91 | -------------------------------------------------------------------------------- /src/components/PasswordChange.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Button, Form, FormGroup, Label, Input, Alert } from "reactstrap"; 3 | 4 | import { auth } from "../firebase"; 5 | 6 | const byPropKey = (propertyName, value) => () => ({ 7 | [propertyName]: value 8 | }); 9 | 10 | const INITIAL_STATE = { 11 | passwordOne: "", 12 | passwordTwo: "", 13 | error: null, 14 | showingAlert: false 15 | }; 16 | 17 | class PasswordChangeForm extends Component { 18 | state = { ...INITIAL_STATE }; 19 | 20 | onSubmit = event => { 21 | const { passwordOne } = this.state; 22 | 23 | auth 24 | .doPasswordUpdate(passwordOne) 25 | .then(() => { 26 | this.setState({ ...INITIAL_STATE }); 27 | }) 28 | .catch(error => { 29 | this.setState(byPropKey("error", error)); 30 | this.timer(); //show alert message for some seconds 31 | }); 32 | 33 | event.preventDefault(); 34 | }; 35 | 36 | timer = () => { 37 | this.setState({ 38 | showingAlert: true 39 | }); 40 | 41 | setTimeout(() => { 42 | this.setState({ 43 | showingAlert: false 44 | }); 45 | }, 4000); 46 | }; 47 | 48 | render() { 49 | const { passwordOne, passwordTwo, error, showingAlert } = this.state; 50 | 51 | const isInvalid = passwordOne !== passwordTwo || passwordOne === ""; 52 | 53 | return ( 54 |
55 | {showingAlert && ( 56 | 57 | {error.message} 58 | 59 | )} 60 | 61 |
62 | 63 | 64 | 71 | this.setState(byPropKey("passwordOne", e.target.value)) 72 | } 73 | /> 74 | 75 | 76 | 77 | 84 | this.setState(byPropKey("passwordTwo", e.target.value)) 85 | } 86 | /> 87 | 88 | 89 |
90 | 93 |
94 |
95 |
96 | ); 97 | } 98 | } 99 | 100 | export default PasswordChangeForm; 101 | 102 | //
103 | // 106 | // this.setState(byPropKey("passwordOne", event.target.value)) 107 | // } 108 | // type="password" 109 | // placeholder="New Password" 110 | // /> 111 | // 114 | // this.setState(byPropKey("passwordTwo", event.target.value)) 115 | // } 116 | // type="password" 117 | // placeholder="Confirm New Password" 118 | // /> 119 | // 122 | 123 | // {error &&

{error.message}

} 124 | //
125 | -------------------------------------------------------------------------------- /src/components/PasswordForget.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { Button, Form, FormGroup, Label, Input, Alert } from "reactstrap"; 4 | 5 | import { auth } from "../firebase"; 6 | import * as routes from "../constants/routes"; 7 | 8 | //it resets your password. It doesn’t matter if you are authenticated or not 9 | const PasswordForgetPage = () => ( 10 |
11 |
12 |

Forget Password

13 | 14 |
15 |
16 | ); 17 | 18 | const byPropKey = (propertyName, value) => () => ({ 19 | [propertyName]: value 20 | }); 21 | 22 | //################### PasswordForget Form ################### 23 | const INITIAL_STATE = { 24 | email: "", 25 | error: null, 26 | showingAlert: false 27 | }; 28 | 29 | class PasswordForgetForm extends Component { 30 | state = { ...INITIAL_STATE }; 31 | 32 | onSubmit = event => { 33 | const { email } = this.state; 34 | 35 | auth 36 | .doPasswordReset(email) 37 | .then(() => { 38 | this.setState({ ...INITIAL_STATE }); 39 | }) 40 | .catch(error => { 41 | this.setState(byPropKey("error", error)); 42 | this.timer(); //show alert message for some seconds 43 | }); 44 | 45 | event.preventDefault(); 46 | }; 47 | 48 | timer = () => { 49 | this.setState({ 50 | showingAlert: true 51 | }); 52 | 53 | setTimeout(() => { 54 | this.setState({ 55 | showingAlert: false 56 | }); 57 | }, 4000); 58 | }; 59 | 60 | render() { 61 | const { email, error, showingAlert } = this.state; 62 | 63 | const isInvalid = email === ""; 64 | 65 | return ( 66 |
67 | {showingAlert && ( 68 | 69 | {error.message} 70 | 71 | )} 72 | 73 |
74 | 75 | 76 | 83 | this.setState(byPropKey("email", event.target.value)) 84 | } 85 | /> 86 | 87 | 88 |
89 | 92 |
93 |
94 |
95 | ); 96 | } 97 | } 98 | 99 | //################### PasswordForget Link ################### 100 | const PasswordForgetLink = () => ( 101 |

102 | Forgot Password? 103 |

104 | ); 105 | 106 | export default PasswordForgetPage; 107 | 108 | export { PasswordForgetForm, PasswordForgetLink }; 109 | 110 | //
111 | // 114 | // this.setState(byPropKey("email", event.target.value)) 115 | // } 116 | // type="text" 117 | // placeholder="Email Address" 118 | // /> 119 | // 122 | 123 | // {error &&

{error.message}

} 124 | //
125 | -------------------------------------------------------------------------------- /src/components/SignIn.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Button, Form, FormGroup, Label, Input, Alert } from "reactstrap"; 3 | import { FacebookLoginButton } from "react-social-login-buttons"; 4 | import logo from "./logo.svg"; 5 | 6 | import { withRouter } from "react-router-dom"; 7 | 8 | import { SignUpLink } from "./SignUp"; 9 | import { PasswordForgetLink } from "./PasswordForget"; 10 | import { auth, db } from "../firebase"; 11 | import * as routes from "../constants/routes"; 12 | 13 | const SignInPage = ({ history }) => { 14 | return ( 15 |
16 |
17 |

Sign In

18 | {/* My logo */} 19 | 20 | 21 | 22 | 23 |
24 |
25 | ); 26 | }; 27 | 28 | const byPropKey = (propertyName, value) => () => ({ 29 | [propertyName]: value 30 | }); 31 | 32 | const INITIAL_STATE = { 33 | email: "", 34 | password: "", 35 | error: null, 36 | showingAlert: false 37 | }; 38 | 39 | class SignInForm extends Component { 40 | state = { ...INITIAL_STATE }; 41 | 42 | onSubmit = event => { 43 | const { email, password } = this.state; 44 | 45 | const { history } = this.props; 46 | 47 | auth 48 | .doSignInWithEmailAndPassword(email, password) 49 | .then(() => { 50 | this.setState({ ...INITIAL_STATE }); 51 | history.push(routes.HOME); 52 | }) 53 | .catch(error => { 54 | this.setState(byPropKey("error", error)); 55 | this.timer(); //defined below 56 | }); 57 | 58 | event.preventDefault(); 59 | }; 60 | 61 | facebookLogin = () => { 62 | const { history } = this.props; 63 | auth 64 | .doFacebookSignIn() 65 | .then(authUser => { 66 | console.log("authUser", authUser); 67 | 68 | db.doCreateUser( 69 | //store some info from facebook into the firebase db 70 | authUser.user.uid, 71 | authUser.user.displayName, 72 | authUser.user.email 73 | ) 74 | .then(() => { 75 | // this.setState({ 76 | // ...INITIAL_STATE 77 | // }); 78 | history.push(routes.HOME); //redirects to Home Page 79 | }) 80 | .catch(error => { 81 | this.setState(byPropKey("error", error)); 82 | }); 83 | }) 84 | .catch(error => { 85 | this.setState(byPropKey("error", error)); 86 | }); 87 | }; 88 | 89 | timer = () => { 90 | this.setState({ 91 | showingAlert: true 92 | }); 93 | 94 | setTimeout(() => { 95 | this.setState({ 96 | showingAlert: false 97 | }); 98 | }, 4000); 99 | }; 100 | 101 | render() { 102 | const { email, password, error, showingAlert } = this.state; 103 | 104 | const isInvalid = password === "" || email === ""; 105 | 106 | return ( 107 |
108 | {showingAlert && ( 109 | 110 | {error.message} 111 | 112 | )} 113 | 114 |
115 | 116 | 117 | 124 | this.setState(byPropKey("email", event.target.value)) 125 | } 126 | /> 127 | 128 | 129 | 130 | 137 | this.setState(byPropKey("password", event.target.value)) 138 | } 139 | /> 140 | 141 | 142 |
143 | 146 |
147 |
148 | 149 |
150 | 151 | 152 | {/* */} 153 |
154 | ); 155 | } 156 | } 157 | 158 | export default withRouter(SignInPage); 159 | 160 | export { SignInForm }; 161 | 162 | { 163 | /*
164 | 167 | this.setState(byPropKey("email", event.target.value)) 168 | } 169 | type="text" 170 | placeholder="Email Address" 171 | /> 172 | 175 | this.setState(byPropKey("password", event.target.value)) 176 | } 177 | type="password" 178 | placeholder="Password" 179 | /> 180 | 183 | 184 | {error &&

{error.message}

} 185 |
*/ 186 | } 187 | -------------------------------------------------------------------------------- /src/components/SignOut.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button } from "reactstrap"; 3 | 4 | import { auth } from "../firebase"; 5 | 6 | const SignOutButton = () => ( 7 | 10 | ); 11 | 12 | export default SignOutButton; 13 | -------------------------------------------------------------------------------- /src/components/SignUp.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link, withRouter } from "react-router-dom"; 3 | import { Button, Form, FormGroup, Label, Input, Alert } from "reactstrap"; 4 | 5 | import * as routes from "../constants/routes"; 6 | import { auth, db } from "../firebase"; 7 | 8 | const SignUpPage = ({ history }) => ( 9 |
10 |
11 |
12 |

Sign Up

13 | 14 |
15 |
16 |
17 | ); 18 | 19 | //################### Sign Up Form ################### 20 | const INITIAL_STATE = { 21 | username: "", 22 | email: "", 23 | passwordOne: "", 24 | passwordTwo: "", 25 | error: null, 26 | showingAlert: false 27 | }; 28 | 29 | //A Higher order function with prop name as key and the value to be assigned to 30 | const byPropKey = (propertyName, value) => () => ({ 31 | [propertyName]: value 32 | }); 33 | 34 | class SignUpForm extends Component { 35 | //defining state 36 | state = { 37 | ...INITIAL_STATE 38 | }; 39 | 40 | // onChange = (propName, value) => { 41 | // this.setState({ 42 | // [propName]: value 43 | // }); 44 | // }; 45 | 46 | onSubmit = event => { 47 | const { username, email, passwordOne } = this.state; 48 | const { history } = this.props; 49 | auth 50 | .doCreateUserWithEmailAndPassword(email, passwordOne) 51 | //it the above functions resolves, reset the state to its initial state values, otherwise, set the error object 52 | .then(authUser => { 53 | //creating a user in the database after the sign up through Firebase auth API 54 | db.doCreateUser(authUser.user.uid, username, email) 55 | .then(() => { 56 | this.setState({ 57 | ...INITIAL_STATE 58 | }); 59 | history.push(routes.HOME); //redirects to Home Page 60 | }) 61 | .catch(error => { 62 | this.setState(byPropKey("error", error)); 63 | this.timer(); //show alert message for some seconds 64 | }); 65 | }) 66 | .catch(err => { 67 | this.setState(byPropKey("error", err)); 68 | this.timer(); //show alert message for some seconds 69 | }); 70 | 71 | event.preventDefault(); //prevents refreshing 72 | }; 73 | 74 | timer = () => { 75 | this.setState({ 76 | showingAlert: true 77 | }); 78 | 79 | setTimeout(() => { 80 | this.setState({ 81 | showingAlert: false 82 | }); 83 | }, 4000); 84 | }; 85 | 86 | render() { 87 | const { 88 | username, 89 | email, 90 | passwordOne, 91 | passwordTwo, 92 | error, 93 | showingAlert 94 | } = this.state; 95 | //a boolen to perform validation 96 | const isInvalid = 97 | passwordOne !== passwordTwo || 98 | passwordOne === "" || 99 | email === "" || 100 | username === ""; 101 | 102 | return ( 103 |
104 | {showingAlert && ( 105 | 106 | {error.message} 107 | 108 | )} 109 |
110 | 111 | 112 | 119 | this.setState(byPropKey("username", e.target.value)) 120 | } 121 | /> 122 | 123 | 124 | 125 | this.setState(byPropKey("email", e.target.value))} 132 | /> 133 | 134 | 135 | 136 | 143 | this.setState(byPropKey("passwordOne", e.target.value)) 144 | } 145 | /> 146 | 147 | 148 | 149 | 156 | this.setState(byPropKey("passwordTwo", e.target.value)) 157 | } 158 | /> 159 | 160 | 161 |
162 | 165 |
166 |
167 |
168 | ); 169 | } 170 | } 171 | 172 | //################### Sign Up Link ################### 173 | //used in the sign in when the user don't have an account registered yet 174 | const SignUpLink = () => ( 175 |

176 | Don't have an account? Sign Up 177 |

178 | ); 179 | 180 | //exports 181 | export default withRouter(SignUpPage); //using a HoC to get access to history 182 | export { SignUpForm, SignUpLink }; 183 | 184 | //
185 | // this.setState(byPropKey("username", e.target.value))} 188 | // // onChange={e => this.onChange("username", e.target.value)} 189 | // type="text" 190 | // placeholder="Full Name" 191 | // /> 192 | // this.setState(byPropKey("email", e.target.value))} 195 | // type="text" 196 | // placeholder="Email Address" 197 | // /> 198 | // 201 | // this.setState(byPropKey("passwordOne", e.target.value)) 202 | // } 203 | // type="password" 204 | // placeholder="Password" 205 | // /> 206 | // 209 | // this.setState(byPropKey("passwordTwo", e.target.value)) 210 | // } 211 | // type="password" 212 | // placeholder="Confirm Password" 213 | // /> 214 | // 217 | 218 | // {error &&

{error.message}

} 219 | //
220 | -------------------------------------------------------------------------------- /src/components/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/withAuthentication.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { firebase } from "../firebase"; 3 | 4 | import AuthUserContext from "./AuthUserContext"; //using provider's context api 5 | 6 | const withAuthentication = Component => { 7 | class WithAuthentication extends React.Component { 8 | state = { 9 | authUser: null 10 | }; 11 | 12 | componentDidMount() { 13 | firebase.auth.onAuthStateChanged(authUser => { 14 | authUser 15 | ? this.setState({ authUser }) 16 | : this.setState({ authUser: null }); 17 | }); 18 | } 19 | 20 | render() { 21 | const { authUser } = this.state; 22 | console.log("withAuthentication file authUser", authUser); 23 | return ( 24 | // passing down the authUser value, so other components can consume it 25 | 26 | 27 | 28 | ); 29 | } 30 | } 31 | 32 | return WithAuthentication; 33 | }; 34 | 35 | export default withAuthentication; 36 | -------------------------------------------------------------------------------- /src/components/withAuthorization.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withRouter } from "react-router-dom"; 3 | 4 | import AuthUserContext from "./AuthUserContext"; 5 | import { firebase } from "../firebase"; 6 | import * as routes from "../constants/routes"; 7 | 8 | const withAuthorization = authCondition => Component => { 9 | class WithAuthorization extends React.Component { 10 | componentDidMount() { 11 | firebase.auth.onAuthStateChanged(authUser => { 12 | if (!authCondition(authUser)) { 13 | //if the authorization fails, redirects to sign in page 14 | this.props.history.push(routes.SIGN_IN); 15 | } 16 | }); 17 | } 18 | 19 | render() { 20 | return ( 21 | 22 | {/* it either renders the passed component or not */} 23 | {authUser => 24 | authUser ? ( 25 | 26 | ) : null 27 | } 28 | 29 | ); 30 | } 31 | } 32 | 33 | return withRouter(WithAuthorization); //using withRouter so we have access to history props 34 | }; 35 | 36 | export default withAuthorization; 37 | -------------------------------------------------------------------------------- /src/constants/routes.js: -------------------------------------------------------------------------------- 1 | export const SIGN_UP = "/signup"; 2 | export const SIGN_IN = "/signin"; 3 | export const LANDING = "/"; 4 | export const HOME = "/home"; 5 | export const ACCOUNT = "/account"; 6 | export const PASSWORD_FORGET = "/pw-forget"; 7 | -------------------------------------------------------------------------------- /src/firebase/auth.js: -------------------------------------------------------------------------------- 1 | import { auth, facebookProvider } from "./firebase"; //importing the previously instatiated object from the firebase.js config file 2 | 3 | //## below the authentication functions ## 4 | 5 | //sign up 6 | export const doCreateUserWithEmailAndPassword = (email, password) => 7 | auth.createUserWithEmailAndPassword(email, password); 8 | 9 | //sign in 10 | export const doSignInWithEmailAndPassword = (email, password) => 11 | auth.signInWithEmailAndPassword(email, password); 12 | 13 | //sign out 14 | export const doSignOut = () => auth.signOut(); 15 | 16 | //## below are two more functions, for resetting or changing passwords ## 17 | 18 | //password reset 19 | export const doPasswordReset = email => auth.sendPasswordResetEmail(email); 20 | 21 | //password change 22 | export const doPasswordChange = password => 23 | auth.currentUser.updatePassword(password); 24 | 25 | //#### for 26 | // facebook ##### 27 | export const doFacebookSignIn = () => auth.signInWithPopup(facebookProvider); 28 | -------------------------------------------------------------------------------- /src/firebase/db.js: -------------------------------------------------------------------------------- 1 | //this is going to store Firebase realtime database API code 2 | import { db } from "./firebase"; 3 | 4 | //##########3 user API 5 | 6 | //create an user and store it at users/id path (it's an asynchronous func) 7 | export const doCreateUser = (id, username, email) => 8 | db.ref(`users/${id}`).set({ 9 | username, 10 | email 11 | }); 12 | 13 | //returns all users from firebase realtime db 14 | export const onceGetUsers = () => db.ref("users").once("value"); 15 | 16 | export const doGetAnUnser = uid => db.ref(`users/${uid}`).once("value"); 17 | 18 | // other APIs could come below 19 | -------------------------------------------------------------------------------- /src/firebase/firebase.js: -------------------------------------------------------------------------------- 1 | import firebase from "firebase/app"; 2 | import "firebase/auth"; 3 | import "firebase/database"; 4 | 5 | //this config is being used for both development and production environment. Though, it is a best practice creating a second project and have two configs: one for production (prodConfig) and another for development (devConfig), so you choose the config based on the environment. 6 | 7 | const config = { 8 | apiKey: "AIzaSyAcOIzZPhgquE2g4NkrJovuIucSkMzYupI", 9 | authDomain: "react-firebase-authentic-b43cd.firebaseapp.com", 10 | databaseURL: "https://react-firebase-authentic-b43cd.firebaseio.com", 11 | projectId: "react-firebase-authentic-b43cd", 12 | storageBucket: "react-firebase-authentic-b43cd.appspot.com", 13 | messagingSenderId: "502482847019" 14 | }; 15 | 16 | if (!firebase.apps.length) { 17 | //initializing with the config object 18 | firebase.initializeApp(config); 19 | } 20 | 21 | //separting database API and authentication 22 | const db = firebase.database(); 23 | const auth = firebase.auth(); 24 | 25 | const facebookProvider = new firebase.auth.FacebookAuthProvider(); 26 | 27 | export { db, auth, facebookProvider }; 28 | -------------------------------------------------------------------------------- /src/firebase/index.js: -------------------------------------------------------------------------------- 1 | import * as auth from "./auth"; //all functions is gonna be preceded with auth 2 | import * as db from "./db"; 3 | import * as firebase from "./firebase"; 4 | 5 | export { auth, db, firebase }; 6 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0 auto; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | .div-flex { 12 | display: flex; 13 | justify-content: center; 14 | /* background: black; */ 15 | } 16 | 17 | .centered { 18 | margin-top: 20px; 19 | text-align: center; 20 | /* background: black; */ 21 | } 22 | 23 | .App-logo { 24 | animation: App-logo-spin infinite 20s linear; 25 | /* height: 20vmin; */ 26 | } 27 | 28 | @keyframes App-logo-spin { 29 | from { 30 | transform: rotate(0deg); 31 | } 32 | to { 33 | transform: rotate(360deg); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import "bootstrap/dist/css/bootstrap.min.css"; 2 | import React from "react"; 3 | import ReactDOM from "react-dom"; 4 | import "./index.css"; 5 | import App from "./components/App"; 6 | import * as serviceWorker from "./serviceWorker"; 7 | 8 | ReactDOM.render(, document.getElementById("root")); 9 | 10 | // If you want your app to work offline and load faster, you can change 11 | // unregister() to register() below. Note this comes with some pitfalls. 12 | // Learn more about service workers: http://bit.ly/CRA-PWA 13 | serviceWorker.unregister(); 14 | -------------------------------------------------------------------------------- /src/serviceWorker.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 function register(config) { 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/facebook/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. Let's check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl, config); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl, config); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl, config) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | 70 | // Execute callback 71 | if (config.onUpdate) { 72 | config.onUpdate(registration); 73 | } 74 | } else { 75 | // At this point, everything has been precached. 76 | // It's the perfect time to display a 77 | // "Content is cached for offline use." message. 78 | console.log('Content is cached for offline use.'); 79 | 80 | // Execute callback 81 | if (config.onSuccess) { 82 | config.onSuccess(registration); 83 | } 84 | } 85 | } 86 | }; 87 | }; 88 | }) 89 | .catch(error => { 90 | console.error('Error during service worker registration:', error); 91 | }); 92 | } 93 | 94 | function checkValidServiceWorker(swUrl, config) { 95 | // Check if the service worker can be found. If it can't reload the page. 96 | fetch(swUrl) 97 | .then(response => { 98 | // Ensure service worker exists, and that we really are getting a JS file. 99 | if ( 100 | response.status === 404 || 101 | response.headers.get('content-type').indexOf('javascript') === -1 102 | ) { 103 | // No service worker found. Probably a different app. Reload the page. 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister().then(() => { 106 | window.location.reload(); 107 | }); 108 | }); 109 | } else { 110 | // Service worker found. Proceed as normal. 111 | registerValidSW(swUrl, config); 112 | } 113 | }) 114 | .catch(() => { 115 | console.log( 116 | 'No internet connection found. App is running in offline mode.' 117 | ); 118 | }); 119 | } 120 | 121 | export function unregister() { 122 | if ('serviceWorker' in navigator) { 123 | navigator.serviceWorker.ready.then(registration => { 124 | registration.unregister(); 125 | }); 126 | } 127 | } 128 | --------------------------------------------------------------------------------