├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── admin ├── .env.example ├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose.yml ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ └── vite.svg ├── render.yaml ├── src │ ├── App.jsx │ ├── assets │ │ └── administrator.png │ ├── components │ │ ├── ErrorFallback.jsx │ │ ├── Navbar.jsx │ │ ├── ProtectedRoute.jsx │ │ └── login.jsx │ ├── index.css │ ├── main.jsx │ └── pages │ │ ├── Add.jsx │ │ ├── Appointments.jsx │ │ ├── Dashboard.jsx │ │ ├── List.jsx │ │ └── Update.jsx ├── tailwind.config.js ├── vercel.json └── vite.config.js ├── backend ├── .env.example ├── .gitignore ├── Dockerfile ├── config │ ├── config.js │ ├── imagekit.js │ ├── mongodb.js │ └── nodemailer.js ├── controller │ ├── Usercontroller.js │ ├── adminController.js │ ├── appointmentController.js │ ├── formcontroller.js │ ├── newscontroller.js │ ├── productcontroller.js │ └── propertyController.js ├── docker-compose.yml ├── email.js ├── middleware │ ├── authmiddleware.js │ ├── multer.js │ └── statsMiddleware.js ├── models │ ├── Usermodel.js │ ├── appointmentModel.js │ ├── formmodel.js │ ├── newsmodel.js │ ├── propertymodel.js │ └── statsModel.js ├── package-lock.json ├── package.json ├── render.yaml ├── routes │ ├── ProductRouter.js │ ├── UserRoute.js │ ├── adminRoute.js │ ├── appointmentRoute.js │ ├── formrouter.js │ ├── newsRoute.js │ └── propertyRoutes.js ├── server.js ├── services │ ├── aiService.js │ └── firecrawlService.js └── vercel.json ├── frontend ├── .env.example ├── .gitignore ├── README.md ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── robots.txt │ └── sitemap.xml ├── src │ ├── App.jsx │ ├── assets │ │ ├── Amazon_logo.svg │ │ ├── Booking.com_logo.svg │ │ ├── Google_2015_logo.svg │ │ ├── airbnb.svg │ │ ├── blogdata.js │ │ ├── data │ │ │ └── teammemberdata.js │ │ ├── featuredata.js │ │ ├── home-regular-24.png │ │ ├── images │ │ │ ├── aayush.png │ │ │ ├── about_enhanced.jpg │ │ │ ├── heroimage.png │ │ │ └── nakul.png │ │ ├── logo.js │ │ ├── microsoft.svg │ │ ├── properties.js │ │ ├── stepsdata.js │ │ ├── testimonialdata.js │ │ └── user_logo.png │ ├── components │ │ ├── Blog.jsx │ │ ├── Companies.jsx │ │ ├── Features.jsx │ │ ├── Hero.jsx │ │ ├── Navbar.jsx │ │ ├── Notfound.jsx │ │ ├── SEO │ │ │ ├── AiHubSEO.jsx │ │ │ └── StructuredData.jsx │ │ ├── Steps.jsx │ │ ├── aboutus │ │ │ ├── Benefit.jsx │ │ │ ├── Contup.jsx │ │ │ ├── Hero.jsx │ │ │ ├── Milestone.jsx │ │ │ ├── Mission.jsx │ │ │ ├── Team.jsx │ │ │ ├── Teammember.jsx │ │ │ └── Values.jsx │ │ ├── ai │ │ │ ├── AnalysisDisplay.jsx │ │ │ ├── Header.jsx │ │ │ ├── LocationTrends.jsx │ │ │ ├── PropertyCard.jsx │ │ │ └── SearchForm.jsx │ │ ├── contact │ │ │ ├── ContactHero.jsx │ │ │ ├── ContactInfo.jsx │ │ │ ├── Formhandle.jsx │ │ │ ├── InfoItem.jsx │ │ │ ├── contactForm.jsx │ │ │ └── useContactform.jsx │ │ ├── footer.jsx │ │ ├── forgetpassword.jsx │ │ ├── login.jsx │ │ ├── properties │ │ │ ├── Filtersection.jsx │ │ │ ├── Propertiespage.jsx │ │ │ ├── Propertycard.jsx │ │ │ ├── ScheduleViewing.jsx │ │ │ ├── Searchbar.jsx │ │ │ └── propertydetail.jsx │ │ ├── propertiesshow.jsx │ │ ├── resetpassword.jsx │ │ ├── signup.jsx │ │ └── testimonial.jsx │ ├── context │ │ └── AuthContext.jsx │ ├── index.css │ ├── main.jsx │ ├── pages │ │ ├── About.jsx │ │ ├── Aiagent.jsx │ │ ├── Contact.jsx │ │ ├── Home.jsx │ │ └── Properties.jsx │ ├── services │ │ └── api.js │ └── styles │ │ └── auth.js ├── tailwind.config.cjs ├── vercel.json └── vite.config.js ├── package-lock.json └── package.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in the repo 2 | * @AAYUSH412 3 | 4 | # Backend code owners 5 | /backend/ @AAYUSH412 6 | 7 | # Frontend code owners 8 | /frontend/ @AAYUSH412 9 | 10 | # Admin dashboard code owners 11 | /admin/ @AAYUSH412 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve BuildEstate 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Environment (please complete the following information):** 26 | - Device: [e.g., Desktop, iPhone 12] 27 | - OS: [e.g., Windows 10, iOS 16] 28 | - Browser: [e.g., Chrome 112, Safari 16] 29 | - BuildEstate Version: [e.g., 1.0.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for BuildEstate 4 | title: "[FEATURE] " 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | 21 | **Impact & Scope** 22 | How would this feature benefit users? Does it affect a small or large part of the application? -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## Related Issue 5 | 6 | 7 | 8 | ## Motivation and Context 9 | 10 | 11 | ## How Has This Been Tested? 12 | 13 | 14 | 15 | ## Screenshots (if appropriate) 16 | 17 | ## Types of changes 18 | 19 | - [ ] Bug fix (non-breaking change which fixes an issue) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 22 | 23 | ## Checklist 24 | 25 | - [ ] My code follows the code style of this project. 26 | - [ ] My change requires a change to the documentation. 27 | - [ ] I have updated the documentation accordingly. 28 | - [ ] I have added tests to cover my changes. 29 | - [ ] All new and existing tests passed. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Logs 8 | logs/ 9 | *.log 10 | *.log.* 11 | 12 | # Dependency directories 13 | node_modules/ 14 | jspm_packages/ 15 | 16 | # Optional npm cache directory 17 | .npm 18 | 19 | # Optional eslint cache 20 | .eslintcache 21 | 22 | # Optional REPL history 23 | .node_repl_history 24 | 25 | # dotenv environment variables file 26 | .env 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # MacOS 33 | .DS_Store 34 | 35 | # Editor directories and files 36 | .idea/ 37 | .vscode/ 38 | *.suo 39 | *.ntvs* 40 | *.njsproj 41 | *.sln 42 | *.sw? 43 | 44 | # Build directories 45 | build/ 46 | dist/ 47 | 48 | # Coverage directory used by tools like istanbul 49 | coverage/ 50 | 51 | # Miscellaneous 52 | *.pem 53 | *.p12 54 | *.key 55 | *.crt 56 | *.csr 57 | *.pfx 58 | 59 | # React Native 60 | .expo/ 61 | .expo-shared/ 62 | 63 | # Parcel-bundler cache (https://parceljs.org/) 64 | .cache 65 | .parcel-cache 66 | 67 | # SvelteKit build / generated files 68 | .svelte-kit 69 | 70 | # Storybook files 71 | out/ 72 | .storybook-out/ 73 | 74 | # Do NOT ignore these important files 75 | !package-lock.json 76 | !yarn.lock -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Project maintainers are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all community spaces, and also applies when 49 | an individual is officially representing the community in public spaces. 50 | 51 | ## Enforcement 52 | 53 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 54 | reported to the community leaders responsible for enforcement at 55 | aayushvaghela12@gmail.com. 56 | All complaints will be reviewed and investigated promptly and fairly. 57 | 58 | ## Attribution 59 | 60 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), 61 | version 2.0, available at 62 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to BuildEstate 2 | 3 | Thank you for considering contributing to BuildEstate! This document outlines the process for contributing to this project. 4 | 5 | ## Code of Conduct 6 | 7 | By participating in this project, you agree to maintain a respectful and inclusive environment for everyone. Please be courteous and respectful in your interactions. 8 | 9 | ## How to Contribute 10 | 11 | There are several ways you can contribute to the BuildEstate project: 12 | 13 | 1. **Reporting Bugs**: Open an issue describing the bug, how to reproduce it, and any potential solutions. 14 | 2. **Suggesting Features**: Open an issue describing your feature idea and how it would benefit the project. 15 | 3. **Submitting Code**: Make a pull request with your code changes. 16 | 4. **Improving Documentation**: Help improve the documentation through pull requests. 17 | 18 | ## Development Process 19 | 20 | ### Setup Local Environment 21 | 22 | 1. Fork the repository 23 | 2. Clone your fork: 24 | ```bash 25 | git clone https://github.com/AAYUSH412/Real-Estate-Website.git 26 | cd buildEstate 27 | ``` 28 | 3. Install dependencies: 29 | ```bash 30 | # Install backend dependencies 31 | cd backend 32 | npm install 33 | 34 | # Install frontend dependencies 35 | cd ../frontend 36 | npm install 37 | 38 | # Install admin dependencies 39 | cd ../admin 40 | npm install 41 | ``` 42 | 4. Create your `.env.local` files as described in the [README.md](README.md) 43 | 44 | ### Making Changes 45 | 46 | 1. Create a new branch: 47 | ```bash 48 | git checkout -b feature/your-feature-name 49 | ``` 50 | 2. Make your changes 51 | 3. Test your changes thoroughly 52 | 4. Commit your changes: 53 | ```bash 54 | git commit -m "Brief description of changes" 55 | ``` 56 | 5. Push to your fork: 57 | ```bash 58 | git push origin feature/your-feature-name 59 | ``` 60 | 6. Create a pull request from your branch to the main repository 61 | 62 | ## Pull Request Guidelines 63 | 64 | - Ensure your code follows the existing style patterns and practices 65 | - Include clear descriptions of the changes made 66 | - Update documentation if necessary 67 | - Make sure all tests pass 68 | - Keep pull requests focused on a single concern/feature 69 | 70 | ## Code Style 71 | 72 | - Follow the existing code style and formatting 73 | - Use meaningful variable and function names 74 | - Write comments for complex logic 75 | - Include JSDoc comments for functions 76 | 77 | ## Testing 78 | 79 | - Add appropriate tests for new features 80 | - Ensure all existing tests pass 81 | - Manual testing should be performed on different devices and browsers 82 | 83 | ## Documentation 84 | 85 | - Update the README.md if necessary 86 | - Document API changes in the appropriate documentation files 87 | - Keep code comments up-to-date 88 | 89 | ## Questions? 90 | 91 | If you have any questions or need help, feel free to open an issue or contact the maintainers. 92 | 93 | Thank you for contributing to BuildEstate! -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Aayush Vaghela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are currently being supported with security updates. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.0.x | :white_check_mark: | 10 | | < 1.0 | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | We take the security of BuildEstate seriously. If you believe you've found a security vulnerability, please follow these steps: 15 | 16 | 1. **Do not disclose the vulnerability publicly** 17 | 2. **Email the details to aayushvaghela12@gmail.com** 18 | - Include a description of the vulnerability 19 | - Steps to reproduce the issue 20 | - Potential impact 21 | - Suggestions for remediation if available 22 | 23 | ## What to expect 24 | - We will acknowledge receipt of your report within 48 hours 25 | - We will provide an initial assessment of the report within 5 business days 26 | - We will keep you informed about our progress in addressing the issue 27 | - After the issue is resolved, we may publicly acknowledge your responsible disclosure 28 | 29 | Thank you for helping keep BuildEstate and our users safe! -------------------------------------------------------------------------------- /admin/.env.example: -------------------------------------------------------------------------------- 1 | VITE_BACKEND_URL=http://localhost:4000 -------------------------------------------------------------------------------- /admin/.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Logs 8 | logs/ 9 | *.log 10 | *.log.* 11 | 12 | # Dependency directories 13 | node_modules/ 14 | jspm_packages/ 15 | 16 | # Optional npm cache directory 17 | .npm 18 | 19 | # Optional eslint cache 20 | .eslintcache 21 | 22 | # Optional REPL history 23 | .node_repl_history 24 | 25 | # dotenv environment variables file 26 | .env 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # MacOS 33 | .DS_Store 34 | 35 | # Editor directories and files 36 | .idea/ 37 | .vscode/ 38 | *.suo 39 | *.ntvs* 40 | *.njsproj 41 | *.sln 42 | *.sw? 43 | 44 | # Build directories 45 | build/ 46 | dist/ 47 | 48 | # Coverage directory used by tools like istanbul 49 | coverage/ 50 | 51 | # Miscellaneous 52 | *.pem 53 | *.p12 54 | *.key 55 | *.crt 56 | *.csr 57 | *.pfx 58 | 59 | # React Native 60 | .expo/ 61 | .expo-shared/ 62 | 63 | # Parcel-bundler cache (https://parceljs.org/) 64 | .cache 65 | .parcel-cache 66 | 67 | # SvelteKit build / generated files 68 | .svelte-kit 69 | 70 | # Storybook files 71 | out/ 72 | .storybook-out/ -------------------------------------------------------------------------------- /admin/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image 2 | FROM node:18-alpine 3 | 4 | # Set working directory 5 | WORKDIR /app 6 | 7 | # Copy package files 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install 12 | 13 | # Copy project files 14 | COPY . . 15 | 16 | # Build the app 17 | RUN npm run build 18 | 19 | # Start command 20 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /admin/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /admin/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | admin: 5 | build: . 6 | volumes: 7 | - .:/app 8 | - /app/node_modules -------------------------------------------------------------------------------- /admin/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Admin Page 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "description": "BuildEstate admin dashboard for property management", 7 | "author": "Aayush Vaghela", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/AAYUSH412/Real-Estate-Website" 12 | }, 13 | "scripts": { 14 | "dev": "vite", 15 | "build": "vite build", 16 | "lint": "eslint .", 17 | "preview": "vite preview" 18 | }, 19 | "dependencies": { 20 | "axios": "^1.7.9", 21 | "chart.js": "^4.4.7", 22 | "flowbite-react": "^0.10.2", 23 | "framer-motion": "^12.4.2", 24 | "lucide-react": "^0.469.0", 25 | "prop-types": "^15.8.1", 26 | "react": "^18.3.1", 27 | "react-chartjs-2": "^5.3.0", 28 | "react-dom": "^18.3.1", 29 | "react-error-boundary": "^5.0.0", 30 | "react-hot-toast": "^2.5.1", 31 | "react-icon": "^1.0.0", 32 | "react-icons": "^5.4.0", 33 | "react-router-dom": "^7.1.1", 34 | "react-toastify": "^11.0.2" 35 | }, 36 | "devDependencies": { 37 | "@eslint/js": "^9.17.0", 38 | "@types/react": "^18.3.18", 39 | "@types/react-dom": "^18.3.5", 40 | "@vitejs/plugin-react": "^4.3.4", 41 | "autoprefixer": "^10.4.20", 42 | "eslint": "^9.17.0", 43 | "eslint-plugin-react": "^7.37.2", 44 | "eslint-plugin-react-hooks": "^5.0.0", 45 | "eslint-plugin-react-refresh": "^0.4.16", 46 | "globals": "^15.14.0", 47 | "postcss": "^8.4.49", 48 | "tailwindcss": "^3.4.17", 49 | "vite": "^6.0.5" 50 | } 51 | } -------------------------------------------------------------------------------- /admin/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /admin/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admin/render.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | - type: web 3 | name: real-estate-admin 4 | env: static 5 | buildCommand: npm install && npm run build 6 | staticPublishPath: ./dist 7 | envVars: 8 | - key: VITE_BACKEND_URL 9 | value: https://real-estate-website-backend-fullcode.onrender.com 10 | routes: 11 | - type: rewrite 12 | source: /* 13 | destination: /index.html -------------------------------------------------------------------------------- /admin/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { Routes, Route, Navigate } from "react-router-dom"; 2 | import { Toaster } from "react-hot-toast"; 3 | import { ErrorBoundary } from "react-error-boundary"; 4 | import { motion, AnimatePresence } from "framer-motion"; 5 | 6 | // Components 7 | import Navbar from "./components/Navbar"; 8 | import ProtectedRoute from "./components/ProtectedRoute"; 9 | import ErrorFallback from "./components/ErrorFallback"; 10 | 11 | // Pages 12 | import Login from "./components/login"; 13 | import Dashboard from "./pages/Dashboard"; 14 | import List from "./pages/List"; 15 | import Add from "./pages/Add"; 16 | import Update from "./pages/Update"; 17 | import Appointments from "./pages/Appointments"; 18 | 19 | // Config 20 | export const backendurl = import.meta.env.VITE_BACKEND_URL; 21 | 22 | // Page transition variants 23 | const pageVariants = { 24 | initial: { opacity: 0, y: 20 }, 25 | animate: { opacity: 1, y: 0 }, 26 | exit: { opacity: 0, y: -20 } 27 | }; 28 | 29 | const App = () => { 30 | return ( 31 | window.location.reload()} 34 | > 35 |
36 | 37 | 38 | 45 | 46 | {/* Public Routes */} 47 | } /> 48 | } /> 49 | 50 | {/* Protected Routes */} 51 | }> 52 | } /> 53 | } /> 54 | } /> 55 | } /> 56 | } /> 57 | 58 | 59 | {/* 404 Route */} 60 | } /> 61 | 62 | 63 | 64 | 65 | {/* Toast Notifications */} 66 | 76 |
77 |
78 | ); 79 | }; 80 | 81 | export default App; -------------------------------------------------------------------------------- /admin/src/assets/administrator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AAYUSH412/Real-Estate-Website/24a68ddd4502b6680132ab32ee6b25eac5844a95/admin/src/assets/administrator.png -------------------------------------------------------------------------------- /admin/src/components/ErrorFallback.jsx: -------------------------------------------------------------------------------- 1 | import { AlertCircle } from "lucide-react"; 2 | import Proptypes from "prop-types"; 3 | 4 | const ErrorFallback = ({ error, resetErrorBoundary }) => { 5 | return ( 6 |
7 |
8 | 9 |

10 | Something went wrong 11 |

12 |

{error.message}

13 | 20 |
21 |
22 | ); 23 | }; 24 | 25 | ErrorFallback.propTypes = { 26 | error: Proptypes.object.isRequired, 27 | resetErrorBoundary: Proptypes.func.isRequired 28 | }; 29 | 30 | 31 | export default ErrorFallback; -------------------------------------------------------------------------------- /admin/src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Link, useNavigate, useLocation } from 'react-router-dom'; 3 | import { motion } from 'framer-motion'; 4 | import { 5 | Home, 6 | List, 7 | PlusSquare, 8 | Calendar, 9 | Menu, 10 | X, 11 | LogOut, 12 | LayoutDashboard, 13 | Database 14 | } from 'lucide-react'; 15 | 16 | const Navbar = () => { 17 | const navigate = useNavigate(); 18 | const location = useLocation(); 19 | const [isMenuOpen, setIsMenuOpen] = useState(false); 20 | 21 | const isActive = (path) => { 22 | return location.pathname === path; 23 | }; 24 | 25 | const handleLogout = () => { 26 | localStorage.removeItem('token'); 27 | localStorage.removeItem('isAdmin'); 28 | navigate('/login'); 29 | }; 30 | 31 | const toggleMenu = () => { 32 | setIsMenuOpen(!isMenuOpen); 33 | }; 34 | 35 | const navItems = [ 36 | { path: '/dashboard', label: 'Dashboard', icon: LayoutDashboard }, 37 | { path: '/list', label: 'Properties', icon: List }, 38 | { path: '/add', label: 'Add Property', icon: PlusSquare }, 39 | { path: '/appointments', label: 'Appointments', icon: Calendar }, 40 | ]; 41 | 42 | return ( 43 |
44 |
45 |
46 | {/* Logo */} 47 | 48 |
49 | 50 |
51 | Admin Panel 52 | 53 | 54 | {/* Desktop Navigation */} 55 | 83 | 84 | {/* Mobile Menu Button */} 85 |
86 | 96 |
97 |
98 |
99 | 100 | {/* Mobile Menu */} 101 | {isMenuOpen && ( 102 | 108 |
109 | {navItems.map((item) => ( 110 | setIsMenuOpen(false)} 119 | > 120 |
121 | 122 | {item.label} 123 |
124 | 125 | ))} 126 | 127 | 136 |
137 |
138 | )} 139 |
140 | ); 141 | }; 142 | 143 | export default Navbar; -------------------------------------------------------------------------------- /admin/src/components/ProtectedRoute.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navigate, Outlet } from 'react-router-dom'; 3 | 4 | const ProtectedRoute = () => { 5 | const token = localStorage.getItem('token'); 6 | const isAdmin = localStorage.getItem('isAdmin'); 7 | 8 | // Check both token and admin status 9 | const isAuthenticated = token && isAdmin === 'true'; 10 | 11 | // Verify token expiration 12 | const isTokenExpired = () => { 13 | if (!token) return true; 14 | try { 15 | const tokenData = JSON.parse(atob(token.split('.')[1])); 16 | return tokenData.exp * 1000 < Date.now(); 17 | } catch (error) { 18 | console.error('Error verifying token:', error); 19 | return true; 20 | } 21 | }; 22 | 23 | if (!isAuthenticated || isTokenExpired()) { 24 | // Clean up storage on invalid auth 25 | localStorage.removeItem('token'); 26 | localStorage.removeItem('isAdmin'); 27 | return ; 28 | } 29 | 30 | // Return Outlet for nested routes 31 | return ; 32 | }; 33 | 34 | export default ProtectedRoute; -------------------------------------------------------------------------------- /admin/src/components/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { FaEye, FaEyeSlash } from "react-icons/fa"; 4 | import { toast } from "react-hot-toast"; 5 | import axios from "axios"; 6 | 7 | const backendUrl = import.meta.env.VITE_BACKEND_URL; 8 | 9 | const Login = () => { 10 | const [email, setEmail] = useState(""); 11 | const [password, setPassword] = useState(""); 12 | const [showPassword, setShowPassword] = useState(false); 13 | const [loading, setLoading] = useState(false); 14 | const navigate = useNavigate(); 15 | 16 | const handleSubmit = async (e) => { 17 | e.preventDefault(); 18 | setLoading(true); 19 | 20 | try { 21 | // Change the endpoint to /api/users/admin for admin login 22 | const response = await axios.post(`${backendUrl}/api/users/admin`, { 23 | email, 24 | password 25 | }); 26 | 27 | if (response.data.success) { 28 | // Store the admin token 29 | localStorage.setItem('token', response.data.token); 30 | localStorage.setItem('isAdmin', 'true'); 31 | 32 | toast.success("Admin login successful!"); 33 | navigate("/dashboard"); 34 | } else { 35 | toast.error(response.data.message || "Login failed"); 36 | } 37 | } catch (error) { 38 | console.error('Error logging in:', error); 39 | toast.error(error.response?.data?.message || 'Invalid admin credentials'); 40 | } finally { 41 | setLoading(false); 42 | } 43 | }; 44 | 45 | return ( 46 |
47 |
48 |
49 |
50 |

51 | Admin Login 52 |

53 |
54 | {/* Email Input */} 55 |
56 | 62 | setEmail(e.target.value)} 71 | /> 72 |
73 | 74 | {/* Password Input */} 75 |
76 | 82 |
83 | setPassword(e.target.value)} 92 | autoComplete="current-password" 93 | /> 94 | 101 |
102 |
103 | 104 | {/* Submit Button */} 105 | 112 |
113 |
114 |
115 |
116 |
117 | ); 118 | }; 119 | 120 | export default Login; -------------------------------------------------------------------------------- /admin/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /admin/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import App from './App'; 5 | import './index.css'; 6 | 7 | ReactDOM.createRoot(document.getElementById('root')).render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /admin/tailwind.config.js: -------------------------------------------------------------------------------- 1 | // tailwind.config.js 2 | module.exports = { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,jsx,ts,tsx}", 6 | ], 7 | darkMode: 'class', 8 | theme: { 9 | extend: { 10 | colors: { 11 | primary: {"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a","950":"#172554"} 12 | } 13 | }, 14 | fontFamily: { 15 | 'body': [ 16 | 'Inter', 17 | 'ui-sans-serif', 18 | 'system-ui', 19 | '-apple-system', 20 | 'system-ui', 21 | 'Segoe UI', 22 | 'Roboto', 23 | 'Helvetica Neue', 24 | 'Arial', 25 | 'Noto Sans', 26 | 'sans-serif', 27 | 'Apple Color Emoji', 28 | 'Segoe UI Emoji', 29 | 'Segoe UI Symbol', 30 | 'Noto Color Emoji' 31 | ], 32 | 'sans': [ 33 | 'Inter', 34 | 'ui-sans-serif', 35 | 'system-ui', 36 | '-apple-system', 37 | 'system-ui', 38 | 'Segoe UI', 39 | 'Roboto', 40 | 'Helvetica Neue', 41 | 'Arial', 42 | 'Noto Sans', 43 | 'sans-serif', 44 | 'Apple Color Emoji', 45 | 'Segoe UI Emoji', 46 | 'Segoe UI Symbol', 47 | 'Noto Color Emoji' 48 | ] 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /admin/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/(.*)", 5 | "destination": "/" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /admin/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server:{port:5174} 8 | }) 9 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | PORT=4000 2 | MONGODB_URI=mongodb://localhost:27017/buildestate 3 | JWT_SECRET=your_jwt_secret_here 4 | EMAIL=your_email_for_notifications 5 | PASSWORD=your_email_password 6 | AZURE_API_KEY=your_azure_ai_key 7 | FIRECRAWL_API_KEY=your_firecrawl_api_key 8 | NODE_ENV=development 9 | IMAGEKIT_PUBLIC_KEY=your_imagekit_public_key 10 | IMAGEKIT_PRIVATE_KEY=your_imagekit_private_key 11 | IMAGEKIT_URL_ENDPOINT=your_imagekit_url_endpoint 12 | ADMIN_EMAIL=admin@buildestate.com 13 | ADMIN_PASSWORD=admin123 14 | WEBSITE_URL=http://localhost:5173 -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Logs 8 | logs/ 9 | *.log 10 | *.log.* 11 | 12 | # Dependency directories 13 | node_modules/ 14 | jspm_packages/ 15 | 16 | # Optional npm cache directory 17 | .npm 18 | 19 | # Optional eslint cache 20 | .eslintcache 21 | 22 | # Optional REPL history 23 | .node_repl_history 24 | 25 | # dotenv environment variables file 26 | .env 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # MacOS 33 | .DS_Store 34 | 35 | # Editor directories and files 36 | .idea/ 37 | .vscode/ 38 | *.suo 39 | *.ntvs* 40 | *.njsproj 41 | *.sln 42 | *.sw? 43 | 44 | # Build directories 45 | build/ 46 | dist/ 47 | 48 | # Coverage directory used by tools like istanbul 49 | coverage/ 50 | 51 | # Miscellaneous 52 | *.pem 53 | *.p12 54 | *.key 55 | *.crt 56 | *.csr 57 | *.pfx 58 | 59 | # React Native 60 | .expo/ 61 | .expo-shared/ 62 | 63 | # Parcel-bundler cache (https://parceljs.org/) 64 | .cache 65 | .parcel-cache 66 | 67 | # SvelteKit build / generated files 68 | .svelte-kit 69 | 70 | # Storybook files 71 | out/ 72 | .storybook-out/ -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Node.js image as the base image 2 | FROM node:18 3 | 4 | # Set the working directory 5 | WORKDIR /app 6 | 7 | # Copy package.json and package-lock.json 8 | COPY package.json package-lock.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install 12 | 13 | # Copy the rest of the application code 14 | COPY . . 15 | 16 | # Expose the port the app runs on 17 | EXPOSE 4000 18 | 19 | # Start the application 20 | CMD ["node", "server.js"] -------------------------------------------------------------------------------- /backend/config/config.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | dotenv.config({ path: './.env.local' }); 3 | 4 | export const config = { 5 | port: process.env.PORT || 3000, 6 | firecrawlApiKey: process.env.FIRECRAWL_API_KEY, 7 | huggingfaceApiKey: process.env.HUGGINGFACE_API_KEY, 8 | modelId: process.env.MODEL_ID || 'mistralai/Mistral-7B-Instruct-v0.2', 9 | openRouterApiKey: process.env.OPENROUTER_API_KEY, 10 | openAIApiKey: process.env.OPENAI_API_KEY, 11 | azureApiKey: process.env.AZURE_API_KEY, 12 | useAzure: process.env.USE_AZURE === 'true', 13 | }; 14 | 15 | 16 | -------------------------------------------------------------------------------- /backend/config/imagekit.js: -------------------------------------------------------------------------------- 1 | import ImageKit from 'imagekit'; 2 | import dotenv from 'dotenv'; 3 | 4 | dotenv.config({ path: './.env.local' }); 5 | 6 | const imagekit = new ImageKit({ 7 | publicKey: process.env.IMAGEKIT_PUBLIC_KEY, 8 | privateKey: process.env.IMAGEKIT_PRIVATE_KEY, 9 | urlEndpoint: process.env.IMAGEKIT_URL_ENDPOINT, 10 | }); 11 | 12 | if (!process.env.IMAGEKIT_PUBLIC_KEY) { 13 | throw new Error('Missing IMAGEKIT_PUBLIC_KEY'); 14 | } 15 | 16 | if (!process.env.IMAGEKIT_PRIVATE_KEY) { 17 | throw new Error('Missing IMAGEKIT_PRIVATE_KEY'); 18 | } 19 | 20 | if (!process.env.IMAGEKIT_URL_ENDPOINT) { 21 | throw new Error('Missing IMAGEKIT_URL_ENDPOINT'); 22 | } 23 | 24 | console.log("ImageKit connected successfully!"); 25 | 26 | export default imagekit; -------------------------------------------------------------------------------- /backend/config/mongodb.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import dotenv from "dotenv"; 3 | 4 | dotenv.config(); 5 | 6 | const connectdb = async () => { 7 | try { 8 | const conn = await mongoose.connect(process.env.MONGO_URI, { 9 | serverSelectionTimeoutMS: 5000, 10 | socketTimeoutMS: 45000, 11 | }); 12 | 13 | console.log(`MongoDB Connected: ${conn.connection.host}`); 14 | 15 | // Handle connection errors after initial connection 16 | mongoose.connection.on('error', err => { 17 | console.error('MongoDB connection error:', err); 18 | }); 19 | 20 | mongoose.connection.on('disconnected', () => { 21 | console.log('MongoDB disconnected'); 22 | }); 23 | 24 | return conn; 25 | } catch (error) { 26 | console.error(`MongoDB Connection Error: ${error.message}`); 27 | process.exit(1); 28 | } 29 | }; 30 | 31 | export default connectdb; -------------------------------------------------------------------------------- /backend/config/nodemailer.js: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer'; 2 | import dotenv from 'dotenv'; 3 | 4 | const transporter = nodemailer.createTransport({ 5 | host: 'smtp-relay.brevo.com', 6 | port:587, 7 | auth: { 8 | user: process.env.SMTP_USER, 9 | pass: process.env.SMTP_PASS, 10 | } 11 | }) 12 | 13 | export default transporter; -------------------------------------------------------------------------------- /backend/controller/Usercontroller.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { Router } from "express"; 3 | import bcrypt from "bcryptjs"; 4 | import jwt from "jsonwebtoken"; 5 | import nodemailer from "nodemailer"; 6 | import dotenv from "dotenv"; 7 | import validator from "validator"; 8 | import crypto from "crypto"; 9 | import userModel from "../models/Usermodel.js"; 10 | import transporter from "../config/nodemailer.js"; 11 | import { getWelcomeTemplate } from "../email.js"; 12 | import { getPasswordResetTemplate } from "../email.js"; 13 | 14 | const backendurl = process.env.BACKEND_URL; 15 | 16 | const createtoken = (id) => { 17 | return jwt.sign({ id }, process.env.JWT_SECRET, { 18 | expiresIn: "30d", 19 | }); 20 | }; 21 | 22 | dotenv.config(); 23 | 24 | const login = async (req, res) => { 25 | try { 26 | const { email, password } = req.body; 27 | const Registeruser = await userModel.findOne({ email }); 28 | if (!Registeruser) { 29 | return res.json({ message: "Email not found", success: false }); 30 | } 31 | const isMatch = await bcrypt.compare(password, Registeruser.password); 32 | if (isMatch) { 33 | const token = createtoken(Registeruser._id); 34 | return res.json({ token, user: { name: Registeruser.name, email: Registeruser.email }, success: true }); 35 | } else { 36 | return res.json({ message: "Invalid password", success: false }); 37 | } 38 | } catch (error) { 39 | console.error(error); 40 | res.json({ message: "Server error", success: false }); 41 | } 42 | }; 43 | 44 | const register = async (req, res) => { 45 | try { 46 | const { name, email, password } = req.body; 47 | if (!validator.isEmail(email)) { 48 | return res.json({ message: "Invalid email", success: false }); 49 | } 50 | const hashedPassword = await bcrypt.hash(password, 10); 51 | const newUser = new userModel({ name, email, password: hashedPassword }); 52 | await newUser.save(); 53 | const token = createtoken(newUser._id); 54 | 55 | // send email 56 | const mailOptions = { 57 | from: process.env.EMAIL, 58 | to: email, 59 | subject: "Welcome to BuildEstate - Your Account Has Been Created", 60 | html: getWelcomeTemplate(name) 61 | }; 62 | 63 | await transporter.sendMail(mailOptions); 64 | 65 | return res.json({ token, user: { name: newUser.name, email: newUser.email }, success: true }); 66 | } catch (error) { 67 | console.error(error); 68 | return res.json({ message: "Server error", success: false }); 69 | } 70 | }; 71 | 72 | const forgotpassword = async (req, res) => { 73 | try { 74 | const { email } = req.body; 75 | const user = await userModel.findOne({ email }); 76 | if (!user) { 77 | return res.status(404).json({ message: "Email not found", success: false }); 78 | } 79 | const resetToken = crypto.randomBytes(20).toString("hex"); 80 | user.resetToken = resetToken; 81 | user.resetTokenExpire = Date.now() + 10 * 60 * 1000; // 1 hour 82 | await user.save(); 83 | const resetUrl = `${process.env.WEBSITE_URL}/reset/${resetToken}`; 84 | const mailOptions = { 85 | from: process.env.EMAIL, 86 | to: email, 87 | subject: "Password Reset - BuildEstate Security", 88 | html: getPasswordResetTemplate(resetUrl) 89 | }; 90 | 91 | await transporter.sendMail(mailOptions); 92 | return res.status(200).json({ message: "Email sent", success: true }); 93 | } catch (error) { 94 | console.error(error); 95 | return res.status(500).json({ message: "Server error", success: false }); 96 | } 97 | }; 98 | 99 | const resetpassword = async (req, res) => { 100 | try { 101 | const { token } = req.params; 102 | const { password } = req.body; 103 | const user = await userModel.findOne({ 104 | resetToken: token, 105 | resetTokenExpire: { $gt: Date.now() }, 106 | }); 107 | if (!user) { 108 | return res.status(400).json({ message: "Invalid or expired token", success: false }); 109 | } 110 | user.password = await bcrypt.hash(password, 10); 111 | user.resetToken = undefined; 112 | user.resetTokenExpire = undefined; 113 | await user.save(); 114 | return res.status(200).json({ message: "Password reset successful", success: true }); 115 | } catch (error) { 116 | console.error(error); 117 | return res.status(500).json({ message: "Server error", success: false }); 118 | } 119 | }; 120 | 121 | const adminlogin = async (req, res) => { 122 | try { 123 | const { email, password } = req.body; 124 | 125 | if (email === process.env.ADMIN_EMAIL && password === process.env.ADMIN_PASSWORD) { 126 | const token = jwt.sign({ email }, process.env.JWT_SECRET, { expiresIn: '1h' }); 127 | return res.json({ token, success: true }); 128 | } else { 129 | return res.status(400).json({ message: "Invalid credentials", success: false }); 130 | } 131 | } catch (error) { 132 | console.error(error); 133 | return res.status(500).json({ message: "Server error", success: false }); 134 | } 135 | }; 136 | 137 | const logout = async (req, res) => { 138 | try { 139 | return res.json({ message: "Logged out", success: true }); 140 | } catch (error) { 141 | console.error(error); 142 | return res.json({ message: "Server error", success: false }); 143 | } 144 | }; 145 | 146 | // get name and email 147 | 148 | const getname = async (req, res) => { 149 | try { 150 | const user = await userModel.findById(req.user.id).select("-password"); 151 | return res.json(user); 152 | } 153 | catch (error) { 154 | console.error(error); 155 | return res.json({ message: "Server error", success: false }); 156 | } 157 | } 158 | 159 | 160 | 161 | export { login, register, forgotpassword, resetpassword, adminlogin, logout, getname }; -------------------------------------------------------------------------------- /backend/controller/formcontroller.js: -------------------------------------------------------------------------------- 1 | import Form from '../models/formmodel.js'; 2 | 3 | export const submitForm = async (req, res) => { 4 | try { 5 | const { name, email, phone, message } = req.body; // Debugging log 6 | 7 | const newForm = new Form({ 8 | name, 9 | email, 10 | phone, 11 | message, 12 | }); 13 | 14 | const savedForm = await newForm.save(); 15 | 16 | 17 | res.json({ message: 'Form submitted successfully' }); 18 | } catch (error) { 19 | console.error('Error saving form data:', error); 20 | res.status(500).json({ message: 'Server error' }); 21 | } 22 | }; -------------------------------------------------------------------------------- /backend/controller/newscontroller.js: -------------------------------------------------------------------------------- 1 | import News from "../models/newsmodel.js"; 2 | import nodemailer from "nodemailer"; 3 | import dotenv from "dotenv"; 4 | import transporter from "../config/nodemailer.js"; 5 | import { getEmailTemplate } from "../email.js"; 6 | 7 | const submitNewsletter = async (req, res) => { 8 | try { 9 | const { email } = req.body; 10 | 11 | const newNewsletter = new News({ 12 | email, 13 | }); 14 | 15 | const savedNewsletter = await newNewsletter.save(); 16 | 17 | const mailOptions = { 18 | from: process.env.EMAIL, 19 | to: email, 20 | subject: "Welcome to BuildEstate Newsletter! 🏠", 21 | html: getNewsletterTemplate(email), 22 | }; 23 | 24 | await transporter.sendMail(mailOptions); 25 | 26 | res.json({ message: "Newsletter submitted successfully" }); 27 | } catch (error) { 28 | console.error("Error saving newsletter data:", error); 29 | res.status(500).json({ message: "Server error" }); 30 | } 31 | }; 32 | 33 | export { submitNewsletter }; 34 | -------------------------------------------------------------------------------- /backend/controller/productcontroller.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import imagekit from "../config/imagekit.js"; 3 | import Property from "../models/propertymodel.js"; 4 | 5 | const addproperty = async (req, res) => { 6 | try { 7 | const { title, location, price, beds, baths, sqft, type, availability, description, amenities,phone } = req.body; 8 | 9 | const image1 = req.files.image1 && req.files.image1[0]; 10 | const image2 = req.files.image2 && req.files.image2[0]; 11 | const image3 = req.files.image3 && req.files.image3[0]; 12 | const image4 = req.files.image4 && req.files.image4[0]; 13 | 14 | const images = [image1, image2, image3, image4].filter((item) => item !== undefined); 15 | 16 | // Upload images to ImageKit and delete after upload 17 | const imageUrls = await Promise.all( 18 | images.map(async (item) => { 19 | const result = await imagekit.upload({ 20 | file: fs.readFileSync(item.path), 21 | fileName: item.originalname, 22 | folder: "Property", 23 | }); 24 | fs.unlink(item.path, (err) => { 25 | if (err) console.log("Error deleting the file: ", err); 26 | }); 27 | return result.url; 28 | }) 29 | ); 30 | 31 | // Create a new product 32 | const product = new Property({ 33 | title, 34 | location, 35 | price, 36 | beds, 37 | baths, 38 | sqft, 39 | type, 40 | availability, 41 | description, 42 | amenities, 43 | image: imageUrls, 44 | phone 45 | }); 46 | 47 | // Save the product to the database 48 | await product.save(); 49 | 50 | res.json({ message: "Product added successfully", success: true }); 51 | } catch (error) { 52 | console.log("Error adding product: ", error); 53 | res.status(500).json({ message: "Server Error", success: false }); 54 | } 55 | }; 56 | 57 | const listproperty = async (req, res) => { 58 | try { 59 | const property = await Property.find(); 60 | res.json({ property, success: true }); 61 | } catch (error) { 62 | console.log("Error listing products: ", error); 63 | res.status(500).json({ message: "Server Error", success: false }); 64 | } 65 | }; 66 | 67 | const removeproperty = async (req, res) => { 68 | try { 69 | const property = await Property.findByIdAndDelete(req.body.id); 70 | if (!property) { 71 | return res.status(404).json({ message: "Property not found", success: false }); 72 | } 73 | return res.json({ message: "Property removed successfully", success: true }); 74 | } catch (error) { 75 | console.log("Error removing product: ", error); 76 | return res.status(500).json({ message: "Server Error", success: false }); 77 | } 78 | }; 79 | 80 | const updateproperty = async (req, res) => { 81 | try { 82 | const { id, title, location, price, beds, baths, sqft, type, availability, description, amenities,phone } = req.body; 83 | 84 | const property = await Property.findById(id); 85 | if (!property) { 86 | console.log("Property not found with ID:", id); // Debugging line 87 | return res.status(404).json({ message: "Property not found", success: false }); 88 | } 89 | 90 | if (!req.files) { 91 | // No new images provided 92 | property.title = title; 93 | property.location = location; 94 | property.price = price; 95 | property.beds = beds; 96 | property.baths = baths; 97 | property.sqft = sqft; 98 | property.type = type; 99 | property.availability = availability; 100 | property.description = description; 101 | property.amenities = amenities; 102 | property.phone = phone; 103 | // Keep existing images 104 | await property.save(); 105 | return res.json({ message: "Property updated successfully", success: true }); 106 | } 107 | 108 | const image1 = req.files.image1 && req.files.image1[0]; 109 | const image2 = req.files.image2 && req.files.image2[0]; 110 | const image3 = req.files.image3 && req.files.image3[0]; 111 | const image4 = req.files.image4 && req.files.image4[0]; 112 | 113 | const images = [image1, image2, image3, image4].filter((item) => item !== undefined); 114 | 115 | // Upload images to ImageKit and delete after upload 116 | const imageUrls = await Promise.all( 117 | images.map(async (item) => { 118 | const result = await imagekit.upload({ 119 | file: fs.readFileSync(item.path), 120 | fileName: item.originalname, 121 | folder: "Property", 122 | }); 123 | fs.unlink(item.path, (err) => { 124 | if (err) console.log("Error deleting the file: ", err); 125 | }); 126 | return result.url; 127 | }) 128 | ); 129 | 130 | property.title = title; 131 | property.location = location; 132 | property.price = price; 133 | property.beds = beds; 134 | property.baths = baths; 135 | property.sqft = sqft; 136 | property.type = type; 137 | property.availability = availability; 138 | property.description = description; 139 | property.amenities = amenities; 140 | property.image = imageUrls; 141 | property.phone = phone; 142 | 143 | await property.save(); 144 | res.json({ message: "Property updated successfully", success: true }); 145 | } catch (error) { 146 | console.log("Error updating product: ", error); 147 | res.status(500).json({ message: "Server Error", success: false }); 148 | } 149 | }; 150 | 151 | const singleproperty = async (req, res) => { 152 | try { 153 | const { id } = req.params; 154 | const property = await Property.findById(id); 155 | if (!property) { 156 | return res.status(404).json({ message: "Property not found", success: false }); 157 | } 158 | res.json({ property, success: true }); 159 | } catch (error) { 160 | console.log("Error fetching property:", error); 161 | res.status(500).json({ message: "Server Error", success: false }); 162 | } 163 | }; 164 | 165 | export { addproperty, listproperty, removeproperty, updateproperty , singleproperty}; -------------------------------------------------------------------------------- /backend/controller/propertyController.js: -------------------------------------------------------------------------------- 1 | import firecrawlService from '../services/firecrawlService.js'; 2 | import aiService from '../services/aiService.js'; 3 | 4 | export const searchProperties = async (req, res) => { 5 | try { 6 | const { city, maxPrice, propertyCategory, propertyType, limit = 6 } = req.body; 7 | 8 | if (!city || !maxPrice) { 9 | return res.status(400).json({ success: false, message: 'City and maxPrice are required' }); 10 | } 11 | 12 | // Extract property data using Firecrawl, specifying the limit 13 | const propertiesData = await firecrawlService.findProperties( 14 | city, 15 | maxPrice, 16 | propertyCategory || 'Residential', 17 | propertyType || 'Flat', 18 | Math.min(limit, 6) // Limit to max 6 properties 19 | ); 20 | 21 | // Analyze the properties using AI 22 | const analysis = await aiService.analyzeProperties( 23 | propertiesData.properties, 24 | city, 25 | maxPrice, 26 | propertyCategory || 'Residential', 27 | propertyType || 'Flat' 28 | ); 29 | 30 | res.json({ 31 | success: true, 32 | properties: propertiesData.properties, 33 | analysis 34 | }); 35 | } catch (error) { 36 | console.error('Error searching properties:', error); 37 | res.status(500).json({ 38 | success: false, 39 | message: 'Failed to search properties', 40 | error: error.message 41 | }); 42 | } 43 | }; 44 | 45 | export const getLocationTrends = async (req, res) => { 46 | try { 47 | const { city } = req.params; 48 | const { limit = 5 } = req.query; 49 | 50 | if (!city) { 51 | return res.status(400).json({ success: false, message: 'City parameter is required' }); 52 | } 53 | 54 | // Extract location trend data using Firecrawl, with limit 55 | const locationsData = await firecrawlService.getLocationTrends(city, Math.min(limit, 5)); 56 | 57 | // Analyze the location trends using AI 58 | const analysis = await aiService.analyzeLocationTrends( 59 | locationsData.locations, 60 | city 61 | ); 62 | 63 | res.json({ 64 | success: true, 65 | locations: locationsData.locations, 66 | analysis 67 | }); 68 | } catch (error) { 69 | console.error('Error getting location trends:', error); 70 | res.status(500).json({ 71 | success: false, 72 | message: 'Failed to get location trends', 73 | error: error.message 74 | }); 75 | } 76 | }; -------------------------------------------------------------------------------- /backend/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | backend: 5 | build: ./backend 6 | ports: 7 | - "4000:4000" 8 | env_file: 9 | - ./backend/.env.local 10 | volumes: 11 | - ./backend:/app -------------------------------------------------------------------------------- /backend/middleware/authmiddleware.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import userModel from "../models/Usermodel.js"; 3 | 4 | export const protect = async (req, res, next) => { 5 | try { 6 | const token = req.headers.authorization?.split(" ")[1]; 7 | 8 | if (!token) { 9 | return res.status(401).json({ 10 | success: false, 11 | message: "Please login to continue", 12 | }); 13 | } 14 | 15 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 16 | const user = await userModel.findById(decoded.id).select("-password"); 17 | 18 | if (!user) { 19 | return res.status(401).json({ 20 | success: false, 21 | message: "User not found", 22 | }); 23 | } 24 | 25 | req.user = user; 26 | next(); 27 | } catch (error) { 28 | console.error("Auth error:", error); 29 | return res.status(401).json({ 30 | success: false, 31 | message: "Not authorized", 32 | }); 33 | } 34 | }; 35 | 36 | // In backend/middleware/authmiddleware.js 37 | export const checkAppointmentOwnership = async (req, res, next) => { 38 | try { 39 | const appointment = await Appointment.findById(req.params.id); 40 | if (!appointment) { 41 | return res.status(404).json({ 42 | success: false, 43 | message: "Appointment not found", 44 | }); 45 | } 46 | 47 | if (appointment.userId.toString() !== req.user._id.toString()) { 48 | return res.status(403).json({ 49 | success: false, 50 | message: "Not authorized to access this appointment", 51 | }); 52 | } 53 | 54 | req.appointment = appointment; 55 | next(); 56 | } catch (error) { 57 | console.error("Error checking appointment ownership:", error); 58 | res.status(500).json({ 59 | success: false, 60 | message: "Error checking appointment ownership", 61 | }); 62 | } 63 | }; 64 | 65 | export default protect; 66 | -------------------------------------------------------------------------------- /backend/middleware/multer.js: -------------------------------------------------------------------------------- 1 | import multer from "multer"; 2 | 3 | const storage = multer.diskStorage({ 4 | destination: (req, file, cb) => { 5 | cb(null, "uploads/"); // Ensure the folder exists 6 | }, 7 | filename: (req, file, cb) => { 8 | cb(null, Date.now() + "-" + file.originalname); // Unique file names 9 | }, 10 | }); 11 | 12 | const upload = multer({ storage }); 13 | export default upload; 14 | -------------------------------------------------------------------------------- /backend/middleware/statsMiddleware.js: -------------------------------------------------------------------------------- 1 | import Stats from '../models/statsModel.js'; 2 | 3 | export const trackAPIStats = async (req, res, next) => { 4 | const start = Date.now(); 5 | 6 | res.on('finish', async () => { 7 | try { 8 | // Skip tracking for OPTIONS and HEAD requests 9 | if (!['OPTIONS', 'HEAD'].includes(req.method)) { 10 | const duration = Date.now() - start; 11 | await Stats.create({ 12 | endpoint: req.originalUrl, 13 | method: req.method, 14 | responseTime: duration, 15 | statusCode: res.statusCode 16 | }); 17 | } 18 | } catch (error) { 19 | // Log error but don't crash the app 20 | console.error('Error tracking API stats:', error); 21 | } 22 | }); 23 | 24 | next(); 25 | }; -------------------------------------------------------------------------------- /backend/models/Usermodel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import bcrypt from 'bcryptjs'; 3 | 4 | const UserSchema = new mongoose.Schema({ 5 | name: { type: String, required: true }, 6 | email: { type: String, required: true, unique: true }, 7 | password: { type: String, required: true }, 8 | resetToken: { type: String }, 9 | resetTokenExpire: { type: Date } 10 | }); 11 | 12 | const User = mongoose.model('User', UserSchema); 13 | 14 | export default User; -------------------------------------------------------------------------------- /backend/models/appointmentModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const appointmentSchema = new mongoose.Schema({ 4 | propertyId: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | ref: 'Property', 7 | required: true 8 | }, 9 | userId: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: 'User', 12 | required: true 13 | }, 14 | date: { 15 | type: Date, 16 | required: true 17 | }, 18 | time: { 19 | type: String, 20 | required: true 21 | }, 22 | status: { 23 | type: String, 24 | enum: ['pending', 'confirmed', 'cancelled', 'completed'], 25 | default: 'pending' 26 | }, 27 | meetingLink: { 28 | type: String, 29 | trim: true 30 | }, 31 | meetingPlatform: { 32 | type: String, 33 | enum: ['zoom', 'google-meet', 'teams', 'other'], 34 | default: 'other' 35 | }, 36 | notes: { 37 | type: String 38 | }, 39 | cancelReason: { 40 | type: String 41 | }, 42 | reminderSent: { 43 | type: Boolean, 44 | default: false 45 | }, 46 | feedback: { 47 | rating: { 48 | type: Number, 49 | min: 1, 50 | max: 5 51 | }, 52 | comment: String 53 | } 54 | }, { 55 | timestamps: true 56 | }); 57 | 58 | // Add indexes for better query performance 59 | appointmentSchema.index({ userId: 1, date: -1 }); 60 | appointmentSchema.index({ propertyId: 1, date: -1 }); 61 | appointmentSchema.index({ status: 1 }); 62 | 63 | const Appointment = mongoose.model('Appointment', appointmentSchema); 64 | 65 | export default Appointment; -------------------------------------------------------------------------------- /backend/models/formmodel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const formSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true, 7 | }, 8 | email: { 9 | type: String, 10 | required: true, 11 | }, 12 | phone: { 13 | type: String, 14 | }, 15 | message: { 16 | type: String, 17 | required: true, 18 | }, 19 | }, { 20 | timestamps: true, 21 | }); 22 | 23 | const Form = mongoose.model('Form', formSchema); 24 | 25 | export default Form; -------------------------------------------------------------------------------- /backend/models/newsmodel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const newsSchema = new mongoose.Schema({ 4 | email: { 5 | type: String, 6 | required: true, 7 | }, 8 | }); 9 | 10 | const News = mongoose.model('News', newsSchema); 11 | 12 | export default News; -------------------------------------------------------------------------------- /backend/models/propertymodel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const propertySchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true, 7 | }, 8 | location: { 9 | type: String, 10 | required: true, 11 | }, 12 | price: { 13 | type: Number, 14 | required: true, 15 | }, 16 | image: { 17 | type: [String], 18 | required: true 19 | }, 20 | beds: { 21 | type: Number, 22 | required: true, 23 | }, 24 | baths: { 25 | type: Number, 26 | required: true, 27 | }, 28 | sqft: { 29 | type: Number, 30 | required: true, 31 | }, 32 | type: { 33 | type: String, 34 | required: true, 35 | }, 36 | availability: { 37 | type: String, 38 | required: true, 39 | }, 40 | description: { 41 | type: String, 42 | required: true, 43 | }, 44 | amenities: { 45 | type: Array, 46 | required: true, 47 | }, 48 | phone: { 49 | type: String, 50 | required: true, 51 | }, 52 | }); 53 | 54 | const Property = mongoose.model("Property", propertySchema); 55 | 56 | export default Property; 57 | -------------------------------------------------------------------------------- /backend/models/statsModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const statsSchema = new mongoose.Schema({ 4 | endpoint: { 5 | type: String, 6 | required: true 7 | }, 8 | method: { 9 | type: String, 10 | required: true, 11 | enum: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'] // Added HEAD 12 | }, 13 | timestamp: { 14 | type: Date, 15 | default: Date.now 16 | }, 17 | responseTime: { 18 | type: Number, 19 | required: true 20 | }, 21 | statusCode: { 22 | type: Number, 23 | required: true 24 | } 25 | }, { timestamps: true }); 26 | 27 | // Index for better query performance 28 | statsSchema.index({ endpoint: 1, timestamp: -1 }); 29 | statsSchema.index({ method: 1 }); 30 | statsSchema.index({ statusCode: 1 }); 31 | 32 | const Stats = mongoose.model('Stats', statsSchema); 33 | 34 | export default Stats; -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "BuildEstate real estate platform backend API", 5 | "type": "module", 6 | "main": "server.js", 7 | "scripts": { 8 | "start": "node server.js", 9 | "dev": "nodemon server.js", 10 | "build": "npm install", 11 | "render-build": "npm install && npm run build" 12 | }, 13 | "author": "Aayush Vaghela", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@azure-rest/ai-inference": "^1.0.0-beta.5", 17 | "@azure/core-auth": "^1.9.0", 18 | "@azure/core-sse": "^2.1.3", 19 | "@google/generative-ai": "^0.23.0", 20 | "@mendable/firecrawl-js": "^1.19.0", 21 | "bcrypt": "^5.1.1", 22 | "bcryptjs": "^2.4.3", 23 | "body-parser": "^1.20.3", 24 | "compression": "^1.8.0", 25 | "cookie-parser": "^1.4.7", 26 | "cors": "^2.8.5", 27 | "crypto": "^1.0.1", 28 | "dotenv": "^16.4.7", 29 | "express": "^4.21.2", 30 | "express-rate-limit": "^7.5.0", 31 | "helmet": "^8.0.0", 32 | "imagekit": "^6.0.0", 33 | "jsonwebtoken": "^9.0.2", 34 | "mongoose": "^8.9.3", 35 | "multer": "^1.4.5-lts.1", 36 | "nodemailer": "^6.9.16", 37 | "nodemon": "^3.1.9", 38 | "transporter": "^0.0.1", 39 | "validator": "^13.12.0" 40 | } 41 | } -------------------------------------------------------------------------------- /backend/render.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | - type: web 3 | name: real-estate-backend 4 | env: node 5 | buildCommand: npm run render-build 6 | startCommand: npm start 7 | envVars: 8 | - key: NODE_ENV 9 | value: production -------------------------------------------------------------------------------- /backend/routes/ProductRouter.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { addproperty, listproperty, removeproperty, updateproperty,singleproperty } from '../controller/productcontroller.js'; 3 | import upload from '../middleware/multer.js'; 4 | 5 | const propertyrouter = express.Router(); 6 | 7 | propertyrouter.post('/add', upload.fields([ 8 | { name: "image1", maxCount: 1 }, 9 | { name: "image2", maxCount: 1 }, 10 | { name: "image3", maxCount: 1 }, 11 | { name: "image4", maxCount: 1 }, 12 | ]), addproperty); 13 | propertyrouter.get('/list', listproperty); 14 | propertyrouter.post('/remove', removeproperty); 15 | propertyrouter.post('/update', upload.fields([ 16 | { name: "image1", maxCount: 1 }, 17 | { name: "image2", maxCount: 1 }, 18 | { name: "image3", maxCount: 1 }, 19 | { name: "image4", maxCount: 1 }, 20 | ]), updateproperty); 21 | propertyrouter.get('/single/:id', singleproperty); 22 | 23 | export default propertyrouter; -------------------------------------------------------------------------------- /backend/routes/UserRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { login, register, forgotpassword,adminlogin,resetpassword,getname } from '../controller/Usercontroller.js'; 3 | import authMiddleware from '../middleware/authmiddleware.js'; 4 | 5 | 6 | const userrouter = express.Router(); 7 | 8 | userrouter.post('/login', login); 9 | userrouter.post('/register', register); 10 | userrouter.post('/forgot', forgotpassword); 11 | userrouter.post('/reset/:token', resetpassword); 12 | userrouter.post('/admin', adminlogin); 13 | userrouter.get('/me', authMiddleware, getname); 14 | 15 | export default userrouter; -------------------------------------------------------------------------------- /backend/routes/adminRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { 3 | getAdminStats, 4 | getAllAppointments, 5 | updateAppointmentStatus 6 | } from '../controller/adminController.js'; 7 | 8 | const router = express.Router(); 9 | 10 | router.get('/stats', getAdminStats); 11 | router.get('/appointments',getAllAppointments); 12 | router.put('/appointments/status',updateAppointmentStatus); 13 | 14 | export default router; -------------------------------------------------------------------------------- /backend/routes/appointmentRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { protect } from '../middleware/authmiddleware.js'; 3 | import { 4 | scheduleViewing, 5 | getAllAppointments, 6 | updateAppointmentStatus, 7 | getAppointmentsByUser, 8 | cancelAppointment, 9 | updateAppointmentMeetingLink, 10 | getAppointmentStats, 11 | submitAppointmentFeedback, 12 | getUpcomingAppointments 13 | } from "../controller/appointmentController.js"; 14 | 15 | 16 | const router = express.Router(); 17 | 18 | // User routes 19 | router.post("/schedule", protect, scheduleViewing); // Add protect middleware 20 | router.get("/user", getAppointmentsByUser); 21 | router.put("/cancel/:id", cancelAppointment); 22 | router.put("/feedback/:id", submitAppointmentFeedback); 23 | router.get("/upcoming", getUpcomingAppointments); 24 | 25 | // Admin routes 26 | router.get("/all", getAllAppointments); 27 | router.get("/stats", getAppointmentStats); 28 | router.put("/status", updateAppointmentStatus); 29 | router.put("/update-meeting", updateAppointmentMeetingLink); 30 | 31 | export default router; -------------------------------------------------------------------------------- /backend/routes/formrouter.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { submitForm } from '../controller/formcontroller.js'; 3 | 4 | const router = express.Router(); 5 | 6 | router.post('/submit', submitForm); 7 | 8 | export default router; -------------------------------------------------------------------------------- /backend/routes/newsRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { submitNewsletter } from '../controller/newscontroller.js'; 3 | 4 | const newsrouter = express.Router(); 5 | 6 | newsrouter.post('/newsdata', submitNewsletter); 7 | 8 | export default newsrouter; -------------------------------------------------------------------------------- /backend/routes/propertyRoutes.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { searchProperties, getLocationTrends } from '../controller/propertyController.js'; 3 | 4 | const router = express.Router(); 5 | 6 | // Route to search for properties 7 | router.post('/properties/search', searchProperties); 8 | 9 | // Route to get location trends 10 | router.get('/locations/:city/trends', getLocationTrends); 11 | 12 | export default router; -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import dotenv from 'dotenv'; 3 | import cors from 'cors'; 4 | import rateLimit from 'express-rate-limit'; 5 | import helmet from 'helmet'; 6 | import compression from 'compression'; 7 | import connectdb from './config/mongodb.js'; 8 | import { trackAPIStats } from './middleware/statsMiddleware.js'; 9 | import propertyrouter from './routes/ProductRouter.js'; 10 | import userrouter from './routes/UserRoute.js'; 11 | import formrouter from './routes/formrouter.js'; 12 | import newsrouter from './routes/newsRoute.js'; 13 | import appointmentRouter from './routes/appointmentRoute.js'; 14 | import adminRouter from './routes/adminRoute.js'; 15 | import propertyRoutes from './routes/propertyRoutes.js'; 16 | 17 | dotenv.config(); 18 | 19 | const app = express(); 20 | 21 | // Rate limiting to prevent abuse 22 | const limiter = rateLimit({ 23 | windowMs: 15 * 60 * 1000, // 15 minutes 24 | max: 500, // Limit each IP to 500 requests per window 25 | standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers 26 | legacyHeaders: false, // Disable the `X-RateLimit-*` headers 27 | message: { success: false, message: 'Too many requests, please try again later.' } 28 | }); 29 | 30 | // Security middlewares 31 | app.use(limiter); 32 | app.use(helmet()); 33 | app.use(compression()); 34 | 35 | // Middleware 36 | app.use(express.json({ limit: '50mb' })); 37 | app.use(express.urlencoded({ extended: true, limit: '50mb' })); 38 | app.use(trackAPIStats); 39 | 40 | 41 | // CORS Configuration 42 | app.use(cors({ 43 | origin: [ 44 | 'http://localhost:4000', 45 | 'http://localhost:5174', 46 | 'http://localhost:5173', 47 | 'https://buildestate.vercel.app', 48 | 'https://real-estate-website-admin.onrender.com', 49 | 'https://real-estate-website-backend-zfu7.onrender.com', 50 | ], 51 | credentials: true, 52 | methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'], // Added HEAD 53 | allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'] 54 | })); 55 | 56 | // Database connection 57 | connectdb().then(() => { 58 | console.log('Database connected successfully'); 59 | }).catch(err => { 60 | console.error('Database connection error:', err); 61 | }); 62 | 63 | 64 | // API Routes 65 | app.use('/api/products', propertyrouter); 66 | app.use('/api/users', userrouter); 67 | app.use('/api/forms', formrouter); 68 | app.use('/api/news', newsrouter); 69 | app.use('/api/appointments', appointmentRouter); 70 | app.use('/api/admin', adminRouter); 71 | app.use('/api', propertyRoutes); 72 | 73 | 74 | app.use((err, req, res, next) => { 75 | console.error('Error:', err); 76 | const statusCode = err.status || 500; 77 | res.status(statusCode).json({ 78 | success: false, 79 | message: err.message || 'Internal server error', 80 | statusCode, 81 | ...(process.env.NODE_ENV === 'development' && { stack: err.stack }), 82 | timestamp: new Date().toISOString() 83 | }); 84 | }); 85 | 86 | 87 | // Handle unhandled rejections 88 | process.on('unhandledRejection', (err) => { 89 | console.log('UNHANDLED REJECTION! 💥 Shutting down...'); 90 | console.error(err); 91 | process.exit(1); 92 | }); 93 | 94 | // Status check endpoint 95 | app.get('/status', (req, res) => { 96 | res.status(200).json({ status: 'OK', time: new Date().toISOString() }); 97 | }); 98 | 99 | // Root endpoint - health check HTML 100 | app.get("/", (req, res) => { 101 | res.send(` 102 | 103 | 104 | 105 | 106 | 107 | BuildEstate API Status 108 | 116 | 117 | 118 |
119 |

