MapAddress
unknown
javascript
9 months ago
12 kB
4
Indexable
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;
Editor is loading...
Leave a Comment