diff --git a/src/api/chatApi.js b/src/api/chatApi.js
index c243390..1fc2e39 100644
--- a/src/api/chatApi.js
+++ b/src/api/chatApi.js
@@ -20,6 +20,11 @@ export async function getUserChatRooms(type = "ALL") {
throw new Error(result.message || "채팅방 정보를 찾을 수 없습니다.");
}
+ if (response.status === 500) {
+ // 서버 에러
+ throw new Error(result.message || "서버 에러가 발생했습니다. 잠시 후 다시 시도해주세요.");
+ }
+
throw new Error(result.message || `채팅방 목록 조회 실패 (${response.status})`);
}
@@ -46,5 +51,10 @@ export async function getChatRoomMessages(roomId) {
throw new Error(result.message || "존재하지 않는 채팅방입니다.");
}
+ if (response.status === 500) {
+ // 서버 에러
+ throw new Error(result.message || "서버 에러가 발생했습니다. 잠시 후 다시 시도해주세요.");
+ }
+
throw new Error(result.message || `채팅 내역 조회 실패 (${response.status})`);
}
diff --git a/src/api/clothesApi.js b/src/api/clothesApi.js
index fd38297..a686334 100644
--- a/src/api/clothesApi.js
+++ b/src/api/clothesApi.js
@@ -386,11 +386,6 @@ export async function filterClothes(keyword = "", styles = [], priceRangeCodes =
const response = await apiRequestGet(url);
const result = await response.json();
- isSuccess: result.isSuccess,
- message: result.message,
- fullResult: result,
- clothesListLength: result.result?.clothesList?.length || 0
- });
if (response.status === 200 && result.isSuccess === true) {
return result.result; // { clothesList, listSize, totalPage, totalElements, isFirst, isLast }
@@ -416,12 +411,24 @@ export async function filterClothes(keyword = "", styles = [], priceRangeCodes =
export async function deleteClothes(clothesId) {
const url = `${API_BASE_URL}/api/clothes/${clothesId}`;
const body = JSON.stringify({ clothesId });
-
+
let response = await apiRequestDelete(url, {
body,
});
- const result = await response.json();
+ // 403 에러 처리 (응답 본문이 비어있을 수 있음)
+ if (response.status === 403) {
+ throw new Error('삭제 권한이 없는 사용자입니다.');
+ }
+
+ // 응답 본문이 비어있을 수 있으므로 안전하게 파싱
+ let result;
+ const text = await response.text();
+ try {
+ result = text ? JSON.parse(text) : {};
+ } catch {
+ result = {};
+ }
if (result.code === 'MEMBER4001') {
const store = getStoreInstance()?.();
@@ -456,7 +463,19 @@ export async function deleteClothes(clothesId) {
body,
});
- const retryResult = await response.json();
+ // 403 에러 처리
+ if (response.status === 403) {
+ throw new Error('삭제 권한이 없는 사용자입니다.');
+ }
+
+ // 응답 본문이 비어있을 수 있으므로 안전하게 파싱
+ let retryResult;
+ const retryText = await response.text();
+ try {
+ retryResult = retryText ? JSON.parse(retryText) : {};
+ } catch {
+ retryResult = {};
+ }
if (response.status === 200 && retryResult.isSuccess === true) {
return retryResult.result;
diff --git a/src/main.jsx b/src/main.jsx
index 7ed8a5f..4248dbf 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,6 +1,6 @@
import React from "react";
import ReactDOM from "react-dom/client";
-import { HashRouter } from "react-router-dom";
+import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import App from "./App.jsx";
import "./styles/global.css";
@@ -13,10 +13,10 @@ setStoreGetter(() => store);
ReactDOM.createRoot(document.getElementById('root')).render(
-
+
-
+
);
diff --git a/src/pages/Chat/ChatRoomPage.jsx b/src/pages/Chat/ChatRoomPage.jsx
index 6d6abc7..61faed8 100644
--- a/src/pages/Chat/ChatRoomPage.jsx
+++ b/src/pages/Chat/ChatRoomPage.jsx
@@ -10,6 +10,7 @@ import { useRequireAuth } from "../../hooks/useRequireAuth.js";
import { getChatRoomMessages } from "../../api/chatApi.js";
import { getMyPageInfo } from "../../api/memberApi.js";
import { payOrder, acceptOrder, cancelOrder } from "../../api/orderApi.js";
+import { formatChatTime } from "../../utils/formatters.js";
import toast from "react-hot-toast";
function ChatRoomPage() {
@@ -51,52 +52,53 @@ function ChatRoomPage() {
}
}, [isAuthenticated]);
- // 채팅방 내역 조회
- useEffect(() => {
+ // 채팅방 내역 조회 함수
+ const fetchChatMessages = async () => {
if (!roomId || !isAuthenticated) return;
- const fetchChatMessages = async () => {
- try {
- setIsLoadingMessages(true);
- const data = await getChatRoomMessages(Number(roomId));
- setChatData(data);
-
- // 메시지를 senderId와 현재 사용자 ID를 비교하여 타입 변환
- if (currentUserId && data.messages) {
- const formattedMessages = data.messages.map((msg) => ({
- id: msg.messageId,
- type: msg.senderId === currentUserId ? "user" : "partner",
- text: msg.content,
- timestamp: msg.sendTime,
- }));
- setMessages(formattedMessages);
- } else if (data.messages) {
- // currentUserId가 아직 로드되지 않았으면 일단 메시지만 저장
- setMessages(data.messages.map((msg) => ({
- id: msg.messageId,
- type: "partner", // 임시로 partner로 설정
- text: msg.content,
- timestamp: msg.sendTime,
- })));
- }
- } catch (error) {
- console.error("채팅 내역 조회 실패:", error);
- const errorMessage = error.message || "채팅 내역을 불러오지 못했습니다.";
- toast.error(errorMessage);
-
- // 404 에러인 경우 채팅 페이지로 이동
- if (errorMessage.includes("존재하지 않는") || errorMessage.includes("404")) {
- navigate("/chat");
- return;
- }
-
- // 나머지 에러는 토스트 메시지만 표시하고 채팅 페이지로 이동
+ try {
+ setIsLoadingMessages(true);
+ const data = await getChatRoomMessages(Number(roomId));
+ setChatData(data);
+
+ // 메시지를 senderId와 현재 사용자 ID를 비교하여 타입 변환
+ if (currentUserId && data.messages) {
+ const formattedMessages = data.messages.map((msg) => ({
+ id: msg.messageId,
+ type: msg.senderId === currentUserId ? "user" : "partner",
+ text: msg.content,
+ timestamp: msg.sendTime,
+ }));
+ setMessages(formattedMessages);
+ } else if (data.messages) {
+ // currentUserId가 아직 로드되지 않았으면 일단 메시지만 저장
+ setMessages(data.messages.map((msg) => ({
+ id: msg.messageId,
+ type: "partner", // 임시로 partner로 설정
+ text: msg.content,
+ timestamp: msg.sendTime,
+ })));
+ }
+ } catch (error) {
+ console.error("채팅 내역 조회 실패:", error);
+ const errorMessage = error.message || "채팅 내역을 불러오지 못했습니다.";
+ toast.error(errorMessage);
+
+ // 404 에러인 경우 채팅 페이지로 이동
+ if (errorMessage.includes("존재하지 않는") || errorMessage.includes("404")) {
navigate("/chat");
- } finally {
- setIsLoadingMessages(false);
+ return;
}
- };
-
+
+ // 나머지 에러는 토스트 메시지만 표시하고 채팅 페이지로 이동
+ navigate("/chat");
+ } finally {
+ setIsLoadingMessages(false);
+ }
+ };
+
+ // 채팅방 내역 조회
+ useEffect(() => {
fetchChatMessages();
}, [roomId, isAuthenticated, currentUserId, navigate]);
@@ -194,21 +196,21 @@ function ChatRoomPage() {
// tradeStatus 가져오기
const tradeStatus = chatData?.clothesInfo?.tradeStatus;
+ // orderStatus 가져오기 (chatData에서도 확인)
+ const currentOrderStatus = chatData?.orderStatus || orderStatus;
- // tradeStatus에 따른 입력창 비활성화
- // BUYER이고 REQUESTED 상태일 때, 또는 SELLER이고 REQUESTED 상태일 때 비활성화
- const isInputDisabled =
- (isBuyer && tradeStatus === "REQUESTED") ||
- (isSeller && tradeStatus === "REQUESTED") ||
- orderStatus === "REQUESTED";
+ // orderStatus에 따른 입력창 비활성화
+ // orderStatus가 REQUESTED일 때 비활성화
+ const isInputDisabled = currentOrderStatus === "REQUESTED";
// 구매자 버튼 상태 (BUYER일 때만)
const getBuyerButtonState = () => {
if (!isBuyer) return null;
- const isPaid = chatData?.clothesInfo?.isPaid;
+ // isPaid 확인 (여러 경로에서 확인)
+ const isPaid = chatData?.clothesInfo?.isPaid || chatData?.isPaid;
- switch (tradeStatus) {
+ switch (currentOrderStatus) {
case "REQUESTED":
return {
disabled: true,
@@ -217,7 +219,7 @@ function ChatRoomPage() {
cursor: "cursor-not-allowed",
};
case "MATCHED":
- // MATCHED 상태에서 isPaid가 true면 비활성화
+ // MATCHED 상태에서 isPaid가 true면 비활성화하고 --main-color 사용
if (isPaid) {
return {
disabled: true,
@@ -323,16 +325,9 @@ function ChatRoomPage() {
try {
const paymentResult = await payOrder(orderId, usedPoints);
- // 결제 성공 시 chatData 업데이트
+ // 결제 성공 시 채팅방 데이터 다시 가져오기
if (paymentResult.isPaid) {
- setChatData((prev) => ({
- ...prev,
- clothesInfo: {
- ...prev.clothesInfo,
- isPaid: true,
- },
- }));
-
+ await fetchChatMessages();
toast.success("결제가 완료되었습니다.");
}
} catch (error) {
@@ -353,16 +348,9 @@ function ChatRoomPage() {
try {
const acceptResult = await acceptOrder(orderId);
- // 수락 성공 시 chatData 업데이트 (status를 MATCHED로 변경)
+ // 수락 성공 시 채팅방 데이터 다시 가져오기
if (acceptResult.status === "MATCHED") {
- setChatData((prev) => ({
- ...prev,
- clothesInfo: {
- ...prev.clothesInfo,
- tradeStatus: "MATCHED",
- },
- }));
-
+ await fetchChatMessages();
toast.success("구매 요청을 수락했습니다. 이제 대화할 수 있습니다.");
}
} catch (error) {
@@ -467,8 +455,8 @@ function ChatRoomPage() {
>
결제하기
- {/* 구매 취소 버튼 (REQUESTED, MATCHED, SELLING 상태일 때만 표시) */}
- {tradeStatus && (tradeStatus === "REQUESTED" || tradeStatus === "MATCHED" || tradeStatus === "SELLING") && (
+ {/* 구매 취소 버튼 (REQUESTED, MATCHED 상태일 때만 표시) */}
+ {currentOrderStatus && (currentOrderStatus === "REQUESTED" || currentOrderStatus === "MATCHED") && (
)}
- {/* 물품 전달 완료 버튼 (매칭확정 상태이고 결제 완료된 경우만 표시) */}
- {order.status === "MATCHED" && order.isPaid && (
+ {/* 물품 전달 완료 버튼 (판매중, 판매완료 상태가 아닐 때만 표시) */}
+ {order.status !== "ON_SALE" && order.status !== "COMPLETED" && (
diff --git a/src/pages/category/CategoryResultPage.jsx b/src/pages/category/CategoryResultPage.jsx
index 23d8332..62a85e7 100644
--- a/src/pages/category/CategoryResultPage.jsx
+++ b/src/pages/category/CategoryResultPage.jsx
@@ -1,6 +1,7 @@
import { useEffect, useState, useCallback, useRef } from "react";
import { useLocation, useNavigate, useSearchParams } from "react-router-dom";
import { useRequireAuth } from "../../hooks/useRequireAuth.js";
+import { MdArrowBackIosNew } from "react-icons/md";
import { getClothesByCategory } from "../../api/clothesApi.js";
function CategoryResultPage() {
@@ -154,9 +155,9 @@ function CategoryResultPage() {
diff --git a/src/utils/formatters.js b/src/utils/formatters.js
index 9871810..5d6a68e 100644
--- a/src/utils/formatters.js
+++ b/src/utils/formatters.js
@@ -48,3 +48,39 @@ export function formatPrice(price) {
return numPrice.toLocaleString("ko-KR");
}
+/**
+ * 채팅 메시지 시간 포맷팅 유틸리티
+ * 시간을 "오후 2:30" 형식으로 변환 (한국 시간대)
+ * @param {string|Date|null|undefined} dateString - 포맷팅할 날짜 문자열 또는 Date 객체
+ * @returns {string} 포맷팅된 시간 문자열 (예: "오후 2:30")
+ */
+export function formatChatTime(dateString) {
+ if (!dateString) return "";
+
+ let date;
+ if (typeof dateString === 'string') {
+ date = new Date(dateString);
+ } else if (dateString instanceof Date) {
+ date = dateString;
+ } else {
+ return "";
+ }
+
+ // 유효하지 않은 날짜 체크
+ if (isNaN(date.getTime())) {
+ return "";
+ }
+
+ // 한국 시간대로 변환 (Asia/Seoul, UTC+9)
+ // toLocaleString으로 한국 시간대의 시간 정보를 가져옴
+ const options = {
+ timeZone: "Asia/Seoul",
+ hour: "numeric",
+ minute: "2-digit",
+ hour12: true
+ };
+
+ // "오전 1:46" 또는 "오후 2:30" 형식으로 자동 변환됨
+ return date.toLocaleString("ko-KR", options);
+}
+