BuildEstate API

120 |

Status: Online

121 |

Server Time: ${new Date().toLocaleString()}

122 | 123 |
124 |

The BuildEstate API is running properly. This backend serves property listings, user authentication, 125 | and AI analysis features for the BuildEstate property platform.

126 |
127 | 128 | 131 |
132 | 133 | 134 | `); 135 | }); 136 | 137 | const port = process.env.PORT || 4000; 138 | 139 | // Start server 140 | if (process.env.NODE_ENV !== 'test') { 141 | app.listen(port, '0.0.0.0', () => { 142 | console.log(`Server running on port ${port}`); 143 | }); 144 | } 145 | 146 | export default app; -------------------------------------------------------------------------------- /backend/services/aiService.js: -------------------------------------------------------------------------------- 1 | import { config } from "../config/config.js"; 2 | import ModelClient, { isUnexpected } from "@azure-rest/ai-inference"; 3 | import { AzureKeyCredential } from "@azure/core-auth"; 4 | 5 | class AIService { 6 | constructor() { 7 | this.azureApiKey = config.azureApiKey; 8 | } 9 | 10 | async generateText(prompt) { 11 | return this.generateTextWithAzure(prompt); 12 | } 13 | 14 | async generateTextWithAzure(prompt) { 15 | try { 16 | console.log(`Starting Azure AI generation at ${new Date().toISOString()}`); 17 | const startTime = Date.now(); 18 | 19 | const client = ModelClient( 20 | "https://models.inference.ai.azure.com", 21 | new AzureKeyCredential(this.azureApiKey) 22 | ); 23 | 24 | const response = await client.path("/chat/completions").post({ 25 | body: { 26 | messages: [ 27 | { 28 | role: "system", 29 | content: "You are an AI real estate expert assistant that provides concise, accurate analysis of property data." 30 | }, 31 | { 32 | role: "user", 33 | content: prompt 34 | } 35 | ], 36 | model: "gpt-4o", 37 | temperature: 0.7, 38 | max_tokens: 800, 39 | top_p: 1 40 | } 41 | }); 42 | 43 | const endTime = Date.now(); 44 | console.log(`Azure AI generation completed in ${(endTime - startTime) / 1000} seconds`); 45 | 46 | if (isUnexpected(response)) { 47 | throw new Error(response.body.error.message || "Azure API error"); 48 | } 49 | 50 | return response.body.choices[0].message.content; 51 | } catch (error) { 52 | console.error("Error generating text with Azure:", error); 53 | return `Error: ${error.message}`; 54 | } 55 | } 56 | 57 | // Helper method to filter and clean property data before analysis 58 | _preparePropertyData(properties, maxProperties = 3) { 59 | // Limit the number of properties 60 | const limitedProperties = properties.slice(0, maxProperties); 61 | 62 | // Clean and simplify each property 63 | return limitedProperties.map(property => ({ 64 | building_name: property.building_name, 65 | property_type: property.property_type, 66 | location_address: property.location_address, 67 | price: property.price, 68 | area_sqft: property.area_sqft, 69 | // Extract just a few key amenities 70 | amenities: Array.isArray(property.amenities) 71 | ? property.amenities.slice(0, 5) 72 | : [], 73 | // Truncate description to save tokens 74 | description: property.description 75 | ? property.description.substring(0, 150) + (property.description.length > 150 ? '...' : '') 76 | : '' 77 | })); 78 | } 79 | 80 | // Helper method to filter and clean location data 81 | _prepareLocationData(locations, maxLocations = 5) { 82 | // Limit the number of locations 83 | return locations.slice(0, maxLocations); 84 | } 85 | 86 | async analyzeProperties( 87 | properties, 88 | city, 89 | maxPrice, 90 | propertyCategory, 91 | propertyType 92 | ) { 93 | // Prepare limited and cleaned property data 94 | const preparedProperties = this._preparePropertyData(properties); 95 | 96 | const prompt = `As a real estate expert, analyze these properties: 97 | 98 | Properties Found in ${city}: 99 | ${JSON.stringify(preparedProperties, null, 2)} 100 | 101 | INSTRUCTIONS: 102 | 1. Focus ONLY on these properties that match: 103 | - Property Category: ${propertyCategory} 104 | - Property Type: ${propertyType} 105 | - Maximum Price: ${maxPrice} crores 106 | 2. Provide a brief analysis with these sections: 107 | - Property Overview (basic facts about each) 108 | - Best Value Analysis (which offers the best value) 109 | - Quick Recommendations 110 | 111 | Keep your response concise and focused on these properties only. 112 | `; 113 | 114 | return this.generateText(prompt); 115 | } 116 | 117 | async analyzeLocationTrends(locations, city) { 118 | // Prepare limited location data 119 | const preparedLocations = this._prepareLocationData(locations); 120 | 121 | const prompt = `As a real estate expert, analyze these location price trends for ${city}: 122 | 123 | ${JSON.stringify(preparedLocations, null, 2)} 124 | 125 | Please provide: 126 | 1. A brief summary of price trends for each location 127 | 2. Which areas are showing the highest appreciation 128 | 3. Which areas offer the best rental yield 129 | 4. Quick investment recommendations based on this data 130 | 131 | Keep your response concise (maximum 300 words). 132 | `; 133 | 134 | return this.generateText(prompt); 135 | } 136 | } 137 | 138 | export default new AIService(); -------------------------------------------------------------------------------- /backend/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "server.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "/api/(.*)", 12 | "destination": "/server.js" 13 | }, 14 | { 15 | "source": "/(.*)", 16 | "destination": "/server.js" 17 | } 18 | ], 19 | "headers": [ 20 | { 21 | "source": "/(.*)", 22 | "headers": [ 23 | { "key": "Access-Control-Allow-Origin", "value": "*" }, 24 | { "key": "Access-Control-Allow-Methods", "value": "GET,POST,PUT,DELETE,OPTIONS,HEAD" }, 25 | { "key": "Access-Control-Allow-Headers", "value": "X-Requested-With, Content-Type, Authorization" }, 26 | { "key": "Access-Control-Allow-Credentials", "value": "true" } 27 | ] 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | VITE_API_BASE_URL=http://localhost:4000 -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Logs 8 | logs/ 9 | *.log 10 | *.log.* 11 | 12 | # Dependency directories 13 | node_modules/ 14 | jspm_packages/ 15 | 16 | # Optional npm cache directory 17 | .npm 18 | 19 | # Optional eslint cache 20 | .eslintcache 21 | 22 | # Optional REPL history 23 | .node_repl_history 24 | 25 | # dotenv environment variables file 26 | .env 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # MacOS 33 | .DS_Store 34 | 35 | # Editor directories and files 36 | .idea/ 37 | .vscode/ 38 | *.suo 39 | *.ntvs* 40 | *.njsproj 41 | *.sln 42 | *.sw? 43 | 44 | # Build directories 45 | build/ 46 | dist/ 47 | 48 | # Coverage directory used by tools like istanbul 49 | coverage/ 50 | 51 | # Miscellaneous 52 | *.pem 53 | *.p12 54 | *.key 55 | *.crt 56 | *.csr 57 | *.pfx 58 | 59 | # React Native 60 | .expo/ 61 | .expo-shared/ 62 | 63 | # Parcel-bundler cache (https://parceljs.org/) 64 | .cache 65 | .parcel-cache 66 | 67 | # SvelteKit build / generated files 68 | .svelte-kit 69 | 70 | # Storybook files 71 | out/ 72 | .storybook-out/ -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AAYUSH412/Real-Estate-Website/24a68ddd4502b6680132ab32ee6b25eac5844a95/frontend/README.md -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | BuildEstate - Premium Real Estate Platform | Find Your Perfect Home 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "buildestate-frontend", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "main": "App.jsx", 7 | "description": "BuildEstate - Premium Real Estate Platform User Interface", 8 | "author": "Aayush Vaghela", 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/AAYUSH412/Real-Estate-Website" 13 | }, 14 | "scripts": { 15 | "dev": "vite", 16 | "build": "vite build", 17 | "lint": "eslint .", 18 | "preview": "vite preview" 19 | }, 20 | "dependencies": { 21 | "@chakra-ui/react": "^3.2.4", 22 | "@emotion/react": "^11.14.0", 23 | "@emotion/styled": "^11.14.0", 24 | "@heroicons/react": "^2.2.0", 25 | "@radix-ui/react-select": "^2.1.4", 26 | "@radix-ui/react-slot": "^1.1.1", 27 | "@react-spring/web": "^9.7.5", 28 | "@shadcn/ui": "^0.0.4", 29 | "@tailwindcss/line-clamp": "^0.4.4", 30 | "axios": "^1.7.9", 31 | "class-variance-authority": "^0.7.1", 32 | "clsx": "^2.1.1", 33 | "framer-motion": "^11.18.2", 34 | "js-cookie": "^3.0.5", 35 | "lucide-react": "^0.469.0", 36 | "next": "^15.1.4", 37 | "prop-types": "^15.8.1", 38 | "react": "^18.3.1", 39 | "react-dom": "^18.3.1", 40 | "react-helmet-async": "^2.0.5", 41 | "react-icons": "^5.4.0", 42 | "react-markdown": "^10.1.0", 43 | "react-router": "^7.1.1", 44 | "react-router-dom": "^7.1.5", 45 | "react-text-gradients": "^1.0.2", 46 | "react-toastify": "^11.0.3", 47 | "remark-gfm": "^4.0.1", 48 | "tailwind-merge": "^2.6.0", 49 | "tailwindcss-animate": "^1.0.7" 50 | }, 51 | "devDependencies": { 52 | "@eslint/js": "^9.17.0", 53 | "@types/node": "^22.10.3", 54 | "@types/react": "^18.3.18", 55 | "@types/react-dom": "^18.3.5", 56 | "@vitejs/plugin-react": "^4.3.4", 57 | "autoprefixer": "^10.4.20", 58 | "eslint": "^9.17.0", 59 | "eslint-plugin-react": "^7.37.2", 60 | "eslint-plugin-react-hooks": "^5.0.0", 61 | "eslint-plugin-react-refresh": "^0.4.16", 62 | "globals": "^15.14.0", 63 | "postcss": "^8.4.49", 64 | "tailwindcss": "^3.4.17", 65 | "vite": "^6.0.5" 66 | } 67 | } -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | // postcss.config.js 2 | export default { 3 | plugins: { 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | 4 | Sitemap: https://buildestate.vercel.app/sitemap.xml 5 | 6 | # Prevent access to sensitive routes 7 | Disallow: /reset/ 8 | Disallow: /forgot-password/ -------------------------------------------------------------------------------- /frontend/public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://buildestate.vercel.app/ 5 | 2024-03-15 6 | weekly 7 | 1.0 8 | 9 | 10 | https://buildestate.vercel.app/properties 11 | 2024-03-15 12 | daily 13 | 0.9 14 | 15 | 16 | https://buildestate.vercel.app/ai-property-hub 17 | 2024-03-15 18 | weekly 19 | 0.8 20 | 21 | 22 | https://buildestate.vercel.app/about 23 | 2024-03-15 24 | monthly 25 | 0.7 26 | 27 | 28 | https://buildestate.vercel.app/contact 29 | 2024-03-15 30 | monthly 31 | 0.7 32 | 33 | 34 | https://buildestate.vercel.app/login 35 | 2024-03-15 36 | monthly 37 | 0.6 38 | 39 | 40 | https://buildestate.vercel.app/signup 41 | 2024-03-15 42 | monthly 43 | 0.6 44 | 45 | -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 3 | import { ToastContainer } from 'react-toastify'; 4 | import { HelmetProvider } from 'react-helmet-async'; 5 | import Navbar from './components/Navbar' 6 | import Home from './pages/Home' 7 | import Properties from './pages/Properties' 8 | import PropertyDetails from './components/properties/propertydetail'; 9 | import Aboutus from './pages/About' 10 | import Contact from './pages/Contact' 11 | import Login from './components/login'; 12 | import Signup from './components/signup'; 13 | import ForgotPassword from './components/forgetpassword'; 14 | import ResetPassword from './components/resetpassword'; 15 | import Footer from './components/footer'; 16 | import NotFoundPage from './components/Notfound'; 17 | import { AuthProvider } from './context/AuthContext'; 18 | import AIPropertyHub from './pages/Aiagent' 19 | import StructuredData from './components/SEO/StructuredData'; 20 | import 'react-toastify/dist/ReactToastify.css'; 21 | 22 | 23 | export const Backendurl = import.meta.env.VITE_API_BASE_URL; 24 | 25 | const App = () => { 26 | return ( 27 | 28 | 29 | 30 | {/* Base website structured data */} 31 | 32 | 33 | 34 | 35 | 36 | } /> 37 | } /> 38 | } /> 39 | } /> 40 | } /> 41 | } /> 42 | } /> 43 | } /> 44 | } /> 45 | } /> 46 | } /> 47 | 48 |