sweetalert2 호출 19 곳 통합 + 환경변수 단일화 + 코스 저장 가드 버그 수정 — POP-SPOT v2.6 프론트 결합도 정리
/shop 라우트 폴더 삭제 · 환경변수 src/lib/env.ts 로 단일화 · Kakao 지도 스크립트 undefined 키 가드 · 메인의 sweetalert2 직접 호출 19 곳을 공용 notify 로 · 장소 추가 모달 분리 · 취소 눌러도 저장되던 버그 수정.
메인 페이지가 sweetalert2 를 19 곳에서 직접 호출, 환경변수는 5 곳에서 따로따로 읽음, /shop 라우트는 폐기했다면서도 단순 리다이렉트만 남아 있음. 코스 저장 가드 버그도 같이 잡은 한 라운드.
이 글에서 다루는 것
모르는 단어 한 줄로
| 용어 | 한 줄 설명 |
|---|---|
| sweetalert2 | "alert·confirm·prompt 이쁜 버전" 레퍼런스 라이브러리 |
| 결합도 (coupling) | "이게 바뀌면 저게 같이 바뀌어야 하는 정도". 낮을수록 좋음 |
| aria-modal | 스크린 리더·키보드 사용자에게 "지금 모달이야" 를 알려주는 속성 |
| guard 명령 | 조건을 먼저 검사하고, 맞으면 다음을 진행하거나 멈추는 패턴 |
무엇이 바뀌었나
| 항목 | v2.5 | v2.6 |
|---|---|---|
| /shop 라우트 | 폐기됐지만 파일이 남음 | 폴더 통째 삭제 |
| 환경변수 | 5 파일에서 따로따로 | src/lib/env.ts 한 파일에서만 관리 |
| Kakao 지도 스크립트 | 키 비어있어도 <code>appkey=undefined</code> 로 로드 | 키 있을 때만 삽입 |
| page.tsx 알림 | sweetalert2 를 19 곳에서 직접 호출 | notify.ts 공용 함수로 교체 (sweetalert2 직접 호출 0 개) |
| 코스 저장 가드 | 취소를 눌러도 저장이 그대로 실행됨 | 확인 결과 대기 후 외부 함수에서 분기 |
| 모달 분리 | "장소 추가하기" 27 줄이 메인 파일에 박혀있음 | AddPlaceModal.tsx 분리, aria-modal + focus trap |
왜 이렇게 했음
공용 래퍼 (notify) — sweetalert2 의 기본 스타일은 POP-SPOT 의 남은 색조·파스텔 톤과 잘 맞지 않음. 그래서 공용 함수 하나에서 기본 아이콘·채도·버튼 텍스트 세팅을 한 곳에 박아둔다. 나중에 sweetalert2 이 아닌 다른 라이브러리로 갈아타도 19 곳을 고칠 필요 없음.
취소 가드 — sweetalert2 의 confirm 은 비동기 (Promise) 다. 역사적으로는 callback 으로 쓰이며 코드 안에서는 confirm 이 반환되자마자 save() 를 호출하는 패턴이 남아 있었음. 결과: 취소를 눌러도 save 가 실행. await 로 결과 받아 if (result.isConfirmed) save() 로 변경.
접근성 — 메인 안에 27 줄로 들어있는 모달은 focus trap (Tab 눌렀을 때 모달 안에서만 포커스 이동) · Esc 로 닫혀서 이걸 구현하기 어렵다. 독립 컴포넌트로 쪼개면 aria-modal · inert 속성을 깔끔하게 적용할 수 있다.
코드로는 어떻게 (필요한 부분만)
공용 notify.
파일: popspot-frontend/src/lib/notify.ts (v2.6 신규 생성)
// v2.6 — src/lib/notify.ts. 공용 알림 래퍼
import Swal from 'sweetalert2';
// 위: sweetalert2 는 이 파일 하나에서만 import. 19곳에서 이제 직접 import 안 함
const base = Swal.mixin({
// 위: mixin 은 공통 옵션 설정. 호출마다 동일 옵션을 반복 적용하고 싶을 때 쓰는 sweetalert 패턴
buttonsStyling: false,
// 위: sweetalert 의 기본 버튼 스타일 비활성화.
// false 로 안 두면 sweetalert 자체 파란/빨간 버튼이 떠서 POP-SPOT 톤과 충돌
customClass: {
confirmButton: 'bg-zinc-900 text-white rounded-lg px-4 py-2',
// 위: 확인 버튼 Tailwind 클래스 — 진한 회색 배경 + 흰 글자 (Primary 느낌)
cancelButton: 'bg-zinc-200 rounded-lg px-4 py-2 mx-2',
// 위: 취소 버튼 — 연한 회색 배경 (Secondary). 좌우 mx-2 로 확인 버튼과 간격
},
});
export const notify = {
// 위: 19곳에서 동일한 단순 인터페이스로 호출할 수 있도록 객체로 묶음
success: (msg: string) => base.fire({ icon: 'success', text: msg }),
// 위: 성공 토스트. 초록 체크 아이콘 + msg 메시지
error: (msg: string) => base.fire({ icon: 'error', text: msg }),
// 위: 에러 토스트. 빨간 X 아이콘 + msg 메시지
confirm: (msg: string) =>
base.fire({ icon: 'question', text: msg,
showCancelButton: true }).then(r => r.isConfirmed),
// 위: 예/아니오 물음. Promise 끝에 .then(r => r.isConfirmed) 를 붙여
// "확인이 눌렸는지" 의 boolean 만 반환 — 호출 쪽에서 쓰기 쉽도록
};취소 가드 수정.
파일: popspot-frontend/src/app/page.tsx 의 코스 저장 핸들러
// v2.5 까지 — confirm 답변을 기다리지 않고 바로 저장
Swal.fire({ text: '저장할까요?' });
// 위: Swal.fire 는 Promise 를 리턴 — await 없이 그냥 쓰면
// 다음 줄의 save() 호출이 바로 일어남 (팝업이 떠 있으는데도)
// 비동기 흐름을 구분 안 하면 이런 버그 자주 생김
save();
// 위: 팝업이 아직 떠있는데도 저장 실행. 취소 눌러도 동일하게 저장됨 (가드 의미 없음)파일: 같은 app/page.tsx (v2.6 교체 후)
// v2.6 — await 로 사용자 선택 기다리고 확인 양자일 때만 저장
if (await notify.confirm('저장할까요?')) {
// 위: notify.confirm 은 위에서 만든 래퍼. Promise<boolean> 리턴
// await 로 우아하게 해결 → true 면 if 내부 실행 계속
// 하위 원항도 async 함수여야 await 키워드 사용 가능
save();
// 위: 이제는 동의했을 때만 호출됨 — 장·별 일·견으는 안·전 조건·부 주 단·단 클 괔 을 명·명·되 며 하 는 숈 은 ·· ·· · · · ··· ·· ··· ·· ·· ··
}Kakao 지도 스크립트 키 가드.
파일: popspot-frontend/src/app/layout.tsx 또는 지도를 쓰는 페이지의 <Script /> 부분
// v2.5 까지 — KEY 가 undefined 여도 그대로 URL 제조
<Script src={`//dapi.kakao.com/.../sdk.js?appkey=${KEY}&autoload=false`} />
// 위: 템플릿 리터럴 안 ${KEY} 가 undefined 이면
// 문자열로 "undefined" 로 변환되어 ?appkey=undefined 가 완성됨
// Kakao SDK 가 401/403 으로 실패 + 콘솔에 대량의 에러
// 개발 중에는 환경변수가 빠진 상황이 자주 생김·클라이언트에 에러 노출 안 되도록 차단파일: 같은 layout.tsx (v2.6 수정 후)
// v2.6 — KEY 가 있을 때만 <Script> 자체를 렌더링
{KEY && (
// 위: 논리·AND 명제 — KEY 가 truthy 일 때만 아래 JSX 소환
// KEY=undefined / KEY='' 경우 false 는 React 가 아무 것도 렌더링 안 함
<Script src={`//dapi.kakao.com/.../sdk.js?appkey=${KEY}&autoload=false`} />
// 위: KEY 가 있을 때만 도달하므로 appkey 는 항상 유효한 값
)}핵심 파일: popspot-frontend/src/lib/env.ts, popspot-frontend/src/lib/notify.ts, popspot-frontend/src/features/popup/AddPlaceModal.tsx