Add admin issue deletion workflow
This commit is contained in:
@@ -2761,6 +2761,46 @@ button:disabled,
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.issue-modal-toolbar > .issue-modal-toolbar-actions {
|
||||
display: flex;
|
||||
grid-auto-flow: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.issue-delete-confirmation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(255, 86, 113, 0.42);
|
||||
border-radius: var(--ops-radius-lg);
|
||||
background: linear-gradient(120deg, rgba(255, 86, 113, 0.11), rgba(255, 86, 113, 0.035));
|
||||
}
|
||||
|
||||
.issue-delete-confirmation > div:first-child {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.issue-delete-confirmation > div:last-child {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.issue-delete-confirmation h3,
|
||||
.issue-delete-confirmation p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.issue-delete-confirmation p {
|
||||
max-width: 720px;
|
||||
color: var(--ops-muted);
|
||||
font-size: 0.74rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.issue-pipeline-card {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
@@ -3785,6 +3825,10 @@ button:disabled,
|
||||
padding-right: 13px;
|
||||
padding-left: 13px;
|
||||
}
|
||||
.issue-modal-toolbar > .issue-modal-toolbar-actions { display: flex; }
|
||||
.issue-delete-confirmation { align-items: stretch; flex-direction: column; }
|
||||
.issue-delete-confirmation > div:last-child { display: grid; }
|
||||
.issue-delete-confirmation button { width: 100%; }
|
||||
.issue-pipeline-card > header { flex-direction: column; }
|
||||
.issue-pipeline-card > ol { grid-template-columns: 1fr; gap: 8px; }
|
||||
.issue-pipeline-card li { grid-template-columns: 25px minmax(0, 1fr); align-items: center; justify-items: start; text-align: left; }
|
||||
|
||||
@@ -8,6 +8,7 @@ type PortalPermissions = {
|
||||
can_edit?: boolean
|
||||
can_comment?: boolean
|
||||
can_moderate?: boolean
|
||||
can_delete?: boolean
|
||||
can_confirm_resolution?: boolean
|
||||
}
|
||||
|
||||
@@ -433,6 +434,8 @@ export default function PortalClient({ workspace }: PortalClientProps) {
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [commenting, setCommenting] = useState(false)
|
||||
const [respondingResolution, setRespondingResolution] = useState(false)
|
||||
const [deleteConfirming, setDeleteConfirming] = useState(false)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [status, setStatus] = useState<string | null>(null)
|
||||
const [totalItems, setTotalItems] = useState(0)
|
||||
@@ -1382,14 +1385,49 @@ export default function PortalClient({ workspace }: PortalClientProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const deleteIssue = async () => {
|
||||
if (selectedItem?.kind !== 'issue' || !selectedItem.permissions?.can_delete) return
|
||||
setDeleting(true)
|
||||
setError(null)
|
||||
setStatus(null)
|
||||
const issueId = selectedItem.id
|
||||
try {
|
||||
const response = await authFetch(`${getApiBase()}/portal/items/${issueId}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
clearToken()
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
const payload = await response.json().catch(() => null)
|
||||
throw new Error(payload?.detail || `Could not delete issue (${response.status})`)
|
||||
}
|
||||
closeIssueModal()
|
||||
setStatus(`Issue #${issueId} was deleted. The linked media request was not changed.`)
|
||||
await Promise.all([loadItems(), loadOverview()])
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
setError(err instanceof Error ? err.message : 'Could not delete the issue.')
|
||||
} finally {
|
||||
setDeleting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const closeIssueModal = () => {
|
||||
setSelectedItemId(null)
|
||||
setSelectedItem(null)
|
||||
setComments([])
|
||||
setActivity([])
|
||||
setCommentText('')
|
||||
setDeleteConfirming(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setDeleteConfirming(false)
|
||||
}, [selectedItemId])
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace !== 'issue' || selectedItemId == null) return
|
||||
const previousOverflow = document.body.style.overflow
|
||||
@@ -2098,9 +2136,21 @@ export default function PortalClient({ workspace }: PortalClientProps) {
|
||||
{selectedItem ? `Issue #${selectedItem.id}` : 'Issue details'}
|
||||
</strong>
|
||||
</div>
|
||||
<button type="button" className="ghost-button" onClick={closeIssueModal}>
|
||||
Close
|
||||
</button>
|
||||
<div className="issue-modal-toolbar-actions">
|
||||
{selectedItem?.permissions?.can_delete ? (
|
||||
<button
|
||||
type="button"
|
||||
className="danger-button"
|
||||
disabled={deleting}
|
||||
onClick={() => setDeleteConfirming(true)}
|
||||
>
|
||||
Delete issue
|
||||
</button>
|
||||
) : null}
|
||||
<button type="button" className="ghost-button" disabled={deleting} onClick={closeIssueModal}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{!selectedItemId ? (
|
||||
@@ -2145,6 +2195,31 @@ export default function PortalClient({ workspace }: PortalClientProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedItem.kind === 'issue' && deleteConfirming ? (
|
||||
<section className="issue-delete-confirmation" aria-live="polite">
|
||||
<div>
|
||||
<span className="section-kicker">Permanent deletion</span>
|
||||
<h3>Delete issue #{selectedItem.id}?</h3>
|
||||
<p>
|
||||
This removes the issue, its comments, and its activity history. The linked media request and collected content will not be changed.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost-button"
|
||||
disabled={deleting}
|
||||
onClick={() => setDeleteConfirming(false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" className="danger-button" disabled={deleting} onClick={() => void deleteIssue()}>
|
||||
{deleting ? 'Deleting…' : 'Delete permanently'}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{selectedItem.kind === 'issue' ? <IssuePipeline item={selectedItem} /> : null}
|
||||
|
||||
{selectedItem.kind === 'issue' && selectedItem.external_ref?.startsWith('/requests/') ? (
|
||||
|
||||
Reference in New Issue
Block a user