From 508005350cc85d2e86a01453fda184e96fde88f3 Mon Sep 17 00:00:00 2001 From: James Han Date: Sat, 16 Nov 2024 13:31:21 -0500 Subject: [PATCH 1/5] Created Signin/Signup with Email functionality for Firebase, Fixed Bug in Google Auth --- frontend/src/assets/google-icon.svg | 1 + frontend/src/pages/SignIn/SignIn.css | 155 ++++++++++++++++++++------ frontend/src/pages/SignIn/SignIn.tsx | 157 +++++++++++++++++++++------ 3 files changed, 247 insertions(+), 66 deletions(-) create mode 100644 frontend/src/assets/google-icon.svg diff --git a/frontend/src/assets/google-icon.svg b/frontend/src/assets/google-icon.svg new file mode 100644 index 0000000..8c3624a --- /dev/null +++ b/frontend/src/assets/google-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/pages/SignIn/SignIn.css b/frontend/src/pages/SignIn/SignIn.css index c4ebd82..883ee0d 100644 --- a/frontend/src/pages/SignIn/SignIn.css +++ b/frontend/src/pages/SignIn/SignIn.css @@ -1,32 +1,125 @@ -/* Login.css */ - .login-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100vh; - } - - .login-title { - font-size: 24px; - margin-bottom: 20px; - color: #333; - } - - .login-button { - padding: 10px 20px; - font-size: 16px; - font-weight: bold; - color: white; - background-color: #4285f4; - border: none; - border-radius: 4px; - cursor: pointer; - transition: background-color 0.3s ease; - } - - .login-button:hover { - background-color: #357ae8; - } - \ No newline at end of file + max-width: 400px; + margin: 50px auto; + padding: 20px; + border: 1px solid #ccc; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + text-align: left; + font-family: Arial, sans-serif; + background-color: #fff; +} + +.login-title { + margin-bottom: 20px; + font-size: 1.5rem; + color: #333; +} + +.login-form { + margin-bottom: 20px; +} + +.form-group { + display: flex; + flex-direction: column; + margin-bottom: 15px; +} + +label { + font-weight: bold; + color: #555; + margin-bottom: 5px; +} + +.form-input { + width: calc(100% - 20px); + margin-left: auto; + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 1rem; +} + +.form-button { + width: calc(100% - 20px); + margin-left: auto; + padding: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; + color: #fff; + background-color: #007bff; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.form-button:hover { + background-color: #0056b3; +} + + +.icon-placeholder { + margin-right: 10px; + font-size: 1.2rem; +} + +.error-message { + margin-top: 10px; + color: #ff4d4d; + font-size: 0.9rem; +} + +.toggle-message { + margin-top: 10px; + font-size: 0.9rem; + color: #555; + text-align: center; +} + +.toggle-link { + color: #007bff; + cursor: pointer; + text-decoration: underline; +} + +.toggle-link:hover { + color: #0056b3; +} + +/* Shared Button Styles */ +.button { + width: calc(100% - 20px); + margin-left: auto; + padding: 8px 12px; /* Smaller padding for compact buttons */ + display: flex; + align-items: center; /* Center the icon and text vertically */ + justify-content: center; /* Center contents horizontally */ + font-size: 0.9rem; /* Slightly smaller font size */ + font-weight: bold; + color: #fff; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +/* Google Button */ +.google-signin-button { + background-color: #db4437; +} + +.google-signin-button:hover { + background-color: #c23321; +} + +/* Icon Styling */ +.button-icon { + width: 16px; /* Make the icon smaller */ + height: 16px; /* Match the height */ + margin-right: 8px; /* Spacing between icon and text */ + vertical-align: middle; +} diff --git a/frontend/src/pages/SignIn/SignIn.tsx b/frontend/src/pages/SignIn/SignIn.tsx index 1536ad9..8afb7cb 100644 --- a/frontend/src/pages/SignIn/SignIn.tsx +++ b/frontend/src/pages/SignIn/SignIn.tsx @@ -1,57 +1,144 @@ import React, { useState } from "react"; import { auth } from "../../firebase"; -import { signInWithPopup, GoogleAuthProvider } from "firebase/auth"; -import { useNavigate } from 'react-router-dom'; +import { + signInWithPopup, + GoogleAuthProvider, + signInWithEmailAndPassword, + createUserWithEmailAndPassword, +} from "firebase/auth"; +import { useNavigate } from "react-router-dom"; import "./SignIn.css"; +// Import icon images +import googleIcon from "../../assets/google-icon.svg"; + const Login: React.FC = () => { - // Declare provider object for Google Auth const provider = new GoogleAuthProvider(); - - // State to track if user is authenticated - const [isAuthenticated, setIsAuthenticated] = useState(false); - // Accessing location/nickname information through local storage - const location = localStorage.getItem('selectedLocation') || 'Cassie Campbell'; - const nickname = localStorage.getItem('nickname') || 'User'; + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isSigningUp, setIsSigningUp] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); - // Hook for navigating to the next page const navigate = useNavigate(); const handleGoogleSignIn = () => { signInWithPopup(auth, provider) - .then((result) => { - const user = result.user; - console.log("User: ", user); - // Set authenticated state to true - setIsAuthenticated(true); - localStorage.setItem("firebaseUID", user.uid); - navigate('/active-view'); // Navigate to the next page - }).catch((error) => { - console.log("Error: ", error); - }); + .then((result) => { + const user = result.user; + console.log("User: ", user); + navigate("/active-view"); + }) + .catch((error) => { + console.log("Error: ", error); + setErrorMessage("Google sign-in failed. Please try again."); + }); + }; + + const handleEmailAuth = (e: React.FormEvent) => { + e.preventDefault(); + const authFunction = isSigningUp + ? createUserWithEmailAndPassword + : signInWithEmailAndPassword; + + authFunction(auth, email, password) + .then((result) => { + const user = result.user; + console.log("User: ", user); + navigate("/active-view"); + }) + .catch((error) => { + console.log("Error: ", error); + + let errorMsg = "An unexpected error occurred. Please try again."; + switch (error.code) { + case "auth/email-already-in-use": + errorMsg = "This email is already in use. Please try signing in instead."; + break; + case "auth/invalid-email": + errorMsg = "The email address is not valid. Please check and try again."; + break; + case "auth/weak-password": + errorMsg = "The password is too weak. Please choose a stronger password."; + break; + case "auth/wrong-password": + errorMsg = "Incorrect password. Please try again."; + break; + case "auth/user-not-found": + errorMsg = "No account found with this email. Please sign up first."; + break; + case "auth/network-request-failed": + errorMsg = "Network error. Please check your connection and try again."; + break; + case "auth/invalid-credential": + errorMsg = "Invalid credential. Please ensure your credentials are correct."; + break; + default: + errorMsg = error.message; + break; + } + setErrorMessage(errorMsg); + }); }; return (
-

Login with Google

- {!isAuthenticated && ( -
-

Welcome, {nickname}!

- +

{isSigningUp ? "Sign Up" : "Sign In"}

+ +
+
+ + setEmail(e.target.value)} + required + />
- )} - {/* Display something if the user is authenticated with Google */} - {isAuthenticated && ( -
-

You are successfully logged in!

-

Enjoy your stay, {nickname}!

+
+ + setPassword(e.target.value)} + required + />
- )} + + + + {errorMessage &&

{errorMessage}

} + +

