Skip to content

Commit

Permalink
Merge pull request #54 from CarrotAuction/feat/KAN-13/kakao-map-api
Browse files Browse the repository at this point in the history
feat: 게시글 상세페이지 판매자와 구매자 카카오 map api를 활용해 길찾기 시각화
  • Loading branch information
Leeseunghwan7305 authored Feb 26, 2024
2 parents 6fc0392 + aba72d4 commit 4483353
Show file tree
Hide file tree
Showing 18 changed files with 377 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NEXT_PUBLIC_KAKAO_MAP_REST_API=179a8c907891026029fb59adbc860843
NEXT_PUBLIC_KAKAO_MAP_CLIENT=4948158107c883c7ba08e9ca9ea1fbb7
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ yarn-error.log*
# local env files
.env*.local

.env
# vercel
.vercel

Expand Down
1 change: 1 addition & 0 deletions src/app/auction/[slug]/index.module.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@import '@/src/styles/media.scss';

.page {
position: relative;
display: flex;
flex-direction: column;
max-width: 800px;
Expand Down
86 changes: 83 additions & 3 deletions src/app/auction/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import React, { useEffect, useRef, useState } from 'react';
/* eslint-disable no-restricted-globals */
import React, { useEffect, useState } from 'react';
import classNamees from 'classnames/bind';
import {
useGetComments,
Expand All @@ -11,6 +12,13 @@ import ProductInfo from '@/src/components/auctionDetailPage/ProductInfo';
import { CreatorCommentType } from '@/src/types/auctionDetail';
import Loading from '@/src/common/UI/Loading';
import CommentContainer from '@/src/components/auctionDetailPage/CommentContainer';
import Makers from '@/src/common/Makers';
import { useRecoilValue } from 'recoil';
import { currentLocationState, mapState } from '@/src/atom';
import {
GetCurrentLocation,
GetFindLocation,
} from '@/src/remote/apis/AuctionDetail/AuctionDetail.get.api';
import styles from './index.module.scss';

type AuctionDetailProps = {
Expand All @@ -22,8 +30,17 @@ const cx = classNamees.bind(styles);
const AuctionDetail = ({ params }: AuctionDetailProps) => {
const boardId = params.slug;
const [ref, inView] = useInView();
const [arriveLocation, setArriveLocation] = useState({
lat: '',
lng: '',
zoom: 8,
});
const map = useRecoilValue(mapState);
const currentLocation = useRecoilValue(currentLocationState);

const [comment, setCommnet] = useState<CreatorCommentType[]>([]);
const { data } = useGetDetailInfo(boardId);
const { data, isPending } = useGetDetailInfo(boardId);

const cursor = comment[comment.length - 1]?.id;
const { data: commentData, isPending: isCommentPending } = useGetComments({
boardId,
Expand All @@ -41,11 +58,74 @@ const AuctionDetail = ({ params }: AuctionDetailProps) => {
}
}, [data]);

useEffect(() => {
if (!isPending) {
const query = encodeURIComponent(
`${data?.board.creator.province.name}${data?.board.creator.city.name}`,
);
(async () => {
const data = await GetCurrentLocation(query);
setArriveLocation({
lat: data?.documents[0]?.y,
lng: data?.documents[0]?.x,
zoom: 8,
});
})();
}
}, [data]);

useEffect(() => {
if (currentLocation && arriveLocation) {
(async () => {
const url = 'https://apis-navi.kakaomobility.com/v1/directions';

const origin = `${currentLocation.lng},${currentLocation.lat}`;
const destination = `${arriveLocation.lng},${arriveLocation.lat}`;

if (origin === ',' || destination === ',') return;
const queryParams = new URLSearchParams({
origin,
destination,
});

const response = await GetFindLocation(queryParams);

const linePath: any[] = [];
response.routes[0].sections[0].roads.forEach(
(router: { vertexes: any[] }) => {
router.vertexes.forEach((vertex, index) => {
if (index % 2 === 0) {
linePath.push(
new window.kakao.maps.LatLng(
router.vertexes[index + 1],
router.vertexes[index],
),
);
}
});
},
);
const polyline = new window.kakao.maps.Polyline({
path: linePath,
strokeWeight: 5,
strokeColor: '#f57a00',
strokeOpacity: 0.7,
strokeStyle: 'solid',
});
polyline.setMap(map);
})();
}
}, [currentLocation, arriveLocation]);

if (!data) return <Loading width="300" height="300" />;

return (
<div className={cx('page')}>
<ProductInfo productInfo={data} />
<ProductInfo productInfo={data} arriveLocation={arriveLocation} />
<CommentContainer totalComments={data.totalComments} comments={comment} />
{arriveLocation && (
<Makers location={arriveLocation} info title="판매자 위치" />
)}
<div style={{ display: 'hidden', height: '100px' }} ref={ref} />
</div>
);
Expand Down
20 changes: 11 additions & 9 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';

import type { Metadata } from 'next';
import { Roboto } from 'next/font/google';
import '../styles/globalStyle.scss';
import ReactQueryProviders from '../providers/ReactQueryProvider';
import NavBar from '../common/Nav';
import Footer from '../common/Footer';
import RecoilProviders from '../providers/RecoilProviders';

const NotoR = Roboto({
weight: '400',
Expand All @@ -28,13 +28,15 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="kr">
<body className={`${NotoR.className} ${NotoB.variable}`}>
<NavBar />
<ReactQueryProviders>{children}</ReactQueryProviders>
<Footer />
<div id="modal" />
</body>
</html>
<RecoilProviders>
<html lang="kr">
<body className={`${NotoR.className} ${NotoB.variable}`}>
<NavBar />
<ReactQueryProviders>{children}</ReactQueryProviders>
<Footer />
<div id="modal" />
</body>
</html>
</RecoilProviders>
);
}
Binary file added src/assets/auction/arrive.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/auction/home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions src/atom/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { atom } from 'recoil';
import { LocationType } from '../types/map';

const DEFAULT_LAT = '37.497625203';
const DEFAULT_LNG = '127.03088379';
const DEFAULT_ZOOM = 8;

export const mapState = atom({
key: 'map',
default: null,
dangerouslyAllowMutability: true,
});

export const locationState = atom<LocationType>({
key: 'location',
default: {
lat: DEFAULT_LAT,
lng: DEFAULT_LNG,
zoom: DEFAULT_ZOOM,
},
});

export const currentLocationState = atom<LocationType>({
key: 'currentLocation',
default: {
lat: '',
lng: '',
zoom: DEFAULT_ZOOM,
},
});

export const arriveLocationState = atom<LocationType>({
key: 'arriveLocation',
default: {
lat: '',
lng: '',
zoom: DEFAULT_ZOOM,
},
});
67 changes: 67 additions & 0 deletions src/common/Makers/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* eslint-disable react/jsx-no-useless-fragment */
import { mapState } from '@/src/atom';
import { useRecoilValue } from 'recoil';
import React, { useEffect } from 'react';
import { LocationType } from '@/src/types/map';
import arrive from '../../assets/auction/arrive.png';

const Makers = ({
location,
info,
title,
}: {
location: LocationType;
info?: boolean;
title?: string;
}) => {
const map = useRecoilValue(mapState);

const loadKakaoMarker = () => {
if (map) {
const imageSrc = arrive.src;

const imageSize = new window.kakao.maps.Size(40, 40);
const imageOption = { offset: new window.kakao.maps.Point(27, 69) };

const markerImage = new window.kakao.maps.MarkerImage(
imageSrc,
imageSize,
imageOption,
);

const markerPosition = new window.kakao.maps.LatLng(
location.lat,
location.lng,
);

const marker = new window.kakao.maps.Marker({
position: markerPosition,
image: markerImage,
});
marker.setMap(map);

if (info) {
const iwContent = `<div style="padding:5px;">${title}<br><a href="https://map.kakao.com/link/map/나의 위치,${location.lat},${location.lng}" style="color:blue" target="_blank">길찾기</a></div>`;

const iwPosition = new window.kakao.maps.LatLng(
location.lat,
location.lng,
);

const infowindow = new window.kakao.maps.InfoWindow({
position: iwPosition,
content: iwContent,
});

infowindow.open(map, marker);
}
}
};
useEffect(() => {
loadKakaoMarker();
}, [map]);

return <></>;
};

export default Makers;
6 changes: 6 additions & 0 deletions src/common/Map/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.map {
width: 100%;
height: 500px;
z-index: 99;
border: 1px solid black;
}
52 changes: 52 additions & 0 deletions src/common/Map/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use client';

import Script from 'next/script';
import classNamees from 'classnames/bind';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { locationState, mapState } from '@/src/atom';
import styles from './index.module.scss';

declare global {
interface Window {
kakao: any;
}
}

type MapProps = {
lat?: string | null;
lng?: string | null;
zoom?: number;
};

export default function Map({ lat, lng, zoom }: MapProps) {
const cx = classNamees.bind(styles);

const setMap = useSetRecoilState(mapState);
const location = useRecoilValue(locationState);

const loadKakaoMap = () => {
window.kakao.maps.load(() => {
const mapContainer = document.getElementById('map');
const mapOption = {
center: new window.kakao.maps.LatLng(lat, lng),
level: zoom ?? location.zoom,
};

const map = new window.kakao.maps.Map(mapContainer, mapOption);

setMap(map);
});
};

return (
<>
<div id="map" className={cx('map')} />
<Script
strategy="afterInteractive"
type="text/javascript"
src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_CLIENT}&autoload=false`}
onReady={loadKakaoMap}
/>
</>
);
}
Empty file.
Loading

0 comments on commit 4483353

Please sign in to comment.