Skip to content

Commit 5d3a09f

Browse files
committed
feat: 맵 컴포넌트 추가
1 parent 14c1a31 commit 5d3a09f

File tree

6 files changed

+162
-1
lines changed

6 files changed

+162
-1
lines changed

apps/pyconkr-admin/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@
3232
user-scalable=no,
3333
shrink-to-fit=no" />
3434
<meta name="author" content="PyCon Korea Organizing Team" />
35-
<meta name="description" content="Teaser site for PyCon Korea 2025" />
35+
<meta name="description" content="Admin for PyCon Korea" />
3636
<meta name="keywords" content="PyCon, Python, Conference, Korea, 2025" />
3737
<meta name="google" content="notranslate" />
3838
<meta name="googlebot" content="index, follow" />
3939
<meta name="robots" content="index, follow" />
4040

41+
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=d3945eccce7debf0942f885e90a71f97"></script>
4142
<script src="https://cdn.iamport.kr/v1/iamport.js"></script>
4243

4344
<style>

apps/pyconkr/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<meta name="googlebot" content="index, follow" />
3939
<meta name="robots" content="index, follow" />
4040

41+
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=d3945eccce7debf0942f885e90a71f97"></script>
4142
<script src="https://cdn.iamport.kr/v1/iamport.js"></script>
4243

4344
<title>PyCon Korea 2025</title>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"@tanstack/react-query-devtools": "^5.76.1",
6161
"@types/crypto-js": "^4.2.2",
6262
"@types/json-schema": "^7.0.15",
63+
"@types/kakaomaps": "^1.1.5",
6364
"@types/mdx": "^2.0.13",
6465
"@types/node": "^22.15.18",
6566
"@types/react": "^19.1.4",

packages/common/src/components/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
} from "./dynamic_route";
88
import { ErrorFallback as ErrorFallbackComponent } from "./error_handler";
99
import { MDXRenderer as MDXRendererComponent } from "./mdx";
10+
import type { MapPropType as MapComponentPropType } from "./mdx_components/map";
11+
import { Map as MapComponent } from "./mdx_components/map";
1012
import { MDXEditor as MDXEditorComponent } from "./mdx_editor";
1113
import { PythonKorea as PythonKoreaComponent } from "./pythonkorea";
1214

@@ -20,6 +22,11 @@ namespace Components {
2022
export const MDXRenderer = MDXRendererComponent;
2123
export const PythonKorea = PythonKoreaComponent;
2224
export const ErrorFallback = ErrorFallbackComponent;
25+
26+
export namespace MDX {
27+
export const Map = MapComponent;
28+
export type MapPropType = MapComponentPropType;
29+
}
2330
}
2431

