Skip to content
10 changes: 10 additions & 0 deletions src/api/chatApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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})`);
}

Expand All @@ -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})`);
}
35 changes: 27 additions & 8 deletions src/api/clothesApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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()?.();
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/main.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -13,10 +13,10 @@ setStoreGetter(() => store);

ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<HashRouter>
<BrowserRouter>
<SignupProvider>
<App />
</SignupProvider>
</HashRouter>
</BrowserRouter>
</Provider>
);
138 changes: 63 additions & 75 deletions src/pages/Chat/ChatRoomPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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]);

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -467,8 +455,8 @@ function ChatRoomPage() {
>
결제하기
</button>
{/* 구매 취소 버튼 (REQUESTED, MATCHED, SELLING 상태일 때만 표시) */}
{tradeStatus && (tradeStatus === "REQUESTED" || tradeStatus === "MATCHED" || tradeStatus === "SELLING") && (
{/* 구매 취소 버튼 (REQUESTED, MATCHED 상태일 때만 표시) */}
{currentOrderStatus && (currentOrderStatus === "REQUESTED" || currentOrderStatus === "MATCHED") && (
<button
type="button"
onClick={handleCancelOrder}
Expand All @@ -483,9 +471,9 @@ function ChatRoomPage() {
<button
type="button"
onClick={handleAcceptPurchase}
disabled={tradeStatus !== "REQUESTED"}
disabled={!chatData || currentOrderStatus !== "REQUESTED"}
className={`flex-shrink-0 rounded-lg px-3 py-1.5 text-[14px] font-medium transition-colors ${
tradeStatus === "REQUESTED"
chatData && currentOrderStatus === "REQUESTED"
? "bg-[var(--main-color)] text-black hover:bg-[#a4ceb5]"
: "bg-[var(--color-light-grey)] text-[#999] cursor-not-allowed"
}`}
Expand All @@ -506,7 +494,7 @@ function ChatRoomPage() {
) : messages.length === 0 ? (
<div className="flex h-full items-center justify-center">
<p className="text-[14px] text-[#9f9f9f]">
{isBuyer && tradeStatus === "REQUESTED"
{isBuyer && currentOrderStatus === "REQUESTED"
? "판매자가 채팅을 수락하지 않았습니다"
: "메시지가 없습니다."}
</p>
Expand Down Expand Up @@ -550,7 +538,7 @@ function ChatRoomPage() {
<p className={`mt-1 text-[11px] ${
message.type === "user" ? "text-[#999]" : "text-[#666]"
}`}>
{message.timestamp}
{formatChatTime(message.timestamp)}
</p>
)}
</div>
Expand Down
9 changes: 7 additions & 2 deletions src/pages/MyPage/components/SalesHistoryList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import toast from "react-hot-toast";
function SalesHistoryItem({ order, onRefresh, onShowModal }) {
const navigate = useNavigate();
const [isConfirming, setIsConfirming] = useState(false);

// 디버깅용 로그
console.log("SalesHistoryItem order:", order);
console.log("order.status:", order.status);
console.log("order.sellerConfirmed:", order.sellerConfirmed);

const handleItemClick = () => {
if (order?.clothesId) {
Expand Down Expand Up @@ -121,8 +126,8 @@ function SalesHistoryItem({ order, onRefresh, onShowModal }) {
</button>
)}

{/* 물품 전달 완료 버튼 (매칭확정 상태이고 결제 완료된 경우만 표시) */}
{order.status === "MATCHED" && order.isPaid && (
{/* 물품 전달 완료 버튼 (판매중, 판매완료 상태가 아닐 때만 표시) */}
{order.status !== "ON_SALE" && order.status !== "COMPLETED" && (
<button
type="button"
onClick={handleConfirm}
Expand Down
27 changes: 25 additions & 2 deletions src/pages/auth/AuthStartPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,25 @@ const buildKakaoAuthUrl = () => {
export default function AuthStartPage() {
const navigate = useNavigate();

const handleKakaoLogin = () => {
const handleKakaoLogin = (e) => {
e.preventDefault();
e.stopPropagation();

const REST_API_KEY = import.meta.env.VITE_KAKAO_REST_API_KEY;
const REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI;

if (!REST_API_KEY || !REDIRECT_URI) {
console.error('카카오 로그인 환경 변수가 설정되지 않았습니다.');
return;
}

const kakaoAuthUrl = buildKakaoAuthUrl();

if (!kakaoAuthUrl || !kakaoAuthUrl.includes('kauth.kakao.com')) {
console.error('카카오 인증 URL이 올바르지 않습니다:', kakaoAuthUrl);
return;
}

window.location.href = kakaoAuthUrl;
};

Expand Down Expand Up @@ -59,7 +76,13 @@ export default function AuthStartPage() {
className="auth-start__kakao-button"
onClick={handleKakaoLogin}
>
<img src={kakaoImage} alt="카카오 로고" className="auth-start__kakao-img" />
<img
src={kakaoImage}
alt="카카오 로고"
className="auth-start__kakao-img"
draggable={false}
onDragStart={(e) => e.preventDefault()}
/>
</button>

</div>
Expand Down
Loading