diff --git a/components/projects/acceptance-criteria-editor.tsx b/components/projects/acceptance-criteria-editor.tsx
new file mode 100644
index 0000000..7637d12
--- /dev/null
+++ b/components/projects/acceptance-criteria-editor.tsx
@@ -0,0 +1,96 @@
+'use client';
+
+import { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Checkbox } from '@/components/ui/checkbox';
+import { X, Plus } from 'lucide-react';
+
+interface AcceptanceCriteriaEditorProps {
+ criteria: string[];
+ onChange: (criteria: string[]) => void;
+ disabled?: boolean;
+}
+
+export function AcceptanceCriteriaEditor({
+ criteria,
+ onChange,
+ disabled,
+}: AcceptanceCriteriaEditorProps) {
+ const [newCriterion, setNewCriterion] = useState('');
+
+ const addCriterion = () => {
+ if (newCriterion.trim()) {
+ onChange([...criteria, newCriterion.trim()]);
+ setNewCriterion('');
+ }
+ };
+
+ const removeCriterion = (index: number) => {
+ onChange(criteria.filter((_, i) => i !== index));
+ };
+
+ const handleKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ addCriterion();
+ }
+ };
+
+ return (
+
+ {/* Existing criteria list */}
+ {criteria.length > 0 && (
+
+ {criteria.map((criterion, index) => (
+
+
+ {criterion}
+ {!disabled && (
+
+ )}
+
+ ))}
+
+ )}
+
+ {/* Add new criterion */}
+ {!disabled && (
+
+
setNewCriterion(e.target.value)}
+ onKeyPress={handleKeyPress}
+ />
+
+
+ )}
+
+ {criteria.length === 0 && disabled && (
+
+ No acceptance criteria defined
+
+ )}
+
+ );
+}
diff --git a/components/projects/story-form.tsx b/components/projects/story-form.tsx
index 2c7837c..e5a6456 100644
--- a/components/projects/story-form.tsx
+++ b/components/projects/story-form.tsx
@@ -28,6 +28,8 @@ import type { Story, WorkItemPriority } from '@/types/project';
import { toast } from 'sonner';
import { Loader2 } from 'lucide-react';
import { useAuthStore } from '@/stores/authStore';
+import { AcceptanceCriteriaEditor } from './acceptance-criteria-editor';
+import { TagsInput } from './tags-input';
const storySchema = z.object({
epicId: z.string().min(1, 'Parent Epic is required'),
@@ -45,6 +47,16 @@ const storySchema = z.object({
.min(0, 'Estimated hours must be positive')
.optional()
.or(z.literal('')),
+ // Sprint 4 Story 3: New fields
+ acceptanceCriteria: z.array(z.string()).default([]),
+ assigneeId: z.string().optional(),
+ tags: z.array(z.string()).default([]),
+ storyPoints: z
+ .number()
+ .min(0, 'Story points must be positive')
+ .max(100, 'Story points must be less than 100')
+ .optional()
+ .or(z.literal('')),
});
type StoryFormValues = z.infer;
@@ -80,6 +92,11 @@ export function StoryForm({
description: story?.description || '',
priority: story?.priority || 'Medium',
estimatedHours: story?.estimatedHours || ('' as any),
+ // Sprint 4 Story 3: New field defaults
+ acceptanceCriteria: story?.acceptanceCriteria || [],
+ assigneeId: story?.assigneeId || '',
+ tags: story?.tags || [],
+ storyPoints: story?.storyPoints || ('' as any),
},
});
@@ -94,6 +111,12 @@ export function StoryForm({
priority: data.priority,
estimatedHours:
typeof data.estimatedHours === 'number' ? data.estimatedHours : undefined,
+ // Sprint 4 Story 3: New fields
+ acceptanceCriteria: data.acceptanceCriteria,
+ assigneeId: data.assigneeId || undefined,
+ tags: data.tags,
+ storyPoints:
+ typeof data.storyPoints === 'number' ? data.storyPoints : undefined,
},
});
toast.success('Story updated successfully');
@@ -115,6 +138,12 @@ export function StoryForm({
estimatedHours:
typeof data.estimatedHours === 'number' ? data.estimatedHours : undefined,
createdBy: user.id,
+ // Sprint 4 Story 3: New fields
+ acceptanceCriteria: data.acceptanceCriteria,
+ assigneeId: data.assigneeId || undefined,
+ tags: data.tags,
+ storyPoints:
+ typeof data.storyPoints === 'number' ? data.storyPoints : undefined,
});
toast.success('Story created successfully');
}
@@ -259,6 +288,111 @@ export function StoryForm({
/>
+ {/* Sprint 4 Story 3: Acceptance Criteria */}
+ (
+
+ Acceptance Criteria
+
+
+
+
+ Define conditions that must be met for this story to be complete
+
+
+
+ )}
+ />
+
+ {/* Sprint 4 Story 3: Assignee and Story Points */}
+
+ (
+
+ Assignee
+
+ Assign to team member
+
+
+ )}
+ />
+
+ (
+
+ Story Points
+
+ {
+ const value = e.target.value;
+ field.onChange(value === '' ? '' : parseInt(value));
+ }}
+ value={field.value === undefined ? '' : field.value}
+ disabled={isLoading}
+ />
+
+ Fibonacci: 1, 2, 3, 5, 8, 13...
+
+
+ )}
+ />
+
+
+ {/* Sprint 4 Story 3: Tags */}
+ (
+
+ Tags
+
+
+
+
+ Add tags to categorize this story (e.g., frontend, bug, urgent)
+
+
+
+ )}
+ />
+
{onCancel && (