From b0adcb152c4da309b38d5511d7ce06da3b6ed3e2 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 17:31:24 -0500 Subject: [PATCH 01/16] Update README.md --- .github/CONTRIBUTING.md | 101 ++++++++++++++++++++++++++++++++++++++++ .github/README.md | 95 ++++++++++++++++++++++++++++++++++++- README.md | 95 ++++++++++++++++++++++++++++++++++++- 3 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 .github/CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..60083d1 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,101 @@ +# Team Norms + +Welcome to our Bill-Splitting App! We aim to create a collabortaive and respectful environemt here. Please take a look at our norms: + +- We'll have regular meetings to share updates and listen to each other's thoughts and concerns. +- In cases where physcial co-location isn't practical, we emphasize everyone making an effort to show to meetings via video call. + +## Team Values + +- Each team member will be assigned specific tasks and we will make sure to complete it before the next meeting +- In the event of encountering challenges or needing help, we will consult each other first before notifying a supervisor. +- Conflicts will be resolved through constant communication with one another. + - In the event of encountering a disagreement on project direction, we will host a duscission for everyone to have the chance to input their opinions and hear each other out. + - In the event where one or multiple team memebers fail to deliver their obligations, please inform the team about your challenges and we will make necessary adjsutments to keep everything on track. + - Members are expected to respond to messages by the end of the work day. Communication is key! + +## Sprint Cadence + +For our project, we have decided that a sprint should last 2 weeks. It's enough time to be able to complete meaningful work while also being short enough to maintain a sense of urgency and focus. + +## Daily Standups + +Our daily standups will happen on every Tuesday and Thursday at 1:45pm. Each standup will last 15 minutes and we expect all team memebers to be present synchronously, ready to share their progress and or any blockers. Active participation is key so members will not cover for other members who do not participate. If someone consistently doesn't contribute for two consecutive standups, we'll report this concern to management. + +## Coding Standards + +- We will be using Visual Studio Code. +- Always pull from master before working. +- Always push working code, if you break the pipeline/build then fix it. +- Write self documenting code. Use descriptive variable and function names. Avoid unnecessary name shortening. +- Provide specific commit messages. + +## Concluding Statement + +Our Group Bill Splitting App, makes splitting bills in a group super easy. In today's world, we often share costs for things like meals, trips, or living expenses. Our app helps manage these shared expenses in a way that's simple and fair for everyone involved. The Group Bill Splitting App is here to simplify this process by giving you a straightforward and user-friendly tool to handle shared expenses effortlessly. + +## Git Workflow + +- Always pull from master branch before working +- Push each commit with a meaningful message +- Before we incorporate changes into our main branch, a pull request must be made and must be approved by another member. + +## Rules of Contribution + +Before you start contributing, please keep the following considerations in mind: + +- **Adhere to Team Values**: All contributors must adhere to the team values. Any form of inappropriate behavior will not be tolerated. + +- **Pull Requests**: Ensure that your pull requests(PRs) are small enough to be reviewed easily. Always provide a concise and informative description. + +- **Issues**: If you're working on a particular issue, please comment on it to prevent multiple contributors working on the same problem. + +- **Commit Messages**: Write meaningful commit messages that clearly explain the changes made. This helps in understanding the history and purpose of changes. + +- **Coding Standards**: Adhere to the coding standards established in this project for consistency. + +#### What to Contribute? + +- **Bug Fixes**: If you've discovered a bug and know how to fix it, please submit a PR. Make sure to describe the problem and solution in the PR's description. + +- **Features**: New features can be proposed by opening an issue. Once it's discussed and approved, you or someone else can start working on it. + +- **Documentation**: Improving or correcting the project's documentation is always appreciated. + +## Setting up a Local Development Environment + +To set up a local development environment, follow these steps: + +(Note: Now, we assume that we will have a 'package.json' and the project will use separate npm scripts to launch the back-end and front-end servers. This may change later on. ) + +1. **Install Dependencies**: + + After cloning the repository and navigating to the project directory, run the following command to install all the necessary dependencies: + + ```bash + npm install + ``` + +2. **Launch the Back-End Server**: + + To start the back-end server, run: + + ```bash + npm run start-backend + ``` + +3. **Launch the Front-End (React.js) Server**: + + To start the front-end server, use: + + ```bash + npm run start-frontend + ``` + +--- + +More to add: More instructions will be added as we develop further. + +## Building and Testing the Project + +Please refer to [README.md](README.md) for details on how to build and test the project. diff --git a/.github/README.md b/.github/README.md index 7768631..96214b9 100644 --- a/.github/README.md +++ b/.github/README.md @@ -22,10 +22,101 @@ Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contrib ## Instructions for Building and Testing -To be updated +### Step 1. Clone the [Group Bill Split Repo](https://github.com/agiledev-students-fall2023/4-final-project-group-bill-splitting-app) by + +```bash +git clone https://github.com/agiledev-students-fall2023/4-final-project-group-bill-splitting-app.git +``` + +### Step 2. Make sure you have Node installed. + +Both the back-end and front-end should be running. + +### Step 3. Building and Running the Backend + +#### Navigate to the backend directory: + +```bash +cd back-end +``` + +#### Install Dependencies + +Install the necessary Node.js packages for the backend: + +```bash +npm install +``` + +#### Start the Server + +Run the following command to start the Express.js server: + +```bash +nodemon server +``` + +You can open `http://localhost:3001` to see back end.
+ +### Step 4. Building and Running the Frontend + +#### Navigate to the front-end directory + +```bash +cd front-end +``` + +#### Install Dependencies + +Install the required packages using npm. These packages are essential for the app to function correctly: + +```bash +npm install +``` + +#### Start the Application + +With all the dependencies in place, start the application by running: + +```bash +npm start +``` + +### Step 5. Accessing the App + +Once the app is running, it will be available in development mode. Open your web browser and go to [http://localhost:3000](http://localhost:3000) to view the app. + +### Step 6. Unit tests + +#### Navigate to the back-end directory + +```bash +cd back-end +``` + +#### Running Unit Tests + +To run the unit tests, execute the following command: + +```bash +npm test +``` + +### Step 6. Deployment to Digital Ocean Droplet + +Front end address is `http://165.22.42.62:3000/` + +Back end address is `http://165.22.42.62:3001/` ## Additional Links - [User Experience Design](UX-DESIGN.md) -## Notes +## Project Roles + +| Week | Product Owner | Scrum Master | +| -------- | ------------- | ------------ | +| Sprint 1 | Laura Zhao | Elaine Zhang | +| Sprint 2 | Joy Chen | Cindy Liang | +| Sprint 3 | Allison Ji | Joy Chen | +| Sprint 4 | Cindy Liang | Allison Ji | diff --git a/README.md b/README.md index 7768631..96214b9 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,101 @@ Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contrib ## Instructions for Building and Testing -To be updated +### Step 1. Clone the [Group Bill Split Repo](https://github.com/agiledev-students-fall2023/4-final-project-group-bill-splitting-app) by + +```bash +git clone https://github.com/agiledev-students-fall2023/4-final-project-group-bill-splitting-app.git +``` + +### Step 2. Make sure you have Node installed. + +Both the back-end and front-end should be running. + +### Step 3. Building and Running the Backend + +#### Navigate to the backend directory: + +```bash +cd back-end +``` + +#### Install Dependencies + +Install the necessary Node.js packages for the backend: + +```bash +npm install +``` + +#### Start the Server + +Run the following command to start the Express.js server: + +```bash +nodemon server +``` + +You can open `http://localhost:3001` to see back end.
+ +### Step 4. Building and Running the Frontend + +#### Navigate to the front-end directory + +```bash +cd front-end +``` + +#### Install Dependencies + +Install the required packages using npm. These packages are essential for the app to function correctly: + +```bash +npm install +``` + +#### Start the Application + +With all the dependencies in place, start the application by running: + +```bash +npm start +``` + +### Step 5. Accessing the App + +Once the app is running, it will be available in development mode. Open your web browser and go to [http://localhost:3000](http://localhost:3000) to view the app. + +### Step 6. Unit tests + +#### Navigate to the back-end directory + +```bash +cd back-end +``` + +#### Running Unit Tests + +To run the unit tests, execute the following command: + +```bash +npm test +``` + +### Step 6. Deployment to Digital Ocean Droplet + +Front end address is `http://165.22.42.62:3000/` + +Back end address is `http://165.22.42.62:3001/` ## Additional Links - [User Experience Design](UX-DESIGN.md) -## Notes +## Project Roles + +| Week | Product Owner | Scrum Master | +| -------- | ------------- | ------------ | +| Sprint 1 | Laura Zhao | Elaine Zhang | +| Sprint 2 | Joy Chen | Cindy Liang | +| Sprint 3 | Allison Ji | Joy Chen | +| Sprint 4 | Cindy Liang | Allison Ji | From 8c60fed49183f4896265770a43b82a0d8b1fb983 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 18:04:30 -0500 Subject: [PATCH 02/16] Update FriendsPage.jsx to get rid of the warning message --- front-end/src/components/FriendsPage.jsx | 67 ++++++++++++------------ 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/front-end/src/components/FriendsPage.jsx b/front-end/src/components/FriendsPage.jsx index 814efe1..2b51a31 100644 --- a/front-end/src/components/FriendsPage.jsx +++ b/front-end/src/components/FriendsPage.jsx @@ -55,41 +55,40 @@ function FriendsPage({ isDarkMode }) { }, []); const [settlements, setSettlements] = useState([]); - const fetchSettlementsForFriends = async () => { - if (!userData || !userData.friends) return; - - let settlements = []; - for (const friend of userData.friends) { - try { - const fromUserToFriend = await axios.get( - `${process.env.REACT_APP_BACKEND}/settlement/from/${userData._id}/to/${friend._id}` - ); - const fromFriendToUser = await axios.get( - `${process.env.REACT_APP_BACKEND}/settlement/from/${friend._id}/to/${userData._id}` - ); - - settlements.push({ - friend: friend, - fromUserToFriend: fromUserToFriend.data, - fromFriendToUser: fromFriendToUser.data, - }); - } catch (error) { - console.error( - "Error fetching settlements for friend:", - friend._id, - error - ); - settlements.push({ - friend: friend, - fromUserToFriend: [], - fromFriendToUser: [], - }); - } - } - setSettlements(settlements); - }; - useEffect(() => { + const fetchSettlementsForFriends = async () => { + if (!userData || !userData.friends) return; + + let settlements = []; + for (const friend of userData.friends) { + try { + const fromUserToFriend = await axios.get( + `${process.env.REACT_APP_BACKEND}/settlement/from/${userData._id}/to/${friend._id}` + ); + const fromFriendToUser = await axios.get( + `${process.env.REACT_APP_BACKEND}/settlement/from/${friend._id}/to/${userData._id}` + ); + + settlements.push({ + friend: friend, + fromUserToFriend: fromUserToFriend.data, + fromFriendToUser: fromFriendToUser.data, + }); + } catch (error) { + console.error( + "Error fetching settlements for friend:", + friend._id, + error + ); + settlements.push({ + friend: friend, + fromUserToFriend: [], + fromFriendToUser: [], + }); + } + } + setSettlements(settlements); + }; fetchSettlementsForFriends(); }, [userData]); From 10f5fa01eca0c1ec75fca91a66142701df3aef50 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 18:09:51 -0500 Subject: [PATCH 03/16] Update UserInfo.jsx to fix warnings Fix warnings --- front-end/src/components/UserInfo.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/front-end/src/components/UserInfo.jsx b/front-end/src/components/UserInfo.jsx index c502450..adb8b51 100644 --- a/front-end/src/components/UserInfo.jsx +++ b/front-end/src/components/UserInfo.jsx @@ -1,7 +1,7 @@ /* UserInfo.jsx - components of User Info(Account) Page */ import React, { useState, useEffect } from "react"; -import { useNavigate, Link } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import "../styles/UserInfo.css"; import Navbar from "./Navbar"; import axios from "axios"; @@ -26,9 +26,9 @@ function UserInfo({ isDarkMode, toggleDarkMode }) { e.preventDefault(); const templateParams = { from_name: data.name, - message: message + message: message, }; - + emailjs .send( process.env.REACT_APP_EMAIL_SERVICE_ID, @@ -69,7 +69,7 @@ function UserInfo({ isDarkMode, toggleDarkMode }) { } }; fetchData(); - }, []); + }, [navigate]); // This effect runs when the `isDarkMode` value changes useEffect(() => { From da98d21609fd65ca91318be32b699648b276a530 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 20:14:10 -0500 Subject: [PATCH 04/16] Handle warning --- front-end/src/components/AddEvent.jsx | 523 ++++++++++---------- front-end/src/components/AddExpense.js | 7 +- front-end/src/components/AddFriendModal.jsx | 2 +- front-end/src/components/Event.js | 2 +- front-end/src/components/Events.jsx | 4 +- front-end/src/components/Expense.jsx | 22 +- front-end/src/components/Home.jsx | 65 ++- front-end/src/components/Login.jsx | 19 +- front-end/src/components/Navbar.jsx | 5 +- 9 files changed, 328 insertions(+), 321 deletions(-) diff --git a/front-end/src/components/AddEvent.jsx b/front-end/src/components/AddEvent.jsx index 88a159a..024c144 100644 --- a/front-end/src/components/AddEvent.jsx +++ b/front-end/src/components/AddEvent.jsx @@ -1,290 +1,291 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; -import addMemberButton from "../images/addMember.png"; -import { jwtDecode } from 'jwt-decode'; +import addMemberButton from "../images/addMember.png"; +import { jwtDecode } from "jwt-decode"; -function AddEvent({addEvent, onClose}){ - const[friendsList, setfriendsList] = useState([]) - const[selectedFriends, setselectedFriends] = useState(false) - const[searchQuery, setSearchQuery] = useState(''); - const[searchPerformed, setSearchPerformed] = useState(false); - const[selectedMember, setselectedMember] = useState([]) - const[loading, setLoading] = useState(false) - const [currentUser, setCurrentUser] = useState(null); - const[errors, setErrors] = useState({ - eventName: false, - Date: false, - Description: false, - Members: false - }); - const[eventData, seteventData] = useState({ - eventName: '', - Date: '', - Description: '', - Members: [], - }) +function AddEvent({ addEvent, onClose }) { + const [friendsList, setfriendsList] = useState([]); + const [selectedFriends, setselectedFriends] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); + const [searchPerformed, setSearchPerformed] = useState(false); + const [selectedMember, setselectedMember] = useState([]); + const [loading, setLoading] = useState(false); + const [errors, setErrors] = useState({ + eventName: false, + Date: false, + Description: false, + Members: false, + }); + const [eventData, seteventData] = useState({ + eventName: "", + Date: "", + Description: "", + Members: [], + }); - const backupData_friends = [{ - "id":"507f1f77bcf86cd799439011", - "username":"Gaby Coupar", - "avatar":"https://robohash.org/temporererumomnis.png?size=50x50\u0026set=set1", - "phone":"435-715-2899", - "email":"gcoupar0@rakuten.co.jp" - }, { - "id": "507f1f77bcf86cd799439012", - "username": "Andy Gaber", - "avatar": "https://robohash.org/quaeetcorrupti.png?size=50x50&set=set1", - "phone":"425-712-2309", - "email":"gmember0@rakuten.co.jp" - }] - - const handleInputChange =(e) =>{ - const{name, value} = e.target; - seteventData(prev => ({...prev,[name]:value})) + const handleInputChange = (e) => { + const { name, value } = e.target; + seteventData((prev) => ({ ...prev, [name]: value })); + }; + + const validateForm = () => { + const newErrors = {}; + let isValid = true; + + // Validate Event Name + if (!eventData.eventName) { + newErrors.eventName = true; + isValid = false; } - const validateForm = () => { - const newErrors = {}; - let isValid = true; + // Validate Date + if (!eventData.Date) { + newErrors.Date = true; + isValid = false; + } - // Validate Event Name - if (!eventData.eventName) { - newErrors.eventName = true; - isValid = false; - } + // Validate Description + if (!eventData.Description) { + newErrors.Description = true; + isValid = false; + } + if (selectedMember.length === 0) { + alert("Please select at least one member."); + newErrors.Members = true; + isValid = false; + } - // Validate Date - if (!eventData.Date) { - newErrors.Date = true; - isValid = false; - } + setErrors(newErrors); + return isValid; + }; - // Validate Description - if (!eventData.Description) { - newErrors.Description = true; - isValid = false; - } - if (selectedMember.length === 0) { - alert('Please select at least one member.'); - newErrors.Members = true; - isValid = false; - } + const handleAddEvent = async () => { + if (!validateForm()) { + return; // Stop the function if validation fails + } - setErrors(newErrors); - return isValid; + const submitData = { + eventName: eventData.eventName, + Date: eventData.Date, + Description: eventData.Description, + Members: selectedMember, }; - const handleAddEvent = async () => { - if (!validateForm()) { - return; // Stop the function if validation fails - } - - const submitData = { - eventName: eventData.eventName, - Date: eventData.Date, - Description: eventData.Description, - Members: selectedMember - }; - - try { - console.log('Submitting data:', submitData); - const response = await axios.post(`${process.env.REACT_APP_BACKEND}/addEvent`, submitData); - console.log(`Event added:`, response.data); - onClose(); // Optionally close the form upon successful submission - } catch (error) { - console.error('Failed to submit event:', error.response); - } + try { + const response = await axios.post( + `${process.env.REACT_APP_BACKEND}/addEvent`, + submitData + ); + onClose(); // Optionally close the form upon successful submission + } catch (error) { + console.error("Failed to submit event:", error.response); } + }; - //fetch friends' data for the member list - async function friendsCL(userID){ - setLoading(true); - setSearchPerformed(true); - console.log('userID in friendsCL:', userID, 'Type:', typeof userID); - try{ - - // Make the API call - const response = await axios.get(`${process.env.REACT_APP_BACKEND}/addEventMember/friendsList/${userID}`); - //return the data - console.log(response) - setfriendsList(Array.isArray(response.data.friends) ? response.data.friends : [response.data.friends]); - //setfriendsList(response.data.flatMap(user => user.friends)); + //fetch friends' data for the member list + async function friendsCL(userID) { + setLoading(true); + setSearchPerformed(true); + try { + // Make the API call + const response = await axios.get( + `${process.env.REACT_APP_BACKEND}/addEventMember/friendsList/${userID}` + ); + //return the data + setfriendsList( + Array.isArray(response.data.friends) + ? response.data.friends + : [response.data.friends] + ); + } catch (error) { + console.error("There was an error fetching the data:", error.response); + } finally { + setLoading(false); + } + } - }catch(error){ - console.error("There was an error fetching the data:", error.response); - setfriendsList(backupData_friends) - } - finally{ - setLoading(false); + const handleSearch = () => { + const token = localStorage.getItem("token"); + const decodedUser = jwtDecode(token); + if (decodedUser && decodedUser.id) { + friendsCL(decodedUser.id); + setselectedMember((prevMembers) => { + // Check if the current user's ID is already in the selected members + if (prevMembers.some((member) => member.id === decodedUser.id)) { + return prevMembers; } + // Add the current user's ID to the selected members + return [...prevMembers, decodedUser.id]; + }); + } else { } + }; -/* - useEffect(() => { - // Retrieve the current user from local storage - const token = localStorage.getItem("token"); - const decodedUser = jwtDecode(token); - if (decodedUser && decodedUser.id) { - console.log(decodedUser.id) - setselectedMember(prevMembers => { - // Check if the current user's ID is already in the selected members - if (prevMembers.some(member => member.id === decodedUser.id)) { - return prevMembers; - } - // Add the current user's ID to the selected members - return [...prevMembers, decodedUser.id]; - }); - } else { - console.error("No valid user found in local storage."); - } - }, []); - */ + const handleSearchChange = (e) => { + setSearchQuery(e.target.value); + }; - /* - useEffect(() => { - console.log('Selected Members:', selectedMember); - }, [selectedMember]); - */ + const filteredFriends = searchQuery + ? friendsList.filter((friend) => + friend?.username?.toLowerCase().includes(searchQuery.toLowerCase()) + ) + : []; - const handleSearch = () => { - const token = localStorage.getItem("token"); - const decodedUser = jwtDecode(token); - if (decodedUser && decodedUser.id) { - console.log(decodedUser.id) - setCurrentUser(decodedUser.id) - friendsCL(decodedUser.id) - setselectedMember(prevMembers => { - // Check if the current user's ID is already in the selected members - if (prevMembers.some(member => member.id === decodedUser.id)) { - return prevMembers; - } - // Add the current user's ID to the selected members - return [...prevMembers, decodedUser.id]; - }); - } else { - console.error("No valid user found in local storage."); - } - }; - - - const handleSearchChange = (e) =>{ - setSearchQuery(e.target.value) + const handleSelectedMember = (memberId) => { + if (selectedMember.includes(memberId)) { + return; } - - const filteredFriends = searchQuery ? friendsList.filter(friend => friend?.username?.toLowerCase().includes(searchQuery.toLowerCase())) : []; - - const handleSelectedMember = (memberId) =>{ - if(selectedMember.includes(memberId)){ - return; - } - const member = friendsList.find(p => p._id === memberId) - if(member){ - setselectedMember([...selectedMember, memberId]); - console.log(`Member Successfuly Added!`) - } - + const member = friendsList.find((p) => p._id === memberId); + if (member) { + setselectedMember([...selectedMember, memberId]); } + }; - useEffect(()=>{ - seteventData(prev => ({ - ...prev, - Members:selectedMember - })); - },[selectedMember]) + useEffect(() => { + seteventData((prev) => ({ + ...prev, + Members: selectedMember, + })); + }, [selectedMember]); - return( -
-
- × -
{e.preventDefault();handleAddEvent();}}> -
- - {errors.eventName &&

Event Name is required.

} -
-
- - {errors.Date &&

Event Date is required.

} -
-
- - {errors.Description &&

Event Description is required.

} -
- - - - + {selectedFriends && ( +
+
+ setselectedFriends(false)} > - Select Members + × + + + - {selectedFriends && ( -
-
- setselectedFriends(false)}>× - - - {loading ? (

Loading...

- ) : searchPerformed ? ( - filteredFriends.length>0?( - filteredFriends.map(friend =>( -
- - {friend.username} - {!selectedMember.includes(friend._id) && ( - Add Member handleSelectedMember(friend._id)} - style={{ width: "40px", height: "40px" }} - /> - )} -
- )) - ) : searchPerformed?( -

No Friend Found

- ): null - ): null - } -
- -
- )} - -
- -
- + {loading ? ( +

Loading...

+ ) : searchPerformed ? ( + filteredFriends.length > 0 ? ( + filteredFriends.map((friend) => ( +
+ {`${friend.username}'s + {friend.username} + {!selectedMember.includes(friend._id) && ( + Add Member handleSelectedMember(friend._id)} + style={{ width: "40px", height: "40px" }} + /> + )} +
+ )) + ) : searchPerformed ? ( +

No Friend Found

+ ) : null + ) : null} +
+
-
- ); + )} + +
+ +
+ +
+ + ); } -export default AddEvent; \ No newline at end of file +export default AddEvent; diff --git a/front-end/src/components/AddExpense.js b/front-end/src/components/AddExpense.js index 7986bfd..e6cd2ea 100644 --- a/front-end/src/components/AddExpense.js +++ b/front-end/src/components/AddExpense.js @@ -1,12 +1,11 @@ import axios from "axios"; import React, { useState, useEffect } from "react"; -import { Link, useNavigate, useParams } from "react-router-dom"; +import { Link, useParams } from "react-router-dom"; import Navbar from "./Navbar"; import SplitModal from "./SplitModal"; import "../styles/AddExpense.css"; const AddExpense = (props) => { - const navigate = useNavigate(); const isDarkMode = props.isDarkMode; const { eventId } = useParams(); const [showModal, setShowModal] = useState(false); @@ -243,7 +242,7 @@ const AddExpense = (props) => { } }; fetchPeople(); - }, []); + }, [eventId]); const handlePaidByChange = (event) => { const selectedUserId = event.target.value; @@ -292,7 +291,7 @@ const AddExpense = (props) => { } }; fetchAvailablePeople(); - }, []); + }, [eventId]); const handleSelectPerson = (personId) => { const person = availablePeople.find((p) => p._id === personId); diff --git a/front-end/src/components/AddFriendModal.jsx b/front-end/src/components/AddFriendModal.jsx index 5bec811..5ea58f4 100644 --- a/front-end/src/components/AddFriendModal.jsx +++ b/front-end/src/components/AddFriendModal.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import addFriendButton from "../images/plus-button.png"; import { jwtDecode } from "jwt-decode"; diff --git a/front-end/src/components/Event.js b/front-end/src/components/Event.js index b1dc6ad..a20cd95 100644 --- a/front-end/src/components/Event.js +++ b/front-end/src/components/Event.js @@ -132,7 +132,7 @@ const Event = (props) => { } }; fetchEvent(); - }, []); + }, [eventId]); useEffect(() => { const token = localStorage.getItem("token"); diff --git a/front-end/src/components/Events.jsx b/front-end/src/components/Events.jsx index b85f844..cb9f203 100644 --- a/front-end/src/components/Events.jsx +++ b/front-end/src/components/Events.jsx @@ -80,7 +80,7 @@ function Events({ isDarkMode }) { } } dataFetch(); - }, []); + }, [decode.id]); useEffect(() => { console.log(decode.id); @@ -98,7 +98,7 @@ function Events({ isDarkMode }) { }; fetchSettlements(); - }, []); + }, [decode.id]); function calculateAmounts(expenses, currentUserId) { let amountOwed = 0; // Amount the current user owes to others diff --git a/front-end/src/components/Expense.jsx b/front-end/src/components/Expense.jsx index bf93f90..fda9f5e 100644 --- a/front-end/src/components/Expense.jsx +++ b/front-end/src/components/Expense.jsx @@ -14,15 +14,28 @@ function Expense({ isDarkMode }) { const { expenseId } = useParams(); function reformatDate(dateStr) { - const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + const months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; const date = new Date(dateStr); - + const monthName = months[date.getMonth()]; const day = date.getDate(); const year = date.getFullYear(); return `${monthName} ${day} ${year}`; -} + } const fetchData = async () => { try { @@ -103,9 +116,6 @@ function Expense({ isDarkMode }) { return filteredExpenses; }; - { - /* navigates to the previous page */ - } const handleTitleClick = () => { navigate(-1); }; diff --git a/front-end/src/components/Home.jsx b/front-end/src/components/Home.jsx index 5cf748f..e2ea526 100644 --- a/front-end/src/components/Home.jsx +++ b/front-end/src/components/Home.jsx @@ -98,7 +98,6 @@ const Home = ({ isDarkMode }) => { const currentUser = decodeToken(token); if (currentUser) { - const expenseRes = await axios.get( `${process.env.REACT_APP_BACKEND}/settlement/from/${currentUser.id}` ); @@ -181,41 +180,41 @@ const Home = ({ isDarkMode }) => { }, []); const [settlements, setSettlements] = useState([]); - const fetchSettlementsForFriends = async () => { - if (!userData || !userData.friends) return; - let settlements = []; - for (const friend of userData.friends) { - try { - const fromUserToFriend = await axios.get( - `${process.env.REACT_APP_BACKEND}/settlement/from/${userData._id}/to/${friend._id}` - ); - const fromFriendToUser = await axios.get( - `${process.env.REACT_APP_BACKEND}/settlement/from/${friend._id}/to/${userData._id}` - ); + useEffect(() => { + const fetchSettlementsForFriends = async () => { + if (!userData || !userData.friends) return; + + let settlements = []; + for (const friend of userData.friends) { + try { + const fromUserToFriend = await axios.get( + `${process.env.REACT_APP_BACKEND}/settlement/from/${userData._id}/to/${friend._id}` + ); + const fromFriendToUser = await axios.get( + `${process.env.REACT_APP_BACKEND}/settlement/from/${friend._id}/to/${userData._id}` + ); - settlements.push({ - friend: friend, - fromUserToFriend: fromUserToFriend.data, - fromFriendToUser: fromFriendToUser.data, - }); - } catch (error) { - console.error( - "Error fetching settlements for friend:", - friend._id, - error - ); - settlements.push({ - friend: friend, - fromUserToFriend: [], - fromFriendToUser: [], - }); + settlements.push({ + friend: friend, + fromUserToFriend: fromUserToFriend.data, + fromFriendToUser: fromFriendToUser.data, + }); + } catch (error) { + console.error( + "Error fetching settlements for friend:", + friend._id, + error + ); + settlements.push({ + friend: friend, + fromUserToFriend: [], + fromFriendToUser: [], + }); + } } - } - setSettlements(settlements); - }; - - useEffect(() => { + setSettlements(settlements); + }; fetchSettlementsForFriends(); }, [userData]); diff --git a/front-end/src/components/Login.jsx b/front-end/src/components/Login.jsx index 6994e52..fa73167 100644 --- a/front-end/src/components/Login.jsx +++ b/front-end/src/components/Login.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import "../styles/Login.css"; import userImage from "../images/user.png"; -import { useNavigate, Navigate, useSearchParams } from "react-router-dom"; +import { Navigate } from "react-router-dom"; import axios from "axios"; import { useLocation } from "react-router-dom"; @@ -24,12 +24,9 @@ const Login = () => { } }, [showAlert]); - let [urlSearchParams] = useSearchParams(); const [response, setResponse] = useState({}); const [errorMessage, setErrorMessage] = useState(""); - const navigate = useNavigate(); - const handleInputChange = (e) => { const { name, value } = e.target; setFormData({ @@ -49,11 +46,15 @@ const Login = () => { e.preventDefault(); try { - const response = await axios.post(`${process.env.REACT_APP_BACKEND}/`, formData, { - headers: { - "Content-Type": "application/json", - }, - }); + const response = await axios.post( + `${process.env.REACT_APP_BACKEND}/`, + formData, + { + headers: { + "Content-Type": "application/json", + }, + } + ); console.log(`Server response: ${JSON.stringify(response.data, null, 0)}`); setResponse(response.data); } catch (error) { diff --git a/front-end/src/components/Navbar.jsx b/front-end/src/components/Navbar.jsx index 15223d5..a53daac 100644 --- a/front-end/src/components/Navbar.jsx +++ b/front-end/src/components/Navbar.jsx @@ -1,12 +1,11 @@ import React from "react"; -import { Link, useParams } from "react-router-dom"; +import { Link } from "react-router-dom"; import "../styles/Navbar.css"; import { useLocation } from "react-router-dom"; import calendar from "../images/calendar.png"; import group from "../images/group.png"; import home from "../images/home.png"; import person from "../images/person.png"; -import { jwtDecode } from "jwt-decode"; const Navbar = ({ isDarkMode }) => { const location = useLocation(); @@ -19,8 +18,6 @@ const Navbar = ({ isDarkMode }) => { console.error("Plese login in view pages"); return null; } - const decoded = jwtDecode(token); - const userId = decoded.id; //changed up the events route and removed contact page return ( From f766e5ea325dcbbc3ee6a643018dc8adb948fd4c Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 20:38:20 -0500 Subject: [PATCH 05/16] Delete hardcode data --- front-end/src/components/Event.js | 34 -------------------------- front-end/src/components/Home.jsx | 6 ----- front-end/src/components/SplitModal.js | 24 +++++++++--------- 3 files changed, 12 insertions(+), 52 deletions(-) diff --git a/front-end/src/components/Event.js b/front-end/src/components/Event.js index a20cd95..c927f25 100644 --- a/front-end/src/components/Event.js +++ b/front-end/src/components/Event.js @@ -97,38 +97,6 @@ const Event = (props) => { console.log(result.data); } catch (err) { console.error(err); - - // make some backup fake data - const backupData = { - id: 2, - name: "LA Road Trip", - expenses: [ - { - id: 1, - name: "Lunch", - amount: 358, - creator: "Jane", - date: "06/16/2023", - }, - { - id: 2, - name: "Flights to LA", - amount: 261, - creator: "Tom", - date: "01/21/2023", - }, - { - id: 3, - name: "Hotels", - amount: 170, - creator: "David", - date: "08/02/2023", - }, - ], - description: "Road trip with friends", - }; - - setData(backupData); } }; fetchEvent(); @@ -137,7 +105,6 @@ const Event = (props) => { useEffect(() => { const token = localStorage.getItem("token"); const currentUser = jwtDecode(token); - //const currentUserId = currentUser.id; if (data.expenses && currentUser) { const processedExpenses = processUserExpenses( data.expenses, @@ -194,7 +161,6 @@ const Event = (props) => {
{" "} - {/* */} Add Expense
diff --git a/front-end/src/components/Home.jsx b/front-end/src/components/Home.jsx index e2ea526..022745f 100644 --- a/front-end/src/components/Home.jsx +++ b/front-end/src/components/Home.jsx @@ -66,11 +66,6 @@ const Home = ({ isDarkMode }) => { } useEffect(() => { - // const getTokenFromLocalStorage = () => { - // const token = localStorage.getItem("token"); - // return token; - // }; - const decodeToken = (token) => { try { const currentUser = jwtDecode(token); @@ -87,7 +82,6 @@ const Home = ({ isDarkMode }) => { const fetchData = async () => { try { - // const token = getTokenFromLocalStorage(); const token = localStorage.getItem("token"); if (!token) { console.error("No token found"); diff --git a/front-end/src/components/SplitModal.js b/front-end/src/components/SplitModal.js index fd73e0d..f8f5ab4 100644 --- a/front-end/src/components/SplitModal.js +++ b/front-end/src/components/SplitModal.js @@ -175,10 +175,10 @@ function SplitModal({
{participant.username} {activeTab === "equally" && ( - - {"$" + (totalAmount / participants.length).toFixed(2)} + + {"$" + (totalAmount / participants.length).toFixed(2)} - )} + )} {activeTab === "percentage" && (
-
- $ - handleAmountChange(e, participant._id)} - /> -
+
+ $ + handleAmountChange(e, participant._id)} + /> +
)}
From ddaf7ea89e2df5350d85a968e7eadff7f48ddef9 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 21:34:50 -0500 Subject: [PATCH 06/16] Delete some console.log --- front-end/src/components/Events.jsx | 12 ------------ front-end/src/components/Expense.jsx | 4 ---- 2 files changed, 16 deletions(-) diff --git a/front-end/src/components/Events.jsx b/front-end/src/components/Events.jsx index cb9f203..682fbed 100644 --- a/front-end/src/components/Events.jsx +++ b/front-end/src/components/Events.jsx @@ -13,9 +13,6 @@ function Events({ isDarkMode }) { const [amountOwed, setAmountOwed] = useState(0); const [amountOwedBy, setAmountOwedBy] = useState(0); const [searchTerm, setSearchTerm] = useState(""); - //const[showFilter, setShowFilter] = useState(false); - //const[selectedFilter, setSelectedFilter] = useState('all'); - //const[filteredEvents, setFilteredEvents] = useState([]); function reformatDate(dateStr) { const months = [ @@ -65,14 +62,11 @@ function Events({ isDarkMode }) { if (!decode.id) { console.error("No current user found in local storage."); return; - } else { - console.log(decode.id); } //requesting data from the mock API endpoint const response = await axios.get( `${process.env.REACT_APP_BACKEND}/events/for/${decode.id}` ); - console.log(response); //return the data setEventData(response.data); } catch (error) { @@ -83,13 +77,11 @@ function Events({ isDarkMode }) { }, [decode.id]); useEffect(() => { - console.log(decode.id); const fetchSettlements = async () => { try { const response = await axios.get( `${process.env.REACT_APP_BACKEND}/settlement/from/${decode.id}` ); - console.log("Settlements:", response.data); setSettlements(response.data); // Process and use the fetched settlements here } catch (error) { @@ -126,10 +118,6 @@ function Events({ isDarkMode }) { ); setAmountOwed(amountOwed); setAmountOwedBy(amountOwedBy); - // Now you can use amountOwed and amountOwedBy in your component - console.log( - `Amount Owed: ${amountOwed}, Amount Owed By: ${amountOwedBy}` - ); } }, [settlements, decode.id]); diff --git a/front-end/src/components/Expense.jsx b/front-end/src/components/Expense.jsx index fda9f5e..641f0a6 100644 --- a/front-end/src/components/Expense.jsx +++ b/front-end/src/components/Expense.jsx @@ -42,11 +42,9 @@ function Expense({ isDarkMode }) { const response = await axios.get( `${process.env.REACT_APP_BACKEND}/expense/ExpenseDetail/${expenseId}` ); - console.log("Fetched Data:", response.data); // Debug const processedData = processExpenses(response.data, currentuserId); setExpensesData(response.data); setFilteredData(processedData); - console.log(":", processedData); } catch (error) { console.error("There was an error fetching the data:", error); } @@ -58,10 +56,8 @@ function Expense({ isDarkMode }) { `${process.env.REACT_APP_BACKEND}/expenseStatus/${settlementId}`, { status: newStatus } ); - console.log("Settlements updated:", response.data); } catch (error) { console.error("Error updating settlements:", error); - console.log(error.response.data); } }; From 045eb61398684456e909197b2f0ca07569b8e5bb Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 21:36:30 -0500 Subject: [PATCH 07/16] Delete more console.log --- front-end/src/components/AddExpense.js | 1 - front-end/src/components/Event.js | 2 -- front-end/src/components/FriendDetailPage.js | 2 -- 3 files changed, 5 deletions(-) diff --git a/front-end/src/components/AddExpense.js b/front-end/src/components/AddExpense.js index e6cd2ea..09e57f9 100644 --- a/front-end/src/components/AddExpense.js +++ b/front-end/src/components/AddExpense.js @@ -204,7 +204,6 @@ const AddExpense = (props) => { peopleSplit: peopleSplit, event: eventId, // make sure it is not "undefined" }; - console.log(submissionData); try { const response = await axios.post( `${process.env.REACT_APP_BACKEND}/add-expense`, diff --git a/front-end/src/components/Event.js b/front-end/src/components/Event.js index c927f25..45b8ebd 100644 --- a/front-end/src/components/Event.js +++ b/front-end/src/components/Event.js @@ -87,14 +87,12 @@ const Event = (props) => { useEffect(() => { // fetch some mock data about expense - console.log("fetching the event"); const fetchEvent = async () => { try { const result = await axios.get( `${process.env.REACT_APP_BACKEND}/event/${eventId}` ); setData(result.data); - console.log(result.data); } catch (err) { console.error(err); } diff --git a/front-end/src/components/FriendDetailPage.js b/front-end/src/components/FriendDetailPage.js index 991871f..bb6fa4b 100644 --- a/front-end/src/components/FriendDetailPage.js +++ b/front-end/src/components/FriendDetailPage.js @@ -60,10 +60,8 @@ function FriendDetailPage({ isDarkMode }) { `${process.env.REACT_APP_BACKEND}/expenseStatus/${settlementId}`, { status: newStatus } ); - console.log("Settlements updated:", response.data); } catch (error) { console.error("Error updating settlements:", error); - console.log(error.response.data); } }; From 1c4badb8b90d1883116551213097dcea3ba4b4dc Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 22:10:52 -0500 Subject: [PATCH 08/16] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 96214b9..335b3e3 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,15 @@ To run the unit tests, execute the following command: npm test ``` -### Step 6. Deployment to Digital Ocean Droplet +### Step 7. CI/CD using GitHub Actions + +We have set up Continuous Integration and Continuous Deployment (CI/CD) using GitHub Actions. Every push to the main branch triggers automatic testing and deployment to our Digital Ocean Droplet. + +### Step 8. Docker Integration + +We have containerized our application using Docker. You can build and run the Docker containers for both the front end and back end with the provided Dockerfiles. + +## Deployment to Digital Ocean Droplet Front end address is `http://165.22.42.62:3000/` From 1df15d08b5ccca1fbd18af49d5eb035290fee0a5 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 22:15:19 -0500 Subject: [PATCH 09/16] Update UserInfo.jsx --- front-end/src/components/UserInfo.jsx | 58 ++++++++++++++++----------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/front-end/src/components/UserInfo.jsx b/front-end/src/components/UserInfo.jsx index adb8b51..465a55c 100644 --- a/front-end/src/components/UserInfo.jsx +++ b/front-end/src/components/UserInfo.jsx @@ -1,18 +1,24 @@ /* UserInfo.jsx - components of User Info(Account) Page */ import React, { useState, useEffect } from "react"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, Link } from "react-router-dom"; import "../styles/UserInfo.css"; import Navbar from "./Navbar"; import axios from "axios"; import { jwtDecode } from "jwt-decode"; -import emailjs from "emailjs-com"; function UserInfo({ isDarkMode, toggleDarkMode }) { const [data, setData] = useState([]); const [message, setMessage] = useState(""); + + const [isLoggedIn, setIsLoggedIn] = useState(true); + const navigate = useNavigate(); + const handleButtonClick = () => { + navigate("/"); + }; + const redirectToForgotPassword = () => { navigate("/forgot-password"); }; @@ -22,28 +28,24 @@ function UserInfo({ isDarkMode, toggleDarkMode }) { navigate("/"); }; - const sendEmail = (e) => { + const sendMessage = async (e) => { e.preventDefault(); - const templateParams = { - from_name: data.name, + const body = { + user: data.name, message: message, }; - emailjs - .send( - process.env.REACT_APP_EMAIL_SERVICE_ID, - process.env.REACT_APP_EMAIL_TEMPLATE_ID, - templateParams, - process.env.REACT_APP_EMAIL_USER_ID - ) - .then( - (result) => { - window.location.reload(); //This is if you still want the page to reload (since e.preventDefault() cancelled that behavior) - }, - (error) => { - console.log(error.text); - } + try { + console.log("Submitting message:", body); + const response = await axios.post( + `${process.env.REACT_APP_BACKEND}/sendMessage`, + body ); + console.log(`Message sent:`, response.data); + window.location.reload(); + } catch (error) { + console.error("Failed to send message:", error.response); + } }; useEffect(() => { @@ -52,8 +54,8 @@ function UserInfo({ isDarkMode, toggleDarkMode }) { const token = localStorage.getItem("token"); if (!token) { console.error("No token found"); - navigate("/"); - return null; + setIsLoggedIn(false); + return; } const decoded = jwtDecode(token); const userId = decoded.id; @@ -69,7 +71,7 @@ function UserInfo({ isDarkMode, toggleDarkMode }) { } }; fetchData(); - }, [navigate]); + }, []); // This effect runs when the `isDarkMode` value changes useEffect(() => { @@ -88,6 +90,16 @@ function UserInfo({ isDarkMode, toggleDarkMode }) { return
Loading user data...
; } + if (!isLoggedIn) + return ( +
+
Please log in to view pages!
+ +
+ ); + return (
@@ -140,7 +152,7 @@ function UserInfo({ isDarkMode, toggleDarkMode }) {
  • Contact us
    -
    + Date: Wed, 6 Dec 2023 22:15:52 -0500 Subject: [PATCH 10/16] Update UserInfo.jsx --- front-end/src/components/UserInfo.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front-end/src/components/UserInfo.jsx b/front-end/src/components/UserInfo.jsx index 465a55c..658c3d6 100644 --- a/front-end/src/components/UserInfo.jsx +++ b/front-end/src/components/UserInfo.jsx @@ -1,7 +1,7 @@ /* UserInfo.jsx - components of User Info(Account) Page */ import React, { useState, useEffect } from "react"; -import { useNavigate, Link } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import "../styles/UserInfo.css"; import Navbar from "./Navbar"; import axios from "axios"; From 4af245e8cc7e7ddbb1e184ed0fb0efa30d28eb78 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 22:20:49 -0500 Subject: [PATCH 11/16] Update addEventMemberRoute.js --- back-end/routes/addEventMemberRoute.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/back-end/routes/addEventMemberRoute.js b/back-end/routes/addEventMemberRoute.js index 5887d41..8d7e270 100644 --- a/back-end/routes/addEventMemberRoute.js +++ b/back-end/routes/addEventMemberRoute.js @@ -8,12 +8,10 @@ router.get("/friendsList/:userId", async (req, res) => { try { //fetch all data const userId = req.params.userId; - console.log("userId:", userId); const userWithFriends = await User.findById(userId).populate({ path: "friends", model: "User", }); - console.log(userWithFriends); if (!userWithFriends) { return res.status(404).send("User not found"); } From 889c91193a45b42286ff3793688a26961ff6b1c6 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Wed, 6 Dec 2023 23:45:13 -0500 Subject: [PATCH 12/16] Fix add expense select people bug --- front-end/src/components/AddExpense.js | 43 +++++++++----- front-end/src/styles/AddExpense.css | 77 +++++++++++++------------- 2 files changed, 67 insertions(+), 53 deletions(-) diff --git a/front-end/src/components/AddExpense.js b/front-end/src/components/AddExpense.js index 09e57f9..b2acbf8 100644 --- a/front-end/src/components/AddExpense.js +++ b/front-end/src/components/AddExpense.js @@ -433,22 +433,35 @@ const AddExpense = (props) => { )}
    -
    - - +
    + {Array.isArray(availablePeople) && + availablePeople.map((person) => ( +
    handleSelectPerson(person._id)} + style={{ + cursor: "pointer", + margin: "5px", + display: "flex", + alignItems: "center", + }} + > + {person.username} + {person.username} +
    + ))}
    +
    diff --git a/front-end/src/styles/AddExpense.css b/front-end/src/styles/AddExpense.css index 7ea0b96..11057f7 100644 --- a/front-end/src/styles/AddExpense.css +++ b/front-end/src/styles/AddExpense.css @@ -1,104 +1,105 @@ #addExpense form { - @apply flex flex-col space-y-3 mt-5 bg-cyan-50 rounded-2xl p-8; + @apply flex flex-col space-y-3 mt-5 bg-cyan-50 rounded-2xl p-8; } #addExpense form div { - @apply ml-6; + @apply ml-6; } #addExpense form .submitBtn { - @apply m-auto; + @apply m-auto; } .submitBtn button { - @apply w-24 h-8 rounded-full bg-cyan-100 hover:bg-orange-100 active:bg-orange-200 mt-10; + @apply w-24 h-8 rounded-full bg-cyan-100 hover:bg-orange-100 active:bg-orange-200 mt-10; } .splitMethods button { - @apply w-60 h-8 rounded-sm shadow-lg active:bg-cyan-100; + @apply w-60 h-8 rounded-sm shadow-lg active:bg-cyan-100; } #selected-container { - height: 100px; - overflow-y: auto; - margin-bottom: 10px; - padding: 5px; + height: 100px; + overflow-y: auto; + margin-bottom: 10px; + padding: 5px; } #available-container { - height: 65px; - width: 150px; - overflow-y: auto; - margin-bottom: 10px; - padding: 5px; + @apply overflow-auto; + height: 65px; + width: 150px; + overflow-y: auto; + margin-bottom: 10px; + padding: 5px; } .remove-button { - margin-left: auto; - padding: 5px 5px; - background-color: #f44336; /* Red color for remove button */ - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 14px; - transition: background-color 0.3s ease; + margin-left: auto; + padding: 5px 5px; + background-color: #f44336; /* Red color for remove button */ + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s ease; } .remove-button:hover { - background-color: #d32f2f; + background-color: #d32f2f; } .space-to-scroll { - height: 60px; + height: 60px; } /* Dark Mode for Add Expense Modal */ .body-dark-mode { - @apply bg-gray-800; /* add dark mode to the outter most background on the page (area around the container) */ - } + @apply bg-gray-800; /* add dark mode to the outter most background on the page (area around the container) */ +} .dark-mode .add-expense-page-container { - @apply bg-gray-800; /* the area within the outter most background, where "Event | Add New Expense" located */ + @apply bg-gray-800; /* the area within the outter most background, where "Event | Add New Expense" located */ } .dark-mode #addExpense form { - @apply bg-gray-700; + @apply bg-gray-700; } .dark-mode #addExpense form input, .dark-mode #addExpense form select, .dark-mode #addExpense form button { - @apply bg-gray-900 text-white border-gray-700; + @apply bg-gray-900 text-white border-gray-700; } .dark-mode #addExpense form .submitBtn button, .dark-mode .splitMethods button { - @apply bg-gray-700 hover:bg-gray-600 active:bg-gray-500; + @apply bg-gray-700 hover:bg-gray-600 active:bg-gray-500; } .dark-mode #selected-container, .dark-mode #available-container { - @apply bg-gray-900; + @apply bg-gray-900; } .dark-mode .remove-button { - @apply bg-red-700 hover:bg-red-600; + @apply bg-red-700 hover:bg-red-600; } .dark-mode #addExpense form label, .dark-mode #addExpense form .remove-button { - @apply text-gray-300; + @apply text-gray-300; } .dark-mode .splitMethods button { - @apply text-gray-300; + @apply text-gray-300; } .dark-mode .remove-button:hover { - @apply bg-red-600; + @apply bg-red-600; } .dark-mode .space-to-scroll { - @apply bg-gray-700; -} \ No newline at end of file + @apply bg-gray-700; +} From 25beaae38ba0f21dd52679fc27039a31a6df700b Mon Sep 17 00:00:00 2001 From: joyc7 Date: Thu, 7 Dec 2023 00:09:14 -0500 Subject: [PATCH 13/16] Add members in same group automatically --- back-end/routes/addEventRoute.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/back-end/routes/addEventRoute.js b/back-end/routes/addEventRoute.js index c211917..598651c 100644 --- a/back-end/routes/addEventRoute.js +++ b/back-end/routes/addEventRoute.js @@ -44,6 +44,13 @@ router.post( } user.events.push(savedEvent._id); // Add the event ID to the user's events list + + for (const friendId of req.body.Members) { + if (friendId !== userId && !user.friends.includes(friendId)) { + user.friends.push(friendId); + } + } + await user.save(); console.log(`Event added to user ${userId}`); } From 623a0514a12831af6b011df3090d7bfdba3ff717 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Thu, 7 Dec 2023 00:22:04 -0500 Subject: [PATCH 14/16] Update AddExpense.css --- front-end/src/styles/AddExpense.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front-end/src/styles/AddExpense.css b/front-end/src/styles/AddExpense.css index 01f7c2d..41c9afa 100644 --- a/front-end/src/styles/AddExpense.css +++ b/front-end/src/styles/AddExpense.css @@ -102,5 +102,5 @@ .dark-mode .space-to-scroll { @apply bg-gray-800; - height: 20px; + height: 60px; } From 15e986ef4a92d486bedf1ff11a3325b409b86af5 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Thu, 7 Dec 2023 01:09:42 -0500 Subject: [PATCH 15/16] Update AddExpense.css --- front-end/src/styles/AddExpense.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front-end/src/styles/AddExpense.css b/front-end/src/styles/AddExpense.css index 41c9afa..e64d1df 100644 --- a/front-end/src/styles/AddExpense.css +++ b/front-end/src/styles/AddExpense.css @@ -102,5 +102,5 @@ .dark-mode .space-to-scroll { @apply bg-gray-800; - height: 60px; + height: 40px; } From cc66c558bedebbab2d382e5a69b1bf6e02a9e029 Mon Sep 17 00:00:00 2001 From: joyc7 Date: Thu, 7 Dec 2023 01:14:44 -0500 Subject: [PATCH 16/16] Update balance color in dark mode --- front-end/src/styles/Event.css | 83 ++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/front-end/src/styles/Event.css b/front-end/src/styles/Event.css index 02bc1ae..d92c600 100644 --- a/front-end/src/styles/Event.css +++ b/front-end/src/styles/Event.css @@ -1,112 +1,127 @@ header { - @apply text-2xl font-bold; + @apply text-2xl font-bold; } .description { - @apply h-20 w-full text-center p-4 mt-3 mb-6 rounded-lg; + @apply h-20 w-full text-center p-4 mt-3 mb-6 rounded-lg; } .summary { - @apply flex space-x-4 place-content-center mb-6; + @apply flex space-x-4 place-content-center mb-6; } .expenses { - @apply overflow-auto mb-4 p-2 rounded-lg bg-cyan-100 bg-opacity-50; + @apply overflow-auto mb-4 p-2 rounded-lg bg-cyan-100 bg-opacity-50; } .expenses { - height: 50vh; + height: 50vh; } .expenseItem { - @apply grid grid-cols-6 w-full p-1 mb-4 rounded-lg; + @apply grid grid-cols-6 w-full p-1 mb-4 rounded-lg; } .name { - @apply col-span-3 hover:text-orange-200; + @apply col-span-3 hover:text-orange-200; } -.date, .name, .amount { - @apply inline-block m-auto mr-4; +.date, +.name, +.amount { + @apply inline-block m-auto mr-4; } .negative { - color: red; + color: red; } .positive { - color: green; + color: green; } -.settled{ - @apply text-green-500 +.settled { + @apply text-green-500; } .name { - @apply ml-2; + @apply ml-2; } -.amount, .checkbox { - @apply mr-0; +.amount, +.checkbox { + @apply mr-0; } .date { - @apply w-12 p-1 rounded-2xl bg-slate-100 break-words text-center opacity-60; + @apply w-12 p-1 rounded-2xl bg-slate-100 break-words text-center opacity-60; } .addExpenseBtnDiv { - @apply flex justify-end; + @apply flex justify-end; } .addExpenseBtn { - @apply w-24 h-16 rounded-full text-center p-1 bg-cyan-100 border-cyan-500 hover:bg-orange-100 active:bg-orange-200; + @apply w-24 h-16 rounded-full text-center p-1 bg-cyan-100 border-cyan-500 hover:bg-orange-100 active:bg-orange-200; } .space-to-scroll { - height: 60px; + height: 60px; } /* Dark Mode for Each Event Page */ .body-dark-mode { - @apply bg-gray-800; /* add dark mode to the outter most background on the page (area around the container) */ - } + @apply bg-gray-800; /* add dark mode to the outter most background on the page (area around the container) */ +} .dark-mode .event-page-container { - @apply bg-gray-800; /* the area within the outter most background */ + @apply bg-gray-800; /* the area within the outter most background */ } - + .dark-mode .event-page { - @apply bg-gray-800; - min-height: 100vh; + @apply bg-gray-800; + min-height: 100vh; } .dark-mode .expenses { - @apply bg-gray-700; + @apply bg-gray-700; } .dark-mode .description { - @apply bg-gray-800; + @apply bg-gray-800; } .dark-mode .date { - @apply bg-gray-800; + @apply bg-gray-800; } .dark-mode .operations button, .dark-mode .addExpenseBtn { - @apply bg-gray-600 hover:bg-gray-500 active:bg-gray-400; + @apply bg-gray-600 hover:bg-gray-500 active:bg-gray-400; } .dark-mode .date { - @apply bg-gray-600; + @apply bg-gray-600; } .dark-mode .name:hover, .dark-mode .name, .dark-mode .amount { - @apply text-white; + @apply text-white; } .dark-mode .space-to-scroll { - @apply bg-gray-700; - height: 0px; + @apply bg-gray-700; + height: 0px; +} + +.dark-mode .negative { + color: red; +} + +.dark-mode .positive { + color: green; +} + +.dark-mode .settled { + @apply text-green-500; }