From bcd8edba64377597b4f2492a8ecafb5c58449cef Mon Sep 17 00:00:00 2001 From: Aayushh910 Date: Tue, 3 Feb 2026 17:44:31 +0530 Subject: [PATCH 01/16] update --- src/pages/Landing.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Landing.jsx b/src/pages/Landing.jsx index 1baa6d2..af1b273 100644 --- a/src/pages/Landing.jsx +++ b/src/pages/Landing.jsx @@ -4,6 +4,7 @@ import { Button } from '../components/ui/Button'; import { Card } from '../components/ui/Card'; import { useState, useEffect } from 'react'; import { rooms, roommates } from '../data/rooms'; +// import export const Landing = () => { const [typedText, setTypedText] = useState(''); From b73b518eeaee9617c7d95a8c4c654d70d42245f7 Mon Sep 17 00:00:00 2001 From: Aayushh910 Date: Tue, 3 Feb 2026 17:49:08 +0530 Subject: [PATCH 02/16] update ignore file --- .gitignore | 24 ++++++++++++++++++++++++ package-lock.json | 9 --------- 2 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/package-lock.json b/package-lock.json index 4662558..58d6060 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,7 +67,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -1229,7 +1228,6 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -1387,7 +1385,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1792,7 +1789,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -2046,7 +2042,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2216,7 +2211,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -2229,7 +2223,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -2571,7 +2564,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2643,7 +2635,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", From cd7b0166794a66e56fbbc7f0018c15b5cd5e75e4 Mon Sep 17 00:00:00 2001 From: mahek-56 Date: Mon, 9 Feb 2026 00:07:55 +0530 Subject: [PATCH 03/16] update frontend files --- QUICK_START.md | 219 +++++++++++++++++ README.md | 404 ++++++++++++++----------------- SETUP_GUIDE.md | 280 --------------------- index.html | 7 +- package-lock.json | 337 +++++++++++++------------- package.json | 21 +- src/App.jsx | 171 +++++-------- src/admin/AdminDashboard.jsx | 93 +++++++ src/admin/AdminReports.jsx | 67 ----- src/admin/AdminRooms.jsx | 61 ----- src/admin/AdminUsers.jsx | 75 ------ src/admin/ManageRooms.jsx | 99 ++++++++ src/admin/ManageUsers.jsx | 90 +++++++ src/assets/logo.png | Bin 173121 -> 0 bytes src/components/Navbar.jsx | 206 ++++++++++++++++ src/components/ui/Alert.jsx | 40 --- src/components/ui/Badge.jsx | 14 -- src/components/ui/Button.jsx | 32 --- src/components/ui/Card.jsx | 10 - src/components/ui/EmptyState.jsx | 28 --- src/components/ui/Input.jsx | 23 -- src/components/ui/Loader.jsx | 38 --- src/components/ui/Modal.jsx | 56 ++--- src/components/ui/Toast.jsx | 26 ++ src/context/AuthContext.jsx | 34 ++- src/data/admin.js | 61 ----- src/data/mockData.js | 321 ++++++++++++++++++++++++ src/data/reviews.js | 70 ------ src/data/rooms.js | 224 ----------------- src/layouts/MainLayout.jsx | 12 - src/layouts/Navbar.jsx | 144 ----------- src/main.jsx | 2 +- src/pages/AddRoom.jsx | 244 ------------------- src/pages/Dashboard.jsx | 343 -------------------------- src/pages/Info.jsx | 106 -------- src/pages/Landing.jsx | 327 ------------------------- src/pages/Login.jsx | 173 ------------- src/pages/Messages.jsx | 15 -- src/pages/NotFound.jsx | 27 --- src/pages/Profile.jsx | 143 ----------- src/pages/Reviews.jsx | 82 ------- src/pages/RoomDetails.jsx | 360 --------------------------- src/pages/Rooms.jsx | 230 ------------------ src/pages/Signup.jsx | 200 --------------- src/pages/Wishlist.jsx | 20 -- src/routes/PrivateRoute.jsx | 13 - src/routes/ProtectedRoute.jsx | 58 +++++ src/routes/PublicRoute.jsx | 13 - src/routes/RoleRoute.jsx | 21 -- src/styles/index.css | 78 +++--- tailwind.config.js | 70 +++--- vite.config.js | 4 + 52 files changed, 1663 insertions(+), 4129 deletions(-) create mode 100644 QUICK_START.md delete mode 100644 SETUP_GUIDE.md create mode 100644 src/admin/AdminDashboard.jsx delete mode 100644 src/admin/AdminReports.jsx delete mode 100644 src/admin/AdminRooms.jsx delete mode 100644 src/admin/AdminUsers.jsx create mode 100644 src/admin/ManageRooms.jsx create mode 100644 src/admin/ManageUsers.jsx delete mode 100644 src/assets/logo.png create mode 100644 src/components/Navbar.jsx delete mode 100644 src/components/ui/Alert.jsx delete mode 100644 src/components/ui/Badge.jsx delete mode 100644 src/components/ui/Button.jsx delete mode 100644 src/components/ui/Card.jsx delete mode 100644 src/components/ui/EmptyState.jsx delete mode 100644 src/components/ui/Input.jsx delete mode 100644 src/components/ui/Loader.jsx create mode 100644 src/components/ui/Toast.jsx delete mode 100644 src/data/admin.js create mode 100644 src/data/mockData.js delete mode 100644 src/data/reviews.js delete mode 100644 src/data/rooms.js delete mode 100644 src/layouts/MainLayout.jsx delete mode 100644 src/layouts/Navbar.jsx delete mode 100644 src/pages/AddRoom.jsx delete mode 100644 src/pages/Dashboard.jsx delete mode 100644 src/pages/Info.jsx delete mode 100644 src/pages/Landing.jsx delete mode 100644 src/pages/Login.jsx delete mode 100644 src/pages/Messages.jsx delete mode 100644 src/pages/NotFound.jsx delete mode 100644 src/pages/Profile.jsx delete mode 100644 src/pages/Reviews.jsx delete mode 100644 src/pages/RoomDetails.jsx delete mode 100644 src/pages/Rooms.jsx delete mode 100644 src/pages/Signup.jsx delete mode 100644 src/pages/Wishlist.jsx delete mode 100644 src/routes/PrivateRoute.jsx create mode 100644 src/routes/ProtectedRoute.jsx delete mode 100644 src/routes/PublicRoute.jsx delete mode 100644 src/routes/RoleRoute.jsx diff --git a/QUICK_START.md b/QUICK_START.md new file mode 100644 index 0000000..1078ae9 --- /dev/null +++ b/QUICK_START.md @@ -0,0 +1,219 @@ +# 🚀 RoomMateX - Quick Start Guide + +## Installation & Setup (3 Steps) + +### Step 1: Install Dependencies +```bash +cd roomatex +npm install +``` + +### Step 2: Start Development Server +```bash +npm run dev +``` + +### Step 3: Open in Browser +Navigate to: `http://localhost:3000` + +--- + +## 🔑 Demo Credentials + +### Admin Login +- **Email**: `admin@roomatex.com` +- **Password**: `admin123` +- **Access**: Full admin panel, user management, room management + +### Regular User Login +- **Email**: Any valid email (e.g., `user@test.com`) +- **Password**: Any password (e.g., `password`) +- **Access**: User dashboard, browse rooms, add listings, wishlist + +--- + +## 🎯 Key Features to Test + +### As a User: +1. **Dashboard** - View recommended rooms and roommates +2. **Browse Rooms** - Filter by city, rent, amenities +3. **Room Details** - View full details, add to wishlist, contact owner +4. **List a Room** - Add your own room listing (min 3 images required) +5. **Profile** - Edit profile, view listings, manage wishlist +6. **Roommate Profiles** - View detailed roommate information + +### As an Admin: +1. **Admin Dashboard** - View platform statistics +2. **Manage Users** - Verify users, change roles +3. **Manage Rooms** - Activate/deactivate or delete listings + +--- + +## 📋 Test Scenarios + +### Scenario 1: New User Journey +1. Go to landing page (`/`) +2. Click "Sign Up" +3. Register with any credentials +4. Explore dashboard with recommendations +5. Browse rooms using filters +6. Add a room to wishlist +7. View room details and contact owner + +### Scenario 2: Room Owner Journey +1. Login as user +2. Go to "List Room" +3. Fill form with minimum 3 image URLs (use Unsplash) +4. Submit room listing +5. See listing in Dashboard → "My Listings" +6. Edit or delete the listing +7. View in "Find Rooms" + +### Scenario 3: Admin Journey +1. Login with admin credentials +2. View admin dashboard stats +3. Go to "Manage Users" +4. Verify a user account +5. Change user role +6. Go to "Manage Rooms" +7. Activate/deactivate rooms +8. Delete a room listing + +--- + +## 🖼️ Sample Image URLs for Testing + +Use these Unsplash URLs when adding rooms: + +``` +https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=800 +https://images.unsplash.com/photo-1502672260066-6bc35f0a1ec1?w=800 +https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800 +https://images.unsplash.com/photo-1493809842364-78817add7ffb?w=800 +https://images.unsplash.com/photo-1512918728675-ed5a9ecdebfd?w=800 +``` + +--- + +## ⚡ Quick Navigation + +### Public Pages (No Login Required) +- `/` - Landing Page +- `/login` - Login +- `/signup` - Signup + +### User Pages (Login Required) +- `/dashboard` - User Dashboard +- `/rooms` - Browse Rooms +- `/room/:id` - Room Details +- `/roommate/:id` - Roommate Profile +- `/add-room` - List New Room +- `/profile` - User Profile +- `/wishlist` - Saved Rooms +- `/contact` - Contact Support + +### Admin Pages (Admin Only) +- `/admin` - Admin Dashboard +- `/admin/users` - Manage Users +- `/admin/rooms` - Manage Rooms + +--- + +## 🐛 Troubleshooting + +### Port Already in Use +If port 3000 is busy: +```bash +# Kill the process using port 3000 +npx kill-port 3000 + +# Or specify a different port +npm run dev -- --port 3001 +``` + +### Dependencies Not Installing +```bash +# Clear npm cache +npm cache clean --force + +# Delete node_modules and reinstall +rm -rf node_modules package-lock.json +npm install +``` + +### Browser Not Opening Automatically +Manually navigate to: `http://localhost:3000` + +--- + +## 📱 Mobile Testing + +The app is fully responsive. To test on mobile: +1. Start dev server: `npm run dev` +2. Find your local IP: `ipconfig` (Windows) or `ifconfig` (Mac/Linux) +3. On mobile browser: `http://YOUR_IP:3000` + +Or use Chrome DevTools: +1. Open DevTools (F12) +2. Click device toolbar icon (Ctrl+Shift+M) +3. Select mobile device + +--- + +## 🎨 Customization Tips + +### Change Colors +Edit `tailwind.config.js` → `theme.extend.colors` + +### Change Fonts +Edit `index.html` → Google Fonts link +Edit `tailwind.config.js` → `fontFamily` + +### Add More Mock Data +Edit `src/data/mockData.js` + +--- + +## 📦 Build for Production + +```bash +npm run build +``` + +Output: `dist/` folder ready for deployment + +Preview production build: +```bash +npm run preview +``` + +--- + +## ✅ Verification Checklist + +- [ ] All pages load without errors +- [ ] Login/Signup works +- [ ] Dashboard shows dynamic content +- [ ] Room filtering works +- [ ] Wishlist add/remove works +- [ ] Room CRUD operations work +- [ ] Profile editing works +- [ ] Admin panel accessible +- [ ] Mobile responsive +- [ ] No console errors + +--- + +## 💡 Tips + +1. **LocalStorage**: All data is saved in browser LocalStorage +2. **Mock Data**: Pre-loaded with 6 rooms and 6 roommates +3. **No Backend**: Everything runs client-side +4. **Images**: Uses Unsplash and Pravatar for demos +5. **Production Ready**: Clean code, no broken links + +--- + +**Need Help?** Check the main README.md for full documentation. + +**Ready to Deploy?** Run `npm run build` and deploy the `dist` folder to any static hosting (Vercel, Netlify, GitHub Pages). diff --git a/README.md b/README.md index a678080..0bf0239 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,64 @@ # RoomMateX - Room & Roommate Discovery Platform -A modern, production-ready room and roommate discovery platform built with React, Vite, and Tailwind CSS. - -## 🌟 Features - -### 🔐 Authentication & Authorization -- **Public Routes**: Landing page, Login, Signup (auto-redirect if logged in) -- **Protected Routes**: Full authentication guard -- **Role-Based Access**: Seeker, Owner, and Admin roles with specific permissions - -### 👤 User Roles - -#### Room Seeker -- Browse and search verified rooms -- Advanced filtering (city, rent, gender, amenities) -- Wishlist functionality -- Message owners -- Leave reviews - -#### Room Owner -- List rooms with comprehensive details -- Photo gallery (min 3, max 10 images) -- View potential roommates -- Manage listings -- Respond to inquiries - -#### Admin -- User management dashboard -- Room moderation -- Report handling system -- Analytics and insights -- Platform-wide controls - -### 🏠 Core Features -- **Room Discovery**: Advanced search and filtering -- **Verified Listings**: Trust badges and verification system -- **Reviews & Ratings**: User and room reviews -- **Trust & Safety**: Report system for users and rooms -- **Responsive Design**: Mobile-first, fully responsive UI -- **Modern UX**: Loading states, empty states, animations - -## 🚀 Quick Start - -```bash -# Install dependencies -npm install - -# Run development server -npm run dev - -# Build for production -npm run build -``` - -The app will be available at `http://localhost:5173` +A modern, production-level room and roommate discovery platform built with React, Vite, and Tailwind CSS. + +## 🚀 Features + +### User Features +- **Dashboard**: Dynamic dashboard with recommended rooms, roommates, and personalized sections +- **Find Rooms**: Browse and filter rooms by city, rent range, room type, and amenities +- **Room Details**: Comprehensive room information with image gallery, amenities, and owner contact +- **Roommate Profiles**: View detailed roommate profiles with preferences and interests +- **Wishlist**: Save favorite rooms for later viewing +- **List Rooms**: Full CRUD functionality for room listings +- **Profile Management**: Edit profile with completion score tracking +- **Contact**: Contact form for support inquiries + +### Admin Features +- **Admin Dashboard**: Overview of platform statistics +- **Manage Users**: View, verify, and change user roles +- **Manage Rooms**: Activate/deactivate and remove room listings + +### Technical Features +- ✅ Fully working navigation with React Router +- ✅ Context API for state management +- ✅ Protected routes with role-based access +- ✅ Responsive design (mobile-first) +- ✅ Toast notifications +- ✅ Modal dialogs +- ✅ LocalStorage persistence +- ✅ Professional UI with Tailwind CSS + +## 📦 Installation + +### Prerequisites +- Node.js (v16 or higher) +- npm or yarn + +### Setup + +1. **Install dependencies**: + ```bash + npm install + ``` + +2. **Run development server**: + ```bash + npm run dev + ``` + +3. **Open browser**: + Navigate to `http://localhost:3000` + +## 🔐 Demo Credentials + +### Admin Account +- **Email**: admin@roomatex.com +- **Password**: admin123 + +### User Account +- **Email**: any valid email +- **Password**: any password ## 📁 Project Structure @@ -61,208 +66,151 @@ The app will be available at `http://localhost:5173` roomatex/ ├── src/ │ ├── admin/ # Admin pages -│ │ ├── AdminUsers.jsx -│ │ ├── AdminRooms.jsx -│ │ └── AdminReports.jsx +│ │ ├── AdminDashboard.jsx +│ │ ├── ManageUsers.jsx +│ │ └── ManageRooms.jsx │ ├── components/ # Reusable components -│ │ └── ui/ # UI components -│ │ ├── Button.jsx -│ │ ├── Input.jsx -│ │ ├── Card.jsx -│ │ ├── Badge.jsx -│ │ ├── Alert.jsx -│ │ ├── Modal.jsx -│ │ ├── Loader.jsx -│ │ └── EmptyState.jsx -│ ├── context/ # React contexts +│ │ ├── ui/ +│ │ │ ├── Toast.jsx +│ │ │ └── Modal.jsx +│ │ └── Navbar.jsx +│ ├── context/ # React Context │ │ └── AuthContext.jsx -│ ├── data/ # Mock data -│ │ ├── rooms.js -│ │ ├── reviews.js -│ │ └── admin.js -│ ├── layouts/ # Layout components -│ │ ├── Navbar.jsx -│ │ └── MainLayout.jsx -│ ├── pages/ # Application pages -│ │ ├── Landing.jsx -│ │ ├── Login.jsx -│ │ ├── Signup.jsx -│ │ ├── Dashboard.jsx -│ │ ├── Rooms.jsx -│ │ ├── RoomDetails.jsx -│ │ ├── AddRoom.jsx -│ │ ├── Profile.jsx -│ │ ├── Messages.jsx -│ │ ├── Reviews.jsx -│ │ ├── Wishlist.jsx -│ │ ├── Info.jsx -│ │ └── NotFound.jsx -│ ├── routes/ # Route guards -│ │ ├── PrivateRoute.jsx -│ │ ├── PublicRoute.jsx -│ │ └── RoleRoute.jsx -│ ├── styles/ # Global styles +│ ├── data/ # Mock data +│ │ └── mockData.js +│ ├── pages/ # Page components +│ │ ├── LandingPage.jsx +│ │ ├── LoginPage.jsx +│ │ ├── SignupPage.jsx +│ │ ├── DashboardPage.jsx +│ │ ├── RoomsPage.jsx +│ │ ├── RoomDetailsPage.jsx +│ │ ├── RoommateProfilePage.jsx +│ │ ├── AddRoomPage.jsx +│ │ ├── ProfilePage.jsx +│ │ ├── WishlistPage.jsx +│ │ └── ContactPage.jsx +│ ├── routes/ # Route protection +│ │ └── ProtectedRoute.jsx +│ ├── styles/ # Global styles │ │ └── index.css -│ ├── App.jsx # Main app component -│ └── main.jsx # Entry point +│ ├── App.jsx # Main app component +│ └── main.jsx # Entry point ├── index.html ├── package.json +├── vite.config.js ├── tailwind.config.js -├── postcss.config.js -└── vite.config.js +└── postcss.config.js ``` ## 🎨 Design System ### Colors -- **Primary**: Light Green (#22c55e) -- **Dark**: Neutral grays for text and UI elements -- **Semantic**: Success (green), Warning (yellow), Danger (red), Info (blue) +- **Primary**: Blue tones (#0ea5e9) +- **Secondary**: Teal/Emerald (#14b8a6) +- **Accent**: Amber (#f59e0b) +- **Background**: Off-white/Gray (#f9fafb) ### Typography -- **Display Font**: Clash Display (headings) -- **Body Font**: DM Sans (content) - -### Components -- Consistent card-based layouts -- Soft shadows and rounded corners -- Smooth animations and transitions -- Professional color palette +- **Display**: Poppins (headings, titles) +- **Body**: Inter (paragraphs, text) -## 🔑 Demo Credentials +## 🔄 User Flow -```javascript -// Seeker Account -Email: seeker@test.com -Password: password +### Public Routes +1. **Landing Page** (`/`) - Marketing page with features and testimonials +2. **Login** (`/login`) - User authentication +3. **Signup** (`/signup`) - New user registration + +### Protected Routes (Logged In) +1. **Dashboard** (`/dashboard`) - Personalized home with recommendations +2. **Find Rooms** (`/rooms`) - Browse and filter available rooms +3. **Room Details** (`/room/:id`) - Full room information +4. **Roommate Profile** (`/roommate/:id`) - View roommate details +5. **List Room** (`/add-room`) - Add new room listing +6. **Profile** (`/profile`) - User profile with tabs (Profile, Listings, Wishlist, Settings) +7. **Wishlist** (`/wishlist`) - Saved rooms +8. **Contact** (`/contact`) - Support contact form -// Owner Account -Email: owner@test.com -Password: password +### Admin Routes +1. **Admin Dashboard** (`/admin`) - Platform overview +2. **Manage Users** (`/admin/users`) - User management +3. **Manage Rooms** (`/admin/rooms`) - Room moderation + +## ✨ Key Features + +### Dynamic Dashboard Sections +- Recommended Rooms (always visible) +- Recommended Roommates (always visible) +- My Listings (conditional - only if user has listings) +- Recently Viewed (conditional - only if user has viewed rooms) +- Wishlist (conditional - only if user has wishlist items) + +### Room Management (Full CRUD) +- ✅ **Create**: Add new room with validation (min 3 images) +- ✅ **Read**: View room details with gallery +- ✅ **Update**: Edit existing room listings +- ✅ **Delete**: Remove rooms with confirmation modal + +### Profile Completion +- Tracks completion percentage based on: + - Profile photo + - Phone number + - City + - Room listings + - Wishlist activity + +## 🛠️ Technologies + +- **Frontend**: React 18 +- **Build Tool**: Vite +- **Routing**: React Router DOM v6 +- **Styling**: Tailwind CSS 3 +- **State Management**: Context API +- **Storage**: LocalStorage + +## 📱 Responsive Design -// Admin Account -Email: admin@test.com -Password: password -``` +- Mobile-first approach +- Breakpoints: sm, md, lg, xl +- Hamburger menu for mobile +- Optimized layouts for all screen sizes -## 🛣️ Route Structure +## 🔒 Authentication -### Public Routes -- `/` - Landing page -- `/login` - Login page -- `/signup` - Signup page - -### Protected Routes (All Users) -- `/dashboard` - Role-based dashboard -- `/rooms` - Browse rooms (Seeker) -- `/room/:id` - Room details -- `/profile` - User profile -- `/messages` - Chat/messages -- `/reviews` - Reviews -- `/wishlist` - Saved rooms -- `/info` - Info/FAQ - -### Owner Routes -- `/add-room` - Create new listing +- Role-based access control (user, admin) +- Protected routes +- Public routes redirect to dashboard if authenticated +- LocalStorage persistence -### Admin Routes -- `/admin/users` - User management -- `/admin/rooms` - Room management -- `/admin/reports` - Handle reports +## 🎯 Production Ready -## 🎯 Key Features Implementation +- No broken links or dead buttons +- All routes fully functional +- Proper error handling +- Professional UI/UX +- Clean code structure +- Ready for deployment -### Trust & Safety System -- User verification badges -- Report functionality for rooms and users -- Trust score system -- Review moderation (admin) +## 📝 Notes -### UX Polish -- **Loading States**: Skeleton screens, spinners -- **Empty States**: Helpful messages when no data -- **Status Alerts**: Success, error, warning notifications -- **Animations**: Smooth transitions and micro-interactions +- All data is stored in browser LocalStorage +- Mock data includes 6 rooms and 6 roommates +- Contact form submissions show success message (no backend) +- Images use Unsplash and Pravatar for demo purposes -### Responsive Design -- Mobile-first approach -- Breakpoints: sm (640px), md (768px), lg (1024px), xl (1280px) -- Touch-friendly interactions -- Adaptive layouts - -## 🔧 Tech Stack - -- **React 18** - UI library -- **Vite** - Build tool -- **React Router v6** - Routing -- **Tailwind CSS** - Styling -- **Lucide React** - Icons -- **Local Storage** - Data persistence - -## 📱 Features by Page - -### Landing Page -- Hero with typing animation -- Feature showcase -- Demo room cards -- Testimonials -- FAQ section -- Footer - -### Dashboard -- Role-specific content -- Quick stats -- Recommended rooms (Seeker) -- Potential roommates (Owner) -- Admin analytics (Admin) - -### Room Listing -- Advanced filters -- Sort options -- Grid/list views -- Pagination ready - -### Room Details -- Image gallery -- Full description -- Amenities list -- House rules -- Owner profile -- Reviews section -- Wishlist toggle -- Report functionality - -### Profile -- View/edit mode -- Trust score display -- Activity stats -- Settings - -### Admin Dashboard -- User management -- Room moderation -- Report handling -- Analytics - -## 🚧 Future Enhancements - -- Real-time messaging -- Payment integration -- Email notifications -- Advanced search filters -- Map integration -- Photo verification -- Calendar for availability -- Mobile app +## 🚀 Build for Production -## 📄 License +```bash +npm run build +``` -This is a portfolio/educational project. +This creates an optimized production build in the `dist` folder. -## 🤝 Contributing +## 📄 License -This is a demonstration project. Feel free to fork and customize for your needs. +This project is for educational purposes. --- -Built with ❤️ using React, Vite, and Tailwind CSS +**Built with ❤️ using React + Vite + Tailwind CSS** diff --git a/SETUP_GUIDE.md b/SETUP_GUIDE.md deleted file mode 100644 index 553ae94..0000000 --- a/SETUP_GUIDE.md +++ /dev/null @@ -1,280 +0,0 @@ -# RoomMateX - Setup & Installation Guide - -## 📦 Quick Setup - -1. **Extract the project folder** to your preferred location - -2. **Navigate to the project directory**: - ```bash - cd roomatex - ``` - -3. **Install dependencies**: - ```bash - npm install - ``` - -4. **Start the development server**: - ```bash - npm run dev - ``` - -5. **Open your browser** and visit: `http://localhost:5173` - -## 🎯 First Steps - -### 1. Login with Demo Accounts - -Use these credentials to explore different user roles: - -**Room Seeker:** -- Email: `seeker@test.com` -- Password: `password` - -**Room Owner:** -- Email: `owner@test.com` -- Password: `password` - -**Admin:** -- Email: `admin@test.com` -- Password: `password` - -### 2. Explore Features - -**As a Seeker:** -1. Browse rooms on the Dashboard -2. Use filters to find specific rooms -3. Click on any room to view details -4. Add rooms to wishlist -5. View your profile and reviews - -**As an Owner:** -1. Click "List Room" to add a new room -2. Fill in all details and upload at least 3 photos -3. View potential roommates on the Dashboard -4. Manage your listings - -**As an Admin:** -1. Access admin dashboard -2. Manage users and rooms -3. Handle reports -4. View platform analytics - -## 🏗️ Project Architecture - -### Component Structure -``` -UI Components (src/components/ui/) -├── Button.jsx - Reusable button with variants -├── Input.jsx - Form input with label and error handling -├── Card.jsx - Container with hover effects -├── Badge.jsx - Status badges -├── Alert.jsx - Notification alerts -├── Modal.jsx - Modal dialogs -├── Loader.jsx - Loading states and skeletons -└── EmptyState.jsx - Empty state messages -``` - -### Authentication Flow -``` -1. User visits Landing page (/) -2. Clicks Login/Signup -3. After authentication -> Redirects to /dashboard -4. Can never access landing page again while logged in -5. Protected routes check authentication -6. Role routes check user role permissions -``` - -### Routing Guard System -``` -PublicRoute (/, /login, /signup) -└── If authenticated -> Redirect to /dashboard - -PrivateRoute (All app pages) -└── If not authenticated -> Redirect to /login - -RoleRoute (Owner/Admin pages) -└── If wrong role -> Redirect to /dashboard -``` - -## 🎨 Customization Guide - -### Changing Colors - -Edit `tailwind.config.js`: -```javascript -colors: { - primary: { - // Change these hex values - 500: '#22c55e', - 600: '#16a34a', - // ... - } -} -``` - -### Adding New Pages - -1. Create page component in `src/pages/` -2. Add route in `src/App.jsx` -3. Add navigation link in `src/layouts/Navbar.jsx` - -Example: -```jsx -// In App.jsx - - - -} /> -``` - -### Modifying Dummy Data - -Edit files in `src/data/`: -- `rooms.js` - Room listings -- `reviews.js` - User reviews -- `admin.js` - Admin statistics - -## 🚀 Deployment - -### Build for Production -```bash -npm run build -``` - -This creates a `dist` folder with optimized files. - -### Deploy to Vercel -```bash -npm install -g vercel -vercel -``` - -### Deploy to Netlify -1. Connect your GitHub repository -2. Build command: `npm run build` -3. Publish directory: `dist` - -## 🐛 Troubleshooting - -### Issue: "npm install" fails -**Solution**: Make sure you have Node.js 16+ installed -```bash -node --version # Should be 16 or higher -``` - -### Issue: Port 5173 already in use -**Solution**: Kill the process or use a different port -```bash -# Use different port -npm run dev -- --port 3000 -``` - -### Issue: Styles not loading -**Solution**: Make sure Tailwind is properly configured -```bash -# Rebuild -rm -rf node_modules -npm install -npm run dev -``` - -### Issue: Images not loading -**Solution**: Check your internet connection (images are from external URLs) - -## 📱 Browser Compatibility - -- Chrome/Edge: ✅ Full support -- Firefox: ✅ Full support -- Safari: ✅ Full support -- Mobile browsers: ✅ Full support - -## 🔧 Development Tips - -### Hot Module Replacement -Vite provides instant HMR. Save any file and see changes immediately. - -### React DevTools -Install React DevTools browser extension for better debugging. - -### VS Code Extensions (Recommended) -- ES7+ React/Redux/React-Native snippets -- Tailwind CSS IntelliSense -- Prettier - Code formatter -- ESLint - -## 📚 Learning Resources - -### React -- [Official React Docs](https://react.dev) -- [React Router Docs](https://reactrouter.com) - -### Tailwind CSS -- [Tailwind Docs](https://tailwindcss.com/docs) -- [Tailwind UI](https://tailwindui.com) - -### Vite -- [Vite Docs](https://vitejs.dev) - -## 🎓 Code Examples - -### Creating a New Component -```jsx -// src/components/ui/NewComponent.jsx -export const NewComponent = ({ children, variant = 'default' }) => { - return ( -
- {children} -
- ); -}; -``` - -### Using Context -```jsx -import { useAuth } from '../context/AuthContext'; - -function MyComponent() { - const { user, logout } = useAuth(); - - return
Welcome, {user.name}!
; -} -``` - -### Protected Route Example -```jsx - - - -} /> -``` - -## 💡 Best Practices - -1. **Always use Tailwind classes** instead of custom CSS -2. **Keep components small** and focused -3. **Use the existing UI components** for consistency -4. **Follow the folder structure** for organization -5. **Test in multiple screen sizes** for responsiveness - -## 🆘 Getting Help - -If you encounter issues: -1. Check the console for error messages -2. Review this setup guide -3. Check the README.md file -4. Inspect the code comments - -## 🎉 Next Steps - -1. ✅ Run the project -2. ✅ Test all demo accounts -3. ✅ Explore all pages -4. 🔄 Customize for your needs -5. 🚀 Deploy to production - ---- - -**Congratulations!** You now have a fully functional room discovery platform. Happy coding! 🎊 diff --git a/index.html b/index.html index 69dc5a0..97888f8 100644 --- a/index.html +++ b/index.html @@ -1,14 +1,13 @@ - + - RoomMateX - Find Your Perfect Room & Roommate - - + + RoomMateX - Find Your Perfect Room & Roommate
diff --git a/package-lock.json b/package-lock.json index 58d6060..13678d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,19 +8,18 @@ "name": "roomatex", "version": "1.0.0", "dependencies": { - "lucide-react": "^0.263.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router-dom": "^6.22.0" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.20.0" }, "devDependencies": { - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.3", - "autoprefixer": "^10.4.20", - "postcss": "^8.4.49", - "tailwindcss": "^3.4.17", - "vite": "^5.4.11" + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.3.6", + "vite": "^5.0.8" } }, "node_modules/@alloc/quick-lru": { @@ -37,9 +36,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -52,9 +51,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -62,21 +61,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -93,14 +93,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -223,13 +223,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -286,18 +286,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -305,9 +305,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -814,9 +814,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz", - "integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", "cpu": [ "arm" ], @@ -828,9 +828,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz", - "integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", "cpu": [ "arm64" ], @@ -842,9 +842,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz", - "integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", "cpu": [ "arm64" ], @@ -856,9 +856,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz", - "integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", "cpu": [ "x64" ], @@ -870,9 +870,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz", - "integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", "cpu": [ "arm64" ], @@ -884,9 +884,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz", - "integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", "cpu": [ "x64" ], @@ -898,9 +898,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz", - "integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", "cpu": [ "arm" ], @@ -912,9 +912,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz", - "integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", "cpu": [ "arm" ], @@ -926,9 +926,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz", - "integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", "cpu": [ "arm64" ], @@ -940,9 +940,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz", - "integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", "cpu": [ "arm64" ], @@ -954,9 +954,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz", - "integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", "cpu": [ "loong64" ], @@ -968,9 +968,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz", - "integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", "cpu": [ "loong64" ], @@ -982,9 +982,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz", - "integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", "cpu": [ "ppc64" ], @@ -996,9 +996,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz", - "integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", "cpu": [ "ppc64" ], @@ -1010,9 +1010,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz", - "integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", "cpu": [ "riscv64" ], @@ -1024,9 +1024,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz", - "integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", "cpu": [ "riscv64" ], @@ -1038,9 +1038,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz", - "integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", "cpu": [ "s390x" ], @@ -1052,9 +1052,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz", - "integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", "cpu": [ "x64" ], @@ -1066,9 +1066,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz", - "integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", "cpu": [ "x64" ], @@ -1080,9 +1080,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz", - "integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", "cpu": [ "x64" ], @@ -1094,9 +1094,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz", - "integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", "cpu": [ "arm64" ], @@ -1108,9 +1108,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz", - "integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", "cpu": [ "arm64" ], @@ -1122,9 +1122,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz", - "integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", "cpu": [ "ia32" ], @@ -1136,9 +1136,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz", - "integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", "cpu": [ "x64" ], @@ -1150,9 +1150,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", - "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", "cpu": [ "x64" ], @@ -1223,11 +1223,12 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -1293,9 +1294,9 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", "dev": true, "funding": [ { @@ -1314,7 +1315,7 @@ "license": "MIT", "dependencies": { "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -1385,6 +1386,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1410,9 +1412,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "dev": true, "funding": [ { @@ -1538,9 +1540,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.279", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", - "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "dev": true, "license": "ISC" }, @@ -1789,6 +1791,7 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -1867,15 +1870,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lucide-react": { - "version": "0.263.1", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.263.1.tgz", - "integrity": "sha512-keqxAx97PlaEN89PXZ6ki1N8nRjGWtDa4021GFYLNj0RgruM5odbpl8GHTExj0hhPq3sF6Up0gnxt6TSHu+ovw==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2042,6 +2036,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2211,6 +2206,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -2223,6 +2219,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -2329,9 +2326,9 @@ } }, "node_modules/rollup": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz", - "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "dev": true, "license": "MIT", "dependencies": { @@ -2345,31 +2342,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.0", - "@rollup/rollup-android-arm64": "4.57.0", - "@rollup/rollup-darwin-arm64": "4.57.0", - "@rollup/rollup-darwin-x64": "4.57.0", - "@rollup/rollup-freebsd-arm64": "4.57.0", - "@rollup/rollup-freebsd-x64": "4.57.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", - "@rollup/rollup-linux-arm-musleabihf": "4.57.0", - "@rollup/rollup-linux-arm64-gnu": "4.57.0", - "@rollup/rollup-linux-arm64-musl": "4.57.0", - "@rollup/rollup-linux-loong64-gnu": "4.57.0", - "@rollup/rollup-linux-loong64-musl": "4.57.0", - "@rollup/rollup-linux-ppc64-gnu": "4.57.0", - "@rollup/rollup-linux-ppc64-musl": "4.57.0", - "@rollup/rollup-linux-riscv64-gnu": "4.57.0", - "@rollup/rollup-linux-riscv64-musl": "4.57.0", - "@rollup/rollup-linux-s390x-gnu": "4.57.0", - "@rollup/rollup-linux-x64-gnu": "4.57.0", - "@rollup/rollup-linux-x64-musl": "4.57.0", - "@rollup/rollup-openbsd-x64": "4.57.0", - "@rollup/rollup-openharmony-arm64": "4.57.0", - "@rollup/rollup-win32-arm64-msvc": "4.57.0", - "@rollup/rollup-win32-ia32-msvc": "4.57.0", - "@rollup/rollup-win32-x64-gnu": "4.57.0", - "@rollup/rollup-win32-x64-msvc": "4.57.0", + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" } }, @@ -2564,6 +2561,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2635,6 +2633,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/package.json b/package.json index d494a24..8e55a9a 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,17 @@ "preview": "vite preview" }, "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router-dom": "^6.22.0", - "lucide-react": "^0.263.1" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.20.0" }, "devDependencies": { - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.3", - "autoprefixer": "^10.4.20", - "postcss": "^8.4.49", - "tailwindcss": "^3.4.17", - "vite": "^5.4.11" + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.3.6", + "vite": "^5.0.8" } } diff --git a/src/App.jsx b/src/App.jsx index b0336cf..7ea33c0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,125 +1,82 @@ -import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { AuthProvider } from './context/AuthContext'; -import { PrivateRoute } from './routes/PrivateRoute'; -import { PublicRoute } from './routes/PublicRoute'; -import { RoleRoute } from './routes/RoleRoute'; +import { ProtectedRoute, AdminRoute, PublicRoute } from './routes/ProtectedRoute'; +import { Navbar } from './components/Navbar'; -// Public Pages -import { Landing } from './pages/Landing'; -import { Login } from './pages/Login'; -import { Signup } from './pages/Signup'; +// Pages +import { LandingPage } from './pages/LandingPage'; +import { LoginPage } from './pages/LoginPage'; +import { SignupPage } from './pages/SignupPage'; +import { DashboardPage } from './pages/DashboardPage'; +import { RoomsPage } from './pages/RoomsPage'; +import { RoomDetailsPage } from './pages/RoomDetailsPage'; +import { RoommateProfilePage } from './pages/RoommateProfilePage'; +import { AddRoomPage } from './pages/AddRoomPage'; +import { ProfilePage } from './pages/ProfilePage'; +import { WishlistPage } from './pages/WishlistPage'; +import { ContactPage } from './pages/ContactPage'; -// Protected Pages -import { Dashboard } from './pages/Dashboard'; -import { Rooms } from './pages/Rooms'; -import { RoomDetails } from './pages/RoomDetails'; -import { AddRoom } from './pages/AddRoom'; -import { Profile } from './pages/Profile'; -import { Messages } from './pages/Messages'; -import { Reviews } from './pages/Reviews'; -import { Wishlist } from './pages/Wishlist'; -import { Info } from './pages/Info'; - -// Admin Pages -import { AdminUsers } from './admin/AdminUsers'; -import { AdminRooms } from './admin/AdminRooms'; -import { AdminReports } from './admin/AdminReports'; - -// 404 -import { NotFound } from './pages/NotFound'; +// Admin +import { AdminDashboard } from './admin/AdminDashboard'; +import { ManageUsers } from './admin/ManageUsers'; +import { ManageRooms } from './admin/ManageRooms'; function App() { return ( - + {/* Public Routes */} - - - - } /> - - - - } /> - - - - } /> + } /> + } /> + } /> - {/* Protected Routes */} - - - - } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> + {/* Protected Routes with Navbar */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + - {/* Admin Routes */} - - - - } /> - - - - } /> - - - - } /> + {/* Admin Routes with Navbar */} + }> + } /> + } /> + } /> + - {/* 404 */} - } /> + {/* Fallback */} + } /> - + ); } +function NavbarLayout() { + return ( + <> + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +} + export default App; diff --git a/src/admin/AdminDashboard.jsx b/src/admin/AdminDashboard.jsx new file mode 100644 index 0000000..d3c3f72 --- /dev/null +++ b/src/admin/AdminDashboard.jsx @@ -0,0 +1,93 @@ +import { Link } from 'react-router-dom'; +import { useState, useEffect } from 'react'; + +export const AdminDashboard = () => { + const [stats, setStats] = useState({ users: 0, rooms: 0, activeRooms: 0 }); + + useEffect(() => { + const rooms = JSON.parse(localStorage.getItem('roomatex_rooms') || '[]'); + setStats({ users: 10, rooms: rooms.length, activeRooms: rooms.filter(r => r.active).length }); + }, []); + + return ( +
+
+

Admin Dashboard

+ +
+
+
+
+

Total Users

+

{stats.users}

+
+
+ + + +
+
+
+ +
+
+
+

Total Rooms

+

{stats.rooms}

+
+
+ + + +
+
+
+ +
+
+
+

Active Rooms

+

{stats.activeRooms}

+
+
+ + + +
+
+
+
+ +
+ +
+
+ + + +
+
+

Manage Users

+

View and manage user accounts

+
+
+ + + +
+
+ + + +
+
+

Manage Rooms

+

Moderate and manage room listings

+
+
+ +
+
+
+ ); +}; diff --git a/src/admin/AdminReports.jsx b/src/admin/AdminReports.jsx deleted file mode 100644 index a670bf8..0000000 --- a/src/admin/AdminReports.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import { MainLayout } from '../layouts/MainLayout'; -import { Card } from '../components/ui/Card'; -import { Button } from '../components/ui/Button'; -import { Badge } from '../components/ui/Badge'; -import { reports } from '../data/admin'; - -export const AdminReports = () => { - const getSeverityColor = (severity) => { - switch (severity) { - case 'critical': return 'danger'; - case 'high': return 'warning'; - case 'medium': return 'info'; - default: return 'info'; - } - }; - - return ( - -
-

Reports

- -
- {reports.map((report) => ( - -
-
-
- - {report.severity.toUpperCase()} - - {report.type} - - {report.status} - -
-

- {report.targetName} -

-

- Reported by {report.reporterName} • {report.date} -

-
-
-
-
- Reason: - {report.reason} -
-
- Description: - {report.description} -
-
- {report.status === 'pending' && ( -
- - - -
- )} -
- ))} -
-
-
- ); -}; diff --git a/src/admin/AdminRooms.jsx b/src/admin/AdminRooms.jsx deleted file mode 100644 index db6fee9..0000000 --- a/src/admin/AdminRooms.jsx +++ /dev/null @@ -1,61 +0,0 @@ -import { MainLayout } from '../layouts/MainLayout'; -import { Card } from '../components/ui/Card'; -import { Button } from '../components/ui/Button'; -import { Badge } from '../components/ui/Badge'; -import { rooms } from '../data/rooms'; -import { MapPin, Eye } from 'lucide-react'; - -export const AdminRooms = () => { - return ( - -
-

Manage Rooms

- -
- {rooms.map((room) => ( - -
- {room.title} -
-
-
-

{room.title}

-
- - {room.area}, {room.city} -
-
-
-
₹{room.rent.toLocaleString()}
-
per month
-
-
-

{room.description}

-
- - {room.ownerVerified ? 'Verified Owner' : 'Unverified'} - - {room.roomType} -
- - 0 views -
-
-
-
- - - -
-
-
- ))} -
-
-
- ); -}; diff --git a/src/admin/AdminUsers.jsx b/src/admin/AdminUsers.jsx deleted file mode 100644 index 2b08d96..0000000 --- a/src/admin/AdminUsers.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import { MainLayout } from '../layouts/MainLayout'; -import { Card } from '../components/ui/Card'; -import { Button } from '../components/ui/Button'; -import { Badge } from '../components/ui/Badge'; -import { Shield, Search } from 'lucide-react'; - -export const AdminUsers = () => { - const users = [ - { id: '1', name: 'Rahul Kumar', email: 'rahul@example.com', role: 'seeker', verified: true, trustScore: 4.8 }, - { id: '2', name: 'Priya Sharma', email: 'priya@example.com', role: 'owner', verified: true, trustScore: 4.9 }, - { id: '3', name: 'Amit Patel', email: 'amit@example.com', role: 'seeker', verified: false, trustScore: 4.2 }, - ]; - - return ( - -
-
-

Manage Users

-
- - -
-
- - -
- - - - - - - - - - - - {users.map((user) => ( - - - - - - - - ))} - -
UserRoleStatusTrust ScoreActions
-
-
{user.name}
-
{user.email}
-
-
- {user.role} - - {user.verified ? ( - - - Verified - - ) : ( - Unverified - )} - {user.trustScore} -
- - -
-
-
-
-
-
- ); -}; diff --git a/src/admin/ManageRooms.jsx b/src/admin/ManageRooms.jsx new file mode 100644 index 0000000..b082c17 --- /dev/null +++ b/src/admin/ManageRooms.jsx @@ -0,0 +1,99 @@ +import { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { Toast } from '../components/ui/Toast'; +import { Modal } from '../components/ui/Modal'; + +export const ManageRooms = () => { + const [rooms, setRooms] = useState([]); + const [toast, setToast] = useState(null); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [roomToDelete, setRoomToDelete] = useState(null); + + useEffect(() => { + const storedRooms = JSON.parse(localStorage.getItem('roomatex_rooms') || '[]'); + setRooms(storedRooms); + }, []); + + const toggleStatus = (roomId) => { + const updatedRooms = rooms.map(room => + room.id === roomId ? { ...room, active: !room.active } : room + ); + setRooms(updatedRooms); + localStorage.setItem('roomatex_rooms', JSON.stringify(updatedRooms)); + setToast({ message: 'Room status updated', type: 'success' }); + }; + + const handleDelete = (roomId) => { + setRoomToDelete(roomId); + setShowDeleteModal(true); + }; + + const confirmDelete = () => { + const updatedRooms = rooms.filter(room => room.id !== roomToDelete); + setRooms(updatedRooms); + localStorage.setItem('roomatex_rooms', JSON.stringify(updatedRooms)); + setShowDeleteModal(false); + setToast({ message: 'Room deleted successfully', type: 'success' }); + }; + + return ( +
+ {toast && setToast(null)} />} + +
+

Manage Rooms

+ + {rooms.length === 0 ? ( +
+

No rooms to manage

+
+ ) : ( +
+ {rooms.map(room => ( +
+ {room.title} +
+

{room.title}

+

Owner: {room.owner.name}

+
+ ₹{room.rent.toLocaleString()} + + {room.active ? 'Active' : 'Inactive'} + +
+
+ + View + + + +
+
+
+ ))} +
+ )} +
+ + setShowDeleteModal(false)} title="Delete Room"> +
+

Are you sure you want to delete this room? This action cannot be undone.

+
+ + +
+
+
+
+ ); +}; diff --git a/src/admin/ManageUsers.jsx b/src/admin/ManageUsers.jsx new file mode 100644 index 0000000..edd4427 --- /dev/null +++ b/src/admin/ManageUsers.jsx @@ -0,0 +1,90 @@ +import { useState } from 'react'; +import { useAuth } from '../context/AuthContext'; +import { Toast } from '../components/ui/Toast'; + +const mockUsers = [ + { id: '1', name: 'John Doe', email: 'john@example.com', role: 'user', verified: true, city: 'Pune' }, + { id: '2', name: 'Jane Smith', email: 'jane@example.com', role: 'user', verified: false, city: 'Mumbai' }, + { id: '3', name: 'Mike Johnson', email: 'mike@example.com', role: 'user', verified: true, city: 'Bangalore' }, +]; + +export const ManageUsers = () => { + const { user: currentUser } = useAuth(); + const [users, setUsers] = useState([currentUser, ...mockUsers]); + const [toast, setToast] = useState(null); + + const toggleVerification = (userId) => { + setUsers(users.map(u => u.id === userId ? { ...u, verified: !u.verified } : u)); + setToast({ message: 'User verification status updated', type: 'success' }); + }; + + const changeRole = (userId, newRole) => { + setUsers(users.map(u => u.id === userId ? { ...u, role: newRole } : u)); + setToast({ message: 'User role updated', type: 'success' }); + }; + + return ( +
+ {toast && setToast(null)} />} + +
+

Manage Users

+ +
+ + + + + + + + + + + + + {users.map(user => ( + + + + + + + + + ))} + +
UserEmailCityRoleStatusActions
+
+
+ {user.name.charAt(0)} +
+
{user.name}
+
+
{user.email}{user.city} + + + + {user.verified ? 'Verified' : 'Not Verified'} + + + +
+
+
+
+ ); +}; diff --git a/src/assets/logo.png b/src/assets/logo.png deleted file mode 100644 index 38ceb4b15148ae48e55dffb73a1d5a32c75d6d8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173121 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 diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx new file mode 100644 index 0000000..2078281 --- /dev/null +++ b/src/components/Navbar.jsx @@ -0,0 +1,206 @@ +import { Link, useNavigate } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; +import { useState } from 'react'; + +export const Navbar = () => { + const { user, logout, isAdmin } = useAuth(); + const navigate = useNavigate(); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + + const handleLogout = () => { + logout(); + navigate('/login'); + }; + + return ( + + ); +}; diff --git a/src/components/ui/Alert.jsx b/src/components/ui/Alert.jsx deleted file mode 100644 index 31013de..0000000 --- a/src/components/ui/Alert.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from 'lucide-react'; - -export const Alert = ({ type = 'info', message, onClose }) => { - const types = { - success: { - bg: 'bg-green-50 border-green-200', - text: 'text-green-800', - icon: CheckCircle, - }, - error: { - bg: 'bg-red-50 border-red-200', - text: 'text-red-800', - icon: AlertCircle, - }, - warning: { - bg: 'bg-yellow-50 border-yellow-200', - text: 'text-yellow-800', - icon: AlertTriangle, - }, - info: { - bg: 'bg-blue-50 border-blue-200', - text: 'text-blue-800', - icon: Info, - }, - }; - - const { bg, text, icon: Icon } = types[type]; - - return ( -
- -

{message}

- {onClose && ( - - )} -
- ); -}; diff --git a/src/components/ui/Badge.jsx b/src/components/ui/Badge.jsx deleted file mode 100644 index 84136fa..0000000 --- a/src/components/ui/Badge.jsx +++ /dev/null @@ -1,14 +0,0 @@ -export const Badge = ({ children, variant = 'info', className = '' }) => { - const variants = { - success: 'badge-success', - warning: 'badge-warning', - danger: 'badge-danger', - info: 'badge-info', - }; - - return ( - - {children} - - ); -}; diff --git a/src/components/ui/Button.jsx b/src/components/ui/Button.jsx deleted file mode 100644 index ba694c7..0000000 --- a/src/components/ui/Button.jsx +++ /dev/null @@ -1,32 +0,0 @@ -export const Button = ({ - children, - variant = 'primary', - size = 'md', - className = '', - ...props -}) => { - const baseStyles = 'btn transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed'; - - const variants = { - primary: 'btn-primary', - secondary: 'btn-secondary', - outline: 'btn-outline', - ghost: 'hover:bg-dark-50', - danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500', - }; - - const sizes = { - sm: 'px-3 py-1.5 text-sm', - md: 'px-4 py-2', - lg: 'px-6 py-3 text-lg', - }; - - return ( - - ); -}; diff --git a/src/components/ui/Card.jsx b/src/components/ui/Card.jsx deleted file mode 100644 index 595c755..0000000 --- a/src/components/ui/Card.jsx +++ /dev/null @@ -1,10 +0,0 @@ -export const Card = ({ children, className = '', hover = false, ...props }) => { - return ( -
- {children} -
- ); -}; diff --git a/src/components/ui/EmptyState.jsx b/src/components/ui/EmptyState.jsx deleted file mode 100644 index 13d8cce..0000000 --- a/src/components/ui/EmptyState.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Button } from './Button'; - -export const EmptyState = ({ - icon: Icon, - title, - description, - action, - actionLabel -}) => { - return ( -
- {Icon && ( -
- -
- )} -

- {title} -

-

- {description} -

- {action && actionLabel && ( - - )} -
- ); -}; diff --git a/src/components/ui/Input.jsx b/src/components/ui/Input.jsx deleted file mode 100644 index c958111..0000000 --- a/src/components/ui/Input.jsx +++ /dev/null @@ -1,23 +0,0 @@ -export const Input = ({ - label, - error, - className = '', - ...props -}) => { - return ( -
- {label && ( - - )} - - {error && ( -

{error}

- )} -
- ); -}; diff --git a/src/components/ui/Loader.jsx b/src/components/ui/Loader.jsx deleted file mode 100644 index dfee799..0000000 --- a/src/components/ui/Loader.jsx +++ /dev/null @@ -1,38 +0,0 @@ -export const Loader = ({ size = 'md', fullScreen = false }) => { - const sizes = { - sm: 'w-4 h-4', - md: 'w-8 h-8', - lg: 'w-12 h-12', - }; - - const spinner = ( -
- ); - - if (fullScreen) { - return ( -
- {spinner} -
- ); - } - - return spinner; -}; - -export const SkeletonCard = () => { - return ( -
-
-
-
-
-
-
-
-
-
-
-
- ); -}; diff --git a/src/components/ui/Modal.jsx b/src/components/ui/Modal.jsx index 22e204b..3c1bcf2 100644 --- a/src/components/ui/Modal.jsx +++ b/src/components/ui/Modal.jsx @@ -1,41 +1,27 @@ -import { X } from 'lucide-react'; -import { useEffect } from 'react'; - -export const Modal = ({ isOpen, onClose, title, children, size = 'md' }) => { - useEffect(() => { - if (isOpen) { - document.body.style.overflow = 'hidden'; - } else { - document.body.style.overflow = 'unset'; - } - return () => { - document.body.style.overflow = 'unset'; - }; - }, [isOpen]); - +export const Modal = ({ isOpen, onClose, title, children }) => { if (!isOpen) return null; - const sizes = { - sm: 'max-w-md', - md: 'max-w-2xl', - lg: 'max-w-4xl', - xl: 'max-w-6xl', - }; - return ( -
-
-
-

{title}

- -
-
- {children} +
+
+
+ + + +
+
+
+
+

+ {title} +

+
{children}
+
+
+
diff --git a/src/components/ui/Toast.jsx b/src/components/ui/Toast.jsx new file mode 100644 index 0000000..e11a28e --- /dev/null +++ b/src/components/ui/Toast.jsx @@ -0,0 +1,26 @@ +import { useEffect } from 'react'; + +export const Toast = ({ message, type = 'success', onClose, duration = 3000 }) => { + useEffect(() => { + const timer = setTimeout(() => { + onClose(); + }, duration); + + return () => clearTimeout(timer); + }, [duration, onClose]); + + const bgColor = type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : 'bg-blue-500'; + + return ( +
+
+

{message}

+ +
+
+ ); +}; diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx index 4fe946f..6b96869 100644 --- a/src/context/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -15,7 +15,7 @@ export const AuthProvider = ({ children }) => { const [loading, setLoading] = useState(true); useEffect(() => { - // Check if user is logged in on mount + // Check if user is logged in from localStorage const storedUser = localStorage.getItem('roomatex_user'); if (storedUser) { setUser(JSON.parse(storedUser)); @@ -32,8 +32,12 @@ export const AuthProvider = ({ children }) => { const newUser = { ...userData, id: Date.now().toString(), + role: 'user', verified: false, - trustScore: 0, + profilePhoto: null, + listings: [], + wishlist: [], + viewedRooms: [], createdAt: new Date().toISOString(), }; setUser(newUser); @@ -52,14 +56,38 @@ export const AuthProvider = ({ children }) => { localStorage.setItem('roomatex_user', JSON.stringify(updatedUser)); }; + const addToWishlist = (roomId) => { + const wishlist = user.wishlist || []; + if (!wishlist.includes(roomId)) { + updateUser({ wishlist: [...wishlist, roomId] }); + } + }; + + const removeFromWishlist = (roomId) => { + const wishlist = user.wishlist || []; + updateUser({ wishlist: wishlist.filter(id => id !== roomId) }); + }; + + const addViewedRoom = (roomId) => { + const viewedRooms = user.viewedRooms || []; + if (!viewedRooms.includes(roomId)) { + const updated = [roomId, ...viewedRooms].slice(0, 10); // Keep last 10 + updateUser({ viewedRooms: updated }); + } + }; + const value = { user, - loading, login, signup, logout, updateUser, + addToWishlist, + removeFromWishlist, + addViewedRoom, + loading, isAuthenticated: !!user, + isAdmin: user?.role === 'admin', }; return {children}; diff --git a/src/data/admin.js b/src/data/admin.js deleted file mode 100644 index d7b4140..0000000 --- a/src/data/admin.js +++ /dev/null @@ -1,61 +0,0 @@ -export const reports = [ - { - id: 'rp1', - type: 'room', - targetId: '5', - targetName: 'Budget-Friendly Room for Students', - reporterId: 'u10', - reporterName: 'Sanjay Kumar', - reason: 'Misleading information', - description: 'The photos shown are not of the actual room. The amenities listed are also incorrect.', - status: 'pending', - date: '2025-01-26', - severity: 'high', - }, - { - id: 'rp2', - type: 'user', - targetId: '107', - targetName: 'Unknown User', - reporterId: 'u11', - reporterName: 'Priya Menon', - reason: 'Suspicious behavior', - description: 'This user asked for personal details before showing the room.', - status: 'pending', - date: '2025-01-25', - severity: 'critical', - }, - { - id: 'rp3', - type: 'room', - targetId: '2', - targetName: 'Shared 2BHK near Tech Park', - reporterId: 'u12', - reporterName: 'Ankit Verma', - reason: 'False availability', - description: 'Room was already rented but still showing as available.', - status: 'resolved', - date: '2025-01-20', - severity: 'medium', - }, -]; - -export const adminStats = { - totalUsers: 2847, - totalRooms: 1563, - activeListings: 1205, - pendingReports: 12, - verifiedUsers: 1892, - thisMonthSignups: 234, - thisMonthListings: 156, - avgTrustScore: 4.6, -}; - -export const cityStats = [ - { city: 'Mumbai', users: 652, rooms: 384 }, - { city: 'Bangalore', users: 734, rooms: 425 }, - { city: 'Delhi', users: 489, rooms: 287 }, - { city: 'Pune', users: 321, rooms: 198 }, - { city: 'Hyderabad', users: 298, rooms: 156 }, - { city: 'Chennai', users: 253, rooms: 113 }, -]; diff --git a/src/data/mockData.js b/src/data/mockData.js new file mode 100644 index 0000000..54ee474 --- /dev/null +++ b/src/data/mockData.js @@ -0,0 +1,321 @@ +export const mockRooms = [ + { + id: '1', + title: 'Spacious 2BHK in Koregaon Park', + description: 'Beautiful apartment with modern amenities, close to restaurants and cafes. Perfect for young professionals. The apartment features a large living room, fully equipped kitchen, and balcony with city views.', + images: [ + 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=800', + 'https://images.unsplash.com/photo-1502672260066-6bc35f0a1ec1?w=800', + 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800', + ], + rent: 18000, + deposit: 36000, + city: 'Pune', + area: 'Koregaon Park', + roomType: '2BHK', + furnishing: 'Fully Furnished', + amenities: ['WiFi', 'AC', 'Parking', 'Security', 'Gym', 'Power Backup'], + availableFrom: '2024-03-01', + preferredTenant: 'Any', + rules: ['No smoking', 'No pets', 'Quiet hours after 10 PM'], + owner: { + id: 'owner1', + name: 'Rahul Sharma', + phone: '+91 98765 43210', + verified: true, + }, + active: true, + createdAt: '2024-01-15', + }, + { + id: '2', + title: 'Cozy 1BHK near Hinjewadi IT Park', + description: 'Perfect for IT professionals working in Hinjewadi. Peaceful neighborhood with easy access to tech parks. Includes modern kitchen appliances and high-speed internet.', + images: [ + 'https://images.unsplash.com/photo-1493809842364-78817add7ffb?w=800', + 'https://images.unsplash.com/photo-1484154218962-a197022b5858?w=800', + 'https://images.unsplash.com/photo-1505691723518-36a5ac3be353?w=800', + ], + rent: 12000, + deposit: 24000, + city: 'Pune', + area: 'Hinjewadi', + roomType: '1BHK', + furnishing: 'Semi Furnished', + amenities: ['WiFi', 'Parking', 'Security', 'Water Supply'], + availableFrom: '2024-02-15', + preferredTenant: 'Working Professional', + rules: ['No smoking', 'Vegetarian preferred'], + owner: { + id: 'owner2', + name: 'Priya Patel', + phone: '+91 98765 43211', + verified: true, + }, + active: true, + createdAt: '2024-01-20', + }, + { + id: '3', + title: 'Luxury 3BHK in Bandra West', + description: 'Premium apartment with sea view, state-of-the-art amenities, and 24/7 concierge service. Located in the heart of Bandra with easy access to restaurants and nightlife.', + images: [ + 'https://images.unsplash.com/photo-1512918728675-ed5a9ecdebfd?w=800', + 'https://images.unsplash.com/photo-1556912173-46c336c7fd55?w=800', + 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800', + ], + rent: 65000, + deposit: 130000, + city: 'Mumbai', + area: 'Bandra West', + roomType: '3BHK', + furnishing: 'Fully Furnished', + amenities: ['WiFi', 'AC', 'Parking', 'Security', 'Gym', 'Swimming Pool', 'Power Backup', 'Elevator'], + availableFrom: '2024-03-15', + preferredTenant: 'Family', + rules: ['No smoking', 'No loud music'], + owner: { + id: 'owner3', + name: 'Amit Desai', + phone: '+91 98765 43212', + verified: true, + }, + active: true, + createdAt: '2024-01-10', + }, + { + id: '4', + title: 'Student-Friendly PG in Kothrud', + description: 'Affordable accommodation for students and young professionals. Clean, safe, and close to colleges and universities. Includes meals and housekeeping.', + images: [ + 'https://images.unsplash.com/photo-1554995207-c18c203602cb?w=800', + 'https://images.unsplash.com/photo-1595526114035-0d45ed16cfbf?w=800', + ], + rent: 8000, + deposit: 16000, + city: 'Pune', + area: 'Kothrud', + roomType: 'PG', + furnishing: 'Fully Furnished', + amenities: ['WiFi', 'Meals', 'Laundry', 'Security', 'Power Backup'], + availableFrom: '2024-02-01', + preferredTenant: 'Student', + rules: ['No smoking', 'No guests after 10 PM', 'Vegetarian only'], + owner: { + id: 'owner4', + name: 'Sunita Kulkarni', + phone: '+91 98765 43213', + verified: true, + }, + active: true, + createdAt: '2024-01-25', + }, + { + id: '5', + title: 'Modern Studio in Indiranagar', + description: 'Compact and efficient studio apartment perfect for singles. Located in the vibrant Indiranagar area with cafes, restaurants, and shopping nearby.', + images: [ + 'https://images.unsplash.com/photo-1502672023488-70e25813eb80?w=800', + 'https://images.unsplash.com/photo-1536376072261-38c75010e6c9?w=800', + ], + rent: 22000, + deposit: 44000, + city: 'Bangalore', + area: 'Indiranagar', + roomType: 'Studio', + furnishing: 'Fully Furnished', + amenities: ['WiFi', 'AC', 'Security', 'Power Backup'], + availableFrom: '2024-02-20', + preferredTenant: 'Working Professional', + rules: ['No smoking', 'No pets'], + owner: { + id: 'owner5', + name: 'Karthik Reddy', + phone: '+91 98765 43214', + verified: false, + }, + active: true, + createdAt: '2024-01-18', + }, + { + id: '6', + title: 'Spacious 2BHK in Whitefield', + description: 'Great for tech professionals working in Whitefield IT corridor. Gated community with excellent amenities and security. Pet-friendly apartment.', + images: [ + 'https://images.unsplash.com/photo-1574362848149-11496d93a7c7?w=800', + 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800', + ], + rent: 28000, + deposit: 56000, + city: 'Bangalore', + area: 'Whitefield', + roomType: '2BHK', + furnishing: 'Semi Furnished', + amenities: ['WiFi', 'AC', 'Parking', 'Security', 'Gym', 'Playground', 'Power Backup'], + availableFrom: '2024-03-10', + preferredTenant: 'Any', + rules: ['Pets allowed', 'No smoking indoors'], + owner: { + id: 'owner6', + name: 'Deepa Iyer', + phone: '+91 98765 43215', + verified: true, + }, + active: true, + createdAt: '2024-01-12', + }, +]; + +export const mockRoommates = [ + { + id: 'rm1', + name: 'Sneha Joshi', + age: 24, + gender: 'Female', + occupation: 'Software Engineer', + city: 'Pune', + profilePhoto: 'https://i.pravatar.cc/300?img=1', + about: 'Working professional looking for a friendly roommate. I enjoy reading, yoga, and cooking. Prefer a quiet and clean living environment.', + lookingFor: { + roomType: '2BHK', + budgetMin: 10000, + budgetMax: 15000, + }, + preferences: { + genderPreference: 'Female', + lifestyle: 'Non-smoker, Vegetarian', + interests: ['Reading', 'Yoga', 'Cooking', 'Travel'], + }, + verified: true, + phone: '+91 98765 43220', + }, + { + id: 'rm2', + name: 'Aditya Kumar', + age: 27, + gender: 'Male', + occupation: 'Marketing Manager', + city: 'Mumbai', + profilePhoto: 'https://i.pravatar.cc/300?img=12', + about: 'Outgoing person who loves music and movies. Looking for someone chill to share an apartment with. Work from home sometimes.', + lookingFor: { + roomType: '2BHK', + budgetMin: 20000, + budgetMax: 30000, + }, + preferences: { + genderPreference: 'Any', + lifestyle: 'Social, Non-smoker', + interests: ['Music', 'Movies', 'Fitness', 'Gaming'], + }, + verified: true, + phone: '+91 98765 43221', + }, + { + id: 'rm3', + name: 'Meera Nair', + age: 22, + gender: 'Female', + occupation: 'Student', + city: 'Pune', + profilePhoto: 'https://i.pravatar.cc/300?img=5', + about: 'Final year engineering student. Looking for a quiet place to study. I am clean, organized, and respectful of personal space.', + lookingFor: { + roomType: 'PG', + budgetMin: 6000, + budgetMax: 10000, + }, + preferences: { + genderPreference: 'Female', + lifestyle: 'Non-smoker, Vegetarian, Early riser', + interests: ['Studies', 'Books', 'Art'], + }, + verified: false, + phone: '+91 98765 43222', + }, + { + id: 'rm4', + name: 'Rohan Mehta', + age: 29, + gender: 'Male', + occupation: 'Data Analyst', + city: 'Bangalore', + profilePhoto: 'https://i.pravatar.cc/300?img=15', + about: 'Tech enthusiast and fitness freak. I work at a startup and enjoy outdoor activities on weekends. Looking for like-minded roommate.', + lookingFor: { + roomType: '2BHK', + budgetMin: 15000, + budgetMax: 25000, + }, + preferences: { + genderPreference: 'Male', + lifestyle: 'Active, Social', + interests: ['Fitness', 'Hiking', 'Technology', 'Cooking'], + }, + verified: true, + phone: '+91 98765 43223', + }, + { + id: 'rm5', + name: 'Kavya Reddy', + age: 26, + gender: 'Female', + occupation: 'Graphic Designer', + city: 'Bangalore', + profilePhoto: 'https://i.pravatar.cc/300?img=9', + about: 'Creative professional working in the design industry. Love art, coffee, and good conversations. Looking for a mature and responsible roommate.', + lookingFor: { + roomType: 'Studio', + budgetMin: 18000, + budgetMax: 25000, + }, + preferences: { + genderPreference: 'Female', + lifestyle: 'Non-smoker, Night owl', + interests: ['Art', 'Design', 'Photography', 'Music'], + }, + verified: true, + phone: '+91 98765 43224', + }, + { + id: 'rm6', + name: 'Arjun Singh', + age: 25, + gender: 'Male', + occupation: 'Financial Analyst', + city: 'Mumbai', + profilePhoto: 'https://i.pravatar.cc/300?img=13', + about: 'Working in finance sector. Organized, disciplined, and like to maintain a clean living space. Enjoy reading and weekend travels.', + lookingFor: { + roomType: '2BHK', + budgetMin: 25000, + budgetMax: 35000, + }, + preferences: { + genderPreference: 'Male', + lifestyle: 'Non-smoker, Organized', + interests: ['Finance', 'Travel', 'Reading', 'Chess'], + }, + verified: false, + phone: '+91 98765 43225', + }, +]; + +export const cities = ['Pune', 'Mumbai', 'Bangalore', 'Delhi', 'Hyderabad', 'Chennai']; + +export const amenitiesList = [ + 'WiFi', + 'AC', + 'Parking', + 'Security', + 'Gym', + 'Swimming Pool', + 'Power Backup', + 'Elevator', + 'Meals', + 'Laundry', + 'Water Supply', + 'Playground', +]; + +export const roomTypes = ['1BHK', '2BHK', '3BHK', 'Studio', 'PG']; diff --git a/src/data/reviews.js b/src/data/reviews.js deleted file mode 100644 index c27d43d..0000000 --- a/src/data/reviews.js +++ /dev/null @@ -1,70 +0,0 @@ -export const reviews = [ - { - id: 'rev1', - roomId: '1', - userId: 'u1', - userName: 'Vikram Shah', - userImage: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400', - rating: 5, - comment: 'Excellent accommodation! The owner is very cooperative and the locality is great. Highly recommended.', - date: '2025-01-15', - helpful: 8, - }, - { - id: 'rev2', - roomId: '1', - userId: 'u2', - userName: 'Sneha Desai', - userImage: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400', - rating: 4, - comment: 'Good place to stay. Well maintained and clean. Slightly expensive but worth it.', - date: '2025-01-10', - helpful: 5, - }, - { - id: 'rev3', - roomId: '3', - userId: 'u3', - userName: 'Anjali Nair', - userImage: 'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?w=400', - rating: 5, - comment: 'Best PG experience ever! Home-cooked food is delicious and the environment is very safe.', - date: '2025-01-12', - helpful: 12, - }, - { - id: 'rev4', - roomId: '2', - userId: 'u4', - userName: 'Rohan Kapoor', - userImage: 'https://images.unsplash.com/photo-1463453091185-61582044d556?w=400', - rating: 4, - comment: 'Great location near office. Roommates are friendly and cooperative.', - date: '2025-01-08', - helpful: 6, - }, -]; - -export const userReviews = { - given: [ - { - id: 'ur1', - type: 'room', - targetName: 'Cozy Studio in Downtown', - rating: 5, - comment: 'Excellent accommodation! The owner is very cooperative.', - date: '2025-01-15', - }, - ], - received: [ - { - id: 'ur2', - type: 'user', - reviewerName: 'Amit Sharma', - reviewerImage: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400', - rating: 5, - comment: 'Very responsible tenant. Would recommend!', - date: '2025-01-10', - }, - ], -}; diff --git a/src/data/rooms.js b/src/data/rooms.js deleted file mode 100644 index 7b8fcde..0000000 --- a/src/data/rooms.js +++ /dev/null @@ -1,224 +0,0 @@ -export const rooms = [ - { - id: '1', - title: 'Cozy Studio in Downtown', - description: 'Modern studio apartment with all amenities. Perfect for students and young professionals. Walking distance to metro station and shopping centers.', - rent: 12000, - city: 'Mumbai', - area: 'Bandra West', - gender: 'Any', - availability: 'Available', - roomType: 'Private Room', - furnished: true, - amenities: ['WiFi', 'AC', 'Kitchen', 'Parking', 'Security'], - rules: ['No smoking', 'No pets', 'Guest policy: with permission'], - images: [ - 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=800', - 'https://images.unsplash.com/photo-1502672260066-6bc2696d1d8e?w=800', - 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?w=800' - ], - ownerId: '101', - ownerName: 'Priya Sharma', - ownerVerified: true, - ownerRating: 4.8, - reviews: 12, - postedDate: '2025-01-20', - preferredAge: '21-30', - occupants: 1, - maxOccupants: 2, - deposit: 24000, - featured: true, - }, - { - id: '2', - title: 'Shared 2BHK near Tech Park', - description: 'Spacious 2BHK apartment with one room available. Great for IT professionals. Close to major tech parks and cafes.', - rent: 15000, - city: 'Bangalore', - area: 'Koramangala', - gender: 'Male', - availability: 'Available from Feb 1', - roomType: 'Shared Room', - furnished: true, - amenities: ['WiFi', 'AC', 'Washing Machine', 'Gym', 'Power Backup'], - rules: ['No smoking', 'No drinking', 'Maintain cleanliness'], - images: [ - 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=800', - 'https://images.unsplash.com/photo-1556909212-d5b604d0c90d?w=800' - ], - ownerId: '102', - ownerName: 'Rahul Patel', - ownerVerified: true, - ownerRating: 4.5, - reviews: 8, - postedDate: '2025-01-22', - preferredAge: '22-35', - occupants: 2, - maxOccupants: 3, - deposit: 30000, - featured: false, - }, - { - id: '3', - title: 'PG with Home-cooked Meals', - description: 'Clean and comfortable PG accommodation with home-cooked vegetarian meals included. Family-like environment.', - rent: 9000, - city: 'Pune', - area: 'Kothrud', - gender: 'Female', - availability: 'Available', - roomType: 'Shared Room', - furnished: true, - amenities: ['WiFi', 'Meals', 'Laundry', 'Hot Water', 'Security'], - rules: ['No non-veg', 'No guests after 9 PM', 'No smoking/drinking'], - images: [ - 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800', - 'https://images.unsplash.com/photo-1560449752-8b4d462d2873?w=800' - ], - ownerId: '103', - ownerName: 'Lakshmi Iyer', - ownerVerified: true, - ownerRating: 4.9, - reviews: 24, - postedDate: '2025-01-15', - preferredAge: '18-28', - occupants: 3, - maxOccupants: 4, - deposit: 9000, - featured: true, - }, - { - id: '4', - title: 'Luxury 3BHK Apartment', - description: 'Premium apartment with modern interiors and scenic views. Perfect for executives. Swimming pool and gym facilities.', - rent: 25000, - city: 'Delhi', - area: 'South Delhi', - gender: 'Any', - availability: 'Available', - roomType: 'Private Room', - furnished: true, - amenities: ['WiFi', 'AC', 'Gym', 'Swimming Pool', 'Security', 'Parking'], - rules: ['Professional tenants only', 'No pets'], - images: [ - 'https://images.unsplash.com/photo-1502672023488-70e25813eb80?w=800', - 'https://images.unsplash.com/photo-1493809842364-78817add7ffb?w=800' - ], - ownerId: '104', - ownerName: 'Amit Khanna', - ownerVerified: true, - ownerRating: 4.7, - reviews: 15, - postedDate: '2025-01-18', - preferredAge: '25-40', - occupants: 1, - maxOccupants: 2, - deposit: 50000, - featured: true, - }, - { - id: '5', - title: 'Budget-Friendly Room for Students', - description: 'Affordable accommodation near university campus. Perfect for students. Peaceful study environment.', - rent: 6000, - city: 'Chennai', - area: 'Guindy', - gender: 'Any', - availability: 'Available', - roomType: 'Shared Room', - furnished: true, - amenities: ['WiFi', 'Study Table', 'Hot Water', 'Security'], - rules: ['Maintain silence after 10 PM', 'No smoking'], - images: [ - 'https://images.unsplash.com/photo-1555854877-bab0e564b8d5?w=800' - ], - ownerId: '105', - ownerName: 'Suresh Kumar', - ownerVerified: false, - ownerRating: 4.3, - reviews: 6, - postedDate: '2025-01-25', - preferredAge: '18-25', - occupants: 3, - maxOccupants: 4, - deposit: 6000, - featured: false, - }, - { - id: '6', - title: 'Modern 1BHK in IT Hub', - description: 'Newly renovated 1BHK apartment in the heart of IT corridor. All modern amenities and 24/7 security.', - rent: 18000, - city: 'Hyderabad', - area: 'Gachibowli', - gender: 'Any', - availability: 'Available from Feb 15', - roomType: 'Private Room', - furnished: true, - amenities: ['WiFi', 'AC', 'Modular Kitchen', 'Parking', 'Power Backup'], - rules: ['Professional tenants preferred', 'No loud music'], - images: [ - 'https://images.unsplash.com/photo-1522771739844-6a9f6d5f14af?w=800', - 'https://images.unsplash.com/photo-1540518614846-7eded433c457?w=800' - ], - ownerId: '106', - ownerName: 'Divya Reddy', - ownerVerified: true, - ownerRating: 4.6, - reviews: 10, - postedDate: '2025-01-19', - preferredAge: '23-35', - occupants: 0, - maxOccupants: 2, - deposit: 36000, - featured: false, - }, -]; - -export const roommates = [ - { - id: 'rm1', - name: 'Arjun Singh', - age: 24, - gender: 'Male', - occupation: 'Software Engineer', - city: 'Bangalore', - lookingFor: 'Shared 2BHK', - budget: '10000-15000', - preferences: 'Non-smoker, working professional', - verified: true, - rating: 4.7, - image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400', - about: 'Working professional looking for clean and peaceful accommodation.', - }, - { - id: 'rm2', - name: 'Neha Gupta', - age: 22, - gender: 'Female', - occupation: 'Student', - city: 'Delhi', - lookingFor: 'PG or Shared Room', - budget: '8000-12000', - preferences: 'Female roommates only, vegetarian', - verified: true, - rating: 4.9, - image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400', - about: 'MBA student seeking accommodation near campus.', - }, - { - id: 'rm3', - name: 'Karthik Menon', - age: 28, - gender: 'Male', - occupation: 'Designer', - city: 'Mumbai', - lookingFor: 'Private Room', - budget: '15000-20000', - preferences: 'Near metro, quiet area', - verified: false, - rating: 4.5, - image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400', - about: 'Freelance designer looking for a creative space.', - }, -]; diff --git a/src/layouts/MainLayout.jsx b/src/layouts/MainLayout.jsx deleted file mode 100644 index 754d942..0000000 --- a/src/layouts/MainLayout.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Navbar } from './Navbar'; - -export const MainLayout = ({ children }) => { - return ( -
- -
- {children} -
-
- ); -}; diff --git a/src/layouts/Navbar.jsx b/src/layouts/Navbar.jsx deleted file mode 100644 index 46cc132..0000000 --- a/src/layouts/Navbar.jsx +++ /dev/null @@ -1,144 +0,0 @@ -import { Link, useNavigate } from 'react-router-dom'; -import { useAuth } from '../context/AuthContext'; -import { Home, Search, Info, Star, User, LogOut, Menu, X, Shield } from 'lucide-react'; -import { useState } from 'react'; -import { Button } from '../components/ui/Button'; -// import {logo} from "../assets/logo.png"; - -export const Navbar = () => { - const { user, logout } = useAuth(); - const navigate = useNavigate(); - const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - - const handleLogout = () => { - logout(); - navigate('/login'); - }; - - const getNavItems = () => { - if (user?.role === 'admin') { - return [ - { to: '/dashboard', label: 'Dashboard', icon: Home }, - { to: '/admin/users', label: 'Users', icon: User }, - { to: '/admin/rooms', label: 'Rooms', icon: Search }, - { to: '/admin/reports', label: 'Reports', icon: Shield }, - ]; - } - - if (user?.role === 'owner') { - return [ - { to: '/dashboard', label: 'Home', icon: Home }, - { to: '/add-room', label: 'List Room', icon: Search }, - { to: '/info', label: 'Info', icon: Info }, - { to: '/reviews', label: 'Reviews', icon: Star }, - ]; - } - - // seeker - return [ - { to: '/dashboard', label: 'Home', icon: Home }, - { to: '/rooms', label: 'Find Room', icon: Search }, - { to: '/info', label: 'Info', icon: Info }, - { to: '/reviews', label: 'Reviews', icon: Star }, - ]; - }; - - const navItems = getNavItems(); - - return ( - - ); -}; diff --git a/src/main.jsx b/src/main.jsx index 32fcc3f..2be1bec 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from './App'; +import App from './App.jsx'; import './styles/index.css'; ReactDOM.createRoot(document.getElementById('root')).render( diff --git a/src/pages/AddRoom.jsx b/src/pages/AddRoom.jsx deleted file mode 100644 index d68329f..0000000 --- a/src/pages/AddRoom.jsx +++ /dev/null @@ -1,244 +0,0 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { MainLayout } from '../layouts/MainLayout'; -import { Card } from '../components/ui/Card'; -import { Button } from '../components/ui/Button'; -import { Input } from '../components/ui/Input'; -import { Alert } from '../components/ui/Alert'; -import { Upload, X } from 'lucide-react'; - -export const AddRoom = () => { - const navigate = useNavigate(); - const [alert, setAlert] = useState(null); - const [images, setImages] = useState([]); - const [formData, setFormData] = useState({ - title: '', - description: '', - rent: '', - city: '', - area: '', - gender: 'Any', - roomType: 'Private Room', - furnished: true, - amenities: [], - rules: '', - deposit: '', - preferredAge: '', - }); - - const amenitiesList = ['WiFi', 'AC', 'Kitchen', 'Parking', 'Security', 'Washing Machine', 'Gym', 'Power Backup']; - - const handleSubmit = (e) => { - e.preventDefault(); - if (images.length < 3) { - setAlert({ type: 'error', message: 'Please upload at least 3 photos' }); - return; - } - setAlert({ type: 'success', message: 'Room listed successfully!' }); - setTimeout(() => navigate('/dashboard'), 1500); - }; - - const handleImageUpload = (e) => { - const files = Array.from(e.target.files); - if (images.length + files.length > 10) { - setAlert({ type: 'error', message: 'Maximum 10 images allowed' }); - return; - } - setImages([...images, ...files.map(file => URL.createObjectURL(file))]); - }; - - const toggleAmenity = (amenity) => { - setFormData({ - ...formData, - amenities: formData.amenities.includes(amenity) - ? formData.amenities.filter(a => a !== amenity) - : [...formData.amenities, amenity], - }); - }; - - return ( - -
-
-

List Your Room

-

Fill in the details to create your listing

-
- - {alert && setAlert(null)} />} - -
- -

Basic Information

-
- setFormData({ ...formData, title: e.target.value })} - /> -
- - +
+
+ )} -
-
- - setFormData({ ...formData, rent: e.target.value })} - /> -
-
- - setFormData({ ...formData, deposit: e.target.value })} - /> -
-
+ {step === 4 && ( +
+
+

Review your listing

+
+
+ Property Title + {formData.title} +
+
+ Rent + ₹{formData.rent}/mo +
+
+ Location + {formData.area}, {formData.city} +
+
+ Amenities + {formData.amenities.length} Selected +
+
+
+
+ + By clicking Confirm, you agree to our terms and conditions for listing properties. +
+
+ )} -
-
- - -
-
- - setFormData({ ...formData, area: e.target.value })} - /> -
-
+
-
-
- - -
-
- - -
-
+
+ {step > 1 ? ( + + ) : ( +
+ )} -
- -
- {amenitiesList.map(amenity => ( - - ))} + {step < 4 ? ( + + ) : ( + + )} +
+ +
+
-
-
- - -
- -
- - +
+ + + )} +
+
+
+
-
-
- ); + ); }; diff --git a/src/pages/DashboardPage.jsx b/src/pages/DashboardPage.jsx index 19461db..b45369b 100644 --- a/src/pages/DashboardPage.jsx +++ b/src/pages/DashboardPage.jsx @@ -1,338 +1,397 @@ -import { useAuth } from '../context/AuthContext'; -import { Link } from 'react-router-dom'; import { useState, useEffect } from 'react'; -import { mockRooms, mockRoommates } from '../data/mockData'; +import { Link, useNavigate } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; +import { mockRooms } from '../data/mockData'; +import { Navbar } from '../components/Navbar'; +import { Hand } from 'lucide-react'; export const DashboardPage = () => { - const { user } = useAuth(); - const [rooms, setRooms] = useState([]); - const [roommates] = useState(mockRoommates); - const [userListings, setUserListings] = useState([]); - const [wishlist, setWishlist] = useState([]); - const [viewedRooms, setViewedRooms] = useState([]); + const { user, logout } = useAuth(); + const navigate = useNavigate(); + const [myListings, setMyListings] = useState([]); + const [wishlistCount, setWishlistCount] = useState(0); + const [wishlistRooms, setWishlistRooms] = useState([]); + const [incomingRequests, setIncomingRequests] = useState([]); + const [myRequestsCount, setMyRequestsCount] = useState(0); + const [showRequestsModal, setShowRequestsModal] = useState(false); + const [sentRequests, setSentRequests] = useState([]); - useEffect(() => { - // Load rooms from localStorage (includes user's added rooms) - const storedRooms = localStorage.getItem('roomatex_rooms'); - const allRooms = storedRooms ? JSON.parse(storedRooms) : mockRooms; - setRooms(allRooms); + useEffect(() => { + if (user) { + setMyListings(mockRooms.slice(0, 2)); + const wishlistIds = user.wishlist || []; + setWishlistCount(wishlistIds.length); + setWishlistRooms(mockRooms.filter(room => wishlistIds.includes(room.id))); + + const allRequests = JSON.parse(localStorage.getItem('roomRequests') || '[]'); + const myRequests = allRequests.filter(req => req.ownerId === user.email); + setIncomingRequests(myRequests); + + const sentReqs = allRequests.filter(req => req.userId === user.email); + setSentRequests(sentReqs); + setMyRequestsCount(sentReqs.length); + } + }, [user]); - // Get user's listings - const myListings = allRooms.filter(room => room.owner.id === user.id); - setUserListings(myListings); + const handleLogout = () => { + logout(); + navigate('/'); + }; - // Get wishlist - const wishlistIds = user.wishlist || []; - const wishlistRooms = allRooms.filter(room => wishlistIds.includes(room.id)); - setWishlist(wishlistRooms); + const handleDeleteRoom = (id) => { + if (window.confirm('Are you sure you want to delete this room?')) { + setMyListings(prev => prev.filter(room => room.id !== id)); + } + }; - // Get viewed rooms - const viewedIds = user.viewedRooms || []; - const recentlyViewed = allRooms.filter(room => viewedIds.includes(room.id)); - setViewedRooms(recentlyViewed); - }, [user]); + // Calculate stats + const recentlyViewedCount = myRequestsCount; + const availableRoomsCount = mockRooms.length; - const recommendedRooms = rooms.filter(room => - room.active && - room.owner.id !== user.id - ).slice(0, 6); + return ( +
+ - return ( -
-
- {/* Welcome Section */} -
-

Welcome back, {user.name}! 👋

-

Find your perfect room or roommate today

-
+
- {/* Quick Stats */} -
-
-
-
-

My Listings

-

{userListings.length}

-
-
- - - -
-
-
+ {/* Greeting */} +
+
+
+

+ Welcome back, {user?.name || 'User'}! + {/* */} +

+

Find your perfect room or roommate today

+
+
-
-
-
-

Wishlist

-

{wishlist.length}

-
-
- - - -
-
-
+ {/* Display Sections (Stats) */} +
+
+
+

My Listings

+

{myListings.length}

+
+
+ +
+
-
-
-
-

Recently Viewed

-

{viewedRooms.length}

-
-
- - - - -
-
-
+
+
+

Wishlist

+

{wishlistCount}

+
+
+ +
+
-
-
-
-

Available Rooms

-

{recommendedRooms.length}

-
-
- - - -
-
-
-
+
+
+

Recently Viewed

+

0

+
+
+ +
+
- {/* Quick Actions */} -
- -
-
- - - -
-
-

List Your Room

-

Find the perfect tenant for your property

-
-
- +
setShowRequestsModal(true)} className="glass-card p-6 flex items-center justify-between group hover:-translate-y-1 transition-all duration-300 cursor-pointer"> +
+

My Requests

+

{myRequestsCount}

+
+
+ +
+
+
- -
-
- - - -
-
-

Find Rooms

-

Explore available rooms in your city

-
-
- -
+ {/* Two Big Cards: Quick Actions */} +
+ +
+
+ +
+

List Your Room

+

Find the perfect tenant for your property. Quick and easy listing process.

+ + Start Listing + +
+
+ - {/* Recommended Rooms */} -
-
-

Recommended Rooms

- - View All - - - - -
-
- {recommendedRooms.slice(0, 3).map(room => ( - - {room.title} -
-

{room.title}

-

{room.description}

-
- ₹{room.rent.toLocaleString()} - {room.roomType} -
-
- - - - - {room.area}, {room.city} -
+ +
+
+ +
+

Find Rooms

+

Explore available rooms in your city with advanced filters and verified listings.

+ + Browse Available Rooms + +
+
+
- - ))} -
-
- {/* Recommended Roommates */} -
-
-

Recommended Roommates

-
-
- {roommates.slice(0, 3).map(roommate => ( - -
-
- {roommate.name} -
-
-

{roommate.name}

- {roommate.verified && ( - - - - )} -
-

{roommate.age} yrs • {roommate.occupation}

-
-
-

{roommate.about}

-
-
- - - - {roommate.city} + {/* My Listings Section */} + {incomingRequests.length > 0 && ( +
+

+ Incoming Requests {incomingRequests.length} +

+
+
+ {incomingRequests.map((request) => ( +
+
+
+ {request.userName?.[0] || 'U'} +
+
+
+

{request.userName}

+ {request.userVerified && ( + + + + )} +
+

{request.roomTitle}

+

{new Date(request.date).toLocaleDateString()}

+
+
+
+ {request.status.charAt(0).toUpperCase() + request.status.slice(1)} +
+
+ ))} +
+
-
- - - - ₹{roommate.lookingFor.budgetMin}k - ₹{roommate.lookingFor.budgetMax}k -
-
- -
- - ))} -
-
+ )} - {/* My Listings - Only if user has listings */} - {userListings.length > 0 && ( -
-
-

My Listings

- - Manage All - -
-
- {userListings.slice(0, 3).map(room => ( -
- {room.title} -
-

{room.title}

-
- ₹{room.rent.toLocaleString()} - - {room.active ? 'Active' : 'Inactive'} - -
-
- - View - - - Edit - - -
-
+ {/* My Listings Section */} +
+

+ My Listings {myListings.length} +

+ + {myListings.length > 0 ? ( +
+ {myListings.map((room) => ( +
+
+ {room.title} +
+ ₹{room.rent}/mo +
+
+
+

{room.title}

+

{room.area}, {room.city}

+
+ + View + + + Edit + + +
+
+
+ ))} +
+ ) : ( +
+
+ +
+

No listings yet

+

Share your property with thousands of potential tenants.

+ + Create Listing + +
+ )}
- ))} -
-
- )} - {/* Recently Viewed - Only if user has viewed rooms */} - {viewedRooms.length > 0 && ( -
-
-

Recently Viewed

-
-
- {viewedRooms.slice(0, 3).map(room => ( - - {room.title} -
-

{room.title}

-
- ₹{room.rent.toLocaleString()} - {room.area} + {/* Wishlist Section */} + {wishlistRooms.length > 0 && ( +
+
+

+ My Wishlist {wishlistRooms.length} +

+ + View All + +
+ +
+ {wishlistRooms.map((room) => ( + +
+ {room.title} +
+ ₹{room.rent}/mo +
+
+ +
+
+
+

{room.title}

+

{room.area}, {room.city}

+
+ + ))} +
-
- - ))} -
-
- )} + )} - {/* Wishlist - Only if user has wishlist items */} - {wishlist.length > 0 && ( -
-
-

My Wishlist

- - View All - -
-
- {wishlist.slice(0, 3).map(room => ( - -
-
- - - + {/* Recommended Rooms */} +
+
+

Recommended For You

+ + View All +
-
- {room.title} -
-

{room.title}

-
- ₹{room.rent.toLocaleString()} - {room.area} + +
+ {mockRooms.slice(2, 5).map((room) => ( + +
+ {room.title} +
+ ₹{room.rent}/mo +
+
+
+

{room.title}

+

{room.area}, {room.city}

+
+ +
+
+ + ))}
-
- - ))} -
-
- )} +
+ +
+ {/* Requests Modal */} + {showRequestsModal && ( +
+
setShowRequestsModal(false)}>
+
+
+

My Requests

+ +
+ +
+
+ {/* Requests Received */} +
+

+ Requests Received + {incomingRequests.length} +

+
+ {incomingRequests.length > 0 ? incomingRequests.map((request) => ( +
+
+
+ {request.userName?.[0] || 'U'} +
+
+
+

{request.userName}

+ {request.userVerified && ( + + + + )} +
+

{request.roomTitle}

+
+
+
+

{new Date(request.date).toLocaleDateString()}

+ + {request.status.charAt(0).toUpperCase() + request.status.slice(1)} + +
+
+ )) : ( +
+

No requests received yet

+
+ )} +
+
-
-
- ); + {/* Requests Sent */} +
+

+ Requests Sent + {sentRequests.length} +

+
+ {sentRequests.length > 0 ? sentRequests.map((request) => ( +
+
+

{request.roomTitle}

+

Owner: {request.ownerId}

+
+
+

{new Date(request.date).toLocaleDateString()}

+ + {request.status.charAt(0).toUpperCase() + request.status.slice(1)} + +
+
+ )) : ( +
+

No requests sent yet

+
+ )} +
+
+
+
+
+
+ )} +
+ ); }; diff --git a/src/pages/LandingPage.jsx b/src/pages/LandingPage.jsx index 9e450a7..50e823f 100644 --- a/src/pages/LandingPage.jsx +++ b/src/pages/LandingPage.jsx @@ -1,6 +1,6 @@ import { Link } from 'react-router-dom'; import { useState, useEffect } from 'react'; -import { mockRooms } from '../data/mockData'; +import { Shield, Users, Star, MapPin, CheckCircle, Home } from 'lucide-react'; export const LandingPage = () => { const [typedText, setTypedText] = useState(''); @@ -19,282 +19,377 @@ export const LandingPage = () => { return () => clearInterval(timer); }, []); - return ( -
- {/* Navbar */} - + const handleSmoothScroll = (e, targetId) => { + e.preventDefault(); + const element = document.getElementById(targetId); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }; - {/* Hero Section */} -
-
-
-
-

- {typedText} - | -

-

- Discover verified rooms and trusted roommates in your city. Safe, simple, and stress-free housing solutions. -

-
- - Get Started - - - Learn More - -
-
-
+ return ( +
+
+ {/* Background with Mesh Gradients */} +
+
+
+
+
- {/* Stats */} -
- {[ - { number: '2,800+', label: 'Active Users' }, - { number: '1,500+', label: 'Listed Rooms' }, - { number: '4.8★', label: 'Avg Rating' }, - { number: '98%', label: 'Satisfaction' }, - ].map((stat, index) => ( -
-
- {stat.number} -
-
{stat.label}
-
- ))} -
+ {/* Floating Boxes */} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- {/* Demo Cards */} -
-
-

Featured Rooms

-
- {mockRooms.slice(0, 3).map((room) => ( - -
- {room.title} - {room.owner.verified && ( -
- Featured + {/* Content Wrapper */} +
+ + {/* Floating Navbar */} +
+
- - {/* Features Section */} -
-
-
-

Why Choose RoomMateX?

-

We provide a secure, trusted platform to find your ideal living situation

-
-
- {[ - { - icon: '🛡️', - title: 'Verified Listings', - desc: 'All rooms and users are verified for your safety and peace of mind' - }, - { - icon: '👥', - title: 'Trusted Community', - desc: 'Join thousands of verified users finding their perfect match' - }, - { - icon: '⭐', - title: 'Rating System', - desc: 'Read genuine reviews from real tenants and roommates' - }, - { - icon: '📍', - title: 'Location-Based', - desc: 'Find rooms in your preferred areas and neighborhoods' - }, - { - icon: '✅', - title: 'Easy Booking', - desc: 'Simple process to connect with owners and secure your room' - }, - { - icon: '🏠', - title: 'Quality Assured', - desc: 'Only high-quality, safe accommodations make it to our platform' - }, - ].map((feature, idx) => ( -
-
{feature.icon}
-

{feature.title}

-

{feature.desc}

- ))} +
-
-
- {/* Testimonials */} -
-
-
-

What Our Users Say

-

Join thousands of happy residents

-
-
- {[ - { - name: 'Priya Sharma', - role: 'Student', - image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400', - text: 'Found my perfect PG within a week! The verification process made me feel so safe.', - rating: 5 - }, - { - name: 'Arjun Mehta', - role: 'Software Engineer', - image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400', - text: 'Amazing platform! Connected with great roommates who share similar interests.', - rating: 5 - }, - { - name: 'Sneha Gupta', - role: 'Designer', - image: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400', - text: 'The review system helped me make an informed decision. Highly recommend!', - rating: 5 - }, - ].map((testimonial, idx) => ( -
-
- {testimonial.name} -
-

{testimonial.name}

-

{testimonial.role}

+ {/* Hero Section */} +
+ {/* Decorative Background Elements */} +
+
+
+
+ +
+
+ {/* Rating Pill */} +
+
+ + +
+ Rated 4.9/5 from verified users
-
- {[...Array(testimonial.rating)].map((_, i) => ( - - + +

+ {typedText} + | +

+

+ Discover verified rooms and trusted roommates in your city. Safe, simple, and stress-free housing solutions designed for modern living. +

+ -

"{testimonial.text}"

- ))} -
-
-
- {/* Footer */} -
-
-
-
-

- RoomMateX -

-

Your trusted platform for finding rooms and roommates.

+ {/* Stats */} +
+ {[ + { number: '2,800+', label: 'Active Users' }, + { number: '1,500+', label: 'Listed Rooms' }, + { number: '4.8★', label: 'Avg Rating' }, + { number: '98%', label: 'Satisfaction' }, + ].map((stat, index) => ( +
+
+ {stat.number} +
+
{stat.label}
+
+ ))} +
-
-

Company

- +
+ + {/* Demo Cards / Featured Rooms */} +
+
+

Featured Rooms

+
+ {/* Placeholder Room Cards */} + {[ + { title: 'Cozy Studio Apartment', location: 'Downtown, Mumbai', price: '15,000', image: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=400' }, + { title: 'Spacious Apartment', location: 'Koramangala, Bangalore', price: '25,000', image: 'https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=400' }, + { title: 'Modern Shared Room', location: 'Gurgaon, Delhi NCR', price: '12,000', image: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=400' }, + ].map((room, idx) => ( + +
+
+ {room.title} +
+ Featured +
+
+ ₹{room.price}/mo +
+
+
+

{room.title}

+
+ + + + {room.location} +
+
+ Available + Contact now +
+
+ + ))} +
+
+ + View All Rooms + + + + +
-
-

Support

- +
+ + {/* Features Section */} +
+
+
+

Why Choose RoomMateX?

+

We provide a secure, trusted platform to find your ideal living situation with verified profiles and smart matching.

+
+
+ {[ + { + icon: Shield, + title: 'Verified Listings', + desc: 'All rooms and users are verified for your safety and peace of mind.' + }, + { + icon: Users, + title: 'Trusted Community', + desc: 'Join thousands of verified users finding their perfect match everyday.' + }, + { + icon: Star, + title: 'Rating System', + desc: 'Read genuine reviews from real tenants and roommates before booking.' + }, + { + icon: MapPin, + title: 'Location-Based', + desc: 'Find rooms in your preferred areas and neighborhoods with our smart map.' + }, + { + icon: CheckCircle, + title: 'Easy Booking', + desc: 'Simple process to connect with owners and secure your room online.' + }, + { + icon: Home, + title: 'Quality Assured', + desc: 'Only high-quality, safe accommodations make it to our platform.' + }, + ].map((feature, idx) => ( +
+
+ +
+

{feature.title}

+

{feature.desc}

+
+ ))} +
-
-

Legal

- +
+ + {/* Testimonials */} +
+
+
+

What Our Users Say

+

Join thousands of happy residents who found their home with us.

+
+
+ {[ + { + name: 'Priya Sharma', + role: 'Student', + image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400', + text: 'Found my perfect PG within a week! The verification process made me feel so safe.', + rating: 5 + }, + { + name: 'Arjun Mehta', + role: 'Software Engineer', + image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400', + text: 'Amazing platform! Connected with great roommates who share similar interests.', + rating: 5 + }, + { + name: 'Sneha Gupta', + role: 'Designer', + image: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400', + text: 'The review system helped me make an informed decision. Highly recommend!', + rating: 5 + }, + ].map((testimonial, idx) => ( +
+
+ {testimonial.name} +
+

{testimonial.name}

+

{testimonial.role}

+
+
+
+ {[...Array(testimonial.rating)].map((_, i) => ( + + + + ))} +
+

"{testimonial.text}"

+
+ ))} +
-
-
-

© 2026 RoomMateX. All rights reserved.

-
+ + + {/* Footer */} +
- + ); }; diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index 2ce76f6..3454183 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -1,16 +1,54 @@ import { useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; -import API_BASE from '../services/api'; export const LoginPage = () => { const [formData, setFormData] = useState({ email: '', password: '' }); const [showPassword, setShowPassword] = useState(false); const [error, setError] = useState(''); + const [showForgotPassword, setShowForgotPassword] = useState(false); + const [forgotEmail, setForgotEmail] = useState(''); + const [otpSent, setOtpSent] = useState(false); + const [otp, setOtp] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [resetStep, setResetStep] = useState(1); const { login } = useAuth(); const navigate = useNavigate(); - const handleSubmit = async (e) => { + const handleForgotPassword = () => { + if (!forgotEmail) { + setError('Please enter your email'); + return; + } + setError(''); + setOtpSent(true); + setResetStep(2); + }; + + const handleVerifyOtp = () => { + if (otp === '1234') { + setError(''); + setResetStep(3); + } else { + setError('Invalid OTP. Use 1234.'); + } + }; + + const handleResetPassword = () => { + if (!newPassword || newPassword.length < 6) { + setError('Password must be at least 6 characters'); + return; + } + setError(''); + setShowForgotPassword(false); + setResetStep(1); + setForgotEmail(''); + setOtp(''); + setNewPassword(''); + setOtpSent(false); + }; + + const handleSubmit = (e) => { e.preventDefault(); setError(''); @@ -19,99 +57,106 @@ export const LoginPage = () => { return; } - try { - const response = await fetch(`${API_BASE}/auth/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - email: formData.email, - password: formData.password, - }), + // Demo login - Check for admin and user credentials + if (formData.email === 'admin@test.com' && formData.password === 'admin123') { + login({ + id: 'admin1', + name: 'Admin User', + email: formData.email, + role: 'admin', + phone: '+91 98765 43200', + city: 'Pune', + verified: true, + profilePhoto: null, + listings: [], + wishlist: [], + viewedRooms: [], }); - - const data = await response.json(); - - if (!response.ok) { - if (Array.isArray(data.detail)) { - setError(data.detail[0].msg); - } else { - setError(data.detail || 'Invalid credentials'); - } - return; - } - - - // ✅ Store token - localStorage.setItem('access_token', data.access_token); - - // ✅ Save user in auth context + navigate('/admin'); + } else if (formData.email === 'user@test.com' && formData.password === 'password') { + // Regular user login login({ - id: data.user.id, - name: data.user.full_name, - email: data.user.email, - role: data.user.role, - phone: data.user.phone_number, - city: data.user.city, + id: 'user' + Date.now(), + name: 'Demo User', + email: formData.email, + role: 'user', + phone: '+91 98765 43210', + city: 'Pune', verified: true, profilePhoto: null, listings: [], wishlist: [], viewedRooms: [], }); - - // ✅ Redirect based on role - if (data.user.role === 'admin') { - navigate('/admin'); - } else { - navigate('/dashboard'); - } - - } catch (err) { - console.error(err); - setError('Server error. Try again.'); + navigate('/dashboard'); + } else { + setError('Invalid credentials'); } - }; return ( -
- {/* Left Side - Form */} -
-
- -

