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) */}