Skip to content

Commit 05be886

Browse files
authored
Merge pull request #103 from DMU-DebugVisual/inseong
네비게이션 예외처리 및 로그인 모달 동작 개선
2 parents b1a9a70 + 479b9c7 commit 05be886

File tree

7 files changed

+65
-41
lines changed

7 files changed

+65
-41
lines changed

src/App.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HashRouter, Routes, Route, useLocation } from "react-router-dom";
1+
import { HashRouter, Routes, Route, useLocation, Navigate } from "react-router-dom";
22
import { useEffect, useState } from "react";
33

44
import Header from "./components/header/Header";
@@ -46,6 +46,12 @@ function AppContent() {
4646
}
4747
}, []);
4848

49+
useEffect(() => {
50+
const handleOpenLogin = () => setIsLoginModalOpen(true);
51+
window.addEventListener("dv:open-login-modal", handleOpenLogin);
52+
return () => window.removeEventListener("dv:open-login-modal", handleOpenLogin);
53+
}, []);
54+
4955
useEffect(() => {
5056
const savedTheme = localStorage.getItem("theme");
5157
if (savedTheme === "dark") setIsDark(true);
@@ -89,6 +95,7 @@ function AppContent() {
8995
<Route path="setting" element={<Settings nickname={nickname} />} />
9096
<Route path="shared" element={<Shared />} />
9197
</Route>
98+
<Route path="*" element={<Navigate to="/" replace />} />
9299
</Routes>
93100

94101
{/* 👈 푸터 렌더링 조건 수정: CodecastLive 페이지가 아닐 때만 렌더링 */}

src/components/community/CommunityWrite.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import React, { useEffect, useState } from "react";
1+
import React, { useEffect, useState, useRef } from "react";
22
import { useNavigate, useLocation } from "react-router-dom";
33
import {
44
FaBold, FaItalic, FaStrikethrough, FaLink, FaPalette, FaCode, FaQuoteRight,
55
FaImage, FaHeading, FaListUl, FaListOl, FaMinus
66
} from "react-icons/fa";
77
import "./CommunityWrite.css";
88
import config from "../../config";
9+
import { promptLogin } from "../../utils/auth";
910

1011
// ✅ 백엔드 ENUM과 일치하는 허용 태그
1112
const ALLOWED_TAGS = [
@@ -47,6 +48,7 @@ function parseTagsInput(input) {
4748
export default function CommunityWrite() {
4849
const navigate = useNavigate();
4950
const location = useLocation();
51+
const loginPromptedRef = useRef(false);
5052

5153
const defaultGuide = `- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요!
5254
- 마크다운, 단축키를 이용해서 편리하게 글을 작성할 수 있어요.
@@ -61,8 +63,9 @@ export default function CommunityWrite() {
6163
// ✅ 비회원 접근 차단: 알림 + 커뮤니티 페이지로 이동
6264
useEffect(() => {
6365
const token = localStorage.getItem("token");
64-
if (!token) {
65-
alert("로그인이 필요합니다.");
66+
if (!token && !loginPromptedRef.current) {
67+
loginPromptedRef.current = true;
68+
promptLogin();
6669
navigate("/community", { replace: true, state: { from: location.pathname } });
6770
}
6871
}, [navigate, location.pathname]);
@@ -77,7 +80,7 @@ export default function CommunityWrite() {
7780
const handleSubmit = async () => {
7881
const token = localStorage.getItem("token");
7982
if (!token) {
80-
// 🚨 여기서는 다시 알림 필요 없음 → 이미 진입 차단됨
83+
promptLogin();
8184
return;
8285
}
8386

src/components/community/PostDetail.jsx

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
11
// src/pages/PostDetail.jsx
2-
import React, { useEffect, useState, useMemo, useRef } from "react";
2+
import React, { useEffect, useState, useMemo, useRef, useCallback } from "react";
33
import { useParams, useNavigate } from "react-router-dom";
44
import "./PostDetail.css";
55
import config from "../../config";
6+
import { promptLogin } from "../../utils/auth";
7+
8+
const parseIntSafe = (v) => {
9+
const n = Number(v);
10+
return Number.isFinite(n) ? n : null;
11+
};
12+
13+
const deriveCommentCount = (resp, data) => {
14+
try {
15+
const fromHeader = resp?.headers?.get?.("X-Total-Count");
16+
const n = parseIntSafe(fromHeader);
17+
if (n !== null) return n;
18+
} catch (_) {}
19+
20+
if (Array.isArray(data)) return data.length;
21+
if (data && typeof data === "object") {
22+
if (typeof data.totalElements === "number") return data.totalElements;
23+
if (typeof data.total === "number") return data.total;
24+
if (Array.isArray(data.content)) return data.content.length;
25+
}
26+
return 0;
27+
};
628

729
export default function PostDetail() {
830
const { id } = useParams(); // /community/post/:id
@@ -37,28 +59,6 @@ export default function PostDetail() {
3759
? tokenRaw.startsWith("Bearer ") ? tokenRaw : `Bearer ${tokenRaw}`
3860
: null;
3961

40-
// ===== helpers =====
41-
const parseIntSafe = (v) => {
42-
const n = Number(v);
43-
return Number.isFinite(n) ? n : null;
44-
};
45-
46-
const deriveCommentCount = (resp, data) => {
47-
try {
48-
const fromHeader = resp?.headers?.get?.("X-Total-Count");
49-
const n = parseIntSafe(fromHeader);
50-
if (n !== null) return n;
51-
} catch (_) {}
52-
53-
if (Array.isArray(data)) return data.length;
54-
if (data && typeof data === "object") {
55-
if (typeof data.totalElements === "number") return data.totalElements;
56-
if (typeof data.total === "number") return data.total;
57-
if (Array.isArray(data.content)) return data.content.length;
58-
}
59-
return 0;
60-
};
61-
6262
// 좋아요 수 및 내 상태 재조회
6363
const refreshLikeStatus = async () => {
6464
try {
@@ -141,7 +141,7 @@ export default function PostDetail() {
141141
}, [id, authHeader]); // ✅ navigate 제거
142142

143143
// 공통: 댓글 목록 다시 불러오기
144-
const fetchComments = async () => {
144+
const fetchComments = useCallback(async () => {
145145
try {
146146
setLoadingComments(true);
147147
const bust = Date.now();
@@ -168,20 +168,19 @@ export default function PostDetail() {
168168
} finally {
169169
setLoadingComments(false);
170170
}
171-
};
171+
}, [authHeader, id]);
172172

173173
useEffect(() => {
174174
// authHeader가 있거나 없더라도 댓글은 로드 시도
175175
if (!id) return;
176176
fetchComments();
177-
}, [id, authHeader]);
177+
}, [id, authHeader, fetchComments]);
178178

179179

180180
// 좋아요 토글
181181
const handleToggleLike = async () => {
182182
if (!authHeader) {
183-
alert("로그인이 필요합니다.");
184-
navigate("/login"); // 로그인 페이지로 리다이렉트 (경로 가정)
183+
promptLogin();
185184
return;
186185
}
187186
if (liking) return;
@@ -235,8 +234,7 @@ export default function PostDetail() {
235234
const handleCreateComment = async () => {
236235
if (!newComment.trim()) return;
237236
if (!authHeader) {
238-
alert("로그인이 필요합니다.");
239-
navigate("/login");
237+
promptLogin();
240238
return;
241239
}
242240

@@ -273,8 +271,7 @@ export default function PostDetail() {
273271
const handleCreateReply = async (parentId) => {
274272
if (!replyContent.trim()) return;
275273
if (!authHeader) {
276-
alert("로그인이 필요합니다.");
277-
navigate("/login");
274+
promptLogin();
278275
return;
279276
}
280277

src/components/login/Login.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,26 @@
8080
}
8181

8282
.auth-links a,
83+
.auth-links button,
8384
.auth-links .signup-action {
8485
color: #6a1b9a;
8586
cursor: pointer;
8687
text-decoration: none;
8788
}
8889

8990
.auth-links a:hover,
91+
.auth-links button:hover,
9092
.auth-links .signup-action:hover {
9193
text-decoration: underline;
9294
}
9395

96+
.auth-links button {
97+
background: none;
98+
border: none;
99+
padding: 0;
100+
font: inherit;
101+
}
102+
94103
.auth-links .separator {
95104
color: #ccc;
96105
}
@@ -189,6 +198,7 @@
189198
}
190199

191200
.dark-mode .auth-links a,
201+
.dark-mode .auth-links button,
192202
.dark-mode .auth-links .signup-action {
193203
color: #b88eff;
194204
}

src/components/login/Login.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ function Login({ onClose, onLoginSuccess }) {
4646
}
4747
};
4848

49+
const handleComingSoon = (message) => {
50+
alert(message);
51+
};
52+
4953
return (
5054
<div className="modal-overlay">
5155
<div className="modal-content">
@@ -86,7 +90,7 @@ function Login({ onClose, onLoginSuccess }) {
8690
</form>
8791

8892
<div className="auth-links">
89-
<a href="/forgot-password">비밀번호 찾기</a>
93+
<button type="button" onClick={() => handleComingSoon('비밀번호 찾기 기능은 준비 중입니다.')}>비밀번호 찾기</button>
9094
<span className="separator">|</span>
9195
<span
9296
className="signup-action"
@@ -98,7 +102,7 @@ function Login({ onClose, onLoginSuccess }) {
98102
회원가입
99103
</span>
100104
<span className="separator">|</span>
101-
<a href="/forgot-password">아이디 찾기</a>
105+
<button type="button" onClick={() => handleComingSoon('아이디 찾기 기능은 준비 중입니다.')}>아이디 찾기</button>
102106
</div>
103107

104108
<div className="divider">간편 로그인</div>

src/components/mypage/Sidebar.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import React from 'react';
22
import { NavLink } from 'react-router-dom';
33
import './Sidebar.css';
4-
import { FaTachometerAlt, FaFolderOpen, FaComments, FaGlobe, FaShareAlt, FaCog } from 'react-icons/fa';
4+
import { FaTachometerAlt, FaFolderOpen, FaComments, FaShareAlt, FaCog } from 'react-icons/fa';
55

66
const Sidebar = ({nickname}) => {
77
const menuItems = [
88
{ label: '대시보드', path: '/mypage', icon: <FaTachometerAlt /> },
99
{ label: '프로젝트', path: '/mypage/project', icon: <FaFolderOpen /> },
1010
{ label: '커뮤니티', path: '/mypage/community', icon: <FaComments /> },
11-
{ label: '네트워크', path: '/mypage/network', icon: <FaGlobe /> },
1211
{ label: '공유됨', path: '/mypage/shared', icon: <FaShareAlt /> },
1312
{ label: '설정', path: '/mypage/setting', icon: <FaCog /> },
1413
];

src/utils/auth.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export function promptLogin(message = "로그인이 필요합니다.") {
2+
alert(message);
3+
window.dispatchEvent(new CustomEvent("dv:open-login-modal"));
4+
}

0 commit comments

Comments
 (0)