From 2f8d239da0ff34a74bc7189ccbf9e340c5a03bf3 Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Sun, 26 Oct 2025 16:33:18 +0700 Subject: [PATCH] feat: switch to sections --- apps/landing/src/app/demo/gallery/page.tsx | 216 ++------------------ apps/landing/src/app/demo/layout.tsx | 39 ++++ apps/landing/src/app/demo/tti/page.tsx | 221 ++------------------- apps/landing/src/app/demo/upload/page.tsx | 205 ++----------------- 4 files changed, 91 insertions(+), 590 deletions(-) create mode 100644 apps/landing/src/app/demo/layout.tsx diff --git a/apps/landing/src/app/demo/gallery/page.tsx b/apps/landing/src/app/demo/gallery/page.tsx index 1dd9eb6..0da4ff3 100644 --- a/apps/landing/src/app/demo/gallery/page.tsx +++ b/apps/landing/src/app/demo/gallery/page.tsx @@ -1,13 +1,13 @@ 'use client'; import { useState, useEffect, useCallback } from 'react'; -import { MinimizedApiKey } from '@/components/demo/MinimizedApiKey'; +import { useApiKey } from '@/components/shared/ApiKeyWidget/apikey-context'; +import { Section } from '@/components/shared/Section'; import { ImageZoomModal } from '@/components/demo/ImageZoomModal'; import { ImageGrid } from '@/components/demo/gallery/ImageGrid'; import { EmptyGalleryState } from '@/components/demo/gallery/EmptyGalleryState'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; -const API_KEY_STORAGE_KEY = 'banatie_demo_api_key'; const IMAGES_PER_PAGE = 30; type ImageItem = { @@ -31,22 +31,13 @@ type ImagesResponse = { message?: string; }; -type ApiKeyInfo = { - organizationSlug?: string; - projectSlug?: string; -}; - type DownloadTimeMap = { [imageId: string]: number; }; export default function GalleryPage() { - const [apiKey, setApiKey] = useState(''); - const [apiKeyVisible, setApiKeyVisible] = useState(false); - const [apiKeyValidated, setApiKeyValidated] = useState(false); - const [apiKeyInfo, setApiKeyInfo] = useState(null); - const [apiKeyError, setApiKeyError] = useState(''); - const [validatingKey, setValidatingKey] = useState(false); + // API Key from context + const { apiKey, apiKeyValidated, focus } = useApiKey(); const [images, setImages] = useState([]); const [offset, setOffset] = useState(0); @@ -59,110 +50,10 @@ export default function GalleryPage() { const [downloadTimes, setDownloadTimes] = useState({}); useEffect(() => { - const storedApiKey = localStorage.getItem(API_KEY_STORAGE_KEY); - if (storedApiKey) { - setApiKey(storedApiKey); - validateStoredApiKey(storedApiKey); + if (apiKeyValidated) { + fetchImages(apiKey, 0); } - }, []); - - const validateStoredApiKey = async (keyToValidate: string) => { - setValidatingKey(true); - setApiKeyError(''); - - try { - const response = await fetch(`${API_BASE_URL}/api/info`, { - headers: { - 'X-API-Key': keyToValidate, - }, - }); - - if (response.ok) { - const data = await response.json(); - setApiKeyValidated(true); - if (data.keyInfo) { - setApiKeyInfo({ - organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, - projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, - }); - } else { - setApiKeyInfo({ - organizationSlug: 'Unknown', - projectSlug: 'Unknown', - }); - } - await fetchImages(keyToValidate, 0); - } else { - localStorage.removeItem(API_KEY_STORAGE_KEY); - setApiKeyError('Stored API key is invalid or expired'); - setApiKeyValidated(false); - } - } catch (error) { - setApiKeyError('Failed to validate stored API key'); - setApiKeyValidated(false); - } finally { - setValidatingKey(false); - } - }; - - const validateApiKey = async () => { - if (!apiKey.trim()) { - setApiKeyError('Please enter an API key'); - return; - } - - setValidatingKey(true); - setApiKeyError(''); - - try { - const response = await fetch(`${API_BASE_URL}/api/info`, { - headers: { - 'X-API-Key': apiKey, - }, - }); - - if (response.ok) { - const data = await response.json(); - setApiKeyValidated(true); - localStorage.setItem(API_KEY_STORAGE_KEY, apiKey); - - if (data.keyInfo) { - setApiKeyInfo({ - organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, - projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, - }); - } else { - setApiKeyInfo({ - organizationSlug: 'Unknown', - projectSlug: 'Unknown', - }); - } - - await fetchImages(apiKey, 0); - } else { - const error = await response.json(); - setApiKeyError(error.message || 'Invalid API key'); - setApiKeyValidated(false); - } - } catch (error) { - setApiKeyError('Failed to validate API key. Please check your connection.'); - setApiKeyValidated(false); - } finally { - setValidatingKey(false); - } - }; - - const revokeApiKey = () => { - localStorage.removeItem(API_KEY_STORAGE_KEY); - setApiKey(''); - setApiKeyValidated(false); - setApiKeyInfo(null); - setApiKeyError(''); - setImages([]); - setOffset(0); - setHasMore(false); - setError(''); - }; + }, [apiKeyValidated]); const fetchImages = async (keyToUse: string, fetchOffset: number) => { if (fetchOffset === 0) { @@ -223,16 +114,7 @@ export default function GalleryPage() { }, []); return ( -
- {apiKeyValidated && apiKeyInfo && ( - - )} - +

Image Gallery @@ -242,82 +124,22 @@ export default function GalleryPage() {

+ {/* API Key Required Notice - Only show when not validated */} {!apiKeyValidated && ( -
-

API Key

-
-
- setApiKey(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - validateApiKey(); - } - }} - placeholder="Enter your API key" - className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent pr-12" - aria-label="API key input" - /> - +
+
+
+

API Key Required

+

Enter your API key to browse your images

- - {apiKeyError && ( -
-

{apiKeyError}

-

- {apiKeyError.includes('Invalid') - ? 'Please check your API key and try again. You can create a new key in the admin dashboard.' - : 'Please check your internet connection and try again.'} -

-
- )} -
+
)} {apiKeyValidated && ( @@ -372,6 +194,6 @@ export default function GalleryPage() { )} setZoomedImageUrl(null)} /> - + ); } diff --git a/apps/landing/src/app/demo/layout.tsx b/apps/landing/src/app/demo/layout.tsx new file mode 100644 index 0000000..2a98eba --- /dev/null +++ b/apps/landing/src/app/demo/layout.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { ReactNode } from 'react'; +import { usePathname } from 'next/navigation'; +import { SubsectionNav } from '@/components/shared/SubsectionNav'; +import { ApiKeyWidget } from '@/components/shared/ApiKeyWidget/apikey-widget'; +import { ApiKeyProvider } from '@/components/shared/ApiKeyWidget/apikey-context'; + +interface DemoLayoutProps { + children: ReactNode; +} + +const navItems = [ + { label: 'Text to Image', href: '/demo/tti' }, + { label: 'Upload', href: '/demo/upload' }, + { label: 'Gallery', href: '/demo/gallery' }, +]; + +export default function DemoLayout({ children }: DemoLayoutProps) { + const pathname = usePathname(); + + return ( + +
+ {/* Animated gradient background */} +
+
+
+
+ + {/* Subsection Navigation */} + } /> + + {/* Page Content */} +
{children}
+
+
+ ); +} diff --git a/apps/landing/src/app/demo/tti/page.tsx b/apps/landing/src/app/demo/tti/page.tsx index e7db91a..5aef92f 100644 --- a/apps/landing/src/app/demo/tti/page.tsx +++ b/apps/landing/src/app/demo/tti/page.tsx @@ -1,14 +1,14 @@ 'use client'; -import { useState, useRef, useEffect, KeyboardEvent } from 'react'; -import { MinimizedApiKey } from '@/components/demo/MinimizedApiKey'; +import { useState, useRef, KeyboardEvent } from 'react'; +import { useApiKey } from '@/components/shared/ApiKeyWidget/apikey-context'; +import { Section } from '@/components/shared/Section'; 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'; // Generate random 6-character uppercase ID for pairing images function generatePairId(): string { @@ -51,19 +51,9 @@ interface GenerationResult { } & AdvancedOptionsData; } -interface ApiKeyInfo { - organizationSlug?: string; - projectSlug?: string; -} - export default function DemoTTIPage() { - // API Key State - const [apiKey, setApiKey] = useState(''); - const [apiKeyVisible, setApiKeyVisible] = useState(false); - const [apiKeyValidated, setApiKeyValidated] = useState(false); - const [apiKeyInfo, setApiKeyInfo] = useState(null); - const [apiKeyError, setApiKeyError] = useState(''); - const [validatingKey, setValidatingKey] = useState(false); + // API Key from context + const { apiKey, apiKeyValidated, focus } = useApiKey(); // Prompt State const [prompt, setPrompt] = useState(''); @@ -85,118 +75,6 @@ export default function DemoTTIPage() { const textareaRef = useRef(null); - // Load API key from localStorage on mount - useEffect(() => { - const storedApiKey = localStorage.getItem(API_KEY_STORAGE_KEY); - if (storedApiKey) { - setApiKey(storedApiKey); - // Auto-validate the stored key - validateStoredApiKey(storedApiKey); - } - }, []); - - // Validate stored API key (without user interaction) - const validateStoredApiKey = async (keyToValidate: string) => { - setValidatingKey(true); - setApiKeyError(''); - - try { - const response = await fetch(`${API_BASE_URL}/api/info`, { - headers: { - 'X-API-Key': keyToValidate, - }, - }); - - if (response.ok) { - const data = await response.json(); - setApiKeyValidated(true); - if (data.keyInfo) { - setApiKeyInfo({ - organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, - projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, - }); - } else { - setApiKeyInfo({ - organizationSlug: 'Unknown', - projectSlug: 'Unknown', - }); - } - } else { - // Stored key is invalid, clear it - localStorage.removeItem(API_KEY_STORAGE_KEY); - setApiKeyError('Stored API key is invalid or expired'); - setApiKeyValidated(false); - } - } catch (error) { - setApiKeyError('Failed to validate stored API key'); - setApiKeyValidated(false); - } finally { - setValidatingKey(false); - } - }; - - // Validate API Key - const validateApiKey = async () => { - if (!apiKey.trim()) { - setApiKeyError('Please enter an API key'); - return; - } - - setValidatingKey(true); - setApiKeyError(''); - - try { - // Test API key with a minimal request to /api/info or similar - const response = await fetch(`${API_BASE_URL}/api/info`, { - headers: { - 'X-API-Key': apiKey, - }, - }); - - if (response.ok) { - const data = await response.json(); - setApiKeyValidated(true); - - // Save to localStorage on successful validation - localStorage.setItem(API_KEY_STORAGE_KEY, apiKey); - - // Extract org/project info from API response - if (data.keyInfo) { - setApiKeyInfo({ - organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, - projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, - }); - } else { - setApiKeyInfo({ - organizationSlug: 'Unknown', - projectSlug: 'Unknown', - }); - } - } else { - const error = await response.json(); - setApiKeyError(error.message || 'Invalid API key'); - setApiKeyValidated(false); - } - } catch (error) { - setApiKeyError('Failed to validate API key. Please check your connection.'); - setApiKeyValidated(false); - } finally { - setValidatingKey(false); - } - }; - - // Revoke API Key - const revokeApiKey = () => { - // Clear localStorage - localStorage.removeItem(API_KEY_STORAGE_KEY); - - // Clear state - setApiKey(''); - setApiKeyValidated(false); - setApiKeyInfo(null); - setApiKeyError(''); - }; - // Generate Images const generateImages = async () => { if (!prompt.trim()) { @@ -380,17 +258,7 @@ export default function DemoTTIPage() { }; return ( -
- {/* Minimized API Key Badge */} - {apiKeyValidated && apiKeyInfo && ( - - )} - +
{/* Page Header */}

