diff --git a/apps/landing/src/app/demo/tti/page.tsx b/apps/landing/src/app/demo/tti/page.tsx index f95a9d1..e7db91a 100644 --- a/apps/landing/src/app/demo/tti/page.tsx +++ b/apps/landing/src/app/demo/tti/page.tsx @@ -5,6 +5,7 @@ import { MinimizedApiKey } from '@/components/demo/MinimizedApiKey'; import { GenerationTimer } from '@/components/demo/GenerationTimer'; import { ResultCard } from '@/components/demo/ResultCard'; import { AdvancedOptionsModal, AdvancedOptionsData } from '@/components/demo/AdvancedOptionsModal'; +import { ImageZoomModal } from '@/components/demo/ImageZoomModal'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; const API_KEY_STORAGE_KEY = 'banatie_demo_api_key'; @@ -621,29 +622,7 @@ export default function DemoTTIPage() { )} {/* Zoom Modal */} - {zoomedImage && ( -
setZoomedImage(null)} - role="dialog" - aria-modal="true" - aria-label="Zoomed image view" - > - - Zoomed e.stopPropagation()} - /> -
- )} + setZoomedImage(null)} /> ); } diff --git a/apps/landing/src/app/demo/upload/page.tsx b/apps/landing/src/app/demo/upload/page.tsx index 8acd994..c323d42 100644 --- a/apps/landing/src/app/demo/upload/page.tsx +++ b/apps/landing/src/app/demo/upload/page.tsx @@ -2,6 +2,8 @@ import { useState, useEffect, useRef, DragEvent, ChangeEvent } from 'react'; import { MinimizedApiKey } from '@/components/demo/MinimizedApiKey'; +import { CodeExamplesWidget } from '@/components/demo/CodeExamplesWidget'; +import { ImageZoomModal } from '@/components/demo/ImageZoomModal'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; const API_KEY_STORAGE_KEY = 'banatie_demo_api_key'; @@ -61,6 +63,12 @@ export default function DemoUploadPage() { // History State const [uploadHistory, setUploadHistory] = useState([]); + // Zoom Modal State + const [zoomedImageUrl, setZoomedImageUrl] = useState(null); + + // Copy Feedback State + const [codeCopied, setCodeCopied] = useState(false); + // Refs const fileInputRef = useRef(null); @@ -321,6 +329,42 @@ export default function DemoUploadPage() { return `${(ms / 1000).toFixed(2)}s`; }; + const generateUploadCodeExamples = (item: UploadHistoryItem, key: string, baseUrl: string) => { + const localPath = `./${item.originalName}`; + + return { + curl: `curl -X POST "${baseUrl}/api/upload" \\ + -H "X-API-Key: ${key}" \\ + -F "file=@${localPath}"`, + fetch: `const formData = new FormData(); +formData.append('file', fileInput.files[0]); + +fetch('${baseUrl}/api/upload', { + method: 'POST', + headers: { + 'X-API-Key': '${key}' + }, + body: formData +}) + .then(response => response.json()) + .then(data => console.log(data)) + .catch(error => console.error('Error:', error));`, + rest: `POST ${baseUrl}/api/upload +Headers: + X-API-Key: ${key} + Content-Type: multipart/form-data + +Body (form-data): + file: @${localPath}`, + }; + }; + + const handleCopyCode = (code: string) => { + navigator.clipboard.writeText(code); + setCodeCopied(true); + setTimeout(() => setCodeCopied(false), 2000); + }; + return (
{apiKeyValidated && apiKeyInfo && ( @@ -522,52 +566,94 @@ export default function DemoUploadPage() { {uploadHistory.length > 0 && ( -
+

Upload History

-
+
{uploadHistory.map((item) => ( -
-
- {item.originalName} + {/* Column 1: Image Card */} +
+
+
setZoomedImageUrl(item.url)} + role="button" + tabIndex={0} + aria-label="View full size image" + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setZoomedImageUrl(item.url); + } + }} + > + {item.originalName} +
+ + + +
+
+
+

+ {item.originalName} +

+
+ + {formatFileSize(item.size)} + + + {formatDuration(item.durationMs)} + +
+

+ {item.timestamp.toLocaleString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + })} +

+ +
+
+
+ + {/* Columns 2-3: API Code Examples Widget */} +
+
-
-

- {item.originalName} -

-
- {formatFileSize(item.size)} - {formatDuration(item.durationMs)} -
-

- {item.timestamp.toLocaleString('en-US', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - })} -

-
- - View Full Image -
))}
)} + + {/* Image Zoom Modal */} + setZoomedImageUrl(null)} />
); } diff --git a/apps/landing/src/components/demo/CodeExamplesWidget.tsx b/apps/landing/src/components/demo/CodeExamplesWidget.tsx new file mode 100644 index 0000000..4461a32 --- /dev/null +++ b/apps/landing/src/components/demo/CodeExamplesWidget.tsx @@ -0,0 +1,95 @@ +'use client'; + +import { useState } from 'react'; + +type CodeTab = 'curl' | 'fetch' | 'rest'; + +interface CodeExamplesWidgetProps { + codeExamples: { + curl: string; + fetch: string; + rest: string; + }; + onCopy: (text: string) => void; + defaultTab?: CodeTab; +} + +export const CodeExamplesWidget = ({ + codeExamples, + onCopy, + defaultTab = 'curl', +}: CodeExamplesWidgetProps) => { + const [activeTab, setActiveTab] = useState(defaultTab); + + const getCodeForTab = () => { + switch (activeTab) { + case 'curl': + return codeExamples.curl; + case 'fetch': + return codeExamples.fetch; + case 'rest': + return codeExamples.rest; + } + }; + + return ( +
+
+
+
+
+
+
+
+ setActiveTab('curl')} + label="cURL" + /> + setActiveTab('fetch')} + label="JS Fetch" + /> + setActiveTab('rest')} + label="REST" + /> +
+ +
+
+        {getCodeForTab()}
+      
+
+ ); +}; + +const TabButton = ({ + active, + onClick, + label, +}: { + active: boolean; + onClick: () => void; + label: string; +}) => { + return ( + + ); +}; diff --git a/apps/landing/src/components/demo/ImageZoomModal.tsx b/apps/landing/src/components/demo/ImageZoomModal.tsx new file mode 100644 index 0000000..791bd7f --- /dev/null +++ b/apps/landing/src/components/demo/ImageZoomModal.tsx @@ -0,0 +1,52 @@ +'use client'; + +import { useEffect } from 'react'; + +interface ImageZoomModalProps { + imageUrl: string | null; + onClose: () => void; +} + +export const ImageZoomModal = ({ imageUrl, onClose }: ImageZoomModalProps) => { + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + onClose(); + } + }; + + if (imageUrl) { + document.addEventListener('keydown', handleEscape); + } + + return () => { + document.removeEventListener('keydown', handleEscape); + }; + }, [imageUrl, onClose]); + + if (!imageUrl) return null; + + return ( +
+ + Zoomed e.stopPropagation()} + /> +
+ ); +}; diff --git a/apps/landing/src/components/demo/ResultCard.tsx b/apps/landing/src/components/demo/ResultCard.tsx index 646b00d..7d6f059 100644 --- a/apps/landing/src/components/demo/ResultCard.tsx +++ b/apps/landing/src/components/demo/ResultCard.tsx @@ -4,6 +4,7 @@ import { useState } from 'react'; import { InspectMode } from './InspectMode'; import { PromptReuseButton } from './PromptReuseButton'; import { CompletedTimerBadge } from './GenerationTimer'; +import { CodeExamplesWidget } from './CodeExamplesWidget'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; @@ -57,7 +58,6 @@ interface ResultCardProps { } type ViewMode = 'preview' | 'inspect'; -type CodeTab = 'curl' | 'fetch' | 'rest'; export function ResultCard({ result, @@ -68,7 +68,6 @@ export function ResultCard({ onReusePrompt, }: ResultCardProps) { const [viewMode, setViewMode] = useState('preview'); - const [activeTab, setActiveTab] = useState('curl'); // Build enhancement options JSON for code examples const buildEnhancementOptionsJson = () => { @@ -165,15 +164,10 @@ X-API-Key: ${apiKey} "filename": "generated_image" }`; - const getCodeForTab = () => { - switch (activeTab) { - case 'curl': - return curlCode; - case 'fetch': - return fetchCode; - case 'rest': - return restCode; - } + const codeExamples = { + curl: curlCode, + fetch: fetchCode, + rest: restCode, }; return ( @@ -240,12 +234,7 @@ X-API-Key: ${apiKey} {/* API Code Examples */} - + ) : ( void; - code: string; - onCopy: (text: string) => void; -}) { - return ( -
-
-
-
-
-
-
-
- setActiveTab('curl')} - label="cURL" - /> - setActiveTab('fetch')} - label="JS Fetch" - /> - setActiveTab('rest')} - label="REST" - /> -
- -
-
-        {code}
-      
-
- ); -} - -function TabButton({ - active, - onClick, - label, -}: { - active: boolean; - onClick: () => void; - label: string; -}) { - return ( - - ); -}