Skip to content

feat: 키 발급 실패 시 에러 토스트 추가#18

Merged
KwonDeaGeun merged 2 commits intomainfrom
feat/add-key-error-toast
Sep 18, 2025
Merged

feat: 키 발급 실패 시 에러 토스트 추가#18
KwonDeaGeun merged 2 commits intomainfrom
feat/add-key-error-toast

Conversation

@KwonDeaGeun
Copy link
Owner

@KwonDeaGeun KwonDeaGeun commented Sep 18, 2025

Summary by CodeRabbit

  • 새 기능

    • 전역 토스트 알림 도입: 제목/설명·변형(기본·파괴적) 지원, 자동 사라짐 포함.
    • 지도 스크립트 로드 실패 시 사용자에게 파괴적 토스트로 오류 안내.
  • 리팩터링

    • 애플리케이션 루트에 토스트 프로바이더 적용해 전역 컨텍스트 구성.
    • 지도 로드 관련 효과에서 알림 훅 참조를 의존성에 포함하도록 업데이트.
  • 스타일

    • 타입 선언의 유니온 표기 공백 정리(동작 변경 없음).

@KwonDeaGeun KwonDeaGeun self-assigned this Sep 18, 2025
@vercel
Copy link

vercel bot commented Sep 18, 2025

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

Project Deployment Preview Comments Updated (UTC)
what-the-bus-web Ready Ready Preview Comment Sep 18, 2025 1:54am

@coderabbitai
Copy link

coderabbitai bot commented Sep 18, 2025

Walkthrough

App에 useToast를 도입해 Kakao Maps 스크립트 로드 실패를 콘솔 대신 파괴적(destructive) 토스트로 사용자에게 표시하도록 변경했고, 전역으로 ToastProvidermain.tsx에서 래핑하고 토스트 훅/컴포넌트를 새로 추가했습니다. bus.ts는 공백 포맷만 수정되었습니다.

Changes

