"use client"; import { useQuery } from "@apollo/client"; import { useState, useMemo, useEffect, useRef } from "react"; import { GET_CHANNELS, GetChannelsData, Warehouse, } from "@/graphql/queries/getChannels"; import { useAppConfiguration } from "@/app/components/providers/ServerAppConfigurationProvider"; import EmptyState from "@/app/components/reuseableUI/emptyState"; import { SearchIcon } from "@/app/utils/svgs/searchIcon"; import ReactPaginate from "react-paginate"; import { ChevronDownIcon } from "@/app/utils/svgs/chevronDownIcon"; interface ExtendedWarehouse extends Warehouse { distance?: string; phone?: string; website?: string; latitude?: string; longitude?: string; hours?: string; comments?: string; pocFirstName?: string; pocLastName?: string; state?: string; } // Distance calculation utility function (Haversine formula) function calculateDistance( lat1: number, lon1: number, lat2: number, lon2: number ): number { const R = 3959; // Earth's radius in miles const dLat = ((lat2 - lat1) * Math.PI) / 180; const dLon = ((lon2 - lon1) * Math.PI) / 180; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } // Distance filter options const distanceOptions = [ { value: "", label: "Any Distance" }, { value: "25", label: "Within 25 miles" }, { value: "50", label: "Within 50 miles" }, { value: "100", label: "Within 100 miles" }, { value: "250", label: "Within 250 miles" }, { value: "500", label: "Within 500 miles" }, { value: "1000", label: "Within 1000 miles" }, ]; export default function StoreLocator() { const { getGoogleMapsConfig, getDealerLocatorToken } = useAppConfiguration(); const dealerToken = getDealerLocatorToken(); const isConfigured = Boolean(dealerToken); const { data, loading, error } = useQuery(GET_CHANNELS, { skip: !isConfigured, context: { headers: { ...(dealerToken && { "Authorization-Bearer": dealerToken }), }, }, }); const [searchTerm, setSearchTerm] = useState(""); const [selectedCountry, setSelectedCountry] = useState(""); const [selectedDistance, setSelectedDistance] = useState(""); const [getDistanceValue, setGetDistanceValue] = useState( "Please enable your location" ); const [userLocation, setUserLocation] = useState<{ lat: number; lng: number; } | null>(null); const mapRef = useRef(null); const mapInstanceRef = useRef(null); const [mapLoaded, setMapLoaded] = useState(false); const [mapError, setMapError] = useState(""); const [currentPage, setCurrentPage] = useState(1); const pageSize = 20; const dealerWarehouses = useMemo(() => { if (!data?.channels) return []; const dealerChannel = data.channels.find( (channel) => channel.slug === "dealers" ); if (!dealerChannel) return []; return dealerChannel.warehouses.map((warehouse) => { const extendedWarehouse: ExtendedWarehouse = { ...warehouse, distance: "Please enable your location", phone: warehouse?.address?.phone || "", website: "", latitude: "", longitude: "", hours: "", comments: "", pocFirstName: "", pocLastName: "", state: "", }; // Extract additional data from privateMetadata warehouse.privateMetadata?.forEach((meta) => { switch (meta.key) { case "lat": extendedWarehouse.latitude = meta.value; break; case "lng": extendedWarehouse.longitude = meta.value; break; case "url": extendedWarehouse.website = meta.value; break; case "hours": extendedWarehouse.hours = meta.value; break; case "comments": extendedWarehouse.comments = meta.value; break; case "poc_firstname": extendedWarehouse.pocFirstName = meta.value; break; case "poc_lastname": extendedWarehouse.pocLastName = meta.value; break; case "state": extendedWarehouse.state = meta.value; break; } }); // Calculate distance if user location and warehouse coordinates are available if ( userLocation && extendedWarehouse.latitude && extendedWarehouse.longitude && extendedWarehouse.latitude !== "null" && extendedWarehouse.longitude !== "null" ) { const warehouseLat = parseFloat(extendedWarehouse.latitude); const warehouseLng = parseFloat(extendedWarehouse.longitude); if (!isNaN(warehouseLat) && !isNaN(warehouseLng)) { const distance = calculateDistance( userLocation.lat, userLocation.lng, warehouseLat, warehouseLng ); extendedWarehouse.distance = `${distance.toFixed(1)} miles`; setGetDistanceValue(`${distance.toFixed(1)} miles`); } } return extendedWarehouse; }); }, [data, userLocation]); // Get unique countries for dropdown const availableCountries = useMemo(() => { const countries = dealerWarehouses .map((w) => w.address?.country?.country) .filter( (country, index, arr) => country && arr.indexOf(country) === index ) .sort(); return [ { value: "", label: "All Countries" }, ...countries.map((country) => ({ value: country, label: country })), ]; }, [dealerWarehouses]); const filteredWarehouses = useMemo(() => { let filtered = dealerWarehouses; // Filter by search term if (searchTerm) { filtered = filtered.filter( (warehouse) => warehouse.name.toLowerCase().includes(searchTerm.toLowerCase()) || warehouse.address?.city ?.toLowerCase() .includes(searchTerm.toLowerCase()) || warehouse.address?.streetAddress1 ?.toLowerCase() .includes(searchTerm.toLowerCase()) ); } // Filter by country if (selectedCountry) { filtered = filtered.filter( (warehouse) => warehouse.address?.country?.country === selectedCountry ); } // Filter by distance if (selectedDistance && userLocation) { const maxDistance = parseFloat(selectedDistance); filtered = filtered.filter((warehouse) => { if ( !warehouse.latitude || !warehouse.longitude || warehouse.latitude === "null" || warehouse.longitude === "null" ) { return false; } const warehouseLat = parseFloat(warehouse.latitude); const warehouseLng = parseFloat(warehouse.longitude); if (isNaN(warehouseLat) || isNaN(warehouseLng)) { return false; } const distance = calculateDistance( userLocation.lat, userLocation.lng, warehouseLat, warehouseLng ); return distance <= maxDistance; }); } // Sort by distance if user location is available if (userLocation) { filtered = filtered.sort((a, b) => { const distanceA = a.distance && a.distance !== "N/A" ? parseFloat(a.distance.split(" ")[0]) : Infinity; const distanceB = b.distance && b.distance !== "N/A" ? parseFloat(b.distance.split(" ")[0]) : Infinity; return distanceA - distanceB; }); } return filtered; }, [ dealerWarehouses, searchTerm, selectedCountry, selectedDistance, userLocation, ]); useEffect(() => { setCurrentPage(1); }, [searchTerm, selectedCountry, selectedDistance, userLocation]); const totalPages = Math.max( 1, Math.ceil(filteredWarehouses.length / pageSize) ); const paginatedWarehouses = useMemo(() => { const startIndex = (currentPage - 1) * pageSize; return filteredWarehouses.slice(startIndex, startIndex + pageSize); }, [filteredWarehouses, currentPage, pageSize]); const handlePageChange = ({ selected }: { selected: number }) => { const targetPage = selected + 1; setCurrentPage(targetPage); if (typeof window !== "undefined") { window.scrollTo({ top: 0, behavior: "smooth", }); } }; // Load Google Maps useEffect(() => { const loadGoogleMaps = () => { if (window.google) { setMapLoaded(true); return; } // Check if Google Maps script is already being loaded const existingScript = document.querySelector( 'script[src*="maps.googleapis.com/maps/api/js"]' ) as HTMLScriptElement; if (existingScript) { // If script exists, wait for it to load existingScript.addEventListener("load", () => setMapLoaded(true)); existingScript.addEventListener("error", () => setMapError("Failed to load Google Maps. Please check your API key.") ); return; } const mapsConfig = getGoogleMapsConfig(); const apiKey = mapsConfig?.api_key; if (!apiKey) { setMapError( "Google Maps API key is required. Please check your app configuration." ); return; } const script = document.createElement("script"); script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`; script.async = true; script.defer = true; script.onload = () => setMapLoaded(true); script.onerror = () => setMapError("Failed to load Google Maps. Please check your API key."); document.head.appendChild(script); }; loadGoogleMaps(); }, [getGoogleMapsConfig]); // Initialize map when Google Maps is loaded useEffect(() => { if (!mapLoaded || !mapRef.current) return; // Clear the map container to prevent duplicate maps if (mapRef.current && mapRef.current.innerHTML) { mapRef.current.innerHTML = ""; } // Default center (US center) let defaultCenter = { lat: 39.8283, lng: -98.5795 }; let defaultZoom = 5; // If we have warehouses with coordinates, use the first valid one const warehouseWithCoords = filteredWarehouses.find( (w) => w.latitude && w.longitude && w.latitude !== "null" && w.longitude !== "null" ); if (warehouseWithCoords) { defaultCenter = { lat: parseFloat(warehouseWithCoords.latitude!), lng: parseFloat(warehouseWithCoords.longitude!), }; defaultZoom = filteredWarehouses.length === 1 ? 12 : 8; } // If user location is available, center on user if (userLocation) { defaultCenter = userLocation; defaultZoom = 10; } const map = new window.google.maps.Map(mapRef.current, { zoom: defaultZoom, center: defaultCenter, mapTypeControl: true, streetViewControl: true, fullscreenControl: true, minZoom: 3, maxZoom: 18, gestureHandling: "greedy", restriction: { latLngBounds: { north: 85, south: -85, west: -180, east: 180, }, strictBounds: true, }, draggableCursor: "default", }); // Store map instance mapInstanceRef.current = map; // Enforce zoom limits to prevent world wrapping visibility window.google.maps.event.addListener(map, "zoom_changed", function () { const zoom = map.getZoom(); if (zoom !== undefined && zoom < 3) { map.setZoom(3); } }); const bounds = new window.google.maps.LatLngBounds(); let validMarkers = 0; // Add user location marker if available if (userLocation) { new window.google.maps.Marker({ position: userLocation, map: map, title: "Your Location", icon: { url: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent( '' ), scaledSize: new window.google.maps.Size(24, 24), }, }); bounds.extend(userLocation); } filteredWarehouses.forEach((warehouse) => { if ( warehouse.latitude && warehouse.longitude && warehouse.latitude !== "null" && warehouse.longitude !== "null" ) { const lat = parseFloat(warehouse.latitude); const lng = parseFloat(warehouse.longitude); if (!isNaN(lat) && !isNaN(lng)) { const marker = new window.google.maps.Marker({ position: { lat, lng }, map: map, title: warehouse.name, icon: { url: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent( '' ), scaledSize: new window.google.maps.Size(32, 32), }, }); const infoWindow = new window.google.maps.InfoWindow({ content: `

