diff --git a/app/images/page.tsx b/app/images/page.tsx index 47fbdd5..2e123d8 100644 --- a/app/images/page.tsx +++ b/app/images/page.tsx @@ -6,7 +6,7 @@ import Image from "next/image"; import { useSession } from "@/lib/auth-client"; import { useRouter } from "next/navigation"; import { formatDistanceToNow } from "date-fns"; -import { IconLoader2, IconPhoto, IconPhotoPlus, IconSettings } from "@tabler/icons-react"; +import { IconLoader2, IconPhoto, IconPhotoPlus, IconSettings, IconHistory } from "@tabler/icons-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; @@ -19,6 +19,11 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { Textarea } from "@/components/ui/textarea"; import { Dialog, @@ -62,6 +67,10 @@ export default function ImagesLandingPage() { const [loadingSessions, setLoadingSessions] = useState(true); const [loadingError, setLoadingError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); + const [generatedImageUrl, setGeneratedImageUrl] = useState(null); + const [generationError, setGenerationError] = useState(null); + const [promptHistory, setPromptHistory] = useState([]); + const [showHistory, setShowHistory] = useState(false); useEffect(() => { if (!isPending) { @@ -74,6 +83,29 @@ export default function ImagesLandingPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [session?.user, isPending]); + // Load prompt history from localStorage on mount + useEffect(() => { + try { + const saved = localStorage.getItem("image-prompt-history"); + if (saved) { + setPromptHistory(JSON.parse(saved)); + } + } catch (error) { + console.error("Failed to load prompt history:", error); + } + }, []); + + // Save prompt history to localStorage when it changes + useEffect(() => { + try { + if (promptHistory.length > 0) { + localStorage.setItem("image-prompt-history", JSON.stringify(promptHistory)); + } + } catch (error) { + console.error("Failed to save prompt history:", error); + } + }, [promptHistory]); + const fetchSessions = async () => { try { setLoadingSessions(true); @@ -105,6 +137,9 @@ export default function ImagesLandingPage() { try { setIsSubmitting(true); + setGenerationError(null); + setGeneratedImageUrl(null); + const payload = { provider, model, @@ -121,29 +156,44 @@ export default function ImagesLandingPage() { if (!response.ok) { const data = await response.json().catch(() => ({})); - throw new Error(data.error || "Failed to start generation"); + throw new Error(data.error || "Failed to generate image"); } const data = await response.json(); - const sessionId = data?.session?.id; - toast.success("Session created — generating image"); + if (data.outputAsset?.url) { + setGeneratedImageUrl(data.outputAsset.url); + toast.success("Image generated successfully!"); + await fetchSessions(); // Refresh the sessions list - if (sessionId) { - router.push(`/images/${sessionId}`); + // Save prompt to history (avoid duplicates and keep last 10) + const trimmedPrompt = prompt.trim(); + setPromptHistory(prev => { + const filtered = prev.filter(p => p !== trimmedPrompt); + return [trimmedPrompt, ...filtered].slice(0, 10); + }); + } else if (data.session?.id) { + // Fallback: if no direct image was generated, navigate to session + toast.success("Session created — generating image"); + router.push(`/images/${data.session.id}`); } else { - await fetchSessions(); + throw new Error("No image was generated"); } } catch (error) { console.error(error); - toast.error( - error instanceof Error ? error.message : "Failed to start generation", - ); + const errorMessage = error instanceof Error ? error.message : "Failed to generate image"; + setGenerationError(errorMessage); + toast.error(errorMessage); } finally { setIsSubmitting(false); } }; + const handleSelectHistoryPrompt = (historyPrompt: string) => { + setPrompt(historyPrompt); + setShowHistory(false); + }; + const recentSessions = useMemo( () => sessions.slice(0, 12), [sessions], @@ -202,7 +252,7 @@ export default function ImagesLandingPage() {
-
+ @@ -214,8 +264,47 @@ export default function ImagesLandingPage() { rows={8} className="resize-none text-base pr-28 pb-20" disabled={isSubmitting} + aria-describedby="prompt-description" + aria-invalid={!!generationError} + required + minLength={1} />
+ {promptHistory.length > 0 && ( + + + + + +
+

Recent Prompts

+
+ {promptHistory.map((historyPrompt, index) => ( + + ))} +
+
+
+
+ )} @@ -282,7 +373,9 @@ export default function ImagesLandingPage() { type="submit" size="icon" className="pointer-events-auto h-10 w-10" - disabled={isSubmitting} + disabled={isSubmitting || !prompt.trim()} + aria-label={isSubmitting ? "Generating image" : "Generate image"} + title={isSubmitting ? "Generating image..." : "Generate image"} > {isSubmitting ? ( @@ -292,7 +385,79 @@ export default function ImagesLandingPage() {
+ + {/* Screen reader announcements */} +
+ {isSubmitting && "Generating image, please wait..."} + {generatedImageUrl && "Image generated successfully."} + {generationError && `Image generation failed: ${generationError}`} +
+ + {/* Generated Image Display Section */} + {(isSubmitting || generatedImageUrl || generationError) && ( +
+ {isSubmitting && ( +
+
+ )} + + {generatedImageUrl && ( +
+
+
+ Generated image from your prompt +
+
+
+ +
+
+ )} + + {generationError && ( +
+
+

Generation Failed

+

{generationError}

+ +
+
+ )} +
+ )}
diff --git a/documentation/tasks/greet-user_2025-11-12_17-01-48-423Z.json b/documentation/tasks/greet-user_2025-11-12_17-01-48-423Z.json new file mode 100644 index 0000000..78c34a1 --- /dev/null +++ b/documentation/tasks/greet-user_2025-11-12_17-01-48-423Z.json @@ -0,0 +1,66 @@ +[ + { + "title": "Create Image Generation Form Component", + "description": "Build a client-side form component for users to input text prompts for AI image generation", + "details": "Convert the existing app/images/page.tsx to a Client Component by adding 'use client' directive. Create a form using shadcn/ui components including a textarea for prompt input, a submit button, and proper form validation. Implement React state management using useState hooks for prompt text, loading state, error messages, and generated image URL. The form should handle user input validation and provide clear visual feedback.", + "status": "pending", + "test_strategy": "Unit test the form component to ensure proper state management, input validation, and event handling. Test form submission with valid and invalid inputs. Mock the API call to test loading states.", + "priority": "high", + "ordinal": 0, + "task_group_id": "c4a32e9f-39cc-4a82-b512-ae63248b04b2", + "parent_task_id": null, + "ai_result": null, + "id": "bd3dda73-3594-4ed8-8961-345671e05ba9", + "created_at": "2025-11-12T17:01:43.233325Z", + "user_id": "user_2qaB6nlVH3R9QXhQZpt1nmVDymN", + "subtasks": [] + }, + { + "title": "Implement API Integration for Image Generation", + "description": "Create the client-side logic to communicate with the /api/images/generate endpoint", + "details": "Implement an async function to handle form submission that sends a POST request to /api/images/generate with the user's prompt. Handle the API response to extract the generated image URL or error message. Implement proper error handling for network failures, API rate limits, and server errors. Update component state based on API response to trigger UI updates.", + "status": "pending", + "test_strategy": "Mock the API endpoint to test successful responses, error responses, and network failures. Verify that loading states are properly managed during API calls. Test error handling scenarios.", + "priority": "high", + "ordinal": 1, + "task_group_id": "c4a32e9f-39cc-4a82-b512-ae63248b04b2", + "parent_task_id": null, + "ai_result": null, + "id": "f6cfd52a-f799-45da-8f0f-4fece32c795d", + "created_at": "2025-11-12T17:01:43.233333Z", + "user_id": "user_2qaB6nlVH3R9QXhQZpt1nmVDymN", + "subtasks": [] + }, + { + "title": "Build Image Display and Loading States", + "description": "Create UI components to display generated images and provide user feedback during generation", + "details": "Implement conditional rendering logic to show different UI states: idle (form only), loading (spinner with progress message), success (display generated image), and error (error message with retry option). Use shadcn/ui components for consistent styling including loading spinners, image containers, and error alerts. Ensure the generated image is properly sized and responsive across different screen sizes.", + "status": "pending", + "test_strategy": "Test all UI states by mocking different API response scenarios. Verify responsive design on mobile, tablet, and desktop viewports. Test image loading and error handling for broken image URLs.", + "priority": "medium", + "ordinal": 2, + "task_group_id": "c4a32e9f-39cc-4a82-b512-ae63248b04b2", + "parent_task_id": null, + "ai_result": null, + "id": "dc9edae6-59c7-4d04-82db-d180278e4a05", + "created_at": "2025-11-12T17:01:43.233336Z", + "user_id": "user_2qaB6nlVH3R9QXhQZpt1nmVDymN", + "subtasks": [] + }, + { + "title": "Add User Experience Enhancements", + "description": "Implement additional UX features like prompt history, retry functionality, and accessibility improvements", + "details": "Add a retry button for failed generations, implement basic prompt history using local storage or session state, and ensure proper accessibility attributes (ARIA labels, keyboard navigation). Add visual feedback for long-running requests with estimated time indicators. Implement proper focus management and ensure the interface meets WCAG 2.1 AA compliance standards as specified in the PRD.", + "status": "pending", + "test_strategy": "Test keyboard navigation and screen reader compatibility. Verify retry functionality works correctly. Test prompt history persistence across page reloads. Conduct accessibility audit using automated tools.", + "priority": "medium", + "ordinal": 3, + "task_group_id": "c4a32e9f-39cc-4a82-b512-ae63248b04b2", + "parent_task_id": null, + "ai_result": null, + "id": "bb47ab7a-4bbf-4853-af7e-7f622e68cc9a", + "created_at": "2025-11-12T17:01:43.233337Z", + "user_id": "user_2qaB6nlVH3R9QXhQZpt1nmVDymN", + "subtasks": [] + } +] \ No newline at end of file