Cohort / File(s) Summary
Toast 시스템 추가
src/components/ui/use-toast.tsx
토스트 컨텍스트·프로바이더·훅 추가. toast({ title, description, variant }) API, 4초 자동 제거, 상단 고정 토스트 UI 렌더링. ToastProvider, useToast 공개.
App 통합 및 에러 표시 변경
src/App.tsx
useToast 사용으로 Kakao Maps 스크립트 로드 실패 시 destructive 토스트 표시(초기/재시도 경로 모두). try/catch 폴백과 이펙트 의존성에 toast 추가.
Provider 적용(부트스트랩)
src/main.tsx
앱을 ToastProvider로 래핑하여 전역 토스트 컨텍스트 제공.
사소한 타입 포맷 변경
src/data/bus.ts
`direction: string

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as 사용자
  participant App as App (useToast)
  participant Loader as Kakao Script Loader
  participant TP as ToastProvider / Context
  participant UI as Toast UI

  User->>App: 앱 진입 / 지도 초기화
  App->>Loader: Kakao Maps 스크립트 로드 시도
  Loader-->>App: onerror(로드 실패)
  App->>TP: toast({ title, description, variant: "destructive" })
  TP->>UI: 토스트 렌더링 (즉시 표시)
  TP-->>TP: 4초 타이머로 자동 제거
  UI-->>TP: 토스트 제거 요청
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

빨간 토스트 폴짝, 에러가 빙그르르 🐇
스크립트 삑-소리에 알림 퐁당
위로 띄워주니 걱정은 잠깐
사르륵 사라져 네 화면은 깔끔
토끼가 축하할게 — 배포 축포!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목 "feat: 키 발급 실패 시 에러 토스트 추가"은 PR의 핵심 변경사항인 키/맵 로드 실패 시 사용자에게 에러 토스트를 표시하는 기능 추가를 간결하게 요약하며 불필요한 수식 없이 읽기 쉽습니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-key-error-toast

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

Copy link

@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: 1

🧹 Nitpick comments (4)
src/components/ui/use-toast.tsx (3)

27-39: 언마운트 시 남은 타임아웃 정리 필요 (React 경고/메모리 누수 방지)

Provider가 언마운트되면 예약된 setTimeout이 실행되며 setMessages가 호출될 수 있습니다. 정리 루틴을 추가해 주세요.

적용 diff:

-import {
+import {
     createContext,
     useCallback,
     useContext,
+    useEffect,
     useRef,
     useState,
 } from "react";
@@
 export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({
     children,
 }) => {
     const [messages, setMessages] = useState<ToastMessage[]>([]);
     const idRef = useRef(0);
+    const timeoutsRef = useRef(new Set<ReturnType<typeof setTimeout>>());
@@
     const toast = useCallback((opts: ToastOptions) => {
         const id = idRef.current++;
         const timeoutId = setTimeout(() => {
             setMessages((prev) => prev.filter((m) => m.id !== id));
+            timeoutsRef.current.delete(timeoutId);
         }, 4000);
+        timeoutsRef.current.add(timeoutId);
         setMessages((prev) => [{ ...opts, id, timeoutId }, ...prev]);
     }, []);
+
+    useEffect(() => {
+        return () => {
+            // clear pending timeouts on unmount
+            timeoutsRef.current.forEach(clearTimeout);
+            timeoutsRef.current.clear();
+        };
+    }, []);

41-52: 지도 상단 인터랙션 차단 최소화: pointer-events/ARIA 보완 제안

토스트 컨테이너가 지도 상단 클릭을 가로챌 수 있습니다. 컨테이너엔 pointerEvents: "none"을 주고, 카드에만 "auto"를 적용하면 지도 조작 방해를 줄일 수 있습니다. 또한 스크린리더 공지용 ARIA를 추가하세요.

적용 diff:

-            <div
-                style={{
+            <div
+                role="status"
+                aria-live="polite"
+                aria-atomic="true"
+                style={{
                     position: "fixed",
                     left: "50%",
                     transform: "translateX(-50%)",
                     top: 24,
                     zIndex: 9999,
+                    pointerEvents: "none",
                 }}
             >
                 {messages.map((t) => (
                     <div
                         key={t.id}
                         style={{
                             marginTop: 8,
                             background:
                                 t.variant === "destructive"
                                     ? "#ef4444"
                                     : "rgba(0,0,0,0.8)",
                             color: "white",
                             padding: "10px 14px",
                             borderRadius: 8,
                             boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
                             minWidth: 200,
                             textAlign: "center",
+                            pointerEvents: "auto",
                         }}
                     >

Also applies to: 56-69


83-91: Fast Refresh 경고 완화: 훅/Provider 분리 고려

정적 분석 경고대로 컴포넌트 외 export가 섞여 Fast Refresh 최적화가 떨어질 수 있습니다. 영향은 경미하지만, ToastProvider.tsxuseToast.ts로 분리하면 경고가 사라집니다. 유지 시 기능상 문제는 없습니다.

src/App.tsx (1)

300-307: 에러 토스트 처리 방식 일관화 제안

위 분기(라인 270대)와 달리 여기서는 try/catch 없이 호출합니다. 현재 구조상 Provider 보장이 있어 문제는 없으나, 두 경로를 동일한 헬퍼로 묶으면 유지보수가 수월합니다.

예: const showMapLoadError = () => toast({...})로 추출 후 두 곳에서 사용.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c47914f and ef88127.

📒 Files selected for processing (4)
  • src/App.tsx (4 hunks)
  • src/components/ui/use-toast.tsx (1 hunks)
  • src/data/bus.ts (1 hunks)
  • src/main.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/App.tsx (1)
src/components/ui/use-toast.tsx (1)
  • useToast (83-89)
src/main.tsx (1)
src/components/ui/use-toast.tsx (1)
  • ToastProvider (27-81)
🪛 GitHub Check: Build and Test
src/components/ui/use-toast.tsx

[warning] 91-91:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components


[warning] 83-83:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components

🔇 Additional comments (5)
src/data/bus.ts (1)

5-5: 타입 표기 공백 정렬만 변경 — 문제 없습니다

런타임/타입 의미 변화 없고 일관성 좋아졌습니다.

src/main.tsx (1)

5-5: 루트에 ToastProvider 래핑 — 적절합니다

App 전역에서 토스트 사용 가능해졌고, 의존 관계도 명확합니다. 개발 모드(StrictMode)에서 동일 오류가 두 번 발생하면 토스트가 중복될 수 있으니 실제 실패 시 중복 노출 여부만 확인해 주세요.

Also applies to: 14-16

src/App.tsx (3)

4-4: 토스트 훅 도입 연결 OK

useToast 도입 및 구조 분해 할당 문제 없습니다. 의존성 배열에 toast 포함도 적절합니다.

Also applies to: 10-10


270-283: 기존 스크립트 오류 핸들링에 토스트 추가 — 👍 (폴백 콘솔 포함)

Provider 외부 사용 가능성을 대비한 try/catch 폴백도 실용적입니다.


374-374: 의존성 배열 업데이트 적절

toast 참조 안정성 보장 및 훅 교체 시 재실행을 커버합니다.

개발 모드(StrictMode)에서 이 이펙트가 두 번 실행되어 오류 상황에서 토스트가 2회 뜨는지 확인 부탁드립니다. 중복 시 Provider 레벨에서 동일 payload의 연속 토스트 디듀프를 고려할 수 있습니다.

Copy link

@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: 0

♻️ Duplicate comments (1)
src/components/ui/use-toast.tsx (1)

16-19: 브라우저 타이핑 수정 완료 — 이전 리뷰 이슈 해소됨

timeoutIdReturnType<typeof setTimeout>으로 교체한 점 좋습니다. DOM 타겟에서의 NodeJS.Timeout 빌드 오류 우려가 사라졌습니다.

🧹 Nitpick comments (7)
src/components/ui/use-toast.tsx (7)

30-39: 언마운트 시 남은 타이머 정리 필요 (경미한 메모리/경고 예방)

Provider가 언마운트될 때, 스케줄된 setTimeout을 정리하지 않으면 개발 모드에서 “unmounted component” 경고 가능성이 있습니다. 활성 타이머를 ref로 추적하고 언마운트 시 일괄 clearTimeout하세요.

적용 diff:

@@
-import {
-    createContext,
-    useCallback,
-    useContext,
-    useRef,
-    useState,
-} from "react";
+import {
+    createContext,
+    useCallback,
+    useContext,
+    useEffect,
+    useRef,
+    useState,
+} from "react";
@@
     const [messages, setMessages] = useState<ToastMessage[]>([]);
     const idRef = useRef(0);
+    const timersRef = useRef(new Set<ReturnType<typeof setTimeout>>());
 
     const toast = useCallback((opts: ToastOptions) => {
         const id = idRef.current++;
-        const timeoutId = setTimeout(() => {
-            setMessages((prev) => prev.filter((m) => m.id !== id));
-        }, 4000);
+        const timeoutId = setTimeout(() => {
+            setMessages((prev) => prev.filter((m) => m.id !== id));
+            timersRef.current.delete(timeoutId);
+        }, 4000);
+        timersRef.current.add(timeoutId);
         setMessages((prev) => [{ ...opts, id, timeoutId }, ...prev]);
     }, []);
+
+    useEffect(() => {
+        return () => {
+            timersRef.current.forEach(clearTimeout);
+            timersRef.current.clear();
+        };
+    }, []);

Also applies to: 2-8


41-43: Context value 메모이제이션으로 불필요한 리렌더 방지

value={{ toast }}는 매 렌더마다 새 객체를 만들어 모든 소비자 리렌더를 유발합니다. useMemo로 안정화하세요.

적용 diff:

@@
-import {
+import {
     createContext,
     useCallback,
     useContext,
+    useMemo,
     useRef,
     useState,
 } from "react";
@@
-    const toast = useCallback((opts: ToastOptions) => {
+    const toast = useCallback((opts: ToastOptions) => {
         const id = idRef.current++;
@@
-    }, []);
+    }, []);
+    const ctxValue = useMemo(() => ({ toast }), [toast]);
@@
-        <ToastContext.Provider value={{ toast }}>
+        <ToastContext.Provider value={ctxValue}>

Also applies to: 2-8, 39-40


54-56: a11y: 스크린 리더 공지(role/aria-live) 추가

파괴적 토스트는 즉시 공지(assertive), 일반 토스트는 정중 공지(polite)가 적합합니다.

적용 diff:

-                {messages.map((t) => (
-                    <div
-                        key={t.id}
+                {messages.map((t) => (
+                    <div
+                        key={t.id}
+                        role={t.variant === "destructive" ? "alert" : "status"}
+                        aria-live={t.variant === "destructive" ? "assertive" : "polite"}

45-52: 오버레이 클릭 가로채기 방지

고정 상단 오버레이가 클릭을 가로챌 수 있습니다. 컨테이너에 pointerEvents: "none"을 주고, 토스트 카드에만 "auto"를 설정하세요.

적용 diff:

                 <div
                     style={{
                         position: "fixed",
                         left: "50%",
                         transform: "translateX(-50%)",
                         top: 24,
                         zIndex: 9999,
+                        pointerEvents: "none",
                     }}
                 >
                 {messages.map((t) => (
                     <div
                         key={t.id}
                         style={{
+                            pointerEvents: "auto",
                             marginTop: 8,
                             background:
                                 t.variant === "destructive"
                                     ? "#ef4444"
                                     : "rgba(0,0,0,0.8)",

Also applies to: 56-68


58-63: 색 대비(contrast) 개선 제안

#ef4444 위의 흰색 텍스트는 소문자 본문 기준 WCAG AA 대비(4.5:1) 미달 가능성이 큽니다. 더 진한 배경([#dc2626] 또는 [#b91c1c])을 쓰거나 텍스트색을 어둡게 조정 검토 바랍니다.


10-14: 토스트 표시 시간(duration) 옵션화

고정 4초 대신 옵션으로 제어 가능하면 재사용성이 올라갑니다(에러 토스트는 더 길게 등).

적용 diff:

 type ToastOptions = {
     title?: string;
     description?: string;
     variant?: "default" | "destructive";
+    duration?: number; // ms
 };
@@
-        const timeoutId = setTimeout(() => {
+        const timeoutId = setTimeout(() => {
             setMessages((prev) => prev.filter((m) => m.id !== id));
-        }, 4000);
+        }, opts.duration ?? 4000);

Also applies to: 35-37


83-91: Fast Refresh 경고 해결: 파일 분리 제안

정적 분석 경고대로 컴포넌트 외 export가 섞여 있어 Fast Refresh가 비활성화될 수 있습니다. ToastProvider.tsx(컴포넌트만 export)와 use-toast.ts(hook만 export)로 분리하면 경고가 사라집니다. 지금 구조 유지가 필요하면 무시 가능하나, 개발 경험 향상을 위해 분리 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef88127 and f8b3f51.

📒 Files selected for processing (1)
  • src/components/ui/use-toast.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Check: Build and Test
src/components/ui/use-toast.tsx

[warning] 91-91:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components


[warning] 83-83:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components

🔇 Additional comments (1)
src/components/ui/use-toast.tsx (1)

27-81: 전반적 구현 품질 양호

간결한 API(toast(opts)), 선입 스택, 변형(variant) 처리 모두 명확합니다. 카카오 맵 로드 실패 시 사용자 피드백 UX가 개선됩니다.

@KwonDeaGeun KwonDeaGeun merged commit 3410044 into main Sep 18, 2025
4 checks passed
@KwonDeaGeun KwonDeaGeun deleted the feat/add-key-error-toast branch September 18, 2025 02:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant