DonationModal.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. 'use client';
  2. import { useState } from 'react';
  3. import type { BroadcastInfo } from '@/types/broadcast';
  4. interface DonationModalProps {
  5. isOpen: boolean;
  6. onClose: () => void;
  7. broadcast: BroadcastInfo;
  8. onDonationComplete?: (donationData: {
  9. amount: number;
  10. message: string;
  11. isAnonymous: boolean;
  12. username: string;
  13. }) => void;
  14. }
  15. export default function DonationModal({ isOpen, onClose, broadcast, onDonationComplete }: DonationModalProps) {
  16. const [selectedAmount, setSelectedAmount] = useState<number | null>(null);
  17. const [customAmount, setCustomAmount] = useState('');
  18. const [message, setMessage] = useState('');
  19. const [isAnonymous, setIsAnonymous] = useState(false);
  20. const presetAmounts = [1000, 3000, 5000, 10000, 20000, 50000];
  21. if (!isOpen) return null;
  22. const handleDonation = () => {
  23. const amount = selectedAmount || parseInt(customAmount);
  24. // 금액 유효성 검사 강화
  25. if (!amount || isNaN(amount) || amount < 1000) {
  26. alert('최소 후원 금액은 1,000원입니다.');
  27. return;
  28. }
  29. // 1,000원 단위로 반올림 (999원 입력 시 1,000원으로 조정)
  30. const roundedAmount = Math.max(1000, Math.round(amount / 1000) * 1000);
  31. if (roundedAmount !== amount) {
  32. alert(`입력하신 금액이 ${roundedAmount.toLocaleString()}원으로 조정되었습니다.`);
  33. }
  34. // 후원 처리 로직 (실제로는 결제 API 연동)
  35. const donationData = {
  36. broadcastId: broadcast.id,
  37. channel: broadcast.channel,
  38. amount: roundedAmount,
  39. message: message.trim(),
  40. isAnonymous,
  41. timestamp: new Date().toISOString()
  42. };
  43. console.log('후원 정보:', donationData);
  44. // 채팅창에 후원 메시지 추가
  45. if (onDonationComplete) {
  46. onDonationComplete({
  47. amount: roundedAmount,
  48. message: message.trim() || '후원해주셔서 감사합니다!',
  49. isAnonymous,
  50. username: isAnonymous ? '익명' : '나'
  51. });
  52. }
  53. alert(`${roundedAmount.toLocaleString()}원 후원이 완료되었습니다! 감사합니다.`);
  54. onClose();
  55. // 폼 초기화
  56. setSelectedAmount(null);
  57. setCustomAmount('');
  58. setMessage('');
  59. setIsAnonymous(false);
  60. };
  61. return (
  62. <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
  63. <div className="bg-gray-800 rounded-2xl max-w-md w-full max-h-[90vh] overflow-y-auto">
  64. {/* 헤더 */}
  65. <div className="bg-gradient-to-r from-yellow-500 to-orange-500 p-6 rounded-t-2xl">
  66. <div className="flex items-center justify-between">
  67. <div>
  68. <h2 className="text-white text-xl font-bold">후원하기</h2>
  69. <p className="text-white/80 text-sm">{broadcast.channel}님을 응원해주세요!</p>
  70. </div>
  71. <button
  72. onClick={onClose}
  73. className="text-white/80 hover:text-white transition-colors"
  74. >
  75. <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  76. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
  77. </svg>
  78. </button>
  79. </div>
  80. </div>
  81. <div className="p-6 space-y-6">
  82. {/* 금액 선택 */}
  83. <div>
  84. <h3 className="text-white font-semibold mb-3">후원 금액 선택</h3>
  85. <div className="grid grid-cols-3 gap-2 mb-4">
  86. {presetAmounts.map((amount) => (
  87. <button
  88. key={amount}
  89. onClick={() => {
  90. setSelectedAmount(amount);
  91. setCustomAmount('');
  92. }}
  93. className={`p-3 rounded-lg border text-center transition-all ${
  94. selectedAmount === amount
  95. ? 'bg-yellow-500 border-yellow-500 text-white'
  96. : 'bg-gray-700 border-gray-600 text-gray-300 hover:border-yellow-500'
  97. }`}
  98. >
  99. {amount.toLocaleString()}원
  100. </button>
  101. ))}
  102. </div>
  103. {/* 직접 입력 */}
  104. <div>
  105. <label className="block text-gray-300 text-sm mb-2">직접 입력 (1,000원 단위)</label>
  106. <input
  107. type="number"
  108. value={customAmount}
  109. onChange={(e) => {
  110. const value = e.target.value;
  111. // 1,000원 단위로만 입력 허용
  112. if (value === '' || (parseInt(value) >= 1000 && parseInt(value) % 1000 === 0)) {
  113. setCustomAmount(value);
  114. setSelectedAmount(null);
  115. }
  116. }}
  117. placeholder="1,000원 이상 (천원 단위)"
  118. min="1000"
  119. step="1000"
  120. className="w-full bg-gray-700 text-white px-3 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-yellow-500"
  121. />
  122. <div className="text-xs text-gray-400 mt-1">
  123. * 1,000원 단위로만 입력 가능합니다
  124. </div>
  125. </div>
  126. </div>
  127. {/* 후원 메시지 */}
  128. <div>
  129. <h3 className="text-white font-semibold mb-3">후원 메시지 (선택)</h3>
  130. <textarea
  131. value={message}
  132. onChange={(e) => setMessage(e.target.value)}
  133. placeholder="스트리머에게 전할 메시지를 입력해주세요..."
  134. maxLength={100}
  135. rows={3}
  136. className="w-full bg-gray-700 text-white px-3 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-yellow-500 resize-none"
  137. />
  138. <div className="text-right text-xs text-gray-400 mt-1">
  139. {message.length}/100
  140. </div>
  141. </div>
  142. {/* 익명 후원 옵션 */}
  143. <div className="flex items-center space-x-3">
  144. <input
  145. type="checkbox"
  146. id="anonymous"
  147. checked={isAnonymous}
  148. onChange={(e) => setIsAnonymous(e.target.checked)}
  149. className="w-4 h-4 text-yellow-500 bg-gray-700 border-gray-600 rounded focus:ring-yellow-500"
  150. />
  151. <label htmlFor="anonymous" className="text-gray-300 text-sm">
  152. 익명으로 후원하기
  153. </label>
  154. </div>
  155. {/* 후원 금액 요약 */}
  156. <div className="bg-gray-700 rounded-lg p-4">
  157. <div className="flex justify-between items-center text-white">
  158. <span>후원 금액:</span>
  159. <span className="text-xl font-bold text-yellow-400">
  160. {(selectedAmount || parseInt(customAmount) || 0).toLocaleString()}원
  161. </span>
  162. </div>
  163. </div>
  164. {/* 후원 버튼 */}
  165. <button
  166. onClick={handleDonation}
  167. disabled={!selectedAmount && !customAmount}
  168. className="w-full bg-gradient-to-r from-yellow-500 to-orange-500 hover:from-yellow-600 hover:to-orange-600 disabled:from-gray-600 disabled:to-gray-600 text-white font-bold py-4 px-6 rounded-lg transition-all duration-200 transform hover:scale-105 disabled:transform-none disabled:cursor-not-allowed flex items-center justify-center space-x-2"
  169. >
  170. <span>후원하기</span>
  171. </button>
  172. {/* 주의사항 */}
  173. <div className="text-xs text-gray-400 text-center space-y-1">
  174. <p>• 후원은 취소가 불가능합니다.</p>
  175. <p>• 최소 후원 금액은 1,000원입니다.</p>
  176. <p>• 후원 메시지는 스트리머와 모든 시청자에게 공개됩니다.</p>
  177. </div>
  178. </div>
  179. </div>
  180. </div>
  181. );
  182. }