import React, { useState, useEffect, useRef, useMemo } from 'react'; import { ShoppingCart, Search, Plus, Minus, Trash2, X, Send, Store, Settings, Package, Save, Edit3, Upload, Camera, Loader2, Globe, AlertTriangle, CheckCircle2, Image as ImageIcon, Palette, Lock, Key, ChevronLeft, BarChart3, TrendingUp, MousePointerClick, Tag, ShoppingBag, History, RefreshCw, Zap, Eye, ChevronRight, Info } from 'lucide-react'; // Firebase Imports import { initializeApp } from 'firebase/app'; import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, doc, setDoc, getDoc, collection, onSnapshot, addDoc, updateDoc, deleteDoc, query, increment, serverTimestamp } from 'firebase/firestore'; // --- INITIALIZE FIREBASE SAFELY --- const appId = typeof __app_id !== 'undefined' ? __app_id : 'katalog-ultra-pro-v5'; let db, auth; try { // Safe parsing untuk mencegah ReferenceError jika environment belum siap const firebaseConfigString = typeof __firebase_config !== 'undefined' ? __firebase_config : '{}'; const firebaseConfig = JSON.parse(firebaseConfigString); if (firebaseConfig.apiKey) { const app = initializeApp(firebaseConfig); auth = getAuth(app); db = getFirestore(app); } } catch (e) { console.error("Firebase Initialization Error:", e); } const THEMES = { whatsapp: { name: 'Emerald', color: 'bg-emerald-600', text: 'text-emerald-600', border: 'border-emerald-600', light: 'bg-emerald-50', ring: 'ring-emerald-500', shadow: 'shadow-emerald-100' }, professional: { name: 'Ocean', color: 'bg-blue-600', text: 'text-blue-600', border: 'border-blue-600', light: 'bg-blue-50', ring: 'ring-blue-500', shadow: 'shadow-blue-100' }, fashion: { name: 'Rose', color: 'bg-rose-500', text: 'text-rose-500', border: 'border-rose-500', light: 'bg-rose-50', ring: 'ring-rose-500', shadow: 'shadow-rose-100' }, luxury: { name: 'Amber', color: 'bg-amber-600', text: 'text-amber-600', border: 'border-amber-600', light: 'bg-amber-50', ring: 'ring-amber-500', shadow: 'shadow-amber-100' }, modern: { name: 'Dark', color: 'bg-zinc-900', text: 'text-zinc-900', border: 'border-zinc-900', light: 'bg-zinc-100', ring: 'ring-zinc-900', shadow: 'shadow-zinc-200' } }; const CATEGORIES = ["Semua", "Pakaian", "Makanan & Minuman", "Elektronik", "Aksesoris", "Kecantikan", "Lainnya"]; export default function App() { const [user, setUser] = useState(null); const [view, setView] = useState('catalog'); const [adminSubView, setAdminSubView] = useState('dashboard'); const [loading, setLoading] = useState(true); const [notification, setNotification] = useState(null); // Modals & States const [isAdminAuth, setIsAdminAuth] = useState(false); const [showAuthModal, setShowAuthModal] = useState(false); const [inputPassword, setInputPassword] = useState(""); const [selectedProduct, setSelectedProduct] = useState(null); const [itemToDelete, setItemToDelete] = useState(null); const [activeCategory, setActiveCategory] = useState("Semua"); const [storeConfig, setStoreConfig] = useState({ name: "Toko Bisnis Online", phone: "6281234567890", currency: "Rp", theme: "whatsapp", logo: "", adminPassword: "1234" }); const [stats, setStats] = useState({ checkoutCount: 0, totalRevenue: 0 }); const [products, setProducts] = useState([]); const [orders, setOrders] = useState([]); const [cart, setCart] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [tempGallery, setTempGallery] = useState([]); const fileInputRef = useRef(null); const logoInputRef = useRef(null); const activeTheme = useMemo(() => THEMES[storeConfig?.theme] || THEMES.whatsapp, [storeConfig?.theme]); const showToast = (msg, type = 'success') => { setNotification({ msg, type }); setTimeout(() => setNotification(null), 3000); }; // 1. Auth Setup useEffect(() => { const initAuth = async () => { if (!auth) return; try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } } catch (err) { console.error("Auth Fail:", err); } }; initAuth(); const unsub = onAuthStateChanged(auth, u => setUser(u)); return () => unsub(); }, []); // 2. Data Synchronization useEffect(() => { if (!user || !db) return; // Config Sync const configRef = doc(db, 'artifacts', appId, 'public', 'data', 'settings', 'config'); const unsubConfig = onSnapshot(configRef, (ds) => { if (ds.exists()) { setStoreConfig(prev => ({ ...prev, ...ds.data() })); } else { // Init config if missing setDoc(configRef, storeConfig, { merge: true }); } }); // Stats Sync const statsRef = doc(db, 'artifacts', appId, 'public', 'data', 'analytics', 'stats'); const unsubStats = onSnapshot(statsRef, (ds) => { if (ds.exists()) setStats(ds.data()); }); // Products Sync const productsRef = collection(db, 'artifacts', appId, 'public', 'data', 'products'); const unsubProducts = onSnapshot(productsRef, (sn) => { setProducts(sn.docs.map(d => ({ id: d.id, ...d.data() }))); }); // Orders Sync const ordersRef = collection(db, 'artifacts', appId, 'public', 'data', 'orders'); const unsubOrders = onSnapshot(ordersRef, (sn) => { const items = sn.docs.map(d => ({ id: d.id, ...d.data() })); // Create a new array and sort to avoid state mutation setOrders([...items].sort((a, b) => (Number(b.createdAt?.seconds) || 0) - (Number(a.createdAt?.seconds) || 0))); setLoading(false); }, () => setLoading(false)); return () => { unsubConfig(); unsubStats(); unsubProducts(); unsubOrders(); }; }, [user]); // --- SAFE ANALYTICS --- // The fix for the blank screen is here: we use a Memoized array that copies products // before sorting, preventing React from crashing due to in-place mutation. const topProducts = useMemo(() => { if (!products.length) return []; return [...products] .sort((a, b) => (Number(b.salesCount) || 0) - (Number(a.salesCount) || 0)) .slice(0, 10); }, [products]); const monthChartData = useMemo(() => { if (!orders.length) return []; const months = {}; orders.forEach(o => { if (!o.createdAt?.seconds) return; // Prevent latency crash const d = new Date(o.createdAt.seconds * 1000); const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; months[key] = (months[key] || 0) + (Number(o.totalAmount) || 0); }); return Object.entries(months).sort((a, b) => a[0].localeCompare(b[0])).slice(-6); }, [orders]); // --- HANDLERS --- const handleCheckout = async () => { if (!cart.length || !user || !db) return; const cartTotal = cart.reduce((s, i) => s + (Number(i.price || 0) * i.quantity), 0); try { showToast("Memproses Pesanan...", "info"); // Save Order await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'orders'), { items: cart.map(i => ({ name: i.name, quantity: i.quantity, price: Number(i.price) })), totalAmount: cartTotal, createdAt: serverTimestamp() }); // Update Analytics const statsRef = doc(db, 'artifacts', appId, 'public', 'data', 'analytics', 'stats'); await setDoc(statsRef, { checkoutCount: increment(1), totalRevenue: increment(cartTotal) }, { merge: true }); // Update Sales Count per Product for (const item of cart) { await setDoc(doc(db, 'artifacts', appId, 'public', 'data', 'products', item.id), { salesCount: increment(item.quantity) }, { merge: true }); } // Open WhatsApp let msg = `*ORDER BARU - ${storeConfig.name.toUpperCase()}*\n\n`; cart.forEach((i, idx) => msg += `${idx + 1}. *${i.name}* (${i.quantity}x)\n`); msg += `\n*TOTAL: ${storeConfig.currency} ${cartTotal.toLocaleString('id-ID')}*`; window.open(`https://wa.me/${storeConfig.phone}?text=${encodeURIComponent(msg)}`, '_blank'); setCart([]); setView('catalog'); } catch (e) { showToast("Gagal Checkout", "error"); } }; const processDelete = async () => { if (!user || !db || !itemToDelete) return; if (inputPassword !== storeConfig.adminPassword) return showToast("Sandi Salah", "error"); try { const { type, data } = itemToDelete; setItemToDelete(null); setInputPassword(""); if (type === 'order') { const statsRef = doc(db, 'artifacts', appId, 'public', 'data', 'analytics', 'stats'); await setDoc(statsRef, { totalRevenue: increment(-Number(data.totalAmount || 0)), checkoutCount: increment(-1) }, { merge: true }); await deleteDoc(doc(db, 'artifacts', appId, 'public', 'data', 'orders', data.id)); showToast("Transaksi Dihapus"); } else { await deleteDoc(doc(db, 'artifacts', appId, 'public', 'data', 'products', data.id)); showToast("Produk Dihapus"); } } catch (e) { showToast("Gagal Hapus", "error"); } }; const handleUpdateConfig = async (newConfig) => { if (!user || !db) return; try { const configRef = doc(db, 'artifacts', appId, 'public', 'data', 'settings', 'config'); await setDoc(configRef, newConfig, { merge: true }); showToast("Tersimpan"); } catch (e) { showToast("Gagal Simpan", "error"); } }; const processImage = (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); const MAX_W = 800; const scale = Math.min(1, MAX_W / img.width); canvas.width = img.width * scale; canvas.height = img.height * scale; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); resolve(canvas.toDataURL('image/jpeg', 0.6)); }; img.onerror = reject; img.src = e.target.result; }; reader.onerror = reject; reader.readAsDataURL(file); }); }; const handleLogoUpload = async (e) => { const file = e.target.files?.[0]; if (!file) return; showToast("Mengunggah Logo...", "info"); try { const base64 = await processImage(file); const newConfig = { ...storeConfig, logo: base64 }; setStoreConfig(newConfig); await handleUpdateConfig(newConfig); showToast("Logo Toko Diperbarui"); } catch (err) { showToast("Gagal Unggah", "error"); } }; const handleMultipleFiles = async (e) => { const files = Array.from(e.target.files); if (!files.length) return; showToast("Memproses Foto...", "info"); try { const processed = []; for (const file of files) { const base64 = await processImage(file); processed.push(base64); } setTempGallery(prev => [...prev, ...processed].slice(0, 3)); } catch (err) { showToast("Gagal Foto", "error"); } }; if (loading) return (

