From 840bbc4e8c88db52ab9a1d8aef1e1b843e8afb8c Mon Sep 17 00:00:00 2001
From: ZL Asica <40444637+ZL-Asica@users.noreply.github.com>
Date: Fri, 11 Oct 2024 18:07:01 -0500
Subject: [PATCH] refactor: Fix multiple major bugs, refactor ProfileCard, add
useCopyToClipboard hook
fix:
1. GroupsPage.jsx: Resolved issue with current matches not displaying.
2. matches.js: Add error handling for fetchMatchDocument by match ID.
3. userProfile.js: Corrected new user profile creation logic.
4. HomePage.jsx: Prevented rendering of StudentFilter and StudentList when the user's profile is incomplete.
refactor:
1. ProfileCard.jsx: Replaced HTML tags with MUI components and added renderProfileDetail helper function for rendering profiles.
feat:
1. useCopyToClipboard.jsx: Implemented a new hook for clipboard copy functionality with Snackbar.
BREAKING CHANGE: Major bugs fixed.
---
src/components/GroupsPage.jsx | 186 ++++++++++++++++-------------
src/components/HomePage.jsx | 16 ++-
src/components/ProfileCard.jsx | 151 +++++++++++------------
src/hooks/useCopyToClipboard.jsx | 26 ++++
src/utils/firestore/matches.js | 31 ++---
src/utils/firestore/userProfile.js | 19 +--
6 files changed, 249 insertions(+), 180 deletions(-)
create mode 100644 src/hooks/useCopyToClipboard.jsx
diff --git a/src/components/GroupsPage.jsx b/src/components/GroupsPage.jsx
index 6f3a0d2..cd2b1d4 100644
--- a/src/components/GroupsPage.jsx
+++ b/src/components/GroupsPage.jsx
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { Box, Stack, Typography } from '@mui/material';
+import ProfileCard from './ProfileCard';
import StudentCard from './UserCard';
import { useAuthState } from '../hooks/useAuthState';
import useUserProfile from '../hooks/useUserProfile';
@@ -15,6 +16,7 @@ function GroupsPage() {
const [outgoingRequestProfiles, setOutgoingRequestProfiles] = useState([]);
const [matchProfiles, setMatchProfiles] = useState([]);
const [selectedProfile, setSelectedProfile] = useState(null); // State for selected user profile
+ const [openProfileModal, setOpenProfileModal] = useState(false); // State for modal visibility
// Combined useEffect for fetching incoming, outgoing, and current matches
useEffect(() => {
@@ -51,10 +53,6 @@ function GroupsPage() {
setIncomingRequestProfiles(incomingProfiles);
setOutgoingRequestProfiles(outgoingProfiles);
setMatchProfiles(matches);
-
- // Update match profiles and set selected profile to null when closing modal
- const updatedMatches = matches.filter((m) => m.uid !== selectedProfile?.uid);
- setMatchProfiles(updatedMatches);
} catch (error) {
console.error('Error fetching request profiles:', error);
}
@@ -67,95 +65,119 @@ function GroupsPage() {
try {
await resolveMatchRequest(userProfile.uid, requestingUserUid, matchId, accept);
// Update the UI after resolving the request
- setUserProfile((prevProfile) => ({
- ...prevProfile,
- incomingMatches: prevProfile.incomingMatches.filter((req) => req.matchId !== matchId),
- }));
+ setIncomingRequestProfiles((prevProfiles) =>
+ prevProfiles.filter((profile) => profile.matchId !== matchId),
+ );
+ setMatchProfiles((prevProfiles) =>
+ prevProfiles.filter((profile) => profile.uid !== requestingUserUid),
+ );
} catch (error) {
console.error(`Error ${accept ? 'accepting' : 'denying'} request:`, error);
}
};
+ const handleOpenProfileModal = (profile) => {
+ setSelectedProfile(profile);
+ setOpenProfileModal(true);
+ };
+
+ const handleCloseProfileModal = () => {
+ setOpenProfileModal(false);
+ };
+
if (loading) {
return Loading...;
}
- return (
- <>
- {userProfile ? (
-
- Matches
-
- {matchProfiles.length > 0 ? (
- matchProfiles.map((profile, index) => {
- const actions = [
- {
- label: 'View Profile',
- // Allow users to see profile of matches
- onClick: () => setSelectedProfile(profile),
- },
- ];
- return ;
- })
- ) : (
-
- You don't currently have any matches.
-
- )}
-
-
- Incoming Requests
-
- {incomingRequestProfiles.length > 0 ? (
- incomingRequestProfiles.map((profile, index) => {
- const actions = [
- {
- label: 'Accept',
- onClick: () => handleRequestResolution(profile.uid, profile.matchId, true),
- },
- {
- label: 'Deny',
- onClick: () => handleRequestResolution(profile.uid, profile.matchId, false),
- variant: 'outlined',
- color: 'secondary',
- },
- ];
- return ;
- })
- ) : (
-
- You don't have any incoming requests.
-
- )}
-
-
- Outgoing Requests
-
- {outgoingRequestProfiles.length > 0 ? (
- outgoingRequestProfiles.map((profile, index) => {
- const actions = [
- {
- label: 'Requested',
- variant: 'outlined',
- color: 'default',
- onClick: () => {}, // No functionality
- },
- ];
- return ;
- })
- ) : (
-
- You don't have any outgoing requests.
-
- )}
-
-
- ) : (
-
+ if (!user) {
+ return (
+
+
Please log in to view your groups.
- )}
- >
+
+ );
+ }
+
+ // Render the StudentFilter and StudentList only if the userProfile is complete
+ if (!userProfile || !userProfile.major || !userProfile.year || !userProfile.phoneNumber) {
+ return null;
+ }
+
+ return (
+
+ Matches
+
+ {matchProfiles.length > 0 ? (
+ matchProfiles.map((profile, index) => {
+ const actions = [
+ {
+ label: 'View Profile',
+ onClick: () => handleOpenProfileModal(profile),
+ },
+ ];
+ return ;
+ })
+ ) : (
+
+ You don't currently have any matches.
+
+ )}
+
+
+ Incoming Requests
+
+ {incomingRequestProfiles.length > 0 ? (
+ incomingRequestProfiles.map((profile, index) => {
+ const actions = [
+ {
+ label: 'Accept',
+ onClick: () => handleRequestResolution(profile.uid, profile.matchId, true),
+ },
+ {
+ label: 'Deny',
+ onClick: () => handleRequestResolution(profile.uid, profile.matchId, false),
+ variant: 'outlined',
+ color: 'secondary',
+ },
+ ];
+ return ;
+ })
+ ) : (
+
+ You don't have any incoming requests.
+
+ )}
+
+
+ Outgoing Requests
+
+ {outgoingRequestProfiles.length > 0 ? (
+ outgoingRequestProfiles.map((profile, index) => {
+ const actions = [
+ {
+ label: 'Requested',
+ variant: 'outlined',
+ color: 'default',
+ onClick: () => {}, // No functionality
+ },
+ ];
+ return ;
+ })
+ ) : (
+
+ You don't have any outgoing requests.
+
+ )}
+
+
+ {/* Modal for displaying the selected profile */}
+
+
);
}
diff --git a/src/components/HomePage.jsx b/src/components/HomePage.jsx
index a8e82bd..b6565ee 100644
--- a/src/components/HomePage.jsx
+++ b/src/components/HomePage.jsx
@@ -1,6 +1,7 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { Box, CircularProgress, Typography } from '@mui/material';
+import { useNavigate } from 'react-router-dom';
import StudentFilter from './Home/StudentFilter';
import StudentList from './Home/StudentList';
@@ -13,6 +14,14 @@ export default function HomePage() {
useUserProfile(user);
const [selectedMajors, setSelectedMajors] = useState([]);
const [selectedYears, setSelectedYears] = useState([]);
+ const navigate = useNavigate();
+
+ // Redirect user to edit-profile if the profile is incomplete
+ useEffect(() => {
+ if (userProfile && (!userProfile.major || !userProfile.year || !userProfile.phoneNumber)) {
+ navigate('/edit-profile');
+ }
+ }, [userProfile, navigate]);
if (loading) {
return (
@@ -32,6 +41,11 @@ export default function HomePage() {
);
}
+ // Render the StudentFilter and StudentList only if the userProfile is complete
+ if (!userProfile || !userProfile.major || !userProfile.year || !userProfile.phoneNumber) {
+ return null;
+ }
+
return (
- (
+
+
+ {label}:
+ {' '}
+ isCopyable && value && handleCopyToClipboard(value)}
+ >
+ {value || 'N/A'}
+
+
+ );
+
+ return (
+ <>
+
-
- {!profileData?.photoURL && (profileData?.name?.[0] || '')}{' '}
- {/* Display initial if there is no Google photo */}
-
- }
- title={
-
- {profileData?.name}
-
- }
- // subheader={
- //
- // {profileData?.major}
- //
- // }
- />
-
-
- Email: {profileData?.email}
-
-
- Phone Number: {profileData?.phoneNumber}
-
-
- Major: {profileData?.major}
-
-
- Year: {profileData?.year}
-
-
- Description: {profileData?.description}
-
-
-
-
+
+
+ {profileData?.name?.[0] || ''}
+
+ }
+ title={
+
+ {profileData?.name || 'Unknown User'}
+
+ }
+ />
+
+ {renderProfileDetail('Email', profileData?.email, true)}
+ {renderProfileDetail('Phone Number', profileData?.phoneNumber, true)}
+ {renderProfileDetail('Major', profileData?.major)}
+ {renderProfileDetail('Year', profileData?.year)}
+ {renderProfileDetail('Bio', profileData?.description)}
+
+
+
+
+ {/* Snackbar for copy confirmation */}
+
+ >
);
}
diff --git a/src/hooks/useCopyToClipboard.jsx b/src/hooks/useCopyToClipboard.jsx
new file mode 100644
index 0000000..ad5afa1
--- /dev/null
+++ b/src/hooks/useCopyToClipboard.jsx
@@ -0,0 +1,26 @@
+import { useState } from 'react';
+
+import { Snackbar } from '@mui/material';
+
+export const useCopyToClipboard = () => {
+ const [snackbarOpen, setSnackbarOpen] = useState(false);
+ const [snackbarMessage, setSnackbarMessage] = useState('');
+
+ const handleCopyToClipboard = (text) => {
+ navigator.clipboard.writeText(text);
+ setSnackbarMessage(`Copied to clipboard: ${text}`);
+ setSnackbarOpen(true);
+ };
+
+ const SnackbarComponent = () => (
+ setSnackbarOpen(false)}
+ message={snackbarMessage}
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
+ />
+ );
+
+ return { handleCopyToClipboard, SnackbarComponent };
+};
diff --git a/src/utils/firestore/matches.js b/src/utils/firestore/matches.js
index e2f4cf8..9f7bcf0 100644
--- a/src/utils/firestore/matches.js
+++ b/src/utils/firestore/matches.js
@@ -12,9 +12,14 @@ import { db } from '../firebaseConfig';
// Utility function to fetch a match document by ID
const fetchMatchDocument = async (matchId) => {
- const matchDocRef = doc(db, 'matches', matchId);
- const matchSnapshot = await getDoc(matchDocRef);
- return matchSnapshot.exists() ? matchSnapshot.data() : null;
+ try {
+ const matchDocRef = doc(db, 'matches', matchId);
+ const matchSnapshot = await getDoc(matchDocRef);
+ return matchSnapshot.exists() ? matchSnapshot.data() : null;
+ } catch (error) {
+ console.error('Error fetching match document:', error);
+ return null;
+ }
};
// Create a new match
@@ -71,15 +76,6 @@ export const createMatch = async (users, location, description = '') => {
}
};
-// Fetch match by ID
-export const getMatch = async (matchId) => {
- try {
- return await fetchMatchDocument(matchId);
- } catch (error) {
- console.error('Error fetching match:', error);
- }
-};
-
// Get all user matches
export const getUserMatches = async (uid) => {
try {
@@ -92,16 +88,21 @@ export const getUserMatches = async (uid) => {
const { currentMatches } = userSnapshot.data();
if (!currentMatches || currentMatches.length === 0) return [];
+ // Fetch each match and gather the profiles of other users in the match
const matchProfiles = await Promise.all(
currentMatches.map(async (matchId) => {
const matchData = await fetchMatchDocument(matchId);
if (!matchData) return null;
+ // Get all users in the match except the current user
const otherUsers = matchData.users.filter((user) => user.uid !== uid);
- return await Promise.all(
- // Pass empty transaction object if not using transactions
- otherUsers.map(async (user) => fetchUserProfile(user.uid, {})),
+ const profiles = await Promise.all(
+ otherUsers.map(async (user) => {
+ const { profile } = await fetchUserProfile(user.uid);
+ return profile || null;
+ }),
);
+ return profiles.filter((profile) => profile !== null);
}),
);
diff --git a/src/utils/firestore/userProfile.js b/src/utils/firestore/userProfile.js
index 6f59efd..7a59b20 100644
--- a/src/utils/firestore/userProfile.js
+++ b/src/utils/firestore/userProfile.js
@@ -12,7 +12,7 @@ export const fetchUserProfile = async (uid, transaction) => {
if (!userSnapshot.exists()) {
console.warn(`User profile for ${uid} does not exist`);
- return { ref: userRef, profile: null }; // Ensure a consistent return format
+ return { ref: userRef, profile: null }; // Return consistent format with null profile
}
return { ref: userRef, profile: userSnapshot.data() };
@@ -29,7 +29,7 @@ export const checkUserProfile = async (user) => {
const defaultProfile = {
uid,
profilePic: photoURL || '',
- name: displayName || 'Anonymous',
+ name: displayName || '',
email: email || '',
phoneNumber: phoneNumber || '',
major: '',
@@ -43,15 +43,17 @@ export const checkUserProfile = async (user) => {
pastMatches: [],
};
- const fetchedUser = await fetchUserProfile(uid);
- if (!fetchedUser) {
- // Create a new profile if it doesn't exist
+ // Fetch the user profile to check if it exists
+ const { profile } = await fetchUserProfile(uid);
+
+ // If the profile does not exist, create it with the default data
+ if (!profile) {
await setDoc(doc(db, 'users', uid), defaultProfile);
- console.log('User profile created');
+ console.log('New user profile created with default data.');
return false; // Return false indicating a new user profile was created
}
- const existingProfile = fetchedUser.profile;
+ const existingProfile = profile;
const updates = {};
// Check for missing or outdated fields in the user's profile
@@ -66,8 +68,9 @@ export const checkUserProfile = async (user) => {
// If updates are required, update the user profile in Firestore
if (Object.keys(updates).length > 0) {
await updateUserProfile(uid, updates);
- console.log('User profile updated with missing attributes');
+ console.log('User profile updated with missing attributes.');
}
+
return true; // Return true indicating the user profile already existed
} catch (error) {
console.error('Error checking or creating user profile:', error);