본문 바로가기
프로젝트/코드스테이츠 - 2주프로젝트

프로젝트 8 코드스테이츠 119 : 카카오맵으로 약속장소 정하기 구현

by 안뇽! 2021. 11. 17.
반응형

카카오맵으로 약속장소 정하는 기능 구현

3일이 걸렸다..

이걸 할 수 있을까 삽질하는건 아닌가 하는 생각이 들었지만 결국 해버렸다.

useEffect의 의존성배열에 state를 넣어주니 검색한 장소가 다음 검색타이밍에서 나타나던 버그가 사라졌다.

흥분된다...

 

간단하다.

 

지도에 클릭한 지점을 약속장소로 정하는 것이다.

카카오택시에 지도 찍으면 택시기사님한테 나의 위치가 전송되어 만남약속장소가 되는것과 같다.

콘솔창 : 만들기 버튼 클릭시 콘솔창에 뜨는 카카오API 에러는 원인을 찾고 있습니다. 저 에러로 인한 문제는 아직 발견되지 않았습니다.

 

홈화면에는 내가 거주하고 있는 지역의 모각코가 표시된다.

제천에 사는사람이 제천에 방을 만들면 홈화면 페이지에서 볼 수 있고, 내 모임 페이지에서도 당연히 볼 수 있다.

내가 사는 지역에 방 만들기

하지만 제천에 사는 사람이 서울에 방을 만들면 홈화면 페이지에서는 볼 수 없다. 왜냐면 홈화면에서는 같은지역의 모임만 보이기 때문이다.

하지만 내모임 페이지에서는 지역에 상관없이 내가 속한 모임이 모두 보인다.

내가 살고있지 않은 지역에 방 만들기


카카오맵 구현

  1. 홈화면 - 유저의 현재 주소

홈화면이 렌더링될때마다 사용자 위치의 좌표를 얻고 카카오 API를 통해 사용자의 좌표를 주소로 변환합니다.

변환된 주소는 데이터베이스에 '유저의 주소' 로 저장됩니다.

이후 필요할때마다 데이터베이스에서 '유저의 주소'를 불러옵니다.

 

//렌더링될때마다 사용자의 좌표를 얻고 이를 KAKAO API라 주소로 변환한다.
//변환된 주소는 서버로 보내진다.

function getLocation () {navigator.geolocation.getCurrentPosition(onGeoOk,onGeoError)}
  useEffect(()=>{
      getLocation ()

  },[region,city,isLogin])
  
  function onGeoOk(position){
    const lat = position.coords.latitude;
    const lon = position.coords.longitude;
    dispatch(changeLon(lon))
    dispatch(changeLat(lat))

    // 유저의 좌표를 주소로 변환하는 카카오 API
    axios.get(`https://dapi.kakao.com/v2/local/geo/coord2address.json?x=${lon}&y=${lat}&input_coord=WGS84`
    ,{headers:{Authorization:`KakaoAK ${process.env.REACT_APP_REST_API}`}}
    )
    .then(res=>{
    //주소
        dispatch(changeAddress(res.data.documents[0].address.address_name))
    //도
       dispatch(changeRegion(res.data.documents[0].address.region_1depth_name))
	//시
		dispatch(changeCity(res.data.documents[0].address.region_2depth_name)) 
        dispatch(isLoadingHandler(false))
    })
    .then(res=>{

      // 2. DB에 유저의 주소 저장
      axios.post('http://localhost:4000/users/location-registration',{
          region,city
        },{headers:{withCredentials:"true",Authorization : `Bearer ${cookies.accessToken}`, contentType:"application/json"}}
        )
    })
    // .then(res=>console.log(res))
    .catch(e=>console.log(e))
  }
  function onGeoError(){
      alert("위치권한을 확인해주세요");
  }

 

 

  1. 방만들기 - 약속장소의 주소

첫 화면에서 모각코 만들기 버튼을 누르면 생성되는 모달창에 지도가 있습니다. 지도의 초기화면은 유저의 주소에 마커가 찍혀있는 화면입니다.

지도의 어느지점을 클릭하면 클릭한 지점의 도로명주소가 적혀있는 마커가 생성됩니다.

클릭한 지점의 좌표는 주소로 변환되고 '만들기' 버튼을 누르면 클릭한지점의 좌표가 데이터베이스에

'모각코 약속장소의 주소'로 저장됩니다.

이후 필요할때마다 데이터베이스에서 '모각코 약속장소의 주소' 를 불러옵니다.

만들기 버튼 클릭시 콘솔창에 뜨는 카카오API 에러는 원인을 찾고 있습니다. 저 에러로 인한 문제는 아직 발견되지 않음 -> 4주프로젝트하면서 발견함. 처음 전달되는 props가 빈배열이기 때문에 생기는 메시지임

import React, { useEffect, useState } from "react"
import { Link, useHistory } from "react-router-dom";
import { useSelector,shallowEqual, useDispatch } from 'react-redux';
import { makeStyles } from "@material-ui/core/styles"
import { userInfo } from "../../dummy";
import axios from "axios";
import AppBar from "../appbar/AppBar"
import LinearProgress from "@material-ui/core/LinearProgress"
import { withCookies, Cookies, useCookies } from 'react-cookie';
import { isLoadingHandler, isShowCreateRoomModalHandler } from "../../../redux/actions/actions";

