diff --git a/.gitignore b/.gitignore index 4d29575..532eddc 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +.env \ No newline at end of file diff --git a/src/App.js b/src/App.js index e5a65f4..a76cb14 100644 --- a/src/App.js +++ b/src/App.js @@ -1,8 +1,15 @@ +import { useContext } from "react"; import Header from "components/Header"; import { Outlet } from "react-router-dom"; import { Container, Layout } from "./styles/globalStyles"; +import FirebaseAuthService from "./firebase/FirebaseAuthService"; +import GlobalContext from "store/context"; function App() { + const { setUser } = useContext(GlobalContext); + + FirebaseAuthService.subscribeToAuthChanges(setUser); + return (
diff --git a/src/components/Login/SignIn/index.js b/src/components/Login/SignIn/index.js new file mode 100644 index 0000000..438782e --- /dev/null +++ b/src/components/Login/SignIn/index.js @@ -0,0 +1,97 @@ +import FirebaseAuthService from "../../../firebase/FirebaseAuthService"; +import { useState, useContext } from "react"; +import { Content, ButtonGroup, FormGroup } from "./styles"; +import MainButton from "components/MainButton"; +import GlobalContext from "store/context"; + +const SignIn = () => { + const { user } = useContext(GlobalContext); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + await FirebaseAuthService.loginUser(username, password); + setUsername(""); + setPassword(""); + } catch (error) { + alert(error.message); + } + }; + + const handleLoginWithGoogle = async () => { + try { + await FirebaseAuthService.loginWithGoogle(); + } catch (error) { + alert(error.message); + } + }; + + const handleSendResetPasswordEmail = async () => { + if (!username) { + alert("Missing username"); + return; + } + + try { + await FirebaseAuthService.resetPassword(username); + alert("Sent the password reset email"); + } catch (error) { + alert(error.message); + } + }; + + const handleLogout = () => { + FirebaseAuthService.logoutUser(); + }; + + return ( + + {user ? ( +
+

Welcome, {user.email}

+ Logout +
+ ) : ( + <> +

Create an account

+ + + + + + + + + + + + )} +
+ ); +}; + +export default SignIn; diff --git a/src/components/Login/SignIn/styles.js b/src/components/Login/SignIn/styles.js new file mode 100644 index 0000000..f12ddf7 --- /dev/null +++ b/src/components/Login/SignIn/styles.js @@ -0,0 +1,49 @@ +import styled from "styled-components"; + +export const Content = styled.div` + height: 100%; + padding: 3rem 0; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-content: center; + + & > h1 { + margin-bottom: 3rem; + } +`; + +export const FormGroup = styled.form` + & > label { + font-size: 1.4rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: stretch; + & > input { + font-size: 1.6rem; + margin-bottom: 1rem; + padding: 1rem; + border-radius: 0.5rem; + border: none; + } + } +`; + +export const ButtonGroup = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: stretch; + gap: 1rem; + & > button { + border: none; + padding: 1rem 5rem; + border-radius: 0.5rem; + background-color: #1aae9f; + color: #fff; + cursor: pointer; + font-weight: 600; + font-size: 1.6rem; + } +`; diff --git a/src/components/Login/SignUp/index.js b/src/components/Login/SignUp/index.js new file mode 100644 index 0000000..c87682c --- /dev/null +++ b/src/components/Login/SignUp/index.js @@ -0,0 +1,69 @@ +import FirebaseAuthService from "../../../firebase/FirebaseAuthService"; +import { useState, useContext } from "react"; +import { Content, ButtonGroup, FormGroup } from "./styles"; +import MainButton from "components/MainButton"; +import GlobalContext from "store/context"; + +const SignUp = () => { + const { user } = useContext(GlobalContext); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + await FirebaseAuthService.registerUser(username, password); + setUsername(""); + setPassword(""); + } catch (error) { + alert(error.message); + } + }; + + const handleLogout = () => { + FirebaseAuthService.logoutUser(); + }; + + return ( + + {user ? ( +
+

Welcome, {user.email}

+ Logout +
+ ) : ( + <> +

Create an account

+ + + + + + + + + )} +
+ ); +}; + +export default SignUp; diff --git a/src/components/Login/SignUp/styles.js b/src/components/Login/SignUp/styles.js new file mode 100644 index 0000000..2435c34 --- /dev/null +++ b/src/components/Login/SignUp/styles.js @@ -0,0 +1,48 @@ +import styled from "styled-components"; + +export const Content = styled.div` + height: 100%; + padding: 3rem 0; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-content: center; + + & > h1 { + margin-bottom: 3rem; + } +`; + +export const FormGroup = styled.form` + & > label { + font-size: 1.4rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: stretch; + & > input { + font-size: 1.6rem; + margin-bottom: 1rem; + padding: 1rem; + border-radius: 0.5rem; + border: none; + } + } +`; + +export const ButtonGroup = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: stretch; + & > button { + border: none; + padding: 1rem 5rem; + border-radius: 0.5rem; + background-color: #1aae9f; + color: #fff; + cursor: pointer; + font-weight: 600; + font-size: 1.6rem; + } +`; diff --git a/src/firebase/FirebaseAuthService.js b/src/firebase/FirebaseAuthService.js new file mode 100644 index 0000000..1fc505c --- /dev/null +++ b/src/firebase/FirebaseAuthService.js @@ -0,0 +1,65 @@ +import firebase from "./FirebaseConfig"; +import { + createUserWithEmailAndPassword, + signInWithEmailAndPassword, + sendPasswordResetEmail, + signInWithPopup, + GoogleAuthProvider, + onAuthStateChanged, +} from "firebase/auth"; + +const auth = firebase.auth; + +const registerUser = async (email, password) => { + try { + return await createUserWithEmailAndPassword(auth, email, password); + } catch (error) { + throw error; + } +}; + +const loginUser = async (email, password) => { + try { + return await signInWithEmailAndPassword(auth, email, password); + } catch (error) { + throw error; + } +}; + +const logoutUser = () => { + auth.signOut(); +}; + +const loginWithGoogle = async () => { + const provider = new GoogleAuthProvider(); + try { + return await signInWithPopup(auth, provider); + } catch (error) { + throw error; + } +}; + +const subscribeToAuthChanges = (handleAuthChanges) => { + onAuthStateChanged(auth, (user) => { + handleAuthChanges(user); + }); +}; + +const resetPassword = async (email) => { + try { + return await sendPasswordResetEmail(auth, email); + } catch (error) { + throw error; + } +}; + +const FirebaseAuthService = { + registerUser, + loginUser, + logoutUser, + resetPassword, + loginWithGoogle, + subscribeToAuthChanges, +}; + +export default FirebaseAuthService; diff --git a/src/firebase/FirebaseConfig.js b/src/firebase/FirebaseConfig.js new file mode 100644 index 0000000..7c9b050 --- /dev/null +++ b/src/firebase/FirebaseConfig.js @@ -0,0 +1,25 @@ +import { initializeApp } from "firebase/app"; +import { getAnalytics } from "firebase/analytics"; +import { getAuth } from "firebase/auth"; + +const config = { + apiKey: process.env.REACT_APP_API_KEY, + authDomain: process.env.REACT_APP_AUTH_DOMAIN, + projectId: process.env.REACT_APP_PROJECT_ID, + storageBucket: process.env.REACT_APP_STORAGE_BUCKET, + messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID, + appId: process.env.REACT_APP_APP_ID, + measurementId: process.env.REACT_APP_MEASUREMENT_ID, +}; + +// Initialize Firebase +const firebaseApp = initializeApp(config); +const auth = getAuth(firebaseApp); +const analytics = getAnalytics(firebaseApp); + +const firebaseConfig = { + auth, + analytics, +}; + +export default firebaseConfig; diff --git a/src/index.js b/src/index.js index f4bec6d..e7ab7c2 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,8 @@ import EditTodo from "components/Todo/EditTodo"; import ViewTodo from "components/Todo/ViewTodo"; import Reorder from "components/Criteria/Reorder"; import EditCriteriaList from "components/Criteria/EditCriteriaList"; +import SignUp from "components/Login/SignUp"; +import SignIn from "components/Login/SignIn"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( @@ -24,6 +26,8 @@ root.render( }> + }> + }> }> } /> } /> diff --git a/src/store/context/index.js b/src/store/context/index.js index 329e2b0..d286287 100644 --- a/src/store/context/index.js +++ b/src/store/context/index.js @@ -7,6 +7,10 @@ const GlobalContext = createContext(); export const GlobalContextProvider = (props) => { const [state, dispatch] = useReducer(reducer, mock); + const setUser = (user) => { + dispatch({ type: "SET_USER", payload: user }); + }; + const addTodo = (todo) => { dispatch({ type: "ADD_TODO", payload: todo }); }; @@ -46,6 +50,7 @@ export const GlobalContextProvider = (props) => { return ( { switch (action.type) { + case "SET_USER": + return { + ...state, + user: action.payload, + }; case "ADD_CRITERIA": return { ...state,