Skip to content

Commit

Permalink
Siyeon (#39)
Browse files Browse the repository at this point in the history
* fix : 예약 취소 버튼을 눌렀을 때 다른 페이지로 이동, 체크인 날짜부터는 예약 취소 버튼 비활성화

결제 취소를 위해 데이터도 넘겨줘야 할 것 같음 - 다시 확인 필요
예약 취소 완료 페이지로 이동해야 하는데 아직 url을 몰라서 나중에 수정해야 함

* feat : 에약 취소내역 상세페이지 구현

* fix : 예약 취소된 내역을 예약 상세페이지로 접근하면 예약 취소 상세페이지를 렌더링

* refactor : 필요 없어진 중복 코드 제거

* fix : 예약 상세페이지에 호스트 정보 추가

* fix : 예약 상세페이지 url 변경

/reserve/{reserveId} -> /reserve/detail/{reserveId}
/reserve/{hotelId}가 필요해서 변경함

* feat : 예약하기 페이지 화면 기본 구성

* fix : 예약하기 페이지 달력 수정

* fix : 예약하기 페이지에서 인원 선택 시 최대 인원 넘어가지 않도록 설정

* fix : 예약하기 페이지 날짜 선택 화면 수정

체크인 날짜, 체크아웃 날짜 각각 달력 표시
현재 날짜부터 선택 가능, 현재 날짜부터 2년 뒤까지만 선택 가능
react-datepicker, date-fns, dayjs, @mui/icons-material @mui/material @emotion/styled @emotion/react, lodash 설치 필요
아직 예약 불가능한 날짜 비활성화 기능은 없음

* fix : 예약하기 페이지 달력에서 선택 불가능한 날짜 비활성화 설정

아직 DB에서 예약되어있는 날짜를 조회하진 않음
샘플 코드로 선택 불가능한 날짜를 설정했을 때 제대로 동작하는지만 테스트

* fix : 예약하기 페이지 결제 금액 정보 추가

현재는 단순히 1박 가격만 띄워줌. 수정 필요

* fix : 예약하기 페이지 호텔 정보 및 호스트 정보 수정

* fix : HotelReservation에서 isLoading -> isHotelLoading 수정

* feat : 이용 완료된 예약에 한하여 리뷰 작성 버튼 추가

* fix : Calendar에서 사용하던 startDate, endDate와 useState를 HotelReservation으로 이동

HotelReservation에서 그 값을 사용하기 위함
Calendar에는 props로 전달함

* feat : 예약하기 POST

체크인/체크아웃 날짜, 선택한 기간에 대한 결제 요금 전송
isPaid는 false로 전송해서 아직 결제가 되지 않았음을 나타냄(예약 완료되지 않음)

* feat : 멤버 관련해서 접근 권한 설정

* fix : 예약 기간 계산하는 코드 수정, 주석 제거

* feat : 예약하기 버튼 누르면 결제하기 페이지로 라우팅

* feat : 예약할 때 날짜 선택 시 해당 숙소에 예약되어 있는 날짜들 비활성화하기 위한 GET 요청

* fix : 예약하기 페이지 캘린더 수정

* fix : 선택한 체크인/체크아웃 날짜가 디비에는 하루씩 당겨져서 저장되는 문제 해결, 날짜 선택할 때마다 결제 금액 변경

* refactor : 사용하지 않는 달력 코드 삭제

* fix : 이미지 안 뜨는 문제 해결 - Next의 Image 컴포넌트 사용

* refactor : 날짜 관련한 로직 수정

* feat : 예약 취소 버튼 라우팅

* refactor : UI 깔끔하게 수정

* feat : 리뷰 등록 라우팅, 예약 취소 라우팅

* fix : 예약 취소 버튼 라우팅 에러 해결

추가로 reserveId를 전역 상태로 저장하기 위한 Recoil 설정 중

* feat : 예약 상세페이지에서 숙소 이미지와 타이틀 클릭하면 숙소 상세페이지로 이동

* feat : 리뷰가 이미 작성된 예약 내역에서 리뷰 등록 버튼 비활성화

* feat : 예약 데이터 reserveId 전역 상태로 저장

* fix : 결제페이지에서 다른 페이지로 이동한 후 다시 예약 시도하면 새로운 예약 데이터 생성되는 문제 해결
결제가 완료되면 recoil로 관리하던 reserveId를 null로 초기화
결제까지 완료되지 않은 경우에는 다른 숙소 예약해도 기존 예약 임시데이터 사용해서 데이터 변경만 함

* refactor : 디자인 수정

* fix : 달력 날짜 선택 부분 수정...

* fix : 달력 날짜 선택 부분 수정
중간에 하루만 예약되어있지 않은 경우도 선택 못하게 비활성화
체크인/체크아웃 날짜 같은 날짜로 선택되지 않게 변경

* feat : 웹소켓 설정, 채팅방 생성

* feat : 문의하기 버튼 클릭 시 채팅방 생성 후 해당 채팅방 페이지로 이동

* fix : 특정 채팅방만 구독

* feat : 채팅방 UI 변경, 채팅 상대 정보, 송수신자 메세지 분리, 스크롤

* fix : 채팅 메세지 날짜 형식 변경

* refactor : useChatRoomInfo에서 error 부분 이름 변경

* feat : 채팅방 이전 채팅 내역 불러와서 띄우기

* refactor : 사용하지 않는 코드 제거

* feat : 마이페이지 문의내역

* feat : 마이페이지에 문의내역
  • Loading branch information
jkeum-dev authored Mar 7, 2024
1 parent 8df7a23 commit 5f87a83
Show file tree
Hide file tree
Showing 9 changed files with 1,514 additions and 7,747 deletions.
9,029 changes: 1,282 additions & 7,747 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@mui/material": "^5.15.6",
"@nextui-org/react": "^2.2.9",
"@sentry/react": "^7.98.0",
"@stomp/stompjs": "^7.0.0",
"@tanstack/react-query": "^5.8.4",
"@tanstack/react-query-devtools": "^5.8.4",
"@tosspayments/payment-widget-sdk": "^0.10.2",
Expand All @@ -38,6 +39,7 @@
"react-use": "^17.5.0",
"recoil": "^0.7.7",
"recoil-persist": "^5.1.0",
"sockjs-client": "^1.6.1",
"swiper": "^11.0.6"
},
"devDependencies": {
Expand Down
6 changes: 6 additions & 0 deletions src/app/chat/[id]/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react'
import Chat from '@/components/chat/Chat'

export default function page({ params: { id } }) {
return <Chat id={id} />
}
5 changes: 5 additions & 0 deletions src/app/mypage/chats/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import MyChatRooms from "@/components/mypage/MyChatRooms";

export default function page() {
return <MyChatRooms/>;
}
128 changes: 128 additions & 0 deletions src/components/chat/Chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
'use client'

import { useEffect, useState, useRef } from 'react';
import { Stomp } from '@stomp/stompjs';
import SockJS from 'sockjs-client';
import { useUser } from '@/hooks/useUser';
import { useChatRoomInfo, useChatMessageList } from '@/hooks/useChat';
import { format } from 'date-fns';

export default function Chat({ id }) {
const [stompClient, setStompClient] = useState(null);
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([]);
const messagesContainerRef = useRef(null);
const { user, isLoading, isError } = useUser();
const { chatRoom, isChatLoading, isChatError } = useChatRoomInfo(id);
const { chatMessages, isMsgLoading, isMsgError } = useChatMessageList(id);

useEffect(() => {
if (chatMessages && chatMessages.objData && chatMessages.objData.messageList) {
setMessages(chatMessages.objData.messageList);
}
}, [chatMessages]);

useEffect(() => {
const socket = new SockJS(`${process.env.NEXT_PUBLIC_BASE_URL}/chat`);
const client = Stomp.over(socket);
client.connect({}, (frame) => {
console.log('Connected: ' + frame);
client.subscribe(`/topic/messages/${id}`, (message) => {
console.log('Received: ' + message.body);
setMessages((prevMessages) => [...prevMessages, JSON.parse(message.body)]);
});
});
setStompClient(client);

return () => {
if (client && client.connected) {
client.disconnect();
}
};
}, []);

useEffect(() => {
// 채팅 메시지 컨테이너의 스크롤 높이를 최신 값으로 설정하여 스크롤을 아래로 이동
const messagesContainer = messagesContainerRef.current;
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
}, [messages]);

if (isChatLoading || isLoading || isMsgLoading || !user?.objData || !chatRoom?.objData || !chatMessages?.objData) {
return <div className="h-[60vh] mt-32">loading</div>;
}

if (isChatError || isError || isMsgError) {
return <div className="h-[60vh] mt-32">Error</div>;
}

const sendMessage = () => {
if (stompClient && stompClient.connected) {
const chatMessage = {
content: message,
sender: user.objData.nickname,
};
stompClient.send(`/app/chat.sendMessage/${id}`, {}, JSON.stringify(chatMessage));
setMessage('');
}
}

const contactTo = user.objData.nickname === chatRoom.objData.userNickname ? chatRoom.objData.hostNickname : chatRoom.objData.userNickname;

return (
<div className="flex flex-col h-[80vh] max-w-2xl mx-auto border border-gray-200 bg-gray-100 mt-32">
<div className="p-4 text-lg font-semibold">
{/* 이전 페이지로 돌아가기 */}
{contactTo}
</div>
<hr className="border-gray-200" />
<div ref={messagesContainerRef} className="messages-container flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, idx) => (
// 현재 사용자가 보낸 메시지인지 판단하여 ChatMessage 컴포넌트에 전달
<ChatMessage key={idx} msg={msg} isCurrentUser={msg.sender === user.objData.nickname} />
))}
</div>
<div className="p-4 border-t border-gray-200 bg-white flex items-center">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
className="w-full p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-base"
placeholder="메세지를 입력하세요."
/>
<button
onClick={sendMessage}
className="ml-4 px-5 py-2 bg-blue-500 text-white rounded-lg float-right focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 hover:bg-blue-600"
>
Send
</button>
</div>
</div>
);
}

