From af0fb622b8a63546b4c4e854bf411c0c04dcd0f2 Mon Sep 17 00:00:00 2001 From: Juthatip McDevitt Date: Sat, 16 Mar 2024 00:02:31 -0500 Subject: [PATCH] added show more functionality && added calendar for tour requests --- real_estate/client/package-lock.json | 85 ++++++++++++++++++- real_estate/client/package.json | 1 + real_estate/client/src/components/Contact.jsx | 34 ++++++++ .../client/src/components/ListingCard.jsx | 2 +- real_estate/client/src/components/Navbar.jsx | 9 +- real_estate/client/src/components/Search.jsx | 4 +- real_estate/client/src/pages/Listing.jsx | 27 +++++- .../client/src/pages/SearchProperties.jsx | 34 +++++++- 8 files changed, 176 insertions(+), 20 deletions(-) create mode 100644 real_estate/client/src/components/Contact.jsx diff --git a/real_estate/client/package-lock.json b/real_estate/client/package-lock.json index 6cc0f80..9d0940d 100644 --- a/real_estate/client/package-lock.json +++ b/real_estate/client/package-lock.json @@ -11,6 +11,7 @@ "@reduxjs/toolkit": "^2.2.1", "firebase": "^10.8.1", "react": "^18.2.0", + "react-calendar": "^4.8.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", "react-redux": "^9.1.0", @@ -1639,6 +1640,19 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==" + }, + "node_modules/@types/lodash.memoize": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz", + "integrity": "sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/node": { "version": "20.11.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", @@ -1702,6 +1716,14 @@ "vite": "^4 || ^5" } }, + "node_modules/@wojtekmaj/date-utils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz", + "integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==", + "funding": { + "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -2215,6 +2237,14 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3081,6 +3111,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-user-locale": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.1.tgz", + "integrity": "sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==", + "dependencies": { + "@types/lodash.memoize": "^4.1.7", + "lodash.memoize": "^4.1.1" + }, + "funding": { + "url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3835,6 +3877,11 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3978,7 +4025,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4430,7 +4476,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -4500,6 +4545,31 @@ "node": ">=0.10.0" } }, + "node_modules/react-calendar": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-4.8.0.tgz", + "integrity": "sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA==", + "dependencies": { + "@wojtekmaj/date-utils": "^1.1.3", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1", + "prop-types": "^15.6.0", + "warning": "^4.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-calendar?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -4523,8 +4593,7 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-redux": { "version": "9.1.0", @@ -5561,6 +5630,14 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", diff --git a/real_estate/client/package.json b/real_estate/client/package.json index bda3b16..f9ac72f 100644 --- a/real_estate/client/package.json +++ b/real_estate/client/package.json @@ -13,6 +13,7 @@ "@reduxjs/toolkit": "^2.2.1", "firebase": "^10.8.1", "react": "^18.2.0", + "react-calendar": "^4.8.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", "react-redux": "^9.1.0", diff --git a/real_estate/client/src/components/Contact.jsx b/real_estate/client/src/components/Contact.jsx new file mode 100644 index 0000000..ebfb0a9 --- /dev/null +++ b/real_estate/client/src/components/Contact.jsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react" + + +const Contact = ({listing}) => { + const [agent, setAgent] = useState(null); + useEffect(() => { + const fetchAgent = async () => { + try { + const res = await fetch(`/server/user/${listing.userRef}`); + const data = await res.json(); + setAgent(data); + } catch (error) { + console.log(error); + } + }; + fetchAgent(); + }, [listing.userRef]); + + + + return ( +
+ { + agent && ( +
+

Contact{agent.username}{' '} for {' '} {listing.name.toLowerCase()}

+
+ ) + } +
+ ) +} + +export default Contact \ No newline at end of file diff --git a/real_estate/client/src/components/ListingCard.jsx b/real_estate/client/src/components/ListingCard.jsx index ce9efe2..5a3a3f8 100644 --- a/real_estate/client/src/components/ListingCard.jsx +++ b/real_estate/client/src/components/ListingCard.jsx @@ -4,7 +4,7 @@ const ListingCard = ({listing}) => { return (
- +

${listing.offer ? listing.discountPrice.toLocaleString('en-Us') : listing.currentPrice.toLocaleString('en-Us')} diff --git a/real_estate/client/src/components/Navbar.jsx b/real_estate/client/src/components/Navbar.jsx index 6aa91d6..ccdcbfa 100644 --- a/real_estate/client/src/components/Navbar.jsx +++ b/real_estate/client/src/components/Navbar.jsx @@ -1,10 +1,11 @@ -//import { IoIosSearch } from "react-icons/io"; import { IoIosLock } from "react-icons/io"; import {Link} from 'react-router-dom'; import {useSelector} from 'react-redux' + const Navbar = () => { const {currentUser} = useSelector((state) => state.user) + return (

@@ -15,14 +16,10 @@ const Navbar = () => {

Lifestyle properties

- {/*
- - - */}
  • Home
  • Buying
  • -
  • Selling
  • +
  • Renting
  • Contact
  • {currentUser ? ( diff --git a/real_estate/client/src/components/Search.jsx b/real_estate/client/src/components/Search.jsx index c21714a..cc08008 100644 --- a/real_estate/client/src/components/Search.jsx +++ b/real_estate/client/src/components/Search.jsx @@ -20,8 +20,8 @@ const Search = () => { } }, [location.search]) return ( -
    - setSearchTerm(e.target.value)} className="px-4 py-3 focus:outline-none bg-transparent text-sm w-60 sm:w-80"/> + + setSearchTerm(e.target.value)} className="px-4 py-2 focus:outline-none bg-transparent text-sm w-60 sm:w-80"/> diff --git a/real_estate/client/src/pages/Listing.jsx b/real_estate/client/src/pages/Listing.jsx index f9ce7fb..afb3a80 100644 --- a/real_estate/client/src/pages/Listing.jsx +++ b/real_estate/client/src/pages/Listing.jsx @@ -8,8 +8,15 @@ import 'swiper/css/thumbs'; import { FreeMode, Navigation, Thumbs } from 'swiper/modules'; import { BiSolidMap } from "react-icons/bi"; +import Calendar from "react-calendar"; +import "react-calendar/dist/Calendar.css"; + const Listing = () => { + const [date, changeDate] = useState(new Date()); + function changeValue(val) { + changeDate(val); + } const [thumbsSwiper, setThumbsSwiper] = useState(null); const params = useParams(); const [loading, setLoading] = useState(false); @@ -45,9 +52,10 @@ const Listing = () => { {error &&

    Something went wrong

    } {listing && !loading && !error && <> + {listing.imageUrls.map(url => - + )} @@ -55,7 +63,8 @@ const Listing = () => { )} -
    +
    +

    {listing.name} - $ {''} {listing.offer ? listing.discountPrice.toLocaleString('en-US') : listing.currentPrice.toLocaleString('en-US')} @@ -90,7 +99,19 @@ const Listing = () => { About this property {listing.description}

    - + +
    +
    +
    +

    Schedule tour

    +

    What is your preferred tour date?

    +
    + +

    The selected date is - {date.toLocaleDateString()}

    +
    + +
    +
    } diff --git a/real_estate/client/src/pages/SearchProperties.jsx b/real_estate/client/src/pages/SearchProperties.jsx index b0b2f78..0d1f1dd 100644 --- a/real_estate/client/src/pages/SearchProperties.jsx +++ b/real_estate/client/src/pages/SearchProperties.jsx @@ -6,6 +6,7 @@ import ListingCard from "../components/ListingCard"; const SearchProperties = () => { const navigate = useNavigate(); const [loading, setLoading] = useState(false); + const [showMore, setShowMore] = useState(false); const [listings, setListings] = useState([]); const [sidebarSearch, setSidebarSearch] = useState({ searchTerm: '', @@ -38,9 +39,15 @@ const SearchProperties = () => { } const fetchListings = async () => { setLoading(true); + setShowMore(false) const searchQuery = urlParams.toString(); const res = await fetch(`/server/listing/get?${searchQuery}`); const data = await res.json(); + if(data.length > 11){ + showMore(true); + } else{ + setShowMore(false); + } setListings(data); setLoading(false); }; @@ -81,10 +88,25 @@ const SearchProperties = () => { navigate(`/search?${searchQuery}`); }; + //show more functionality + const onShowMore = async () => { + const numberOfListings = listings.length; + const startIndex = numberOfListings; + const urlParams = new URLSearchParams(location.search); + urlParams.set('startIndex', startIndex); + const searchQuery = urlParams.toString(); + const res = await fetch(`/server/listing/get?${searchQuery}`); + const data = await res.json(); + if (data.length < 12) { + setShowMore(false); + } + setListings([...listings, ...data]); + } + return ( -
    -
    +
    +
    @@ -135,8 +157,8 @@ const SearchProperties = () => {
    -

    Listing results

    -
    +

    Listing results

    +
    {!loading && listings.length === 0 &&(

    No listing found

    )} @@ -144,6 +166,10 @@ const SearchProperties = () => {

    loading...

    )} {!loading && listings && listings.map((listing) => ())} + + {showMore &&( + + )}