+ {isSigningUp + ? "Already have an account?" + : "Don't have an account?"}{" "} + { + setIsSigningUp(!isSigningUp); + setErrorMessage(""); + }} + > + {isSigningUp ? "Sign In" : "Sign Up"} + +

+ +
+ + +
); }; -export default Login; \ No newline at end of file +export default Login; From 85865bd906ba0968a9163b2deb64abe23795a047 Mon Sep 17 00:00:00 2001 From: James Han Date: Sat, 16 Nov 2024 13:36:46 -0500 Subject: [PATCH 2/5] Added comments for better readability --- frontend/src/pages/SignIn/SignIn.tsx | 67 +++++++++++++--------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/frontend/src/pages/SignIn/SignIn.tsx b/frontend/src/pages/SignIn/SignIn.tsx index 8afb7cb..575060b 100644 --- a/frontend/src/pages/SignIn/SignIn.tsx +++ b/frontend/src/pages/SignIn/SignIn.tsx @@ -9,75 +9,72 @@ import { import { useNavigate } from "react-router-dom"; import "./SignIn.css"; -// Import icon images +// Import Google icon import googleIcon from "../../assets/google-icon.svg"; const Login: React.FC = () => { - const provider = new GoogleAuthProvider(); + const provider = new GoogleAuthProvider(); // Google authentication provider - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [isSigningUp, setIsSigningUp] = useState(false); - const [errorMessage, setErrorMessage] = useState(""); + // State management for inputs and toggles + const [email, setEmail] = useState(""); // Email input + const [password, setPassword] = useState(""); // Password input + const [isSigningUp, setIsSigningUp] = useState(false); // Toggle for sign-up or sign-in + const [errorMessage, setErrorMessage] = useState(""); // Error message display - const navigate = useNavigate(); + const navigate = useNavigate(); // Navigation hook for redirecting + // Handle Google sign-in const handleGoogleSignIn = () => { signInWithPopup(auth, provider) .then((result) => { - const user = result.user; - console.log("User: ", user); - navigate("/active-view"); + navigate("/active-view"); // Navigate to active view on success }) .catch((error) => { - console.log("Error: ", error); - setErrorMessage("Google sign-in failed. Please try again."); + setErrorMessage("Google sign-in failed. Please try again."); // Set error message }); }; + // Handle email-based authentication const handleEmailAuth = (e: React.FormEvent) => { - e.preventDefault(); + e.preventDefault(); // Prevent page reload const authFunction = isSigningUp ? createUserWithEmailAndPassword : signInWithEmailAndPassword; authFunction(auth, email, password) - .then((result) => { - const user = result.user; - console.log("User: ", user); - navigate("/active-view"); + .then(() => { + navigate("/active-view"); // Navigate to active view on success }) .catch((error) => { - console.log("Error: ", error); - + // Map Firebase error codes to user-friendly messages let errorMsg = "An unexpected error occurred. Please try again."; switch (error.code) { case "auth/email-already-in-use": errorMsg = "This email is already in use. Please try signing in instead."; break; case "auth/invalid-email": - errorMsg = "The email address is not valid. Please check and try again."; + errorMsg = "Invalid email address. Please try again."; break; case "auth/weak-password": - errorMsg = "The password is too weak. Please choose a stronger password."; + errorMsg = "Weak password. Choose a stronger one."; break; case "auth/wrong-password": errorMsg = "Incorrect password. Please try again."; break; case "auth/user-not-found": - errorMsg = "No account found with this email. Please sign up first."; + errorMsg = "No account found. Please sign up."; break; case "auth/network-request-failed": - errorMsg = "Network error. Please check your connection and try again."; + errorMsg = "Network error. Check your connection."; break; case "auth/invalid-credential": - errorMsg = "Invalid credential. Please ensure your credentials are correct."; + errorMsg = "Invalid credentials. Try again."; break; default: - errorMsg = error.message; + errorMsg = error.message; // Default error message break; } - setErrorMessage(errorMsg); + setErrorMessage(errorMsg); // Update error message }); }; @@ -85,6 +82,7 @@ const Login: React.FC = () => {

{isSigningUp ? "Sign Up" : "Sign In"}

+ {/* Form for email and password */}
@@ -115,10 +113,9 @@ const Login: React.FC = () => { {errorMessage &&

{errorMessage}

} + {/* Toggle between sign-in and sign-up */}

- {isSigningUp - ? "Already have an account?" - : "Don't have an account?"}{" "} + {isSigningUp ? "Already have an account?" : "Don't have an account?"}{" "} { @@ -130,15 +127,15 @@ const Login: React.FC = () => {

+ {/* Google sign-in button */}
- - +
); }; -export default Login; +export default Login; \ No newline at end of file From ae8abe3150f1ebabf5e26ee4af3f78b21f5cc158 Mon Sep 17 00:00:00 2001 From: James Han Date: Mon, 18 Nov 2024 09:20:15 -0500 Subject: [PATCH 3/5] Added input checking (email and password format validation) for the Firebase Auth Form --- frontend/src/pages/SignIn/SignIn.css | 4 +++ frontend/src/pages/SignIn/SignIn.tsx | 40 ++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/SignIn/SignIn.css b/frontend/src/pages/SignIn/SignIn.css index 883ee0d..8b93cba 100644 --- a/frontend/src/pages/SignIn/SignIn.css +++ b/frontend/src/pages/SignIn/SignIn.css @@ -73,6 +73,10 @@ label { font-size: 0.9rem; } +.input-error { + border: 2px solid red; +} + .toggle-message { margin-top: 10px; font-size: 0.9rem; diff --git a/frontend/src/pages/SignIn/SignIn.tsx b/frontend/src/pages/SignIn/SignIn.tsx index 575060b..1dc8a49 100644 --- a/frontend/src/pages/SignIn/SignIn.tsx +++ b/frontend/src/pages/SignIn/SignIn.tsx @@ -20,23 +20,51 @@ const Login: React.FC = () => { const [password, setPassword] = useState(""); // Password input const [isSigningUp, setIsSigningUp] = useState(false); // Toggle for sign-up or sign-in const [errorMessage, setErrorMessage] = useState(""); // Error message display + const [inputError, setInputError] = useState(""); // Input-specific error messages const navigate = useNavigate(); // Navigation hook for redirecting // Handle Google sign-in const handleGoogleSignIn = () => { signInWithPopup(auth, provider) - .then((result) => { + .then(() => { navigate("/active-view"); // Navigate to active view on success }) - .catch((error) => { + .catch(() => { setErrorMessage("Google sign-in failed. Please try again."); // Set error message }); }; + // Validate email format + const isValidEmail = (email: string) => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + // Validate password length + const isValidPassword = (password: string) => { + return password.length >= 8; + }; + // Handle email-based authentication const handleEmailAuth = (e: React.FormEvent) => { e.preventDefault(); // Prevent page reload + + // Reset input errors + setInputError(""); + setErrorMessage(""); + + // Input validation + if (!isValidEmail(email)) { + setInputError("Invalid email format. Please enter a valid email address."); + return; + } + + if (!isValidPassword(password)) { + setInputError("Password must be at least 8 characters long."); + return; + } + const authFunction = isSigningUp ? createUserWithEmailAndPassword : signInWithEmailAndPassword; @@ -89,7 +117,7 @@ const Login: React.FC = () => { setEmail(e.target.value)} required @@ -100,7 +128,7 @@ const Login: React.FC = () => { setPassword(e.target.value)} required @@ -111,6 +139,7 @@ const Login: React.FC = () => { + {inputError &&

{inputError}

} {errorMessage &&

{errorMessage}

} {/* Toggle between sign-in and sign-up */} @@ -121,6 +150,7 @@ const Login: React.FC = () => { onClick={() => { setIsSigningUp(!isSigningUp); setErrorMessage(""); + setInputError(""); }} > {isSigningUp ? "Sign In" : "Sign Up"} @@ -138,4 +168,4 @@ const Login: React.FC = () => { ); }; -export default Login; \ No newline at end of file +export default Login; From 426c4c18efff42e164260ff9955519527fc8dba9 Mon Sep 17 00:00:00 2001 From: James Han Date: Thu, 21 Nov 2024 10:45:37 -0500 Subject: [PATCH 4/5] Added UID to localstorage, added delay to ensure UID is stored before redirect. --- frontend/src/pages/SignIn/SignIn.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/SignIn/SignIn.tsx b/frontend/src/pages/SignIn/SignIn.tsx index 1dc8a49..c928ec9 100644 --- a/frontend/src/pages/SignIn/SignIn.tsx +++ b/frontend/src/pages/SignIn/SignIn.tsx @@ -26,8 +26,14 @@ const Login: React.FC = () => { // Handle Google sign-in const handleGoogleSignIn = () => { + localStorage.setItem("AddedToGame", "false"); // Reset added to game status signInWithPopup(auth, provider) - .then(() => { + .then((result) => { + const user = result.user + console.log("user", user) + + localStorage.setItem("firebaseUID", user.uid); // Store user UID in local storage + setTimeout(() => {}, 1000); // Delay to ensure UID is stored before redirect navigate("/active-view"); // Navigate to active view on success }) .catch(() => { From 60e973441336d908e078d8e3e24fe2c6ce429aca Mon Sep 17 00:00:00 2001 From: James Han Date: Thu, 21 Nov 2024 11:22:36 -0500 Subject: [PATCH 5/5] Changed to Added to Game --- frontend/src/pages/SignIn/SignIn.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/SignIn/SignIn.tsx b/frontend/src/pages/SignIn/SignIn.tsx index c928ec9..c96425a 100644 --- a/frontend/src/pages/SignIn/SignIn.tsx +++ b/frontend/src/pages/SignIn/SignIn.tsx @@ -26,7 +26,7 @@ const Login: React.FC = () => { // Handle Google sign-in const handleGoogleSignIn = () => { - localStorage.setItem("AddedToGame", "false"); // Reset added to game status + localStorage.setItem("addedToGame", "false"); // Reset added to game status signInWithPopup(auth, provider) .then((result) => { const user = result.user