--- task_id: sprint_4_story_1_task_4 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 4: Integrate Story API Data Loading and Error Handling ## Description Connect the Story detail page to the backend API using React Query hooks. Implement data loading, error handling, 404 detection, and loading states with skeleton loaders. ## What to Do 1. Use `useStory(id)` hook to fetch Story data 2. Use `useEpic(epicId)` hook to fetch parent Epic data 3. Implement loading states with skeleton loaders 4. Handle 404 errors (Story not found) 5. Handle network errors 6. Handle permission errors (unauthorized) 7. Add breadcrumb navigation with data 8. Display Story description in main content area 9. Test with various Story IDs (valid, invalid, deleted) ## Files to Modify - `app/(dashboard)/stories/[id]/page.tsx` (modify, ~50 lines added) ## Implementation Details ```typescript // app/(dashboard)/stories/[id]/page.tsx 'use client'; import { use } from 'react'; import { notFound } from 'next/navigation'; import { useStory } from '@/lib/hooks/use-stories'; import { useEpic } from '@/lib/hooks/use-epics'; import { StoryHeader } from '@/components/projects/story-header'; import { StoryMetadataSidebar } from '@/components/projects/story-metadata-sidebar'; import { Card } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { AlertCircle } from 'lucide-react'; interface StoryDetailPageProps { params: Promise<{ id: string }>; } export default function StoryDetailPage({ params }: StoryDetailPageProps) { const { id } = use(params); // Fetch Story data const { data: story, isLoading: storyLoading, error: storyError, } = useStory(id); // Fetch parent Epic data const { data: parentEpic, isLoading: epicLoading, } = useEpic(story?.epicId, { enabled: !!story?.epicId, // Only fetch if Story loaded }); // Handle loading state if (storyLoading || epicLoading) { return ; } // Handle 404 - Story not found if (storyError?.response?.status === 404) { notFound(); } // Handle other errors (network, permission, etc.) if (storyError || !story) { return (
{storyError?.message || 'Failed to load Story. Please try again.'}
); } return (
{/* Breadcrumb Navigation */} {/* Story Header */} setIsEditDialogOpen(true)} onDelete={() => setIsDeleteDialogOpen(true)} /> {/* Two-column layout */}
{/* Main Content Column */}
{/* Story Description Card */}

Description

{story.description ? (

{story.description}

) : (

No description provided.

)}
{/* Acceptance Criteria Card - Story 3 */}

Acceptance Criteria

Acceptance criteria will be added in Story 3.

{/* Tasks Section - Story 2 */}

Tasks

Task list will be added in Story 2.

{/* Metadata Sidebar Column */}
); } // Loading skeleton function StoryDetailPageSkeleton() { return (
); } ``` **404 Page** (optional, create if doesn't exist): ```typescript // app/(dashboard)/stories/[id]/not-found.tsx import Link from 'next/link'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { FileQuestion } from 'lucide-react'; export default function StoryNotFound() { return (

Story Not Found

The Story you're looking for doesn't exist or you don't have permission to view it.

); } ``` ## Acceptance Criteria - [ ] Page fetches Story data using `useStory(id)` hook - [ ] Page fetches parent Epic data using `useEpic(epicId)` hook - [ ] Loading state displays skeleton loaders - [ ] 404 error handled (Story not found → shows custom 404 page) - [ ] Network errors display error message with retry option - [ ] Permission errors display appropriate message - [ ] Breadcrumb navigation shows full hierarchy with clickable links - [ ] Story description displays correctly (supports line breaks) - [ ] Empty description shows placeholder message - [ ] All data populates components (header, sidebar) - [ ] No console errors during data fetching ## Testing **Manual Testing**: 1. Navigate to valid Story ID → Verify data loads correctly 2. Navigate to invalid Story ID → Verify 404 page shows 3. Disconnect internet → Navigate to Story → Verify error message 4. Verify skeleton loaders show during initial load 5. Verify breadcrumb links are clickable and navigate correctly 6. Verify Story description displays with line breaks preserved 7. Test with Story that has no description → Verify placeholder shows **Test Cases**: ```bash # Valid Story /stories/[valid-story-id] → Should load Story details # Invalid Story /stories/invalid-id → Should show 404 page # Deleted Story /stories/[deleted-story-id] → Should show 404 page # No permission /stories/[other-tenant-story-id] → Should show error message ``` **E2E Test**: ```typescript test('story detail page loads data correctly', async ({ page }) => { await page.goto('/stories/story-123'); // Wait for data to load await expect(page.locator('h1')).toContainText('Story Title'); // Verify metadata displays await expect(page.locator('[data-testid="story-status"]')).toBeVisible(); await expect(page.locator('[data-testid="story-priority"]')).toBeVisible(); // Verify description await expect(page.getByText('Description')).toBeVisible(); }); test('handles 404 for invalid story', async ({ page }) => { await page.goto('/stories/invalid-id'); await expect(page.getByText('Story Not Found')).toBeVisible(); }); ``` ## Dependencies **Prerequisites**: - Task 1 (page structure must exist) - Task 2 (header component must exist) - Task 3 (sidebar component must exist) - ✅ `useStory(id)` hook (already exists) - ✅ `useEpic(id)` hook (already exists) **Blocks**: - Task 5 (Edit/Delete actions need data loaded) ## Estimated Time 3 hours ## Notes **Error Handling Strategy**: - 404 (Not Found) → Custom 404 page with helpful navigation - 403 (Forbidden) → Error message with permission context - 500 (Server Error) → Error message with retry button - Network Error → Error message with offline indicator **Performance**: Use React Query's built-in caching. Subsequent visits to the same Story load instantly from cache.