├── server ├── .env ├── config │ ├── prismaConfig.js │ └── auth0Config.js ├── vercel.json ├── routes │ ├── residencyRoute.js │ └── userRoute.js ├── package.json ├── index.js ├── .gitignore ├── prisma │ └── schema.prisma ├── controllers │ ├── resdCntrl.js │ └── userCntrl.js ├── data │ └── Residency.json └── yarn.lock ├── client ├── vercel.json ├── public │ ├── logo.png │ ├── r1.png │ ├── r2.png │ ├── r3.png │ ├── logo2.png │ ├── realty.png │ ├── tower.png │ ├── value.png │ ├── contact.jpg │ ├── equinix.png │ ├── prologis.png │ └── hero-image.png ├── src │ ├── context │ │ └── UserDetailContext.js │ ├── components │ │ ├── Companies │ │ │ ├── Companies.css │ │ │ └── Companies.jsx │ │ ├── UploadImage │ │ │ ├── UploadImage.css │ │ │ └── UploadImage.jsx │ │ ├── Footer │ │ │ ├── Footer.css │ │ │ └── Footer.jsx │ │ ├── GetStarted │ │ │ ├── GetStarted.css │ │ │ └── GetStarted.jsx │ │ ├── SearchBar │ │ │ └── SearchBar.jsx │ │ ├── Map │ │ │ └── Map.jsx │ │ ├── Residencies │ │ │ ├── Residencies.css │ │ │ └── Residencies.jsx │ │ ├── Header │ │ │ ├── Header.css │ │ │ └── Header.jsx │ │ ├── PropertyCard │ │ │ ├── PropertyCard.jsx │ │ │ └── PropertyCard.css │ │ ├── ProfileMenu │ │ │ └── ProfileMenu.jsx │ │ ├── Value │ │ │ ├── Value.css │ │ │ └── Value.jsx │ │ ├── GeoCoderMarker │ │ │ └── GeoCoderMarker.jsx │ │ ├── Contact │ │ │ ├── Contact.css │ │ │ └── Contact.jsx │ │ ├── Heart │ │ │ └── Heart.jsx │ │ ├── Layout │ │ │ └── Layout.jsx │ │ ├── BookingModal │ │ │ └── BookingModal.jsx │ │ ├── Hero │ │ │ ├── Hero.css │ │ │ └── Hero.jsx │ │ ├── BasicDetails │ │ │ └── BasicDetails.jsx │ │ ├── AddLocation │ │ │ └── AddLocation.jsx │ │ ├── AddPropertyModal │ │ │ └── AddPropertyModal.jsx │ │ └── Facilities │ │ │ └── Facilities.jsx │ ├── App.css │ ├── hooks │ │ ├── useCountries.jsx │ │ ├── useProperties.jsx │ │ ├── useAuthCheck.jsx │ │ ├── useHeaderColor.jsx │ │ ├── useFavourites.jsx │ │ └── useBookings.jsx │ ├── pages │ │ ├── Properties │ │ │ ├── Properties.css │ │ │ └── Properties.jsx │ │ ├── Website.jsx │ │ ├── Property │ │ │ ├── Property.css │ │ │ └── Property.jsx │ │ ├── Favourites │ │ │ └── Favourites.jsx │ │ └── Bookings │ │ │ └── Bookings.jsx │ ├── main.jsx │ ├── utils │ │ ├── common.js │ │ ├── accordion.jsx │ │ ├── slider.json │ │ └── api.js │ ├── App.jsx │ └── index.css ├── vite.config.js ├── .gitignore ├── index.html └── package.json ├── Project snip ├── Like.JPG ├── DashBoard.JPG └── Adding property.JPG └── README.md /server/.env: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /client/vercel.json: -------------------------------------------------------------------------------- 1 | { "rewrites": [{ "source": "/(.*)", "destination": "/" }] } -------------------------------------------------------------------------------- /Project snip/Like.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/Project snip/Like.JPG -------------------------------------------------------------------------------- /client/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/logo.png -------------------------------------------------------------------------------- /client/public/r1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/r1.png -------------------------------------------------------------------------------- /client/public/r2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/r2.png -------------------------------------------------------------------------------- /client/public/r3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/r3.png -------------------------------------------------------------------------------- /client/public/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/logo2.png -------------------------------------------------------------------------------- /client/public/realty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/realty.png -------------------------------------------------------------------------------- /client/public/tower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/tower.png -------------------------------------------------------------------------------- /client/public/value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/value.png -------------------------------------------------------------------------------- /Project snip/DashBoard.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/Project snip/DashBoard.JPG -------------------------------------------------------------------------------- /client/public/contact.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/contact.jpg -------------------------------------------------------------------------------- /client/public/equinix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/equinix.png -------------------------------------------------------------------------------- /client/public/prologis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/prologis.png -------------------------------------------------------------------------------- /client/public/hero-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/client/public/hero-image.png -------------------------------------------------------------------------------- /Project snip/Adding property.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prazivi/Real_State_FullStack/HEAD/Project snip/Adding property.JPG -------------------------------------------------------------------------------- /server/config/prismaConfig.js: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | const prisma = new PrismaClient() 3 | 4 | export {prisma} -------------------------------------------------------------------------------- /client/src/context/UserDetailContext.js: -------------------------------------------------------------------------------- 1 | import {createContext} from 'react' 2 | 3 | const UserDetailContext = createContext() 4 | 5 | export default UserDetailContext -------------------------------------------------------------------------------- /client/src/components/Companies/Companies.css: -------------------------------------------------------------------------------- 1 | .c-container{ 2 | justify-content: space-around; 3 | gap: 1rem; 4 | } 5 | .c-container>img{ 6 | width: 9rem; 7 | } -------------------------------------------------------------------------------- /client/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /server/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "node-js", 4 | "builds": [ 5 | { "src": "index.js", "use": "@vercel/node" } 6 | ], 7 | "routes": [ 8 | { "src": "/(.*)", "dest": "/" } 9 | ] 10 | } -------------------------------------------------------------------------------- /server/config/auth0Config.js: -------------------------------------------------------------------------------- 1 | import {auth} from 'express-oauth2-jwt-bearer' 2 | 3 | const jwtCheck = auth({ 4 | audience: "http://localhost:8000", 5 | issuerBaseURL: "https://dev-03ifqltxbr6nn0hn.us.auth0.com", 6 | tokenSigningAlg: "RS256" 7 | }) 8 | 9 | export default jwtCheck -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .App{ 2 | position: relative; 3 | overflow-x: clip; 4 | background-color: white; 5 | } 6 | .App>:nth-child(1) 7 | { 8 | background: var(--black); 9 | position: relative; 10 | } 11 | .white-gradient{ 12 | position: absolute; 13 | width: 20rem; 14 | height: 20rem; 15 | background: rgba(255, 255, 255, 0.522); 16 | filter: blur(100px); 17 | border-radius: 100px; 18 | } -------------------------------------------------------------------------------- /server/routes/residencyRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { createResidency, getAllResidencies, getResidency } from "../controllers/resdCntrl.js"; 3 | import jwtCheck from "../config/auth0Config.js"; 4 | const router = express.Router(); 5 | 6 | router.post("/create", jwtCheck, createResidency) 7 | router.get("/allresd", getAllResidencies) 8 | router.get("/:id", getResidency) 9 | export {router as residencyRoute} -------------------------------------------------------------------------------- /client/src/hooks/useCountries.jsx: -------------------------------------------------------------------------------- 1 | import countries from "world-countries"; 2 | 3 | const formattedCountries = countries.map((country)=> ({ 4 | value: country.name.common, 5 | label: `${country.name.common} ${country.flag}`, 6 | latlng: country.latlng, 7 | region: country.region 8 | })) 9 | 10 | const useCountries = ()=> { 11 | const getAll = ()=> formattedCountries; 12 | return {getAll} 13 | } 14 | 15 | export default useCountries -------------------------------------------------------------------------------- /client/src/components/UploadImage/UploadImage.css: -------------------------------------------------------------------------------- 1 | .uploadWrapper{ 2 | margin-top: 3rem; 3 | gap: 2rem; 4 | } 5 | 6 | .uploadZone{ 7 | width: 80%; 8 | height: 25rem; 9 | border: 2px dashed grey; 10 | cursor: pointer; 11 | } 12 | .uploadedImage{ 13 | width: 80%; 14 | height: 25rem; 15 | border-radius: 10px; 16 | cursor: pointer; 17 | overflow: hidden; 18 | } 19 | .uploadedImage>img{ 20 | width: 100%; 21 | height: 100%; 22 | object-fit: cover; 23 | } -------------------------------------------------------------------------------- /client/src/pages/Properties/Properties.css: -------------------------------------------------------------------------------- 1 | .properties-container{ 2 | gap: 2rem; 3 | } 4 | .properties-container .search-bar{ 5 | width: clamp(12rem, 100%, 28rem); 6 | border-radius: 30px; 7 | border: 1px solid rgba(120, 120, 120, 0.374); 8 | flex-wrap: nowrap; 9 | } 10 | 11 | .properties-container .search-bar .button{ 12 | border-radius: 30px; 13 | font-size: 12px; 14 | padding: 10px 15px; 15 | } 16 | 17 | .properties-container .search-bar input{ 18 | width: 70%; 19 | } -------------------------------------------------------------------------------- /client/src/hooks/useProperties.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useQuery } from "react-query"; 3 | import { getAllProperties } from "../utils/api"; 4 | 5 | const useProperties = () => { 6 | const { data, isLoading, isError, refetch } = useQuery( 7 | "allProperties", 8 | getAllProperties, 9 | { refetchOnWindowFocus: false } 10 | ); 11 | 12 | return { 13 | data, 14 | isError, 15 | isLoading, 16 | refetch, 17 | }; 18 | }; 19 | 20 | export default useProperties; 21 | -------------------------------------------------------------------------------- /client/src/components/Companies/Companies.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import './Companies.css' 3 | const Companies = () => { 4 | return ( 5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 |
13 | ); 14 | }; 15 | 16 | export default Companies; 17 | -------------------------------------------------------------------------------- /client/src/hooks/useAuthCheck.jsx: -------------------------------------------------------------------------------- 1 | import {useAuth0} from '@auth0/auth0-react' 2 | import { toast } from "react-toastify"; 3 | 4 | 5 | const useAuthCheck = () => { 6 | 7 | const {isAuthenticated} = useAuth0() 8 | const validateLogin = () => { 9 | if(!isAuthenticated) 10 | { 11 | toast.error("you must be logged in", {position: "bottom-right"}) 12 | return false 13 | } else return true 14 | } 15 | return ( 16 | { 17 | validateLogin 18 | } 19 | ) 20 | } 21 | 22 | export default useAuthCheck -------------------------------------------------------------------------------- /client/src/components/Footer/Footer.css: -------------------------------------------------------------------------------- 1 | .f-container{ 2 | justify-content: space-between; 3 | border-top: .5px solid rgba(0,0,0,0.148) 4 | } 5 | .f-left{ 6 | gap: 1rem; 7 | } 8 | .f-menu{ 9 | gap: 1.5rem; 10 | margin-top: 1.5rem; 11 | font-weight: 500; 12 | } 13 | @media (max-width: 768px) { 14 | .f-container{ 15 | justify-content: center; 16 | } 17 | .f-container>div{ 18 | align-items: center!important; 19 | text-align: center; 20 | } 21 | .f-right{ 22 | justify-content: center; 23 | } 24 | } -------------------------------------------------------------------------------- /client/src/components/GetStarted/GetStarted.css: -------------------------------------------------------------------------------- 1 | .inner-container{ 2 | gap: 1.5rem; 3 | background: #4161df; 4 | padding: 2rem; 5 | border-radius: 10px; 6 | border: 6px solid #5d77d6; 7 | text-align: center; 8 | } 9 | 10 | .inner-container .primaryText{ 11 | color: white; 12 | font-weight: 600; 13 | } 14 | .inner-container .secondaryText{ 15 | color: rgba(255, 255, 255, 0.587); 16 | } 17 | .inner-container .button { 18 | background: #5a73d7; 19 | border: 2px solid white; 20 | box-shadow: var(--shadow); 21 | border-radius: 10px; 22 | } -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "type": "module", 7 | 8 | "scripts": { 9 | "start": "nodemon index.js", 10 | "postinstall": "prisma generate" 11 | }, 12 | "dependencies": { 13 | "@prisma/client": "^4.16.2", 14 | "cookie-parser": "^1.4.6", 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.3.1", 17 | "express": "^4.18.2", 18 | "express-async-handler": "^1.2.0", 19 | "express-oauth2-jwt-bearer": "^1.5.0", 20 | "nodemon": "^2.0.22", 21 | "prisma": "^4.16.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/components/SearchBar/SearchBar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { HiLocationMarker } from "react-icons/hi"; 3 | 4 | const SearchBar = ({ filter, setFilter }) => { 5 | return ( 6 |
7 | 8 | setFilter(e.target.value)} 13 | /> 14 | 15 |
16 | ); 17 | }; 18 | 19 | export default SearchBar; 20 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import dotenv from 'dotenv'; 3 | import cookieParser from 'cookie-parser'; 4 | import cors from 'cors'; 5 | import { userRoute } from './routes/userRoute.js'; 6 | import { residencyRoute } from './routes/residencyRoute.js'; 7 | dotenv.config() 8 | 9 | const app = express(); 10 | 11 | const PORT = process.env.PORT || 3000; 12 | 13 | app.use(express.json()) 14 | app.use(cookieParser()) 15 | app.use(cors()) 16 | 17 | app.listen(PORT, ()=> { 18 | console.log(`Server is running on port ${PORT}`); 19 | }); 20 | 21 | app.use('/api/user', userRoute) 22 | app.use("/api/residency", residencyRoute) -------------------------------------------------------------------------------- /client/src/hooks/useHeaderColor.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | const useHeaderColor = () => { 4 | const [headerColor, setHeaderColor] = useState(false) 5 | //to handle shadow of header 6 | useEffect(() => { 7 | function handleScroll() { 8 | if (window.scrollY > 8) { 9 | setHeaderColor("#302e2e") 10 | } else { 11 | setHeaderColor("none"); 12 | } 13 | } 14 | window.addEventListener("scroll", handleScroll); 15 | return () => { 16 | window.removeEventListener("scroll", handleScroll); 17 | }; 18 | }, []); 19 | 20 | return headerColor 21 | }; 22 | 23 | export default useHeaderColor; 24 | -------------------------------------------------------------------------------- /server/routes/userRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { 3 | bookVisit, 4 | cancelBooking, 5 | createUser, 6 | getAllBookings, 7 | getAllFavorites, 8 | toFav, 9 | } from "../controllers/userCntrl.js"; 10 | import jwtCheck from "../config/auth0Config.js"; 11 | const router = express.Router(); 12 | 13 | router.post("/register", jwtCheck, createUser); 14 | router.post("/bookVisit/:id", jwtCheck, bookVisit); 15 | router.post("/allBookings", getAllBookings); 16 | router.post("/removeBooking/:id", jwtCheck, cancelBooking); 17 | router.post("/toFav/:rid", jwtCheck, toFav); 18 | router.post("/allFav/", jwtCheck, getAllFavorites); 19 | export { router as userRoute }; 20 | -------------------------------------------------------------------------------- /client/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import { Auth0Provider } from "@auth0/auth0-react"; 6 | 7 | ReactDOM.createRoot(document.getElementById("root")).render( 8 | 9 | 18 | 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /client/src/components/Map/Map.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {MapContainer, TileLayer} from 'react-leaflet' 3 | import GeoCoderMarker from '../GeoCoderMarker/GeoCoderMarker' 4 | const Map = ({address, city, country}) => { 5 | return ( 6 | 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | export default Map -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | .next -------------------------------------------------------------------------------- /client/src/components/GetStarted/GetStarted.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./GetStarted.css"; 3 | const GetStarted = () => { 4 | return ( 5 |
6 |
7 |
8 | Get started with Homyz 9 | 10 | Subscribe and find super attractive price quotes from us. 11 |
12 | Find your residence soon 13 |
14 | 17 |
18 |
19 |
20 | ); 21 | }; 22 | 23 | export default GetStarted; 24 | -------------------------------------------------------------------------------- /client/src/pages/Website.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Companies from "../components/Companies/Companies"; 3 | import Contact from "../components/Contact/Contact"; 4 | import Footer from "../components/Footer/Footer"; 5 | import GetStarted from "../components/GetStarted/GetStarted"; 6 | import Header from "../components/Header/Header"; 7 | import Hero from "../components/Hero/Hero"; 8 | import Residencies from "../components/Residencies/Residencies"; 9 | import Value from "../components/Value/Value"; 10 | 11 | 12 | const Website = () => { 13 | return ( 14 |
15 |
16 |
17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 | ) 26 | } 27 | 28 | export default Website -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | Real Estate 14 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /client/src/components/Residencies/Residencies.css: -------------------------------------------------------------------------------- 1 | .r-container { 2 | gap: 2rem; 3 | position: relative; 4 | overflow: hidden; 5 | } 6 | 7 | .r-head{ 8 | margin-bottom: 2rem; 9 | } 10 | 11 | .swiper-horizontal{ 12 | overflow: visible; 13 | } 14 | .r-buttons{ 15 | position: absolute; 16 | gap: 1rem; 17 | top: -4rem; 18 | right: 0; 19 | } 20 | .r-buttons button{ 21 | font-size: 1.2rem; 22 | padding: .2rem .8rem; 23 | color: blue; 24 | border: none; 25 | border-radius: 5px; 26 | background-color: white; 27 | cursor: pointer; 28 | } 29 | .r-buttons>:nth-child(1) 30 | { 31 | background-color: #EEEEFF; 32 | } 33 | .r-buttons>:nth-child(2) 34 | { 35 | box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.25); 36 | } 37 | 38 | 39 | @media (max-width: 640px) { 40 | .r-head{ 41 | align-items: center; 42 | } 43 | .r-buttons{ 44 | position: initial; 45 | } 46 | } -------------------------------------------------------------------------------- /client/src/components/Header/Header.css: -------------------------------------------------------------------------------- 1 | .h-wrapper { 2 | z-index: 99; 3 | position: sticky; 4 | top: 0; 5 | } 6 | .h-container { 7 | padding-top: 1rem; 8 | padding-bottom: 1rem; 9 | color: var(--secondary); 10 | justify-content: space-between; 11 | } 12 | 13 | .h-menu { 14 | gap: 2rem; 15 | } 16 | .h-menu > *:hover { 17 | cursor: pointer; 18 | } 19 | .menu-icon { 20 | display: none; 21 | } 22 | 23 | @media (max-width: 768px) { 24 | .menu-icon { 25 | display: block; 26 | } 27 | .h-menu { 28 | color: var(--black); 29 | position: absolute; 30 | gap: 2rem; 31 | font-weight: 500; 32 | flex-direction: column; 33 | right: 4rem; 34 | top: 3rem; 35 | background: white; 36 | display: flex; 37 | border-radius: 10px; 38 | transition: all 200ms ease; 39 | align-items: flex-start; 40 | padding: 3rem; 41 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.05); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /client/src/components/PropertyCard/PropertyCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import './PropertyCard.css' 3 | import {AiFillHeart} from 'react-icons/ai' 4 | import {truncate} from 'lodash' 5 | import { useNavigate } from "react-router-dom"; 6 | import Heart from "../Heart/Heart"; 7 | const PropertyCard = ({card}) => { 8 | 9 | const navigate = useNavigate(); 10 | return ( 11 |
navigate(`../properties/${card.id}`)} 13 | > 14 | 15 | home 16 | 17 | $ 18 | {card.price} 19 | 20 | {truncate(card.title, {length: 15})} 21 | {truncate(card.description, {length: 80})} 22 |
23 | ); 24 | }; 25 | 26 | export default PropertyCard; 27 | -------------------------------------------------------------------------------- /client/src/components/ProfileMenu/ProfileMenu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Avatar, Menu} from '@mantine/core' 3 | import { useNavigate } from 'react-router-dom' 4 | const ProfileMenu = ({user, logout}) => { 5 | const navigate = useNavigate() 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | navigate("./favourites", {replace: true})}> 13 | Favourites 14 | 15 | 16 | navigate("./bookings", {replace: true})}> 17 | Bookings 18 | 19 | 20 | { 21 | localStorage.clear(); 22 | logout() 23 | }}> 24 | Logout 25 | 26 | 27 | 28 | ) 29 | } 30 | 31 | export default ProfileMenu -------------------------------------------------------------------------------- /client/src/components/PropertyCard/PropertyCard.css: -------------------------------------------------------------------------------- 1 | .r-card { 2 | gap: 0.6rem; 3 | padding: 1rem; 4 | border-radius: 10px; 5 | max-width: max-content; 6 | margin: auto; 7 | transition: all 300ms ease-in; 8 | position: relative; 9 | z-index: 0; 10 | } 11 | .r-card>svg{ 12 | position: absolute; 13 | top: 25px; 14 | right: 30px; 15 | z-index: 1; 16 | } 17 | 18 | .r-card > img { 19 | width: 15rem; 20 | height: 10rem; 21 | border-radius: 10px; 22 | } 23 | .r-card > :nth-child(3) { 24 | font-size: 1.2rem; 25 | font-weight: 600; 26 | } 27 | .r-card > :nth-child(4) { 28 | font-size: 1.5rem; 29 | } 30 | .r-card > :nth-child(5) { 31 | font-size: 0.7rem; 32 | width: 15rem; 33 | } 34 | 35 | .r-card:hover { 36 | scale: 1.025; 37 | cursor: pointer; 38 | background: linear-gradient( 39 | 180deg, 40 | rgba(255, 255, 255, 0) 0%, 41 | rgba(136, 160, 255, 0.46) 217.91% 42 | ); 43 | box-shadow: 0px 72px 49px -51px rgba(136, 160, 255, 0.21); 44 | } -------------------------------------------------------------------------------- /client/src/utils/common.js: -------------------------------------------------------------------------------- 1 | export const getMenuStyles = (menuOpened) => { 2 | if (document.documentElement.clientWidth <= 800) { 3 | return { right: !menuOpened && "-100%" }; 4 | } 5 | }; 6 | 7 | export const sliderSettings = { 8 | slidesPerView: 1, 9 | spaceBetween: 50, 10 | breakpoints: { 11 | 480: { 12 | slidesPerView: 1, 13 | }, 14 | 600: { 15 | slidesPerView: 2, 16 | }, 17 | 750: { 18 | slidesPerView: 3, 19 | }, 20 | 1100: { 21 | slidesPerView: 4, 22 | }, 23 | }, 24 | }; 25 | 26 | export const updateFavourites = (id, favourites) => { 27 | if (favourites.includes(id)) { 28 | return favourites.filter((resId) => resId !== id); 29 | } else { 30 | return [...favourites, id]; 31 | } 32 | }; 33 | 34 | export const checkFavourites = (id, favourites) => { 35 | return favourites?.includes(id) ? "#fa3e5f" : "white"; 36 | }; 37 | 38 | export const validateString = (value) => { 39 | return value?.length < 3 || value === null 40 | ? "Must have atleast 3 characters" 41 | : null; 42 | }; 43 | -------------------------------------------------------------------------------- /client/src/hooks/useFavourites.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useRef } from "react"; 2 | import UserDetailContext from "../context/UserDetailContext"; 3 | import { useQuery } from "react-query"; 4 | import { useAuth0 } from "@auth0/auth0-react"; 5 | import { getAllFav } from "../utils/api"; 6 | 7 | const useFavourites = () => { 8 | const { userDetails, setUserDetails } = useContext(UserDetailContext); 9 | const queryRef = useRef(); 10 | const { user } = useAuth0(); 11 | 12 | const { data, isLoading, isError, refetch } = useQuery({ 13 | queryKey: "allFavourites", 14 | queryFn: () => getAllFav(user?.email, userDetails?.token), 15 | onSuccess: (data) => 16 | setUserDetails((prev) => ({ ...prev, favourites: data })), 17 | enabled: user !== undefined, 18 | staleTime: 30000, 19 | }); 20 | 21 | queryRef.current = refetch; 22 | 23 | useEffect(() => { 24 | queryRef.current && queryRef.current(); 25 | }, [userDetails?.token]); 26 | 27 | return { data, isError, isLoading, refetch }; 28 | }; 29 | 30 | export default useFavourites; 31 | -------------------------------------------------------------------------------- /client/src/utils/accordion.jsx: -------------------------------------------------------------------------------- 1 | import { HiShieldCheck } from "react-icons/hi"; 2 | import { MdCancel, MdAnalytics } from "react-icons/md"; 3 | const data = [ 4 | { 5 | icon: , 6 | heading: "Best interest rates on the market", 7 | detail: 8 | "Exercitation in fugiat est ut ad ea cupidatat ut in cupidatat occaecat ut occaecat consequat est minim minim esse tempor laborum consequat esse adipisicing eu reprehenderit enim.", 9 | }, 10 | { 11 | icon: , 12 | heading: "Prevent unstable prices", 13 | detail: 14 | "Exercitation in fugiat est ut ad ea cupidatat ut in cupidatat occaecat ut occaecat consequat est minim minim esse tempor laborum consequat esse adipisicing eu reprehenderit enim.", 15 | }, 16 | { 17 | icon: , 18 | heading: "Best price on the market", 19 | detail: 20 | "Exercitation in fugiat est ut ad ea cupidatat ut in cupidatat occaecat ut occaecat consequat est minim minim esse tempor laborum consequat esse adipisicing eu reprehenderit enim.", 21 | }, 22 | ]; 23 | export default data; -------------------------------------------------------------------------------- /client/src/hooks/useBookings.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useRef } from "react"; 2 | import UserDetailContext from "../context/UserDetailContext"; 3 | import { useQuery } from "react-query"; 4 | import { useAuth0 } from "@auth0/auth0-react"; 5 | import { getAllBookings, getAllFav } from "../utils/api"; 6 | 7 | const useBookings = () => { 8 | const { userDetails, setUserDetails } = useContext(UserDetailContext); 9 | const queryRef = useRef(); 10 | const { user } = useAuth0(); 11 | 12 | const { data, isLoading, isError, refetch } = useQuery({ 13 | queryKey: "allBookings", 14 | queryFn: () => getAllBookings(user?.email, userDetails?.token), 15 | onSuccess: (data) => 16 | setUserDetails((prev) => ({ ...prev, bookings: data })), 17 | enabled: user !== undefined, 18 | staleTime: 30000, 19 | }); 20 | 21 | queryRef.current = refetch; 22 | 23 | useEffect(() => { 24 | queryRef.current && queryRef.current(); 25 | }, [userDetails?.token]); 26 | 27 | return { data, isError, isLoading, refetch }; 28 | }; 29 | 30 | export default useBookings; 31 | -------------------------------------------------------------------------------- /client/src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Footer.css"; 3 | const Footer = () => { 4 | return ( 5 |
6 |
7 | {/* left side */} 8 |
9 | 10 | 11 | Our vision is to make all people
12 | the best place to live for them. 13 |
14 |
15 | 16 |
17 | Information 18 | 145 New York, FL 5467, USA 19 |
20 | Property 21 | Services 22 | Product 23 | About Us 24 |
25 |
26 |
27 |
28 | ); 29 | }; 30 | 31 | export default Footer; 32 | -------------------------------------------------------------------------------- /server/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "mongodb" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | model User { 11 | id String @id @default(auto()) @map("_id") @db.ObjectId 12 | name String? 13 | email String @unique 14 | image String? 15 | bookedVisits Json[] 16 | favResidenciesID String[] @db.ObjectId 17 | ownedResidencies Residency[] @relation("Owner") 18 | } 19 | 20 | model Residency { 21 | id String @id @default(auto()) @map("_id") @db.ObjectId 22 | title String 23 | description String 24 | price Int 25 | address String 26 | city String 27 | country String 28 | image String 29 | facilities Json 30 | userEmail String 31 | owner User @relation("Owner", fields: [userEmail], references: [email]) 32 | createdAt DateTime @default(now()) 33 | updatedAt DateTime @updatedAt 34 | 35 | @@unique(fields: [address, userEmail]) 36 | } 37 | -------------------------------------------------------------------------------- /client/src/components/Value/Value.css: -------------------------------------------------------------------------------- 1 | .v-container .image-container { 2 | border: 8px solid rgb(232 232 232 / 93%); 3 | } 4 | .v-container > div { 5 | flex: 1; 6 | } 7 | .v-right { 8 | gap: 0.5rem; 9 | } 10 | .accordion { 11 | margin-top: 2rem; 12 | border: none; 13 | } 14 | .accordionItem { 15 | background: white; 16 | border: 0.8px solid rgba(128, 128, 128, 0.143); 17 | border-radius: 8px; 18 | overflow: hidden; 19 | margin-bottom: 20px; 20 | } 21 | 22 | .accordionItem.expanded { 23 | box-shadow: var(--shadow); 24 | border-radius: 6px; 25 | } 26 | .accordionButton { 27 | background: white; 28 | padding: 1rem; 29 | width: 100%; 30 | justify-content: space-between; 31 | cursor: pointer; 32 | } 33 | 34 | .icon { 35 | padding: 10px; 36 | background: #eeeeff; 37 | border-radius: 5px; 38 | } 39 | .icon svg { 40 | fill: var(--blue); 41 | } 42 | 43 | .accordionButton .primaryText{ 44 | font-size: 1.1rem; 45 | } 46 | 47 | @media (max-width: 1024px) { 48 | .v-container{ 49 | flex-direction: column; 50 | } 51 | } 52 | 53 | @media (max-width: 768px) { 54 | .accordionButton .primaryText{ 55 | font-size: .8rem; 56 | } 57 | } -------------------------------------------------------------------------------- /client/src/components/GeoCoderMarker/GeoCoderMarker.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { Marker, Popup, useMap } from 'react-leaflet' 3 | import L from 'leaflet' 4 | import "leaflet/dist/leaflet.css" 5 | import icon from "leaflet/dist/images/marker-icon.png"; 6 | import iconShadow from "leaflet/dist/images/marker-shadow.png"; 7 | import * as ELG from 'esri-leaflet-geocoder' 8 | 9 | let DefaulIcon = L.icon ({ 10 | iconUrl : icon, 11 | shadowUrl: iconShadow 12 | }) 13 | L.Marker.prototype.options.icon = DefaulIcon 14 | 15 | 16 | const GeoCoderMarker = ({address}) => { 17 | 18 | const map = useMap() 19 | const [position, setPosition] = useState([60, 19]) 20 | 21 | useEffect(()=> { 22 | ELG.geocode().text(address).run((err, results, response)=> { 23 | if(results?.results?.length > 0){ 24 | const {lat, lng} = results?.results[0].latlng 25 | setPosition([lat, lng]) 26 | map.flyTo([lat, lng], 6) 27 | } 28 | }) 29 | }, [address]) 30 | 31 | return ( 32 | 33 | 34 | 35 | ) 36 | } 37 | 38 | export default GeoCoderMarker -------------------------------------------------------------------------------- /client/src/utils/slider.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Aliva Priva Jardin", 4 | "price": "47,043", 5 | "detail": "Jakarta Garden City Street, Cakung. Pulo Gadung, Jakarta Timur, DKI Jakarta", 6 | "image": "./r1.png" 7 | }, 8 | { 9 | "name": "Asatti Garden City", 10 | "price": "66,353", 11 | "detail": "Pahlawan Street XVII No.215, Cinangka, Sawangan, Depok, Jawa Barat", 12 | "image": "./r2.png" 13 | }, 14 | { 15 | "name": "Citralan Puri Serang", 16 | "price": "35,853", 17 | "detail": "Ruko Puri Indah Residence Block A7, Lingkar Street, Ciracas, Serang, Banten", 18 | "image": "./r3.png" 19 | }, 20 | { 21 | "name": "Aliva Priva Jardin", 22 | "price": "47,043", 23 | "detail": "Jakarta Garden City Street, Cakung. Pulo Gadung, Jakarta Timur, DKI Jakarta", 24 | "image": "./r1.png" 25 | }, 26 | { 27 | "name": "Asatti Garden City", 28 | "price": "66,353", 29 | "detail": "Pahlawan Street XVII No.215, Cinangka, Sawangan, Depok, Jawa Barat", 30 | "image": "./r2.png" 31 | }, 32 | { 33 | "name": "Citralan Puri Serang", 34 | "price": "35,853", 35 | "detail": "Ruko Puri Indah Residence Block A7, Lingkar Street, Ciracas, Serang, Banten", 36 | "image": "./r3.png" 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "realestate", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@auth0/auth0-react": "2.0.1", 13 | "@emotion/react": "^11.11.1", 14 | "@mantine/core": "^6.0.16", 15 | "@mantine/dates": "^6.0.16", 16 | "@mantine/form": "^6.0.16", 17 | "@mantine/hooks": "^6.0.16", 18 | "axios": "^1.4.0", 19 | "dayjs": "^1.11.9", 20 | "esri-leaflet-geocoder": "2.3.3", 21 | "framer-motion": "^10.8.5", 22 | "leaflet": "^1.9.4", 23 | "lodash": "^4.17.21", 24 | "react": "^18.2.0", 25 | "react-accessible-accordion": "^5.0.0", 26 | "react-countup": "^6.4.2", 27 | "react-dom": "^18.2.0", 28 | "react-icons": "^4.8.0", 29 | "react-leaflet": "4.2.1", 30 | "react-outside-click-handler": "^1.3.0", 31 | "react-query": "^3.39.3", 32 | "react-router-dom": "6.10.0", 33 | "react-spinners": "^0.13.8", 34 | "react-toastify": "^9.1.3", 35 | "swiper": "^9.1.1", 36 | "world-countries": "^4.0.0" 37 | }, 38 | "devDependencies": { 39 | "@types/react": "^18.0.28", 40 | "@types/react-dom": "^18.0.11", 41 | "@vitejs/plugin-react": "^3.1.0", 42 | "vite": "^4.2.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/src/pages/Property/Property.css: -------------------------------------------------------------------------------- 1 | .property-container{ 2 | gap: 2rem; 3 | position: relative; 4 | } 5 | 6 | .property-container .like{ 7 | position: absolute; 8 | top: 3rem;right: 3rem; 9 | cursor: pointer; 10 | } 11 | 12 | 13 | .property-container>img{ 14 | align-self: center; 15 | max-height: 35rem; 16 | width: 100%; 17 | border-radius: 1rem; 18 | object-fit: cover; 19 | } 20 | 21 | .property-details{ 22 | width: 100%; 23 | gap: 2rem; 24 | justify-content: space-between; 25 | } 26 | 27 | .property-details>div{ 28 | flex: 1; 29 | gap: 1.5rem; 30 | } 31 | 32 | .property-details .left .head{ 33 | justify-content: space-between; 34 | width: 100%; 35 | } 36 | 37 | .facilities{ 38 | gap: 1rem; 39 | font-size: 0.9rem; 40 | padding: 1rem; 41 | } 42 | 43 | .facility{ 44 | gap: 0.5rem; 45 | } 46 | 47 | .property-details .button{ 48 | width: 100%; 49 | margin-top: 1rem; 50 | } 51 | 52 | .property-details .button:hover{ 53 | transform: scale(1.05); 54 | } 55 | 56 | 57 | @media (max-width: 700px) { 58 | .property-container>div{ 59 | flex-direction: column; 60 | } 61 | .head{ 62 | flex-direction: column; 63 | } 64 | .facilities{ 65 | flex-direction: column; 66 | align-items: flex-start; 67 | } 68 | .property-details>div{ 69 | width: 100%; 70 | } 71 | } -------------------------------------------------------------------------------- /client/src/components/Contact/Contact.css: -------------------------------------------------------------------------------- 1 | .c-container { 2 | justify-content: space-between; 3 | } 4 | .c-container > div { 5 | flex: 1; 6 | } 7 | .c-right { 8 | width: 100%; 9 | } 10 | .c-left { 11 | gap: 0.5rem; 12 | } 13 | .contactModes { 14 | margin-top: 2rem; 15 | gap: 1rem; 16 | } 17 | .contactModes .row { 18 | gap: 1.5rem; 19 | } 20 | .mode { 21 | width: 16rem; 22 | padding: 1rem; 23 | border: 0.8px solid rgba(128, 128, 128, 0.143); 24 | border-radius: 5px; 25 | gap: 1rem; 26 | transition: all 300ms ease-in; 27 | } 28 | .mode .button { 29 | width: 100%; 30 | background: var(--lightBlue); 31 | color: var(--blue); 32 | font-size: 0.9rem; 33 | font-weight: 600; 34 | } 35 | .mode > :nth-child(1) { 36 | width: 100%; 37 | gap: 1.6rem; 38 | } 39 | .mode .detail .primaryText { 40 | font-size: 1.1rem; 41 | font-weight: 600; 42 | } 43 | 44 | .mode:hover { 45 | scale: 1.1; 46 | box-shadow: var(--shadow); 47 | } 48 | 49 | .mode .button:hover { 50 | background: var(--blue-gradient); 51 | color: white; 52 | scale: 0.8; 53 | } 54 | 55 | @media (max-width: 1024px) { 56 | .c-container { 57 | flex-direction: column; 58 | } 59 | .c-right { 60 | justify-content: center; 61 | } 62 | } 63 | @media (max-width: 1024px) 64 | { 65 | .contactModes{ 66 | width: 100%; 67 | } 68 | .row{ 69 | flex-direction: column; 70 | width: 100%; 71 | } 72 | .mode{ 73 | width: 100%; 74 | } 75 | } -------------------------------------------------------------------------------- /client/src/components/Heart/Heart.jsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react" 2 | import { AiFillHeart } from "react-icons/ai" 3 | import useAuthCheck from "../../hooks/useAuthCheck" 4 | import { useMutation } from "react-query" 5 | import { useAuth0 } from "@auth0/auth0-react" 6 | import UserDetailContext from "../../context/UserDetailContext" 7 | import { checkFavourites, updateFavourites } from "../../utils/common" 8 | import { toFav } from "../../utils/api" 9 | 10 | const Heart = ({id}) => { 11 | 12 | const [heartColor, setHeartColor] = useState("white") 13 | const {validateLogin} = useAuthCheck() 14 | const {user} = useAuth0() 15 | 16 | const { 17 | userDetails: { favourites, token }, 18 | setUserDetails, 19 | } = useContext(UserDetailContext); 20 | 21 | useEffect(()=> { 22 | setHeartColor(()=> checkFavourites(id, favourites)) 23 | },[favourites]) 24 | 25 | 26 | const {mutate} = useMutation({ 27 | mutationFn: () => toFav(id, user?.email, token), 28 | onSuccess: ()=> { 29 | setUserDetails((prev)=> ( 30 | { 31 | ...prev, 32 | favourites: updateFavourites(id, prev.favourites) 33 | } 34 | )) 35 | } 36 | }) 37 | 38 | const handleLike = () => { 39 | if(validateLogin()) 40 | { 41 | mutate() 42 | setHeartColor((prev)=> prev === "#fa3e5f" ? "white": "#fa3e5f") 43 | } 44 | } 45 | 46 | return ( 47 | { 48 | e.stopPropagation() 49 | handleLike() 50 | }}/> 51 | ) 52 | } 53 | 54 | export default Heart -------------------------------------------------------------------------------- /client/src/components/Layout/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from "react"; 2 | import Header from "../Header/Header"; 3 | import Footer from "../Footer/Footer"; 4 | import { Outlet } from "react-router-dom"; 5 | import { useAuth0 } from "@auth0/auth0-react"; 6 | import UserDetailContext from "../../context/UserDetailContext"; 7 | import { useMutation } from "react-query"; 8 | import { createUser } from "../../utils/api"; 9 | import useFavourites from "../../hooks/useFavourites"; 10 | import useBookings from "../../hooks/useBookings"; 11 | 12 | const Layout = () => { 13 | 14 | useFavourites() 15 | useBookings() 16 | 17 | const { isAuthenticated, user, getAccessTokenWithPopup } = useAuth0(); 18 | const { setUserDetails } = useContext(UserDetailContext); 19 | 20 | const { mutate } = useMutation({ 21 | mutationKey: [user?.email], 22 | mutationFn: (token) => createUser(user?.email, token), 23 | }); 24 | 25 | useEffect(() => { 26 | const getTokenAndRegsiter = async () => { 27 | 28 | const res = await getAccessTokenWithPopup({ 29 | authorizationParams: { 30 | audience: "http://localhost:8000", 31 | scope: "openid profile email", 32 | }, 33 | }); 34 | localStorage.setItem("access_token", res); 35 | setUserDetails((prev) => ({ ...prev, token: res })); 36 | mutate(res) 37 | }; 38 | 39 | 40 | isAuthenticated && getTokenAndRegsiter(); 41 | }, [isAuthenticated]); 42 | 43 | return ( 44 | <> 45 |
46 |
47 | 48 |
49 |
}> 31 | 32 | }> 33 | } /> 34 | 35 | } /> 36 | } /> 37 | 38 | } /> 39 | } /> 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | 51 | export default App; 52 | -------------------------------------------------------------------------------- /client/src/components/Hero/Hero.css: -------------------------------------------------------------------------------- 1 | .hero-wrapper { 2 | color: white; 3 | position: relative; 4 | z-index: 1; 5 | padding-bottom: 2rem; 6 | } 7 | 8 | 9 | .hero-container { 10 | justify-content: space-around; 11 | align-items: flex-end; 12 | } 13 | 14 | 15 | /* left side */ 16 | .hero-left { 17 | gap: 3rem; 18 | } 19 | .hero-title { 20 | position: relative; 21 | z-index: 1; 22 | } 23 | .hero-title > h1 { 24 | font-weight: 600; 25 | font-size: 3.8rem; 26 | line-height: 4rem; 27 | } 28 | .orange-circle { 29 | height: 4rem; 30 | width: 4rem; 31 | background: var(--orange-gradient); 32 | border-radius: 999px; 33 | position: absolute; 34 | right: 28%; 35 | top: -10%; 36 | z-index: -1; 37 | } 38 | .search-bar { 39 | background-color: white; 40 | border-radius: 5px; 41 | border: 3px solid rgba(120, 120, 120, 0.374); 42 | padding: 0.5rem 1rem; 43 | width: 100%; 44 | justify-content: space-between; 45 | } 46 | .search-bar > input { 47 | border: none; 48 | outline: none; 49 | } 50 | .stats{ 51 | width: 100%; 52 | justify-content: space-between; 53 | } 54 | .stat>:nth-child(1) 55 | { 56 | font-size: 2rem; 57 | } 58 | .stat>:nth-child(1)>:last-child{ 59 | color: orange; 60 | } 61 | /* right side */ 62 | 63 | .image-container { 64 | width: 30rem; 65 | height: 35rem; 66 | overflow: hidden; 67 | border-radius: 15rem 15rem 0 0; 68 | border: 8px solid rgba(255, 255, 255, 0.12); 69 | } 70 | .image-container>img{ 71 | width: 100%; 72 | height: 100%; 73 | } 74 | 75 | /* mobile screens */ 76 | @media (max-width: 640px){ 77 | .hero-container{ 78 | margin-top: 2rem; 79 | } 80 | .hero-title>h1{ 81 | font-size: 2.5rem; 82 | line-height: 3rem; 83 | } 84 | .image-container{ 85 | width: 95%; 86 | height: 25rem; 87 | } 88 | .stats{ 89 | justify-content: center; 90 | gap: 1.5rem; 91 | font-weight: wrap; 92 | } 93 | .stat>:nth-child(1){ 94 | font-size: 1.5rem; 95 | } 96 | .stat>:nth-child(2) 97 | { 98 | font-size: 0.8rem; 99 | } 100 | } -------------------------------------------------------------------------------- /client/src/components/Residencies/Residencies.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Swiper, SwiperSlide, useSwiper } from "swiper/react"; 3 | // Import Swiper styles 4 | import "swiper/css"; 5 | import "./Residencies.css"; 6 | import { sliderSettings } from "../../utils/common"; 7 | import PropertyCard from "../PropertyCard/PropertyCard"; 8 | import useProperties from "../../hooks/useProperties"; 9 | import {PuffLoader} from 'react-spinners' 10 | 11 | const Residencies = () => { 12 | 13 | const {data, isError, isLoading} = useProperties() 14 | 15 | if(isError){ 16 | return( 17 |
18 | Error while fetching data 19 |
20 | ) 21 | } 22 | 23 | if(isLoading){ 24 | return( 25 |
26 | 33 |
34 | ) 35 | } 36 | 37 | 38 | return ( 39 |
40 |
41 |
42 | Best Choices 43 | Popular Residencies 44 |
45 | 46 | 47 | {/* slider */} 48 | {data.slice(0, 8).map((card, i) => ( 49 | 50 | 51 | 52 | ))} 53 | 54 |
55 |
56 | ); 57 | }; 58 | 59 | export default Residencies; 60 | 61 | const SlideNextButton = () => { 62 | const swiper = useSwiper(); 63 | return ( 64 |
65 | 68 | 71 |
72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /client/src/components/BasicDetails/BasicDetails.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TextInput, Box, Textarea, Group, Button, NumberInput } from "@mantine/core"; 3 | import { useForm } from "@mantine/form"; 4 | import { validateString } from "../../utils/common"; 5 | 6 | const BasicDetails = ({ prevStep, nextStep, propertyDetails, setPropertyDetails }) => { 7 | const form = useForm({ 8 | initialValues: { 9 | title: propertyDetails.title, 10 | description: propertyDetails.description, 11 | price: propertyDetails.price, 12 | }, 13 | validate: { 14 | title: (value) => validateString(value), 15 | description: (value) => validateString(value), 16 | price: (value) => 17 | value < 1000 ? "Must be greater than 999 dollars" : null, 18 | }, 19 | }); 20 | 21 | const {title, description, price} = form.values 22 | 23 | 24 | const handleSubmit = ()=> { 25 | const {hasErrors} = form.validate() 26 | if(!hasErrors) { 27 | setPropertyDetails((prev)=> ({...prev, title, description, price})) 28 | nextStep() 29 | } 30 | } 31 | return ( 32 | 33 |
{ 34 | e.preventDefault(); 35 | handleSubmit(); 36 | }}> 37 | 43 |