Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FE - 1.5: Firebase Auth: Email/Password #60

Merged
merged 6 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/assets/google-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
159 changes: 128 additions & 31 deletions frontend/src/pages/SignIn/SignIn.css
Original file line number Diff line number Diff line change
@@ -1,32 +1,129 @@
/* 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;
}

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;
}

.input-error {
border: 2px solid red;
}

.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;
}
185 changes: 158 additions & 27 deletions frontend/src/pages/SignIn/SignIn.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,115 @@
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 Google icon
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);
const provider = new GoogleAuthProvider(); // Google authentication provider

// Accessing location/nickname information through local storage
const location = localStorage.getItem('selectedLocation') || 'Cassie Campbell';
const nickname = localStorage.getItem('nickname') || 'User';
// 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 [inputError, setInputError] = useState(""); // Input-specific error messages

// Hook for navigating to the next page
const navigate = useNavigate();
const navigate = useNavigate(); // Navigation hook for redirecting

// Handle Google sign-in
const handleGoogleSignIn = () => {
localStorage.setItem("addedToGame", "false"); // Reset added to game status
signInWithPopup(auth, provider)
.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(() => {
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;

authFunction(auth, email, password)
.then(() => {
navigate("/active-view"); // Navigate to active view on success
})
.catch((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 = "Invalid email address. Please try again.";
break;
case "auth/weak-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. Please sign up.";
break;
case "auth/network-request-failed":
errorMsg = "Network error. Check your connection.";
break;
case "auth/invalid-credential":
errorMsg = "Invalid credentials. Try again.";
break;
default:
errorMsg = error.message; // Default error message
break;
}
setErrorMessage(errorMsg); // Update error message
});
localStorage.setItem("addedToGame", "false");
signInWithPopup(auth, provider)
.then((result) => {
Expand All @@ -36,24 +127,64 @@ const Login: React.FC = () => {

return (
<div className="login-container">
<h2 className="login-title">Login with Google</h2>
{!isAuthenticated && (
<div>
<h1>Welcome, {nickname}!</h1>
<button className="login-button" onClick={handleGoogleSignIn}>
Sign in with Google
</button>
<h2 className="login-title">{isSigningUp ? "Sign Up" : "Sign In"}</h2>

{/* Form for email and password */}
<form className="login-form" onSubmit={handleEmailAuth}>
<div className="form-group">
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
className={`form-input ${inputError ? "input-error" : ""}`}
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
)}
{/* Display something if the user is authenticated with Google */}
{isAuthenticated && (
<div className="welcome-message">
<h3>You are successfully logged in!</h3>
<p>Enjoy your stay, {nickname}!</p>
<div className="form-group">
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
className={`form-input ${inputError ? "input-error" : ""}`}
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
)}
<button type="submit" className="form-button email-button">
{isSigningUp ? "Sign Up with Email" : "Sign In with Email"}
</button>
</form>

{inputError && <p className="error-message">{inputError}</p>}
{errorMessage && <p className="error-message">{errorMessage}</p>}

{/* Toggle between sign-in and sign-up */}
<p className="toggle-message">
{isSigningUp ? "Already have an account?" : "Don't have an account?"}{" "}
<span
className="toggle-link"
onClick={() => {
setIsSigningUp(!isSigningUp);
setErrorMessage("");
setInputError("");
}}
>
{isSigningUp ? "Sign In" : "Sign Up"}
</span>
</p>

{/* Google sign-in button */}
<div className="google-signin-container">
<button className="button google-signin-button" onClick={handleGoogleSignIn}>
<img src={googleIcon} alt="Google Icon" className="button-icon" />
Sign in with Google
</button>
</div>
</div>
);
};

export default Login;
export default Login;