import * as React from 'react'; // FIX: Import FormBlock and related types to handle form creation and editing. import { Block, BlockType, LinkBlock, HeaderBlock, SocialBlock, VideoBlock, ImageBlock, TextBlock, MapBlock, PdfBlock, SocialLink, MediaSource, EmailBlock, PhoneBlock, AIGCVideo, AIGCArticle, NewsBlock, NewsItemFromUrl, ProductBlock, ProductItem, SocialPlatform, ChatBlock, EnterpriseInfoBlock, EnterpriseInfoItem, EnterpriseInfoIcon, FormBlock, FormField, FormFieldId, FormPurposeOption, AwardBlock, AwardItem, FooterBlock, FooterLink } from '../types'; import { Icon } from './ui/Icon'; import { parseProductUrl, mockProductDatabase } from '../services/geminiService'; const blockTypeMetadata: Record = { link: { icon: , name: 'Link' }, header: { icon: , name: 'Header' }, text: { icon: , name: 'Text' }, social: { icon: , name: 'Social Icons' }, chat: { icon: , name: 'Chat Button' }, enterprise_info: { icon: , name: 'Enterprise Info'}, video: { icon: , name: 'Video' }, image: { icon: , name: 'Image' }, map: { icon: , name: 'Map' }, pdf: { icon: , name: 'PDF' }, email: { icon: , name: 'Email' }, phone: { icon: , name: 'Phone' }, news: { icon: , name: 'News' }, product: { icon: , name: 'Product' }, award: { icon: , name: 'Awards' }, form: { icon: , name: 'Form' }, footer: { icon: , name: 'Footer' }, }; const AddBlockModal: React.FC<{ onSelect: (type: BlockType) => void; onClose: () => void; hasChatBlock: boolean; hasFooterBlock: boolean; }> = ({ onSelect, onClose, hasChatBlock, hasFooterBlock }) => { return (
e.stopPropagation()}>

Add Block

{(Object.keys(blockTypeMetadata) as BlockType[]).map(type => { const isDisabled = (type === 'chat' && hasChatBlock) || (type === 'footer' && hasFooterBlock); return ( ) })}
); }; const LayoutToggle: React.FC<{ label: string; options: {label: string, value: string}[]; value: string; onLayoutChange: (value: any) => void; }> = ({ label, options, value, onLayoutChange }) => (
{options.map(opt => ( ))}
); const SourceTypeToggle: React.FC<{ isVideo: boolean, type: 'url' | 'file' | 'aigc'; onTypeChange: (type: any) => void; onAIGCOpen: () => void; }> = ({ isVideo, type, onTypeChange, onAIGCOpen }) => (
{isVideo && }
); const AIGCNewsLibraryModal: React.FC<{ articles: AIGCArticle[]; selectedArticleIds: string[]; onClose: () => void; onSave: (selectedIds: string[]) => void; }> = ({ articles, selectedArticleIds, onClose, onSave }) => { const [localSelected, setLocalSelected] = React.useState(selectedArticleIds); const toggleSelection = (id: string) => { setLocalSelected(prev => prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]); }; return (
e.stopPropagation()}>

Select News Articles

