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 (
);
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.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 && (
)}
)}
{/* --- PRODUCT DETAIL MODAL --- */}
{selectedProduct && (
{(selectedProduct?.gallery?.length > 0 ? selectedProduct.gallery : [selectedProduct.image || ""]).map((img, i) => (

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 ? (
) : (
{cart.map(item => (
{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}
{p.name}
{p.salesCount || 0} PCS
))}
)}
{/* ORDERS */}
{adminSubView === 'orders' && (
History Pesanan
Data Tersinkronisasi
| Date | Amount | Delete |
{orders.map(o => (
|
{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 => (
{p.name}
Stok: {p.stock} | Terjual: {p.salesCount || 0}
))}
)}
{/* SETTINGS */}
{adminSubView === 'settings' && (
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 ?

:
}
Ganti Image
{Object.keys(THEMES).map(t =>
)}
)}
{/* --- AUTH MODAL --- */}
{showAuthModal && (
)}
{/* --- DYNAMIC MODAL (EDIT & DELETE) --- */}
{itemToDelete && (
{itemToDelete.type === 'edit' ? (
{itemToDelete.data ? 'PERBARUI' : 'TAMBAH'} PRODUK
) : (
Hapus Permanen?
"Masukkan sandi untuk konfirmasi penghapusan cloud."
)}
)}
{/* CSS UTILITIES */}
);
}