Skip to content

[feature] 동아리 상세페이지에 동아리방 위치를 지도에 표시한다#1367

Open
suhyun113 wants to merge 8 commits intodevelop-fefrom
feature/#1357-club-detail-map-location-MOA-767
Open

[feature] 동아리 상세페이지에 동아리방 위치를 지도에 표시한다#1367
suhyun113 wants to merge 8 commits intodevelop-fefrom
feature/#1357-club-detail-map-location-MOA-767

Conversation

@suhyun113
Copy link
Copy Markdown
Collaborator

@suhyun113 suhyun113 commented Mar 29, 2026

#️⃣연관된 이슈

ex) #1357

📝작업 내용

상세페이지 네이버 지도 연동 및 마커 UI 개선


image

모바일
image

❓작업 개요

상세페이지에 동아리방 위치를 확인할 수 있도록 네이버 지도 API를 연동하고, 커스텀 마커 및 UI를 적용했습니다.
신입 부원 기준으로 동아리실 위치를 쉽게 찾을 수 있도록 UX 개선을 목표로 구현했습니다.

🤔지도 API 선정 이유

1. 네이버 지도

  • 한국 서비스 → 주소 및 위치 정확도 가장 높음
  • 학교/건물 기반 위치 표현에 적합
  • 동아리방 위치 안내 목적에 가장 적합

2. 카카오 지도

  • 구현은 쉬움
  • React 예제 많음
  • 그러나 위치 정확도 상대적으로 낮음

3. 구글 지도

  • 기능 가장 많음
  • 글로벌 서비스
  • 한국 위치 정확도 낮음 + 비용 발생
    => 위 이유로 네이버 지도를 채택했습니다.

🤗도입 목적

  • 신입 부원이 동아리실 위치를 기억하지 못하는 상황 고려
  • 상세페이지에서 바로 위치 확인 가능하도록 개선
  • 결과적으로 서비스 리텐션 향상 기대

🛠️구현 내용

  • 네이버 지도 API 연동
  • script loader 방식으로 동적 로딩
  • 지도 컴포넌트 분리 (NaverMap, useNaverMap)
  • 동아리별 위도/경도 데이터 추가
  • 커스텀 SVG 마커 적용
  • 지도 하단 위치 텍스트 UI 추가
  • 위치 정보 없는 동아리는 지도 미노출 처리

🔑환경 변수 설정

네이버 지도는 API 키가 없으면 동작하지 않기 때문에

VITE_NAVER_MAP_CLIENT_ID=발급받은_키

.env 파일에 추가해야합니다. FE 노션 자료실에 NaverMap API 키 글에 작성해두었으니 참고해주세요.

네이버 지도 API 특성상 preview URL이 매번 변경되기 때문에
모든 preview 도메인을 사전에 등록하기 어려운 한계가 있습니다.

현재 등록된 preview 주소에서는 정상 동작하지만,
그 외 새로 생성되는 preview 환경에서는 도메인 미등록으로 인해
지도 API 요청이 정상적으로 동작하지 않을 수 있습니다.

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • New Features
    • 클럽 상세 페이지에 네이버 지도를 추가하여 클럽 위치를 지도에서 확인할 수 있습니다.
    • 각 클럽의 건물명 및 상세 위치 정보가 지도 카드에 함께 표시됩니다.
  • UI / 스타일
    • 상세 페이지와 지도 카드용 레이아웃 및 스타일이 추가되어 반응형으로 개선되었습니다.
  • 개발 편의
    • 개발 환경에서 지도를 초기화 관련 오류를 확인할 수 있는 핸들러가 추가되었습니다.

@suhyun113 suhyun113 self-assigned this Mar 29, 2026
@suhyun113 suhyun113 added ✨ Feature 기능 개발 💻 FE Frontend labels Mar 29, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
moadong Ready Ready Preview, Comment Mar 30, 2026 9:13am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