RoomMateX

+
+ {/* Left Side: Clean Form */} +
+
+ {/* Brand/Logo */} + +
+ + + +
+ RoomMateX -
-

Welcome Back

-

Sign in to continue to RoomMateX

+ + {/* Form Header */} +
+

Sign in

+

Access your RoomMateX dashboard

{error && ( -
+
+ + + {error}
)} -
-
- - setFormData({ ...formData, email: e.target.value })} - /> + +
+ +
+
+ + + +
+ setFormData({ ...formData, email: e.target.value })} + /> +
-
- -
+
+
+ + +
+
+
+ + + +
setFormData({ ...formData, password: e.target.value })} @@ -119,7 +164,7 @@ export const LoginPage = () => {
- -
-

- Don't have an account?{' '} - - Sign up - -

-
+

+ Don't have an account?{' '} + Sign up +

-
-

Demo Credentials:

-
-
- User: user@test.com / password -
-
- Admin: admin@test.com / admin123 -
+ + +
+
+
+

Demo Access

+
+
+

User: user@test.com / password

+

Admin: admin@test.com / admin123

- {/* Right Side - Image */} -
-
-
-

Find Your Perfect Living Space

-

- Join thousands of users who found their ideal room and roommates through our platform. -

-
-
-
- + {/* Forgot Password Modal */} + {showForgotPassword && ( +
+
+
+

+ {resetStep === 1 && 'Forgot Password'} + {resetStep === 2 && 'Enter OTP'} + {resetStep === 3 && 'Reset Password'} +

+ +
+ + {error &&
{error}
} + + {resetStep === 1 && ( +
+

Enter your email address and we'll send you an OTP to reset your password.

+
+ + { setForgotEmail(e.target.value); setError(''); }} + className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all" + placeholder="you@email.com" + />
- Verified listings and users +
-
-
- + )} + + {resetStep === 2 && ( +
+
+

We sent an OTP to

+

{forgotEmail}

- Safe and secure platform +
+ + { setOtp(e.target.value); setError(''); }} + className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all text-center tracking-[1em] font-bold text-2xl" + placeholder="0000" + maxLength={4} + /> +

Demo OTP: 1234

+
+
-
-
- + )} + + {resetStep === 3 && ( +
+

Enter your new password

+
+ + { setNewPassword(e.target.value); setError(''); }} + className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all" + placeholder="Enter new password" + /> +

Password must be at least 6 characters

- Trusted by 2,800+ users +
+ )} +
+
+ )} + + {/* Right Side: Themed/Animated Content */} +
+ {/* Landing Page Theme Background */} +
+
+
+
+
+ + {/* Floating Boxes */} +
+
+
+
+
+ +
+ {/* Badge */} +
+ + + + Better Together +
+ +

