ChatWindow.tsx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. 'use client';
  2. import { useState, useEffect, useRef } from 'react';
  3. import type { BroadcastInfo } from '@/types/broadcast';
  4. import DonationModal from './DonationModal';
  5. interface ChatMessage {
  6. id: string;
  7. username: string;
  8. message: string;
  9. timestamp: Date;
  10. isStreamer?: boolean;
  11. isDonation?: boolean;
  12. donationAmount?: number;
  13. }
  14. interface ChatWindowProps {
  15. broadcast: BroadcastInfo;
  16. }
  17. export default function ChatWindow({ broadcast }: ChatWindowProps) {
  18. const [messages, setMessages] = useState<ChatMessage[]>([]);
  19. const [inputMessage, setInputMessage] = useState('');
  20. const [isConnected, setIsConnected] = useState(true);
  21. const [isDonationModalOpen, setIsDonationModalOpen] = useState(false);
  22. const chatEndRef = useRef<HTMLDivElement>(null);
  23. // 채팅 메시지 더미 데이터
  24. const dummyMessages: ChatMessage[] = [
  25. {
  26. id: '1',
  27. username: broadcast.channel,
  28. message: '안녕하세요! 방송 시작합니다 🎉',
  29. timestamp: new Date(Date.now() - 300000),
  30. isStreamer: true
  31. },
  32. {
  33. id: '2',
  34. username: '시청자123',
  35. message: '안녕하세요~!',
  36. timestamp: new Date(Date.now() - 250000)
  37. },
  38. {
  39. id: '3',
  40. username: '라이브러버',
  41. message: '오늘도 재밌는 방송 부탁드려요!',
  42. timestamp: new Date(Date.now() - 200000)
  43. },
  44. {
  45. id: '4',
  46. username: '후원왕',
  47. message: '응원합니다! 화이팅!',
  48. timestamp: new Date(Date.now() - 150000),
  49. isDonation: true,
  50. donationAmount: 5000
  51. },
  52. {
  53. id: '5',
  54. username: '열심팬',
  55. message: 'ㅋㅋㅋㅋㅋ 재밌네요',
  56. timestamp: new Date(Date.now() - 100000)
  57. }
  58. ];
  59. // 실시간 채팅 시뮬레이션
  60. useEffect(() => {
  61. setMessages(dummyMessages);
  62. const interval = setInterval(() => {
  63. const randomMessages = [
  64. '와 대박',
  65. 'ㅋㅋㅋㅋㅋ',
  66. '재밌어요!',
  67. '최고!',
  68. '👏👏👏',
  69. '하이~',
  70. '안녕하세요',
  71. '오늘 방송 재밌네요',
  72. '팔로우 했어요!',
  73. '응원합니다'
  74. ];
  75. const randomUsernames = [
  76. '시청자' + Math.floor(Math.random() * 999),
  77. '라이브팬' + Math.floor(Math.random() * 999),
  78. '스트림러버' + Math.floor(Math.random() * 999),
  79. '채팅왕' + Math.floor(Math.random() * 999)
  80. ];
  81. const newMessage: ChatMessage = {
  82. id: Date.now().toString(),
  83. username: randomUsernames[Math.floor(Math.random() * randomUsernames.length)],
  84. message: randomMessages[Math.floor(Math.random() * randomMessages.length)],
  85. timestamp: new Date(),
  86. isDonation: Math.random() > 0.9,
  87. donationAmount: Math.random() > 0.9 ? (Math.floor(Math.random() * 10) + 1) * 1000 : undefined
  88. };
  89. setMessages(prev => [...prev.slice(-20), newMessage]);
  90. }, 3000 + Math.random() * 5000);
  91. return () => clearInterval(interval);
  92. }, []);
  93. // 자동 스크롤
  94. useEffect(() => {
  95. chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  96. }, [messages]);
  97. const handleSendMessage = () => {
  98. if (!inputMessage.trim()) return;
  99. const newMessage: ChatMessage = {
  100. id: Date.now().toString(),
  101. username: '나',
  102. message: inputMessage,
  103. timestamp: new Date()
  104. };
  105. setMessages(prev => [...prev, newMessage]);
  106. setInputMessage('');
  107. };
  108. const handleKeyPress = (e: React.KeyboardEvent) => {
  109. if (e.key === 'Enter' && !e.shiftKey) {
  110. e.preventDefault();
  111. handleSendMessage();
  112. }
  113. };
  114. const handleDonationComplete = (donationData: {
  115. amount: number;
  116. message: string;
  117. isAnonymous: boolean;
  118. username: string;
  119. }) => {
  120. const donationMessage: ChatMessage = {
  121. id: Date.now().toString(),
  122. username: donationData.username,
  123. message: donationData.message,
  124. timestamp: new Date(),
  125. isDonation: true,
  126. donationAmount: donationData.amount
  127. };
  128. setMessages(prev => [...prev, donationMessage]);
  129. };
  130. return (
  131. <div className="h-full flex flex-col bg-white border-l border-gray-200">
  132. {/* 채팅 헤더 */}
  133. <div className="bg-white p-4 border-b border-gray-200">
  134. <div className="flex items-center justify-between">
  135. <h3 className="text-gray-900 font-bold text-sm">채팅</h3>
  136. <div className="flex items-center space-x-2">
  137. <div className={`w-2 h-2 rounded-full ${isConnected ? 'bg-green-400' : 'bg-red-400'}`}></div>
  138. <span className="text-xs text-gray-500">
  139. {isConnected ? '연결됨' : '연결 끊김'}
  140. </span>
  141. </div>
  142. </div>
  143. </div>
  144. {/* 채팅 메시지 영역 */}
  145. <div className="flex-1 overflow-y-auto p-3 space-y-2 bg-gray-50">
  146. {messages.map((message) => (
  147. <div
  148. key={message.id}
  149. className={`${
  150. message.isDonation
  151. ? 'bg-amber-50 border border-amber-200 rounded-lg p-2'
  152. : ''
  153. }`}
  154. >
  155. {/* 후원 메시지 */}
  156. {message.isDonation && (
  157. <div className="flex items-center space-x-2 mb-1">
  158. <span className="text-amber-600 text-xs">💰 후원</span>
  159. <span className="text-amber-700 text-xs font-bold">
  160. {message.donationAmount?.toLocaleString()}원
  161. </span>
  162. </div>
  163. )}
  164. {/* 메시지 내용 */}
  165. <div className="flex flex-col space-y-1">
  166. <div className="flex items-start space-x-1">
  167. <span
  168. className={`text-xs font-bold flex-shrink-0 ${
  169. message.isStreamer
  170. ? 'text-purple-600'
  171. : message.isDonation
  172. ? 'text-amber-700'
  173. : 'text-blue-600'
  174. }`}
  175. >
  176. {message.username}
  177. {message.isStreamer && (
  178. <span className="ml-1 text-xs bg-purple-100 text-purple-600 px-1 rounded">스트리머</span>
  179. )}
  180. </span>
  181. </div>
  182. <p className="text-gray-800 text-sm break-words leading-relaxed">{message.message}</p>
  183. </div>
  184. </div>
  185. ))}
  186. <div ref={chatEndRef} />
  187. </div>
  188. {/* 채팅 입력 영역 */}
  189. <div className="bg-white p-3 border-t border-gray-200">
  190. <div className="flex space-x-2 mb-3">
  191. <input
  192. type="text"
  193. value={inputMessage}
  194. onChange={(e) => setInputMessage(e.target.value)}
  195. onKeyPress={handleKeyPress}
  196. placeholder="채팅을 입력하세요..."
  197. className="flex-1 bg-white border border-gray-300 text-gray-900 px-3 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
  198. maxLength={200}
  199. />
  200. <button
  201. onClick={handleSendMessage}
  202. disabled={!inputMessage.trim()}
  203. className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white px-4 py-2 rounded-lg transition-colors text-sm font-medium"
  204. >
  205. <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
  206. <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
  207. </svg>
  208. </button>
  209. </div>
  210. {/* 후원하기 버튼 */}
  211. <button
  212. onClick={() => setIsDonationModalOpen(true)}
  213. className="w-full bg-black hover:bg-gray-800 text-white font-medium py-3 px-4 rounded-lg transition-colors text-sm"
  214. >
  215. 후원하기
  216. </button>
  217. </div>
  218. {/* 후원 모달 */}
  219. <DonationModal
  220. isOpen={isDonationModalOpen}
  221. onClose={() => setIsDonationModalOpen(false)}
  222. broadcast={broadcast}
  223. onDonationComplete={handleDonationComplete}
  224. />
  225. </div>
  226. );
  227. }