네이버 지도 API 통합을 위한 NaverMap 컴포넌트와 관련 훅/스크립트 로더, 스타일 및 클럽 위치 상수를 추가하고 ClubDetailPage를 수정해 조건부로 지도를 렌더링하도록 구현했습니다. 전역 타입 및 개발용 인증 실패 핸들러도 추가되었습니다.

Changes

Cohort / File(s) Summary
NaverMap 컴포넌트 및 스타일
frontend/src/components/map/NaverMap.tsx, frontend/src/components/map/NaverMap.styles.ts
NaverMap React 컴포넌트와 styled-components 기반 MapContainer 추가; mapRef를 통해 렌더 영역 제공.
NaverMap 초기화 및 스크립트 로드
frontend/src/components/map/useNaverMap.ts, frontend/src/components/map/loadNaverMapScript.ts
Naver 지도 스크립트 동적 로드 기능 추가(중복 로드 검사 포함) 및 useNaverMap 훅에서 지도/마커 초기화 로직 구현.
클럽 위치 데이터
frontend/src/constants/clubLocation.ts
ClubLocation 타입 정의 및 다수 클럽 좌표/위치 정보를 담은 상수 배열 추가 (읽기전용).
ClubDetailPage 통합 변경
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx, frontend/src/pages/ClubDetailPage/ClubDetailPage.styles.ts
좌측 섹션 및 맵 카드 스타일 추가, clubLocations 조회 후 조건부로 NaverMap 렌더링하도록 페이지 구조 변경.
프로필 카드 스타일 조정
frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts
태블릿 브레이크포인트에서 background-color에 더해 padding 조정.
전역 타입 및 인덱스 변경
frontend/src/types/window.d.ts, frontend/src/index.tsx
Window 인터페이스에 navernavermap_authFailure 추가; 개발 환경에서 인증 실패 핸들러 정의(콘솔 출력).

Sequence Diagram

sequenceDiagram
    participant User as User
    participant ClubDetail as ClubDetailPage
    participant NaverMap as NaverMap Component
    participant Hook as useNaverMap Hook
    participant Loader as loadNaverMapScript
    participant API as Naver Maps API

    User->>ClubDetail: 클럽 상세 페이지 방문
    ClubDetail->>ClubDetail: clubLocations에서 위치 조회
    ClubDetail->>NaverMap: 위치 props로 렌더
    NaverMap->>Hook: mapRef, lat, lng 전달
    Hook->>Loader: 스크립트 로드 요청
    alt 스크립트 이미 존재
        Loader-->>Hook: 즉시 반환
    else 스크립트 미존재
        Loader->>API: 외부 스크립트 요청
        API-->>Loader: 스크립트 로드 완료
        Loader-->>Hook: Promise 해결
    end
    Hook->>API: 지도 인스턴스 및 마커 생성
    API-->>NaverMap: 지도/마커 렌더링 완료
    NaverMap->>User: 지도 표시
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

Suggested reviewers

  • seongwon030
  • oesnuj
  • lepitaaar
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경 사항의 주요 목표를 명확하게 설명하고 있습니다. 네이버 지도 API를 동아리 상세페이지에 통합하여 동아리방 위치를 지도에 표시하는 기능 추가가 정확히 반영되어 있습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#1357-club-detail-map-location-MOA-767

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 29, 2026

🎨 UI 변경사항을 확인해주세요

변경된 스토리를 Chromatic에서 확인해주세요.

구분 링크
🔍 변경사항 리뷰 https://www.chromatic.com/build?appId=67904e61c16daa99a63b44a7&number=128
📖 Storybook https://67904e61c16daa99a63b44a7-xxanabppwf.chromatic.com/

2개 스토리 변경 · 전체 56개 스토리 · 22개 컴포넌트

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts (1)

171-173: ⚠️ Potential issue | 🔴 Critical

구문 오류: IntroSection의 닫는 중괄호가 누락되었습니다.