2532
export default Components;
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { Box, Button, Stack, Tab, Tabs } from "@mui/material";
2+
import * as React from "react";
3+
import { renderToStaticMarkup } from "react-dom/server";
4+
5+
type SupportedMapType = "kakao" | "google" | "naver";
6+
const MAP_TYPES: SupportedMapType[] = ["kakao", "google", "naver"];
7+
8+
type LangType = "ko" | "en";
9+
10+
export type MapPropType = {
11+
language: LangType;
12+
geo: {
13+
lat: number;
14+
lng: number;
15+
};
16+
placeName: { [key in LangType]: string };
17+
placeCode: { [key in SupportedMapType]: string };
18+
googleMapIframeSrc: string;
19+
};
20+
21+
type MapStateType = {
22+
tab: number;
23+
};
24+
25+
export type MapDataType = {
26+
title: {
27+
ko: string;
28+
en: string;
29+
};
30+
color: {
31+
backgroundColor: React.CSSProperties["backgroundColor"];
32+
color: React.CSSProperties["color"];
33+
};
34+
basePlaceInfoUrl: string;
35+
hideInTabs?: boolean;
36+
};
37+
38+
const MapData: { [key in SupportedMapType]: MapDataType } = {
39+
kakao: {
40+
title: { ko: "카카오맵", en: "Kakaomap" },
41+
color: { backgroundColor: "#fee500", color: "#191919" },
42+
basePlaceInfoUrl: "https://map.kakao.com/link/map/",
43+
},
44+
naver: {
45+
title: { ko: "네이버 지도", en: "NAVER Map" },
46+
color: { backgroundColor: "#04c75b", color: "#fff" },
47+
basePlaceInfoUrl: "https://naver.me/",
48+
hideInTabs: true,
49+
},
50+
google: {
51+
title: { ko: "구글 지도", en: "Google Maps" },
52+
color: { backgroundColor: "#4285f4", color: "#fff" },
53+
basePlaceInfoUrl: "https://maps.app.goo.gl/",
54+
},
55+
};
56+
57+
export const Map: React.FC<MapPropType> = ({ language, geo, placeName, placeCode, googleMapIframeSrc }) => {
58+
const kakaoMapRef = React.useRef<HTMLDivElement>(null);
59+
const [mapState, setMapState] = React.useState<MapStateType>({ tab: 0 });
60+
const selectedMapType = MAP_TYPES[mapState.tab] || "kakao";
61+
const setTab = (_: React.SyntheticEvent, tab: number) => setMapState((ps) => ({ ...ps, tab }));
62+
63+
React.useEffect(() => {
64+
const kakaoMapDiv = kakaoMapRef.current;
65+
if (!(window.kakao && window.kakao.maps && kakaoMapDiv)) return;
66+
67+
const kakaoMapUrl = MapData.kakao.basePlaceInfoUrl + placeCode.kakao;
68+
const content: string = renderToStaticMarkup(
69+
<a
70+
href={kakaoMapUrl}
71+
target="_blank"
72+
rel="noopener noreferrer"
73+
style={{ width: "max-content", height: "max-content" }}
74+
>
75+
<div
76+
style={{
77+
width: "100%",
78+
height: "100%",
79+
textAlign: "center",
80+
fontSize: "18px",
81+
whiteSpace: "pre-wrap",
82+
textWrap: "nowrap",
83+
}}
84+
>
85+
{placeName[language]}
86+
</div>
87+
</a>
88+
);
89+
const position = new window.kakao.maps.LatLng(geo.lat, geo.lng);
90+
const map = new window.kakao.maps.Map(kakaoMapDiv, { center: position, level: 3 });
91+
const infoWindow = new kakao.maps.InfoWindow({ content });
92+
infoWindow.open(map, new kakao.maps.Marker({ map, position }));
93+
94+
return () => {
95+
if (infoWindow) infoWindow.close();
96+
if (map) map.setCenter(new kakao.maps.LatLng(geo.lat, geo.lng));
97+
if (kakaoMapDiv) kakaoMapDiv.innerHTML = ""; // Clear the map container
98+
};
99+
}, [mapState.tab, geo, language, placeName, placeCode.kakao]);
100+
101+
const mapStyle: React.CSSProperties = { border: 0, width: "100%", aspectRatio: "3/2" };
102+
103+
return (
104+
<Box>
105+
<Tabs value={mapState.tab} onChange={setTab} variant="fullWidth">
106+
{Object.entries(MapData)
107+
.filter(([, v]) => !v.hideInTabs)
108+
.map(([k, d]) => (
109+
<Tab key={k} label={d.title[language]} sx={{ textTransform: "none" }} />
110+
))}
111+
</Tabs>
112+
{selectedMapType === "kakao" && <div ref={kakaoMapRef} style={mapStyle} />}
113+
{selectedMapType === "google" && (
114+
<iframe
115+
title="map"
116+
src={googleMapIframeSrc}
117+
style={mapStyle}
118+
allowFullScreen
119+
loading="lazy"
120+
referrerPolicy="no-referrer-when-downgrade"
121+
/>
122+
)}
123+
<Stack>
124+
{Object.entries(MapData).map(([key, data]) => {
125+
return (
126+
<Button
127+
key={key}
128+
sx={{
129+
backgroundColor: data.color.backgroundColor,
130+
color: data.color.color,
131+
textTransform: "none",
132+
}}
133+
href={`${data.basePlaceInfoUrl}${placeCode[key as SupportedMapType]}`}
134+
target="_blank"
135+
>
136+
{language === "ko" ? `${data.title.ko}에서 열기` : `Open in ${data.title.en}`}
137+
</Button>
138+
);
139+
})}
140+
</Stack>
141+
</Box>
142+
);
143+
};

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)