diff --git a/package-lock.json b/package-lock.json index 2a30e3a..55b6683 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", "firebase": "^11.0.1", - "react": "^18.3.0", + "react": "^18.3.1", "react-dom": "^18.3.0", "react-router-dom": "^6.27.0" }, diff --git a/package.json b/package.json index a0be452..3f8346c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", "firebase": "^11.0.1", - "react": "^18.3.0", + "react": "^18.3.1", "react-dom": "^18.3.0", "react-router-dom": "^6.27.0" }, diff --git a/src/components/common/Header.jsx b/src/components/common/Header.jsx new file mode 100644 index 0000000..84ef4c6 --- /dev/null +++ b/src/components/common/Header.jsx @@ -0,0 +1,63 @@ +import { AppBar, Avatar, Box, Button, IconButton, Toolbar, Typography } from '@mui/material'; +import { handleSignIn, handleSignOut } from '@utils/auth'; +import { auth } from '@utils/firebaseConfig'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +const Header = () => { + const [user, setUser] = useState(null); + const navigate = useNavigate(); + + useEffect(() => { + const unsubscribe = auth.onAuthStateChanged((user) => { + setUser(user); + }); + return () => unsubscribe(); + }, []); + + const handleAuthClick = async () => { + if (user) { + await handleSignOut(navigate); + } else { + await handleSignIn(); + } + }; + + return ( + + + + {/* Placeholder Box for back button if needed in the future */} + + + + + Stepwise + + + + {user ? ( + + + + ) : ( + + )} + + + + ); +}; + +export default Header; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 6270bf7..ced5c3b 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,5 +1,12 @@ +import Header from '@components/common/Header'; + const Home = () => { - return

Home

; + return ( +
+
+

Home

+
+ ); }; export default Home; diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..1cf069b --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,47 @@ +import { createFirstUserProfile, fetchUserProfile } from '@utils/firebase/createUserProfile'; +import { auth } from '@utils/firebaseConfig'; +import { GoogleAuthProvider, signInWithPopup, signOut } from 'firebase/auth'; + +const provider = new GoogleAuthProvider(); + +// Google Sign-In function +const signInWithGoogle = async () => { + try { + const result = await signInWithPopup(auth, provider); + return result.user; + } catch (error) { + console.error('Error during sign-in:', error); + return null; + } +}; + +// Handle Sign-In +// Returns true if the user already exists in the database, false if a new profile was created +export const handleSignIn = async () => { + const user = await signInWithGoogle(); + + if (user) { + // Try to fetch the existing user profile + const { profile } = await fetchUserProfile(user.uid); + + // If no profile found, create one with default data + if (!profile) { + await createFirstUserProfile(user); + return false; // New user profile created + } + return true; // User profile already exists + } + return false; // Sign-in failed or cancelled +}; + +// Handle Sign-Out +// Redirects to homepage after sign-out +export const handleSignOut = async (navigate) => { + try { + await signOut(auth); + console.log('Sign out successful'); + navigate('/'); + } catch (error) { + console.error('Error during sign-out:', error); + } +}; diff --git a/src/utils/firebase/createUserProfile.js b/src/utils/firebase/createUserProfile.js new file mode 100644 index 0000000..7656c7d --- /dev/null +++ b/src/utils/firebase/createUserProfile.js @@ -0,0 +1,77 @@ +import { db } from '@utils/firebaseConfig'; +import { doc, getDoc, setDoc, updateDoc } from 'firebase/firestore'; + +// Unified function to fetch and listen to user profile changes by UID +// (supports both regular and transaction-based fetches) +export const fetchUserProfile = async (uid, transaction = null) => { + try { + const userRef = doc(db, 'users', uid); + + // If transaction is provided, use it to fetch the document + const userSnapshot = transaction ? await transaction.get(userRef) : await getDoc(userRef); + + if (!userSnapshot.exists()) { + console.error(`User profile for ${uid} does not exist`); + return { ref: userRef, profile: null }; // Return consistent format with null profile + } + + const profile = userSnapshot.data(); + return { ref: userRef, profile }; + } catch (error) { + console.error('Error fetching user profile:', error); + return { ref: null, profile: null }; + } +}; + +// Check or create user profile in Firestore (uses fetchUserProfile to streamline code) +export const createFirstUserProfile = async (user) => { + try { + const { uid, photoURL, displayName } = user; + const defaultProfile = { + uid, + profilePic: photoURL || '', + name: displayName || '', + goals: [ + { + name: '', + progress: 0, + microgoals: [ + { + name: '', + progress: 0, + tasks: [{ name: '', completed: false }], + }, + ], + }, + ], + streak: [], + }; + + // If the profile does not exist, create it with the default data + await setDoc(doc(db, 'users', uid), defaultProfile); + console.warn('New user profile created with default data.'); + + return true; + } catch (error) { + console.error('Error checking or creating user profile:', error); + return false; // Return false if an error occurs + } +}; + +// Get user profile by UID +// (simple wrapper around fetchUserProfile for non-transaction use) +export const getUserProfile = async (uid) => { + const fetchedUser = await fetchUserProfile(uid); + return fetchedUser ? fetchedUser.profile : null; // Return only the profile data +}; + +// Update user profile by UID +export const updateUserProfile = async (uid, updates) => { + try { + const userDocRef = doc(db, 'users', uid); + await updateDoc(userDocRef, updates); + console.warn('User profile updated'); + } catch (error) { + console.error('Error updating user profile:', error); + } +}; diff --git a/src/utils/firebaseConfig.js b/src/utils/firebaseConfig.js index 98f07d7..3ac43db 100644 --- a/src/utils/firebaseConfig.js +++ b/src/utils/firebaseConfig.js @@ -1,5 +1,12 @@ // Import the functions you need from the SDKs you need import { initializeApp } from 'firebase/app'; +import { getAuth } from 'firebase/auth'; +import { + CACHE_SIZE_UNLIMITED, + initializeFirestore, + persistentLocalCache, + persistentSingleTabManager, +} from 'firebase/firestore'; // TODO: Add SDKs for Firebase products that you want to use // https://firebase.google.com/docs/web/setup#available-libraries @@ -17,6 +24,13 @@ const firebaseConfig = { // Initialize Firebase const app = initializeApp(firebaseConfig); -const db = getFirestore(app); -export { db }; +// Enable IndexedDB persistence with single-tab manager +export const db = initializeFirestore(app, { + localCache: persistentLocalCache({ + tabManager: persistentSingleTabManager(), + cacheSizeBytes: CACHE_SIZE_UNLIMITED, + }), +}); + +export const auth = getAuth(app);