Files
ColaFlow/docs/plans/sprint_4_story_1_task_5.md
Yaojia Wang 88d6413f81 feat(frontend): Create Sprint 4 Stories and Tasks for Story Management
Created comprehensive Story and Task files for Sprint 4 frontend implementation:

Story 1: Story Detail Page Foundation (P0 Critical - 3 days)
- 6 tasks: route creation, header, sidebar, data loading, Edit/Delete, responsive design
- Fixes critical 404 error when clicking Story cards
- Two-column layout consistent with Epic detail page

Story 2: Task Management in Story Detail (P0 Critical - 2 days)
- 6 tasks: API verification, hooks, TaskList, TaskCard, TaskForm, integration
- Complete Task CRUD with checkbox status toggle
- Filters, sorting, and optimistic UI updates

Story 3: Enhanced Story Form (P1 High - 2 days)
- 6 tasks: acceptance criteria, assignee selector, tags, story points, integration
- Aligns with UX design specification
- Backward compatible with existing Stories

Story 4: Quick Add Story Workflow (P1 High - 2 days)
- 5 tasks: inline form, keyboard shortcuts, batch creation, navigation
- Rapid Story creation with minimal fields
- Keyboard shortcut (Cmd/Ctrl + N)

Story 5: Story Card Component (P2 Medium - 1 day)
- 4 tasks: component variants, visual states, Task count, optimization
- Reusable component with list/kanban/compact variants
- React.memo optimization

Story 6: Kanban Story Creation Enhancement (P2 Optional - 2 days)
- 4 tasks: Epic card enhancement, inline form, animation, real-time updates
- Contextual Story creation from Kanban
- Stretch goal - implement only if ahead of schedule

Total: 6 Stories, 31 Tasks, 12 days estimated
Priority breakdown: P0 (2), P1 (2), P2 (2 optional)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 21:49:57 +01:00

10 KiB

task_id, story_id, sprint_id, status, type, assignee, created_date, estimated_hours
task_id story_id sprint_id status type assignee created_date estimated_hours
sprint_4_story_1_task_5 sprint_4_story_1 sprint_4 not_started frontend Frontend Developer 1 2025-11-05 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

// 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 (
    <div className="container mx-auto px-4 py-6 max-w-7xl">
      {/* ... existing content ... */}

      <StoryHeader
        story={story}
        onEdit={() => setIsEditDialogOpen(true)}
        onDelete={() => setIsDeleteDialogOpen(true)}
      />

      {/* ... rest of content ... */}

      {/* Edit Story Dialog */}
      <Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
        <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
          <DialogHeader>
            <DialogTitle>Edit Story</DialogTitle>
            <DialogDescription>
              Update Story details. Changes will be saved immediately.
            </DialogDescription>
          </DialogHeader>

          <StoryForm
            mode="edit"
            initialData={{
              epicId: story.epicId,
              projectId: story.projectId,
              title: story.title,
              description: story.description,
              priority: story.priority,
              estimatedHours: story.estimatedHours,
            }}
            onSubmit={handleEditStory}
            onCancel={() => setIsEditDialogOpen(false)}
            isSubmitting={updateStory.isPending}
          />
        </DialogContent>
      </Dialog>

      {/* Delete Story Confirmation Dialog */}
      <AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle className="flex items-center gap-2">
              <AlertTriangle className="h-5 w-5 text-destructive" />
              Delete Story?
            </AlertDialogTitle>
            <AlertDialogDescription>
              This will permanently delete <strong>{story?.title}</strong> and all associated data.
              This action cannot be undone.

              {/* Cascade warning if Tasks exist */}
              {tasks.length > 0 && (
                <div className="mt-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md">
                  <p className="font-medium text-destructive mb-2">
                    ⚠️ This Story has {tasks.length} Task{tasks.length > 1 ? 's' : ''} that will also be deleted:
                  </p>
                  <ul className="list-disc list-inside text-sm space-y-1">
                    {tasks.slice(0, 5).map((task) => (
                      <li key={task.id}>{task.title}</li>
                    ))}
                    {tasks.length > 5 && (
                      <li className="italic">... and {tasks.length - 5} more</li>
                    )}
                  </ul>
                </div>
              )}
            </AlertDialogDescription>
          </AlertDialogHeader>

          <AlertDialogFooter>
            <AlertDialogCancel disabled={deleteStory.isPending}>
              Cancel
            </AlertDialogCancel>
            <AlertDialogAction
              onClick={handleDeleteStory}
              disabled={deleteStory.isPending}
              className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
            >
              {deleteStory.isPending && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
              Delete Story
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </div>
  );
}

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:

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.