+ Find Your Perfect + Living Space +

+ +

+ Discover verified rooms and trusted roommates in your city. Safe, simple, and stress-free housing solutions. +

+ +
+

Why choose RoomMateX?

+
    +
  • +
    + +
    + Verified Listings & Users +
  • +
  • +
    + +
    + Secure & Easy Communication +
  • +
  • +
    + +
    + Smart Matchmaking Features +
  • +
+
+ + {/* Feature Stats */} +
+
+
2,800+
+
Active Users
+
+
+
98%
+
Satisfaction
+
+
+ + {/* Trust Avatars */} +
+
+ + +
+

Join 1,000+ happy residents

- Modern Living Space
); diff --git a/src/pages/ProfilePage.jsx b/src/pages/ProfilePage.jsx index 617df69..c66bfbe 100644 --- a/src/pages/ProfilePage.jsx +++ b/src/pages/ProfilePage.jsx @@ -1,347 +1,426 @@ import { useState, useEffect } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; -import { Toast } from '../components/ui/Toast'; -import { Modal } from '../components/ui/Modal'; +import { useNavigate } from 'react-router-dom'; +import { Navbar } from '../components/Navbar'; +import { GUJARAT_CITIES } from '../constants/cities'; export const ProfilePage = () => { - const { user, updateUser } = useAuth(); - const navigate = useNavigate(); - const [activeTab, setActiveTab] = useState('profile'); - const [toast, setToast] = useState(null); - const [isEditing, setIsEditing] = useState(false); - const [editData, setEditData] = useState({}); - const [profilePhoto, setProfilePhoto] = useState(null); - const [passwordData, setPasswordData] = useState({ current: '', new: '', confirm: '' }); - const [rooms, setRooms] = useState([]); - const [wishlist, setWishlist] = useState([]); - const [showDeleteModal, setShowDeleteModal] = useState(false); - const [roomToDelete, setRoomToDelete] = useState(null); + const { user, updateUser } = useAuth(); + const navigate = useNavigate(); + const [activeTab, setActiveTab] = useState('personal'); + const [isEditing, setIsEditing] = useState(false); + + const [twoFactor, setTwoFactor] = useState(false); + const [notifications, setNotifications] = useState({ + emailNotifications: true, + pushNotifications: true, + marketingMessages: false + }); - useEffect(() => { - const storedRooms = JSON.parse(localStorage.getItem('roomatex_rooms') || '[]'); - const userListings = storedRooms.filter(room => room.owner.id === user.id); - setRooms(userListings); + const toggleNotification = (key) => { + setNotifications(prev => ({ ...prev, [key]: !prev[key] })); + }; - const wishlistIds = user.wishlist || []; - const wishlistRooms = storedRooms.filter(room => wishlistIds.includes(room.id)); - setWishlist(wishlistRooms); + // Form State + const [formData, setFormData] = useState({ + name: '', + email: '', + phone: '', + city: '', + occupation: '', + about: '', + gender: '', + age: '', + budgetMin: '', + budgetMax: '', + lifestyle: [], + interests: [] + }); - setEditData({ name: user.name, email: user.email, phone: user.phone, city: user.city }); - setProfilePhoto(user.profilePhoto); - }, [user]); + // Initialize form data from user context + useEffect(() => { + if (user) { + setFormData({ + name: user.name || '', + email: user.email || '', + phone: user.phone || '', + city: user.city || '', + occupation: user.occupation || '', + about: user.about || '', + gender: user.gender || '', + age: user.age || '', + budgetMin: user.preferences?.budgetMin || '', + budgetMax: user.preferences?.budgetMax || '', + lifestyle: user.preferences?.lifestyle || [], + interests: user.preferences?.interests || [] + }); + } + }, [user]); - const profileCompletion = () => { - let score = 0; - let total = 4; - if (user.name) score += 25; - if (user.email) score += 25; - if (user.phone) score += 25; - if (user.city) score += 25; - return score; - }; + const handleChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + }; - const handleSave = () => { - updateUser({ ...editData, profilePhoto }); - setIsEditing(false); - setToast({ message: 'Profile updated successfully', type: 'success' }); - }; + const handlePreferenceChange = (category, item) => { + setFormData(prev => { + const list = prev[category] || []; + if (list.includes(item)) { + return { ...prev, [category]: list.filter(i => i !== item) }; + } else { + return { ...prev, [category]: [...list, item] }; + } + }); + }; - const handlePhotoChange = (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onloadend = () => { - setProfilePhoto(reader.result); - }; - reader.readAsDataURL(file); - } - }; + const handleSave = () => { + // Construct the updates object + const updates = { + name: formData.name, + email: formData.email, + phone: formData.phone, + city: formData.city, + occupation: formData.occupation, + about: formData.about, + gender: formData.gender, + age: formData.age, + preferences: { + budgetMin: formData.budgetMin, + budgetMax: formData.budgetMax, + lifestyle: formData.lifestyle, + interests: formData.interests + } + }; - const handlePasswordChange = () => { - if (passwordData.current === passwordData.new) { - setToast({ message: 'Current password and new password cannot be the same', type: 'error' }); - return; - } - if (passwordData.new !== passwordData.confirm) { - setToast({ message: 'New passwords do not match', type: 'error' }); - return; - } - setToast({ message: 'Password updated successfully', type: 'success' }); - setPasswordData({ current: '', new: '', confirm: '' }); - }; + updateUser(updates); + setIsEditing(false); + // Optional: Show success toast + }; - const handleDeleteRoom = (roomId) => { - setRoomToDelete(roomId); - setShowDeleteModal(true); - }; + // Verification Handler + const handleVerifyClick = () => { + navigate('/verification?action=verify'); + }; - const confirmDelete = () => { - const storedRooms = JSON.parse(localStorage.getItem('roomatex_rooms') || '[]'); - const updatedRooms = storedRooms.filter(room => room.id !== roomToDelete); - localStorage.setItem('roomatex_rooms', JSON.stringify(updatedRooms)); - - setRooms(updatedRooms.filter(room => room.owner.id === user.id)); - setShowDeleteModal(false); - setToast({ message: 'Room deleted successfully', type: 'success' }); - }; + // Change Password Handler + const handlePasswordClick = () => { + navigate('/verification?action=changePassword'); + }; - return ( -
- {toast && setToast(null)} />} - -
-
- {/* Sidebar */} -
-
-
-
- {profilePhoto ? ( - Profile - ) : ( -
- {user.name.charAt(0)} -
- )} - - -
-

{user.name}

-

{user.email}

-
+ // Delete Account Handler + const handleDeleteClick = () => { + navigate('/verification?action=deleteAccount'); + }; -
-
- Profile Completion - {profileCompletion()}% -
-
-
-
-
+ const lifestyleOptions = ['Non-smoker', 'Early Riser', 'Night Owl', 'Pet Friendly', 'Vegetarian', 'Vegan', 'Clean', 'Social', 'Introvert']; + const interestOptions = ['Reading', 'Gaming', 'Music', 'Cooking', 'Travel', 'Fitness', 'Art', 'Movies', 'Tech', 'Outdoors']; - -
-
+ return ( +
+ - {/* Main Content */} -
- {activeTab === 'profile' && ( -
-
-

Profile Information

- {!isEditing && ( - - )} -
+
+
- {isEditing ? ( -
-
- - setEditData({ ...editData, name: e.target.value })} - /> -
-
- - setEditData({ ...editData, email: e.target.value })} - /> -
-
- - setEditData({ ...editData, phone: e.target.value })} - /> -
-
- - setEditData({ ...editData, city: e.target.value })} - /> -
-
- - -
-
- ) : ( -
-
- Name - {user.name} -
-
- Email - {user.email} -
-
- Phone - {user.phone} -
-
- City - {user.city} -
-
- )} -
- )} + {/* Left Sidebar: Profile Card */} +
+
+
+
+ {user?.profilePhoto ? ( + {user.name} + ) : ( +
+ {user?.name?.[0]?.toUpperCase() || 'U'} +
+ )} +
+ +
+
+ {user?.verified && ( +
+ +
+ )} +
- {activeTab === 'listings' && ( -
-
-

My Listings ({rooms.length})

- Add New Room -
+

{user?.name}

+

{user?.city || 'No Location Set'}

- {rooms.length === 0 ? ( -
-

You haven't listed any rooms yet

- List Your First Room -
- ) : ( -
- {rooms.map(room => ( -
- {room.title} -
-

{room.title}

-
- ₹{room.rent.toLocaleString()} - - {room.active ? 'Active' : 'Inactive'} - -
-
- - View - - - Edit - - -
-
-
- ))} -
- )} -
- )} + {!user?.verified ? ( + + ) : ( +
+
+ + Identity Verified +
+ +
+ )} - {activeTab === 'wishlist' && ( -
-

My Wishlist ({wishlist.length})

+
+
+ {user?.listings?.length || 0} + Listings +
+
+ {user?.wishlist?.length || 0} + Wishlisted +
+
- {wishlist.length === 0 ? ( -
-

Your wishlist is empty

- Browse Rooms -
- ) : ( -
- {wishlist.map(room => ( - - {room.title} -
-

{room.title}

-
- ₹{room.rent.toLocaleString()} - {room.area} -
+
+ + +
- - ))} -
- )} -
- )} +
+ + {/* Right Content: Tabs & Forms */} +
+
+ {/* Tab Headers */} +
+ {['personal', 'preferences'].map((tab) => ( + + ))} +
+ +
+
+