{articles.length === 0 ? (

No articles in your library. Create some in the AIGC News Creator!

) : (
{articles.map(article => ( ))}
)}
); }; const SparkleIcon = () => ( ); const BlockItemEditor: React.FC<{ block: Block; onUpdate: (updatedBlock: Block) => void; onAIGCOpen: () => void, aigcVideos: AIGCVideo[]; aigcArticles: AIGCArticle[] }> = ({ block, onUpdate, onAIGCOpen, aigcVideos, aigcArticles }) => { const inputClasses = "w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border-2 border-gray-300 dark:border-gray-600 focus:outline-none focus:border-brand-primary"; const labelClasses = "text-sm font-semibold text-gray-600 dark:text-gray-300"; const handleUrlBlur = (e: React.FocusEvent) => { const { value: url } = e.target; const linkBlock = block as LinkBlock; if (!url || !url.startsWith('http')) { onUpdate({ ...linkBlock, iconUrl: undefined, url }); return; } try { const domain = new URL(url).hostname; const iconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=64`; onUpdate({ ...linkBlock, iconUrl, url }); } catch (error) { console.error("Invalid URL for favicon:", error); onUpdate({ ...linkBlock, iconUrl: undefined, url }); } }; switch (block.type) { case 'header': { const headerBlock = block as HeaderBlock; return
onUpdate({ ...block, text: e.target.value })} className={`${inputClasses} mt-1`} />
onUpdate({ ...headerBlock, titleAlignment: align })} />
} case 'link': { const linkBlock = block as LinkBlock; return
onUpdate({ ...block, title: e.target.value })} className={`${inputClasses} mt-1`} />
onUpdate({ ...block, thumbnailUrl: e.target.value })} className={`${inputClasses} mt-1`} />
} case 'chat': { const chatBlock = block as ChatBlock; return (
onUpdate({ ...chatBlock, layout })} />

Under Avatar: Only visible in Personal mode.
Floating Widget: Recommended for Enterprise mode.
Block Button: A standard button for any mode.

) } case 'enterprise_info': { const infoBlock = block as EnterpriseInfoBlock; const availableIcons: { value: EnterpriseInfoIcon; label: string }[] = [ { value: 'building', label: 'Building' }, { value: 'bank', label: 'Bank' }, { value: 'money', label: 'Money' }, { value: 'location', label: 'Location' }, { value: 'calendar', label: 'Calendar' }, { value: 'users', label: 'Users' }, { value: 'lightbulb', label: 'Lightbulb' }, ]; const handleUpdateItem = (id: string, field: 'icon' | 'label' | 'value', value: string) => { const updatedItems = infoBlock.items.map(item => item.id === id ? { ...item, [field]: value } : item); onUpdate({ ...infoBlock, items: updatedItems }); }; const handleAddItem = () => { const newItem: EnterpriseInfoItem = { id: `ei-${Date.now()}`, icon: 'lightbulb', label: 'New Item', value: 'Value' }; onUpdate({ ...infoBlock, items: [...infoBlock.items, newItem] }); }; const handleRemoveItem = (id: string) => { onUpdate({ ...infoBlock, items: infoBlock.items.filter(item => item.id !== id) }); }; return (
onUpdate({ ...infoBlock, alignment: align })} /> {infoBlock.items.map(item => (
handleUpdateItem(item.id, 'label', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded border border-gray-300 dark:border-gray-600" />
handleUpdateItem(item.id, 'value', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded border border-gray-300 dark:border-gray-600" />
))}
) } case 'social': { const socialBlock = block as SocialBlock; const availablePlatforms: SocialPlatform[] = ['twitter', 'instagram', 'facebook', 'linkedin', 'youtube', 'tiktok', 'github']; const handleAddLink = () => { const usedPlatforms = new Set(socialBlock.links.map(l => l.platform)); const nextPlatform = availablePlatforms.find(p => !usedPlatforms.has(p)) || 'twitter'; const newLink: SocialLink = { id: `sl-${Date.now()}`, platform: nextPlatform, url: '' }; onUpdate({ ...socialBlock, links: [...socialBlock.links, newLink] }); }; const handleAddMockLinks = () => { const mockLinks: SocialLink[] = [ { id: 'sl-mock-twitter', platform: 'twitter', url: 'https://twitter.com/johndoe' }, { id: 'sl-mock-github', platform: 'github', url: 'https://github.com/johndoe' }, { id: 'sl-mock-linkedin', platform: 'linkedin', url: 'https://linkedin.com/in/johndoe' }, ]; const existingPlatforms = new Set(socialBlock.links.map(l => l.platform)); const newMockLinks = mockLinks.filter(ml => !existingPlatforms.has(ml.platform)); onUpdate({ ...socialBlock, links: [...socialBlock.links, ...newMockLinks] }); }; const handleRemoveLink = (linkId: string) => { onUpdate({ ...socialBlock, links: socialBlock.links.filter(l => l.id !== linkId) }); }; const handleUpdateLink = (linkId: string, field: 'platform' | 'url', value: string) => { onUpdate({ ...socialBlock, links: socialBlock.links.map(l => l.id === linkId ? { ...l, [field]: value } as SocialLink : l) }); }; return
{socialBlock.links.map(link =>
handleUpdateLink(link.id, 'url', e.target.value)} placeholder="https://..." className={`flex-1 ${inputClasses}`} />
)}
} case 'image': case 'video': { const mediaBlock = block as ImageBlock | VideoBlock; const addMedia = () => { const newSource: MediaSource = mediaBlock.type === 'image' ? { type: 'url', value: '' } : { type: 'url', value: '' }; onUpdate({ ...mediaBlock, sources: [...mediaBlock.sources, newSource] }); }; const updateMediaSource = (index: number, newSource: MediaSource) => onUpdate({ ...mediaBlock, sources: mediaBlock.sources.map((s, i) => i === index ? newSource : s) }); const removeMedia = (index: number) => onUpdate({ ...mediaBlock, sources: mediaBlock.sources.filter((_, i) => i !== index) }); const displayClasses = "w-full text-sm p-2 bg-gray-100 dark:bg-gray-700 rounded-md border-2 border-gray-300 dark:border-gray-600"; const handleAddMockMedia = () => { const newSources: MediaSource[] = Array.from({ length: 3 }).map((_, i) => { if (mediaBlock.type === 'image') { return { type: 'url', value: `https://picsum.photos/seed/${Date.now() + i}/400/300` }; } else { const mockVideoUrls = [ 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4', 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4', 'https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' ]; return { type: 'url', value: mockVideoUrls[i % mockVideoUrls.length] }; } }); onUpdate({ ...mediaBlock, sources: [...mediaBlock.sources, ...newSources] }); }; return
onUpdate({ ...mediaBlock, layout })} />
{mediaBlock.sources.map((source, index) => { const videoFromAIGC = source.type === 'aigc' ? aigcVideos.find(v => v.id === source.videoId) : null; return (
{ const newSource: MediaSource = type === 'url' ? { type: 'url', value: '' } : { type: 'file', value: { name: 'example.file', size: 12345, previewUrl: `https://picsum.photos/seed/upload-${Date.now()}/200` } }; updateMediaSource(index, newSource); }} onAIGCOpen={onAIGCOpen} />
{source.type === 'url' ? updateMediaSource(index, { ...source, value: e.target.value })} className={inputClasses} placeholder="https://..." /> : source.type === 'file' ?
Simulated Upload: {source.value.name}
: videoFromAIGC ?
{videoFromAIGC.title}
:
AIGC video not found.
}
); })}
} case 'text': { const textBlock = block as TextBlock; return
onUpdate({ ...textBlock, fontSize: e.target.value })} className="w-16 p-1 text-sm bg-white dark:bg-gray-700 rounded" placeholder="16px" /> onUpdate({ ...textBlock, fontColor: e.target.value })} className="h-8 w-8 rounded-md" />