const ChatMessage = ({ msg, isCurrentUser }) => {
// 현재 사용자가 보낸 메시지인지에 따라 다른 배경색 적용
const messageStyle = isCurrentUser
? "break-words p-2 rounded-lg bg-blue-100 border border-blue-200 max-w-xs ml-auto text-sm"
: "break-words p-2 rounded-lg bg-white border border-gray-200 max-w-xs mr-auto text-sm";

// timestamp를 Date 객체로 변환
const date = new Date(msg.timestamp);
// HH:mm 형식으로 시간 변환
// const formattedTime = date.toLocaleTimeString('ko-KR', {
// hour: '2-digit',
// minute: '2-digit',
// hour12: false // 24시간 형식
// });
// 메시지 시간을 'MM.DD HH:mm' 형식으로 형식화
const formattedTime = format(date, 'MM/dd HH:mm');

return (
<div className={messageStyle}>
<div className="text-xs text-gray-500">{formattedTime}</div>
{msg.content}
</div>
);
};
25 changes: 25 additions & 0 deletions src/components/hotel/HotelDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { formatPrice } from '@/constants/hotel'
import { useUser } from '@/hooks/useUser'
import LikeButton from '@/app/hotel/like/LikeButton'
import ReviewList from '@/components/review/ReviewList'
import axios from "@/config/axios-config";

