ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 아마존 E-commerce 클론 -29) Google Map적용하기
    NODE.JS 2021. 5. 30. 15:38

     

    먼저 console.cloud.google.com에가서 프로젝트를 생성한다. 

     

     

     

     

    api & service->사용자 인증정보 클릭

     

    새로운 credential을 생성한다. 

    api키 생성

    키를 복사해서 

    라이브러리로 이동

     

    map을 검색후 javascript api클릭

    enable을 클릭하고 manage로 들어간다. 

    다시 라이브러리에서 places api 검색후 enable-manage클릭

     

    아까 복사한 api key를 최상위 폴더의 .env파일에 저장한다. 

    JWT_SECRET = somethingsecret
    
    PAYPAL_CLIENT_ID = AeYAR3plI8AMo9Tmu-NUmDnNzEk5K1Pmhu1csipoH_Y7RGnOi5s6fcJywoGgahvu4ENE9ran7hBCAqoq
    
    GOOGLE_API_KEY = AIzaSyD_nYNcNYSFLZiSqDN29mnbdKUTPb93Neg

     

    다음과 같이 googleAPI를 입력한다. 

    server.js(paypal처럼)

    app.get('/api/config/google', (req, res) => {
      res.send(process.env.GOOGLE_API_KEY || '');
    });
    

     

    frontend 폴더로 가서 npm install @react-google-maps/api 해준다.

    이제 지도를 보여줄 페이지를 만든다. 

    screens>MapScreen.js

    import React, { useEffect, useState,useRef } from 'react'
    import LoadingBox from '../components/LoadingBox'
    import {LoadScript,GoogleMap,StandaloneSearchBox,Marker} from '@react-google-maps/api';
    import axios from '../../node_modules/axios/index';
    import { USER_ADDRESSS_MAP_CONFIRM } from '../constants/userConstants';
    import { useDispatch } from 'react-redux';
     
    const libs=['places'];
    const defaultLocation = {lat:45.516, lng:-73.56};
    
    
    function MapScreen() {
    
        const [googleApiKey, setGoogleApiKey] = useState('')
        const [center, setCenter] = useState(defaultLocation)//얘는 googlemap을 위해서
        const [location, setLocation] = useState(center)//얘는 Marker를 위해서
    
        const mapRef = useRef(null);
        const placeRef = useRef(null);
        const markerRef = useRef(null);
    
        useEffect(() => {
            const fetch = async ()=>{
                const{data} = await axios.Cancel('/api/config/google');//백엔드에서 googleApiKey를 받아온다.
                setGoogleApiKey(data);
                getUserCurrentLocation();
            }
            fetch();
        }, [])
    
        const onLoad= (map)=>{
            mapRef.current = map;
        }
        const onMarkerLoad = (marker)=>{
            markerRef.current = marker;
        }
        const onLoadPlaces = (place)=>{
            placeRef.current = place
        }
        const onIdle = ()=>{
            setLocation({lat:mapRef.current.center.lat(), lng:mapRef.current.center.lng()})
        }
        const onPlacesChanged =()=>{
            const place = placeRef.current.getPlaces()[0].geometry.location;
            setCenter({lat:place.lat(), lng:place.lng()})
            setLocation({lat:place.lat(), lng:place.lng()})
        }
    
        const dispatch = useDispatch()
        const onConfirm = ()=>{
            const places = placeRef.current.getPlaces();
            if(places && places.length ===1){
                dispatch({
                    type:USER_ADDRESSS_MAP_CONFIRM,//주소를 확정짓는 action작성
                    payload:{
                        lat:location.lat,
                        lng:location.lng,
                        address:places[0].formatted_address,
                        name:places[0].name,
                        vicinity:places[0].vicinity,
                        googleAddressId:places[0].id
                    }
                })
                alert('location selecte successfully')
            }else{
                alert('Please enter your address')
            }
        }
        const getUserCurrentLocation = ()=>{//사용자의 현재위치 받아오기
            if(!navigator.geolocation){
                alert('Geolocation is not supported by this browser.')
            }else{
                navigator.geolocation.getCurrentPosition((position)=>{
                    setCenter({
                        lat:position.coords.latitude,
                        lng:position.coords.longitude
                    })
                    setLocation({
                        lat:position.coords.latitude,
                        lng:position.coords.longitude
                    })
                })
            }
        }
    
        return googleApiKey ? (
            <div className="full-container">
                <LoadScript libraries={libs} googleMapsApiKey={googleApiKey}>
                    <GoogleMap id="sample-map" mapContainerStyle={{height:'100%',width:'100%' }} center={center} zoom={15} onLoad={onLoad}onIdle={onIdle} >
                        <StandaloneSearchBox onLoad={onLoadPlaces} onPlacesChanged={onPlacesChanged}>
                            <div className="map-input-box">
                                <input type="text" placeholder="Enter your address" />
                                <button className="primary" onClick={onConfirm} type="button">Confirm</button>
                            </div>
                        </StandaloneSearchBox>
                        <Marker position={location} onLoad={onMarkerLoad}></Marker>
                    </GoogleMap>
                </LoadScript>
            </div>
        ) : <LoadingBox></LoadingBox>
    }
    
    export default MapScreen
    

     

    action은 스크린에서 바로 보내줬으니, 

    reducer를 작성하자. 

    export const userAddressmapReducer = (state={}, action)=>{
        switch(action.type){
            case USER_ADDRESSS_MAP_CONFIRM:
                return {address:action.payload};
            default:
                return state;
        }
    }
    

     

    store.js

        userAddressMap:userAddressmapReducer
    })

     

    이제 ShippingAddressScreen에서 지도에서 선택한 위치를 가져오도록 설정한다. 

    지도에서 선택하기 버튼을 만들고 버튼을 누르면 지도페이지로 가도록 설정한다. 

    제출 버튼을 누른 경우를 다음과 같이 수정해서 만약 지도에서 고른 경우엔 새로운 위치정보를 저장하고 ,

    선택하지 않은 경우에는 이전 주소로 그대로 간다. 

    import { saveShippingAddress } from '../actions/cartActions'
    
        const [lat, setLat] = useState(shippingAddress.lat);
        const [lng, setLng] = useState(shippingAddress.lng)
        
        const userAddressMap = useSelector(state => state.userAddressMap)
        const {address: addressMap} = userAddressMap
    
    
    	const dispatch = useDispatch()
    
        const submitHandler= (e) =>{
            e.preventDefault();
            const newLat = addressMap? addressMap.lat: lat;
            const newLng = addressMap? addressMap.lng:lng;
            if(addressMap){//지도에서 선택은 한 경우
                setLat(addressMap.lat);
                setLng(addressMap.lng);
            }
            let moveOn = true;
             if(!newLat ||!newLng){//지도가 올바르게 선택되지 않았다면 
                 moveOn = window.confirm(
                     'You did not set your location on map. Continue?'
                 )
             }
             if(moveOn){//지도에서 올바르게 선택된경우
                 dispatch(saveShippingAddress({fullName, address, city, postalCode, country, lat:newLat, lng:newLng}))//새로운 좌표로 등록해놓는다. 
                 props.history.push('/payment')
    
             }
        }
        const chooseOnMap=() =>{
            dispatch(saveShippingAddress({fullName, address, city, postalCode, country, lat, lng}))//지도 페이지로 가기전에 일단 현재데이터를 저장한다. 
            props.history.push('/map')
        }
        
        ...
        
                        <div>
                        <label htmlFor="country">Country</label>
                        <input type="text" id = "country" placeholder="Enter country" value = {country} onChange={e=>setCountry(e.target.value)} required/>
                    </div>
                    <div>
                        <label htmlFor="chooseOnMap">Location</label>
                        <button type="button" onClick={chooseOnMap}>Choose On Map</button>
                    </div>
                    <div>

    로그인 한 사람만 가게 privateRoute로 app.js에 다음과 같이 페이지를 추가해준다. 

    import MapScreen from './screens/MapScreen';
    
                  <PrivateRoute path="/map" component={MapScreen} exact></PrivateRoute>
    

     

    이렇게 뜨는 이유는 클라우드계정에 결제계정을 연결해놓지 않았기 때문이다. 

     

    추가로 주소를 지도에서 선택하고 입력이 완료되면 페이지를 이동시켜보도록 하자. 

    mapScreen.js

    function MapScreen(props) {
    
        const onConfirm = ()=>{
            const places = placeRef.current.getPlaces();
            if(places && places.length ===1){
                dispatch({
                    type:USER_ADDRESSS_MAP_CONFIRM,
                    payload:{
                        lat:location.lat,
                        lng:location.lng,
                        address:places[0].formatted_address,
                        name:places[0].name,
                        vicinity:places[0].vicinity,
                        googleAddressId:places[0].id
                    }
                })
                alert('location selecte successfully')
                props.history.push('/shipping')
            }else{
                alert('Please enter your address')
            }
        }

     

    추가로 이제 ordermodel에 lat, lng를 추가하자. 

        shippingAddress:{
            fullName:{
                type:String,
                required:true
            },
            address:{
                type:String,
                required:true
            },
            city:{
                type:String,
                required:true
            },
            postalCode:{
                type:String,
                required:true
            },
            country:{
                type:String,
                required:true
            },
            lat: Number,
            lng:Number
        },  

    지도에서 선택하지않으면 뜨는 화면

    지도에서 선택하고 나면 order>shippingAddress>lng, lat가 추가된것을 확인할 수 있다. 

     

     

     

     

     

     

     

     

Designed by Tistory.