import React, { useState, useEffect, useRef } from 'react';
import {
Presentation,
Upload,
Type,
LayoutTemplate,
FileText,
Download,
Loader2,
Image as ImageIcon,
CheckCircle2,
AlertCircle,
Trash2,
Eye,
X
} from 'lucide-react';
export default function App() {
// State untuk form input
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [template, setTemplate] = useState('minimalist');
const [font, setFont] = useState('Arial');
const [logoBase64, setLogoBase64] = useState(null);
// State untuk Ukuran Tulisan
const [titleFontSize, setTitleFontSize] = useState(44);
const [slideTitleFontSize, setSlideTitleFontSize] = useState(32);
const [contentFontSize, setContentFontSize] = useState(18);
// State untuk UI/UX
const [isGenerating, setIsGenerating] = useState(false);
const [scriptLoaded, setScriptLoaded] = useState(false);
const [message, setMessage] = useState({ type: '', text: '' });
// State untuk Preview
const [showPreview, setShowPreview] = useState(false);
const [parsedSlides, setParsedSlides] = useState([]);
const fileInputRef = useRef(null);
// Daftar Pilihan
const templates = [
{ id: 'minimalist', name: 'Minimalis (Putih)', bg: 'FFFFFF', text: '333333', accent: 'E2E8F0' },
{ id: 'modern_dark', name: 'Gelap Modern', bg: '1E293B', text: 'F8FAFC', accent: '3B82F6' },
{ id: 'corporate', name: 'Korporat (Biru)', bg: 'F0F9FF', text: '0F172A', accent: '0284C7' },
];
const fonts = [
{ id: 'Arial', name: 'Arial (Modern Sans)' },
{ id: 'Times New Roman', name: 'Times New Roman (Klasik Serif)' },
{ id: 'Courier New', name: 'Courier New (Monospace)' },
{ id: 'Verdana', name: 'Verdana (Lebar & Jelas)' },
];
// Memuat library PptxGenJS dari CDN secara asinkron
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/pptxgenjs@3.12.0/dist/pptxgen.bundle.js';
script.async = true;
script.onload = () => setScriptLoaded(true);
script.onerror = () => {
setMessage({ type: 'error', text: 'Gagal memuat pustaka generator PPT. Pastikan koneksi internet Anda aktif.' });
};
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, []);
// Handler untuk upload logo
const handleLogoUpload = (e) => {
const file = e.target.files[0];
if (file) {
if (file.size > 2000000) { // Limit 2MB
setMessage({ type: 'error', text: 'Ukuran logo maksimal 2MB' });
return;
}
const reader = new FileReader();
reader.onloadend = () => {
setLogoBase64(reader.result);
setMessage({ type: '', text: '' });
};
reader.readAsDataURL(file);
}
};
const removeLogo = () => {
setLogoBase64(null);
if (fileInputRef.current) fileInputRef.current.value = '';
};
// Fungsi untuk mem-parsing teks menjadi data slide untuk Preview
const handlePreview = () => {
if (!title.trim()) {
setMessage({ type: 'error', text: 'Judul presentasi tidak boleh kosong.' });
return;
}
setMessage({ type: '', text: '' });
const slides = [];
// Slide 1: Judul
slides.push({ type: 'title', title: title });
// Slide 2+: Content
if (content.trim()) {
const rawSlides = content.split(/\n\s*\n/).filter(s => s.trim() !== '');
rawSlides.forEach(slideText => {
const lines = slideText.trim().split('\n');
const slideTitle = lines[0];
const slideBodyText = lines.slice(1).join('\n');
const bulletPoints = slideBodyText.trim() ? slideBodyText.split('\n').filter(l => l.trim() !== '').map(l => l.replace(/^- /, '')) : [];
slides.push({ type: 'content', title: slideTitle, bullets: bulletPoints });
});
}
setParsedSlides(slides);
setShowPreview(true);
};
// Fungsi utama untuk generate PPT
const generatePPT = async () => {
if (!scriptLoaded || !window.PptxGenJS) {
setMessage({ type: 'error', text: 'Pustaka belum siap. Tunggu sebentar atau muat ulang halaman.' });
return;
}
if (!title.trim()) {
setMessage({ type: 'error', text: 'Judul presentasi tidak boleh kosong.' });
return;
}
setIsGenerating(true);
setMessage({ type: '', text: '' });
try {
// Inisialisasi PptxGenJS
const pres = new window.PptxGenJS();
pres.author = 'AI Presentation Generator';
pres.company = 'App';
pres.layout = 'LAYOUT_16x9';
// Dapatkan warna template
const selectedTemplate = templates.find(t => t.id === template) || templates[0];
const bgColor = selectedTemplate.bg;
const textColor = selectedTemplate.text;
const accentColor = selectedTemplate.accent;
// ================= SLIDE 1: JUDUL =================
let slide1 = pres.addSlide();
slide1.background = { color: bgColor };
// Tambahkan Logo jika ada (pojok kanan atas)
if (logoBase64) {
slide1.addImage({ data: logoBase64, x: 8.5, y: 0.5, w: 1.2, h: 1.2, sizing: { type: 'contain' } });
}
// Desain Judul (di tengah)
slide1.addText(title, {
x: '10%', y: '40%', w: '80%', h: 1.5,
fontSize: titleFontSize, bold: true, color: textColor, align: 'center', fontFace: font
});
// Garis aksen di bawah judul
slide1.addShape(pres.ShapeType.rect, { x: '40%', y: '60%', w: '20%', h: 0.05, fill: { color: accentColor } });
// ================= SLIDE 2+: MATERI =================
if (content.trim()) {
// Pisahkan materi berdasarkan double enter (paragraf kosong) untuk membuat slide baru
const slidesData = content.split(/\n\s*\n/).filter(s => s.trim() !== '');
slidesData.forEach((slideText) => {
let contentSlide = pres.addSlide();
contentSlide.background = { color: bgColor };
if (logoBase64) {
contentSlide.addImage({ data: logoBase64, x: 8.8, y: 0.3, w: 0.8, h: 0.8, sizing: { type: 'contain' } });
}
// Baris pertama teks dianggap sebagai Judul Slide, sisanya isi
const lines = slideText.trim().split('\n');
const slideTitle = lines[0];
const slideBodyText = lines.slice(1).join('\n');
// Header Slide
contentSlide.addText(slideTitle, {
x: 0.5, y: 0.5, w: '80%', h: 0.8,
fontSize: slideTitleFontSize, bold: true, color: textColor, fontFace: font
});
// Garis bawah header
contentSlide.addShape(pres.ShapeType.line, { x: 0.5, y: 1.4, w: '90%', h: 0, line: { color: accentColor, width: 1 } });
// Isi Slide (jika ada)
if (slideBodyText.trim()) {
// Ubah baris menjadi array string untuk format bullet points
const bulletPoints = slideBodyText.split('\n').filter(l => l.trim() !== '').map(l => l.replace(/^- /, ''));
contentSlide.addText(
bulletPoints.map(p => ({ text: p, options: { bullet: true } })),
{ x: 0.5, y: 1.6, w: '90%', h: 3.5, fontSize: contentFontSize, color: textColor, fontFace: font, valign: 'top' }
);
}
});
}
// Generate nama file
const fileName = `${title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_presentation.pptx`;
// Simpan file
await pres.writeFile({ fileName: fileName });
setMessage({ type: 'success', text: 'Presentasi berhasil dibuat dan diunduh!' });
} catch (error) {
console.error("Error generating PPT:", error);
setMessage({ type: 'error', text: 'Terjadi kesalahan saat memproses presentasi.' });
} finally {
setIsGenerating(false);
}
};
return (
)}
);
}
{/* Header */}
{/* Notifikasi Message */}
{message.text && (
{/* Modal Preview Seluruh Slide */}
{showPreview && (
Auto PPT Generator
Ubah teks dan ide Anda menjadi slide presentasi PowerPoint (.pptx) yang siap pakai hanya dalam hitungan detik.
{message.type === 'error' ? : }
)}
{message.text}
{/* Form Panel (Kiri) */}
{/* Preview Panel (Kanan) - Simulasi Tampilan */}
{/* Judul PPT */}
{/* Unggah Logo */}
{/* Footer Action */}
setTitle(e.target.value)}
placeholder="Contoh: Strategi Pemasaran Q3 2026"
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all outline-none"
/>
{/* Materi PPT */}
{/* Template PPT */}
{/* Model Tulisan */}
{/* Ukuran Tulisan */}
setTitleFontSize(Number(e.target.value))}
className="w-full px-3 py-2.5 rounded-lg border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all outline-none bg-white"
min="10" max="100"
/>
setSlideTitleFontSize(Number(e.target.value))}
className="w-full px-3 py-2.5 rounded-lg border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all outline-none bg-white"
min="10" max="100"
/>
setContentFontSize(Number(e.target.value))}
className="w-full px-3 py-2.5 rounded-lg border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all outline-none bg-white"
min="10" max="100"
/>
{!logoBase64 ? (
)}
fileInputRef.current?.click()}
>
) : (
Klik untuk mengunggah logo
PNG, JPG up to 2MB
{!scriptLoaded && (
Menyiapkan mesin pembuat PPT...
)}Preview Slide Judul
{/* Aspek rasio 16:9 untuk preview */}
t.id === template)?.bg}`,
color: `#${templates.find(t => t.id === template)?.text}`,
fontFamily: font
}}
>
{/* Logo Preview */}
{logoBase64 && (
)}
{/* Title Preview */}
t.id === template)?.text}`, fontSize: `${Math.max(16, titleFontSize * 0.7)}px` // Diskalakan sedikit untuk preview agar tidak menabrak kotak }} > {title || "Judul Presentasi Anda"}
{/* Accent Line */} t.id === template)?.accent}` }}
>
💡 Tips Penulisan Materi:
Pisahkan antar slide dengan 2x Enter (baris kosong).
Baris pertama pada setiap bagian akan otomatis dijadikan sebagai Judul Slide, dan baris berikutnya akan menjadi poin-poin isi slide.
{/* Modal Header */}
{/* Modal Body (Scrollable Slides) */}
Preview Seluruh Slide
{parsedSlides.map((slide, index) => (
))}
{/* Modal Footer */}
Slide {index + 1} {index === 0 && '(Judul Utama)'}
t.id === template)?.bg}`,
color: `#${templates.find(t => t.id === template)?.text}`,
fontFamily: font
}}
>
{/* Logo Preview */}
{logoBase64 && (
)}
{slide.type === 'title' ? (
) : (
)}
{slide.title}
t.id === template)?.accent}` }}
>
{slide.title}
t.id === template)?.accent}` }}
>
-
{slide.bullets.map((bullet, bIndex) => (
- {bullet} ))}