From fb928819ca58a6ee31391e42295388df32293fdd Mon Sep 17 00:00:00 2001
From: Mary Caserio <114102750+marycaserio@users.noreply.github.com>
Date: Sun, 27 Oct 2024 15:14:08 -0500
Subject: [PATCH] (feats): Initialize database, sign-in/sign-out, header (#3)
Please include a summary of the changes and the related issue.
## Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] Code refactor
- [ ] Breaking change
- [ ] Style update
- [ ] Documentation update
## Checklist
- [ ] I have performed a self-review of my code
- [ ] Single file within 200 lines of codes
- [ ] Comments has added
- [ ] My changes generate no new warnings
---------
Co-authored-by: WeiViv <100058931+WeiViv@users.noreply.github.com>
---
package-lock.json | 2 +-
package.json | 2 +-
src/components/common/Header.jsx | 63 ++++++++++++++++++++
src/pages/Home.jsx | 9 ++-
src/utils/auth.js | 47 +++++++++++++++
src/utils/firebase/createUserProfile.js | 77 +++++++++++++++++++++++++
6 files changed, 197 insertions(+), 3 deletions(-)
create mode 100644 src/components/common/Header.jsx
create mode 100644 src/utils/auth.js
create mode 100644 src/utils/firebase/createUserProfile.js
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);
+ }
+};