diff --git a/webui/src/common/components/Modal/Modal.jsx b/webui/src/common/components/Modal/Modal.jsx
new file mode 100644
index 0000000..6e1e62c
--- /dev/null
+++ b/webui/src/common/components/Modal/Modal.jsx
@@ -0,0 +1,65 @@
+import React, { useEffect } from 'react';
+import { XIcon } from '@phosphor-icons/react';
+import Button from '@/common/components/Button';
+import './styles.sass';
+
+export const Modal = ({
+ isOpen,
+ onClose,
+ title,
+ children,
+ size = 'md',
+ closeOnOverlayClick = true,
+ showCloseButton = true,
+ className = ''
+}) => {
+ useEffect(() => {
+ const handleEsc = (event) => {
+ if (event.keyCode === 27) {
+ onClose();
+ }
+ };
+
+ if (isOpen) {
+ document.addEventListener('keydown', handleEsc, false);
+ document.body.style.overflow = 'hidden';
+ }
+
+ return () => {
+ document.removeEventListener('keydown', handleEsc, false);
+ document.body.style.overflow = 'unset';
+ };
+ }, [isOpen, onClose]);
+
+ if (!isOpen) return null;
+
+ const handleOverlayClick = (e) => {
+ if (closeOnOverlayClick && e.target === e.currentTarget) {
+ onClose();
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}>
+ {(title || showCloseButton) && (
+
+ {title &&
{title}
}
+ {showCloseButton && (
+ }
+ onClick={onClose}
+ className="modal-close"
+ />
+ )}
+
+ )}
+
+ {children}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/webui/src/common/components/Modal/index.js b/webui/src/common/components/Modal/index.js
new file mode 100644
index 0000000..8fb160b
--- /dev/null
+++ b/webui/src/common/components/Modal/index.js
@@ -0,0 +1 @@
+export { Modal as default } from './Modal.jsx';
\ No newline at end of file
diff --git a/webui/src/common/components/Modal/styles.sass b/webui/src/common/components/Modal/styles.sass
new file mode 100644
index 0000000..48787a3
--- /dev/null
+++ b/webui/src/common/components/Modal/styles.sass
@@ -0,0 +1,80 @@
+.modal-overlay
+ position: fixed
+ top: 0
+ left: 0
+ right: 0
+ bottom: 0
+ background: rgba(0, 0, 0, 0.5)
+ display: flex
+ align-items: center
+ justify-content: center
+ z-index: 1000
+ padding: 2rem
+ backdrop-filter: blur(4px)
+
+.modal
+ background: var(--bg-alt)
+ border-radius: var(--radius-lg)
+ border: 1px solid var(--border)
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2)
+ width: 100%
+ max-height: 90vh
+ overflow: hidden
+ display: flex
+ flex-direction: column
+ animation: modalIn 0.2s ease-out
+
+ &--sm
+ max-width: 400px
+
+ &--md
+ max-width: 500px
+
+ &--lg
+ max-width: 700px
+
+ &--xl
+ max-width: 900px
+
+.modal-header
+ display: flex
+ align-items: center
+ justify-content: space-between
+ padding: 1.5rem 1.5rem 0
+ flex-shrink: 0
+
+.modal-title
+ font-size: 1.3rem
+ font-weight: 600
+ color: var(--text)
+ margin: 0
+
+.modal-close
+ margin-left: auto
+
+.modal-content
+ padding: 1.5rem
+ overflow-y: auto
+ flex: 1
+
+@keyframes modalIn
+ from
+ opacity: 0
+ transform: scale(0.95) translateY(-10px)
+ to
+ opacity: 1
+ transform: scale(1) translateY(0)
+
+@media (max-width: 768px)
+ .modal-overlay
+ padding: 1rem
+
+ .modal
+ max-width: none
+ margin: 0
+
+ .modal-header
+ padding: 1rem 1rem 0
+
+ .modal-content
+ padding: 1rem
\ No newline at end of file