IntroSection styled-component의 ${media.tablet} 블록에 닫는 } 가 없어 빌드 오류가 발생합니다.

🐛 수정 제안
 export const IntroSection = styled.section`
   padding: 16px;
   border-radius: 14px;
   background-color: ${colors.base.white};

   ${media.tablet} {
     background-color: ${colors.gray[100]};
+  }
 `;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts`
around lines 171 - 173, The IntroSection styled-component has a missing closing
brace causing a syntax/build error; locate the IntroSection definition (the
block containing ${media.tablet} and colors.gray[100]) and add the missing
closing curly brace(s) to properly terminate the ${media.tablet} media query and
the IntroSection styled-component so the CSS block parses correctly.
🧹 Nitpick comments (6)
frontend/src/constants/clubLocation.ts (1)

9-9: 상수 이름에 UPPER_SNAKE_CASE 사용을 고려해 주세요.

코딩 가이드라인에 따르면 상수는 UPPER_SNAKE_CASE를 사용해야 합니다. clubLocationsCLUB_LOCATIONS로 변경하면 일관성이 향상됩니다.

♻️ 상수 이름 변경 예시
-export const clubLocations = [
+export const CLUB_LOCATIONS = [
   // ...
 ] as const;

사용처도 함께 업데이트:

-import { clubLocations } from '@/constants/clubLocation';
+import { CLUB_LOCATIONS } from '@/constants/clubLocation';

-const location = clubLocations.find(
+const location = CLUB_LOCATIONS.find(

As per coding guidelines: Use UPPER_SNAKE_CASE for constant names and centralize them in src/constants/

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/constants/clubLocation.ts` at line 9, Rename the exported
constant clubLocations to CLUB_LOCATIONS and update its export accordingly; then
update all usages/imports of clubLocations across the codebase to reference
CLUB_LOCATIONS (search for the symbol "clubLocations" and replace with
"CLUB_LOCATIONS") and ensure it remains exported from
src/constants/clubLocation.ts so other modules import the new name.
frontend/src/types/window.d.ts (1)

4-5: naverany 대신 최소한의 타입 정의를 고려해 주세요.

코딩 가이드라인에서 any 타입 사용을 지양하도록 권장하고 있습니다. Naver Maps SDK에 공식 TypeScript 타입이 없으므로, 프로젝트에서 사용하는 API에 대해 최소한의 인터페이스를 정의하면 타입 안전성이 향상됩니다.

♻️ 최소 타입 정의 예시
interface NaverMaps {
  Map: new (element: HTMLElement, options: object) => object;
  LatLng: new (lat: number, lng: number) => object;
  Marker: new (options: object) => object;
  // 필요한 추가 API...
}

interface Naver {
  maps: NaverMaps;
}

declare global {
  interface Window {
    Kakao: any;
    naver: Naver | undefined;
    navermap_authFailure: () => void;
  }
}