Syncing Cloud...

); return (
{/* GLOBAL TOAST */} {notification && (
{notification.msg}
)} {/* HEADER PREMIUM */}
{/* --- CATALOG VIEW --- */} {view === 'catalog' && (
{/* Search */}
setSearchQuery(e.target.value)} />
{/* Horizontal Categories */}
{CATEGORIES.map(c => ( ))}
{/* Product Grid */}
{products .filter(p => (activeCategory === "Semua" || p.category === activeCategory)) .filter(p => (p.name || "").toLowerCase().includes(searchQuery.toLowerCase())) .map(p => { const discount = p.originalPrice > p.price ? Math.round(((p.originalPrice - p.price) / p.originalPrice) * 100) : 0; return (
setSelectedProduct(p)} className="bg-white rounded-[2rem] overflow-hidden border border-slate-100 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all cursor-pointer group flex flex-col">
{p.image ? {p.name} :
}
{p.isFlashSale &&
Sale
} {discount > 0 &&
-{discount}%
}

{p.category || "General"}

Rp {Number(p.price || 0).toLocaleString()}

{p.name}

0 ? 'text-slate-400' : 'text-red-500'}`}>{Number(p.stock) > 0 ? `Stok ${p.stock}` : "Habis"}
); })}
{products.length === 0 && (

Katalog Kosong

)}
)} {/* --- PRODUCT DETAIL MODAL --- */} {selectedProduct && (
{(selectedProduct?.gallery?.length > 0 ? selectedProduct.gallery : [selectedProduct.image || ""]).map((img, i) => ( gallery e.target.src="https://via.placeholder.com/800"} /> ))} {selectedProduct?.gallery?.length > 1 && (
{selectedProduct.gallery.map((_, i) =>
)}
)}
{selectedProduct?.category || "Umum"}

{selectedProduct?.name}

{selectedProduct?.isFlashSale &&
}

Rp {Number(selectedProduct?.price || 0).toLocaleString()}

{Number(selectedProduct?.originalPrice) > Number(selectedProduct?.price) && (

Rp {Number(selectedProduct.originalPrice).toLocaleString()}

)}

Deskripsi

{selectedProduct?.description || "Kualitas premium untuk Anda."}

Stok Unit

{selectedProduct?.stock || 0}

Total Laris

{selectedProduct?.salesCount || 0}+

{/* Sticky Add to Cart */}
)} {/* --- CART VIEW --- */} {view === 'cart' && (

Shopping Bag

{cart.length === 0 ? (

Kosong

) : (
{cart.map(item => (
c

{item.name}

Rp {Number(item.price || 0).toLocaleString()}

{item.quantity}
))}
Total Bayar Rp {cart.reduce((s,i)=>s+(i.price*i.quantity),0).toLocaleString()}
)}
)} {/* --- ADMIN HUB --- */} {view === 'admin' && (

Admin Hub

{[ {id: 'dashboard', icon: BarChart3}, {id: 'orders', icon: History}, {id: 'inventory', icon: Package}, {id: 'settings', icon: Settings} ].map(m => ( ))}
{/* DASHBOARD */} {adminSubView === 'dashboard' && (

Total Omzet Cloud

Rp {Number(stats?.totalRevenue || 0).toLocaleString()}

Konversi Checkout

{stats?.checkoutCount || 0}

Grafik Bulanan

{monthChartData.length === 0 ?

Belum Ada Data

: monthChartData.map(([m, a]) => (
d[1])) || 1)) * 100}%`}}>
{m}
))}