const useStyles = makeStyles(theme => ({
  
  map: {
    width: "100%",
    height: "100%"
  },
  progress: {
    position: "fixed",
    zIndex: 9999,
    width: "100%",
    top: 56,
    [theme.breakpoints.up("sm")]: {
      top: 64
    }
  }
}))

const Map = ({ title ,meetingTime ,population, description }) => {
  const history = useHistory();
  const {lat,lon,region,city, add} = useSelector((state=>state.locationReducer),shallowEqual)
  const [pending, setPending] = useState(true)
  const [map, setMap] = useState(null)
  const kakao = window.kakao
  const classes = useStyles()
  const dispatch = useDispatch()
  const [meetingPlace,setMeetingPlace] = useState([region,city,add])
  const isLoading = useSelector(state => state.isLoadingReducer.isLoading)
  const [centerPosition,setCenterPosition] = useState([lat,lon])
  const [createdRoom,setCreatedRoom] = useState(false)
  /**
   * 장소 검색
   * @param keyword 검색어
   */
  
  const [cookies, setCookie, removeCookie] = useCookies(['cookie-name']);

  const searchPlace = keyword => {
    setPending(true)
    const places = new kakao.maps.services.Places()
    places.keywordSearch(keyword, (result, status) => {
      if (status === kakao.maps.services.Status.OK) {
        const firstItem = result[0]
        const { y, x } = firstItem
        
        const moveLatLng = new kakao.maps.LatLng(y,x)
        map.panTo(moveLatLng)
          
        
        
      } else if (status === kakao.maps.services.Status.ZERO_RESULT) {
        alert("검색 결과가 없습니다.")
      } else {
        alert("서비스에 문제가 발생했습니다. 잠시 후 다시 시도해주세요.")
      }
      setPending(false)
    })
  }
  

  const handleSearch = searchText => {
    if (searchText) {
      searchPlace(searchText)
    }
  }



  useEffect(() => {
    //  만들기 버튼이 눌리면 클릭한 지점의 주소를 방의 주소로 DB에 저장한다.
    if(createdRoom===true){
      axios.post('http://localhost:4000/rooms/new-room',{
        title ,population, description,
        meeting_time : meetingTime,
        region:meetingPlace[0],
        city : meetingPlace[1],
        UserId : userInfo[0].id,
        meeting_place:meetingPlace[2]
      },{
          headers:{contentType:"application/json",withCredentials:"true",Authorization : `Bearer ${cookies.accessToken}`}
        })
        .then(res=>{
          
          
          history.push(`/roominfo/${res.data.data.id}`)
          dispatch(isLoadingHandler(true))
          dispatch(isShowCreateRoomModalHandler(false))
          console.log(res.data.data.id)
          return res
        })
        .then(res=>{
          const {id,title,description,population,UserId,region,city,meetingPlace} = res.data.data
          dispatch(isLoadingHandler(false))
      }) 
      setCreatedRoom(false)
    }
    console.log("effect")
    const container = document.getElementById("map") //지도를 담을 영역의 DOM 레퍼런스
    console.log('여기왔니')
    let options = {
      //지도를 생성할 때 필요한 기본 옵션
      center: new kakao.maps.LatLng(centerPosition[0],centerPosition[1]), //지도의 중심좌표.
      level: 3 //지도의 레벨(확대, 축소 정도)
    }
   
    
    const map = new kakao.maps.Map(container, options) //지도 생성 및 객체 리턴
    //마커가 표시될 위치입니다.
    var marker = new kakao.maps.Marker({ 
      // 지도 중심좌표에 마커를 생성합니다 
      position: map.getCenter() 
    }); 

    marker.setMap(map);
   //  마커위에 도로명주소 표시
var iwContent = '<div style="padding:5px;"> <a href = "http://localhost:3000/roominfo">' + meetingPlace[2] + '</a> <br/></div>', // 인포윈도우에 표출될 내용으로 HTML 문자열이나 document element가 가능합니다
    iwPosition = new kakao.maps.LatLng(lat,lon); //인포윈도우 표시 위치입니다
    // iwRemoveable = true; // removeable 속성을 ture 로 설정하면 인포윈도우를 닫을 수 있는 x버튼이 표시됩니다

// 인포윈도우를 생성합니다
var infowindow = new kakao.maps.InfoWindow({
    // map: map,
    position : iwPosition, 
    content : iwContent,
    // removable : iwRemoveable 
});
infowindow.open(map, marker); 
  
// * 마커에 클릭이벤트를 등록합니다
kakao.maps.event.addListener(marker, 'click', function() {
  infowindow.open(map, marker); 
  console.log('hello')

});

// ? 맵에 클릭이벤트 등록
kakao.maps.event.addListener(map, 'click', function(mouseEvent) {       
  
  // infowindow 생성
  infowindow.open(map, marker);  
  // 클릭한 위도, 경도 정보를 가져옵니다 
  var latlng = mouseEvent.latLng; 
  console.log(latlng.La, latlng.Ma)
  //  마커 위치를 클릭한 위치로 옮깁니다
  marker.setPosition(latlng);
  
  // infowindow를 마커위에 생성
  infowindow.setPosition(latlng)
  console.log('도착')

  setCenterPosition([latlng.getLat(),latlng.getLng()])
  //  좌표를 주소로 변환
  axios.get(`https://dapi.kakao.com/v2/local/geo/coord2address.json?x=${latlng.getLng()}&y=${latlng.getLat()}&input_coord=WGS84`
    ,{headers:{Authorization:`KakaoAK ${process.env.REACT_APP_REST_API}`}}
    )
    .then(res=>res.data.documents[0].address)
    .then((address)=>{
      setMeetingPlace([address.region_1depth_name,address.region_2depth_name,address.address_name])
      console.log(meetingPlace)
      return false
    })
    .catch(err=>console.log(err))  
});
    
    setMap(map)
    setPending(false)
  }, [meetingPlace,createdRoom])

// 모각코 만들기 버튼 클릭시 createdRoom=true가 되어 useEffect()시작부분의 axios가 실행된다.
  const sendRoomInfo = (e) => {
    console.log(meetingPlace)
    setCreatedRoom(true)
  }

  return (
    <>
      <AppBar onSearch={handleSearch} />
      {pending && <LinearProgress color="secondary" className={classes.progress} />}
      <div id="map" className={classes.map} ></div>
      <button className="create-room-btn" onClick = {(e)=>sendRoomInfo(e)}>모각코 만들기</button>
    </>
  )
}

