From 1e45dacaf4a49c4907fe14c673ee242d5726314e Mon Sep 17 00:00:00 2001 From: jymaeng Date: Tue, 1 Oct 2024 10:34:28 +0900 Subject: [PATCH] =?UTF-8?q?Feat=20:=20MapModal=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MapModal/index.tsx | 105 ++++++++++++++++++++++++++++++ src/components/MapModal/styles.ts | 69 ++++++++++++++++++++ src/vite-env.d.ts | 3 + vite.config.ts | 27 +++++--- 4 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 src/components/MapModal/index.tsx create mode 100644 src/components/MapModal/styles.ts diff --git a/src/components/MapModal/index.tsx b/src/components/MapModal/index.tsx new file mode 100644 index 0000000..944f2e4 --- /dev/null +++ b/src/components/MapModal/index.tsx @@ -0,0 +1,105 @@ +import { useEffect, useMemo, useRef } from 'react'; +import * as S from './styles'; +import ReactDOM from 'react-dom'; +import { H16, P16 } from '@/components/Text'; +const KAKAO_MAP_API_KEY = import.meta.env.VITE_KAKAO_MAP_API_KEY; + +declare global { + interface Window { + kakao: any; + } +} + +interface MapModalProps { + place: string; + phone: string; + address: string; + url: string; + visible: boolean; + width?: number; + height?: number; + latitude: number; + longitude: number; + onClose: () => void; +} + +export const MapModal = ({ + place, + phone, + address, + url, + visible, + width = 800, + height = 600, + latitude, + longitude, + onClose, +}: MapModalProps) => { + const containerStyle = useMemo( + () => ({ + width, + height, + }), + [width, height], + ); + + const el = useMemo(() => document.createElement('div'), []); + const mapRef = useRef(null); + + useEffect(() => { + document.body.appendChild(el); + return () => { + document.body.removeChild(el); + }; + }, [el]); + useEffect(() => { + if (visible && mapRef.current) { + const script = document.createElement('script'); + script.async = true; + script.src = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${KAKAO_MAP_API_KEY}&autoload=false`; + document.head.appendChild(script); + + script.onload = () => { + window.kakao.maps.load(() => { + const options = { + center: new window.kakao.maps.LatLng(latitude, longitude), + level: 3, + }; + const map = new window.kakao.maps.Map(mapRef.current, options); + + const markerPosition = new window.kakao.maps.LatLng(latitude, longitude); + const marker = new window.kakao.maps.Marker({ + position: markerPosition, + }); + marker.setMap(map); + }); + }; + + return () => { + document.head.removeChild(script); + }; + } + }, [visible, latitude, longitude]); + + if (!visible) return null; + + return ReactDOM.createPortal( + + + + 공연장 정보 + X + + + + {place} + 전화번호 : {phone} + 주소 : {address} + 홈페이지 : {url} + + + + , + el, + ); +}; diff --git a/src/components/MapModal/styles.ts b/src/components/MapModal/styles.ts new file mode 100644 index 0000000..a877427 --- /dev/null +++ b/src/components/MapModal/styles.ts @@ -0,0 +1,69 @@ +import { styled } from 'styled-components'; +import theme from '@/styles/theme'; + +export const MapModal = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(255, 255, 255, 0.9); + z-index: 1000; +`; + +export const ModalContainer = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + padding: 30px; + background-color: var(${theme.colors.black}); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5); + box-sizing: border-box; + border-radius: 10px; + display: flex; + flex-direction: column; + min-width: 800px; + min-height: 600px; +`; + +export const ModalHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + position: relative; + + &::after { + content: ''; + border: 1px solid ${theme.colors.gray}; + position: absolute; + bottom: -10px; + left: 0; + width: 100%; + height: 1px; + } +`; + +export const ModalCloseBtn = styled.button` + border: none; + background-color: transparent; + font-size: 20px; + &:hover { + color: ${theme.colors.gray}; + } +`; + +export const ModalPlaceInfo = styled.div` + display: flex; + flex-direction: column; + padding: 20px 0; + gap: 10px; + flex-grow: 1; + overflow-y: auto; +`; + +export const MapContainer = styled.div` + border: 1px solid ${theme.colors.gray}; + flex-grow: 1; + margin-top: 20px; +`; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..a82f076 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,4 @@ /// +interface ImportMetaEnv { + readonly VITE_KAKAO_MAP_API_KEY: string; +} diff --git a/vite.config.ts b/vite.config.ts index 8f98d9d..dbc9f3b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,17 +1,24 @@ -import { defineConfig } from 'vite'; +import { defineConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react-swc'; import tsconfigPaths from 'vite-tsconfig-paths'; // https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react(), tsconfigPaths()], - server: { - proxy: { - '/api': { - target: 'http://www.kopis.or.kr/openApi/restful', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, ''), +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ''); + return { + plugins: [react(), tsconfigPaths()], + server: { + proxy: { + '/api': { + target: 'http://www.kopis.or.kr/openApi/restful', + changeOrigin: true, + rewrite: path => path.replace(/^\/api/, ''), + }, }, }, - }, + + define: { + 'import.meta.env.VITE_KAKAO_MAP_API_KEY': JSON.stringify(env.VITE_KAKAO_MAP_API_KEY), + }, + }; });