MapAddress
import { useState, useCallback, useEffect, useRef, useReducer, useLayoutEffect, } from "react"; import BillingDetailsForm from "../address/BilllingDetailsForm"; import Loader from "../loader/Loader"; import { Sheet } from "react-modal-sheet"; import { GoogleMap, useJsApiLoader, Marker, Autocomplete, } from "@react-google-maps/api"; import "../map/style.scss"; import { Skeleton, Stack } from "@mui/material"; import { TbCurrentLocation } from "react-icons/tb"; import { IoIosClose } from "react-icons/io"; import { IoChevronBackCircleSharp } from "react-icons/io5"; import { GrLocationPin } from "react-icons/gr"; import { removePlusCode } from "../../utils/helper"; const initialState = { loading: false, erroMsg: "", userCurrentPosition: null, userSearchedPosition: null, markerPosition: null, mapInstance: null, userSearchedAddress: "", userCurrentAddress: "", inputFieldValue: "", isBillingDetailsFormOpen: false, }; function reducer(state, action) { switch (action.type) { case "userCurrentPosition/updated": return { ...state, userCurrentPosition: { ...action.payload.userCurrentPosition }, markerPosition: { ...action.payload.userCurrentPosition }, userCurrentAddress: action.payload.userCurrentAddress, }; case "userSearchedPosition/updated": return { ...state, userSearchedPosition: { ...action.payload.userSearchedPosition }, markerPosition: { ...action.payload.userSearchedPosition }, userSearchedAddress: action.payload.userSearchedAddress, inputFieldValue: action.payload.userSearchedAddress, }; case "userSearchedAddress/cleared": return { ...state, userSearchedAddress: "", inputFieldValue: "" }; case "inputFieldValue/update": return { ...state, inputFieldValue: action.payload }; case "loading/set": return { ...state, loading: action.payload }; case "geolocation/error": return { ...state, erroMsg: action.payload, loading: false }; case "mapInstance/centered": return { ...state, mapInstance: action.payload }; case "billingDetailsForm/toggled": return { ...state, isBillingDetailsFormOpen: action.payload }; default: throw new Error("Unknow action at MapAddress"); } } function MapAddress({ onShowAddressDetailForm, isSmallScreen = false, showAddressDetailForm, phoneNumberInput, addressFromMap, onSaveUserEnteredAddressDetails, }) { const [ { userCurrentPosition, userSearchedPosition, markerPosition, loading, erroMsg, mapInstance, userSearchedAddress, userCurrentAddress, inputFieldValue, isBillingDetailsFormOpen, }, dispatch, ] = useReducer(reducer, initialState); const autoCompleteRef = useRef(null); const inputRef = useRef(null); const { isLoaded } = useJsApiLoader({ id: "google-map-script", googleMapsApiKey: import.meta.env.VITE_GM_API_KEY, libraries: ["places"], }); // console.log(isLoaded); // console.log("userCurrentPosition: ", userCurrentPosition); // console.log("userSearchedPosition: ", userSearchedPosition); // console.log(import.meta.env.VITE_GM_API_KEY); // console.log("Searched Address : ", userSearchedAddress); // console.log("Current Address : ", userCurrentAddress); // EFFECT to fetch the user latitude and longitude on initail Load useEffect(function () { // console.log("EEFECT EXECUTED"); dispatch({ type: "loading/set", payload: true }); if (!navigator.geolocation) { dispatch({ type: "geolocation/error", payload: "Your browser does not support geolocation", }); return; } navigator.geolocation.getCurrentPosition( // SUCCESS Callback (success) => { // console.log("success.coords.latitude : ", success.coords.latitude); const { latitude, longitude } = success.coords; // Fetch User Address using lat and lng with Geocoder API const geocoder = new window.google.maps.Geocoder(); geocoder.geocode( { location: { lat: latitude, lng: longitude } }, (results, status) => { if (status === "OK" && results.at(0)) { // console.log(results.at(0)); const address = results.at(0)?.formatted_address; // console.log(address); dispatch({ type: "userCurrentPosition/updated", payload: { userCurrentPosition: { lat: latitude, lng: longitude }, userCurrentAddress: address, }, }); } else { console.error("Geocoder failed due to: " + status); } }, ); }, // ERROR Callback (error) => { console.log(error.message); dispatch({ type: "geolocation/error", payload: error.message, }); }, ); }, []); // Handle place selection from suggestions list of google-api from Autocomplete Component function handlePlaceSelect() { if (autoCompleteRef.current) { const place = autoCompleteRef.current.getPlace(); if (place && place.geometry) { const location = place.geometry.location; const address = place.formatted_address; console.log("address : ", address); dispatch({ type: "userSearchedPosition/updated", payload: { userSearchedPosition: { lat: location.lat(), lng: location.lng() }, userSearchedAddress: address, }, }); mapInstance?.panTo({ lat: location.lat(), lng: location.lng() }); } } } // Handle input field focus on clicking the "Change" button function handleChangeAddress() { if (inputRef.current) inputRef.current.focus(); } if (!isLoaded) return ( <Stack spacing={0.5}> <Skeleton variant="rectangular" sx={{ width: "100%", height: 420, bgcolor: "grey.400", border: "none", }} /> <Skeleton variant="text" sx={{ fontSize: "2rem" }} /> <Skeleton variant="text" sx={{ fontSize: "4rem" }} /> </Stack> ); return ( <> <div className="tw-relative"> {/* Input Field Wrapped with Autocomplete for typed place suggestion list */} <Autocomplete onLoad={(autocomplete) => (autoCompleteRef.current = autocomplete)} onPlaceChanged={handlePlaceSelect} > <form action="" onSubmit={(e) => { e.preventDefault(); console.log("SUBMITTED"); }} className="tw-absolute tw-left-1/2 tw-top-4 tw-z-10 tw-w-10/12 -tw-translate-x-1/2 tw-transform" > <input ref={inputRef} type="text" placeholder="🔍 Enter your address" className="tw-rounded-xl tw-p-1.5 tw-pl-3 placeholder:tw-pl-2 placeholder:tw-font-light" value={inputFieldValue} onChange={(e) => dispatch({ type: "inputFieldValue/update", payload: e.target.value, }) } /> </form> </Autocomplete> {/* GOOGLE MAP BOX */} <GoogleMap mapContainerStyle={{ width: "100%", // Make the map responsive height: isSmallScreen ? "420px" : "400px", }} center={userCurrentPosition} zoom={18} options={{ mapTypeControl: false, streetViewControl: false, fullscreenControl: false, zoomControl: false, // keyboardShortcuts: false, }} onLoad={ (map) => dispatch({ type: "mapInstance/centered", payload: map }) // This map parameter is a fully functional Google Maps object, providing methods and properties to interact with the map (e.g., panning, zooming, event listeners). } > {/* CURRENT LOACTION MARKER BOX */} <Marker icon={{ url: "/svg/location-pin.svg", scaledSize: new window.google.maps.Size(40, 40), origin: new window.google.maps.Point(0, 0), anchor: new window.google.maps.Point(20, 40), }} key={crypto.randomUUID()} position={markerPosition} /> </GoogleMap> {/* Current loaction Button */} <button onClick={() => { mapInstance?.panTo(userCurrentPosition); // Clear user searched address details when user moves back to currect loaction dispatch({ type: "userSearchedAddress/cleared" }); }} className="tw-absolute tw-bottom-4 tw-left-1/2 tw-flex -tw-translate-x-1/2 tw-transform tw-items-center tw-justify-center tw-gap-2 tw-whitespace-nowrap tw-rounded-md tw-border-[1px] tw-border-zcGreen tw-bg-white tw-px-2 tw-py-1.5 tw-text-xs sm:tw-w-fit" > <TbCurrentLocation className="tw-text-lg tw-text-zcGreen" /> <span className="tw-font-semibold">Current Location</span> </button> </div> <div className="tw-mt-7"> {/* Displaying Address and Change Button */} <div className="tw-flex tw-items-center tw-justify-between tw-gap-4 tw-px-3 tw-py-5"> <p className="tw-flex tw-overflow-x-scroll tw-whitespace-nowrap tw-text-sm"> <span> <GrLocationPin className="tw-text-4xl tw-text-zcGreen" /> </span> <span className="tw-self-center"> {removePlusCode(userSearchedAddress) || removePlusCode(userCurrentAddress)} </span> </p> <button onClick={handleChangeAddress} className="tw-border tw-border-zcGreen/60 tw-px-2.5 tw-py-2 tw-text-xs tw-font-semibold" > Change </button> </div> {/* Confirm location and Proceed */} <button onClick={() => // onShowAddressDetailForm(userSearchedAddress || userCurrentAddress) dispatch({ type: "billingDetailsForm/toggled", payload: true }) } className="tw-mx-auto tw-mt-3 tw-block tw-w-11/12 tw-bg-zcGreen tw-px-4 tw-py-2 tw-font-semibold tw-text-stone-700" > Confirm location and Proceed </button> </div> {/* TESTING */} {/* {showAddressDetailForm && ( <BillingDetailsForm phoneNumberInput={phoneNumberInput} addressFromMap={addressFromMap} onSaveUserEnteredAddressDetails={onSaveUserEnteredAddressDetails} /> )} */} {/* <Sheet //* Open sheet only when window width is <= 480 and the isMobileSheetOpen is set to true isOpen={isSmallScreen && isModalSheetOpen} // isOpen={false} onClose={() => dispatch({ type: "sheetModal/close" })} detent="content-height" > */} <Sheet isOpen={isSmallScreen && isBillingDetailsFormOpen} onClose={() => dispatch({ type: "billingDetailsForm/toggled", payload: false }) } detent="content-height" > <Sheet.Container> <Sheet.Header></Sheet.Header> <Sheet.Content> <BillingDetailsForm phoneNumberInput={phoneNumberInput} addressFromMap={addressFromMap} onSaveUserEnteredAddressDetails={onSaveUserEnteredAddressDetails} /> </Sheet.Content> </Sheet.Container> </Sheet> </> ); } export default MapAddress;
Leave a Comment