From eaf34921362e2916be13e6bc35362c9dbe7fe84c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:08:52 +0000 Subject: [PATCH 1/8] Initial plan From f4e7e690825bc2d49e720e94a9166620faa6cf1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:16:59 +0000 Subject: [PATCH 2/8] Implement core roommate management features with expense splitting and auto-task assignment Co-authored-by: mohdrazakhan <115514669+mohdrazakhan@users.noreply.github.com> --- .env.example | 7 + .gitignore | 33 +++ README.md | 254 ++++++++++++++++- client/package.json | 36 +++ client/public/index.html | 14 + client/src/App.js | 74 +++++ client/src/components/Navbar.js | 20 ++ client/src/index.css | 382 ++++++++++++++++++++++++++ client/src/index.js | 11 + client/src/pages/Dashboard.js | 216 +++++++++++++++ client/src/pages/Login.js | 73 +++++ client/src/pages/MyTasks.js | 127 +++++++++ client/src/pages/Register.js | 102 +++++++ client/src/pages/RoomDetails.js | 443 ++++++++++++++++++++++++++++++ client/src/services/api.js | 79 ++++++ package.json | 32 +++ server/index.js | 52 ++++ server/middleware/auth.js | 24 ++ server/models/Expense.js | 54 ++++ server/models/Room.js | 45 +++ server/models/Task.js | 64 +++++ server/models/User.js | 30 ++ server/routes/expense.js | 236 ++++++++++++++++ server/routes/room.js | 187 +++++++++++++ server/routes/task.js | 277 +++++++++++++++++++ server/routes/user.js | 131 +++++++++ server/utils/expenseCalculator.js | 126 +++++++++ server/utils/taskAssignment.js | 85 ++++++ 28 files changed, 3213 insertions(+), 1 deletion(-) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 client/package.json create mode 100644 client/public/index.html create mode 100644 client/src/App.js create mode 100644 client/src/components/Navbar.js create mode 100644 client/src/index.css create mode 100644 client/src/index.js create mode 100644 client/src/pages/Dashboard.js create mode 100644 client/src/pages/Login.js create mode 100644 client/src/pages/MyTasks.js create mode 100644 client/src/pages/Register.js create mode 100644 client/src/pages/RoomDetails.js create mode 100644 client/src/services/api.js create mode 100644 package.json create mode 100644 server/index.js create mode 100644 server/middleware/auth.js create mode 100644 server/models/Expense.js create mode 100644 server/models/Room.js create mode 100644 server/models/Task.js create mode 100644 server/models/User.js create mode 100644 server/routes/expense.js create mode 100644 server/routes/room.js create mode 100644 server/routes/task.js create mode 100644 server/routes/user.js create mode 100644 server/utils/expenseCalculator.js create mode 100644 server/utils/taskAssignment.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..32b262d --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Environment variables +PORT=5000 +MONGODB_URI=mongodb://localhost:27017/oneroom +JWT_SECRET=your-secret-key-change-in-production + +# Client URL (for CORS) +CLIENT_URL=http://localhost:3000 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6e3c53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Dependencies +node_modules/ +package-lock.json + +# Environment +.env + +# Logs +logs +*.log +npm-debug.log* + +# Build +dist/ +build/ +client/build/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Testing +coverage/ + +# Temporary +tmp/ +temp/ diff --git a/README.md b/README.md index cc5b80d..ea28709 100644 --- a/README.md +++ b/README.md @@ -1 +1,253 @@ -# oneroom \ No newline at end of file +# 🏠 OneRoom - Roommate Management Application + +OneRoom is a comprehensive web application designed to simplify roommate life by managing shared expenses and automatically assigning daily tasks among roommates. + +## ✨ Features + +### 💰 Expense Management +- **Easy Expense Tracking**: Add and track all shared expenses +- **Automatic Splitting**: Expenses are automatically split equally among all roommates +- **Custom Splits**: Support for custom percentage-based splits +- **Category Organization**: Categorize expenses (groceries, utilities, rent, entertainment, etc.) +- **Balance Calculation**: Automatic calculation of who owes whom +- **Settlement Tracking**: Mark expenses as settled when payments are made +- **Smart Settlement Plan**: Minimizes the number of transactions needed to settle all debts + +### ✅ Task Management +- **Task Assignment**: Create and assign tasks to roommates +- **Auto-Assignment**: Fair rotation system that automatically assigns tasks +- **Recurring Tasks**: Set up daily, weekly, or monthly recurring tasks with auto-rotation +- **Task Categories**: Organize tasks by type (cleaning, cooking, shopping, maintenance) +- **Priority Levels**: Set task priorities (low, medium, high) +- **Status Tracking**: Track task status (pending, in-progress, completed) +- **Due Dates**: Set and track task deadlines +- **Personal Dashboard**: View all your assigned tasks in one place + +### 👥 Room & User Management +- **Multiple Rooms**: Support for multiple shared living spaces +- **Easy Onboarding**: Simple registration and login +- **Invite System**: Unique invite codes for each room +- **Role-Based Access**: Admin and member roles with appropriate permissions +- **Member Management**: Add/remove members from rooms + +### 🎨 Additional Features +- **Responsive Design**: Works seamlessly on desktop and mobile devices +- **Intuitive Dashboard**: Clean overview of all your rooms, expenses, and tasks +- **Real-time Updates**: Instant updates when roommates add expenses or tasks +- **Attractive UI**: Modern, gradient-based design with smooth animations +- **Secure Authentication**: JWT-based authentication with password hashing + +## 🚀 Getting Started + +### Prerequisites +- Node.js (v14 or higher) +- MongoDB (local or cloud instance) +- npm or yarn + +### Installation + +1. **Clone the repository** +```bash +git clone https://github.com/mohdrazakhan/oneroom.git +cd oneroom +``` + +2. **Install dependencies** +```bash +# Install server dependencies +npm install + +# Install client dependencies +cd client +npm install +cd .. +``` + +3. **Set up environment variables** +```bash +# Copy the example env file +cp .env.example .env + +# Edit .env with your configuration +# Required variables: +# - MONGODB_URI: Your MongoDB connection string +# - JWT_SECRET: A secure random string for JWT tokens +# - PORT: Server port (default: 5000) +``` + +4. **Start MongoDB** +```bash +# If running MongoDB locally +mongod +``` + +5. **Run the application** + +Development mode (runs both server and client): +```bash +npm run dev-all +``` + +Or run separately: +```bash +# Terminal 1 - Start the server +npm run dev + +# Terminal 2 - Start the client +npm run client +``` + +The application will be available at: +- Frontend: http://localhost:3000 +- Backend API: http://localhost:5000 + +### Production Build + +```bash +# Build the client +npm run build + +# Start the server +npm start +``` + +## 📖 Usage Guide + +### Creating Your First Room + +1. **Register/Login**: Create an account or login +2. **Create a Room**: Click "Create Room" on the dashboard +3. **Share Invite Code**: Share the generated invite code with your roommates +4. **They Join**: Roommates use the invite code to join your room + +### Managing Expenses + +1. **Add an Expense**: + - Go to your room + - Click "Add Expense" in the Expenses tab + - Enter description, amount, and category + - The expense will be automatically split equally among all members + +2. **View Balances**: + - Check the Overview tab to see who owes whom + - The app calculates the minimum number of transactions needed + +3. **Settle Up**: + - When someone pays their share, mark it as settled + - The balance summary updates automatically + +### Managing Tasks + +1. **Create a Task**: + - Go to your room + - Click "Add Task" in the Tasks tab + - Set title, category, priority, and due date + - The task will be auto-assigned fairly + +2. **Recurring Tasks**: + - Enable "Recurring Task" when creating + - Choose frequency (daily, weekly, monthly) + - Tasks automatically rotate to the next person after completion + +3. **Complete Tasks**: + - View your tasks in "My Tasks" + - Click "Complete" when done + - For recurring tasks, a new instance is created and assigned to the next person + +## 🏗️ Project Structure + +``` +oneroom/ +├── client/ # React frontend +│ ├── public/ +│ └── src/ +│ ├── components/ # Reusable components +│ ├── pages/ # Page components +│ ├── services/ # API services +│ └── utils/ # Utility functions +├── server/ # Express backend +│ ├── controllers/ # Route controllers +│ ├── middleware/ # Custom middleware +│ ├── models/ # MongoDB models +│ ├── routes/ # API routes +│ ├── utils/ # Utility functions +│ └── index.js # Server entry point +├── .env.example # Example environment variables +├── .gitignore +├── package.json +└── README.md +``` + +## 🛠️ Tech Stack + +### Frontend +- **React 18**: UI library +- **React Router**: Client-side routing +- **Axios**: HTTP client +- **CSS3**: Styling with modern features + +### Backend +- **Node.js**: Runtime environment +- **Express**: Web framework +- **MongoDB**: Database +- **Mongoose**: ODM for MongoDB +- **JWT**: Authentication +- **bcryptjs**: Password hashing + +## 🔐 Security + +- Passwords are hashed using bcrypt +- JWT tokens for secure authentication +- Protected API routes with authentication middleware +- Input validation and sanitization +- CORS enabled for cross-origin requests + +## 📱 API Documentation + +### Authentication +- `POST /api/users/register` - Register new user +- `POST /api/users/login` - Login user +- `GET /api/users/me` - Get current user profile +- `PUT /api/users/me` - Update user profile + +### Rooms +- `POST /api/rooms` - Create new room +- `GET /api/rooms` - Get all user's rooms +- `GET /api/rooms/:id` - Get room details +- `POST /api/rooms/join` - Join room with invite code +- `PUT /api/rooms/:id` - Update room +- `DELETE /api/rooms/:id/members/:userId` - Remove member + +### Expenses +- `POST /api/expenses` - Create expense +- `GET /api/expenses/room/:roomId` - Get room expenses +- `GET /api/expenses/room/:roomId/balances` - Get balance summary +- `PUT /api/expenses/:id` - Update expense +- `PUT /api/expenses/:id/settle/:userId` - Mark as settled +- `DELETE /api/expenses/:id` - Delete expense + +### Tasks +- `POST /api/tasks` - Create task +- `GET /api/tasks/room/:roomId` - Get room tasks +- `GET /api/tasks/my-tasks` - Get user's tasks +- `PUT /api/tasks/:id/status` - Update task status +- `PUT /api/tasks/:id` - Update task +- `DELETE /api/tasks/:id` - Delete task +- `POST /api/tasks/room/:roomId/rotate` - Rotate recurring tasks + +## 🤝 Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## 📝 License + +This project is licensed under the MIT License. + +## 👨‍💻 Author + +Created with ❤️ for making roommate life easier! + +## 🙏 Acknowledgments + +- Thanks to all roommates who inspired this project +- Built with modern web technologies for the best user experience \ No newline at end of file diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..7f0f60b --- /dev/null +++ b/client/package.json @@ -0,0 +1,36 @@ +{ + "name": "oneroom-client", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.11.0", + "axios": "^1.4.0", + "react-scripts": "5.0.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "proxy": "http://localhost:5000" +} diff --git a/client/public/index.html b/client/public/index.html new file mode 100644 index 0000000..e51bdd4 --- /dev/null +++ b/client/public/index.html @@ -0,0 +1,14 @@ + + + + + + + + OneRoom - Roommate Management + + + +
+ + diff --git a/client/src/App.js b/client/src/App.js new file mode 100644 index 0000000..f9fc31c --- /dev/null +++ b/client/src/App.js @@ -0,0 +1,74 @@ +import React, { useState, useEffect } from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; +import Navbar from './components/Navbar'; +import Login from './pages/Login'; +import Register from './pages/Register'; +import Dashboard from './pages/Dashboard'; +import RoomDetails from './pages/RoomDetails'; +import MyTasks from './pages/MyTasks'; + +function App() { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Check if user is logged in + const token = localStorage.getItem('token'); + const savedUser = localStorage.getItem('user'); + + if (token && savedUser) { + setUser(JSON.parse(savedUser)); + } + setLoading(false); + }, []); + + const handleLogin = (userData, token) => { + localStorage.setItem('token', token); + localStorage.setItem('user', JSON.stringify(userData)); + setUser(userData); + }; + + const handleLogout = () => { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + setUser(null); + }; + + if (loading) { + return
Loading...
; + } + + return ( + +
+ {user && } +
+ + : } + /> + : } + /> + : } + /> + : } + /> + : } + /> + +
+
+
+ ); +} + +export default App; diff --git a/client/src/components/Navbar.js b/client/src/components/Navbar.js new file mode 100644 index 0000000..ea6bcf6 --- /dev/null +++ b/client/src/components/Navbar.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +function Navbar({ user, onLogout }) { + return ( + + ); +} + +export default Navbar; diff --git a/client/src/index.css b/client/src/index.css new file mode 100644 index 0000000..adbcbfc --- /dev/null +++ b/client/src/index.css @@ -0,0 +1,382 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; +} + +.app-container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* Navigation */ +.navbar { + background: rgba(255, 255, 255, 0.95); + padding: 1rem 2rem; + border-radius: 10px; + margin-bottom: 2rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + display: flex; + justify-content: space-between; + align-items: center; +} + +.navbar h1 { + color: #667eea; + font-size: 1.5rem; +} + +.nav-links { + display: flex; + gap: 1rem; + align-items: center; +} + +.nav-links a { + text-decoration: none; + color: #333; + padding: 0.5rem 1rem; + border-radius: 5px; + transition: background 0.3s; +} + +.nav-links a:hover, +.nav-links a.active { + background: #667eea; + color: white; +} + +/* Buttons */ +.btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 1rem; + transition: all 0.3s; + font-weight: 500; +} + +.btn-primary { + background: #667eea; + color: white; +} + +.btn-primary:hover { + background: #5568d3; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.btn-secondary { + background: #6c757d; + color: white; +} + +.btn-secondary:hover { + background: #5a6268; +} + +.btn-success { + background: #28a745; + color: white; +} + +.btn-danger { + background: #dc3545; + color: white; +} + +.btn-small { + padding: 0.5rem 1rem; + font-size: 0.9rem; +} + +/* Cards */ +.card { + background: white; + padding: 2rem; + border-radius: 10px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + margin-bottom: 1.5rem; +} + +.card h2 { + color: #333; + margin-bottom: 1rem; +} + +.card h3 { + color: #555; + margin-bottom: 0.5rem; +} + +/* Forms */ +.form-group { + margin-bottom: 1.5rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + color: #333; + font-weight: 500; +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + padding: 0.75rem; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 1rem; + transition: border-color 0.3s; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: none; + border-color: #667eea; +} + +.form-group textarea { + resize: vertical; + min-height: 100px; +} + +/* Auth Pages */ +.auth-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + padding: 20px; +} + +.auth-card { + background: white; + padding: 3rem; + border-radius: 10px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); + max-width: 400px; + width: 100%; +} + +.auth-card h2 { + text-align: center; + color: #667eea; + margin-bottom: 2rem; +} + +/* Dashboard */ +.dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.stat-card { + background: white; + padding: 1.5rem; + border-radius: 10px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + text-align: center; +} + +.stat-card h3 { + color: #667eea; + font-size: 2rem; + margin-bottom: 0.5rem; +} + +.stat-card p { + color: #666; +} + +/* Lists */ +.list-item { + background: #f8f9fa; + padding: 1rem; + margin-bottom: 1rem; + border-radius: 5px; + border-left: 4px solid #667eea; + display: flex; + justify-content: space-between; + align-items: center; +} + +.list-item:hover { + background: #e9ecef; +} + +.list-item-content h4 { + color: #333; + margin-bottom: 0.25rem; +} + +.list-item-content p { + color: #666; + font-size: 0.9rem; +} + +.list-item-actions { + display: flex; + gap: 0.5rem; +} + +/* Tags */ +.tag { + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.85rem; + font-weight: 500; + margin-right: 0.5rem; +} + +.tag-pending { + background: #ffc107; + color: #000; +} + +.tag-completed { + background: #28a745; + color: white; +} + +.tag-in-progress { + background: #007bff; + color: white; +} + +.tag-high { + background: #dc3545; + color: white; +} + +.tag-medium { + background: #ffc107; + color: #000; +} + +.tag-low { + background: #6c757d; + color: white; +} + +/* Modal */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.modal-content { + background: white; + padding: 2rem; + border-radius: 10px; + max-width: 500px; + width: 90%; + max-height: 90vh; + overflow-y: auto; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +.modal-header h3 { + color: #333; +} + +.close-btn { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: #666; +} + +/* Error/Success Messages */ +.alert { + padding: 1rem; + border-radius: 5px; + margin-bottom: 1rem; +} + +.alert-error { + background: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +.alert-success { + background: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +/* Loading */ +.loading { + text-align: center; + padding: 2rem; + color: white; + font-size: 1.2rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .navbar { + flex-direction: column; + gap: 1rem; + } + + .nav-links { + flex-direction: column; + width: 100%; + } + + .nav-links a { + width: 100%; + text-align: center; + } + + .dashboard-grid { + grid-template-columns: 1fr; + } + + .list-item { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .list-item-actions { + width: 100%; + justify-content: flex-end; + } +} diff --git a/client/src/index.js b/client/src/index.js new file mode 100644 index 0000000..2cb1087 --- /dev/null +++ b/client/src/index.js @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); diff --git a/client/src/pages/Dashboard.js b/client/src/pages/Dashboard.js new file mode 100644 index 0000000..e145ba1 --- /dev/null +++ b/client/src/pages/Dashboard.js @@ -0,0 +1,216 @@ +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { roomAPI, taskAPI } from '../services/api'; + +function Dashboard({ user }) { + const [rooms, setRooms] = useState([]); + const [myTasks, setMyTasks] = useState([]); + const [showCreateRoom, setShowCreateRoom] = useState(false); + const [showJoinRoom, setShowJoinRoom] = useState(false); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + useEffect(() => { + fetchData(); + }, []); + + const fetchData = async () => { + try { + const [roomsRes, tasksRes] = await Promise.all([ + roomAPI.getAll(), + taskAPI.getMyTasks('pending') + ]); + setRooms(roomsRes.data); + setMyTasks(tasksRes.data.slice(0, 5)); // Show only 5 recent tasks + setLoading(false); + } catch (err) { + setError('Failed to load data'); + setLoading(false); + } + }; + + const CreateRoomModal = () => { + const [formData, setFormData] = useState({ name: '', description: '' }); + const [creating, setCreating] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + setCreating(true); + try { + await roomAPI.create(formData); + setShowCreateRoom(false); + fetchData(); + } catch (err) { + alert(err.response?.data?.error || 'Failed to create room'); + } finally { + setCreating(false); + } + }; + + return ( +
setShowCreateRoom(false)}> +
e.stopPropagation()}> +
+

Create New Room

+ +
+
+
+ + setFormData({ ...formData, name: e.target.value })} + required + /> +
+
+ +