Skip to content

Commit 6e55ec1

Browse files
hye-inADDINGJOO
andauthored
fix : 연속 선언된 변수 t 제거 (#201)
* [Feat] : AI 프리토킹(Speaking) 기능 구현 (#195) * feat : AI 말하기 연습 화면 구현 * refactor : Rest API 변경에 따른 화면 구현 변경 * feature : AI와 대화하기 UI & STT서비스 구현 * [FEAT] SSE 기반 실시간 알림 시스템 연동 (#197) (#198) * [FEAT] SSE 기반 실시간 알림 시스템 연동 (#197) - SSE(Server-Sent Events)를 통한 실시간 알림 연동 - 알림 타입 정의 (BADGE_EARNED, DAILY_COMPLETE, TEST_COMPLETE 등 8종) - useNotifications 훅 (EventSource API, 자동 재연결) - NotificationContext (알림 상태 관리, 읽음 처리) - NotificationToast (토스트 알림 UI) - NotificationMenu (헤더 드롭다운 메뉴) - VITE_NOTIFICATION_URL 환경 변수 추가 - 기존 하드코딩된 목 알림을 실제 시스템으로 교체 * [FIX] FreetalkAiPage → SpeakingPage 라우트 수정 정의되지 않은 FreetalkAiPage를 SpeakingPage로 교체 * [DEBUG] SSE 연결 디버깅 로그 추가 - connect 함수에 상세 로그 추가 - NotificationContext에 auth 상태 로그 추가 - 브라우저 콘솔에서 연결 상태 확인 가능 * [FIX] StrictMode 중복 연결 방지를 위한 debounce 추가 React StrictMode에서 useEffect가 2번 실행되어 SSE 연결이 중복되는 문제 방지 * [FEAT] 알림 기능 on/off 플래그 추가 (VITE_NOTIFICATION_ENABLED) - VITE_NOTIFICATION_ENABLED 환경 변수로 SSE 연결 제어 - 기본값 false (환경변수 미설정 시 비활성화) - NotificationMenu에 비활성화 상태 UI 추가 - Lambda 동시성 이슈 대응용 * fix : 연속 선언된 변수 t 제거 (#199) * feat : AI 말하기 연습 화면 구현 * refactor : Rest API 변경에 따른 화면 구현 변경 * feature : AI와 대화하기 UI & STT서비스 구현 * [Feat] : AI 프리토킹(Speaking) 기능 구현 (#195) (#196) * feat : AI 말하기 연습 화면 구현 * refactor : Rest API 변경에 따른 화면 구현 변경 * feature : AI와 대화하기 UI & STT서비스 구현 --------- Co-authored-by: DDING JOO <ddingsha9@teambind.co.kr>
1 parent e05a12b commit 6e55ec1

9 files changed

Lines changed: 1035 additions & 124 deletions

File tree

src/App.jsx

Lines changed: 54 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ import { BadgeSection } from './domains/badge'
3232
import CatchmindLobbyPage from './domains/games/pages/CatchmindLobbyPage'
3333
import CatchmindWaitingPage from './domains/games/pages/CatchmindWaitingPage'
3434
import CatchmindPlayPage from './domains/games/pages/CatchmindPlayPage'
35-
import {NewsListPage, NewsDetailPage, NewsQuizPage, NewsWordsPage, NewsStatsPage} from './domains/news'
36-
import {dailyService, statsService} from './domains/vocab/services/vocabService'
37-
import {getNewsStats, getDashboardStats} from './domains/news/services/newsService'
38-
import {useChat} from './contexts/ChatContext'
39-
import {useSettings} from './contexts/SettingsContext'
40-
import {useAuth} from './contexts/AuthContext'
35+
import { NewsListPage, NewsDetailPage, NewsQuizPage, NewsWordsPage, NewsStatsPage } from './domains/news'
36+
import { dailyService, statsService } from './domains/vocab/services/vocabService'
37+
import { getNewsStats, getDashboardStats } from './domains/news/services/newsService'
38+
import { useChat } from './contexts/ChatContext'
39+
import { useSettings } from './contexts/SettingsContext'
40+
import { useAuth } from './contexts/AuthContext'
4141
import LoginPage from './pages/Login'
4242
import SignUpPage from './pages/SignUp'
4343

@@ -93,8 +93,7 @@ function PublicRoute({ children }) {
9393
function Dashboard() {
9494
const navigate = useNavigate()
9595
const [expandedCard, setExpandedCard] = useState(null)
96-
const { t } = useSettings()
97-
const {t, isKorean} = useSettings()
96+
const { t, isKorean } = useSettings()
9897
const [activityData, setActivityData] = useState(null)
9998
const [loadingActivity, setLoadingActivity] = useState(true)
10099

@@ -467,14 +466,14 @@ function Dashboard() {
467466
</Grid>
468467

469468
{/* Today's Activity Stats */}
470-
<Box sx={{mt: 6}}>
469+
<Box sx={{ mt: 6 }}>
471470
{loadingActivity ? (
472471
<Grid container spacing={2}>
473472
{[...Array(4)].map((_, i) => (
474-
<Grid key={i} size={{xs: 6, md: 3}}>
475-
<Card sx={{borderRadius: '16px', height: 140}}>
476-
<CardContent sx={{p: 2.5, textAlign: 'center'}}>
477-
<CircularProgress size={24}/>
473+
<Grid key={i} size={{ xs: 6, md: 3 }}>
474+
<Card sx={{ borderRadius: '16px', height: 140 }}>
475+
<CardContent sx={{ p: 2.5, textAlign: 'center' }}>
476+
<CircularProgress size={24} />
478477
</CardContent>
479478
</Card>
480479
</Grid>
@@ -483,7 +482,7 @@ function Dashboard() {
483482
) : (
484483
<Grid container spacing={2}>
485484
{/* 오늘 외운 단어 */}
486-
<Grid size={{xs: 6, md: 3}}>
485+
<Grid size={{ xs: 6, md: 3 }}>
487486
<Card
488487
onClick={() => navigate('/vocab')}
489488
sx={{
@@ -497,7 +496,7 @@ function Dashboard() {
497496
},
498497
}}
499498
>
500-
<CardContent sx={{p: 2.5, textAlign: 'center'}}>
499+
<CardContent sx={{ p: 2.5, textAlign: 'center' }}>
501500
<Box
502501
sx={{
503502
width: 48,
@@ -511,7 +510,7 @@ function Dashboard() {
511510
mb: 1.5,
512511
}}
513512
>
514-
<VocabIcon sx={{color: '#f97316', fontSize: 24}}/>
513+
<VocabIcon sx={{ color: '#f97316', fontSize: 24 }} />
515514
</Box>
516515
<Typography variant="h4" fontWeight={800} color="#f97316">
517516
{activityData?.todayWords || 0}
@@ -528,7 +527,7 @@ function Dashboard() {
528527
</Card>
529528
</Grid>
530529
{/* 읽은 뉴스 */}
531-
<Grid size={{xs: 6, md: 3}}>
530+
<Grid size={{ xs: 6, md: 3 }}>
532531
<Card
533532
onClick={() => navigate('/news')}
534533
sx={{
@@ -542,7 +541,7 @@ function Dashboard() {
542541
},
543542
}}
544543
>
545-
<CardContent sx={{p: 2.5, textAlign: 'center'}}>
544+
<CardContent sx={{ p: 2.5, textAlign: 'center' }}>
546545
<Box
547546
sx={{
548547
width: 48,
@@ -556,7 +555,7 @@ function Dashboard() {
556555
mb: 1.5,
557556
}}
558557
>
559-
<NewsIcon sx={{color: '#8b5cf6', fontSize: 24}}/>
558+
<NewsIcon sx={{ color: '#8b5cf6', fontSize: 24 }} />
560559
</Box>
561560
<Typography variant="h4" fontWeight={800} color="#8b5cf6">
562561
{activityData?.newsRead || 0}
@@ -568,7 +567,7 @@ function Dashboard() {
568567
</Card>
569568
</Grid>
570569
{/* 총 학습 단어 */}
571-
<Grid size={{xs: 6, md: 3}}>
570+
<Grid size={{ xs: 6, md: 3 }}>
572571
<Card
573572
onClick={() => navigate('/vocab/words')}
574573
sx={{
@@ -582,7 +581,7 @@ function Dashboard() {
582581
},
583582
}}
584583
>
585-
<CardContent sx={{p: 2.5, textAlign: 'center'}}>
584+
<CardContent sx={{ p: 2.5, textAlign: 'center' }}>
586585
<Box
587586
sx={{
588587
width: 48,
@@ -596,7 +595,7 @@ function Dashboard() {
596595
mb: 1.5,
597596
}}
598597
>
599-
<WordListIcon sx={{color: '#f97316', fontSize: 24}}/>
598+
<WordListIcon sx={{ color: '#f97316', fontSize: 24 }} />
600599
</Box>
601600
<Typography variant="h4" fontWeight={800} color="#f97316">
602601
{activityData?.totalWords || 0}
@@ -608,7 +607,7 @@ function Dashboard() {
608607
</Card>
609608
</Grid>
610609
{/* 연속 학습 */}
611-
<Grid size={{xs: 6, md: 3}}>
610+
<Grid size={{ xs: 6, md: 3 }}>
612611
<Card
613612
onClick={() => navigate('/reports')}
614613
sx={{
@@ -622,7 +621,7 @@ function Dashboard() {
622621
},
623622
}}
624623
>
625-
<CardContent sx={{p: 2.5, textAlign: 'center'}}>
624+
<CardContent sx={{ p: 2.5, textAlign: 'center' }}>
626625
<Box
627626
sx={{
628627
width: 48,
@@ -636,7 +635,7 @@ function Dashboard() {
636635
mb: 1.5,
637636
}}
638637
>
639-
<LearnIcon sx={{color: '#ec4899', fontSize: 24}}/>
638+
<LearnIcon sx={{ color: '#ec4899', fontSize: 24 }} />
640639
</Box>
641640
<Typography variant="h4" fontWeight={800} color="#ec4899">
642641
{Math.max(activityData?.vocabStreak || 0, activityData?.newsStreak || 0)}
@@ -666,7 +665,7 @@ function OpicPage() {
666665

667666

668667
function ReportsPage() {
669-
const {isKorean} = useSettings()
668+
const { isKorean } = useSettings()
670669
const [loading, setLoading] = useState(true)
671670
const [stats, setStats] = useState({
672671
totalStudyDays: 0,
@@ -685,7 +684,7 @@ function ReportsPage() {
685684
setLoading(true)
686685
const [vocabStatsRes, vocabHistoryRes, newsStatsRes] = await Promise.allSettled([
687686
statsService.getOverall().catch(() => null),
688-
statsService.getDaily(null, {limit: 30}).catch(() => null),
687+
statsService.getDaily(null, { limit: 30 }).catch(() => null),
689688
getNewsStats().catch(() => null),
690689
])
691690

@@ -719,9 +718,9 @@ function ReportsPage() {
719718

720719
if (loading) {
721720
return (
722-
<Container maxWidth="lg" sx={{py: 4}}>
721+
<Container maxWidth="lg" sx={{ py: 4 }}>
723722
<Box display="flex" justifyContent="center" alignItems="center" minHeight="40vh">
724-
<CircularProgress/>
723+
<CircularProgress />
725724
</Box>
726725
</Container>
727726
)
@@ -815,13 +814,13 @@ function ReportsPage() {
815814

816815
{/* 뉴스 학습 통계 */}
817816
{(stats.newsRead > 0 || stats.newsQuizScore > 0) && (
818-
<Card sx={{p: 3, borderRadius: '20px', mb: 4, backgroundColor: '#f5f3ff'}}>
817+
<Card sx={{ p: 3, borderRadius: '20px', mb: 4, backgroundColor: '#f5f3ff' }}>
819818
<Typography variant="h6" fontWeight={700} gutterBottom color="#8b5cf6">
820819
{isKorean ? '뉴스 학습' : 'News Learning'}
821820
</Typography>
822821
<Grid container spacing={3}>
823-
<Grid size={{xs: 6}}>
824-
<Box sx={{textAlign: 'center'}}>
822+
<Grid size={{ xs: 6 }}>
823+
<Box sx={{ textAlign: 'center' }}>
825824
<Typography variant="h3" fontWeight={800} color="#8b5cf6">
826825
{stats.newsRead}
827826
</Typography>
@@ -830,8 +829,8 @@ function ReportsPage() {
830829
</Typography>
831830
</Box>
832831
</Grid>
833-
<Grid size={{xs: 6}}>
834-
<Box sx={{textAlign: 'center'}}>
832+
<Grid size={{ xs: 6 }}>
833+
<Box sx={{ textAlign: 'center' }}>
835834
<Typography variant="h3" fontWeight={800} color="#8b5cf6">
836835
{stats.newsQuizScore}%
837836
</Typography>
@@ -1161,27 +1160,27 @@ function App() {
11611160
<MainLayout />
11621161
</ProtectedRoute>
11631162
}>
1164-
<Route path="/" element={<Dashboard/>}/>
1165-
<Route path="/dashboard" element={<Dashboard/>}/>
1166-
<Route path="/opic" element={<OpicPage/>}/>
1167-
<Route path="/freetalk/people" element={<FreetalkPeoplePage/>}/>
1168-
<Route path="/freetalk/ai" element={<FreetalkAiPage/>}/>
1169-
<Route path="/writing" element={<WritingPage/>}/>
1170-
<Route path="/vocab" element={<VocabDashboard/>}/>
1171-
<Route path="/vocab/daily" element={<DailyLearning/>}/>
1172-
<Route path="/vocab/test" element={<TestPage/>}/>
1173-
<Route path="/vocab/words" element={<WordListPage/>}/>
1174-
<Route path="/vocab/stats" element={<StatsPage/>}/>
1175-
<Route path="/reports" element={<ReportsPage/>}/>
1176-
<Route path="/settings" element={<SettingsPage/>}/>
1177-
<Route path="/games/catchmind" element={<CatchmindLobbyPage/>}/>
1178-
<Route path="/games/catchmind/:roomId/waiting" element={<CatchmindWaitingPage/>}/>
1179-
<Route path="/games/catchmind/:roomId/play" element={<CatchmindPlayPage/>}/>
1180-
<Route path="/news" element={<NewsListPage/>}/>
1181-
<Route path="/news/:articleId" element={<NewsDetailPage/>}/>
1182-
<Route path="/news/:articleId/quiz" element={<NewsQuizPage/>}/>
1183-
<Route path="/news/words" element={<NewsWordsPage/>}/>
1184-
<Route path="/news/stats" element={<NewsStatsPage/>}/>
1163+
<Route path="/" element={<Dashboard />} />
1164+
<Route path="/dashboard" element={<Dashboard />} />
1165+
<Route path="/opic" element={<OpicPage />} />
1166+
<Route path="/freetalk/people" element={<FreetalkPeoplePage />} />
1167+
<Route path="/freetalk/ai" element={<FreetalkAiPage />} />
1168+
<Route path="/writing" element={<WritingPage />} />
1169+
<Route path="/vocab" element={<VocabDashboard />} />
1170+
<Route path="/vocab/daily" element={<DailyLearning />} />
1171+
<Route path="/vocab/test" element={<TestPage />} />
1172+
<Route path="/vocab/words" element={<WordListPage />} />
1173+
<Route path="/vocab/stats" element={<StatsPage />} />
1174+
<Route path="/reports" element={<ReportsPage />} />
1175+
<Route path="/settings" element={<SettingsPage />} />
1176+
<Route path="/games/catchmind" element={<CatchmindLobbyPage />} />
1177+
<Route path="/games/catchmind/:roomId/waiting" element={<CatchmindWaitingPage />} />
1178+
<Route path="/games/catchmind/:roomId/play" element={<CatchmindPlayPage />} />
1179+
<Route path="/news" element={<NewsListPage />} />
1180+
<Route path="/news/:articleId" element={<NewsDetailPage />} />
1181+
<Route path="/news/:articleId/quiz" element={<NewsQuizPage />} />
1182+
<Route path="/news/words" element={<NewsWordsPage />} />
1183+
<Route path="/news/stats" element={<NewsStatsPage />} />
11851184
</Route>
11861185

11871186
{/* 404 */}

0 commit comments

Comments
 (0)