125 lines
4.0 KiB
Vue
125 lines
4.0 KiB
Vue
<script setup lang="ts">
|
|||
|
|
import { ref } from 'vue'
|
||
|
|
import { useRoute, useRouter } from 'vue-router'
|
||
|
|
import { useDialog } from '../composables/useDialog'
|
||
|
|
import { ApiError, apiRequest } from '../lib/api'
|
||
|
|
import { useCardsStore } from '../stores/cards'
|
||
|
|
import { useInboxStore } from '../stores/inbox'
|
||
|
|
|
||
|
|
// The "Manage" dropdown + its delete-card confirmation modal, shared by the
|
||
|
|
// card view and its configuration view -- each links to the other, and
|
||
|
|
// either can delete the card. Mirrors ProjectManageMenu.
|
||
|
|
const props = defineProps<{
|
||
|
|
cardId: number
|
||
|
|
cardText: string
|
||
|
|
/** null for an inbox card -- there's no project to return to, so a delete
|
||
|
|
* (or the "Back to project" link on the pages using this menu) goes to
|
||
|
|
* the dashboard instead. */
|
||
|
|
projectId: number | null
|
||
|
|
}>()
|
||
|
|
|
||
|
|
const route = useRoute()
|
||
|
|
const router = useRouter()
|
||
|
|
const cards = useCardsStore()
|
||
|
|
const inbox = useInboxStore()
|
||
|
|
|
||
|
|
const menuOpen = ref(false)
|
||
|
|
const confirmingDelete = ref(false)
|
||
|
|
const deleting = ref(false)
|
||
|
|
const deleteError = ref<string | null>(null)
|
||
|
|
const cancelButton = ref<HTMLButtonElement>()
|
||
|
|
|
||
|
|
function askDelete() {
|
||
|
|
menuOpen.value = false
|
||
|
|
deleteError.value = null
|
||
|
|
confirmingDelete.value = true
|
||
|
|
}
|
||
|
|
|
||
|
|
async function confirmDelete() {
|
||
|
|
deleting.value = true
|
||
|
|
deleteError.value = null
|
||
|
|
try {
|
||
|
|
await apiRequest(`/cards/${props.cardId}`, { method: 'DELETE', auth: true })
|
||
|
|
// Refresh whichever store holds this card -- a project's board, or the
|
||
|
|
// sidebar inbox -- so it no longer shows the card we just deleted.
|
||
|
|
await (props.projectId !== null ? cards.load(props.projectId) : inbox.load())
|
||
|
|
await router.push(
|
||
|
|
props.projectId !== null ? { name: 'project', params: { id: props.projectId } } : { name: 'dashboard' },
|
||
|
|
)
|
||
|
|
} catch (e) {
|
||
|
|
deleteError.value = e instanceof ApiError ? e.message : 'Could not delete the card.'
|
||
|
|
deleting.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The modal takes priority: while it's open, Escape closes it, not the menu
|
||
|
|
// underneath (askDelete() already closes the menu when the modal opens, so
|
||
|
|
// the two are never both open at once).
|
||
|
|
useDialog(confirmingDelete, () => (confirmingDelete.value = false), cancelButton)
|
||
|
|
useDialog(menuOpen, () => (menuOpen.value = false))
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div class="menu">
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
class="menu__toggle"
|
||
|
|
aria-haspopup="true"
|
||
|
|
:aria-expanded="menuOpen"
|
||
|
|
@click="menuOpen = !menuOpen"
|
||
|
|
>
|
||
|
|
Manage ▾
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<template v-if="menuOpen">
|
||
|
|
<div class="menu__backdrop" @click="menuOpen = false" />
|
||
|
|
<ul class="menu__list" role="menu">
|
||
|
|
<li v-if="route.name !== 'card-configure'" role="none">
|
||
|
|
<RouterLink
|
||
|
|
:to="{ name: 'card-configure', params: { id: cardId } }"
|
||
|
|
role="menuitem"
|
||
|
|
class="menu__item"
|
||
|
|
@click="menuOpen = false"
|
||
|
|
>
|
||
|
|
Configure
|
||
|
|
</RouterLink>
|
||
|
|
</li>
|
||
|
|
<li role="none">
|
||
|
|
<button type="button" role="menuitem" class="menu__item menu__item--danger" @click="askDelete">
|
||
|
|
Delete card
|
||
|
|
</button>
|
||
|
|
</li>
|
||
|
|
</ul>
|
||
|
|
</template>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div
|
||
|
|
v-if="confirmingDelete"
|
||
|
|
class="modal"
|
||
|
|
role="dialog"
|
||
|
|
aria-modal="true"
|
||
|
|
aria-labelledby="confirm-delete-title"
|
||
|
|
>
|
||
|
|
<div class="modal__backdrop" @click="confirmingDelete = false" />
|
||
|
|
<div class="modal__dialog">
|
||
|
|
<h2 id="confirm-delete-title">Delete this card?</h2>
|
||
|
|
<p class="muted">“{{ cardText }}” will be permanently deleted.</p>
|
||
|
|
<p v-if="deleteError" class="form-error">{{ deleteError }}</p>
|
||
|
|
<div class="modal__actions">
|
||
|
|
<button
|
||
|
|
ref="cancelButton"
|
||
|
|
type="button"
|
||
|
|
class="btn-secondary"
|
||
|
|
:disabled="deleting"
|
||
|
|
@click="confirmingDelete = false"
|
||
|
|
>
|
||
|
|
Cancel
|
||
|
|
</button>
|
||
|
|
<button type="button" class="btn-danger" :disabled="deleting" @click="confirmDelete">
|
||
|
|
{{ deleting ? 'Deleting…' : 'Delete card' }}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|