{activeTab} Details

+ +
+ + {/* Personal Info Tab */} + {activeTab === 'personal' && ( +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ )} + + {/* Preferences Tab */} + {activeTab === 'preferences' && ( +
+
+
+ +
+ + - + +
+
+
+ + +
+
- {activeTab === 'settings' && ( -
-

Settings

-
-
-

Change Password

-
-
- - setPasswordData({ ...passwordData, current: e.target.value })} /> -
-
- - setPasswordData({ ...passwordData, new: e.target.value })} /> -
-
- - setPasswordData({ ...passwordData, confirm: e.target.value })} /> -
- +
+ +
+ {lifestyleOptions.map(option => ( + + ))} +
+
+ +
+ +
+ {interestOptions.map(option => ( + + ))} +
+
+
+ )} + + +
+
-
-
- )} -
-
-
- - setShowDeleteModal(false)} title="Delete Room"> -
-

Are you sure you want to delete this room listing? This action cannot be undone.

-
- - -
+
- -
- ); + ); }; diff --git a/src/pages/RoomDetailsPage.jsx b/src/pages/RoomDetailsPage.jsx index 7f09dff..93b44e9 100644 --- a/src/pages/RoomDetailsPage.jsx +++ b/src/pages/RoomDetailsPage.jsx @@ -1,329 +1,553 @@ +import { useParams, Link, useNavigate } from 'react-router-dom'; +import { mockRooms } from '../data/mockData'; import { useState, useEffect } from 'react'; -import { useParams, useNavigate, Link } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; -import { Modal } from '../components/ui/Modal'; -import { Toast } from '../components/ui/Toast'; +import { Navbar } from '../components/Navbar'; export const RoomDetailsPage = () => { - const { id } = useParams(); - const navigate = useNavigate(); - const { user, addToWishlist, removeFromWishlist, addViewedRoom } = useAuth(); - const [room, setRoom] = useState(null); - const [currentImageIndex, setCurrentImageIndex] = useState(0); - const [showContactModal, setShowContactModal] = useState(false); - const [showReportModal, setShowReportModal] = useState(false); - const [toast, setToast] = useState(null); - const [isInWishlist, setIsInWishlist] = useState(false); + const { id } = useParams(); + const navigate = useNavigate(); + const room = mockRooms.find(r => r.id === id); + const { user } = useAuth(); + const [activeImage, setActiveImage] = useState(0); + const [showContactModal, setShowContactModal] = useState(false); + const [showReportModal, setShowReportModal] = useState(false); + const [isWishlisted, setIsWishlisted] = useState(false); + const [showVerificationWarning, setShowVerificationWarning] = useState(false); + const [showReviewModal, setShowReviewModal] = useState(false); + const [reviewRating, setReviewRating] = useState(0); + const [reviewText, setReviewText] = useState(''); + const [reviews, setReviews] = useState([ + { id: 1, name: 'Sarah Johnson', rating: 5, date: '2024-01-15', text: 'Amazing place! Very clean and the owner is super responsive. Highly recommend!', verified: true }, + { id: 2, name: 'Mike Chen', rating: 4, date: '2024-01-10', text: 'Good location and amenities. The room is exactly as described in the photos.', verified: true }, + { id: 3, name: 'Priya Sharma', rating: 5, date: '2023-12-28', text: 'Loved staying here! Great neighborhood and all facilities are well maintained.', verified: false } + ]); + const [requestSent, setRequestSent] = useState(false); + const [requestExpiry, setRequestExpiry] = useState(null); + const [requestStatus, setRequestStatus] = useState('pending'); // 'pending', 'approved', 'declined' - useEffect(() => { - // Load room data - const storedRooms = localStorage.getItem('roomatex_rooms'); - const rooms = storedRooms ? JSON.parse(storedRooms) : []; - const foundRoom = rooms.find(r => r.id === id); - - if (!foundRoom) { - navigate('/rooms'); - return; - } + // Form states + const [message, setMessage] = useState('Hi, I am interested in your room. Is it still available?'); + const [reportReason, setReportReason] = useState(''); - setRoom(foundRoom); - - // Mark as viewed - if (user) { - addViewedRoom(id); - } + useEffect(() => { + if (!user?.verified) { + setShowVerificationWarning(true); + } + }, [user]); - // Check if in wishlist - if (user && user.wishlist) { - setIsInWishlist(user.wishlist.includes(id)); - } - }, [id, navigate, user, addViewedRoom]); + useEffect(() => { + if (requestExpiry && Date.now() > requestExpiry) { + setRequestSent(false); + setRequestExpiry(null); + } + }, [requestExpiry]); - const handleWishlistToggle = () => { - if (isInWishlist) { - removeFromWishlist(id); - setIsInWishlist(false); - setToast({ message: 'Removed from wishlist', type: 'success' }); - } else { - addToWishlist(id); - setIsInWishlist(true); - setToast({ message: 'Added to wishlist', type: 'success' }); + if (!room) { + return ( +
+
+

Room not found

+ Back to Rooms +
+
+ ); } - }; - const handleReport = () => { - setShowReportModal(false); - setToast({ message: 'Report submitted successfully', type: 'success' }); - }; + const handleContactSubmit = (e) => { + e.preventDefault(); + setShowContactModal(false); + }; + + const handleSendRequest = () => { + const newRequest = { + id: Date.now(), + roomId: room.id, + roomTitle: room.title, + userId: user?.email, + userName: user?.name, + userVerified: user?.verified, + status: 'pending', + date: new Date().toISOString(), + ownerId: room.owner.email + }; + + const existingRequests = JSON.parse(localStorage.getItem('roomRequests') || '[]'); + localStorage.setItem('roomRequests', JSON.stringify([...existingRequests, newRequest])); + + setRequestSent(true); + setRequestStatus('pending'); + setRequestExpiry(Date.now() + 24 * 60 * 60 * 1000); + }; + + const handleReportSubmit = (e) => { + e.preventDefault(); + setShowReportModal(false); + }; + + const handleReviewSubmit = (e) => { + e.preventDefault(); + if (reviewRating === 0) { + return; + } + const newReview = { + id: reviews.length + 1, + name: user?.name || 'Anonymous', + rating: reviewRating, + date: new Date().toISOString().split('T')[0], + text: reviewText, + verified: user?.verified || false + }; + setReviews([newReview, ...reviews]); + setShowReviewModal(false); + setReviewRating(0); + setReviewText(''); + }; + + // Check if user can review (request approved) + const canReview = requestSent && requestStatus === 'approved'; - if (!room) { return ( -
-
-
- ); - } +
+ +
+
+
+ + + + + Back to Search + +
- return ( -
- {toast && setToast(null)} />} - -
- {/* Breadcrumb */} -
- Dashboard - / - Rooms - / - {room.title} -
+
+ {/* Main Content */} +
+ {/* Image Gallery */} +
+
+ {room.title} +
+
+ {room.images.map((img, idx) => ( + + ))} +
+
-
- {/* Main Content */} -
- {/* Image Gallery */} -
-
- {room.title} - {room.images.length > 1 && ( - <> - - - - )} -
-
- {room.images.map((img, idx) => ( - - ))} -
-
+ {/* Details */} +
+
+
+

