feat(03-06): wire EventDetailPopover Edit/Delete footer and implement DeleteConfirmationDialog

- EventDetailPopover: replace aria-hidden placeholder with Edit2/Trash2 footer buttons
  - Edit opens EventForm in edit mode and closes popover
  - Delete opens DeleteConfirmationDialog via setDeleteDialog (T-03-17 two-tap)
- DeleteConfirmationDialog: centered modal, max-width 320px, backdrop + focus trap
  - heading 'Delete event?', Fastmail body copy per UI-SPEC
  - Cancel/Escape close without deleting; Delete fires mutation
  - On success: setLastSyncedUid (feeds SyncStateToast), close dialog + popover
  - TanStack mutation; 48px Delete button (--color-destructive)
- CalendarShell: mount DeleteConfirmationDialog in both phone and tablet/desktop layouts
This commit is contained in:
Lucas Berger
2026-06-05 18:46:24 -04:00
parent 2fbeffee9a
commit 40322e11bf
3 changed files with 284 additions and 5 deletions
+69 -5
View File
@@ -24,7 +24,7 @@
import { useEffect, useRef } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import { MapPin } from 'lucide-react'
import { MapPin, Edit2, Trash2 } from 'lucide-react'
import { useCalendarStore } from '../store/calendarStore.js'
import type { CalendarOccurrence } from '../api/client.js'
@@ -107,6 +107,8 @@ function formatDateTime(start: string, end: string, allDay: boolean): string {
*/
export function EventDetailPopover(props: ScheduleXEventModalProps = {}) {
const { openEventId, setOpenEventId } = useCalendarStore()
const setEventForm = useCalendarStore((s) => s.setEventForm)
const setDeleteDialog = useCalendarStore((s) => s.setDeleteDialog)
const queryClient = useQueryClient()
const dialogRef = useRef<HTMLDivElement>(null)
@@ -377,14 +379,76 @@ export function EventDetailPopover(props: ScheduleXEventModalProps = {}) {
: (occurrence.ownerName ?? occurrence.calendarName)}
</div>
{/* Phase 3 footer action area — Phase 3 adds edit/delete actions here (D-08) */}
{/* Phase 3 footer: Edit / Delete actions (D-10) */}
<div
aria-hidden="true"
style={{
// Reserved: empty in Phase 2 (read-only); Phase 3 wires edit/delete buttons here
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 'var(--space-4)',
paddingTop: 'var(--space-3)',
borderTop: '1px solid var(--color-border-subtle)',
}}
/>
>
{/* Edit button — ghost style, left-aligned */}
<button
aria-label="Edit event"
onClick={() => {
setEventForm(true, 'edit', occurrence.uid)
setOpenEventId(null)
}}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
minWidth: '44px',
minHeight: '44px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 'var(--space-1)',
fontSize: 'var(--text-label-size)',
fontWeight: 'var(--text-label-weight)',
color: 'var(--color-text-primary)',
borderRadius: 'var(--space-1)',
padding: '0 var(--space-2)',
fontFamily: 'var(--font-family-base)',
}}
>
<Edit2 size={16} aria-hidden="true" />
{/* Plain text — XSS guard */}
Edit
</button>
{/* Delete button — ghost style, right-aligned, destructive color */}
<button
aria-label="Delete event"
onClick={() => {
setDeleteDialog(true, occurrence.uid)
}}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
minWidth: '44px',
minHeight: '44px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 'var(--space-1)',
fontSize: 'var(--text-label-size)',
fontWeight: 'var(--text-label-weight)',
color: 'var(--color-destructive)',
borderRadius: 'var(--space-1)',
padding: '0 var(--space-2)',
fontFamily: 'var(--font-family-base)',
}}
>
<Trash2 size={16} aria-hidden="true" />
{/* Plain text — XSS guard */}
Delete
</button>
</div>
</div>
</>
)