From 4dfef5208c141c4eb982aad746738cf05ac7ffa2 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Mon, 29 Dec 2025 21:09:53 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20axios.ts=EC=97=90=EC=84=9C=20baseURL?= =?UTF-8?q?=20=ED=83=80=EC=9E=85=20=EB=AA=85=EC=8B=9C=20=EB=B0=8F=20useSto?= =?UTF-8?q?mp.ts=EC=97=90=EC=84=9C=20=EC=95=88=EC=A0=84=ED=95=9C=20WS=20UR?= =?UTF-8?q?L=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20ChatPage.tsx=EC=97=90=EC=84=9C=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EA=B8=B8=EC=9D=B4=20=EC=A0=9C=ED=95=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20MentoringPage.tsx=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EC=A0=95=EB=B3=B4=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EA=B8=B0=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/shared/lib/axios.ts | 2 +- src/hooks/useStomp.ts | 45 +++++++++++++-------------- src/pages/chat/ChatPage.tsx | 5 +++ src/pages/mentoring/MentoringPage.tsx | 39 ++++++++++++++--------- 4 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/assets/shared/lib/axios.ts b/src/assets/shared/lib/axios.ts index a87c842e..4b05b89f 100644 --- a/src/assets/shared/lib/axios.ts +++ b/src/assets/shared/lib/axios.ts @@ -9,7 +9,7 @@ export class TokenRefreshError extends Error { } } -export const baseURL = import.meta.env.DEV +export const baseURL: string = import.meta.env.DEV ? '' : 'https://port-0-gami-server-mj0rdvda8d11523e.sel3.cloudtype.app'; diff --git a/src/hooks/useStomp.ts b/src/hooks/useStomp.ts index b21fecb2..6970d271 100644 --- a/src/hooks/useStomp.ts +++ b/src/hooks/useStomp.ts @@ -211,40 +211,37 @@ export function useStomp( isConnectingRef.current = true; - const wsUrl = `${baseURL}/ws`; + // 안전한 WS URL 생성: 개발환경에서는 baseURL이 빈 문자열이므로 + // origin을 사용해 백엔드 endpoint로 연결하도록 합니다. + // baseURL may be '' in dev. Ensure we have a usable origin string. + const backendOriginRaw = baseURL && baseURL !== '' ? baseURL : window.location.origin; + let backendOrigin = String(backendOriginRaw); + + // If someone provided a ws/wss URL, convert to http/https for SockJS. + if (backendOrigin.startsWith('ws:')) { + backendOrigin = backendOrigin.replace(/^ws:/, 'http:'); + } else if (backendOrigin.startsWith('wss:')) { + backendOrigin = backendOrigin.replace(/^wss:/, 'https:'); + } + + // Ensure trailing slash handling + const sockjsUrl = backendOrigin.endsWith('/') + ? backendOrigin + 'ws' + : backendOrigin + '/ws'; if (connectionTimeoutIdRef.current) { clearTimeout(connectionTimeoutIdRef.current); connectionTimeoutIdRef.current = null; } - const socket = new SockJS(wsUrl, null, { - transports: ['websocket', 'xhr-streaming', 'xhr-polling'], - }); - - socket.onerror = (error: Event) => { - console.error('SockJS 오류:', error); - isConnectingRef.current = false; - if (connectionTimeoutIdRef.current) { - clearTimeout(connectionTimeoutIdRef.current); - connectionTimeoutIdRef.current = null; - } - }; - - socket.onclose = () => { - isConnectingRef.current = false; - if (connectionTimeoutIdRef.current) { - clearTimeout(connectionTimeoutIdRef.current); - connectionTimeoutIdRef.current = null; - } - }; - const client = new Client({ - webSocketFactory: () => socket as WebSocket, + // SockJS 인스턴스를 webSocketFactory에서 직접 생성해서 반환합니다. + webSocketFactory: () => new SockJS(sockjsUrl), connectHeaders: { Authorization: `Bearer ${token}`, }, - reconnectDelay: 0, + // 재접속 지연을 두어 반복 연결 시도를 완화합니다. + reconnectDelay: 2000, heartbeatIncoming: 4000, heartbeatOutgoing: 4000, connectionTimeout: 10000, diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx index ab7ea790..ed59478b 100644 --- a/src/pages/chat/ChatPage.tsx +++ b/src/pages/chat/ChatPage.tsx @@ -406,6 +406,11 @@ export default function ChatPage() { return; } + if (message.length > 250) { + toast.error(`메시지는 250자 이하로 작성해주세요. (현재 ${message.length}자)`); + return; + } + if (!isConnected()) { toast.error( 'WebSocket이 연결되지 않았습니다. 잠시 후 다시 시도해주세요.' diff --git a/src/pages/mentoring/MentoringPage.tsx b/src/pages/mentoring/MentoringPage.tsx index 0ccde095..2c26cbf7 100644 --- a/src/pages/mentoring/MentoringPage.tsx +++ b/src/pages/mentoring/MentoringPage.tsx @@ -62,22 +62,33 @@ export default function MentoringPage() { const fetchData = async () => { try { setIsLoading(true); - const [mentorsResponse, memberResponse, sentAppliesResponse] = - await Promise.all([ - instance.get('/api/mentoring/mentor/all', { - params: { - page: 0, - size: 100, - }, - }), - instance.get('/api/member'), - instance - .get(API_PATHS.MENTORING_APPLY_SENT) - .catch(() => ({ data: [] })), - ]); + + // 먼저 현재 멤버 정보를 가져와 generation을 확보합니다. + let memberResponse: { data: MemberInfo } | null = null; + try { + memberResponse = await instance.get('/api/member'); + setCurrentMemberId(memberResponse.data.memberId); + } catch (memberErr) { + // 멤버 조회 실패(예: 비인증) 시에도 멘토 목록은 generation 없이 가져옵니다. + memberResponse = null; + setCurrentMemberId(null); + } + + const mentorParams: any = { page: 0, size: 100 }; + if (memberResponse && memberResponse.data.generation != null) { + mentorParams.generation = memberResponse.data.generation; + } + + const [mentorsResponse, sentAppliesResponse] = await Promise.all([ + instance.get('/api/mentoring/mentor/all', { + params: mentorParams, + }), + instance + .get(API_PATHS.MENTORING_APPLY_SENT) + .catch(() => ({ data: [] })), + ]); setAllMentors(mentorsResponse.data.content); - setCurrentMemberId(memberResponse.data.memberId); if (Array.isArray(sentAppliesResponse.data)) { setSentApplies(sentAppliesResponse.data); } From 51c928bb74c572bfaaec6d1264c1683679b4d76d Mon Sep 17 00:00:00 2001 From: koreahghg Date: Mon, 29 Dec 2025 21:15:57 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20useStomp.ts=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=95=88=EC=A0=84=ED=95=9C=20WS=20URL=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20ChatPage.?= =?UTF-8?q?tsx=EC=97=90=EC=84=9C=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EA=B8=B8?= =?UTF-8?q?=EC=9D=B4=20=EC=A0=9C=ED=95=9C=20=EC=98=A4=EB=A5=98=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=ED=8F=AC=EB=A7=B7=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useStomp.ts | 3 ++- src/pages/chat/ChatPage.tsx | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hooks/useStomp.ts b/src/hooks/useStomp.ts index 6970d271..d33a50d8 100644 --- a/src/hooks/useStomp.ts +++ b/src/hooks/useStomp.ts @@ -214,7 +214,8 @@ export function useStomp( // 안전한 WS URL 생성: 개발환경에서는 baseURL이 빈 문자열이므로 // origin을 사용해 백엔드 endpoint로 연결하도록 합니다. // baseURL may be '' in dev. Ensure we have a usable origin string. - const backendOriginRaw = baseURL && baseURL !== '' ? baseURL : window.location.origin; + const backendOriginRaw = + baseURL && baseURL !== '' ? baseURL : window.location.origin; let backendOrigin = String(backendOriginRaw); // If someone provided a ws/wss URL, convert to http/https for SockJS. diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx index ed59478b..efddb700 100644 --- a/src/pages/chat/ChatPage.tsx +++ b/src/pages/chat/ChatPage.tsx @@ -407,7 +407,9 @@ export default function ChatPage() { } if (message.length > 250) { - toast.error(`메시지는 250자 이하로 작성해주세요. (현재 ${message.length}자)`); + toast.error( + `메시지는 250자 이하로 작성해주세요. (현재 ${message.length}자)` + ); return; } From d046b439cf7d504e239dac066c163e2059da33e9 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Mon, 29 Dec 2025 21:16:54 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20axios.ts=EC=97=90=EC=84=9C=20baseURL?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=20=EB=B0=8F=20=EA=B8=B0=EB=B3=B8=20=EB=B0=B1=EC=97=94=EB=93=9C?= =?UTF-8?q?=20URL=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/shared/lib/axios.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/assets/shared/lib/axios.ts b/src/assets/shared/lib/axios.ts index 4b05b89f..1812203d 100644 --- a/src/assets/shared/lib/axios.ts +++ b/src/assets/shared/lib/axios.ts @@ -9,9 +9,13 @@ export class TokenRefreshError extends Error { } } -export const baseURL: string = import.meta.env.DEV - ? '' - : 'https://port-0-gami-server-mj0rdvda8d11523e.sel3.cloudtype.app'; +const DEFAULT_BACKEND = + 'https://port-0-gami-server-mj0rdvda8d11523e.sel3.cloudtype.app'; + +// Allow override via Vite env var VITE_API_BASE for local testing. +// Fallback to DEFAULT_BACKEND to avoid dev-time proxying to vite origin. +export const baseURL: string = (import.meta.env.VITE_API_BASE as string) || + DEFAULT_BACKEND; export const instance = axios.create({ baseURL, From 3d2be6f52fe8da2cc1683ff38a4168424d02fc36 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Mon, 29 Dec 2025 22:49:11 +0900 Subject: [PATCH 4/6] fix: ESLint --- src/assets/shared/lib/axios.ts | 4 ++-- src/hooks/useStomp.ts | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/assets/shared/lib/axios.ts b/src/assets/shared/lib/axios.ts index 1812203d..1984cab9 100644 --- a/src/assets/shared/lib/axios.ts +++ b/src/assets/shared/lib/axios.ts @@ -14,8 +14,8 @@ const DEFAULT_BACKEND = // Allow override via Vite env var VITE_API_BASE for local testing. // Fallback to DEFAULT_BACKEND to avoid dev-time proxying to vite origin. -export const baseURL: string = (import.meta.env.VITE_API_BASE as string) || - DEFAULT_BACKEND; +export const baseURL: string = + (import.meta.env.VITE_API_BASE as string) || DEFAULT_BACKEND; export const instance = axios.create({ baseURL, diff --git a/src/hooks/useStomp.ts b/src/hooks/useStomp.ts index d33a50d8..3119bcf3 100644 --- a/src/hooks/useStomp.ts +++ b/src/hooks/useStomp.ts @@ -210,22 +210,16 @@ export function useStomp( } isConnectingRef.current = true; - - // 안전한 WS URL 생성: 개발환경에서는 baseURL이 빈 문자열이므로 - // origin을 사용해 백엔드 endpoint로 연결하도록 합니다. - // baseURL may be '' in dev. Ensure we have a usable origin string. const backendOriginRaw = baseURL && baseURL !== '' ? baseURL : window.location.origin; let backendOrigin = String(backendOriginRaw); - // If someone provided a ws/wss URL, convert to http/https for SockJS. if (backendOrigin.startsWith('ws:')) { backendOrigin = backendOrigin.replace(/^ws:/, 'http:'); } else if (backendOrigin.startsWith('wss:')) { backendOrigin = backendOrigin.replace(/^wss:/, 'https:'); } - - // Ensure trailing slash handling + const sockjsUrl = backendOrigin.endsWith('/') ? backendOrigin + 'ws' : backendOrigin + '/ws'; From 46b5040cf51df9ebec05addf7aebfebe945dab77 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Mon, 29 Dec 2025 22:51:03 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20useStomp.ts=EC=97=90=EC=84=9C=20WebS?= =?UTF-8?q?ocket=20URL=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=EC=9D=98?= =?UTF-8?q?=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EA=B3=B5=EB=B0=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useStomp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useStomp.ts b/src/hooks/useStomp.ts index 3119bcf3..a27f03fe 100644 --- a/src/hooks/useStomp.ts +++ b/src/hooks/useStomp.ts @@ -219,7 +219,7 @@ export function useStomp( } else if (backendOrigin.startsWith('wss:')) { backendOrigin = backendOrigin.replace(/^wss:/, 'https:'); } - + const sockjsUrl = backendOrigin.endsWith('/') ? backendOrigin + 'ws' : backendOrigin + '/ws'; From ebf71df7ab6e1f31f0aee860487818f8bff0f651 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Mon, 29 Dec 2025 22:52:41 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20ChatApplyPage.tsx=EC=97=90=EC=84=9C?= =?UTF-8?q?=20useEffect=EC=9D=98=20eslint=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20MentoringPage.tsx=EC=97=90=EC=84=9C=20M?= =?UTF-8?q?entorParams=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/chat/ChatApplyPage.tsx | 1 - src/pages/mentoring/MentoringPage.tsx | 10 ++++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/pages/chat/ChatApplyPage.tsx b/src/pages/chat/ChatApplyPage.tsx index 929df0c8..3a33caf1 100644 --- a/src/pages/chat/ChatApplyPage.tsx +++ b/src/pages/chat/ChatApplyPage.tsx @@ -78,7 +78,6 @@ export default function ChatApplyPage() { return () => { disconnectWebSocket(); }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [connectWebSocket, disconnectWebSocket]); useEffect(() => { diff --git a/src/pages/mentoring/MentoringPage.tsx b/src/pages/mentoring/MentoringPage.tsx index 2c26cbf7..c77d6dd4 100644 --- a/src/pages/mentoring/MentoringPage.tsx +++ b/src/pages/mentoring/MentoringPage.tsx @@ -50,6 +50,12 @@ interface SentApply { createdAt: string; } +type MentorParams = { + page: number; + size: number; + generation?: number; +}; + export default function MentoringPage() { const [allMentors, setAllMentors] = useState([]); const [searchQuery, setSearchQuery] = useState(''); @@ -68,13 +74,13 @@ export default function MentoringPage() { try { memberResponse = await instance.get('/api/member'); setCurrentMemberId(memberResponse.data.memberId); - } catch (memberErr) { + } catch { // 멤버 조회 실패(예: 비인증) 시에도 멘토 목록은 generation 없이 가져옵니다. memberResponse = null; setCurrentMemberId(null); } - const mentorParams: any = { page: 0, size: 100 }; + const mentorParams: MentorParams = { page: 0, size: 100 }; if (memberResponse && memberResponse.data.generation != null) { mentorParams.generation = memberResponse.data.generation; }