@@ -401,77 +269,22 @@ export default function DemoTTIPage() {

- {/* API Key Section - Only show when not validated */} + {/* API Key Required Notice - Only show when not validated */} {!apiKeyValidated && ( -
-

API Key

-
-
- setApiKey(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - validateApiKey(); - } - }} - placeholder="Enter your API key" - className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent pr-12" - aria-label="API key input" - /> - +
+
+
+

API Key Required

+

Enter your API key to use this workbench

- - {apiKeyError && ( -

- {apiKeyError} -

- )} -
+
)} {/* Unified Prompt & Generation Card */} @@ -623,6 +436,6 @@ export default function DemoTTIPage() { {/* Zoom Modal */} setZoomedImage(null)} /> - + ); } diff --git a/apps/landing/src/app/demo/upload/page.tsx b/apps/landing/src/app/demo/upload/page.tsx index 9c3d16b..9fab785 100644 --- a/apps/landing/src/app/demo/upload/page.tsx +++ b/apps/landing/src/app/demo/upload/page.tsx @@ -1,7 +1,8 @@ 'use client'; import { useState, useEffect, useRef, DragEvent, ChangeEvent } from 'react'; -import { MinimizedApiKey } from '@/components/demo/MinimizedApiKey'; +import { useApiKey } from '@/components/shared/ApiKeyWidget/apikey-context'; +import { Section } from '@/components/shared/Section'; import { CodeExamplesWidget } from '@/components/demo/CodeExamplesWidget'; import { ImageZoomModal } from '@/components/demo/ImageZoomModal'; import { SelectedFileCodePreview } from '@/components/demo/SelectedFileCodePreview'; @@ -10,7 +11,6 @@ import { ImageCard } from '@/components/shared/ImageCard'; import { calculateAspectRatio } from '@/utils/imageUtils'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; -const API_KEY_STORAGE_KEY = 'banatie_demo_api_key'; const UPLOAD_HISTORY_KEY = 'banatie_upload_history'; const ALLOWED_FILE_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp']; @@ -46,19 +46,9 @@ interface UploadHistoryItem { downloadMs?: number; } -interface ApiKeyInfo { - organizationSlug?: string; - projectSlug?: string; -} - export default function DemoUploadPage() { - // API Key State - const [apiKey, setApiKey] = useState(''); - const [apiKeyVisible, setApiKeyVisible] = useState(false); - const [apiKeyValidated, setApiKeyValidated] = useState(false); - const [apiKeyInfo, setApiKeyInfo] = useState(null); - const [apiKeyError, setApiKeyError] = useState(''); - const [validatingKey, setValidatingKey] = useState(false); + // API Key from context + const { apiKey, apiKeyValidated, focus } = useApiKey(); // Upload State const [selectedFile, setSelectedFile] = useState(null); @@ -85,15 +75,6 @@ export default function DemoUploadPage() { // Refs const fileInputRef = useRef(null); - // Load API key from localStorage on mount - useEffect(() => { - const storedApiKey = localStorage.getItem(API_KEY_STORAGE_KEY); - if (storedApiKey) { - setApiKey(storedApiKey); - validateStoredApiKey(storedApiKey); - } - }, []); - // Load upload history from sessionStorage useEffect(() => { const storedHistory = sessionStorage.getItem(UPLOAD_HISTORY_KEY); @@ -119,97 +100,6 @@ export default function DemoUploadPage() { } }, [uploadHistory]); - const validateStoredApiKey = async (keyToValidate: string) => { - setValidatingKey(true); - setApiKeyError(''); - - try { - const response = await fetch(`${API_BASE_URL}/api/info`, { - headers: { - 'X-API-Key': keyToValidate, - }, - }); - - if (response.ok) { - const data = await response.json(); - setApiKeyValidated(true); - if (data.keyInfo) { - setApiKeyInfo({ - organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, - projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, - }); - } else { - setApiKeyInfo({ - organizationSlug: 'Unknown', - projectSlug: 'Unknown', - }); - } - } else { - localStorage.removeItem(API_KEY_STORAGE_KEY); - setApiKeyError('Stored API key is invalid or expired'); - setApiKeyValidated(false); - } - } catch (error) { - setApiKeyError('Failed to validate stored API key'); - setApiKeyValidated(false); - } finally { - setValidatingKey(false); - } - }; - - const validateApiKey = async () => { - if (!apiKey.trim()) { - setApiKeyError('Please enter an API key'); - return; - } - - setValidatingKey(true); - setApiKeyError(''); - - try { - const response = await fetch(`${API_BASE_URL}/api/info`, { - headers: { - 'X-API-Key': apiKey, - }, - }); - - if (response.ok) { - const data = await response.json(); - setApiKeyValidated(true); - localStorage.setItem(API_KEY_STORAGE_KEY, apiKey); - - if (data.keyInfo) { - setApiKeyInfo({ - organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, - projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, - }); - } else { - setApiKeyInfo({ - organizationSlug: 'Unknown', - projectSlug: 'Unknown', - }); - } - } else { - const error = await response.json(); - setApiKeyError(error.message || 'Invalid API key'); - setApiKeyValidated(false); - } - } catch (error) { - setApiKeyError('Failed to validate API key. Please check your connection.'); - setApiKeyValidated(false); - } finally { - setValidatingKey(false); - } - }; - - const revokeApiKey = () => { - localStorage.removeItem(API_KEY_STORAGE_KEY); - setApiKey(''); - setApiKeyValidated(false); - setApiKeyInfo(null); - setApiKeyError(''); - }; - const validateFile = (file: File): string | null => { if (!ALLOWED_FILE_TYPES.includes(file.type)) { return `Invalid file type. Allowed: PNG, JPEG, JPG, WebP`; @@ -407,16 +297,7 @@ Body (form-data): }; return ( -
- {apiKeyValidated && apiKeyInfo && ( - - )} - +

File Upload Workbench @@ -426,76 +307,22 @@ Body (form-data):

+ {/* API Key Required Notice - Only show when not validated */} {!apiKeyValidated && ( -
-

API Key

-
-
- setApiKey(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - validateApiKey(); - } - }} - placeholder="Enter your API key" - className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent pr-12" - aria-label="API key input" - /> - +
+
+
+

API Key Required

+

Enter your API key to use this workbench

- - {apiKeyError && ( -

- {apiKeyError} -

- )} -
+
)}
setZoomedImageUrl(null)} /> - +
); }