From c3b4574966041efe7f7fa08f084d5c228edd604f Mon Sep 17 00:00:00 2001 From: Raphael Ako-Mensah Date: Tue, 5 Aug 2025 00:54:21 +0100 Subject: [PATCH] url --- gatsby-config.js | 2 +- gatsby-node.js | 8 +- makefile | 36 +++++--- package.json | 2 +- src/components/PropertyCard.js | 46 +++++----- src/components/PropertyPreview.js | 146 +++++++++++++++--------------- 6 files changed, 127 insertions(+), 113 deletions(-) diff --git a/gatsby-config.js b/gatsby-config.js index bba25b9..f1e1bd1 100755 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -163,7 +163,7 @@ module.exports = { { resolve: `gatsby-plugin-offline`, options: { - precachePages: [`/`, `/blog/*`, `/property/*`], + precachePages: [`/`, `/blog/*`, `/properties/*`], workboxConfig: { runtimeCaching: [ { diff --git a/gatsby-node.js b/gatsby-node.js index 4a49a80..b2bf982 100755 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -69,10 +69,10 @@ exports.createPages = async ({ actions, graphql, reporter }) => { // Create a page for each property propertyResult.data.allSanityProperty.nodes.forEach((node) => { // Use slug if available, otherwise use ID - const path = node.slug?.current - ? `/property/${node.slug.current}/` - : `/property/${node._id}/`; - + const path = node.slug?.current + ? `/properties/${node.slug.current}/` + : `/properties/${node._id}/`; + createPage({ path, component: propertyDetailTemplate, diff --git a/makefile b/makefile index 80bf368..61ac1bc 100755 --- a/makefile +++ b/makefile @@ -1,4 +1,17 @@ -init: +NODE_VERSION := $(shell node -v 2>/dev/null) +REQUIRED_NODE := v18.20.8 + +check-node: + @if [ -z "$(NODE_VERSION)" ]; then \ + echo "Error: Node.js is not installed. Please install Node.js $(REQUIRED_NOTE)"; \ + exit 1; \ + elif [ "$(NODE_VERSION)" != "$(REQUIRED_NODE)" ]; then \ + echo "Error: Wrong Node.js version. Please switch to $(REQUIRED_NODE) (current: $(NODE_VERSION))"; \ + echo "Run: nvm use lts/hydrogen"; \ + exit 1; \ + fi + +init: check-node yarn install clean: @@ -6,38 +19,39 @@ clean: rm -rf public || true yarn run clean -clean-dev: +clean-dev: check-node make clean || true make dev -dev: +dev: check-node yarn run develop -dev-m: +dev-m: check-node yarn run develop-net -clean-dev-m: +clean-dev-m: check-node make clean || true make dev-m -format: +format: check-node yarn run format -build: +build: check-node yarn run build -serve: +serve: check-node yarn run serve -serve-m: +serve-m: check-node yarn run serve-net -docker-buildup: +docker-buildup: check-node docker-compose build || true docker-compose up -docker-up: +docker-up: check-node docker-compose up + # deploy-live: # git pull upstream master || true # git push origin master \ No newline at end of file diff --git a/package.json b/package.json index 5b94085..b29a668 100755 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@types/mapbox-gl": "^3.4.1", "dotenv": "^16.4.5", "framer-motion": "^11.1.7", - "gatsby": "~5.13.4", + "gatsby": "^5.14.5", "gatsby-plugin-gatsby-cloud": "~5.13.1", "gatsby-plugin-google-adsense": "^1.1.3", "gatsby-plugin-google-gtag": "~5.13.1", diff --git a/src/components/PropertyCard.js b/src/components/PropertyCard.js index 9442aec..0a96ba2 100644 --- a/src/components/PropertyCard.js +++ b/src/components/PropertyCard.js @@ -15,13 +15,13 @@ const PropertyCard = ({ property }) => { const imageData = mainImage?.asset?.gatsbyImageData // Format location string from location object - const locationString = location?.city ? - `${location.city}${location.state ? `, ${location.state}` : ''}` : + const locationString = location?.city ? + `${location.city}${location.state ? `, ${location.state}` : ''}` : 'Location not specified' - + // Status badge styling const getStatusBadge = () => { - switch(status) { + switch (status) { case 'sold': return (
@@ -44,23 +44,23 @@ const PropertyCard = ({ property }) => { return null; } }; - + // Check if property is sold or rented const isUnavailable = status === 'sold' || status === 'rented'; - + return ( -
{/* Image with overlay link to full details */} - + {imageData ? ( <> - @@ -84,16 +84,16 @@ const PropertyCard = ({ property }) => {
*/} {getStatusBadge()}
- +
- +

{title}

{locationString}
- +
@@ -108,24 +108,24 @@ const PropertyCard = ({ property }) => { {area ? `${area} ${property.areaUnit || 'sqft'}` : 'N/A'}
- + {description && (
{description[0]?.children?.map(child => child.text).join(' ') || 'No description available'}
)} - + {amenities && amenities.length > 0 && ( -
+
{amenities.slice(0, 3).map((amenity, index) => ( - + {amenity} - - ))} -
+ + ))} +
)}
- +
{isUnavailable ? ( { Quick View - + { formType={FORM_TYPES.PROPERTY_ENQUIRY} buttonText="Enquire" buttonClass="bg-primary-600 w-full hover:bg-primary-700 text-white font-semibold py-3 px-4 rounded-md transition-colors" - data={{ + data={{ property: `${title} (${slug?.current || property._id})` }} /> diff --git a/src/components/PropertyPreview.js b/src/components/PropertyPreview.js index 35a0046..e0a2ea0 100644 --- a/src/components/PropertyPreview.js +++ b/src/components/PropertyPreview.js @@ -7,12 +7,12 @@ import PropertyImageCarousel from './PropertyImageCarousel' const PropertyPreview = () => { const { isPreviewOpen, previewProperty, closePreview } = usePropertyPreview() - + // References for focus management and accessibility const previewRef = useRef(null) const closeButtonRef = useRef(null) const lastFocusedElement = useRef(null) - + // Close on navigation useEffect(() => { // Function to handle any navigation @@ -21,49 +21,49 @@ const PropertyPreview = () => { closePreview(); } }; - + // Handle browser back/forward window.addEventListener('popstate', handleNavigate); - + return () => { window.removeEventListener('popstate', handleNavigate); }; }, [isPreviewOpen, closePreview]); - + // Handle focus management useEffect(() => { if (isPreviewOpen) { // Store the currently focused element to restore later lastFocusedElement.current = document.activeElement - + // Focus the close button when preview opens if (closeButtonRef.current) { closeButtonRef.current.focus() } - + // Trap focus inside preview const handleTabKey = (e) => { if (!previewRef.current) return - + // Get all focusable elements const focusableElements = previewRef.current.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ) const firstElement = focusableElements[0] const lastElement = focusableElements[focusableElements.length - 1] - + // If shift+tab pressed and focus on first element, move to last focusable element if (e.shiftKey && document.activeElement === firstElement) { lastElement.focus() e.preventDefault() - } + } // If tab pressed and focus on last element, move to first focusable element else if (!e.shiftKey && document.activeElement === lastElement) { firstElement.focus() e.preventDefault() } } - + const handleKeyDown = (e) => { if (e.key === 'Tab') { handleTabKey(e) @@ -71,13 +71,13 @@ const PropertyPreview = () => { closePreview() } } - + // Add event listener document.addEventListener('keydown', handleKeyDown) - + // Prevent body scrolling when preview is open document.body.style.overflow = 'hidden' - + // Remove event listener on cleanup return () => { document.removeEventListener('keydown', handleKeyDown) @@ -88,54 +88,54 @@ const PropertyPreview = () => { lastFocusedElement.current.focus() } }, [isPreviewOpen, closePreview]) - + // Prepare property data if available if (!previewProperty) return null - - const { - _id, - title, - slug, - location, + + const { + _id, + title, + slug, + location, // price, // priceUnit = 'gbp', - bedrooms, - bathrooms, - area, - areaUnit, - description, - amenities, - mainImage, - images, + bedrooms, + bathrooms, + area, + areaUnit, + description, + amenities, + mainImage, + images, status, propertyType, // publishedAt, councilTaxBand, tenure } = previewProperty - + // Format price with currency symbol // const formattedPrice = new Intl.NumberFormat('en-GB', { // style: 'currency', // currency: priceUnit?.toUpperCase() || 'GBP', // maximumFractionDigits: 0 // }).format(price || 0) - + // Format date // const formattedDate = publishedAt ? new Date(publishedAt).toLocaleDateString('en-GB', { // day: 'numeric', // month: 'long', // year: 'numeric' // }) : null - + // Format location string from location object - const locationString = location?.city ? - `${location.city}${location.state ? `, ${location.state}` : ''}` : + const locationString = location?.city ? + `${location.city}${location.state ? `, ${location.state}` : ''}` : 'Location not specified' - + // Status text and color based on property status const getStatusDetails = () => { - switch(status) { + switch (status) { case 'sold': return { color: 'bg-red-600', text: 'Sold' }; case 'for-rent': @@ -147,17 +147,17 @@ const PropertyPreview = () => { return { color: 'bg-primary-600', text: 'For Sale' }; } } - + const statusDetails = getStatusDetails() - + // Get first paragraph of description - const firstParagraph = description && description.length > 0 - ? description[0]?.children?.map(child => child.text).join(' ') + const firstParagraph = description && description.length > 0 + ? description[0]?.children?.map(child => child.text).join(' ') : 'No description available' // Property details URL - const propertyUrl = `/property/${slug?.current || _id}`; - + const propertyUrl = `/properties/${slug?.current || _id}`; + return ( {isPreviewOpen && ( @@ -171,7 +171,7 @@ const PropertyPreview = () => { className="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-[9000]" onClick={closePreview} /> - + {/* Preview Panel */} { > - + {/* Property content */}
{/* Property Image Carousel - Removed aspect ratio */}
- - +
{statusDetails.text}
- + {/* View Full Details Button - At top level */} - + {/* Content with no top padding */}

{title}

@@ -230,11 +230,11 @@ const PropertyPreview = () => { {locationString}
- + {/*
{formattedPrice}
*/} - + {/* Property key details */}
@@ -255,7 +255,7 @@ const PropertyPreview = () => {
- + {/* Property type and tenure */}
{propertyType && ( @@ -264,34 +264,34 @@ const PropertyPreview = () => { Property Type: {propertyType.charAt(0).toUpperCase() + propertyType.slice(1)}
)} - + {tenure && ( -
- - Tenure: {tenure} -
- )} - - {councilTaxBand && ( -
- - Council Tax: Band {councilTaxBand} -
- )} -
- - Status: {statusDetails.text} + + Tenure: {tenure}
+ )} + + {councilTaxBand && ( +
+ + Council Tax: Band {councilTaxBand} +
+ )} + +
+ + Status: {statusDetails.text} +
- + {/* Description preview with Read More link */} {description && ( )} - + {/* Amenities preview */} {amenities && amenities.length > 0 && (
@@ -325,10 +325,10 @@ const PropertyPreview = () => {
)} - + {/* Full detail link (bottom) */}
- { e.preventDefault();