export default Map

쿠키사용법

쿠키에 accessToken을 전달하여 암호화된 개인정보를 서버와 클라이언트가 주고받는 방식이다.

토큰은 JWT토큰을 사용했다.

1. 토큰으로 개인정보 암호화

    post: async (req, res) => {
        const { email, password } = req.body;
        if (!email || !password) {
            res.status(404).json('you should enter all the required information');
        } else {
        //로그인할때 입력된 email,pw와 같은 email,pw를 가진 유저를 DB에서 찾는다.
            const data = await user.findOne({ where: { email, password }});
            if (!data) { 
            //없으면 404 코드 클라이언트에 전달.
                res.status(404).json('not authorized');
            } else {
                // console.log(data.username);
                //해당되는 유저가 있다면 유저정보를 암호화하여 accessToken에 할당한다. 
                //암호화 함수는 밑에 적어놓았다.
                const accessToken = token.generateAccessToken(data.toJSON()); 
                const username = data.username;
                console.log(accessToken);
                
                //토큰을 쿠키로 전달한다. sameSite,secure,httpOnly 속성을 추가해야 보안이 강해진다.
                res.cookie("accessToken", accessToken, {sameSite : 'none',secure:true,httpOnly:true});
                res.status(200).json({ "username": username, "message": "ok" });
            }
        }   
        
    }
    
    // 암호화 복호화
module.exports = {
    generateAccessToken: (data) => {
        const { username, email } = data;
        const sortedData = { username, email }
        return jwt.sign(sortedData, process.env.ACCESS_SECRET, {expiresIn: '24h'});
    },

    isAuthorized: (token) => {
        if (!token) {
            	return 'unauthorized'
        	} else {
            	return jwt.verify(token, process.env.ACCESS_SECRET);
        	}
    	}
	}

 

2. 클라이언트에서 쿠키 다루는 법

서버에서 클라이언트로 쿠키를 보내줄때는 res.cookie.('key','value') 로 보내준다는것을 알고 있었다.

그러면 클라이언트에서는 쿠키를 어떻게 다룰까??

이번 프로젝트에서 알게된 방법은 react-cookie를 사용하는 것이다.

  1. 먼저 해당 파일의 터미널에서 npm install react-cookie를 입력한다.

  1. 코드는 다음과 같다.
//1. react-cookie import한다. 

	import { useCookies } from 'react-cookie'; 

//2. cookies는 쿠키(name : value)들을 모아놓은 javascript object이다. 

	const [cookies, setCookie, removeCookie] = useCookies(['cookie-name']); 

//3. headers의 Authorization 에 cookies.accessToken을 넣어서 서버로 보내준다. 

	axios.post('url'), { data }, 
		{headers:{withCredentials:"true", Authorization : `Bearer ${cookies.accessToken}`, 
		contentType:"application/json"}} ) 
	})

 

다음과 같이 요청헤더에 cookies.accessToken이 담겨서 서버로 전달된다.

 

 

3. 서버에서 쿠키 받는 법.

 

서버에서는 다음과 같이 req.headers.authorization에 들어가 accessToken을 확인할 수 있다.

req.headers.authorization은 String 타입의 Bearer 토큰 으로 이루어져 있기에 string.split(' ') [ 1 ]하여 토큰만 가져온다.

여기서 Bearer는 JWT 혹은 OAuth에 대한 토큰을 사용한다. 라는 뜻이다.

반응형