---
task_id: sprint_4_story_1_task_2
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 2: Implement Story Header Component

## Description

Create the Story header component that displays the Story title, status badge, priority badge, and action buttons (Edit, Delete). This header sits at the top of the Story detail page and includes a back button for navigation.

## What to Do

1. Create `components/projects/story-header.tsx`
2. Display Story title (32px, bold)
3. Add back button (navigates to parent Epic)
4. Display status badge with color coding
5. Display priority badge with color coding
6. Add Edit button (opens Story form dialog)
7. Add Delete button (opens confirmation dialog)
8. Implement responsive layout for mobile
9. Add keyboard navigation (ESC key for back)

## Files to Create/Modify

- `components/projects/story-header.tsx` (new, ~100-150 lines)
- `app/(dashboard)/stories/[id]/page.tsx` (modify, integrate header)

## Implementation Details

```typescript
// components/projects/story-header.tsx
'use client';

import { useRouter } from 'next/navigation';
import { ArrowLeft, Edit, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import type { Story } from '@/types/project';

interface StoryHeaderProps {
  story: Story;
  onEdit: () => void;
  onDelete: () => void;
}

export function StoryHeader({ story, onEdit, onDelete }: StoryHeaderProps) {
  const router = useRouter();

  const handleBack = () => {
    router.push(`/epics/${story.epicId}`);
  };

  // Keyboard shortcut: ESC to go back
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        handleBack();
      }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, []);

  return (
    <div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
      <div className="flex items-start gap-4">
        {/* Back Button */}
        <Button
          variant="ghost"
          size="icon"
          onClick={handleBack}
          aria-label="Back to Epic"
        >
          <ArrowLeft className="h-5 w-5" />
        </Button>

        {/* Title and Badges */}
        <div className="flex-1">
          <h1 className="text-3xl font-bold tracking-tight mb-2 line-clamp-2">
            {story.title}
          </h1>

          <div className="flex items-center gap-2 flex-wrap">
            {/* Status Badge */}
            <Badge variant={getStatusVariant(story.status)}>
              {story.status}
            </Badge>

            {/* Priority Badge */}
            <Badge variant={getPriorityVariant(story.priority)}>
              {story.priority}
            </Badge>
          </div>
        </div>
      </div>

      {/* Action Buttons */}
      <div className="flex items-center gap-2">
        <Button
          variant="outline"
          size="sm"
          onClick={onEdit}
          aria-label="Edit Story"
        >
          <Edit className="h-4 w-4 mr-2" />
          Edit
        </Button>

        <Button
          variant="destructive"
          size="sm"
          onClick={onDelete}
          aria-label="Delete Story"
        >
          <Trash2 className="h-4 w-4 mr-2" />
          Delete
        </Button>
      </div>
    </div>
  );
}

// Helper functions for badge variants
function getStatusVariant(status: string) {
  const variants = {
    Backlog: 'secondary',
    Todo: 'default',
    InProgress: 'warning',
    Done: 'success',
  };
  return variants[status] || 'default';
}

function getPriorityVariant(priority: string) {
  const variants = {
    Low: 'default',
    Medium: 'warning',
    High: 'destructive',
    Critical: 'destructive',
  };
  return variants[priority] || 'default';
}
```

**Integration in page.tsx**:
```typescript
// app/(dashboard)/stories/[id]/page.tsx
import { StoryHeader } from '@/components/projects/story-header';

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

## Acceptance Criteria

- [ ] Header displays Story title (max 2 lines, ellipsis overflow)
- [ ] Back button navigates to parent Epic detail page
- [ ] ESC key also navigates back
- [ ] Status badge shows correct color (Backlog/Todo/InProgress/Done)
- [ ] Priority badge shows correct color (Low/Medium/High/Critical)
- [ ] Edit button opens Story form dialog (handler passed from page)
- [ ] Delete button opens confirmation dialog (handler passed from page)
- [ ] Responsive layout: Stack vertically on mobile
- [ ] All buttons have proper ARIA labels
- [ ] Keyboard accessible (Tab navigation works)

## Testing

**Manual Testing**:
1. View Story detail page
2. Verify title displays correctly
3. Click back button → Navigates to Epic detail
4. Press ESC key → Navigates to Epic detail
5. Verify status badge color matches status value
6. Verify priority badge color matches priority value
7. Click Edit button → Triggers onEdit callback
8. Click Delete button → Triggers onDelete callback
9. Test on mobile → Buttons stack vertically

**Unit Test**:
```typescript
import { render, screen, fireEvent } from '@testing-library/react';
import { StoryHeader } from './story-header';

describe('StoryHeader', () => {
  const mockStory = {
    id: '123',
    title: 'Test Story',
    status: 'InProgress',
    priority: 'High',
    epicId: 'epic-123',
  };

  it('renders story title', () => {
    render(<StoryHeader story={mockStory} onEdit={jest.fn()} onDelete={jest.fn()} />);
    expect(screen.getByText('Test Story')).toBeInTheDocument();
  });

  it('calls onEdit when edit button clicked', () => {
    const onEdit = jest.fn();
    render(<StoryHeader story={mockStory} onEdit={onEdit} onDelete={jest.fn()} />);
    fireEvent.click(screen.getByLabelText('Edit Story'));
    expect(onEdit).toHaveBeenCalled();
  });

  it('calls onDelete when delete button clicked', () => {
    const onDelete = jest.fn();
    render(<StoryHeader story={mockStory} onEdit={jest.fn()} onDelete={onDelete} />);
    fireEvent.click(screen.getByLabelText('Delete Story'));
    expect(onDelete).toHaveBeenCalled();
  });
});
```

## Dependencies

**Prerequisites**:
- Task 1 (page structure must exist)
- ✅ shadcn/ui Button, Badge components

**Blocks**:
- Task 5 (Edit/Delete dialogs need header buttons)

## Estimated Time

3 hours

## Notes

Reuse badge color logic from Epic header component if it exists. Keep the component simple and focused - dialogs are handled in Task 5.