{room.title}

+

+ + + + {room.area}, {room.city} +

+
+
+ {[...Array(5)].map((_, i) => ( + + + + ))} +
+ {room.rating || 4.5} + ({room.reviews || 12} reviews) +
+
+
+

₹{room.rent.toLocaleString()}

+

per month

+
+
- {/* Room Details */} -
-

{room.title}

- -
- - - - {room.area}, {room.city} -
+
-
- {room.roomType} - {room.furnishing} - {room.preferredTenant} -
+
+

Deposit

+

₹{room.deposit.toLocaleString()}

+
+
+

Available From

+

{new Date(room.availableFrom).toLocaleDateString()}

+
+
-
-

Description

-

{room.description}

-
+
+
+

Description

+

{room.description}

+
-
-

Amenities

-
- {room.amenities.map((amenity, idx) => ( -
- - - - {amenity} -
- ))} -
-
+
+

Amenities

+
+ {room.amenities.map((amenity) => ( +
+ + {amenity} +
+ ))} +
+
-
-

House Rules

-
    - {room.rules.map((rule, idx) => ( -
  • - - - - {rule} -
  • - ))} -
-
-
-
+
+

House Rules

+
    + {room.rules.map((rule, idx) => ( +
  • + + + + {rule} +
  • + ))} +