Top 10 Best Sellers

{topProducts.map((p, i) => (
#{i+1} t {p.name}
{p.salesCount || 0} PCS
))}
)} {/* ORDERS */} {adminSubView === 'orders' && (

History Pesanan

Data Tersinkronisasi

{orders.map(o => ( ))}
DateAmountDelete
{o.createdAt?.seconds ? new Date(o.createdAt.seconds * 1000).toLocaleDateString('id-ID', {day:'2-digit', month:'short'}) : '...'} Rp {Number(o.totalAmount || 0).toLocaleString()}
{!orders.length &&
Belum Ada Transaksi
}
)} {/* INVENTORY */} {adminSubView === 'inventory' && (
{products.map(p => (
t

{p.name}

Stok: {p.stock} | Terjual: {p.salesCount || 0}

))}
)} {/* SETTINGS */} {adminSubView === 'settings' && (

Identity Setup

setStoreConfig(prev => ({...prev, name: e.target.value}))} onBlur={() => handleUpdateConfig({name: storeConfig.name})} />
setStoreConfig(prev => ({...prev, phone: e.target.value}))} onBlur={() => handleUpdateConfig({phone: storeConfig.phone})} />
setStoreConfig(prev => ({...prev, adminPassword: e.target.value}))} onBlur={() => handleUpdateConfig({adminPassword: storeConfig.adminPassword})} />

