Add admin issue deletion workflow
Magent CI/CD / verify (push) Successful in 10m47s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 22s

This commit is contained in:
2026-09-01 14:58:58 +12:00
parent 16876e1cf0
commit 5de14b1cb7
5 changed files with 234 additions and 3 deletions
+78 -3
View File
@@ -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/') ? (