+
+
+
- {/* Sidebar */} -
- {/* Price Card */} -
-
-

Rent per month

-

₹{room.rent.toLocaleString()}

-

Deposit: ₹{room.deposit.toLocaleString()}

-
+ {/* Reviews Section */} +
+
+
+

Reviews

+

{reviews.length} reviews

+
+ {canReview && ( + + )} +
+ {!canReview && ( +
+

Only users with approved requests can leave reviews

+
+ )} +
+ {reviews.map((review) => ( +
+
+
+
+ {review.name[0]} +
+
+
+

{review.name}

+ {review.verified && ( + + + + )} +
+

{new Date(review.date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}

+
+
+
+ {[...Array(5)].map((_, i) => ( + + + + ))} +
+
+

{review.text}

+
+ ))} +
+
+
-
- + {/* Sidebar / Owner Card */} +
+
+

Contact Owner

- +
+
+ {room.owner.name[0]} +
+
+

+ {room.owner.name} + {room.owner.verified && ( + + + + )} +

+

Property Owner

+
+
- -
+
+ + +
-
-

Property Details

-
-
- Room Type - {room.roomType} -
-
- Furnishing - {room.furnishing} -
-
-
-
+ - {/* Owner Card */} -
-

Property Owner

-
-
- {room.owner.name.charAt(0)} -
-
-
-

{room.owner.name}

- {room.owner.verified && ( - - - - )} -
-

Property Owner

-
-
- {room.owner.verified && ( -
-
- - - - Verified Owner -
+

+ By contacting, you agree to our Terms of Service. +

+
+
+
- )} -
-
-
-
- {/* Contact Modal */} - setShowContactModal(false)} title="Contact Owner"> -
-

You can contact the property owner using the details below:

-
-
-

Name

-

{room.owner.name}

-
-
-

Phone Number

- - {room.owner.phone} - -
-
- -
-
+ {/* Contact Modal */} + {showContactModal && ( +
+
setShowContactModal(false)}>
+
+
+

Contact Owner

+ +
+ + {/* Request Status Section */} +
+ {!requestSent ? ( + + ) : ( +
+ {requestStatus === 'pending' && ( + <> + + Request Pending + + )} + {requestStatus === 'approved' && ( + <> + + Request Approved + + )} + {requestStatus === 'declined' && ( + <> + + Request Declined + + )} +
+ )} +
+ +
+
+
+ {room.owner.name[0]} +
+
+

+ {room.owner.name} + {room.owner.verified && ( + + + + )} +

+

Property Owner

+
+
+
+
+ +
+

Phone

+

{room.owner.phone}

+
+
+
+ +
+

Email

+

{room.owner.email}

+
+
+
+ +
+
+
+ )} - {/* Report Modal */} - setShowReportModal(false)} title="Report Listing"> -
-

