From 15f9dc3526c2a713f33764b94b5f0ba30b1c7e5a Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Sat, 11 Oct 2025 17:17:31 +0700 Subject: [PATCH] feat: show upload snippet on input --- apps/landing/src/app/demo/upload/page.tsx | 39 +++- .../components/demo/CodeExamplesWidget.tsx | 19 ++ .../demo/SelectedFileCodePreview.tsx | 206 ++++++++++++++++++ 3 files changed, 257 insertions(+), 7 deletions(-) create mode 100644 apps/landing/src/components/demo/SelectedFileCodePreview.tsx diff --git a/apps/landing/src/app/demo/upload/page.tsx b/apps/landing/src/app/demo/upload/page.tsx index c323d42..11db857 100644 --- a/apps/landing/src/app/demo/upload/page.tsx +++ b/apps/landing/src/app/demo/upload/page.tsx @@ -4,6 +4,7 @@ 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'; +import { SelectedFileCodePreview } from '@/components/demo/SelectedFileCodePreview'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; const API_KEY_STORAGE_KEY = 'banatie_demo_api_key'; @@ -330,14 +331,25 @@ export default function DemoUploadPage() { }; const generateUploadCodeExamples = (item: UploadHistoryItem, key: string, baseUrl: string) => { - const localPath = `./${item.originalName}`; + const fileName = item.originalName; return { - curl: `curl -X POST "${baseUrl}/api/upload" \\ + curl: `# Navigate to your images folder +cd /your/images/folder + +# Upload the file +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]); + -F "file=@${fileName}"`, + fetch: `// Set your images folder path +const imagePath = '/your/images/folder'; +const fileName = '${fileName}'; + +// For Node.js with fs module: +const fs = require('fs'); +const FormData = require('form-data'); +const formData = new FormData(); +formData.append('file', fs.createReadStream(\`\${imagePath}/\${fileName}\`)); fetch('${baseUrl}/api/upload', { method: 'POST', @@ -349,13 +361,15 @@ fetch('${baseUrl}/api/upload', { .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error));`, - rest: `POST ${baseUrl}/api/upload + rest: `# From your images folder: /your/images/folder + +POST ${baseUrl}/api/upload Headers: X-API-Key: ${key} Content-Type: multipart/form-data Body (form-data): - file: @${localPath}`, + file: @${fileName}`, }; }; @@ -545,6 +559,17 @@ Body (form-data):

)} + {selectedFile && apiKeyValidated && !validationError && ( +
+ +
+ )} +
{uploading ? 'Uploading...' : selectedFile ? 'Ready to upload' : 'No file selected'} diff --git a/apps/landing/src/components/demo/CodeExamplesWidget.tsx b/apps/landing/src/components/demo/CodeExamplesWidget.tsx index 4461a32..e55407a 100644 --- a/apps/landing/src/components/demo/CodeExamplesWidget.tsx +++ b/apps/landing/src/components/demo/CodeExamplesWidget.tsx @@ -2,6 +2,25 @@ import { useState } from 'react'; +/** + * Production-ready code examples widget. + * + * Core Principle: All code snippets are designed to be COPY-PASTE-AND-RUN ready. + * Users should be able to copy the code, replace the placeholder folder path, + * and execute it immediately on their machine with minimal edits. + * + * Pattern Used: + * - Navigate to folder (with placeholder path: /your/images/folder) + * - Use actual filename from selected/uploaded file + * - Clear comments showing what to replace + * + * @example + * // User has file at: /home/user/Downloads/sunset.jpg + * // Code shows: cd /your/images/folder; curl ... -F "file=@sunset.jpg" + * // User replaces: /your/images/folder → /home/user/Downloads + * // Result: Works immediately after one simple edit! + */ + type CodeTab = 'curl' | 'fetch' | 'rest'; interface CodeExamplesWidgetProps { diff --git a/apps/landing/src/components/demo/SelectedFileCodePreview.tsx b/apps/landing/src/components/demo/SelectedFileCodePreview.tsx new file mode 100644 index 0000000..38cc598 --- /dev/null +++ b/apps/landing/src/components/demo/SelectedFileCodePreview.tsx @@ -0,0 +1,206 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { CodeExamplesWidget } from './CodeExamplesWidget'; + +/** + * SelectedFileCodePreview Component + * + * Shows code snippets for uploading a selected file BEFORE upload. + * Helps users understand they can either: + * 1. Upload via UI button + * 2. Copy code and run programmatically + * + * Layout: + * - Mobile: Stacked vertically + * - Desktop: 3-column grid (1 col preview + 2 cols code) + */ + +interface SelectedFileCodePreviewProps { + file: File; + apiKey: string; + apiBaseUrl: string; + onCopy: (text: string) => void; +} + +export const SelectedFileCodePreview = ({ + file, + apiKey, + apiBaseUrl, + onCopy, +}: SelectedFileCodePreviewProps) => { + const [previewUrl, setPreviewUrl] = useState(''); + + // Create and cleanup preview URL + useEffect(() => { + const objectUrl = URL.createObjectURL(file); + setPreviewUrl(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [file]); + + const codeExamples = generateSelectedFileCodeExamples(file, apiKey, apiBaseUrl); + + return ( +
+
+

+ Selected File - Code Preview +

+
+ +
+ {/* Column 1: Compact File Preview Card */} +
+
+ {/* Image Preview - Compact */} +
+ {`Preview +
+ + {/* File Metadata */} +
+

+ {file.name} +

+ +
+ {/* File Size */} + + {formatFileSize(file.size)} + + + {/* File Type Badge */} + + {getFileType(file.name)} + +
+ + {/* Status Badge */} +
+ + + Ready to Upload + +
+ + {/* Helper Text */} +

+ Click upload button or copy code to run programmatically +

+
+
+
+ + {/* Columns 2-3: Code Examples Widget */} +
+ +
+
+
+ ); +}; + +/** + * Generate production-ready code examples for selected file upload + * + * Pattern: Users navigate to their folder, then run the upload command. + * This matches how real workflows work - users know where their file is. + */ +const generateSelectedFileCodeExamples = ( + file: File, + apiKey: string, + apiBaseUrl: string +) => { + const fileName = file.name; + + return { + curl: `# Navigate to your images folder +cd /your/images/folder + +# Upload the file +curl -X POST "${apiBaseUrl}/api/upload" \\ + -H "X-API-Key: ${apiKey}" \\ + -F "file=@${fileName}"`, + + fetch: `// Set your images folder path +const imagePath = '/your/images/folder'; +const fileName = '${fileName}'; + +// For Node.js with fs module: +const fs = require('fs'); +const FormData = require('form-data'); +const formData = new FormData(); +formData.append('file', fs.createReadStream(\`\${imagePath}/\${fileName}\`)); + +fetch('${apiBaseUrl}/api/upload', { + method: 'POST', + headers: { + 'X-API-Key': '${apiKey}' + }, + body: formData +}) + .then(response => response.json()) + .then(data => console.log(data)) + .catch(error => console.error('Error:', error));`, + + rest: `# From your images folder: /your/images/folder + +POST ${apiBaseUrl}/api/upload +Headers: + X-API-Key: ${apiKey} + Content-Type: multipart/form-data + +Body (form-data): + file: @${fileName}`, + }; +}; + +/** + * Format file size in human-readable format + */ +const formatFileSize = (bytes: number): string => { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`; +}; + +/** + * Extract file type from filename + */ +const getFileType = (filename: string): string => { + const extension = filename.split('.').pop()?.toUpperCase(); + if (extension === 'JPG' || extension === 'JPEG') return 'JPEG'; + if (extension === 'PNG') return 'PNG'; + if (extension === 'WEBP') return 'WebP'; + if (extension === 'GIF') return 'GIF'; + return extension || 'FILE'; +};