As per coding guidelines: frontend/**/*.{ts,tsx}: Do not use 'any' type; use explicit type definitions instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/types/window.d.ts` around lines 4 - 5, Replace the broad any on
window.naver with a minimal typed interface: define NaverMaps and Naver
interfaces that cover the SDK pieces you use (e.g., Map, LatLng, Marker
constructors and any methods/options you call) and change the Window declaration
so naver is typed as Naver | undefined; keep navermap_authFailure as a function
type. Update the types in frontend/src/types/window.d.ts by adding the NaverMaps
and Naver interface names and using them in the global Window declaration
instead of any to satisfy the lint rule against any.
frontend/src/index.tsx (1)

8-12: SDK 초기화 로직을 initSDK.ts로 이동하는 것을 고려해 주세요.

학습된 패턴에 따르면 SDK 초기화는 src/utils/initSDK.ts에서 관리됩니다. navermap_authFailure 핸들러도 해당 파일로 이동하면 일관성이 향상됩니다.

♻️ initSDK.ts로 이동 예시

frontend/src/utils/initSDK.ts에 추가:

export function initializeNaverMapAuthHandler() {
  if (import.meta.env.DEV) {
    window.navermap_authFailure = function () {
      console.error('Naver Map Error 인증 실패');
    };
  }
}

frontend/src/index.tsx에서 호출:

-if (import.meta.env.DEV) {
-  window.navermap_authFailure = function () {
-    console.error('Naver Map Error 인증 실패');
-  };
-}
+import { initializeNaverMapAuthHandler } from './utils/initSDK';
+initializeNaverMapAuthHandler();

Based on learnings: Manage all SDK initialization (Mixpanel, Sentry, Channel.io, Kakao) in src/utils/initSDK.ts

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/index.tsx` around lines 8 - 12, Move the dev-only Naver Map auth
failure handler out of frontend/src/index.tsx into the centralized SDK init
module: create or use src/utils/initSDK.ts and add a function (e.g.,
initializeNaverMapAuthHandler) that registers window.navermap_authFailure in the
same DEV-only conditional, then import and call that function from index.tsx as
part of the SDK initialization sequence (alongside other init functions like
Mixpanel/Sentry/Channel.io/Kakao) so the handler and SDK bootstrapping are
managed in one place.
frontend/src/components/map/NaverMap.tsx (1)

5-13: 사용되지 않는 props를 제거하거나 활용해 주세요.

NaverMapProps 인터페이스에 clubName, building, detailLocation이 정의되어 있지만 컴포넌트에서 사용되지 않습니다. 현재 필요하지 않다면 제거하고, 향후 마커 레이블이나 InfoWindow에 사용할 예정이라면 해당 기능을 구현하거나 TODO 주석을 추가해 주세요.

♻️ 사용하지 않는 props 제거
 interface NaverMapProps {
   lat: number;
   lng: number;
-  clubName: string;
-  building: string;
-  detailLocation: string;
 }

