├── .gitignore ├── README.md ├── images.d.ts ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── components │ ├── App.tsx │ ├── Navigation.tsx │ └── SignOutButton.tsx ├── constants │ └── routes.ts ├── firebase │ ├── AuthUserContext.ts │ ├── auth.ts │ ├── db.ts │ ├── firebase.ts │ ├── index.ts │ ├── withAuthentication.tsx │ └── withAuthorization.tsx ├── index.css ├── index.tsx ├── pages │ ├── Account │ │ ├── PasswordChangeForm.tsx │ │ └── index.tsx │ ├── Home │ │ ├── UserList.tsx │ │ └── index.tsx │ ├── Landing │ │ └── index.tsx │ ├── PasswordForget │ │ ├── PasswordForgetForm.tsx │ │ └── index.tsx │ ├── SignIn │ │ ├── SignInForm.tsx │ │ └── index.tsx │ └── SignUp │ │ ├── SingUpForm.tsx │ │ └── index.tsx └── registerServiceWorker.ts ├── tsconfig.json ├── tsconfig.prod.json ├── tsconfig.test.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/22a5eb3da72c0a224a179033e92722bdbf5a05ad/Ruby.gitignore 2 | 3 | *.gem 4 | *.rbc 5 | /.config 6 | /coverage/ 7 | /InstalledFiles 8 | /pkg/ 9 | /spec/reports/ 10 | /spec/examples.txt 11 | /test/tmp/ 12 | /test/version_tmp/ 13 | /tmp/ 14 | 15 | # Used by dotenv library to load environment variables. 16 | # .env 17 | 18 | ## Specific to RubyMotion: 19 | .dat* 20 | .repl_history 21 | build/ 22 | *.bridgesupport 23 | build-iPhoneOS/ 24 | build-iPhoneSimulator/ 25 | 26 | ## Specific to RubyMotion (use of CocoaPods): 27 | # 28 | # We recommend against adding the Pods directory to your .gitignore. However 29 | # you should judge for yourself, the pros and cons are mentioned at: 30 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 31 | # 32 | # vendor/Pods/ 33 | 34 | ## Documentation cache and generated files: 35 | /.yardoc/ 36 | /_yardoc/ 37 | /doc/ 38 | /rdoc/ 39 | 40 | ## Environment normalization: 41 | /.bundle/ 42 | /vendor/bundle 43 | /lib/bundler/man/ 44 | 45 | # for a library or gem, you might want to ignore these files since the code is 46 | # intended to run in multiple environments; otherwise, check them in: 47 | # Gemfile.lock 48 | # .ruby-version 49 | # .ruby-gemset 50 | 51 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 52 | .rvmrc 53 | 54 | 55 | ### https://raw.github.com/github/gitignore/22a5eb3da72c0a224a179033e92722bdbf5a05ad/Global/MacOS.gitignore 56 | 57 | # General 58 | .DS_Store 59 | .AppleDouble 60 | .LSOverride 61 | 62 | # Icon must end with two \r 63 | Icon 64 | 65 | 66 | # Thumbnails 67 | ._* 68 | 69 | # Files that might appear in the root of a volume 70 | .DocumentRevisions-V100 71 | .fseventsd 72 | .Spotlight-V100 73 | .TemporaryItems 74 | .Trashes 75 | .VolumeIcon.icns 76 | .com.apple.timemachine.donotpresent 77 | 78 | # Directories potentially created on remote AFP share 79 | .AppleDB 80 | .AppleDesktop 81 | Network Trash Folder 82 | Temporary Items 83 | .apdisk 84 | 85 | 86 | ### https://raw.github.com/github/gitignore/22a5eb3da72c0a224a179033e92722bdbf5a05ad/Global/JetBrains.gitignore 87 | 88 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 89 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 90 | 91 | # User-specific stuff: 92 | .idea/**/workspace.xml 93 | .idea/**/tasks.xml 94 | .idea/dictionaries 95 | 96 | # Sensitive or high-churn files: 97 | .idea/**/dataSources/ 98 | .idea/**/dataSources.ids 99 | .idea/**/dataSources.xml 100 | .idea/**/dataSources.local.xml 101 | .idea/**/sqlDataSources.xml 102 | .idea/**/dynamic.xml 103 | .idea/**/uiDesigner.xml 104 | 105 | # Gradle: 106 | .idea/**/gradle.xml 107 | .idea/**/libraries 108 | 109 | # CMake 110 | cmake-build-debug/ 111 | cmake-build-release/ 112 | 113 | # Mongo Explorer plugin: 114 | .idea/**/mongoSettings.xml 115 | 116 | ## File-based project format: 117 | *.iws 118 | 119 | ## Plugin-specific files: 120 | 121 | # IntelliJ 122 | out/ 123 | 124 | # mpeltonen/sbt-idea plugin 125 | .idea_modules/ 126 | 127 | # JIRA plugin 128 | atlassian-ide-plugin.xml 129 | 130 | # Cursive Clojure plugin 131 | .idea/replstate.xml 132 | 133 | # Crashlytics plugin (for Android Studio and IntelliJ) 134 | com_crashlytics_export_strings.xml 135 | crashlytics.properties 136 | crashlytics-build.properties 137 | fabric.properties 138 | 139 | 140 | ### https://raw.github.com/github/gitignore/22a5eb3da72c0a224a179033e92722bdbf5a05ad/Node.gitignore 141 | 142 | # Logs 143 | logs 144 | *.log 145 | npm-debug.log* 146 | yarn-debug.log* 147 | yarn-error.log* 148 | 149 | # Runtime data 150 | pids 151 | *.pid 152 | *.seed 153 | *.pid.lock 154 | 155 | # Directory for instrumented libs generated by jscoverage/JSCover 156 | lib-cov 157 | 158 | # Coverage directory used by tools like istanbul 159 | coverage 160 | 161 | # nyc test coverage 162 | .nyc_output 163 | 164 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 165 | .grunt 166 | 167 | # Bower dependency directory (https://bower.io/) 168 | bower_components 169 | 170 | # node-waf configuration 171 | .lock-wscript 172 | 173 | # Compiled binary addons (https://nodejs.org/api/addons.html) 174 | build/Release 175 | 176 | # Dependency directories 177 | node_modules/ 178 | jspm_packages/ 179 | 180 | # Typescript v1 declaration files 181 | typings/ 182 | 183 | # Optional npm cache directory 184 | .npm 185 | 186 | # Optional eslint cache 187 | .eslintcache 188 | 189 | # Optional REPL history 190 | .node_repl_history 191 | 192 | # Output of 'npm pack' 193 | *.tgz 194 | 195 | # Yarn Integrity file 196 | .yarn-integrity 197 | 198 | # dotenv environment variables file 199 | .env 200 | 201 | # next.js build output 202 | .next 203 | 204 | 205 | ### https://raw.github.com/github/gitignore/22a5eb3da72c0a224a179033e92722bdbf5a05ad/Global/VisualStudioCode.gitignore 206 | 207 | .vscode/* 208 | !.vscode/settings.json 209 | !.vscode/tasks.json 210 | !.vscode/launch.json 211 | !.vscode/extensions.json 212 | 213 | # Private 214 | .idea 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-typescript-firebase-auth 2 | 3 | create-react-app with TypeScript and Firebase authentication 4 | -------------------------------------------------------------------------------- /images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-typescript-firebase-auth", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "firebase": "^5.1.0", 7 | "react": "^16.4.1", 8 | "react-dom": "^16.4.1", 9 | "react-router-dom": "^4.3.1", 10 | "react-scripts-ts": "2.16.0" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts-ts start", 14 | "build": "react-scripts-ts build", 15 | "test": "react-scripts-ts test --env=jsdom", 16 | "eject": "react-scripts-ts eject", 17 | "tslint": "tslint --project tsconfig.json", 18 | "tslint:fix": "tslint --fix --project tsconfig.json" 19 | }, 20 | "devDependencies": { 21 | "@types/jest": "^23.1.1", 22 | "@types/node": "^10.3.5", 23 | "@types/react": "^16.4.1", 24 | "@types/react-dom": "^16.0.6", 25 | "@types/react-router-dom": "^4.2.7", 26 | "typescript": "^2.9.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morizyun/react-typescript-firebase-auth/08ee9470e5d79d32fa453482d0f8b57da5ff00dc/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 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": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { BrowserRouter, Route, Switch } from "react-router-dom"; 3 | import * as routes from "../constants/routes"; 4 | import { firebase } from "../firebase"; 5 | import { withAuthentication } from "../firebase/withAuthentication"; 6 | import { Account } from "../pages/Account"; 7 | import { Home } from "../pages/Home"; 8 | import { Landing } from "../pages/Landing"; 9 | import { PasswordForget } from "../pages/PasswordForget"; 10 | import { SignIn } from "../pages/SignIn"; 11 | import { SignUp } from "../pages/SignUp"; 12 | import { Navigation } from "./Navigation"; 13 | 14 | class AppComponent extends React.Component { 15 | constructor(props: any) { 16 | super(props); 17 | 18 | this.state = { 19 | authUser: null 20 | }; 21 | } 22 | 23 | public componentDidMount() { 24 | firebase.auth.onAuthStateChanged(authUser => { 25 | authUser 26 | ? this.setState(() => ({ authUser })) 27 | : this.setState(() => ({ authUser: null })); 28 | }); 29 | } 30 | 31 | public render() { 32 | return ( 33 | 34 |
35 | 36 |
37 | 38 | 39 | 40 | 41 | 46 | 47 | 48 | 49 |
50 |
51 | ); 52 | } 53 | } 54 | 55 | export const App = withAuthentication(AppComponent); 56 | -------------------------------------------------------------------------------- /src/components/Navigation.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import * as routes from "../constants/routes"; 4 | import { AuthUserContext } from "../firebase/AuthUserContext"; 5 | import { SignOutButton } from "./SignOutButton"; 6 | 7 | export const Navigation = () => ( 8 | 9 | {authUser => (authUser ? : )} 10 | 11 | ); 12 | 13 | const NavigationAuth = () => ( 14 | 28 | ); 29 | 30 | const NavigationNonAuth = () => ( 31 | 39 | ); 40 | -------------------------------------------------------------------------------- /src/components/SignOutButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { auth } from "../firebase"; 3 | 4 | export const SignOutButton = () => ( 5 | 8 | ); 9 | -------------------------------------------------------------------------------- /src/constants/routes.ts: -------------------------------------------------------------------------------- 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 = "/password_forget"; 7 | -------------------------------------------------------------------------------- /src/firebase/AuthUserContext.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const AuthUserContext = React.createContext(null); 4 | -------------------------------------------------------------------------------- /src/firebase/auth.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "./firebase"; 2 | 3 | // Sign Up 4 | export const doCreateUserWithEmailAndPassword = ( 5 | email: string, 6 | password: string 7 | ) => auth.createUserWithEmailAndPassword(email, password); 8 | 9 | // Sign In 10 | export const doSignInWithEmailAndPassword = (email: string, password: string) => 11 | auth.signInWithEmailAndPassword(email, password); 12 | 13 | // Sign out 14 | export const doSignOut = () => auth.signOut(); 15 | 16 | // Password Reset 17 | export const doPasswordReset = (email: string) => 18 | auth.sendPasswordResetEmail(email); 19 | 20 | // Password Change 21 | export const doPasswordUpdate = async (password: string) => { 22 | if (auth.currentUser) { 23 | await auth.currentUser.updatePassword(password); 24 | } 25 | throw Error("No auth.currentUser!"); 26 | }; 27 | -------------------------------------------------------------------------------- /src/firebase/db.ts: -------------------------------------------------------------------------------- 1 | import { db } from "./firebase"; 2 | 3 | // User API 4 | export const doCreateUser = (id: string, username: string, email: string) => 5 | db.ref(`users/${id}`).set({ 6 | email, 7 | username 8 | }); 9 | 10 | export const onceGetUsers = () => db.ref("users").once("value"); 11 | -------------------------------------------------------------------------------- /src/firebase/firebase.ts: -------------------------------------------------------------------------------- 1 | import * as firebase from "firebase/app"; 2 | import "firebase/auth"; 3 | import "firebase/database"; 4 | 5 | const config = { 6 | apiKey: "YOUR_API_KEY", 7 | authDomain: "YOUR_AUTH_DOMAIN", 8 | databaseURL: "YOUR_DATABASE_URL", 9 | messagingSenderId: "YOUR_MESSAGING_SENDER_ID", 10 | projectId: "YOUR PROJECT_ID", 11 | storageBucket: "YOUR_STORAGE_BUCKET" 12 | }; 13 | 14 | if (!firebase.apps.length) { 15 | firebase.initializeApp(config); 16 | } 17 | 18 | export const auth = firebase.auth(); 19 | export const db = firebase.database(); 20 | -------------------------------------------------------------------------------- /src/firebase/index.ts: -------------------------------------------------------------------------------- 1 | import * as auth from "./auth"; 2 | import * as db from "./db"; 3 | import * as firebase from "./firebase"; 4 | 5 | export { auth, db, firebase }; 6 | -------------------------------------------------------------------------------- /src/firebase/withAuthentication.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { firebase } from "../firebase"; 3 | import { AuthUserContext } from "./AuthUserContext"; 4 | 5 | interface InterfaceProps { 6 | authUser?: any; 7 | } 8 | 9 | interface InterfaceState { 10 | authUser?: any; 11 | } 12 | 13 | export const withAuthentication = (Component: any) => { 14 | class WithAuthentication extends React.Component< 15 | InterfaceProps, 16 | InterfaceState 17 | > { 18 | constructor(props: any) { 19 | super(props); 20 | 21 | this.state = { 22 | authUser: null 23 | }; 24 | } 25 | 26 | public componentDidMount() { 27 | firebase.auth.onAuthStateChanged(authUser => { 28 | authUser 29 | ? this.setState(() => ({ authUser })) 30 | : this.setState(() => ({ authUser: null })); 31 | }); 32 | } 33 | 34 | public render() { 35 | const { authUser } = this.state; 36 | 37 | return ( 38 | 39 | 40 | 41 | ); 42 | } 43 | } 44 | return WithAuthentication; 45 | }; 46 | -------------------------------------------------------------------------------- /src/firebase/withAuthorization.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { withRouter } from "react-router-dom"; 3 | import * as routes from "../constants/routes"; 4 | import { firebase } from "../firebase"; 5 | import { AuthUserContext } from "./AuthUserContext"; 6 | 7 | interface InterfaceProps { 8 | history?: any; 9 | } 10 | 11 | export const withAuthorization = (condition: any) => (Component: any) => { 12 | class WithAuthorization extends React.Component { 13 | public componentDidMount() { 14 | firebase.auth.onAuthStateChanged(authUser => { 15 | if (!condition(authUser)) { 16 | this.props.history.push(routes.SIGN_IN); 17 | } 18 | }); 19 | } 20 | 21 | public render() { 22 | return ( 23 | 24 | {authUser => (authUser ? : null)} 25 | 26 | ); 27 | } 28 | } 29 | 30 | return withRouter(WithAuthorization as any); 31 | }; 32 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { App } from "./components/App"; 4 | import "./index.css"; 5 | import registerServiceWorker from "./registerServiceWorker"; 6 | 7 | ReactDOM.render(, document.getElementById("root")); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /src/pages/Account/PasswordChangeForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { auth } from "../../firebase"; 3 | 4 | interface InterfaceProps { 5 | error?: any; 6 | history?: any; 7 | passwordOne?: string; 8 | passwordTwo?: string; 9 | } 10 | 11 | interface InterfaceState { 12 | error?: any; 13 | passwordOne?: string; 14 | passwordTwo?: string; 15 | } 16 | 17 | export class PasswordChangeForm extends React.Component< 18 | InterfaceProps, 19 | InterfaceState 20 | > { 21 | private static INITIAL_STATE = { 22 | error: null, 23 | passwordOne: "", 24 | passwordTwo: "" 25 | }; 26 | 27 | private static propKey(propertyName: string, value: string): object { 28 | return { [propertyName]: value }; 29 | } 30 | 31 | constructor(props: any) { 32 | super(props); 33 | this.state = { ...PasswordChangeForm.INITIAL_STATE }; 34 | } 35 | 36 | public onSubmit = (event: any) => { 37 | const { passwordOne }: any = this.state; 38 | 39 | auth 40 | .doPasswordUpdate(passwordOne) 41 | .then(() => { 42 | this.setState(() => ({ ...PasswordChangeForm.INITIAL_STATE })); 43 | }) 44 | .catch(error => { 45 | this.setState(PasswordChangeForm.propKey("error", error)); 46 | }); 47 | 48 | event.preventDefault(); 49 | }; 50 | 51 | public render() { 52 | const { passwordOne, passwordTwo, error }: any = this.state; 53 | 54 | const isInvalid = passwordOne !== passwordTwo || passwordOne === ""; 55 | 56 | return ( 57 |
this.onSubmit(event)}> 58 | this.setStateWithEvent(event, "passwordOne")} 61 | type="password" 62 | placeholder="New Password" 63 | /> 64 | this.setStateWithEvent(event, "passwordTwo")} 67 | type="password" 68 | placeholder="Confirm New Password" 69 | /> 70 | 73 | 74 | {error &&

{error.message}

} 75 |
76 | ); 77 | } 78 | 79 | private setStateWithEvent(event: any, columnType: string): void { 80 | this.setState( 81 | PasswordChangeForm.propKey(columnType, (event.target as any).value) 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/pages/Account/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { AuthUserContext } from "../../firebase/AuthUserContext"; 3 | import { withAuthorization } from "../../firebase/withAuthorization"; 4 | import { PasswordForgetForm } from "../PasswordForget/PasswordForgetForm"; 5 | import { PasswordChangeForm } from "./PasswordChangeForm"; 6 | 7 | export const AccountComponent = () => ( 8 | 9 | {authUser => ( 10 |
11 |

Account: {(authUser as any).email}

12 | 13 | 14 |
15 | )} 16 |
17 | ); 18 | 19 | const authCondition = (authUser: any) => !!authUser; 20 | 21 | export const Account = withAuthorization(authCondition)(AccountComponent); 22 | -------------------------------------------------------------------------------- /src/pages/Home/UserList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface InterfaceProps { 4 | users?: any; 5 | } 6 | 7 | export class UserList extends React.Component { 8 | constructor(props: any) { 9 | super(props); 10 | } 11 | 12 | public render() { 13 | const { users }: any = this.props; 14 | 15 | return ( 16 |
17 |

List of User name

18 |

(Saved on Sign Up in Firebase Database)

19 | 20 |
    21 | {Object.keys(users).map(key => { 22 | return
  • {users[key].username}
  • ; 23 | })} 24 |
25 |
26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { db } from "../../firebase"; 3 | import { withAuthorization } from "../../firebase/withAuthorization"; 4 | import { UserList } from "./UserList"; 5 | 6 | class HomeComponent extends React.Component { 7 | constructor(props: any) { 8 | super(props); 9 | 10 | this.state = { 11 | users: null 12 | }; 13 | } 14 | 15 | public componentDidMount() { 16 | db.onceGetUsers().then(snapshot => 17 | this.setState(() => ({ users: snapshot.val() })) 18 | ); 19 | } 20 | 21 | public render() { 22 | const { users }: any = this.state; 23 | 24 | return ( 25 |
26 |

Home Page

27 |

The Home Page is accessible by every signed in user.

28 | 29 | {!!users && } 30 |
31 | ); 32 | } 33 | } 34 | 35 | const authCondition = (authUser: any) => !!authUser; 36 | 37 | export const Home = withAuthorization(authCondition)(HomeComponent); 38 | -------------------------------------------------------------------------------- /src/pages/Landing/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const Landing = () => { 4 | return ( 5 |
6 |

Landing Page

7 |
8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /src/pages/PasswordForget/PasswordForgetForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { auth } from "../../firebase"; 3 | 4 | export class PasswordForgetForm extends React.Component { 5 | private static INITIAL_STATE = { 6 | email: "", 7 | error: null 8 | }; 9 | 10 | private static propKey(propertyName: string, value: string) { 11 | return { [propertyName]: value }; 12 | } 13 | 14 | constructor(props: any) { 15 | super(props); 16 | 17 | this.state = { ...PasswordForgetForm.INITIAL_STATE }; 18 | } 19 | 20 | public onSubmit = (event: any) => { 21 | const { email }: any = this.state; 22 | 23 | auth 24 | .doPasswordReset(email) 25 | .then(() => { 26 | this.setState(() => ({ ...PasswordForgetForm.INITIAL_STATE })); 27 | }) 28 | .catch(error => { 29 | this.setState(PasswordForgetForm.propKey("error", error)); 30 | }); 31 | 32 | event.preventDefault(); 33 | }; 34 | 35 | public render() { 36 | const { email, error }: any = this.state; 37 | const isInvalid = email === ""; 38 | 39 | return ( 40 |
this.onSubmit(event)}> 41 | this.setStateWithEvent(event, "email")} 44 | type="text" 45 | placeholder="Email Address" 46 | /> 47 | 50 | 51 | {error &&

{error.message}

} 52 |
53 | ); 54 | } 55 | 56 | private setStateWithEvent(event: any, columnType: string): void { 57 | this.setState( 58 | PasswordForgetForm.propKey(columnType, (event.target as any).value) 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/pages/PasswordForget/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { PasswordForgetForm } from "./PasswordForgetForm"; 4 | 5 | export const PasswordForget = () => ( 6 |
7 |

PasswordForget

8 | 9 |
10 | ); 11 | 12 | export const PasswordForgetLink = () => ( 13 |

14 | Forgot Password? 15 |

16 | ); 17 | -------------------------------------------------------------------------------- /src/pages/SignIn/SignInForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as routes from "../../constants/routes"; 3 | import { auth } from "../../firebase"; 4 | 5 | interface InterfaceProps { 6 | email?: string; 7 | error?: any; 8 | history?: any; 9 | password?: string; 10 | } 11 | 12 | interface InterfaceState { 13 | email: string; 14 | error: any; 15 | password: string; 16 | } 17 | 18 | export class SignInForm extends React.Component< 19 | InterfaceProps, 20 | InterfaceState 21 | > { 22 | private static INITIAL_STATE = { 23 | email: "", 24 | error: null, 25 | password: "" 26 | }; 27 | 28 | private static propKey(propertyName: string, value: any): object { 29 | return { [propertyName]: value }; 30 | } 31 | 32 | constructor(props: InterfaceProps) { 33 | super(props); 34 | 35 | this.state = { ...SignInForm.INITIAL_STATE }; 36 | } 37 | 38 | public onSubmit = (event: any) => { 39 | const { email, password } = this.state; 40 | 41 | const { history } = this.props; 42 | 43 | auth 44 | .doSignInWithEmailAndPassword(email, password) 45 | .then(() => { 46 | this.setState(() => ({ ...SignInForm.INITIAL_STATE })); 47 | history.push(routes.HOME); 48 | }) 49 | .catch(error => { 50 | this.setState(SignInForm.propKey("error", error)); 51 | }); 52 | 53 | event.preventDefault(); 54 | }; 55 | 56 | public render() { 57 | const { email, password, error } = this.state; 58 | 59 | const isInvalid = password === "" || email === ""; 60 | 61 | return ( 62 |
this.onSubmit(event)}> 63 | this.setStateWithEvent(event, "email")} 66 | type="text" 67 | placeholder="Email Address" 68 | /> 69 | this.setStateWithEvent(event, "password")} 72 | type="password" 73 | placeholder="Password" 74 | /> 75 | 78 | 79 | {error &&

{error.message}

} 80 |
81 | ); 82 | } 83 | 84 | private setStateWithEvent(event: any, columnType: string): void { 85 | this.setState(SignInForm.propKey(columnType, (event.target as any).value)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/pages/SignIn/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { withRouter } from "react-router-dom"; 3 | import { PasswordForgetLink } from "../PasswordForget"; 4 | import { SignUpLink } from "../SignUp"; 5 | import { SignInForm } from "./SignInForm"; 6 | 7 | const SignInComponent = ({ history }: { [key: string]: any }) => ( 8 |
9 |

SignIn

10 | 11 | 12 | 13 |
14 | ); 15 | 16 | export const SignIn = withRouter(SignInComponent); 17 | -------------------------------------------------------------------------------- /src/pages/SignUp/SingUpForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as routes from "../../constants/routes"; 3 | import { auth, db } from "../../firebase"; 4 | 5 | interface InterfaceProps { 6 | email?: string; 7 | error?: any; 8 | history?: any; 9 | passwordOne?: string; 10 | passwordTwo?: string; 11 | username?: string; 12 | } 13 | 14 | interface InterfaceState { 15 | email: string; 16 | error: any; 17 | passwordOne: string; 18 | passwordTwo: string; 19 | username: string; 20 | } 21 | 22 | export class SignUpForm extends React.Component< 23 | InterfaceProps, 24 | InterfaceState 25 | > { 26 | private static INITIAL_STATE = { 27 | email: "", 28 | error: null, 29 | passwordOne: "", 30 | passwordTwo: "", 31 | username: "" 32 | }; 33 | 34 | private static propKey(propertyName: string, value: any): object { 35 | return { [propertyName]: value }; 36 | } 37 | 38 | constructor(props: InterfaceProps) { 39 | super(props); 40 | this.state = { ...SignUpForm.INITIAL_STATE }; 41 | } 42 | 43 | public onSubmit(event: any) { 44 | event.preventDefault(); 45 | 46 | const { email, passwordOne, username } = this.state; 47 | const { history } = this.props; 48 | 49 | auth 50 | .doCreateUserWithEmailAndPassword(email, passwordOne) 51 | .then((authUser: any) => { 52 | 53 | // Create a user in your own accessible Firebase Database too 54 | db.doCreateUser(authUser.user.uid, username, email) 55 | .then(() => { 56 | 57 | this.setState(() => ({ ...SignUpForm.INITIAL_STATE })); 58 | history.push(routes.HOME); 59 | }) 60 | .catch(error => { 61 | this.setState(SignUpForm.propKey("error", error)); 62 | }); 63 | }) 64 | .catch(error => { 65 | this.setState(SignUpForm.propKey("error", error)); 66 | }); 67 | } 68 | 69 | public render() { 70 | const { username, email, passwordOne, passwordTwo, error } = this.state; 71 | 72 | const isInvalid = 73 | passwordOne !== passwordTwo || 74 | passwordOne === "" || 75 | email === "" || 76 | username === ""; 77 | 78 | return ( 79 |
this.onSubmit(event)}> 80 | this.setStateWithEvent(event, "username")} 83 | type="text" 84 | placeholder="Full Name" 85 | /> 86 | this.setStateWithEvent(event, "email")} 89 | type="text" 90 | placeholder="Email Address" 91 | /> 92 | this.setStateWithEvent(event, "passwordOne")} 95 | type="password" 96 | placeholder="Password" 97 | /> 98 | this.setStateWithEvent(event, "passwordTwo")} 101 | type="password" 102 | placeholder="Confirm Password" 103 | /> 104 | 107 | 108 | {error &&

{error.message}

} 109 |
110 | ); 111 | } 112 | 113 | private setStateWithEvent(event: any, columnType: string) { 114 | this.setState(SignUpForm.propKey(columnType, (event.target as any).value)); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/pages/SignUp/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Link, withRouter } from "react-router-dom"; 3 | import * as routes from "../../constants/routes"; 4 | import { SignUpForm } from "./SingUpForm"; 5 | 6 | const SignUpComponent = () => ( 7 |
8 |

SignUp

9 | 10 |
11 | ); 12 | 13 | export const SignUpLink = () => ( 14 |

15 | Don't have an account? Sign Up 16 |

17 | ); 18 | 19 | export const SignUp = withRouter(SignUpComponent); 20 | -------------------------------------------------------------------------------- /src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | // In production, we register a service worker to serve assets from local cache. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on the 'N+1' visit to a page, since previously 7 | // cached resources are updated in the background. 8 | 9 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 10 | // This link also includes instructions on opting out of this behavior. 11 | 12 | const isLocalhost = Boolean( 13 | window.location.hostname === "localhost" || 14 | // [::1] is the IPv6 localhost address. 15 | window.location.hostname === "[::1]" || 16 | // 127.0.0.1/8 is considered localhost for IPv4. 17 | window.location.hostname.match( 18 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 19 | ) 20 | ); 21 | 22 | export default function register() { 23 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { 24 | // The URL constructor is available in all browsers that support SW. 25 | const publicUrl = new URL( 26 | process.env.PUBLIC_URL!, 27 | window.location.toString() 28 | ); 29 | if (publicUrl.origin !== window.location.origin) { 30 | // Our service worker won't work if PUBLIC_URL is on a different origin 31 | // from what our page is served on. This might happen if a CDN is used to 32 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 33 | return; 34 | } 35 | 36 | window.addEventListener("load", () => { 37 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 38 | 39 | if (isLocalhost) { 40 | // This is running on localhost. Lets check if a service worker still exists or not. 41 | checkValidServiceWorker(swUrl); 42 | 43 | // Add some additional logging to localhost, pointing developers to the 44 | // service worker/PWA documentation. 45 | navigator.serviceWorker.ready.then(() => { 46 | console.log( 47 | "This web app is being served cache-first by a service " + 48 | "worker. To learn more, visit https://goo.gl/SC7cgQ" 49 | ); 50 | }); 51 | } else { 52 | // Is not local host. Just register service worker 53 | registerValidSW(swUrl); 54 | } 55 | }); 56 | } 57 | } 58 | 59 | function registerValidSW(swUrl: string) { 60 | navigator.serviceWorker 61 | .register(swUrl) 62 | .then(registration => { 63 | registration.onupdatefound = () => { 64 | const installingWorker = registration.installing; 65 | if (installingWorker) { 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === "installed") { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the old content will have been purged and 70 | // the fresh content will have been added to the cache. 71 | // It's the perfect time to display a 'New content is 72 | // available; please refresh.' message in your web app. 73 | console.log("New content is available; please refresh."); 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 | } 81 | }; 82 | } 83 | }; 84 | }) 85 | .catch(error => { 86 | console.error("Error during service worker registration:", error); 87 | }); 88 | } 89 | 90 | function checkValidServiceWorker(swUrl: string) { 91 | // Check if the service worker can be found. If it can't reload the page. 92 | fetch(swUrl) 93 | .then(response => { 94 | // Ensure service worker exists, and that we really are getting a JS file. 95 | if ( 96 | response.status === 404 || 97 | response.headers.get("content-type")!.indexOf("javascript") === -1 98 | ) { 99 | // No service worker found. Probably a different app. Reload the page. 100 | navigator.serviceWorker.ready.then(registration => { 101 | registration.unregister().then(() => { 102 | window.location.reload(); 103 | }); 104 | }); 105 | } else { 106 | // Service worker found. Proceed as normal. 107 | registerValidSW(swUrl); 108 | } 109 | }) 110 | .catch(() => { 111 | console.log( 112 | "No internet connection found. App is running in offline mode." 113 | ); 114 | }); 115 | } 116 | 117 | export function unregister() { 118 | if ("serviceWorker" in navigator) { 119 | navigator.serviceWorker.ready.then(registration => { 120 | registration.unregister(); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["es6", "dom"], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true 20 | }, 21 | "exclude": [ 22 | "node_modules", 23 | "build", 24 | "scripts", 25 | "acceptance-tests", 26 | "webpack", 27 | "jest", 28 | "src/setupTests.ts" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 3 | "rules": { 4 | "jsx-no-lambda": false 5 | }, 6 | "linterOptions": { 7 | "exclude": ["config/**/*.js", "node_modules/**/*.ts"] 8 | } 9 | } 10 | --------------------------------------------------------------------------------