Please select a reason for reporting this listing:

-
- {['Fake listing', 'Incorrect information', 'Inappropriate content', 'Already rented', 'Other'].map(reason => ( - - ))} -
-
- - -
+ {/* Report Modal */} + {showReportModal && ( +
+
setShowReportModal(false)}>
+
+
+

Report Listing

+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ )} + + {/* Review Modal */} + {showReviewModal && ( +
+
setShowReviewModal(false)}>
+
+
+

Write a Review

+ +
+
+
+ +
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+
+
+ + +
+
+ + +
+
+
+
+ )} + + {/* Verification Warning Modal */} + {showVerificationWarning && ( +
+
+
+
+
+ +
+

Verification Required

+

To access room details, you need to complete your personal details and verify your identity.

+
+ + +
+
+
+
+ )} +
- -
- ); + ); }; diff --git a/src/pages/RoommateProfilePage.jsx b/src/pages/RoommateProfilePage.jsx index 5057414..6602ba7 100644 --- a/src/pages/RoommateProfilePage.jsx +++ b/src/pages/RoommateProfilePage.jsx @@ -1,160 +1,107 @@ -import { useState, useEffect } from 'react'; import { useParams, Link } from 'react-router-dom'; import { mockRoommates } from '../data/mockData'; -import { Modal } from '../components/ui/Modal'; +import { Navbar } from '../components/Navbar'; export const RoommateProfilePage = () => { - const { id } = useParams(); - const [roommate, setRoommate] = useState(null); - const [showContactModal, setShowContactModal] = useState(false); + const { id } = useParams(); + const roommate = mockRoommates.find(r => r.id === id); - useEffect(() => { - const found = mockRoommates.find(rm => rm.id === id); - setRoommate(found); - }, [id]); + if (!roommate) { + return
Roommate not found
; + } - if (!roommate) { return ( -
-
-
- ); - } - - return ( -
-
-
- Dashboard - / - Roommate Profile -
+
+ +
+
+ ← Back to Search -
- {/* Header */} -
- {roommate.name} -
-
-

{roommate.name}

- {roommate.verified && ( - - - - )} -
-
- {roommate.age} years old - - {roommate.gender} - - {roommate.occupation} -
-
- - - - {roommate.city} -
-
- -
+
+
+
+
+
+ {roommate.name} +
+
+

+ {roommate.name} + {roommate.verified && ( + + + + )} +

+

{roommate.occupation} • {roommate.age} years old

+
+ +
- {/* About Section */} -
-

About

-

{roommate.about}

-
+
+
+
+

Details

+
+
+ Gender + {roommate.gender} +
+
+ City + {roommate.city} +
+
+
- {/* Looking For */} -
-

Looking For

-
-
- Room Type - {roommate.lookingFor.roomType} -
-
- Budget Range - ₹{roommate.lookingFor.budgetMin}k - ₹{roommate.lookingFor.budgetMax}k per month -
-
-
+
+

Looking For

+
+

Type: {roommate.lookingFor.roomType}

+

Budget: ₹{roommate.lookingFor.budgetMin} - {roommate.lookingFor.budgetMax}

+
+
+
- {/* Preferences */} -
-

Preferences

-
-
-

Gender Preference

- {roommate.preferences.genderPreference} -
-
-

Lifestyle

-

{roommate.preferences.lifestyle}

-
-
-

Interests

-
- {roommate.preferences.interests.map((interest, idx) => ( - {interest} - ))} -
-
-
-
+
+
+

About Me

+

{roommate.about}

+
- {/* Trust & Verification */} -
-

Trust & Verification

-
-
- - - -
-

- {roommate.verified ? 'Verified Profile' : 'Profile Not Verified'} -

-

- {roommate.verified - ? 'This user has been verified by RoomMateX' - : 'Verification pending'} -

+
+

Preferences

+
+
+
+ Roommate Gender + {roommate.preferences.genderPreference} +
+
+ Lifestyle + {roommate.preferences.lifestyle} +
+
+
+ Interests +
+ {roommate.preferences.interests.map(interest => ( + + {interest} + + ))} +
+
+
+
+
+
+
+
-
-
-
-
-
- - {/* Contact Modal */} - setShowContactModal(false)} title="Contact Roommate"> -
-

You can contact this roommate using the details below:

-
-
-

Name

-

{roommate.name}

-
-
-

Phone Number

- - {roommate.phone} -
-
-
-
-
- ); + ); }; diff --git a/src/pages/RoomsPage.jsx b/src/pages/RoomsPage.jsx index 80029f7..204cb75 100644 --- a/src/pages/RoomsPage.jsx +++ b/src/pages/RoomsPage.jsx @@ -1,248 +1,277 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Link } from 'react-router-dom'; -import { mockRooms, cities, amenitiesList, roomTypes } from '../data/mockData'; +import { Navbar } from '../components/Navbar'; +import { mockRooms, cities, amenitiesList } from '../data/mockData'; export const RoomsPage = () => { - const [rooms, setRooms] = useState([]); - const [filteredRooms, setFilteredRooms] = useState([]); - const [filters, setFilters] = useState({ - city: '', - minRent: '', - maxRent: '', - roomType: '', - amenities: [], - }); - - useEffect(() => { - // Load rooms from localStorage or use mock data - const storedRooms = localStorage.getItem('roomatex_rooms'); - const allRooms = storedRooms ? JSON.parse(storedRooms) : mockRooms; - - // Only show active rooms - const activeRooms = allRooms.filter(room => room.active); - setRooms(activeRooms); - setFilteredRooms(activeRooms); - - // Save to localStorage if not already there - if (!storedRooms) { - localStorage.setItem('roomatex_rooms', JSON.stringify(mockRooms)); - } - }, []); - - useEffect(() => { - let filtered = rooms; - - if (filters.city) { - filtered = filtered.filter(room => room.city === filters.city); - } - - if (filters.minRent) { - filtered = filtered.filter(room => room.rent >= parseInt(filters.minRent)); - } - - if (filters.maxRent) { - filtered = filtered.filter(room => room.rent <= parseInt(filters.maxRent)); - } - - if (filters.roomType) { - filtered = filtered.filter(room => room.roomType === filters.roomType); - } - - if (filters.amenities.length > 0) { - filtered = filtered.filter(room => - filters.amenities.every(amenity => room.amenities.includes(amenity)) - ); - } - - setFilteredRooms(filtered); - }, [filters, rooms]); - - const handleAmenityToggle = (amenity) => { - setFilters(prev => ({ - ...prev, - amenities: prev.amenities.includes(amenity) - ? prev.amenities.filter(a => a !== amenity) - : [...prev.amenities, amenity] - })); - }; - - const clearFilters = () => { - setFilters({ - city: '', - minRent: '', - maxRent: '', - roomType: '', - amenities: [], + const [filters, setFilters] = useState({ + search: '', + city: '', + rentMin: 0, + rentMax: 100000, + gender: '', + amenities: [], }); - }; - - return ( -
-
-
-

Find Your Perfect Room

-

Discover amazing living spaces in your preferred location

-
+ const [sortBy, setSortBy] = useState('newest'); -
- {/* Filters Sidebar */} -
-
-
-

Filters

- -
- -
- {/* City Filter */} -
- - -
+ const handleAmenityChange = (amenity) => { + setFilters(prev => { + if (prev.amenities.includes(amenity)) { + return { ...prev, amenities: prev.amenities.filter(a => a !== amenity) }; + } else { + return { ...prev, amenities: [...prev.amenities, amenity] }; + } + }); + }; - {/* Rent Range */} -
- -
- setFilters({ ...filters, minRent: e.target.value })} - className="input-field text-sm" - /> - setFilters({ ...filters, maxRent: e.target.value })} - className="input-field text-sm" - /> -
-
+ const filteredRooms = mockRooms.filter((room) => { + const matchesSearch = filters.search === '' || + room.title.toLowerCase().includes(filters.search.toLowerCase()) || + room.area.toLowerCase().includes(filters.search.toLowerCase()); + const matchesCity = filters.city === '' || room.city === filters.city; + const matchesRent = room.rent >= filters.rentMin && room.rent <= filters.rentMax; + const matchesGender = filters.gender === '' || room.preferredTenant === filters.gender || room.preferredTenant === 'Any'; + const matchesAmenities = filters.amenities.length === 0 || filters.amenities.every(a => room.amenities.includes(a)); - {/* Room Type */} -
- - -
+ return matchesSearch && matchesCity && matchesRent && matchesGender && matchesAmenities; + }).sort((a, b) => { + if (sortBy === 'priceLow') return a.rent - b.rent; + if (sortBy === 'priceHigh') return b.rent - a.rent; + return 0; + }); - {/* Amenities */} -
- -
- {amenitiesList.map(amenity => ( - - ))} -
-
-
-
-
- - {/* Rooms Grid */} -
-
-

- {filteredRooms.length} room{filteredRooms.length !== 1 ? 's' : ''} found -

+ return ( +
+ + {/* Mobile Filter Toggle (Visible only on small screens) */} +
+
- {filteredRooms.length === 0 ? ( -
- - - -

No rooms found

-

Try adjusting your filters to see more results

- -
- ) : ( -
- {filteredRooms.map(room => ( - -
- {room.title} - {room.owner.verified && ( -
- - - - Verified +
+ {/* Sidebar Filters (Desktop) */} + + + {/* Main Content */} +
+ {/* Search Bar */} +
+
+ setFilters({ ...filters, search: e.target.value })} + className="w-full px-5 py-3 pl-12 rounded-xl border border-gray-200 shadow-sm focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all" + /> + + +
- -
- - ))} -
- )} -
+ + {/* Results Count */} +
+

+ Showing {filteredRooms.length} results +

+
+ Sort by: + +
+
+ + {/* Grid */} +
+ {filteredRooms.map((room) => ( + +
+
+ {room.title} +
+ {room.owner.verified && ( +
+ + Verified +
+ )} +
+
+ +
+
+

{room.title}

+

+ + {room.area}, {room.city} +

+
+
+ {[...Array(5)].map((_, i) => ( + + + + ))} +
+ {room.rating || 4.5} + ({room.reviews || 12} reviews) +
+
+ +
+ {room.amenities.map((amenity, index) => ( + + {amenity} + + ))} +
+ +
+ + {room.preferredTenant} + +
+ +
+
+ ₹{room.rent.toLocaleString()}/mo +
+ +
+
+ + ))} + + {filteredRooms.length === 0 && ( +
+
+ +
+

No matches found

+

We couldn't find any rooms matching your filters.

+ +
+ )} +
+
+
-
-
- ); + ); }; diff --git a/src/pages/SignupPage.jsx b/src/pages/SignupPage.jsx index 3ddeebb..e20c19f 100644 --- a/src/pages/SignupPage.jsx +++ b/src/pages/SignupPage.jsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; +import { GUJARAT_CITIES } from '../constants/cities'; export const SignupPage = () => { const [formData, setFormData] = useState({ @@ -9,6 +10,7 @@ export const SignupPage = () => { password: '', phone: '', city: '', + role: '', agreedToTerms: false, }); const [showPassword, setShowPassword] = useState(false); @@ -20,7 +22,7 @@ export const SignupPage = () => { e.preventDefault(); setError(''); - if (!formData.name || !formData.email || !formData.password || !formData.phone || !formData.city) { + if (!formData.name || !formData.email || !formData.password || !formData.phone || !formData.city || !formData.role) { setError('Please fill in all fields'); return; } @@ -40,41 +42,134 @@ export const SignupPage = () => { }; return ( -
- {/* Left Side - Form */} -
-
- -

RoomMateX

+
+ {/* Left Side: Themed/Animated Content & Benefits */} +
+ {/* Landing Page Theme Background */} +
+
+
+
+
+ + {/* Floating Boxes */} +
+
+
+
+
+ +
+ +
+ + + +
+ RoomMateX -
-

Create Account

-

Join our community to find your perfect room

+ +

+ Join Our Trusted + Community +

+ +

+ Create an account to unlock full access to verified rooms and roommates. +

+ +
+
+
+ +
+
+

Verified Community

+

Every member is verified for security and trust.

+
+
+
+
+ +
+
+

Direct Communication

+

Connect directly with room owners and seekers.

+
+
+
+
+ +
+
+

Smart Listings

+

Advanced filters to find exactly what you need.

+
+
+
+
+
+ + {/* Right Side: Form */} +
+
+
+ +
+ + + +
+ RoomMateX + +
+ +
+

Create Account

+

Fill in your details to get started.

{error && ( -
+
+ + + {error}
)} -
-
- - setFormData({ ...formData, name: e.target.value })} - /> + +
+
+ + setFormData({ ...formData, name: e.target.value })} + /> +
+
+ + { + const value = e.target.value.replace(/\D/g, ''); + if (value.length <= 10) setFormData({ ...formData, phone: value }); + }} + /> +
-
- +
+ setFormData({ ...formData, email: e.target.value })} @@ -82,28 +177,28 @@ export const SignupPage = () => {
- -
+ +
setFormData({ ...formData, password: e.target.value })} /> @@ -111,83 +206,67 @@ export const SignupPage = () => {
- - { - const value = e.target.value.replace(/\D/g, ''); - if (value.length <= 10) { - setFormData({ ...formData, phone: value }); - } - }} - /> -
- -
- - City +
-
+
+ +
+ + +
+
+ +
+ + I agree to the Terms and Privacy Policy + + - -
-

- Already have an account?{' '} - - Sign in - -

-
-
-
- - {/* Right Side - Image */} -
-
-
-

Start Your Journey Today

-

- Create an account and discover amazing living spaces in your city. -

-
+

+ Already have an account?{' '} + Sign in +

- Happy Residents
); diff --git a/src/pages/VerificationPage.jsx b/src/pages/VerificationPage.jsx new file mode 100644 index 0000000..c441a2e --- /dev/null +++ b/src/pages/VerificationPage.jsx @@ -0,0 +1,354 @@ +import { useState } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; +import { Navbar } from '../components/Navbar'; + +export const VerificationPage = () => { + const { user, updateUser } = useAuth(); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const action = searchParams.get('action') || 'verify'; + const [step, setStep] = useState(1); + const [verificationType, setVerificationType] = useState(''); + const [otp, setOtp] = useState(''); + const [error, setError] = useState(''); + const [passwordData, setPasswordData] = useState({ + currentPassword: '', + newPassword: '', + confirmPassword: '' + }); + + const handleSendOtp = (type) => { + setVerificationType(type); + setStep(2); + }; + + const handleVerifyOtp = () => { + if (otp === '1234') { + setError(''); + setStep(3); + setTimeout(() => { + if (action === 'verify') { + updateUser({ verified: true }); + navigate('/profile'); + } else if (action === 'changePassword') { + setStep(4); + } else if (action === 'deleteAccount') { + navigate('/profile'); + } + }, 2000); + } else { + setError('Invalid OTP. Please use 1234.'); + } + }; + + const handlePasswordChange = (e) => { + const { name, value } = e.target; + setPasswordData(prev => ({ ...prev, [name]: value })); + }; + + const handleChangePassword = () => { + if (!passwordData.currentPassword || !passwordData.newPassword || !passwordData.confirmPassword) { + setError('Please fill all fields'); + return; + } + if (passwordData.newPassword !== passwordData.confirmPassword) { + setError('New passwords do not match'); + return; + } + if (passwordData.newPassword.length < 6) { + setError('Password must be at least 6 characters'); + return; + } + setError(''); + setStep(5); + setTimeout(() => { + navigate('/profile'); + }, 2000); + }; + + const getTitle = () => { + if (action === 'verify') return 'Verify Your Identity'; + if (action === 'changePassword') return 'Change Password'; + if (action === 'deleteAccount') return 'Delete Account'; + return 'Verification'; + }; + + const getDescription = () => { + if (action === 'verify') return 'Complete the verification process to access all features'; + if (action === 'changePassword') return 'Verify your identity to change your password'; + if (action === 'deleteAccount') return 'Verify your identity to delete your account'; + return ''; + }; + + const getSuccessMessage = () => { + if (action === 'verify') return "You're Verified!"; + if (action === 'deleteAccount') return 'Account Deleted!'; + return 'Verified!'; + }; + + return ( +
+ + +
+
+
+ {/* Left Side - Information */} +
+
+
+ {action === 'verify' && ( +
+ +
+ )} + {action === 'changePassword' && ( +
+ +
+ )} + {action === 'deleteAccount' && ( +
+ +
+ )} +

{getTitle()}

+

{getDescription()}

+
+ +
+
+
+ 1 +
+
+

Choose Method

+

Select email or phone verification

+
+
+
+
+ 2 +
+
+

Enter OTP

+

Verify with the code sent to you

+
+
+
+
+ 3 +
+
+

Complete

+

+ {action === 'verify' && 'Get verified badge on your profile'} + {action === 'changePassword' && 'Set your new password'} + {action === 'deleteAccount' && 'Confirm account deletion'} +

+
+
+
+
+
+ + {/* Right Side - Form */} +
+ + {step === 1 && ( +
+

Choose Verification Method

+ + +

Phone verification is currently under process. Please use email verification.

+ + +
+ )} + + {step === 2 && ( +
+

Enter OTP Code

+
+

We sent an OTP to your {verificationType}

+

{verificationType === 'email' ? user?.email : user?.phone || '+91 98765 XXXXX'}

+
+ +
+ + { setOtp(e.target.value); setError(''); }} + className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all text-center tracking-[1em] font-bold text-2xl" + placeholder="0000" + maxLength={4} + /> + {error &&

{error}

} +

Demo OTP: 1234

+
+ + + + +
+ )} + + {step === 3 && action !== 'changePassword' && ( +
+
+ +
+

{getSuccessMessage()}

+

+ {action === 'verify' && 'Your profile now has the blue tick badge.'} + {action === 'deleteAccount' && 'Your account has been permanently deleted.'} +

+

Redirecting to profile...

+
+ )} + + {step === 3 && action === 'changePassword' && ( +
+
+ +
+

Verified!

+

Now you can change your password.

+
+ )} + + {step === 4 && action === 'changePassword' && ( +
+

Set New Password

+ {error &&
{error}
} +
+ + { handlePasswordChange(e); setError(''); }} + className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all" + placeholder="Enter current password" + /> +
+
+ + { handlePasswordChange(e); setError(''); }} + className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all" + placeholder="Enter new password" + /> +
+
+ + { handlePasswordChange(e); setError(''); }} + className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all" + placeholder="Confirm new password" + /> +
+

Password must be at least 6 characters long

