AlertModal.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. "use client";
  2. import { useEffect } from "react";
  3. interface AlertModalProps {
  4. isOpen: boolean;
  5. onClose: () => void;
  6. title?: string;
  7. message: string;
  8. type?: "info" | "success" | "warning" | "error";
  9. confirmText?: string;
  10. showCancel?: boolean;
  11. cancelText?: string;
  12. onConfirm?: () => void;
  13. onCancel?: () => void;
  14. }
  15. export default function AlertModal({
  16. isOpen,
  17. onClose,
  18. title,
  19. message,
  20. type = "info",
  21. confirmText = "확인",
  22. showCancel = false,
  23. cancelText = "취소",
  24. onConfirm,
  25. onCancel,
  26. }: AlertModalProps) {
  27. useEffect(() => {
  28. const handleEscape = (event: KeyboardEvent) => {
  29. if (event.key === "Escape") {
  30. onClose();
  31. }
  32. };
  33. if (isOpen) {
  34. document.addEventListener("keydown", handleEscape);
  35. document.body.style.overflow = "hidden";
  36. }
  37. return () => {
  38. document.removeEventListener("keydown", handleEscape);
  39. document.body.style.overflow = "unset";
  40. };
  41. }, [isOpen, onClose]);
  42. const handleConfirm = () => {
  43. if (onConfirm) {
  44. onConfirm();
  45. } else {
  46. onClose();
  47. }
  48. };
  49. const handleCancel = () => {
  50. if (onCancel) {
  51. onCancel();
  52. } else {
  53. onClose();
  54. }
  55. };
  56. const getIcon = () => {
  57. switch (type) {
  58. case "success":
  59. return (
  60. <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
  61. <svg className="h-6 w-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  62. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
  63. </svg>
  64. </div>
  65. );
  66. case "warning":
  67. return (
  68. <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100">
  69. <svg className="h-6 w-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  70. <path
  71. strokeLinecap="round"
  72. strokeLinejoin="round"
  73. strokeWidth="2"
  74. d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
  75. />
  76. </svg>
  77. </div>
  78. );
  79. case "error":
  80. return (
  81. <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
  82. <svg className="h-6 w-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  83. <path
  84. strokeLinecap="round"
  85. strokeLinejoin="round"
  86. strokeWidth="2"
  87. d="M6 18L18 6M6 6l12 12"
  88. />
  89. </svg>
  90. </div>
  91. );
  92. default:
  93. return (
  94. <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-100">
  95. <svg className="h-6 w-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  96. <path
  97. strokeLinecap="round"
  98. strokeLinejoin="round"
  99. strokeWidth="2"
  100. d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
  101. />
  102. </svg>
  103. </div>
  104. );
  105. }
  106. };
  107. const getButtonColor = () => {
  108. switch (type) {
  109. case "success":
  110. return "bg-green-600 hover:bg-green-700 focus:ring-green-500";
  111. case "warning":
  112. return "bg-yellow-600 hover:bg-yellow-700 focus:ring-yellow-500";
  113. case "error":
  114. return "bg-red-600 hover:bg-red-700 focus:ring-red-500";
  115. default:
  116. return "bg-blue-600 hover:bg-blue-700 focus:ring-blue-500";
  117. }
  118. };
  119. if (!isOpen) return null;
  120. return (
  121. <div
  122. className="fixed inset-0 z-50 overflow-y-auto"
  123. aria-labelledby="modal-title"
  124. role="dialog"
  125. aria-modal="true"
  126. >
  127. <div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
  128. <div className="bg-opacity-75 fixed inset-0 bg-gray-500 transition-opacity" onClick={onClose}></div>
  129. <span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true">
  130. &#8203;
  131. </span>
  132. <div className="relative inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle">
  133. <div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
  134. <div className="sm:flex sm:items-start">
  135. <div className="mt-3 w-full text-center sm:mt-0 sm:text-left">
  136. {getIcon()}
  137. {title && (
  138. <h3
  139. className="mb-2 text-center text-lg leading-6 font-medium text-gray-900"
  140. id="modal-title"
  141. >
  142. {title}
  143. </h3>
  144. )}
  145. <div className="mt-2">
  146. <p className="text-center text-sm whitespace-pre-line text-gray-500">{message}</p>
  147. </div>
  148. </div>
  149. </div>
  150. </div>
  151. <div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
  152. <button
  153. type="button"
  154. className={`inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-white shadow-sm focus:ring-2 focus:ring-offset-2 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm ${getButtonColor()}`}
  155. onClick={handleConfirm}
  156. >
  157. {confirmText}
  158. </button>
  159. {showCancel && (
  160. <button
  161. type="button"
  162. className="mt-3 inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
  163. onClick={handleCancel}
  164. >
  165. {cancelText}
  166. </button>
  167. )}
  168. </div>
  169. </div>
  170. </div>
  171. </div>
  172. );
  173. }