---
task_id: sprint_4_story_1_task_5
story_id: sprint_4_story_1
sprint_id: sprint_4
status: not_started
type: frontend
assignee: Frontend Developer 1
created_date: 2025-11-05
estimated_hours: 3
---
# Task 5: Add Edit and Delete Actions with Dialogs
## Description
Implement Edit and Delete functionality for Stories in the detail page. Reuse existing Story form dialog for editing and create a confirmation dialog for deletion with cascade warning.
## What to Do
1. Add state management for dialog visibility (Edit dialog, Delete dialog)
2. Integrate existing Story form component for Edit action
3. Create Delete confirmation dialog
4. Show cascade warning if Story has Tasks (list affected Tasks)
5. Implement delete mutation with error handling
6. Add success/error toast notifications
7. Handle post-delete navigation (redirect to Epic detail page)
8. Test Edit and Delete flows
## Files to Modify
- `app/(dashboard)/stories/[id]/page.tsx` (modify, ~80 lines added for dialogs)
## Implementation Details
```typescript
// app/(dashboard)/stories/[id]/page.tsx
'use client';
import { use, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useStory, useUpdateStory, useDeleteStory } from '@/lib/hooks/use-stories';
import { useTasks } from '@/lib/hooks/use-tasks';
import { StoryForm } from '@/components/projects/story-form';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import { toast } from 'sonner';
import { Loader2, AlertTriangle } from 'lucide-react';
export default function StoryDetailPage({ params }: StoryDetailPageProps) {
const { id } = use(params);
const router = useRouter();
// Fetch data
const { data: story } = useStory(id);
const { data: tasks = [] } = useTasks(id);
// Dialog state
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
// Mutations
const updateStory = useUpdateStory();
const deleteStory = useDeleteStory();
// Edit Story handler
const handleEditStory = async (data: UpdateStoryDto) => {
try {
await updateStory.mutateAsync({ id: story.id, data });
setIsEditDialogOpen(false);
toast.success('Story updated successfully');
} catch (error) {
toast.error('Failed to update Story');
console.error(error);
}
};
// Delete Story handler
const handleDeleteStory = async () => {
try {
await deleteStory.mutateAsync(story.id);
toast.success('Story deleted successfully');
// Redirect to parent Epic detail page
router.push(`/epics/${story.epicId}`);
} catch (error) {
toast.error('Failed to delete Story');
console.error(error);
setIsDeleteDialogOpen(false);
}
};
return (
{/* ... existing content ... */}
setIsEditDialogOpen(true)}
onDelete={() => setIsDeleteDialogOpen(true)}
/>
{/* ... rest of content ... */}
{/* Edit Story Dialog */}
{/* Delete Story Confirmation Dialog */}
Delete Story?
This will permanently delete {story?.title} and all associated data.
This action cannot be undone.
{/* Cascade warning if Tasks exist */}
{tasks.length > 0 && (
⚠️ This Story has {tasks.length} Task{tasks.length > 1 ? 's' : ''} that will also be deleted:
{tasks.slice(0, 5).map((task) => (
- {task.title}
))}
{tasks.length > 5 && (
- ... and {tasks.length - 5} more
)}
)}
Cancel
{deleteStory.isPending && }
Delete Story
);
}
```
## Acceptance Criteria
- [ ] Edit button opens Story form dialog with current Story data
- [ ] Story form displays in "edit" mode with pre-filled values
- [ ] Editing Story updates data correctly
- [ ] Edit success shows toast notification
- [ ] Edit error shows error toast
- [ ] Dialog closes after successful edit
- [ ] Delete button opens confirmation dialog
- [ ] Confirmation dialog shows Story title
- [ ] If Story has Tasks, shows cascade warning with Task list
- [ ] Delete action removes Story and navigates to Epic detail page
- [ ] Delete success shows toast notification
- [ ] Delete error shows error toast and keeps user on page
- [ ] Dialog buttons disabled during mutation (loading state)
- [ ] ESC key closes dialogs
- [ ] Outside click closes dialogs (configurable)
## Testing
**Manual Testing**:
**Edit Story Flow**:
1. Click Edit button → Verify dialog opens
2. Verify form pre-filled with current Story data
3. Change title → Save → Verify title updates on page
4. Change description → Save → Verify description updates
5. Change priority → Save → Verify priority badge updates
6. Test validation → Submit empty title → Verify error message
7. Click Cancel → Verify dialog closes without saving
8. Press ESC → Verify dialog closes
**Delete Story Flow**:
1. Click Delete button → Verify confirmation dialog opens
2. Verify Story title displayed in confirmation message
3. If Story has Tasks → Verify cascade warning shows with Task list
4. Click Cancel → Verify dialog closes, Story not deleted
5. Click Delete Story → Verify Story deleted
6. Verify redirected to Epic detail page
7. Verify success toast shown
8. Go to Epic page → Verify Story no longer in list
**Error Handling**:
1. Disconnect internet
2. Try to edit Story → Verify error toast shown
3. Try to delete Story → Verify error toast shown
4. Verify user stays on page after error
**E2E Test**:
```typescript
test('user can edit story', async ({ page }) => {
await page.goto('/stories/story-123');
// Open edit dialog
await page.click('[aria-label="Edit Story"]');
await expect(page.getByRole('dialog')).toBeVisible();
// Edit title
const titleInput = page.locator('[name="title"]');
await titleInput.fill('Updated Story Title');
// Save
await page.click('button:has-text("Save")');
// Verify updated
await expect(page.locator('h1')).toContainText('Updated Story Title');
await expect(page.getByText('Story updated successfully')).toBeVisible();
});
test('user can delete story', async ({ page }) => {
await page.goto('/stories/story-123');
// Open delete dialog
await page.click('[aria-label="Delete Story"]');
await expect(page.getByRole('alertdialog')).toBeVisible();
// Confirm delete
await page.click('button:has-text("Delete Story")');
// Verify redirected
await expect(page).toHaveURL(/\/epics\//);
await expect(page.getByText('Story deleted successfully')).toBeVisible();
});
```
## Dependencies
**Prerequisites**:
- Task 1, 2, 3, 4 (page, header, sidebar, data loading must exist)
- ✅ StoryForm component (already exists)
- ✅ `useUpdateStory()` hook (already exists)
- ✅ `useDeleteStory()` hook (already exists)
- ✅ `useTasks(storyId)` hook (for cascade warning)
- ✅ shadcn/ui Dialog, AlertDialog components
- ✅ sonner for toast notifications
**Blocks**:
- None (final task for Story 1)
## Estimated Time
3 hours
## Notes
**Cascade Warning**: Show list of Tasks that will be deleted when Story is deleted. This helps users understand the impact of deletion and prevents accidental data loss.
**Navigation After Delete**: Always redirect to the parent Epic detail page after successful deletion. This prevents users from staying on a deleted Story page.
**Form Reuse**: The StoryForm component already exists and handles both "create" and "edit" modes. No changes needed to the form itself.
**Error Handling**: Use optimistic UI for updates - update immediately, revert on error. For deletes, wait for confirmation before navigating.