+ +
+ )} + + {step === 5 && action === 'changePassword' && ( +
+
+ +
+

Password Changed!

+

Your password has been updated successfully.

+

Redirecting to profile...

+
+ )} +
+
+
+
+
+ ); +}; diff --git a/src/pages/WishlistPage.jsx b/src/pages/WishlistPage.jsx index 96df739..b869dd6 100644 --- a/src/pages/WishlistPage.jsx +++ b/src/pages/WishlistPage.jsx @@ -1,57 +1,15 @@ -import { useState, useEffect } from 'react'; -import { Link } from 'react-router-dom'; -import { useAuth } from '../context/AuthContext'; +import { Navbar } from '../components/Navbar'; export const WishlistPage = () => { - const { user } = useAuth(); - const [wishlist, setWishlist] = useState([]); - - useEffect(() => { - const storedRooms = JSON.parse(localStorage.getItem('roomatex_rooms') || '[]'); - const wishlistIds = user.wishlist || []; - const wishlistRooms = storedRooms.filter(room => wishlistIds.includes(room.id)); - setWishlist(wishlistRooms); - }, [user]); - - return ( -
-
-

My Wishlist ({wishlist.length})

- - {wishlist.length === 0 ? ( -
- - - -

Your wishlist is empty

-

Start adding rooms to your wishlist to save them for later

- Browse Rooms -
- ) : ( -
- {wishlist.map(room => ( - -
-
- - - -
-
- {room.title} -
-

{room.title}

-

{room.description}

-
- ₹{room.rent.toLocaleString()} - {room.area} -
+ return ( +
+ +
+
+

Your Wishlist

+

Currently empty. Start adding rooms you like!

- - ))} -
- )} -
-
- ); +
+
+ ); }; diff --git a/src/routes/ProtectedRoute.jsx b/src/routes/ProtectedRoute.jsx deleted file mode 100644 index fae6cae..0000000 --- a/src/routes/ProtectedRoute.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Navigate, useLocation } from 'react-router-dom'; -import { useAuth } from '../context/AuthContext'; - -export const ProtectedRoute = ({ children }) => { - const { isAuthenticated, loading } = useAuth(); - const location = useLocation(); - - if (loading) { - return ( -
-
-
- ); - } - - if (!isAuthenticated) { - return ; - } - - return children; -}; - -export const AdminRoute = ({ children }) => { - const { isAdmin, loading } = useAuth(); - const location = useLocation(); - - if (loading) { - return ( -
-
-
- ); - } - - if (!isAdmin) { - return ; - } - - return children; -}; - -export const PublicRoute = ({ children }) => { - const { isAuthenticated, loading } = useAuth(); - - if (loading) { - return ( -
-
-
- ); - } - - if (isAuthenticated) { - return ; - } - - return children; -}; diff --git a/src/services/api.js b/src/services/api.js deleted file mode 100644 index ee1de91..0000000 --- a/src/services/api.js +++ /dev/null @@ -1,3 +0,0 @@ -const API_BASE = "http://127.0.0.1:8000/api/v1"; - -export default API_BASE; diff --git a/src/styles/index.css b/src/styles/index.css index 9401e18..2fd1d2e 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -12,38 +12,51 @@ .btn-primary { @apply bg-primary-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-primary-700 transition-colors duration-200 shadow-sm; } - + .btn-secondary { @apply bg-white text-primary-600 px-6 py-3 rounded-lg font-medium border border-primary-600 hover:bg-primary-50 transition-colors duration-200; } - + .btn-outline { @apply bg-transparent text-gray-700 px-6 py-3 rounded-lg font-medium border border-gray-300 hover:bg-gray-50 transition-colors duration-200; } - + .card { @apply bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden; } - + .input-field { @apply w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring-2 focus:ring-primary-200 outline-none transition-all duration-200; } - + .badge { @apply inline-flex items-center px-3 py-1 rounded-full text-xs font-medium; } - + .badge-success { @apply bg-green-100 text-green-800; } - + .badge-warning { @apply bg-amber-100 text-amber-800; } - + .badge-info { @apply bg-blue-100 text-blue-800; } + + /* Glassmorphism Utilities */ + .glass-card { + @apply bg-white/60 backdrop-blur-xl border border-white/60 shadow-lg shadow-blue-900/5 rounded-2xl; + } + + .glass-panel { + @apply bg-white/40 backdrop-blur-md border border-white/40 rounded-2xl; + } + + .glass-button { + @apply backdrop-blur-md border border-white/20 shadow-sm transition-all duration-300 rounded-xl; + } } /* Custom scrollbar */ @@ -66,19 +79,149 @@ } /* Animation utilities */ +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + transform: translateY(20px); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slideDown { + from { + transform: translateY(-20px); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slideInRight { + from { + transform: translateX(20px); + opacity: 0; + } + + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slideInLeft { + from { + transform: translateX(-20px); + opacity: 0; + } + + to { + transform: translateX(0); + opacity: 1; + } +} + .animate-fade-in { - animation: fadeIn 0.5s ease-in-out; + animation: fadeIn 0.8s ease-out forwards; } .animate-slide-up { - animation: slideUp 0.5s ease-out; + animation: slideUp 0.8s ease-out forwards; } .animate-slide-down { - animation: slideDown 0.3s ease-out; + animation: slideDown 0.6s ease-out forwards; +} + +.animate-slide-in-right { + animation: slideInRight 0.8s ease-out forwards; +} + +.animate-slide-in-left { + animation: slideInLeft 0.8s ease-out forwards; +} + +/* Animation Delays */ +.delay-100 { + animation-delay: 100ms; +} + +.delay-200 { + animation-delay: 200ms; +} + +.delay-300 { + animation-delay: 300ms; +} + +.delay-400 { + animation-delay: 400ms; +} + +.delay-500 { + animation-delay: 500ms; +} + +.delay-600 { + animation-delay: 600ms; +} + +.delay-700 { + animation-delay: 700ms; +} + +.delay-1000 { + animation-delay: 1000ms; } /* Transitions */ .transition-smooth { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } + +/* Floating boxes animation */ +@keyframes floatUp { + 0% { + transform: translateY(100vh) rotate(0deg); + opacity: 0; + } + + 10% { + opacity: 0.6; + } + + 90% { + opacity: 0.6; + } + + 100% { + transform: translateY(-100vh) rotate(360deg); + opacity: 0; + } +} + +.floating-box { + position: absolute; + background: linear-gradient(135deg, rgba(139, 92, 246, 0.1), rgba(99, 102, 241, 0.1)); + border: 1px solid rgba(139, 92, 246, 0.1); + animation: floatUp linear infinite; + pointer-events: none; + transform: translateY(100vh); + opacity: 0; +} \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 8e91a21..d6a8aea 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,54 +4,45 @@ export default { "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], + darkMode: 'class', theme: { extend: { colors: { primary: { - 50: '#f0f9ff', - 100: '#e0f2fe', - 200: '#bae6fd', - 300: '#7dd3fc', - 400: '#38bdf8', - 500: '#0ea5e9', - 600: '#0284c7', - 700: '#0369a1', - 800: '#075985', - 900: '#0c4a6e', + 50: '#eef2ff', + 100: '#e0e7ff', + 200: '#c7d2fe', + 300: '#a5b4fc', + 400: '#818cf8', + 500: '#6366f1', + 600: '#5750F1', + 700: '#4338ca', + 800: '#3730a3', + 900: '#312e81', }, - secondary: { - 50: '#f0fdfa', - 100: '#ccfbf1', - 200: '#99f6e4', - 300: '#5eead4', - 400: '#2dd4bf', - 500: '#14b8a6', - 600: '#0d9488', - 700: '#0f766e', - 800: '#115e59', - 900: '#134e4a', - }, - accent: { - 50: '#fffbeb', - 100: '#fef3c7', - 200: '#fde68a', - 300: '#fcd34d', - 400: '#fbbf24', - 500: '#f59e0b', - 600: '#d97706', - 700: '#b45309', - 800: '#92400e', - 900: '#78350f', + secondary: { // Keeping secondary as a neutral/complementary or just use gray/white + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e8f0', + 300: '#cbd5e1', + 400: '#94a3b8', + 500: '#64748b', + 600: '#475569', + 700: '#334155', + 800: '#1e293b', + 900: '#0f172a', }, }, fontFamily: { display: ['Poppins', 'sans-serif'], body: ['Inter', 'sans-serif'], }, + plugins: [], animation: { 'fade-in': 'fadeIn 0.5s ease-in-out', 'slide-up': 'slideUp 0.5s ease-out', 'slide-down': 'slideDown 0.3s ease-out', + 'blob': 'blob 7s infinite', }, keyframes: { fadeIn: { @@ -66,8 +57,13 @@ export default { '0%': { transform: 'translateY(-20px)', opacity: '0' }, '100%': { transform: 'translateY(0)', opacity: '1' }, }, + blob: { + '0%': { transform: 'translate(0px, 0px) scale(1)' }, + '33%': { transform: 'translate(30px, -50px) scale(1.1)' }, + '66%': { transform: 'translate(-20px, 20px) scale(0.9)' }, + '100%': { transform: 'translate(0px, 0px) scale(1)' }, + }, }, }, }, - plugins: [], } From 85db1d610a7bf1ada1da910e63eaa0689eb4a9f0 Mon Sep 17 00:00:00 2001 From: mahek-56 Date: Sun, 22 Feb 2026 21:10:53 +0530 Subject: [PATCH 08/16] update the frontend --- .env | 1 + .env.example | 1 + QUICK_START.md | 219 -------- package-lock.json | 705 +++++++++++++++++++++++- package.json | 5 +- src/App.jsx | 103 +++- src/components/AdminProtectedRoute.jsx | 13 + src/components/Navbar.jsx | 85 +-- src/components/ui/ImageCropModal.jsx | 147 +++++ src/context/AuthContext.jsx | 117 ++-- src/data/mockData.js | 24 +- src/pages/AddRoomPage.jsx | 163 ++++-- src/pages/AdminDashboardPage.jsx | 720 ++++++++++++++++++------- src/pages/ContactPage.jsx | 287 ++++++---- src/pages/DashboardPage.jsx | 410 ++++++-------- src/pages/EditRoomPage.jsx | 376 +++++++++++++ src/pages/LandingPage.jsx | 109 ++-- src/pages/LoginPage.jsx | 110 ++-- src/pages/ProfilePage.jsx | 210 ++++++-- src/pages/RoomDetailsPage.jsx | 675 ++++++++++++++--------- src/pages/RoommateProfilePage.jsx | 107 ---- src/pages/RoomsPage.jsx | 174 ++++-- src/pages/SignupPage.jsx | 31 +- src/pages/UserProfileViewPage.jsx | 259 +++++++++ src/pages/VerificationPage.jsx | 133 ++++- src/pages/WishlistPage.jsx | 142 ++++- src/services/adminService.js | 96 ++++ src/services/api.js | 134 +++++ src/services/authService.js | 97 ++++ src/services/dashboardService.js | 63 +++ src/services/propertyService.js | 154 ++++++ src/services/reviewService.js | 33 ++ src/services/userService.js | 54 ++ src/services/wishlistService.js | 31 ++ src/styles/index.css | 179 ++---- src/utils/imageUtils.js | 36 ++ vite.config.js | 2 +- 37 files changed, 4567 insertions(+), 1638 deletions(-) create mode 100644 .env create mode 100644 .env.example delete mode 100644 QUICK_START.md create mode 100644 src/components/AdminProtectedRoute.jsx create mode 100644 src/components/ui/ImageCropModal.jsx create mode 100644 src/pages/EditRoomPage.jsx delete mode 100644 src/pages/RoommateProfilePage.jsx create mode 100644 src/pages/UserProfileViewPage.jsx create mode 100644 src/services/adminService.js create mode 100644 src/services/api.js create mode 100644 src/services/authService.js create mode 100644 src/services/dashboardService.js create mode 100644 src/services/propertyService.js create mode 100644 src/services/reviewService.js create mode 100644 src/services/userService.js create mode 100644 src/services/wishlistService.js create mode 100644 src/utils/imageUtils.js diff --git a/.env b/.env new file mode 100644 index 0000000..c2058b5 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://localhost:8000 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c2058b5 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://localhost:8000 diff --git a/QUICK_START.md b/QUICK_START.md deleted file mode 100644 index d2b08a0..0000000 --- a/QUICK_START.md +++ /dev/null @@ -1,219 +0,0 @@ -# RoomMateX - Quick Start Guide - -## Installation & Setup (3 Steps) - -### Step 1: Install Dependencies -```bash -cd roomatex -npm install -``` - -### Step 2: Start Development Server -```bash -npm run dev -``` - -### Step 3: Open in Browser -Navigate to: `http://localhost:3000` - ---- - -## Demo Credentials - -### Admin Login -- **Email**: `admin@roomatex.com` -- **Password**: `admin123` -- **Access**: Full admin panel, user management, room management - -### Regular User Login -- **Email**: Any valid email (e.g., `user@test.com`) -- **Password**: Any password (e.g., `password`) -- **Access**: User dashboard, browse rooms, add listings, wishlist - ---- - -## Key Features to Test - -### As a User: -1. **Dashboard** - View recommended rooms and roommates -2. **Browse Rooms** - Filter by city, rent, amenities -3. **Room Details** - View full details, add to wishlist, contact owner -4. **List a Room** - Add your own room listing (min 3 images required) -5. **Profile** - Edit profile, view listings, manage wishlist -6. **Roommate Profiles** - View detailed roommate information - -### As an Admin: -1. **Admin Dashboard** - View platform statistics -2. **Manage Users** - Verify users, change roles -3. **Manage Rooms** - Activate/deactivate or delete listings - ---- - -## Test Scenarios - -### Scenario 1: New User Journey -1. Go to landing page (`/`) -2. Click "Sign Up" -3. Register with any credentials -4. Explore dashboard with recommendations -5. Browse rooms using filters -6. Add a room to wishlist -7. View room details and contact owner - -### Scenario 2: Room Owner Journey -1. Login as user -2. Go to "List Room" -3. Fill form with minimum 3 image URLs (use Unsplash) -4. Submit room listing -5. See listing in Dashboard → "My Listings" -6. Edit or delete the listing -7. View in "Find Rooms" - -### Scenario 3: Admin Journey -1. Login with admin credentials -2. View admin dashboard stats -3. Go to "Manage Users" -4. Verify a user account -5. Change user role -6. Go to "Manage Rooms" -7. Activate/deactivate rooms -8. Delete a room listing - ---- - -## Sample Image URLs for Testing - -Use these Unsplash URLs when adding rooms: - -``` -https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=800 -https://images.unsplash.com/photo-1502672260066-6bc35f0a1ec1?w=800 -https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800 -https://images.unsplash.com/photo-1493809842364-78817add7ffb?w=800 -https://images.unsplash.com/photo-1512918728675-ed5a9ecdebfd?w=800 -``` - ---- - -## Quick Navigation - -### Public Pages (No Login Required) -- `/` - Landing Page -- `/login` - Login -- `/signup` - Signup - -### User Pages (Login Required) -- `/dashboard` - User Dashboard -- `/rooms` - Browse Rooms -- `/room/:id` - Room Details -- `/roommate/:id` - Roommate Profile -- `/add-room` - List New Room -- `/profile` - User Profile -- `/wishlist` - Saved Rooms -- `/contact` - Contact Support - -### Admin Pages (Admin Only) -- `/admin` - Admin Dashboard -- `/admin/users` - Manage Users -- `/admin/rooms` - Manage Rooms - ---- - -## Troubleshooting - -### Port Already in Use -If port 3000 is busy: -```bash -# Kill the process using port 3000 -npx kill-port 3000 - -# Or specify a different port -npm run dev -- --port 3001 -``` - -### Dependencies Not Installing -```bash -# Clear npm cache -npm cache clean --force - -# Delete node_modules and reinstall -rm -rf node_modules package-lock.json -npm install -``` - -### Browser Not Opening Automatically -Manually navigate to: `http://localhost:3000` - ---- - -## Mobile Testing - -The app is fully responsive. To test on mobile: -1. Start dev server: `npm run dev` -2. Find your local IP: `ipconfig` (Windows) or `ifconfig` (Mac/Linux) -3. On mobile browser: `http://YOUR_IP:3000` - -Or use Chrome DevTools: -1. Open DevTools (F12) -2. Click device toolbar icon (Ctrl+Shift+M) -3. Select mobile device - ---- - -## Customization Tips - -### Change Colors -Edit `tailwind.config.js` → `theme.extend.colors` - -### Change Fonts -Edit `index.html` → Google Fonts link -Edit `tailwind.config.js` → `fontFamily` - -### Add More Mock Data -Edit `src/data/mockData.js` - ---- - -## Build for Production - -```bash -npm run build -``` - -Output: `dist/` folder ready for deployment - -Preview production build: -```bash -npm run preview -``` - ---- - -## Verification Checklist - -- [ ] All pages load without errors -- [ ] Login/Signup works -- [ ] Dashboard shows dynamic content -- [ ] Room filtering works -- [ ] Wishlist add/remove works -- [ ] Room CRUD operations work -- [ ] Profile editing works -- [ ] Admin panel accessible -- [ ] Mobile responsive -- [ ] No console errors - ---- - -## Tips - -1. **LocalStorage**: All data is saved in browser LocalStorage -2. **Mock Data**: Pre-loaded with 6 rooms and 6 roommates -3. **No Backend**: Everything runs client-side -4. **Images**: Uses Unsplash and Pravatar for demos -5. **Production Ready**: Clean code, no broken links - ---- - -**Need Help?** Check the main README.md for full documentation. - -**Ready to Deploy?** Run `npm run build` and deploy the `dist` folder to any static hosting (Vercel, Netlify, GitHub Pages). diff --git a/package-lock.json b/package-lock.json index d9be58c..a17f70b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,13 @@ "name": "roomatex", "version": "1.0.0", "dependencies": { + "axios": "^1.13.5", "lucide-react": "^0.563.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.20.0" + "react-easy-crop": "^5.5.6", + "react-router-dom": "^6.20.0", + "recharts": "^3.7.0" }, "devDependencies": { "@types/react": "^18.2.43", @@ -798,6 +801,42 @@ "node": ">= 8" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@remix-run/router": { "version": "1.23.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", @@ -1164,6 +1203,18 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1209,6 +1260,69 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1220,14 +1334,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -1245,6 +1359,12 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1294,6 +1414,12 @@ "dev": true, "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.24", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", @@ -1331,6 +1457,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.9.19", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", @@ -1402,6 +1539,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1471,6 +1621,27 @@ "node": ">= 6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1505,9 +1676,130 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1526,6 +1818,21 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1540,6 +1847,20 @@ "dev": true, "license": "MIT" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.286", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", @@ -1547,6 +1868,61 @@ "dev": true, "license": "ISC" }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", + "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1596,6 +1972,12 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1649,6 +2031,42 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -1682,7 +2100,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1698,6 +2115,43 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1711,11 +2165,49 @@ "node": ">=10.13.0" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -1724,6 +2216,25 @@ "node": ">= 0.4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1880,6 +2391,15 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1904,6 +2424,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1959,6 +2500,12 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-wheel": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz", + "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==", + "license": "BSD-3-Clause" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2190,6 +2737,12 @@ "dev": true, "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2238,6 +2791,51 @@ "react": "^18.3.1" } }, + "node_modules/react-easy-crop": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.5.6.tgz", + "integrity": "sha512-Jw3/ozs8uXj3NpL511Suc4AHY+mLRO23rUgipXvNYKqezcFSYHxe4QXibBymkOoY6oOtLVMPO2HNPRHYvMPyTw==", + "license": "MIT", + "dependencies": { + "normalize-wheel": "^1.0.1", + "tslib": "^2.0.1" + }, + "peerDependencies": { + "react": ">=16.4.0", + "react-dom": ">=16.4.0" + } + }, + "node_modules/react-is": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -2303,6 +2901,58 @@ "node": ">=8.10.0" } }, + "node_modules/recharts": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz", + "integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT", + "peer": true + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -2530,6 +3180,12 @@ "node": ">=0.8" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -2599,6 +3255,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -2630,6 +3292,15 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2637,6 +3308,28 @@ "dev": true, "license": "MIT" }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", diff --git a/package.json b/package.json index 0225fe7..91aa37b 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,13 @@ "preview": "vite preview" }, "dependencies": { + "axios": "^1.13.5", "lucide-react": "^0.563.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.20.0" + "react-easy-crop": "^5.5.6", + "react-router-dom": "^6.20.0", + "recharts": "^3.7.0" }, "devDependencies": { "@types/react": "^18.2.43", diff --git a/src/App.jsx b/src/App.jsx index cf201b7..de4939a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,54 +1,113 @@ -import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { AuthProvider } from './context/AuthContext'; import { ThemeProvider } from './context/ThemeContext'; -import { PageBackground } from './components/PageBackground'; import { ProtectedRoute } from './components/ProtectedRoute'; +import { AdminProtectedRoute } from './components/AdminProtectedRoute'; // Pages import { LandingPage } from './pages/LandingPage'; import { LoginPage } from './pages/LoginPage'; import { SignupPage } from './pages/SignupPage'; -import { DashboardPage } from './pages/DashboardPage'; +import { VerificationPage } from './pages/VerificationPage'; import { RoomsPage } from './pages/RoomsPage'; import { RoomDetailsPage } from './pages/RoomDetailsPage'; import { AddRoomPage } from './pages/AddRoomPage'; +import { EditRoomPage } from './pages/EditRoomPage'; +import { DashboardPage } from './pages/DashboardPage'; import { ProfilePage } from './pages/ProfilePage'; -import { RoommateProfilePage } from './pages/RoommateProfilePage'; import { WishlistPage } from './pages/WishlistPage'; import { ContactPage } from './pages/ContactPage'; -import { CompleteProfilePage } from './pages/CompleteProfilePage'; import { AdminDashboardPage } from './pages/AdminDashboardPage'; -import { VerificationPage } from './pages/VerificationPage'; +import { CompleteProfilePage } from './pages/CompleteProfilePage'; +import { UserProfileViewPage } from './pages/UserProfileViewPage'; function App() { return ( - - + {/* Public Routes */} } /> } /> } /> + } /> + } /> + } /> + } /> + + {/* Protected Routes */} + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> - {/* Private/Protected Routes */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + {/* Admin Routes - Protected with AdminProtectedRoute */} + + + + } + /> - {/* Fallback - redirect to landing */} + {/* Catch all - redirect to home */} } /> - + ); diff --git a/src/components/AdminProtectedRoute.jsx b/src/components/AdminProtectedRoute.jsx new file mode 100644 index 0000000..a704093 --- /dev/null +++ b/src/components/AdminProtectedRoute.jsx @@ -0,0 +1,13 @@ +import { Navigate } from 'react-router-dom'; +import { adminService } from '../services/adminService'; + +export const AdminProtectedRoute = ({ children }) => { + const isAdmin = adminService.isAdmin(); + + if (!isAdmin) { + // Redirect to login if not admin + return ; + } + + return children; +}; diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 79e8707..e6681db 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -2,6 +2,7 @@ import { Link, useNavigate } from 'react-router-dom'; import { useTheme } from '../context/ThemeContext'; import { useAuth } from '../context/AuthContext'; import { useState } from 'react'; +import { getImageUrl } from '../utils/imageUtils'; export const Navbar = () => { const { theme } = useTheme(); @@ -15,67 +16,75 @@ export const Navbar = () => { }; return ( -
-