├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.js ├── App.test.js ├── components │ ├── auth │ │ ├── ConfirmForm.js │ │ └── LoginForm.js │ ├── dashboard │ │ └── Dashboard.js │ ├── layout │ │ ├── ColorModeSwitcher.js │ │ ├── Layout.js │ │ ├── Nav.js │ │ └── NotFound.js │ └── route │ │ └── PrivateRoute.js ├── hooks │ └── useAuth.js ├── index.js ├── reportWebVitals.js ├── serviceWorker.js ├── setupTests.js └── test-utils.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "semi": true 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Passwordless login with Firebase and React 2 | 3 | This repo is from a complete step-by-step tutorial by [Skillthrive](https://youtu.be/8Xnpipa2k2M). 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firebase-auth-video", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@chakra-ui/react": "^1.0.0", 7 | "@emotion/react": "^11.0.0", 8 | "@emotion/styled": "^11.0.0", 9 | "@testing-library/jest-dom": "^5.9.0", 10 | "@testing-library/react": "^10.2.1", 11 | "@testing-library/user-event": "^12.0.2", 12 | "firebase": "^8.2.9", 13 | "framer-motion": ">=3.0.0", 14 | "react": "^17.0.1", 15 | "react-dom": "^17.0.1", 16 | "react-hook-form": "^6.15.4", 17 | "react-icons": "^3.0.0", 18 | "react-router-dom": "^5.2.0", 19 | "react-scripts": "4.0.3", 20 | "web-vitals": "^0.2.2" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": "react-app" 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterbecton/react-firebase-passwordless/ca39b6c11bda4e22ee4c92959c5a13bf6e207133/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterbecton/react-firebase-passwordless/ca39b6c11bda4e22ee4c92959c5a13bf6e207133/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterbecton/react-firebase-passwordless/ca39b6c11bda4e22ee4c92959c5a13bf6e207133/public/logo512.png -------------------------------------------------------------------------------- /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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 3 | 4 | import Layout from './components/layout/Layout'; 5 | import LoginForm from './components/auth/LoginForm'; 6 | import ConfirmForm from './components/auth/ConfirmForm'; 7 | import PrivateRoute from './components/route/PrivateRoute'; 8 | import Dashboard from './components/dashboard/Dashboard'; 9 | import NotFound from './components/layout/NotFound'; 10 | 11 | function App() { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | 34 | export default App; 35 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { screen } from '@testing-library/react'; 3 | import { render } from './test-utils'; 4 | import App from './App'; 5 | 6 | test('renders learn react link', () => { 7 | render(); 8 | const linkElement = screen.getByText(/learn chakra/i); 9 | expect(linkElement).toBeInTheDocument(); 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/auth/ConfirmForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | import { 4 | Heading, 5 | GridItem, 6 | Alert, 7 | AlertIcon, 8 | FormLabel, 9 | FormControl, 10 | Input, 11 | Button, 12 | } from '@chakra-ui/react'; 13 | import { useHistory, useLocation } from 'react-router-dom'; 14 | 15 | import { useAuth } from '../../hooks/useAuth'; 16 | 17 | const ConfirmForm = () => { 18 | const { handleSubmit, register, errors, setError, formState } = useForm(); 19 | 20 | const { signInWithEmailLink } = useAuth(); 21 | 22 | const history = useHistory(); 23 | 24 | const location = useLocation(); 25 | 26 | const onSubmit = async data => { 27 | try { 28 | await signInWithEmailLink(data.email, location.search); 29 | history.push('/'); 30 | } catch (error) { 31 | setError('email', { 32 | type: 'manual', 33 | message: error.message, 34 | }); 35 | } 36 | }; 37 | 38 | return ( 39 | 44 | 45 | Confirm Email 46 | 47 | {errors.email && ( 48 | 49 | 50 | {errors.email.message} 51 | 52 | )} 53 |
54 | 55 | Email 56 | 57 | 65 | 66 |
67 |
68 | ); 69 | }; 70 | 71 | export default ConfirmForm; 72 | -------------------------------------------------------------------------------- /src/components/auth/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | import { 4 | Heading, 5 | GridItem, 6 | Alert, 7 | AlertIcon, 8 | FormLabel, 9 | FormControl, 10 | Input, 11 | Button, 12 | } from '@chakra-ui/react'; 13 | 14 | import { useAuth } from '../../hooks/useAuth'; 15 | 16 | const LoginForm = () => { 17 | const { handleSubmit, register, errors, setError, formState } = useForm(); 18 | 19 | const { sendSignInLinkToEmail } = useAuth(); 20 | 21 | const onSubmit = async data => { 22 | try { 23 | await sendSignInLinkToEmail(data.email); 24 | } catch (error) { 25 | setError('email', { 26 | type: 'manual', 27 | message: error.message, 28 | }); 29 | } 30 | }; 31 | 32 | return ( 33 | 38 | 39 | Login 40 | 41 | {errors.email && ( 42 | 43 | 44 | {errors.email.message} 45 | 46 | )} 47 | {formState.isSubmitSuccessful && ( 48 | 49 | 50 | Check your email to complete login! 51 | 52 | )} 53 |
54 | 55 | Email 56 | 57 | 65 | 66 |
67 |
68 | ); 69 | }; 70 | 71 | export default LoginForm; 72 | -------------------------------------------------------------------------------- /src/components/dashboard/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Heading, GridItem, Text } from '@chakra-ui/react'; 3 | 4 | import { useAuth } from '../../hooks/useAuth'; 5 | 6 | const Dashboard = () => { 7 | const { user } = useAuth(); 8 | 9 | return ( 10 | 15 | 16 | Dashboard 17 | 18 | Welcome, {user.email}! 19 | 20 | ); 21 | }; 22 | 23 | export default Dashboard; 24 | -------------------------------------------------------------------------------- /src/components/layout/ColorModeSwitcher.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useColorMode, useColorModeValue, IconButton } from '@chakra-ui/react'; 3 | import { FaMoon, FaSun } from 'react-icons/fa'; 4 | 5 | export const ColorModeSwitcher = props => { 6 | const { toggleColorMode } = useColorMode(); 7 | const text = useColorModeValue('dark', 'light'); 8 | const SwitchIcon = useColorModeValue(FaMoon, FaSun); 9 | 10 | return ( 11 | } 20 | {...props} 21 | /> 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/layout/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Grid } from '@chakra-ui/react'; 3 | 4 | import { ColorModeSwitcher } from './ColorModeSwitcher'; 5 | import Nav from './Nav'; 6 | 7 | const Layout = ({ children }) => { 8 | return ( 9 | 16 |