Skip to content

Commit bac0cfd

Browse files
authored
Merge pull request #527 from Chris0Jeky/fix/513-516-ux-small-fixes
fix: delete card confirmation + column Enter key submit (#513 #516)
2 parents 599a2e0 + d335b5a commit bac0cfd

File tree

3 files changed

+67
-12
lines changed

3 files changed

+67
-12
lines changed

frontend/taskdeck-web/src/components/board/CardModal.vue

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { onBeforeUnmount, ref, computed, watch } from 'vue'
33
import { useBoardStore } from '../../store/boardStore'
44
import { useSessionStore } from '../../store/sessionStore'
55
import { useEscapeToClose } from '../../composables/useEscapeToClose'
6+
import TdDialog from '../ui/TdDialog.vue'
67
import type { Card, CardCaptureProvenance, Label } from '../../types/board'
78
import type { CardComment } from '../../types/comments'
89
import { normalizeProposalStatus } from '../../utils/automation'
@@ -37,6 +38,11 @@ const captureProvenance = ref<CardCaptureProvenance | null>(null)
3738
const captureProvenanceError = ref<string | null>(null)
3839
const loadingCaptureProvenance = ref(false)
3940
const loadedCaptureProvenanceCardId = ref<string | null>(null)
41+
const showDeleteConfirm = ref(false)
42+
const isDeleting = ref(false)
43+
const deleteConfirmDescription = computed(
44+
() => `Are you sure you want to delete "${props.card.title}"? This action cannot be undone.`
45+
)
4046
4147
const comments = computed<CardComment[]>(() => boardStore.getCardComments(props.card.id))
4248
const topLevelComments = computed(() => comments.value.filter(comment => !comment.parentCommentId))
@@ -158,15 +164,26 @@ async function handleSave() {
158164
}
159165
}
160166
161-
async function handleDelete() {
162-
if (!confirm(`Are you sure you want to delete "${props.card.title}"?`)) return
167+
function handleDeleteClick() {
168+
showDeleteConfirm.value = true
169+
}
170+
171+
function handleDeleteCancel() {
172+
showDeleteConfirm.value = false
173+
}
163174
175+
async function handleDeleteConfirm() {
176+
if (isDeleting.value) return
177+
isDeleting.value = true
164178
try {
165179
await boardStore.deleteCard(props.card.boardId, props.card.id)
180+
showDeleteConfirm.value = false
166181
emit('updated')
167182
emit('close')
168183
} catch (error) {
169184
console.error('Failed to delete card:', error)
185+
} finally {
186+
isDeleting.value = false
170187
}
171188
}
172189
@@ -593,7 +610,7 @@ onBeforeUnmount(() => {
593610
<!-- Actions -->
594611
<div class="mt-6 flex items-center justify-between">
595612
<button
596-
@click="handleDelete"
613+
@click="handleDeleteClick"
597614
type="button"
598615
class="px-4 py-2 text-sm font-medium text-red-600 hover:text-red-700 hover:bg-red-50 border border-red-300 rounded-md transition-colors"
599616
>
@@ -620,4 +637,32 @@ onBeforeUnmount(() => {
620637
</div>
621638
</div>
622639
</div>
640+
641+
<!-- Delete Confirmation Dialog -->
642+
<TdDialog
643+
:open="showDeleteConfirm"
644+
title="Delete Card"
645+
:description="deleteConfirmDescription"
646+
:close-on-backdrop="!isDeleting"
647+
@close="handleDeleteCancel"
648+
>
649+
<template #footer>
650+
<button
651+
type="button"
652+
:disabled="isDeleting"
653+
class="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 border border-gray-300 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
654+
@click="handleDeleteCancel"
655+
>
656+
Cancel
657+
</button>
658+
<button
659+
type="button"
660+
:disabled="isDeleting"
661+
class="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 border border-transparent rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
662+
@click="handleDeleteConfirm"
663+
>
664+
{{ isDeleting ? 'Deleting…' : 'Delete' }}
665+
</button>
666+
</template>
667+
</TdDialog>
623668
</template>

frontend/taskdeck-web/src/tests/components/CardModal.spec.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -290,50 +290,59 @@ describe('CardModal', () => {
290290
expect((saveButton?.element as HTMLButtonElement).disabled).toBe(true)
291291
})
292292

293-
it('should show delete confirmation before deleting', async () => {
294-
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false)
295-
293+
it('should show delete confirmation dialog before deleting', async () => {
296294
const wrapper = mount(CardModal, {
297295
props: {
298296
card,
299297
isOpen: true,
300298
labels,
301299
},
300+
attachTo: document.body,
302301
})
303302

304303
const deleteButton = wrapper
305304
.findAll('button')
306305
.find((btn) => btn.text().includes('Delete Card'))
307306
await deleteButton?.trigger('click')
307+
await wrapper.vm.$nextTick()
308308

309-
expect(confirmSpy).toHaveBeenCalled()
309+
// Dialog should now be open; deleteCard must NOT have been called yet
310310
expect(mockStore.deleteCard).not.toHaveBeenCalled()
311+
// The confirmation dialog should be visible in the DOM
312+
expect(document.querySelector('.td-dialog')).not.toBeNull()
311313

312-
confirmSpy.mockRestore()
314+
wrapper.unmount()
313315
})
314316

315-
it('should call deleteCard when deletion is confirmed', async () => {
316-
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true)
317-
317+
it('should call deleteCard when deletion is confirmed in dialog', async () => {
318318
const wrapper = mount(CardModal, {
319319
props: {
320320
card,
321321
isOpen: true,
322322
labels,
323323
},
324+
attachTo: document.body,
324325
})
325326

327+
// Open the confirmation dialog
326328
const deleteButton = wrapper
327329
.findAll('button')
328330
.find((btn) => btn.text().includes('Delete Card'))
329331
await deleteButton?.trigger('click')
332+
await wrapper.vm.$nextTick()
330333

334+
// Click the "Delete" confirm button inside the dialog
335+
const confirmButton = Array.from(document.querySelectorAll<HTMLButtonElement>('button')).find(
336+
(btn) => btn.textContent?.trim() === 'Delete',
337+
)
338+
confirmButton?.click()
339+
await wrapper.vm.$nextTick()
331340
await wrapper.vm.$nextTick()
332341

333342
expect(mockStore.deleteCard).toHaveBeenCalledWith('board-1', 'card-1')
334343
expect(wrapper.emitted('close')).toBeTruthy()
335344

336-
confirmSpy.mockRestore()
345+
wrapper.unmount()
337346
})
338347

339348
it('should handle label selection', async () => {

frontend/taskdeck-web/src/views/BoardView.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ useKeyboardShortcuts([
317317
placeholder="Column name"
318318
class="td-column-form__input"
319319
autofocus
320+
@keydown.enter.prevent="createColumn"
320321
/>
321322
<button
322323
type="submit"

0 commit comments

Comments
 (0)