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..335b3e3 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,109 @@ 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 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/` + +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/back-end/routes/addEventMemberRoute.js b/back-end/routes/addEventMemberRoute.js index 718c364..8d7e270 100644 --- a/back-end/routes/addEventMemberRoute.js +++ b/back-end/routes/addEventMemberRoute.js @@ -1,19 +1,17 @@ const express = require("express"); const router = express.Router(); const { User } = require("../models/User.js"); -const { Event } = require("../models/Event.js"); +const Event = require("../models/Event.js"); const { body, validationResult } = require("express-validator"); -router.get('/friendsList/:userId', async (req, res) => { +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"); } 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}`); } diff --git a/front-end/src/App.js b/front-end/src/App.js index 7789a64..814432d 100644 --- a/front-end/src/App.js +++ b/front-end/src/App.js @@ -1,5 +1,10 @@ import React, { useState } from "react"; -import { useLocation, BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import { + useLocation, + BrowserRouter as Router, + Routes, + Route, +} from "react-router-dom"; import Event from "./components/Event"; import "./index.css"; import "./App.css"; @@ -34,14 +39,17 @@ function App() { }; return ( - - + + } /> } /> } /> - } /> + } + /> - - + + ); } @@ -118,9 +126,11 @@ function AppContainer({ isDarkMode, children }) { const isLoginPage = location.pathname === "/"; const isForgotPasswordPage = location.pathname === "/forgot-password"; // disable dark mode on Login and ForgotPassword page - const containerClass = isDarkMode && !isLoginPage && !isForgotPasswordPage ? "container dark-mode" : "container"; + const containerClass = + isDarkMode && !isLoginPage && !isForgotPasswordPage + ? "container dark-mode" + : "container"; return
{children}
; } - export default App; 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..b2acbf8 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); @@ -205,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`, @@ -243,7 +241,7 @@ const AddExpense = (props) => { } }; fetchPeople(); - }, []); + }, [eventId]); const handlePaidByChange = (event) => { const selectedUserId = event.target.value; @@ -292,7 +290,7 @@ const AddExpense = (props) => { } }; fetchAvailablePeople(); - }, []); + }, [eventId]); const handleSelectPerson = (personId) => { const person = availablePeople.find((p) => p._id === personId); @@ -435,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/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 9c7da6d..dfc8d2a 100644 --- a/front-end/src/components/Event.js +++ b/front-end/src/components/Event.js @@ -95,57 +95,22 @@ 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); - - // 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(); - }, []); + }, [eventId]); useEffect(() => { const token = localStorage.getItem("token"); const currentUser = jwtDecode(token); - //const currentUserId = currentUser.id; if (data.expenses && currentUser) { const processedExpenses = processUserExpenses( data.expenses, @@ -200,7 +165,6 @@ const Event = (props) => {
{" "} - {/* */} Add Expense
diff --git a/front-end/src/components/Events.jsx b/front-end/src/components/Events.jsx index 7cb7d59..682fbed 100644 --- a/front-end/src/components/Events.jsx +++ b/front-end/src/components/Events.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import '../styles/Events.css'; +import "../styles/Events.css"; import Navbar from "./Navbar"; import { Link } from "react-router-dom"; import axios from "axios"; @@ -7,181 +7,200 @@ import AddEvent from "./AddEvent"; import { jwtDecode } from "jwt-decode"; function Events({ isDarkMode }) { - const[eventData, setEventData] = useState([]) - const[addEvent, setaddEvent] = useState(false) - const [settlements, setSettlements] = useState([]); - 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 = ["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 [eventData, setEventData] = useState([]); + const [addEvent, setaddEvent] = useState(false); + const [settlements, setSettlements] = useState([]); + const [amountOwed, setAmountOwed] = useState(0); + const [amountOwedBy, setAmountOwedBy] = useState(0); + const [searchTerm, setSearchTerm] = useState(""); + + function reformatDate(dateStr) { + 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}`; + } + + // Toggle the 'body-dark-mode' class on the body element + useEffect(() => { + if (isDarkMode) { + document.body.classList.add("body-dark-mode"); + } else { + document.body.classList.remove("body-dark-mode"); } - // Toggle the 'body-dark-mode' class on the body element - useEffect(() => { - if (isDarkMode) { - document.body.classList.add('body-dark-mode'); - } else { - document.body.classList.remove('body-dark-mode'); - } - - // Clean up function to remove the class when the component unmounts or when dark mode is turned off - return () => { - document.body.classList.remove('body-dark-mode'); - }; - }, [isDarkMode]); - - const token = localStorage.getItem("token"); - const decode = jwtDecode(token); - - useEffect(()=>{ - //fetch mock data about a user's events list - async function dataFetch(){ - try{ - 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){ - console.error("There was an error fetching the data:", error); - } + // Clean up function to remove the class when the component unmounts or when dark mode is turned off + return () => { + document.body.classList.remove("body-dark-mode"); + }; + }, [isDarkMode]); + + const token = localStorage.getItem("token"); + const decode = jwtDecode(token); + + useEffect(() => { + //fetch mock data about a user's events list + async function dataFetch() { + try { + if (!decode.id) { + console.error("No current user found in local storage."); + return; } - dataFetch(); - },[]); - - 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) { - console.error("Error fetching settlements:", error.response); - } - }; - - fetchSettlements(); - - }, []); - - function calculateAmounts(expenses, currentUserId) { - let amountOwed = 0; // Amount the current user owes to others - let amountOwedBy = 0; // Amount owed to the current user by others - - expenses.forEach(expense => { - if (expense.settleTo._id !== currentUserId && !expense.status) { - // If the current user is not the one who paid and the status is false (unsettled) - amountOwed += expense.amount; - } - if (expense.settleTo._id === currentUserId && !expense.status) { - // If the current user is the one who paid and the status is false (unsettled) - amountOwedBy += expense.amount; - } - }); - - return { amountOwed, amountOwedBy }; + //requesting data from the mock API endpoint + const response = await axios.get( + `${process.env.REACT_APP_BACKEND}/events/for/${decode.id}` + ); + //return the data + setEventData(response.data); + } catch (error) { + console.error("There was an error fetching the data:", error); + } } + dataFetch(); + }, [decode.id]); - useEffect(() => { - if (settlements.length > 0) { - const { amountOwed, amountOwedBy } = calculateAmounts(settlements, decode.id); - 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]); + useEffect(() => { + const fetchSettlements = async () => { + try { + const response = await axios.get( + `${process.env.REACT_APP_BACKEND}/settlement/from/${decode.id}` + ); + setSettlements(response.data); + // Process and use the fetched settlements here + } catch (error) { + console.error("Error fetching settlements:", error.response); + } + }; - function EventClick(eventId){ - console.log(`Event ${eventId} was clicked`) - } + fetchSettlements(); + }, [decode.id]); - return( -
-

