diff --git a/.vscode/settings.json b/.vscode/settings.json index 8540aa9b..1d345e86 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,13 @@ { - "cSpell.words": ["autodocs", "hyungjun", "inversed", "jikwon", "kakao", "suhyeon"] + "cSpell.words": [ + "appkey", + "autodocs", + "clusterer", + "dapi", + "hyungjun", + "inversed", + "jikwon", + "kakao", + "suhyeon" + ] } diff --git a/package-lock.json b/package-lock.json index c31e437c..45f12ca6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "react": "^19.1.4", "react-dom": "^19.1.4", "react-hook-form": "^7.62.0", + "react-kakao-maps-sdk": "^1.2.0", "zod": "^4.1.5", "zustand": "^5.0.8" }, @@ -107,7 +108,6 @@ "version": "7.28.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1728,7 +1728,6 @@ }, "node_modules/@babel/runtime": { "version": "7.28.4", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1997,7 +1996,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -2201,7 +2199,6 @@ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", "license": "MIT", - "peer": true, "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -4258,7 +4255,6 @@ "version": "1.55.1", "devOptional": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "playwright": "1.55.1" }, @@ -4823,7 +4819,6 @@ "version": "8.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -5282,7 +5277,6 @@ "node_modules/@tanstack/react-query": { "version": "5.90.2", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.90.2" }, @@ -5408,7 +5402,8 @@ "node_modules/@types/aria-query": { "version": "5.0.4", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -5621,7 +5616,6 @@ "version": "20.19.18", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -5637,7 +5631,6 @@ "version": "19.1.16", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -5646,7 +5639,6 @@ "version": "19.1.9", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -5997,7 +5989,6 @@ "version": "8.15.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6076,7 +6067,6 @@ "version": "8.17.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6538,7 +6528,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -6866,7 +6855,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -8052,7 +8040,8 @@ "node_modules/dom-accessibility-api": { "version": "0.5.16", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dom-converter": { "version": "0.2.0", @@ -8451,7 +8440,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8998,7 +8986,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -10540,7 +10527,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -11955,6 +11941,12 @@ "node": "*" } }, + "node_modules/kakao.maps.d.ts": { + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/kakao.maps.d.ts/-/kakao.maps.d.ts-0.1.40.tgz", + "integrity": "sha512-nX69MB1ok04epe3OqS+/tEeWBbU31GSQbvDPJmQRRltzzqn6t4jBsO5v1nzalUjCKzwcH2CptOc767NZ7Hbu3g==", + "license": "MIT" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -12501,6 +12493,7 @@ "version": "1.5.0", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -12824,7 +12817,6 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz", "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "15.5.9", "@swc/helpers": "0.5.15", @@ -13807,7 +13799,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14063,7 +14054,6 @@ "version": "3.6.2", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -14174,6 +14164,7 @@ "version": "27.5.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -14187,6 +14178,7 @@ "version": "5.2.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -14354,7 +14346,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.4.tgz", "integrity": "sha512-DHINL3PAmPUiK1uszfbKiXqfE03eszdt5BpVSuEAHb5nfmNPwnsy7g39h2t8aXFc/Bv99GH81s+j8dobtD+jOw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14396,7 +14387,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.4.tgz", "integrity": "sha512-s2868ab/xo2SI6H4106A7aFI8Mrqa4xC6HZT/pBzYyQ3cBLqa88hu47xYD8xf+uECleN698Awn7RCWlkTiKnqQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -14421,13 +14411,27 @@ "node_modules/react-is": { "version": "17.0.2", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true + }, + "node_modules/react-kakao-maps-sdk": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-kakao-maps-sdk/-/react-kakao-maps-sdk-1.2.0.tgz", + "integrity": "sha512-ptyHhtSbvyza+9IAf6TXjE4ZhwwLbXR6avgNM2ju5xed2+fDwJ+gJDFSqhfsKbvFn9K9+3I+dY3JrqruwsGNBw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.15", + "kakao.maps.d.ts": "^0.1.39" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } }, "node_modules/react-refresh": { "version": "0.14.2", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15523,7 +15527,6 @@ "integrity": "sha512-kfr6kxQAjA96ADlH6FMALJwJ+eM80UqXy106yVHNgdsAP/CdzkkicglRAhZAvUycXK9AeadF6KZ00CWLtVMN4w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@testing-library/jest-dom": "^6.6.3", @@ -16623,7 +16626,6 @@ "version": "0.21.3", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -16711,7 +16713,6 @@ "version": "5.9.2", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16996,7 +16997,6 @@ "version": "5.102.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -17073,7 +17073,6 @@ "version": "2.26.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-html-community": "0.0.8", "html-entities": "^2.1.0", diff --git a/package.json b/package.json index ca13da0e..cbabd293 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react": "^19.1.4", "react-dom": "^19.1.4", "react-hook-form": "^7.62.0", + "react-kakao-maps-sdk": "^1.2.0", "zod": "^4.1.5", "zustand": "^5.0.8" }, diff --git a/public/kakao-map/marker.svg b/public/kakao-map/marker.svg new file mode 100644 index 00000000..f840ce9b --- /dev/null +++ b/public/kakao-map/marker.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/(route)/list/[id]/_components/PostDetail/PostDetail.tsx b/src/app/(route)/list/[id]/_components/PostDetail/PostDetail.tsx index efea6658..e1db1971 100644 --- a/src/app/(route)/list/[id]/_components/PostDetail/PostDetail.tsx +++ b/src/app/(route)/list/[id]/_components/PostDetail/PostDetail.tsx @@ -46,8 +46,10 @@ const PostDetail = ({ type, item }: PostDetailProps) => { )} diff --git a/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailBody/PostDetailBody.tsx b/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailBody/PostDetailBody.tsx index 4c537706..149859cf 100644 --- a/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailBody/PostDetailBody.tsx +++ b/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailBody/PostDetailBody.tsx @@ -12,7 +12,6 @@ type BodyData = { itemStatus: ItemStatus; category: CategoryType; }; - interface PostDetailBodyProps { isBoardType: boolean; label: "find" | "lost" | "notice" | "customer"; diff --git a/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailMap/KakaoMap.tsx b/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailMap/KakaoMap.tsx new file mode 100644 index 00000000..1e85ec25 --- /dev/null +++ b/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailMap/KakaoMap.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { Map, MapMarker } from "react-kakao-maps-sdk"; + +interface KakaoMapProps { + latitude: number; + longitude: number; +} + +const KakaoMap = ({ latitude, longitude }: KakaoMapProps) => { + return ( + + + + ); +}; + +export default KakaoMap; diff --git a/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailMap/PostDetailMap.tsx b/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailMap/PostDetailMap.tsx index b2b0c84e..8b4ab1f1 100644 --- a/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailMap/PostDetailMap.tsx +++ b/src/app/(route)/list/[id]/_components/PostDetail/_internal/PostDetailMap/PostDetailMap.tsx @@ -1,36 +1,47 @@ import { Icon } from "@/components"; +import KakaoMap from "./KakaoMap"; +import Link from "next/link"; + +type MapData = { + address: string; + latitude: number; + longitude: number; + postId: string; + radius: number; +}; interface PostDetailMapProps { - data: { - address: string; - latitude: string; - longitude: string; - }; + data: MapData; } const PostDetailMap = ({ data }: PostDetailMapProps) => { - const { address, latitude, longitude } = data; + const { address, latitude, longitude, postId, radius } = data; return (
- {/* TODO(지권): 추후 지도 컴포넌트 변경 */} -
-
- - {address && ( - - {address && } -
+
+ +
+ +
+ + {address && ( + + {address && } +
+
); }; diff --git a/src/app/(route)/list/[id]/map/_components/MapContent/MapContent.tsx b/src/app/(route)/list/[id]/map/_components/MapContent/MapContent.tsx new file mode 100644 index 00000000..0f23c151 --- /dev/null +++ b/src/app/(route)/list/[id]/map/_components/MapContent/MapContent.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { useSearchParams } from "next/navigation"; +import { toNumber } from "../../_utils/toNumber"; +import { DetailKakaoMap } from "../_internal"; + +const MapContent = () => { + const searchParams = useSearchParams(); + + let address = searchParams.get("address") || "서울특별시 중구 세종대로 110 서울특별시청"; + try { + address = decodeURIComponent(address); + } catch (error) { + // noop + } + + const rawData = { + lat: toNumber(searchParams.get("lat"), 37.566370748), + lng: toNumber(searchParams.get("lng"), 126.977918341), + radius: toNumber(searchParams.get("radius"), 1000), + address, + }; + + return ( +
+

지도 영역

+ +
+ ); +}; + +export default MapContent; diff --git a/src/app/(route)/list/[id]/map/_components/_internal/DetailKakaoMap/DetailKakaoMap.tsx b/src/app/(route)/list/[id]/map/_components/_internal/DetailKakaoMap/DetailKakaoMap.tsx new file mode 100644 index 00000000..87dae33f --- /dev/null +++ b/src/app/(route)/list/[id]/map/_components/_internal/DetailKakaoMap/DetailKakaoMap.tsx @@ -0,0 +1,49 @@ +import { Circle, Map, MapMarker } from "react-kakao-maps-sdk"; +import { Icon } from "@/components"; + +type LocationDataType = { + lat: number; + lng: number; + address: string; + radius: number; +}; + +interface DetailKakaoMapProps { + data: LocationDataType; +} + +const DetailKakaoMap = ({ data }: DetailKakaoMapProps) => { + const { lat, lng, address, radius } = data; + + return ( + <> + + + + + + +
+ + {address} +
+ + ); +}; + +export default DetailKakaoMap; diff --git a/src/app/(route)/list/[id]/map/_components/_internal/index.ts b/src/app/(route)/list/[id]/map/_components/_internal/index.ts new file mode 100644 index 00000000..1228f149 --- /dev/null +++ b/src/app/(route)/list/[id]/map/_components/_internal/index.ts @@ -0,0 +1 @@ +export { default as DetailKakaoMap } from "./DetailKakaoMap/DetailKakaoMap"; diff --git a/src/app/(route)/list/[id]/map/_components/index.ts b/src/app/(route)/list/[id]/map/_components/index.ts new file mode 100644 index 00000000..b801a9fb --- /dev/null +++ b/src/app/(route)/list/[id]/map/_components/index.ts @@ -0,0 +1 @@ +export { default as MapContent } from "./MapContent/MapContent"; diff --git a/src/app/(route)/list/[id]/map/_utils/toNumber.ts b/src/app/(route)/list/[id]/map/_utils/toNumber.ts new file mode 100644 index 00000000..6233ab51 --- /dev/null +++ b/src/app/(route)/list/[id]/map/_utils/toNumber.ts @@ -0,0 +1,6 @@ +export const toNumber = (value: string | null, fallback: number): number => { + if (value === null) return fallback; + + const num = Number(value); + return isNaN(num) ? fallback : num; +}; diff --git a/src/app/(route)/list/[id]/map/page.tsx b/src/app/(route)/list/[id]/map/page.tsx new file mode 100644 index 00000000..0dce9e4d --- /dev/null +++ b/src/app/(route)/list/[id]/map/page.tsx @@ -0,0 +1,18 @@ +import { Suspense } from "react"; +import { DetailHeader } from "@/components"; +import MapContent from "./_components/MapContent/MapContent"; + +const page = () => { + return ( +
+ +

분실/습득 위치 지도

+ + + + +
+ ); +}; + +export default page; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 56f1339d..a41e2952 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -54,6 +54,10 @@ export default function RootLayout({
{children}