Store Branding

logoInputRef.current.click()} className="w-32 h-32 bg-slate-50 rounded-[2.5rem] border-4 border-dashed border-slate-200 flex flex-col items-center justify-center cursor-pointer overflow-hidden group relative transition-all hover:border-slate-400"> {storeConfig?.logo ? l :

Logo

}
Ganti Image
{Object.keys(THEMES).map(t =>
)}
)}
{/* --- AUTH MODAL --- */} {showAuthModal && (

Admin Login

{ e.preventDefault(); if(inputPassword === storeConfig?.adminPassword) { setIsAdminAuth(true); setShowAuthModal(false); setView('admin'); setInputPassword(""); } else { showToast("Sandi Salah", "error"); setInputPassword(""); } }} className="space-y-6 mt-8"> setInputPassword(e.target.value)} />
)} {/* --- DYNAMIC MODAL (EDIT & DELETE) --- */} {itemToDelete && (
{itemToDelete.type === 'edit' ? (

{itemToDelete.data ? 'PERBARUI' : 'TAMBAH'} PRODUK

{ e.preventDefault(); const fd = new FormData(e.target); const data = { name: fd.get('name') || "Produk", price: Number(fd.get('price')) || 0, originalPrice: Number(fd.get('originalPrice')) || 0, stock: Number(fd.get('stock')) || 0, category: fd.get('category') || CATEGORIES[1], isFlashSale: fd.get('isFlashSale') === 'true', description: fd.get('description') || "", gallery: tempGallery.length > 0 ? tempGallery : [itemToDelete.data?.image || ""], image: tempGallery[0] || itemToDelete.data?.image || "", updatedAt: serverTimestamp() }; try { if(itemToDelete.data?.id) await setDoc(doc(db, 'artifacts', appId, 'public', 'data', 'products', itemToDelete.data.id), data, {merge: true}); else await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'products'), {...data, salesCount: 0}); setItemToDelete(null); setTempGallery([]); showToast("Tersimpan!"); } catch(err) { showToast("Gagal Simpan", "error"); } }} className="p-10 space-y-6 overflow-y-auto custom-scrollbar" >
{tempGallery.map((img, i) => (
))} {tempGallery.length < 3 &&
fileInputRef.current?.click()} className="aspect-square border-4 border-dashed border-slate-100 rounded-[1.5rem] flex items-center justify-center text-slate-200 cursor-pointer hover:bg-slate-50 transition-all">
}
) : (

Hapus Permanen?

"Masukkan sandi untuk konfirmasi penghapusan cloud."

{e.preventDefault(); processDelete();}} className="space-y-6"> setInputPassword(e.target.value)} />
)}
)} {/* CSS UTILITIES */}
); }