Add confirmation modal for project deletion
All checks were successful
ci/woodpecker/push/build Pipeline was successful
All checks were successful
ci/woodpecker/push/build Pipeline was successful
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,60 @@ h2 {
|
|||||||
background: #e74c3c;
|
background: #e74c3c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
min-width: 300px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal p {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions button {
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions button:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions button.danger {
|
||||||
|
background: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions button.danger:hover {
|
||||||
|
background: #e74c3c;
|
||||||
|
}
|
||||||
|
|
||||||
.vpn-block {
|
.vpn-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -11,6 +11,28 @@ import {
|
|||||||
} from "./api";
|
} from "./api";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
|
function ConfirmModal({
|
||||||
|
message,
|
||||||
|
onConfirm,
|
||||||
|
onCancel,
|
||||||
|
}: {
|
||||||
|
message: string;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="modal-overlay" onClick={onCancel}>
|
||||||
|
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<p>{message}</p>
|
||||||
|
<div className="modal-actions">
|
||||||
|
<button onClick={onCancel}>Отмена</button>
|
||||||
|
<button className="danger" onClick={onConfirm}>Удалить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ProjectList({
|
function ProjectList({
|
||||||
onSelect,
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
@@ -18,6 +40,7 @@ function ProjectList({
|
|||||||
}) {
|
}) {
|
||||||
const [projects, setProjects] = useState<ProjectMeta[]>([]);
|
const [projects, setProjects] = useState<ProjectMeta[]>([]);
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
|
const [deleteId, setDeleteId] = useState<number | null>(null);
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
setProjects(await fetchProjects());
|
setProjects(await fetchProjects());
|
||||||
@@ -56,10 +79,9 @@ function ProjectList({
|
|||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
className="project-delete"
|
className="project-delete"
|
||||||
onClick={async (e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
await deleteProject(p.id);
|
setDeleteId(p.id);
|
||||||
await load();
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
X
|
X
|
||||||
@@ -67,6 +89,17 @@ function ProjectList({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{deleteId !== null && (
|
||||||
|
<ConfirmModal
|
||||||
|
message="Удалить проект?"
|
||||||
|
onCancel={() => setDeleteId(null)}
|
||||||
|
onConfirm={async () => {
|
||||||
|
await deleteProject(deleteId);
|
||||||
|
setDeleteId(null);
|
||||||
|
await load();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -84,6 +117,7 @@ function ProjectPage({
|
|||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState("");
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -184,8 +218,15 @@ function ProjectPage({
|
|||||||
{project.file_name && (
|
{project.file_name && (
|
||||||
<button onClick={handleDownload}>Download file</button>
|
<button onClick={handleDownload}>Download file</button>
|
||||||
)}
|
)}
|
||||||
<button className="danger" onClick={handleDelete}>Delete</button>
|
<button className="danger" onClick={() => setShowDeleteModal(true)}>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
{showDeleteModal && (
|
||||||
|
<ConfirmModal
|
||||||
|
message="Удалить проект?"
|
||||||
|
onCancel={() => setShowDeleteModal(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user