Vertical.ai / frontend /src /components /InfographicModal.jsx
Abhisingh-18's picture
Mirror of github.com/Abhisingh18/Vertical.ai
1f7ead8 verified
Raw
History Blame Contribute Delete
11.4 kB
import React, { useState } from 'react';
import { X, ZoomIn, ZoomOut, Download, Loader2, RefreshCw,
Target, Zap, Book, Users, Clock, ArrowRight, CheckCircle,
BarChart, GitBranch, Layers, Layout, Share2, Award, Lightbulb } from 'lucide-react';
import { generateInfographic } from '../api';
import * as htmlToImage from 'html-to-image';
const iconMap = {
'Target': Target, 'Zap': Zap, 'Book': Book, 'Users': Users,
'Clock': Clock, 'ArrowRight': ArrowRight, 'CheckCircle': CheckCircle,
'BarChart': BarChart, 'GitBranch': GitBranch, 'Layers': Layers,
'Layout': Layout, 'Share2': Share2, 'Award': Award, 'Lightbulb': Lightbulb
};
const InfographicModal = ({ isOpen, onClose, notebookId }) => {
const [status, setStatus] = useState('idle'); // idle, loading, ready
const [data, setData] = useState(null);
const [scale, setScale] = useState(1);
if (!isOpen) return null;
const handleGenerate = async () => {
setStatus('loading');
try {
const result = await generateInfographic(notebookId);
setData(result);
setStatus('ready');
} catch (error) {
console.error(error);
setStatus('error');
}
};
// Auto-generate on first open if no data
React.useEffect(() => {
if (isOpen && status === 'idle') {
handleGenerate();
}
}, [isOpen]);
const handleDownload = async () => {
const node = document.getElementById('infographic-canvas');
if (node) {
const dataUrl = await htmlToImage.toPng(node);
const link = document.createElement('a');
link.download = `infographic-${notebookId}.png`;
link.href = dataUrl;
link.click();
}
};
const DynamicIcon = ({ name, size=24, className }) => {
const Icon = iconMap[name] || Lightbulb;
return <Icon size={size} className={className} />;
};
// Render Logic based on Type
const renderLayout = () => {
if (!data) return null;
const { type, title, summary, sections } = data;
// Common Header
const Header = () => (
<div className="text-center mb-12">
<h1 className="text-4xl font-extrabold text-slate-900 mb-4 tracking-tight">{title}</h1>
<p className="text-lg text-slate-500 max-w-2xl mx-auto leading-relaxed">{summary}</p>
<div className="mt-6 flex justify-center">
<div className="h-1 w-24 bg-indigo-500 rounded-full"></div>
</div>
</div>
);
// PROCESS / TIMELINE Layout
if (type === 'PROCESS' || type === 'TIMELINE') {
return (
<div className="p-8 bg-slate-50 min-h-[600px] w-full" id="infographic-canvas">
<Header />
<div className="max-w-4xl mx-auto relative">
{/* Vertical Line */}
<div className="absolute left-1/2 top-0 bottom-0 w-1 bg-indigo-200 transform -translate-x-1/2 hidden md:block"></div>
<div className="space-y-12 relative">
{sections.map((section, idx) => (
<div key={idx} className={`flex items-center gap-8 ${idx % 2 === 0 ? 'md:flex-row' : 'md:flex-row-reverse'} flex-col`}>
{/* Content Card */}
<div className="flex-1 w-full">
<div className="bg-white p-6 rounded-2xl shadow-sm border border-slate-100 hover:shadow-md transition-shadow relative overflow-hidden">
<div className="absolute top-0 left-0 w-2 h-full bg-indigo-500"></div>
<h3 className="text-xl font-bold text-slate-800 mb-3">{section.title}</h3>
<ul className="space-y-2">
{section.points.map((pt, i) => (
<li key={i} className="flex items-start gap-2 text-slate-600 text-sm">
<span className="mt-1.5 w-1.5 h-1.5 bg-indigo-400 rounded-full shrink-0"></span>
{pt}
</li>
))}
</ul>
</div>
</div>
{/* Icon Marker */}
<div className="relative z-10 shrink-0">
<div className="w-16 h-16 rounded-full bg-white border-4 border-indigo-100 flex items-center justify-center text-indigo-600 shadow-sm">
<DynamicIcon name={section.icon} size={28} />
</div>
</div>
{/* Spacer */}
<div className="flex-1 hidden md:block"></div>
</div>
))}
</div>
</div>
{/* Branding */}
<div className="mt-16 text-center text-slate-400 text-sm font-medium uppercase tracking-widest">
Generated by Vertical.ai
</div>
</div>
);
}
// DEFAULT: OVERVIEW Grid Layout
return (
<div className="p-12 bg-white min-h-[600px] w-full" id="infographic-canvas">
<Header />
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
{sections.map((section, idx) => (
<div key={idx} className="bg-slate-50 rounded-2xl p-8 border border-slate-100 flex flex-col items-start hover:bg-indigo-50/30 transition-colors">
<div className="w-12 h-12 rounded-xl bg-indigo-100 text-indigo-600 flex items-center justify-center mb-6">
<DynamicIcon name={section.icon} size={24} />
</div>
<h3 className="text-xl font-bold text-slate-900 mb-4">{section.title}</h3>
<ul className="space-y-3 w-full">
{section.points.map((pt, i) => (
<li key={i} className="flex items-start gap-3 text-slate-600">
<CheckCircle size={16} className="text-indigo-400 mt-1 shrink-0" />
<span className="text-sm font-medium">{pt}</span>
</li>
))}
</ul>
</div>
))}
</div>
<div className="mt-16 text-center text-slate-400 text-sm font-medium uppercase tracking-widest border-t border-slate-100 pt-8">
Research Summary • Vertical.ai
</div>
</div>
);
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
<div className="bg-slate-100 rounded-2xl shadow-2xl w-full max-w-[90vw] h-[90vh] flex flex-col animate-in fade-in zoom-in duration-200 overflow-hidden">
{/* Header */}
<div className="p-4 bg-white border-b border-slate-200 flex items-center justify-between shrink-0">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-indigo-600 flex items-center justify-center text-white">
<Layout size={20} />
</div>
<div>
<h2 className="text-lg font-bold text-slate-900">Infographic Generator</h2>
<p className="text-sm text-slate-500">Visual Insights</p>
</div>
</div>
<div className="flex items-center gap-2">
<button onClick={() => setScale(s => Math.max(0.5, s - 0.1))} className="p-2 hover:bg-slate-100 rounded-lg"><ZoomOut size={20} /></button>
<span className="text-xs font-mono w-12 text-center">{Math.round(scale * 100)}%</span>
<button onClick={() => setScale(s => Math.min(2, s + 0.1))} className="p-2 hover:bg-slate-100 rounded-lg"><ZoomIn size={20} /></button>
<div className="h-6 w-px bg-slate-200 mx-2"></div>
<button onClick={handleDownload} disabled={status !== 'ready'} className="flex items-center gap-2 px-4 py-2 bg-slate-900 text-white rounded-lg hover:bg-slate-800 transition-colors disabled:opacity-50">
<Download size={18} /> Export PNG
</button>
<button onClick={onClose} className="p-2 hover:bg-slate-100 rounded-full text-slate-400 ml-2">
<X size={20} />
</button>
</div>
</div>
{/* Canvas Area */}
<div className="flex-1 overflow-auto bg-slate-200 p-8 flex items-start justify-center">
{status === 'loading' && (
<div className="flex flex-col items-center justify-center h-full text-slate-500">
<Loader2 size={48} className="animate-spin text-indigo-500 mb-6" />
<h3 className="text-xl font-medium text-slate-800">Designing Layout...</h3>
<p className="mt-2">Analyzing structure and composing visuals.</p>
</div>
)}
{status === 'error' && (
<div className="flex flex-col items-center justify-center h-full text-red-500">
<p>Failed to generate infographic.</p>
<button onClick={handleGenerate} className="mt-4 underline flex items-center gap-2"><RefreshCw size={16}/> Retry</button>
</div>
)}
{status === 'ready' && (
<div
style={{ transform: `scale(${scale})`, transformOrigin: 'top center' }}
className="transition-transform duration-200 shadow-2xl"
>
{renderLayout()}
</div>
)}
</div>
</div>
</div>
);
};
export default InfographicModal;