export default function HotelDetail({ id }) {
const router = useRouter()
Expand Down Expand Up @@ -50,6 +51,24 @@ export default function HotelDetail({ id }) {
if (isLoading) return <div></div>
if (isHotelLoading) return <div></div>

const handleChattingButton = async () => {
try {
const response = await axios.post(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/v1/chat/create`,
{ hotelId: id },
{
...axios.defaults,
useAuth: true,
}
);

const chatRoomId = response.data.objData.chatRoomId; // 응답에서 채팅방 ID 추출
router.push(`/chat/${chatRoomId}`); // 채팅방 페이지로 라우팅
} catch (error) {
console.error('채팅방 생성 실패', error);
}
};

return (
<div className='w-full mx-auto p-10'>
<div className='flex justify-between'>
Expand Down Expand Up @@ -204,6 +223,12 @@ export default function HotelDetail({ id }) {
</button>
</div>
</div>
<button
onClick={handleChattingButton}
className="justify-end underline"
>
호스트에게 문의하기
</button>
</div>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions src/components/mypage/LeftBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default function LeftBar(props) {
{ text: "내가 등록한 숙소", link: "/mypage/hotels" },
{ text: "내가 찜한 숙소", link: "/mypage/like" },
{ text: "나의 리뷰", link: "/mypage/reviews" },
{ text: "나의 문의내역", link: "/mypage/chats" },
];

return (
Expand Down
9 changes: 9 additions & 0 deletions src/components/mypage/MyChatRooms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use client'

export default function MyChatRooms() {
return (
<div>
chat rooms
</div>
)
}
56 changes: 56 additions & 0 deletions src/hooks/useChat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useQuery } from "@tanstack/react-query";
import axios from "@/config/axios-config";

/** 채팅방 정보 */
const fetchChatRoomInfo = async (roomId) => {
const res = await axios.get(`api/v1/chat/info/${roomId}`,
{
...axios.defaults,
useAuth: true
}
);

return res.data;
};

export const useChatRoomInfo = (roomId) => {
const {
data: chatRoom,
isChatLoading,
isChatFetching,
isChatError,
chatError,
} = useQuery({
queryKey: ["chatRoomInfo", roomId],
queryFn: () => fetchChatRoomInfo(roomId),
});

return { chatRoom, isChatLoading, isChatFetching, isChatError, chatError };
};

/** 채팅 메세지 리스트 */
const fetchChatMessageList = async (roomId) => {
const res = await axios.get(`api/v1/chat/messages/${roomId}`,
{
...axios.defaults,
useAuth: true
}
);

return res.data;
};

export const useChatMessageList = (roomId) => {
const {
data: chatMessages,
isMsgLoading,
isMsgFetching,
isMsgError,
msgError,
} = useQuery({
queryKey: ["chatMessageList", roomId],
queryFn: () => fetchChatMessageList(roomId),
});

return { chatMessages, isMsgLoading, isMsgFetching, isMsgError, msgError };
};

0 comments on commit 5f87a83

Please sign in to comment.