import React, { useState, useEffect, useMemo, useRef } from 'react'; import { PenSquare, LayoutDashboard, Edit3, Trash2, X, Play, Square, Camera, Sparkles, Loader2, AlertCircle, User, Crown, TrendingUp, Zap, Target, Trophy, CheckCircle2, Circle } from 'lucide-react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, doc, setDoc, deleteDoc, onSnapshot } from 'firebase/firestore'; // --- FIREBASE INITIALIZATION --- 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'; export default function App() { const [user, setUser] = useState(null); const [isProfileLoading, setIsProfileLoading] = useState(true); const [view, setView] = useState('log'); // 'log', 'vault', 'profile', 'trophy', 'edit' const [sessions, setSessions] = useState([]); const [legacyWins, setLegacyWins] = useState([]); const [includeLegacyInTotal, setIncludeLegacyInTotal] = useState(false); const [userProfile, setUserProfile] = useState(null); const [signupData, setSignupData] = useState({ email: '', gamblingName: '', homeCasino: '' }); const [successMsg, setSuccessMsg] = useState(''); const [errorMsg, setErrorMsg] = useState(''); const [editingId, setEditingId] = useState(null); const [isScanning, setIsScanning] = useState(false); // Timer State const [timerStart, setTimerStart] = useState(null); const [elapsedSeconds, setElapsedSeconds] = useState(0); // Form State const initialFormState = { location: '', gameType: 'Slots', specificGame: '', buyIn: '', cashOut: '', expensesFood: '', expensesPark: '', expensesOther: '', tips: '' }; const [formData, setFormData] = useState(initialFormState); const initialLegacyState = { title: '', amount: '', date: new Date().toISOString().split('T')[0], type: 'Jackpot' }; const [legacyForm, setLegacyForm] = useState(initialLegacyState); const fileInputRef = useRef(null); // --- FIREBASE AUTHENTICATION --- useEffect(() => { const initAuth = async () => { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } }; initAuth(); const unsubscribe = onAuthStateChanged(auth, setUser); return () => unsubscribe(); }, []); // --- FIRESTORE REAL-TIME SYNC --- useEffect(() => { if (!user) { setIsProfileLoading(true); return; } const profileRef = doc(db, 'artifacts', appId, 'users', user.uid, 'profile', 'data'); const sessionsRef = collection(db, 'artifacts', appId, 'users', user.uid, 'sessions'); const legacyRef = collection(db, 'artifacts', appId, 'users', user.uid, 'legacyWins'); const unsubProfile = onSnapshot(profileRef, (docSnap) => { if (docSnap.exists()) { setUserProfile(docSnap.data()); } else { setUserProfile(null); } setIsProfileLoading(false); }, (err) => { console.error("Profile sync error:", err); setIsProfileLoading(false); }); const unsubSessions = onSnapshot(sessionsRef, (snapshot) => { const data = snapshot.docs.map(d => ({ id: d.id, ...d.data() })); data.sort((a, b) => new Date(b.date) - new Date(a.date)); setSessions(data); }, (err) => console.error("Sessions sync error:", err)); const unsubLegacy = onSnapshot(legacyRef, (snapshot) => { const data = snapshot.docs.map(d => ({ id: d.id, ...d.data() })); data.sort((a, b) => { const dateA = a.date ? new Date(a.date).getTime() : new Date(a.year, 0, 1).getTime(); const dateB = b.date ? new Date(b.date).getTime() : new Date(b.year, 0, 1).getTime(); return dateB - dateA; }); setLegacyWins(data); }, (err) => console.error("Legacy wins sync error:", err)); return () => { unsubProfile(); unsubSessions(); unsubLegacy(); }; }, [user]); // --- TIMER LOGIC --- useEffect(() => { let interval; if (timerStart) { interval = setInterval(() => { setElapsedSeconds(Math.floor((Date.now() - timerStart) / 1000)); }, 1000); } return () => clearInterval(interval); }, [timerStart]); const toggleTimer = () => { if (!timerStart) { setTimerStart(Date.now() - elapsedSeconds * 1000); } else { setTimerStart(null); setElapsedSeconds(0); } }; const formatTime = (totalSeconds) => { const h = Math.floor(totalSeconds / 3600).toString().padStart(2, '0'); const m = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0'); const s = (totalSeconds % 60).toString().padStart(2, '0'); return `${h}:${m}:${s}`; }; // --- AI SCAN LOGIC --- const triggerCamera = () => { if (fileInputRef.current) { fileInputRef.current.click(); } }; const handleImageUpload = (e) => { const file = e.target.files[0]; if (!file) return; setIsScanning(true); setErrorMsg(''); const reader = new FileReader(); reader.onloadend = async () => { const base64Data = reader.result.split(',')[1]; await performAIScan(base64Data, file.type); }; reader.onerror = () => { setErrorMsg('Failed to read image file.'); setIsScanning(false); }; reader.readAsDataURL(file); }; const performAIScan = async (base64Data, mimeType) => { const apiKey = ""; // Runtime-supplied empty key placeholder const url = `https://generGenerativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`; const cleanUrl = url.replace("generGenerative", "generative"); const prompt = `Analyze this image of a casino floor, slot machine, video poker unit, or table game. 1. Identify the exact specific game name (e.g. "Buffalo Gold", "Dragon Link", "Ultimate Texas Hold'em", "Double Double Bonus Poker"). 2. Try to locate any session duration info, credit/cash values, or play stats on screen to estimate duration. Return ONLY JSON matching this structure: { "gameName": "Extracted Game Name", "estimatedDurationMinutes": null or number }`; const payload = { contents: [{ role: "user", parts: [ { text: prompt }, { inlineData: { mimeType: mimeType, data: base64Data } } ] }], generationConfig: { responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { gameName: { type: "STRING" }, estimatedDurationMinutes: { type: "INTEGER", nullable: true } }, required: ["gameName"] } } }; let retries = 5; let delay = 1000; while (retries > 0) { try { const res = await fetch(cleanUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error("API failed"); const data = await res.json(); const responseText = data.candidates?.[0]?.content?.parts?.[0]?.text; if (responseText) { const parsed = JSON.parse(responseText); if (parsed.gameName) { setFormData(prev => ({ ...prev, specificGame: parsed.gameName, gameType: (parsed.gameName.toLowerCase().includes('blackjack') || parsed.gameName.toLowerCase().includes('craps') || parsed.gameName.toLowerCase().includes('roulette') || parsed.gameName.toLowerCase().includes('baccarat') || parsed.gameName.toLowerCase().includes('poker') && !parsed.gameName.toLowerCase().includes('video')) ? 'Tables' : 'Slots' })); if (parsed.estimatedDurationMinutes) { setElapsedSeconds(parsed.estimatedDurationMinutes * 60); if (!timerStart) { setTimerStart(Date.now() - parsed.estimatedDurationMinutes * 60 * 1000); } } setSuccessMsg(`AI Scan complete: Loaded "${parsed.gameName}"!`); setTimeout(() => setSuccessMsg(''), 4000); } } break; } catch (err) { retries--; if (retries === 0) { setErrorMsg("AI Scan could not identify game. Please enter it manually below."); } else { await new Promise(resolve => setTimeout(resolve, delay)); delay *= 2; } } } setIsScanning(false); }; const uniqueLocations = useMemo(() => [...new Set(sessions.map(s => s.location).filter(Boolean))], [sessions]); const uniqueGames = useMemo(() => [...new Set(sessions.map(s => s.specificGame).filter(Boolean))], [sessions]); const stats = useMemo(() => { let baseProfit = 0; let totalMinutes = 0; let winningSessions = 0; let slotsProfit = 0; let tablesProfit = 0; let sportsProfit = 0; sessions.forEach(s => { baseProfit += s.profit; totalMinutes += s.duration; if (s.profit > 0) winningSessions++; if (s.gameType === 'Slots') slotsProfit += s.profit; if (s.gameType === 'Tables') tablesProfit += s.profit; if (s.gameType === 'Sports') sportsProfit += s.profit; }); let legacyTotal = 0; if (includeLegacyInTotal) { legacyWins.forEach(w => legacyTotal += w.amount); } const totalProfit = baseProfit + legacyTotal; const hourlyWage = totalMinutes > 0 ? (baseProfit / (totalMinutes / 60)) : 0; const winRate = sessions.length > 0 ? Math.round((winningSessions / sessions.length) * 100) : 0; return { totalProfit, totalMinutes, hourlyWage, winRate, slotsProfit, tablesProfit, sportsProfit }; }, [sessions, legacyWins, includeLegacyInTotal]); const gameRankings = useMemo(() => { const rankings = {}; sessions.forEach(s => { if (!rankings[s.specificGame]) { rankings[s.specificGame] = { name: s.specificGame, profit: 0, time: 0, count: 0, gameType: s.gameType }; } rankings[s.specificGame].profit += s.profit; rankings[s.specificGame].time += s.duration; rankings[s.specificGame].count += 1; }); return Object.values(rankings).sort((a, b) => b.profit - a.profit); }, [sessions]); const handleInputChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleLegacyChange = (e) => { const { name, value } = e.target; setLegacyForm(prev => ({ ...prev, [name]: value })); }; const calculateProfit = (data) => { const buy = parseFloat(data.buyIn) || 0; const out = parseFloat(data.cashOut) || 0; const expFood = parseFloat(data.expensesFood) || 0; const expPark = parseFloat(data.expensesPark) || 0; const expOther = parseFloat(data.expensesOther) || 0; const tips = parseFloat(data.tips) || 0; return out - buy - expFood - expPark - expOther - tips; }; // --- FIRESTORE WRITE HANDLERS --- const handleProfileSubmit = async (e) => { e.preventDefault(); if (!user) return; try { await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'profile', 'data'), signupData); setFormData(prev => ({ ...prev, location: signupData.homeCasino })); } catch (err) { console.error("Error saving profile:", err); } }; const handleSubmit = async (e) => { e.preventDefault(); if (!user) return; const profit = calculateProfit(formData); const durationMins = Math.ceil(elapsedSeconds / 60); const newSession = { id: Date.now().toString(), date: new Date().toISOString(), location: formData.location || 'Unknown Casino', gameType: formData.gameType, specificGame: formData.specificGame || 'Unknown', duration: durationMins, buyIn: parseFloat(formData.buyIn) || 0, cashOut: parseFloat(formData.cashOut) || 0, expensesFood: parseFloat(formData.expensesFood) || 0, expensesPark: parseFloat(formData.expensesPark) || 0, expensesOther: parseFloat(formData.expensesOther) || 0, tips: parseFloat(formData.tips) || 0, profit }; try { await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'sessions', newSession.id), newSession); setFormData({ ...initialFormState, location: userProfile?.homeCasino || '' }); setTimerStart(null); setElapsedSeconds(0); setSuccessMsg('Session Locked In. Good luck!'); setTimeout(() => setSuccessMsg(''), 3000); } catch (err) { console.error("Error saving session:", err); } }; const handleLegacySubmit = async (e) => { e.preventDefault(); if (!user) return; const newLegacy = { id: Date.now().toString(), title: legacyForm.title || 'Unknown Win', amount: parseFloat(legacyForm.amount) || 0, date: legacyForm.date, year: legacyForm.date ? legacyForm.date.split('-')[0] : new Date().getFullYear().toString(), type: legacyForm.type }; try { await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'legacyWins', newLegacy.id), newLegacy); setLegacyForm(initialLegacyState); setSuccessMsg('Legacy Win Added!'); setTimeout(() => setSuccessMsg(''), 3000); } catch (err) { console.error("Error saving legacy win:", err); } }; const handleEditSubmit = async (e) => { e.preventDefault(); if (!user) return; const profit = calculateProfit(formData); const updatedSession = { location: formData.location, gameType: formData.gameType, specificGame: formData.specificGame, buyIn: parseFloat(formData.buyIn) || 0, cashOut: parseFloat(formData.cashOut) || 0, expensesFood: parseFloat(formData.expensesFood) || 0, expensesPark: parseFloat(formData.expensesPark) || 0, expensesOther: parseFloat(formData.expensesOther) || 0, tips: parseFloat(formData.tips) || 0, profit }; try { await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'sessions', editingId), updatedSession, { merge: true }); setView('vault'); setEditingId(null); setFormData({ ...initialFormState, location: userProfile?.homeCasino || '' }); } catch (err) { console.error("Error updating session:", err); } }; const deleteSession = async (id) => { if (!user) return; if (window.confirm('Permanently delete this session? This cannot be undone.')) { try { await deleteDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'sessions', id)); if (view === 'edit') { setView('vault'); setEditingId(null); setFormData({ ...initialFormState, location: userProfile?.homeCasino || '' }); } } catch (err) { console.error("Error deleting session:", err); } } }; const deleteLegacy = async (id) => { if (!user) return; if (window.confirm('Permanently delete this legacy win?')) { try { await deleteDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'legacyWins', id)); } catch (err) { console.error("Error deleting legacy win:", err); } } }; const openEdit = (session) => { setFormData({ location: session.location, gameType: session.gameType, specificGame: session.specificGame, buyIn: session.buyIn, cashOut: session.cashOut, expensesFood: session.expensesFood || '', expensesPark: session.expensesPark || '', expensesOther: session.expensesOther || '', tips: session.tips || '' }); setEditingId(session.id); setView('edit'); }; const formatMoney = (val) => { const isNegative = val < 0; const formatted = Math.abs(val).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return isNegative ? `-$${formatted}` : `+$${formatted}`; }; const formatMoneyNoCents = (val) => { const isNegative = val < 0; const formatted = Math.abs(val).toLocaleString('en-US', { maximumFractionDigits: 0 }); return isNegative ? `-$${formatted}` : `+$${formatted}`; }; // --- RENDER LOADERS --- if (!user || isProfileLoading) { return (
I'm here to help you track wins and losses.
Provide a few details below to get started.
True Net Profit
+$4,250
Win Rate
68%
Hourly
+$85
True Net Profit {includeLegacyInTotal && W/ LEGACY}
Win Rate
{stats.winRate}%
Hourly Wage
= 0 ? 'text-emerald-400' : 'text-rose-400'}`}> {stats.hourlyWage >= 0 ? '+' : ''}${Math.abs(stats.hourlyWage).toFixed(2)}
🎰 Slots Edge
= 0 ? 'text-emerald-400' : 'text-rose-500'}`}>{formatMoneyNoCents(stats.slotsProfit)}
🃏 Tables Edge
= 0 ? 'text-emerald-400' : 'text-rose-500'}`}>{formatMoneyNoCents(stats.tablesProfit)}
🏈 Sports Edge
= 0 ? 'text-emerald-400' : 'text-rose-500'}`}>{formatMoneyNoCents(stats.sportsProfit)}
No sessions logged yet.
Home: {userProfile.homeCasino}
Data indicates your most powerful edge is playing {gameRankings[0].name}. You have generated a total yield of {formatMoney(gameRankings[0].profit)} in {gameRankings[0].count} sessions. Dedicate more of your bankroll here to maximize your ROI.
) : (Log more winning sessions. The Oracle needs more data to generate a statistically significant play recommendation for your edge.
)}No data to rank.
) : (Legacy Wins & Bragging Rights
No legacy wins recorded yet.