diff --git a/client/package-lock.json b/client/package-lock.json index ea44d29..fbb4190 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -27,7 +27,7 @@ "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", "dotenv": "^16.4.5", - "firebase": "^10.10.0", + "firebase": "^10.14.1", "jotai": "^2.10.0", "localforage": "^1.10.0", "lucide-react": "^0.447.0", diff --git a/client/package.json b/client/package.json index 226ca69..c575244 100644 --- a/client/package.json +++ b/client/package.json @@ -29,7 +29,7 @@ "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", "dotenv": "^16.4.5", - "firebase": "^10.10.0", + "firebase": "^10.14.1", "jotai": "^2.10.0", "localforage": "^1.10.0", "lucide-react": "^0.447.0", diff --git a/client/src/component/Firebase/Setup.js b/client/src/component/Firebase/Setup.js deleted file mode 100644 index 35d01c3..0000000 --- a/client/src/component/Firebase/Setup.js +++ /dev/null @@ -1,33 +0,0 @@ -// Import the functions you need from the SDKs you need -import { initializeApp } from "firebase/app"; -import { getAnalytics } from "firebase/analytics"; -import { getAuth, GoogleAuthProvider } from 'firebase/auth'; - -// TODO: Add SDKs for Firebase products that you want to use -// https://firebase.google.com/docs/web/setup#available-libraries - -// Your web app's Firebase configuration -// For Firebase JS SDK v7.20.0 and later, measurementId is optional -const firebaseConfig = { - apiKey: "AIzaSyDir0QNwcFvX-R0ZJ2TwEkp_o0weA9CL6w", - authDomain: "bitbox-authentication-1.firebaseapp.com", - projectId: "bitbox-authentication-1", - storageBucket: "bitbox-authentication-1.appspot.com", - messagingSenderId: "103312708002", - appId: "1:103312708002:web:002fde0518bc0304e022fb", - measurementId: "G-17QXQ0WCRJ" -}; - -// Initialize Firebase -// const app = initializeApp(firebaseConfig); -// const analytics = getAnalytics(app); -// const auth = getAuth(app) -// const provider=new googleAuthProvider() -// export {auth,provider} -const app = initializeApp(firebaseConfig); -const analytics = getAnalytics(app); // eslint-disable-line no-unused-vars - -const auth = getAuth(); -const provider = new GoogleAuthProvider(); - -export { auth, provider }; \ No newline at end of file diff --git a/client/src/component/Login.jsx b/client/src/component/Login.jsx index 2316b5b..2befc22 100644 --- a/client/src/component/Login.jsx +++ b/client/src/component/Login.jsx @@ -10,6 +10,8 @@ import { } from "@ant-design/icons"; import "../css/Login.css"; import toast from "react-hot-toast"; +import { doSignInWithGoogle } from '../firebase/auth' +import { useAuth } from '../contexts/authContext/index' const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; @@ -57,8 +59,21 @@ const Login = ({ mode, showAlert, isloggedin, setloggedin }) => { setCredentials({ ...credentials, [e.target.name]: e.target.value }); }; + const { userLoggedIn } = useAuth() + + const onGoogleSignIn = (e) => { + e.preventDefault() + if (!isloggedin) { + setloggedin(true) + doSignInWithGoogle().catch(err => { + setloggedin(false) + }) + } + } + return ( <div className="h-screen flex items-center justify-center"> + {userLoggedIn && navigate('/')} <div className="wrapper h-3/4 mt-10" style={{ @@ -121,6 +136,26 @@ const Login = ({ mode, showAlert, isloggedin, setloggedin }) => { <button className="submit" type="submit" disabled={loading}> {loading ? <Spin size="small" /> : "Login"} + </Button> + <button + disabled={isloggedin} + onClick={(e) => { onGoogleSignIn(e) }} + className={`w-full flex items-center justify-center mt-3 gap-x-3 py-2.5 border rounded-lg text-sm font-medium ${isloggedin ? 'cursor-not-allowed' : 'hover:bg-gray-100 transition duration-300 active:bg-gray-100'}`}> + <svg className="w-5 h-5" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> + <g clipPath="url(#clip0_17_40)"> + <path d="M47.532 24.5528C47.532 22.9214 47.3997 21.2811 47.1175 19.6761H24.48V28.9181H37.4434C36.9055 31.8988 35.177 34.5356 32.6461 36.2111V42.2078H40.3801C44.9217 38.0278 47.532 31.8547 47.532 24.5528Z" fill="#4285F4" /> + <path d="M24.48 48.0016C30.9529 48.0016 36.4116 45.8764 40.3888 42.2078L32.6549 36.2111C30.5031 37.675 27.7252 38.5039 24.4888 38.5039C18.2275 38.5039 12.9187 34.2798 11.0139 28.6006H3.03296V34.7825C7.10718 42.8868 15.4056 48.0016 24.48 48.0016Z" fill="#34A853" /> + <path d="M11.0051 28.6006C9.99973 25.6199 9.99973 22.3922 11.0051 19.4115V13.2296H3.03298C-0.371021 20.0112 -0.371021 28.0009 3.03298 34.7825L11.0051 28.6006Z" fill="#FBBC04" /> + <path d="M24.48 9.49932C27.9016 9.44641 31.2086 10.7339 33.6866 13.0973L40.5387 6.24523C36.2 2.17101 30.4414 -0.068932 24.48 0.00161733C15.4055 0.00161733 7.10718 5.11644 3.03296 13.2296L11.005 19.4115C12.901 13.7235 18.2187 9.49932 24.48 9.49932Z" fill="#EA4335" /> + </g> + <defs> + <clipPath id="clip0_17_40"> + <rect width="48" height="48" fill="white" /> + </clipPath> + </defs> + </svg> + {isloggedin ? 'Signing In...' : 'Continue with Google'} + </button> <p @@ -147,6 +182,7 @@ const Login = ({ mode, showAlert, isloggedin, setloggedin }) => { > Forgot Password? </Button> + </form> <div className="banner"> diff --git a/client/src/component/Navbar.jsx b/client/src/component/Navbar.jsx index e3ce017..dfc307b 100644 --- a/client/src/component/Navbar.jsx +++ b/client/src/component/Navbar.jsx @@ -6,11 +6,16 @@ import axios from "axios"; import AddProject from "./AddProject"; import logo from "../assets/images/logo.png"; import avatarDropdown from "../assets/images/Dropdown/avatar.png"; -import { auth } from "../component/Firebase/Setup"; import { FaSun } from "react-icons/fa6"; import { FaMoon } from "react-icons/fa6"; +import { useAuth } from "../contexts/authContext"; +import { doSignOut } from "../firebase/auth"; function Navbar(props) { + const { currentUser } = useAuth() + const { userLoggedIn } = useAuth(); + const [isHovered, setIsHovered] = useState(false) + const { showAlert, mode } = props; const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; @@ -60,7 +65,7 @@ function Navbar(props) { const handleLogout = async () => { try { - await auth.signOut(); // Sign out the user + doSignOut() localStorage.removeItem("token"); navigate("/login"); } catch (error) { @@ -91,9 +96,8 @@ function Navbar(props) { return ( <div> <nav - className={`navbar navbar-expand-lg ${ - isScrolled ? "sticky" : "" - } navbar-${props.mode === "dark" ? "dark" : "light"}`} + className={`navbar navbar-expand-lg ${isScrolled ? "sticky" : "" + } navbar-${props.mode === "dark" ? "dark" : "light"}`} style={{ backgroundColor: props.mode === "dark" ? "black" : "white", borderBottom: "1px solid white", @@ -103,9 +107,8 @@ function Navbar(props) { {/* Hamburger icon */} <div - className={`gap-[3rem] w-full visible navbar-collapse rnav ${ - isOpen ? "show" : "" - }`} + className={`gap-[3rem] w-full visible navbar-collapse rnav ${isOpen ? "show" : "" + }`} style={{ backgroundColor: props.mode === "dark" ? "black" : "white" }} > <Link @@ -118,17 +121,15 @@ function Navbar(props) { alt="logo" /> <div - className={`logoTitle md:block hidden ${ - props.mode === "dark" ? "text-white" : "text-black" - }`} + className={`logoTitle md:block hidden ${props.mode === "dark" ? "text-white" : "text-black" + }`} > {props.title} </div> </Link> <div - className={`collapse navbar-collapse justify-content-center ${ - isOpen ? "show" : "" - }`} + className={`collapse navbar-collapse justify-content-center ${isOpen ? "show" : "" + }`} id="navbarSupportedContent" > <ul @@ -137,9 +138,8 @@ function Navbar(props) { > <li className="nav-item fs-4 fw-medium"> <Link - className={`nav-link ${ - location.pathname === "/" ? "active" : "" - }`} + className={`nav-link ${location.pathname === "/" ? "active" : "" + }`} aria-current="page" to="/" > @@ -148,9 +148,8 @@ function Navbar(props) { </li> <li className="nav-item fs-4"> <Link - className={`nav-link ${ - location.pathname === "/about" ? "active" : "" - }`} + className={`nav-link ${location.pathname === "/about" ? "active" : "" + }`} aria-current="page" to="/about" > @@ -159,9 +158,8 @@ function Navbar(props) { </li> <li className="nav-item fs-4"> <Link - className={`nav-link ${ - location.pathname === "/community" ? "active" : "" - }`} + className={`nav-link ${location.pathname === "/community" ? "active" : "" + }`} aria-current="page" to="/community" > @@ -170,9 +168,8 @@ function Navbar(props) { </li> <li className="nav-item fs-4"> <Link - className={`nav-link ${ - location.pathname === "/discussion" ? "active" : "" - }`} + className={`nav-link ${location.pathname === "/discussion" ? "active" : "" + }`} aria-current="page" to="/discussion" > @@ -214,22 +211,64 @@ function Navbar(props) { </div> </div> </div> - <Link - role="button" - to="/login" - className="btn loginbtn mx-2 h-10 " - style={{ height: "45px", color: "white" }} - > - Login - </Link> - <Link - role="button" - to="/signup" - className="btn loginbtn mx-2 h-10 " - style={{ height: "45px", color: "white" }} - > - Signup - </Link> + {userLoggedIn == false ? + <> + <Link + role="button" + to="/login" + className="btn loginbtn mx-2 h-10 " + style={{ height: "45px", color: "white" }} + > + Login + </Link> + <Link + role="button" + to="/signup" + className="btn loginbtn mx-2 h-10 " + style={{ height: "45px", color: "white" }} + > + Signup + </Link> + </> + : + <> + + <div className="relative w-16 flex justify-center"> + {/* Placeholder image if user.picture is not available */} + <img + src={currentUser.picture || 'https://img.freepik.com/free-vector/blue-circle-with-white-user_78370-4707.jpg?size=338&ext=jpg&ga=GA1.1.2113030492.1729036800&semt=ais_hybrid'} + className="useremailbutton rounded-full w-12 bg-black cursor-pointer" + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + alt="User Profile" + /> + + {/* Information box */} + <div + className={`absolute accountBox bg-gray-800 top-16 -right-24 py-2 pr-10 pl-2 text-start rounded-lg font-normal ${isHovered ? 'block' : 'hidden' + }`} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + <span className="text-gray-200 text-sm">Bitbox Account</span> + <br /> + <span className="text-gray-400 text-sm">{currentUser.displayName}</span> + <br /> + <span className="text-gray-400 text-sm">{currentUser.email}</span> + </div> + </div> + + <Link + role="button" + to="/signup" + className="btn loginbtn mx-2 h-10 " + style={{ height: "45px", color: "white" }} + onClick={handleLogout} + > + Signout + </Link> + </> + } <button className="navbar-toggler block lg:hidden ml-5 focus:outline-none" type="button" @@ -360,14 +399,6 @@ function Navbar(props) { }} /> </li> - <li> - <a - onClick={handleLogout} - style={{ cursor: "pointer" }} - > - Logout - </a> - </li> </ul> </li> </div> diff --git a/client/src/component/Signup.jsx b/client/src/component/Signup.jsx index 4f70f62..49a895f 100644 --- a/client/src/component/Signup.jsx +++ b/client/src/component/Signup.jsx @@ -6,10 +6,13 @@ import "../css/Signup.css"; import { registerValidation } from "../validations/validation"; import toast from "react-hot-toast"; import { EyeInvisibleOutlined, EyeTwoTone } from "@ant-design/icons"; +import { doSignInWithGoogle } from '../firebase/auth' +import { useAuth } from '../contexts/authContext/index' const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || 'https://bitbox-uxbo.onrender.com'; const Signup = ({ mode }) => { + const { userLoggedIn } = useAuth() const navigate = useNavigate(); const [name, setName] = useState(""); @@ -39,6 +42,17 @@ const Signup = ({ mode }) => { await signUpWithGoogle(email, name, password); }; + const [isloggedin, setloggedin] = useState(false) + + const onGoogleSignIn = (e) => { + e.preventDefault() + if (!isloggedin) { + setloggedin(true) + doSignInWithGoogle().catch(err => { + setloggedin(false) + }) + } + } const signUpWithGoogle = async (email, name, password) => { const response = await fetch(`${VITE_SERVER_PORT}/api/auth/createuser`, { @@ -61,6 +75,8 @@ const Signup = ({ mode }) => { return ( <div className="min-h-screen flex items-center justify-center p-4"> + {userLoggedIn && navigate('/')} + <div className="signup-wrapper" style={{ @@ -211,6 +227,25 @@ const Signup = ({ mode }) => { <button type="submit" className="signup-submit"> Sign Up </button> + <button + disabled={isloggedin} + onClick={(e) => { onGoogleSignIn(e) }} + className={`w-full flex items-center mt-3 justify-center gap-x-3 py-2.5 border rounded-lg text-sm font-medium ${isloggedin ? 'cursor-not-allowed' : 'hover:bg-gray-100 transition duration-300 active:bg-gray-100'}`}> + <svg className="w-5 h-5" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> + <g clipPath="url(#clip0_17_40)"> + <path d="M47.532 24.5528C47.532 22.9214 47.3997 21.2811 47.1175 19.6761H24.48V28.9181H37.4434C36.9055 31.8988 35.177 34.5356 32.6461 36.2111V42.2078H40.3801C44.9217 38.0278 47.532 31.8547 47.532 24.5528Z" fill="#4285F4" /> + <path d="M24.48 48.0016C30.9529 48.0016 36.4116 45.8764 40.3888 42.2078L32.6549 36.2111C30.5031 37.675 27.7252 38.5039 24.4888 38.5039C18.2275 38.5039 12.9187 34.2798 11.0139 28.6006H3.03296V34.7825C7.10718 42.8868 15.4056 48.0016 24.48 48.0016Z" fill="#34A853" /> + <path d="M11.0051 28.6006C9.99973 25.6199 9.99973 22.3922 11.0051 19.4115V13.2296H3.03298C-0.371021 20.0112 -0.371021 28.0009 3.03298 34.7825L11.0051 28.6006Z" fill="#FBBC04" /> + <path d="M24.48 9.49932C27.9016 9.44641 31.2086 10.7339 33.6866 13.0973L40.5387 6.24523C36.2 2.17101 30.4414 -0.068932 24.48 0.00161733C15.4055 0.00161733 7.10718 5.11644 3.03296 13.2296L11.005 19.4115C12.901 13.7235 18.2187 9.49932 24.48 9.49932Z" fill="#EA4335" /> + </g> + <defs> + <clipPath id="clip0_17_40"> + <rect width="48" height="48" fill="white" /> + </clipPath> + </defs> + </svg> + {isloggedin ? 'Signing In...' : 'Continue with Google'} + </button> </form> <div className="signup-footer"> <p className=""> diff --git a/client/src/contexts/authContext/index.jsx b/client/src/contexts/authContext/index.jsx new file mode 100644 index 0000000..ecc4d83 --- /dev/null +++ b/client/src/contexts/authContext/index.jsx @@ -0,0 +1,63 @@ +import React, { useContext, useState, useEffect } from "react"; +import { auth } from "../../firebase/firebase"; +// import { GoogleAuthProvider } from "firebase/auth"; +import { onAuthStateChanged } from "firebase/auth"; + +const AuthContext = React.createContext(); + +export function useAuth() { + return useContext(AuthContext); +} + +export function AuthProvider({ children }) { + const [currentUser, setCurrentUser] = useState(null); + const [userLoggedIn, setUserLoggedIn] = useState(false); + const [isEmailUser, setIsEmailUser] = useState(false); + const [isGoogleUser, setIsGoogleUser] = useState(false); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const unsubscribe = onAuthStateChanged(auth, initializeUser); + return unsubscribe; + }, []); + + async function initializeUser(user) { + if (user) { + + setCurrentUser({ ...user }); + + // check if provider is email and password login + const isEmail = user.providerData.some( + (provider) => provider.providerId === "password" + ); + setIsEmailUser(isEmail); + + // check if the auth provider is google or not + // const isGoogle = user.providerData.some( + // (provider) => provider.providerId === GoogleAuthProvider.PROVIDER_ID + // ); + // setIsGoogleUser(isGoogle); + + setUserLoggedIn(true); + } else { + setCurrentUser(null); + setUserLoggedIn(false); + } + + setLoading(false); + } + + const value = { + userLoggedIn, + isEmailUser, + isGoogleUser, + currentUser, + setCurrentUser + }; + + return ( + <AuthContext.Provider value={value}> + {!loading && children} + </AuthContext.Provider> + ); +} \ No newline at end of file diff --git a/client/src/firebase/auth.js b/client/src/firebase/auth.js new file mode 100644 index 0000000..925fd34 --- /dev/null +++ b/client/src/firebase/auth.js @@ -0,0 +1,44 @@ +import { auth } from "./firebase"; +import { + createUserWithEmailAndPassword, + signInWithEmailAndPassword, + sendPasswordResetEmail, + sendEmailVerification, + updatePassword, + signInWithPopup, + GoogleAuthProvider, +} from "firebase/auth"; + +export const doCreateUserWithEmailAndPassword = async (email, password) => { + return createUserWithEmailAndPassword(auth, email, password); +}; + +export const doSignInWithEmailAndPassword = (email, password) => { + return signInWithEmailAndPassword(auth, email, password); +}; + +export const doSignInWithGoogle = async () => { + const provider = new GoogleAuthProvider(); + const result = await signInWithPopup(auth, provider); + const user = result.user; + + // add user to firestore +}; + +export const doSignOut = () => { + return auth.signOut(); +}; + +export const doPasswordReset = (email) => { + return sendPasswordResetEmail(auth, email); +}; + +export const doPasswordChange = (password) => { + return updatePassword(auth.currentUser, password); +}; + +export const doSendEmailVerification = () => { + return sendEmailVerification(auth.currentUser, { + url: `${window.location.origin}/home`, + }); +}; \ No newline at end of file diff --git a/client/src/component/Firebase/firebase.js b/client/src/firebase/firebase.js similarity index 57% rename from client/src/component/Firebase/firebase.js rename to client/src/firebase/firebase.js index 777ebf1..6f710c6 100644 --- a/client/src/component/Firebase/firebase.js +++ b/client/src/firebase/firebase.js @@ -7,16 +7,17 @@ import { getAuth } from "firebase/auth"; // Your web app's Firebase configuration // For Firebase JS SDK v7.20.0 and later, measurementId is optional const firebaseConfig = { - apiKey: "AIzaSyDiZGqgpVvHYlX6ZqmJ5Bs7QBvUplNQM0s", - authDomain: "bitbox-sms.firebaseapp.com", - projectId: "bitbox-sms", - storageBucket: "bitbox-sms.appspot.com", - messagingSenderId: "663052856919", - appId: "1:663052856919:web:8e3f3015922c5a54717d12", - measurementId: "G-WJ940QQMH4" + apiKey: "AIzaSyC4jZOLTES2JBLpGGYGmkxA8IGbB-vSls4", + authDomain: "gssocbitbox.firebaseapp.com", + projectId: "gssocbitbox", + storageBucket: "gssocbitbox.appspot.com", + messagingSenderId: "1007470403116", + appId: "1:1007470403116:web:0ca732a35153070c19fb89", + measurementId: "G-TYDYZZ7V1S" }; // Initialize Firebase const app = initializeApp(firebaseConfig); +const auth = getAuth(app); -export const auth = getAuth(app); \ No newline at end of file +export { app, auth }; \ No newline at end of file diff --git a/client/src/main.jsx b/client/src/main.jsx index d951535..f2b59f7 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -4,10 +4,13 @@ import ReactDOM from "react-dom/client"; import App from "./App.jsx"; import "./index.css"; import { Toaster } from "react-hot-toast"; +import { AuthProvider } from "./contexts/authContext/index.jsx"; ReactDOM.createRoot(document.getElementById("root")).render( - <> - <App /> - <Toaster/> + <> + <AuthProvider> + <App /> + <Toaster /> + </AuthProvider> </> );