import React, { useState, useEffect, useRef } from 'react'; import { FileText, Plus, History, Settings, User, ChevronRight, Sparkles, Code, MessageSquare, Briefcase, Download, Copy, Check, AlertCircle, ArrowLeft, Trash2, Cloud, Sun, Moon } from 'lucide-react'; // Firebase Imports import { initializeApp } from 'firebase/app'; import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, doc, setDoc, onSnapshot, deleteDoc } from 'firebase/firestore'; // --- UTILITIES & API --- const apiKey = ""; // Provided by execution environment // Firebase Setup const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // Exponential backoff fetcher for Gemini API const fetchWithRetry = async (url, options, maxRetries = 5) => { let delays = [1000, 2000, 4000, 8000, 16000]; for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { if (i === maxRetries - 1) throw error; await new Promise(res => setTimeout(res, delays[i])); } } }; const generatePRDWithAI = async (formData) => { const systemPrompt = `You are an expert Product Manager and Software Architect. Your job is to transform raw software ideas into structured, engineering-ready Product Requirements Documents (PRDs). Follow these rules: 1. Output ONLY in Markdown format. 2. Be highly specific and technical, avoiding fluff. 3. You MUST include exactly these sections: - 1. Overview (Problem Statement & Core Value) - 2. Target Audience - 3. MVP Feature Scope (Bullet points of core features) - 4. User Flow (Step-by-step) - 5. System Architecture (Mermaid Sequence Diagram using \`\`\`mermaid) - 6. Database Schema (Mermaid ERD using \`\`\`mermaid) - 7. Technical & Design Constraints 4. Write the PRD in the requested language.`; const userPrompt = ` Please generate a PRD based on the following input: - Product Name: ${formData.productName || 'Untitled Project'} - Raw Idea: ${formData.rawIdea} - Target User: ${formData.targetUser || 'General users'} - Platform: ${formData.platform} - Scope Level: ${formData.scopeLevel} - Preferred Language: ${formData.language} - Detail Level: ${formData.detailLevel} `; const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: userPrompt }] }], systemInstruction: { parts: [{ text: systemPrompt }] } }; const data = await fetchWithRetry(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); return data.candidates?.[0]?.content?.parts?.[0]?.text || "Generation failed. Please try again."; }; // Dynamic Script Loader const loadScript = (src, globalVar) => { return new Promise((resolve, reject) => { if (window[globalVar]) return resolve(window[globalVar]); const script = document.createElement('script'); script.src = src; script.onload = () => resolve(window[globalVar]); script.onerror = reject; document.head.appendChild(script); }); }; // --- GLOBAL STYLES (Roboto Font) --- const GlobalStyles = () => ( ); // --- COMPONENTS --- const Button = ({ children, variant = 'primary', className = '', isLoading, icon: Icon, ...props }) => { const baseStyle = "inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"; const variants = { // Light mode uses orange, Dark mode uses emerald primary: "bg-orange-600 text-white hover:bg-orange-700 focus:ring-orange-500 shadow-sm dark:bg-emerald-600 dark:hover:bg-emerald-500 dark:text-white dark:focus:ring-emerald-500", secondary: "bg-white text-slate-700 border border-slate-300 hover:bg-slate-50 focus:ring-orange-500 shadow-sm dark:bg-slate-800 dark:text-slate-200 dark:border-emerald-800/50 dark:hover:bg-slate-700 dark:focus:ring-emerald-500", ghost: "text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100", danger: "bg-red-50 text-red-600 hover:bg-red-100 focus:ring-red-500 dark:bg-red-900/30 dark:text-red-400 dark:hover:bg-red-900/50 dark:focus:ring-red-500" }; return ( ); }; const Input = ({ label, id, error, className = '', ...props }) => (
{error}
}{error}
}Error rendering content.
${content}`);
}
};
renderContent();
return () => { isMounted = false; };
}, [content, theme]);
useEffect(() => {
if (html && window.mermaid && containerRef.current) {
const codeBlocks = containerRef.current.querySelectorAll('code.language-mermaid');
codeBlocks.forEach((block, index) => {
const source = block.textContent;
const div = document.createElement('div');
div.className = 'mermaid my-6 flex justify-center bg-slate-50 dark:bg-slate-900/50 p-4 rounded-xl border border-slate-100 dark:border-slate-800 overflow-x-auto';
div.id = `mermaid-${Date.now()}-${index}`;
div.textContent = source;
const pre = block.parentNode;
pre.parentNode.replaceChild(div, pre);
});
try {
window.mermaid.run({ querySelector: '.mermaid' });
} catch (e) {
console.warn("Mermaid parsing error:", e);
}
}
}, [html]);
return (
);
};
// --- MAIN APP COMPONENT ---
export default function App() {
const [theme, setTheme] = useState('dark');
const [currentView, setCurrentView] = useState('home');
const [projects, setProjects] = useState([]);
const [activeProject, setActiveProject] = useState(null);
const [user, setUser] = useState(null);
const [toast, setToast] = useState(null);
const showToast = (message, type = 'success') => setToast({ message, type });
const toggleTheme = () => setTheme(prev => prev === 'dark' ? 'light' : 'dark');
const navigate = (view, project = null) => {
if (project) setActiveProject(project);
setCurrentView(view);
};
// Firebase Auth Init
useEffect(() => {
const initAuth = async () => {
try {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
} catch (error) {
console.error("Auth error:", error);
}
};
initAuth();
const unsubscribe = onAuthStateChanged(auth, setUser);
return () => unsubscribe();
}, []);
// Firebase Data Sync
useEffect(() => {
if (!user) return;
const projectsRef = collection(db, 'artifacts', appId, 'users', user.uid, 'projects');
const unsubscribe = onSnapshot(projectsRef, (snapshot) => {
const loadedProjects = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
loadedProjects.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
setProjects(loadedProjects);
}, (error) => {
console.error("Firestore sync error:", error);
showToast("Gagal sinkronisasi dari Cloud.", "error");
});
return () => unsubscribe();
}, [user]);
// Handlers for Firestore Operations
const handleAddProject = async (project) => {
setProjects(prev => [project, ...prev]);
navigate('editor', project);
if (!user) return;
try {
await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'projects', project.id), project);
} catch (err) {
console.error(err);
showToast("Gagal menyimpan ke Cloud.", "error");
}
};
const handleUpdateProject = async (updatedProject) => {
setActiveProject(updatedProject);
setProjects(prev => prev.map(p => p.id === updatedProject.id ? updatedProject : p));
showToast("PRD berhasil disimpan!");
if (!user) return;
try {
await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'projects', updatedProject.id), updatedProject, { merge: true });
} catch (err) {
console.error(err);
showToast("Gagal update ke Cloud.", "error");
}
};
const handleDeleteProject = async (id) => {
setProjects(prev => prev.filter(p => p.id !== id));
showToast("Project dihapus.");
if (!user) return;
try {
await deleteDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'projects', id));
} catch (err) {
console.error(err);
}
};
return (
Ubah ide mentah aplikasi kamu menjadi Product Requirements Document (PRD) yang elegan, terstruktur, dan siap dikerjakan.
Ceritakan idemu, AI akan merapikannya.
Kelola dan lihat daftar dokumen PRD yang pernah kamu buat (Tersinkronisasi ke Cloud).
Kamu belum membuat PRD apapun. Mulai ceritakan ide aplikasimu dan biarkan AI menyusunnya.
{project.brief}