Events

- - -
- < img src={eventData.avatar} alt="User's Avatar" className="Total_Balance_avatar"> -
-
Total Balance
-
- { ( -
You owe ${Math.abs(amountOwed).toFixed(2)}
- )} - {( -
You are owed ${amountOwedBy.toFixed(2)}
- )} - {amountOwed === 0 && amountOwedBy === 0 && ( -
All Balances are Settled!
- )} -
-
-
+ function calculateAmounts(expenses, currentUserId) { + let amountOwed = 0; // Amount the current user owes to others + let amountOwedBy = 0; // Amount owed to the current user by others - setSearchTerm(e.target.value)} - className="mt-4 search-input" - /> - -
-
    - {eventData.events && eventData.events.length > 0 ? (eventData.events - .filter(event => event.name.toLowerCase().includes(searchTerm.toLowerCase())) - .map(event => ( -
  • -
    - {reformatDate(event.date)} -
    -
    - {event.name} -
    - - - -
  • - )) - ) : ( -
    Please add your first event!
    - )} -
-
+ expenses.forEach((expense) => { + if (expense.settleTo._id !== currentUserId && !expense.status) { + // If the current user is not the one who paid and the status is false (unsettled) + amountOwed += expense.amount; + } + if (expense.settleTo._id === currentUserId && !expense.status) { + // If the current user is the one who paid and the status is false (unsettled) + amountOwedBy += expense.amount; + } + }); + return { amountOwed, amountOwedBy }; + } - {addEvent && ( - {setaddEvent(false); window.location.reload();}} /> - )} -
-
+ useEffect(() => { + if (settlements.length > 0) { + const { amountOwed, amountOwedBy } = calculateAmounts( + settlements, + decode.id + ); + setAmountOwed(amountOwed); + setAmountOwedBy(amountOwedBy); + } + }, [settlements, decode.id]); + + function EventClick(eventId) { + console.log(`Event ${eventId} was clicked`); + } + + return ( +
+

Events

+ +
+ User's Avatar +
+
Total Balance
+
+ {
You owe ${Math.abs(amountOwed).toFixed(2)}
} + {
You are owed ${amountOwedBy.toFixed(2)}
} + {amountOwed === 0 && amountOwedBy === 0 && ( +
All Balances are Settled!
+ )} +
- ) +
+ + setSearchTerm(e.target.value)} + className="mt-4 search-input" + /> + +
+
    + {eventData.events && eventData.events.length > 0 ? ( + eventData.events + .filter((event) => + event.name.toLowerCase().includes(searchTerm.toLowerCase()) + ) + .map((event) => ( +
  • +
    {reformatDate(event.date)}
    +
    + {event.name} +
    + + + +
  • + )) + ) : ( +
    + Please add your first event! +
    + )} +
+
+ + {addEvent && ( + { + setaddEvent(false); + window.location.reload(); + }} + /> + )} +
+
+ +
+
+ ); } -export default Events \ No newline at end of file +export default Events; diff --git a/front-end/src/components/Expense.jsx b/front-end/src/components/Expense.jsx index 3893dd5..3521097 100644 --- a/front-end/src/components/Expense.jsx +++ b/front-end/src/components/Expense.jsx @@ -14,26 +14,37 @@ 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 { 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); } @@ -45,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); } }; @@ -110,9 +119,6 @@ function Expense({ isDarkMode }) { return filteredExpenses; }; - { - /* navigates to the previous page */ - } const handleTitleClick = () => { navigate(-1); }; 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); } }; 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]); diff --git a/front-end/src/components/Home.jsx b/front-end/src/components/Home.jsx index 5cf748f..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"); @@ -98,7 +92,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 +174,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 df3ac8b..91a1ad3 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 = ({ onLoginSuccess }) => { } }, [showAlert]); - let [urlSearchParams] = useSearchParams(); const [response, setResponse] = useState({}); const [errorMessage, setErrorMessage] = useState(""); - const navigate = useNavigate(); - const handleInputChange = (e) => { const { name, value } = e.target; setFormData({ diff --git a/front-end/src/components/Navbar.jsx b/front-end/src/components/Navbar.jsx index 0fc2ee9..d274a44 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 ( diff --git a/front-end/src/components/SplitModal.js b/front-end/src/components/SplitModal.js index 4afb3e3..843e957 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)} + /> +
)}
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"; diff --git a/front-end/src/styles/AddExpense.css b/front-end/src/styles/AddExpense.css index 1c2a714..e64d1df 100644 --- a/front-end/src/styles/AddExpense.css +++ b/front-end/src/styles/AddExpense.css @@ -1,106 +1,106 @@ #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-800; - height: 20px; - -} \ No newline at end of file + @apply bg-gray-800; + height: 40px; +} 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; }