${warehouse.name}

${ warehouse.address?.streetAddress1 || "" }

${ warehouse.address?.city || "" }, ${ warehouse.state || warehouse.address?.country?.code || "" } ${warehouse.address?.postalCode || ""}

${ warehouse.phone && warehouse.phone !== "null" ? `

Phone: ${warehouse.phone}

` : "" } ${ warehouse.hours && warehouse.hours !== "null" ? `

Hours: ${warehouse.hours}

` : "" } ${ warehouse.website && warehouse.website !== "null" ? `

Visit Website

` : "" } ${ warehouse.distance && warehouse.distance !== "N/A" ? `

Distance: ${warehouse.distance}

` : "" }
`, }); marker.addListener("click", () => { infoWindow.open(map, marker); }); bounds.extend({ lat, lng }); validMarkers++; } } }); // Fit bounds if we have multiple points or auto-fit for better view if (validMarkers > 0 && (userLocation || validMarkers > 1)) { map.fitBounds(bounds); // Set zoom level constraints to avoid zooming too close or too far const listener = window.google.maps.event.addListener( map, "bounds_changed", () => { const zoom = map.getZoom(); if (zoom !== undefined) { // Prevent zooming too close if (zoom > 15) { map.setZoom(15); } // Prevent zooming too far out (where world wrapping is visible) if (zoom < 3) { map.setZoom(3); } } window.google.maps.event.removeListener(listener); } ); } // Cleanup function to clear map when dependencies change return () => { if (mapRef.current) { mapRef.current.innerHTML = ""; } mapInstanceRef.current = null; }; }, [mapLoaded, filteredWarehouses, userLocation]); if (!isConfigured) { return ( ); } if (loading) { return (
); } if (error) { return (
); } return (
{userLocation && (
✓ Using your location for distance calculations
)}
{/* Search and Filter Controls */}
setSearchTerm(e.target.value)} className="w-full h-10 pl-10 pr-4 text-sm border border-[var(--color-secondary-300)] bg-white focus:outline-none focus:ring-2 focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-600)] text-[var(--color-secondary-900)] placeholder-[var(--color-secondary-500)]" /> {/* Search icon */}
{SearchIcon}
{/* Clear button */} {searchTerm && (
)}
{/* Google Map */}
{mapError ? (
⚠️ Map Error
{mapError}
) : !mapLoaded ? (
Loading map...
) : null}
{filteredWarehouses.length === 0 ? ( ) : ( <>
{paginatedWarehouses.map((warehouse) => ( { e.currentTarget.style.backgroundColor = "var(--color-secondary-100)"; }} onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = "transparent"; }} > ))}
Name Distance Address
{warehouse.name}
{warehouse.email || "No email available"}
{warehouse.website && warehouse.website !== "null" && (
Website:{" "} {warehouse.website}
)} {warehouse.phone && warehouse.phone !== "null" && (
Phone: {warehouse.phone}
)} {warehouse.hours && warehouse.hours !== "null" && (
Hours: {warehouse.hours}
)} {(warehouse.pocFirstName && warehouse.pocFirstName !== "null") || (warehouse.pocLastName && warehouse.pocLastName !== "null") ? (
Contact:{" "} {warehouse.pocFirstName !== "null" ? warehouse.pocFirstName : ""}{" "} {warehouse.pocLastName !== "null" ? warehouse.pocLastName : ""}
) : null} {warehouse.comments && warehouse.comments !== "null" && (
{warehouse.comments}
)}
{warehouse.distance || "N/A"}
{warehouse.address?.streetAddress1} {warehouse.address?.streetAddress2 && warehouse.address.streetAddress2.trim() !== "" && ( <>
{warehouse.address.streetAddress2} )}
{warehouse.address?.city},{" "} {warehouse.state || warehouse.address?.country?.code}{" "} {warehouse.address?.postalCode}
{warehouse.address?.country?.country} {warehouse.latitude && warehouse.longitude && warehouse.latitude !== "null" && warehouse.longitude !== "null" && ( <>
Coordinates: {warehouse.latitude},{" "} {warehouse.longitude} )}
{/* Pagination */} {filteredWarehouses.length > pageSize && (
{ChevronDownIcon} Prev

} nextLabel={

Next {ChevronDownIcon}

} renderOnZeroPageCount={undefined} containerClassName="flex items-center justify-center gap-2 font-secondary" pageClassName="list-none" pageLinkClassName="px-3 py-2 !text-base bg-[var(--color-secondary-200)] text-gray-900 hover:opacity-80" previousClassName="list-none" previousLinkClassName="px-3 py-2 !text-base bg-[var(--color-secondary-200)] text-gray-700 hover:opacity-80 flex items-center gap-1" nextClassName="list-none" nextLinkClassName="px-3 py-2 !text-base bg-[var(--color-secondary-200)] text-gray-700 hover:opacity-80 flex items-center gap-1" activeClassName="list-none" activeLinkClassName="px-3 py-2 !text-base !bg-[var(--color-primary-600)] text-white border border-[var(--color-primary-600)]" disabledClassName="opacity-50 pointer-events-none" breakLabel={"..."} breakLinkClassName="px-2 !text-base text-gray-500" />
)} )}
); }