-const NaverMap = ({ lat, lng }: NaverMapProps) => {
+const NaverMap = ({ lat, lng }: NaverMapProps) => {
   const mapRef = useRef<HTMLDivElement | null>(null);

   useNaverMap(mapRef, lat, lng);

   return <Styled.MapContainer ref={mapRef} />;
 };

또는 ClubDetailPage.tsx에서 호출 시에도 해당 props를 제거해야 합니다:

 <NaverMap
-  clubName={location.clubName}
   lat={location.lat}
   lng={location.lng}
-  building={location.building}
-  detailLocation={location.detailLocation}
 />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/map/NaverMap.tsx` around lines 5 - 13, The
NaverMapProps interface declares clubName, building, and detailLocation but the
NaverMap component signature and body only use lat and lng; either remove those
unused props from NaverMapProps and from the NaverMap call sites (e.g., where
ClubDetailPage calls NaverMap) or update NaverMap to accept and use
clubName/building/detailLocation (for example to render a marker label or
InfoWindow) and add a TODO comment if you prefer to defer UI work; update the
interface, the NaverMap function parameters, and all places that construct
NaverMap props (notably ClubDetailPage) to keep them in sync.
frontend/src/pages/ClubDetailPage/ClubDetailPage.styles.ts (1)

57-66: 하드코딩된 색상 대신 테마 색상 사용을 권장합니다.

background-color: #f2f2f2``가 하드코딩되어 있습니다. 일관성을 위해 테마 시스템의 색상을 사용하는 것이 좋습니다. border 속성에서는 이미 `colors.gray[400]`을 사용하고 있습니다.

♻️ 수정 제안
 export const MapCard = styled.div`
   width: 100%;
   height: 189px;

   border-radius: 20px;
   border: 1px solid ${colors.gray[400]};
   overflow: hidden;

-  background-color: `#f2f2f2`;
+  background-color: ${colors.gray[100]};
 `;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/ClubDetailPage/ClubDetailPage.styles.ts` around lines 57 -
66, Replace the hardcoded background-color in the MapCard styled component with
a themed color; instead of `background-color: `#f2f2f2``, use the project's color
token (e.g., `colors.gray[100]`) to match the existing `colors.gray[400]` usage
so the component follows the theme system and remains consistent with other
styles.
frontend/src/components/map/useNaverMap.ts (1)

3-3: 내부 모듈 import는 @/ alias로 통일해 주세요.

Line 3의 상대경로 import를 alias 경로로 맞추면 규칙 일관성과 리팩터링 안정성이 좋아집니다.

수정 제안
-import { loadNaverMapScript } from './loadNaverMapScript';
+import { loadNaverMapScript } from '@/components/map/loadNaverMapScript';

As per coding guidelines, Use path alias @/* to reference src/* for cleaner imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/map/useNaverMap.ts` at line 3, Replace the relative
import of loadNaverMapScript in useNaverMap.ts with the project path-alias form
(use "@/..." to reference the same module) so the import for loadNaverMapScript
uses the `@/` alias consistently with the repo convention; update the import
statement that currently references './loadNaverMapScript' to the equivalent
'@/...' alias path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/map/loadNaverMapScript.ts`:
- Around line 2-3: Remove the debug console.log statements in
loadNaverMapScript.ts (the two lines logging import.meta.env and
VITE_NAVER_CLIENT_ID) to eliminate CI `Unexpected console statement` warnings
and noise; also correct the environment variable usage to the actual runtime key
(replace references to VITE_NAVER_CLIENT_ID with VITE_NAVER_MAP_CLIENT_ID where
the script reads the Naver map client id) so the loader uses the correct value.
- Around line 5-24: The loader currently resolves too early when an existing
script tag is present and never rejects on load failure; update
loadNaverMapScript so it never resolves simply because document.querySelector
found a script — instead attach load and error listeners to the existingScript
(listen for 'load' to resolve and 'error' to reject or fallback) and add both
onload and onerror handlers to the newly created script before appending it
(ensure you append the script to document.head/document.body); keep using
window.naver?.maps to short-circuit only if the SDK is already available, and
ensure the Promise always settles (resolve on load, reject or resolve with
failure handling on error) so callers won’t hang.

In `@frontend/src/constants/clubLocation.ts`:
- Around line 118-132: There are duplicate entries for clubName '모비딕'
(detailLocation '106호' and '214호') so only the first will be returned by any
Array.find lookup; either deduplicate the data by removing the unintended
duplicate or merge them into a single record (e.g., change detailLocation to an
array like detailLocations: ['106호','214호'] and adjust consumers), or if
multiple locations are intended keep both objects but update the lookup code to
use Array.filter (or a dedicated getLocationsByClubName function) instead of
Array.find; locate the objects with clubName '모비딕' in clubLocation.ts and apply
the chosen fix consistently across consumers that reference clubName.

---

Outside diff comments:
In
`@frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts`:
- Around line 171-173: The IntroSection styled-component has a missing closing
brace causing a syntax/build error; locate the IntroSection definition (the
block containing ${media.tablet} and colors.gray[100]) and add the missing
closing curly brace(s) to properly terminate the ${media.tablet} media query and
the IntroSection styled-component so the CSS block parses correctly.

---

Nitpick comments:
In `@frontend/src/components/map/NaverMap.tsx`:
- Around line 5-13: The NaverMapProps interface declares clubName, building, and
detailLocation but the NaverMap component signature and body only use lat and
lng; either remove those unused props from NaverMapProps and from the NaverMap
call sites (e.g., where ClubDetailPage calls NaverMap) or update NaverMap to
accept and use clubName/building/detailLocation (for example to render a marker
label or InfoWindow) and add a TODO comment if you prefer to defer UI work;
update the interface, the NaverMap function parameters, and all places that
construct NaverMap props (notably ClubDetailPage) to keep them in sync.

In `@frontend/src/components/map/useNaverMap.ts`:
- Line 3: Replace the relative import of loadNaverMapScript in useNaverMap.ts
with the project path-alias form (use "@/..." to reference the same module) so
the import for loadNaverMapScript uses the `@/` alias consistently with the repo
convention; update the import statement that currently references
'./loadNaverMapScript' to the equivalent '@/...' alias path.

In `@frontend/src/constants/clubLocation.ts`:
- Line 9: Rename the exported constant clubLocations to CLUB_LOCATIONS and
update its export accordingly; then update all usages/imports of clubLocations
across the codebase to reference CLUB_LOCATIONS (search for the symbol
"clubLocations" and replace with "CLUB_LOCATIONS") and ensure it remains
exported from src/constants/clubLocation.ts so other modules import the new
name.

In `@frontend/src/index.tsx`:
- Around line 8-12: Move the dev-only Naver Map auth failure handler out of
frontend/src/index.tsx into the centralized SDK init module: create or use
src/utils/initSDK.ts and add a function (e.g., initializeNaverMapAuthHandler)
that registers window.navermap_authFailure in the same DEV-only conditional,
then import and call that function from index.tsx as part of the SDK
initialization sequence (alongside other init functions like
Mixpanel/Sentry/Channel.io/Kakao) so the handler and SDK bootstrapping are
managed in one place.

In `@frontend/src/pages/ClubDetailPage/ClubDetailPage.styles.ts`:
- Around line 57-66: Replace the hardcoded background-color in the MapCard
styled component with a themed color; instead of `background-color: `#f2f2f2``,
use the project's color token (e.g., `colors.gray[100]`) to match the existing
`colors.gray[400]` usage so the component follows the theme system and remains
consistent with other styles.

In `@frontend/src/types/window.d.ts`:
- Around line 4-5: Replace the broad any on window.naver with a minimal typed
interface: define NaverMaps and Naver interfaces that cover the SDK pieces you
use (e.g., Map, LatLng, Marker constructors and any methods/options you call)
and change the Window declaration so naver is typed as Naver | undefined; keep
navermap_authFailure as a function type. Update the types in
frontend/src/types/window.d.ts by adding the NaverMaps and Naver interface names
and using them in the global Window declaration instead of any to satisfy the
lint rule against any.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 09b15eb6-e842-4223-bbea-bae4fee24d6e

📥 Commits

Reviewing files that changed from the base of the PR and between 6803a4e and d79f371.

⛔ Files ignored due to path filters (2)
  • frontend/src/assets/images/icons/location_icon.svg is excluded by !**/*.svg
  • frontend/src/assets/images/icons/marker.svg is excluded by !**/*.svg
📒 Files selected for processing (10)
  • frontend/src/components/map/NaverMap.styles.ts
  • frontend/src/components/map/NaverMap.tsx
  • frontend/src/components/map/loadNaverMapScript.ts
  • frontend/src/components/map/useNaverMap.ts
  • frontend/src/constants/clubLocation.ts
  • frontend/src/index.tsx
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.styles.ts
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts
  • frontend/src/types/window.d.ts

Comment on lines +2 to +3
console.log(import.meta.env);
console.log('NAVER CLIENT ID: ', import.meta.env.VITE_NAVER_CLIENT_ID);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

디버그 console.log는 머지 전에 제거해 주세요.

Line 2-3은 CI 경고(Unexpected console statement)와 동일하며, 운영 로그 노이즈를 유발합니다. 또한 VITE_NAVER_CLIENT_ID와 실제 사용 키(VITE_NAVER_MAP_CLIENT_ID)가 달라 디버깅 혼선을 줍니다.

🧰 Tools
🪛 GitHub Check: ci

[warning] 3-3:
Unexpected console statement


[warning] 2-2:
Unexpected console statement

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/map/loadNaverMapScript.ts` around lines 2 - 3, Remove
the debug console.log statements in loadNaverMapScript.ts (the two lines logging
import.meta.env and VITE_NAVER_CLIENT_ID) to eliminate CI `Unexpected console
statement` warnings and noise; also correct the environment variable usage to
the actual runtime key (replace references to VITE_NAVER_CLIENT_ID with
VITE_NAVER_MAP_CLIENT_ID where the script reads the Naver map client id) so the
loader uses the correct value.

Comment on lines +5 to +24
return new Promise<void>((resolve) => {
if (window.naver?.maps) {
resolve();
return;
}

const existingScript = document.querySelector(
'script[src*="oapi.map.naver.com"]',
);
if (existingScript) {
resolve();
return;
}

const script = document.createElement('script');
script.src = `https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=${import.meta.env.VITE_NAVER_MAP_CLIENT_ID}`;
script.async = true;

script.onload = () => resolve();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

SDK 로더가 조기 resolve/무한 대기 상태를 만들 수 있습니다.

Line 14-16에서 script 태그가 “존재”하기만 해도 resolve되어 SDK 준비 전에 다음 로직이 실행될 수 있습니다. 또한 Line 23은 onload만 있어 로드 실패 시 Promise가 정착되지 않습니다. 로더 Promise를 단일화하고, 기존 script 분기에서도 load/error를 기다리도록 바꿔주세요.

수정 제안
+let naverMapScriptPromise: Promise<void> | null = null;
+
 export const loadNaverMapScript = () => {
-  console.log(import.meta.env);
-  console.log('NAVER CLIENT ID: ', import.meta.env.VITE_NAVER_CLIENT_ID);
-
-  return new Promise<void>((resolve) => {
-    if (window.naver?.maps) {
-      resolve();
-      return;
-    }
+  if (window.naver?.maps) return Promise.resolve();
+  if (naverMapScriptPromise) return naverMapScriptPromise;
+
+  naverMapScriptPromise = new Promise<void>((resolve, reject) => {
+    const finish = () => resolve();
+    const fail = () => reject(new Error('Failed to load Naver Map SDK'));
 
     const existingScript = document.querySelector(
       'script[src*="oapi.map.naver.com"]',
-    );
+    ) as HTMLScriptElement | null;
+
     if (existingScript) {
-      resolve();
+      existingScript.addEventListener('load', finish, { once: true });
+      existingScript.addEventListener('error', fail, { once: true });
       return;
     }
 
     const script = document.createElement('script');
     script.src = `https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=${import.meta.env.VITE_NAVER_MAP_CLIENT_ID}`;
     script.async = true;
-
-    script.onload = () => resolve();
+    script.onload = finish;
+    script.onerror = fail;
 
     document.head.appendChild(script);
   });
+
+  return naverMapScriptPromise;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/map/loadNaverMapScript.ts` around lines 5 - 24, The
loader currently resolves too early when an existing script tag is present and
never rejects on load failure; update loadNaverMapScript so it never resolves
simply because document.querySelector found a script — instead attach load and
error listeners to the existingScript (listen for 'load' to resolve and 'error'
to reject or fallback) and add both onload and onerror handlers to the newly
created script before appending it (ensure you append the script to
document.head/document.body); keep using window.naver?.maps to short-circuit
only if the SDK is already available, and ensure the Promise always settles
(resolve on load, reject or resolve with failure handling on error) so callers
won’t hang.

Comment on lines +118 to +132
{
clubName: '모비딕',
lat: 35.131654,
lng: 129.104659,
building: '한솔관(E16)',
detailLocation: '106호',
},
// 바구니-A동
{
clubName: '모비딕',
lat: 35.131654,
lng: 129.104659,
building: '한솔관(E16)',
detailLocation: '214호',
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

중복된 clubName: '모비딕'이 있습니다.

'모비딕'이 두 번 등록되어 있습니다 (Line 119, 127). Array.find()는 첫 번째 항목만 반환하므로 두 번째 위치 정보(214호)는 사용되지 않습니다. 의도된 동작인지 확인이 필요합니다.

#!/bin/bash
# Description: 중복된 clubName 확인

echo "=== 중복된 clubName 목록 ==="
rg -o "clubName: '[^']+'" frontend/src/constants/clubLocation.ts | sort | uniq -d
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/constants/clubLocation.ts` around lines 118 - 132, There are
duplicate entries for clubName '모비딕' (detailLocation '106호' and '214호') so only
the first will be returned by any Array.find lookup; either deduplicate the data
by removing the unintended duplicate or merge them into a single record (e.g.,
change detailLocation to an array like detailLocations: ['106호','214호'] and
adjust consumers), or if multiple locations are intended keep both objects but
update the lookup code to use Array.filter (or a dedicated
getLocationsByClubName function) instead of Array.find; locate the objects with
clubName '모비딕' in clubLocation.ts and apply the chosen fix consistently across
consumers that reference clubName.

);
if (existingScript) {
resolve();
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 로더는 existingScript 존재만 확인하고 즉시 resolve해서, 실제 로드 완료 전에 훅이 실행되면 window.naver가 없어 초기화가 누락될 수 있습니다. 기존 스크립트가 있더라도 load/error 리스너를 연결해 로드 완료 시점에 resolve/reject 하도록 보강해 주세요.


const location = clubLocations.find(
(location) => location.clubName === clubDetail?.name,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clubName 기준 조회(find)와 데이터 중복이 결합되어 두 번째 동일 이름 항목이 사실상 도달 불가합니다. 위치 매핑은 clubId 같은 고유 키로 전환하거나, 최소한 중복 이름 데이터를 정리해 매핑 오차를 막아주세요.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재는 동아리명이 고유하게 관리되고 있어 name 기반 매핑으로 구현했습니다. 또한 위치 데이터는 프론트에서 직접 관리하고 있어, 작성 편의성 측면에서 clubId보다 clubName 기반이 더 간단한 장점이 있었습니다.
다만 확장 시 안정성을 위해 clubId 기반 매핑으로 전환하는 방향도 고려하겠습니다.

@@ -0,0 +1,27 @@
export const loadNaverMapScript = () => {
console.log(import.meta.env);
console.log('NAVER CLIENT ID: ', import.meta.env.VITE_NAVER_CLIENT_ID);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

운영 콘솔 노이즈와 불필요한 환경 정보 노출을 줄이기 위해 디버그 로그(import.meta.env, client id)는 제거하거나 DEV 조건으로 제한하는 것을 권장합니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제거했습니다.

Copy link
Copy Markdown
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always Approve 정책에 따라 승인합니다.

중복 제거 및 근거 보강된 핵심 사항은 인라인 코멘트로 남겼습니다.

거절 사유(비차단 아님)

  • Severity: Medium

  • 항목: Naver script loader의 existingScript 즉시 resolve로 인한 race condition

  • 영향: 스크립트 태그가 존재하지만 아직 로드 전인 타이밍에서 지도 초기화가 누락되어 빈 지도 상태가 발생할 수 있음

  • 수정방안: existingScript 분기에서도 load/error 리스너로 로드 완료를 보장한 뒤 resolve/reject 처리

  • Severity: Medium

  • 항목: clubName 기반 위치 매핑 + 중복 데이터 결합

  • 영향: 동명이칭 또는 중복 이름 항목에서 잘못된 좌표가 선택되거나 일부 데이터가 사실상 도달 불가

  • 수정방안: clubId 등 고유 키 기반 매핑으로 전환하고 중복 이름 데이터 정리

  • Severity: Low

  • 항목: map loader 내 운영 콘솔 디버그 로그 노출

  • 영향: 콘솔 노이즈 증가 및 불필요한 환경 정보 노출

  • 수정방안: 로그 제거 또는 DEV 조건으로 제한

@suhyun113 suhyun113 changed the title 동아리 상세페이지에 동아리방 위치를 지도에 표시한다 [feature] 동아리 상세페이지에 동아리방 위치를 지도에 표시한다 Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants