Spaces:
Running
Running
Update cointube.jsx
Browse files- cointube.jsx +57 -49
cointube.jsx
CHANGED
|
@@ -22,41 +22,9 @@ import {
|
|
| 22 |
limit
|
| 23 |
} from 'firebase/firestore';
|
| 24 |
|
| 25 |
-
// ---
|
| 26 |
let app, auth, db;
|
| 27 |
|
| 28 |
-
try {
|
| 29 |
-
// 1. Intentamos leer la variable de entorno
|
| 30 |
-
const configRaw = import.meta.env.VITE_FIREBASE_CONFIG;
|
| 31 |
-
|
| 32 |
-
let firebaseConfig;
|
| 33 |
-
|
| 34 |
-
if (configRaw) {
|
| 35 |
-
// 2. Si existe, la parseamos de JSON a Objeto
|
| 36 |
-
firebaseConfig = JSON.parse(configRaw);
|
| 37 |
-
} else {
|
| 38 |
-
// 3. Fallback de seguridad o error visible en consola
|
| 39 |
-
console.error("FALTA CONFIGURACIÓN: La variable VITE_FIREBASE_CONFIG está vacía.");
|
| 40 |
-
// Opcional: Valores por defecto hardcodeados si todo falla
|
| 41 |
-
firebaseConfig = {
|
| 42 |
-
apiKey: "AIzaSyCwJjVJGuOmA_PKRbaTnQDrK-Q07NI_utc",
|
| 43 |
-
authDomain: "insights-5c2d6.firebaseapp.com",
|
| 44 |
-
projectId: "insights-5c2d6",
|
| 45 |
-
storageBucket: "insights-5c2d6.firebasestorage.app",
|
| 46 |
-
messagingSenderId: "61805724903",
|
| 47 |
-
appId: "1:61805724903:web:0f3771dc5cd44416600e42"
|
| 48 |
-
};
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
app = initializeApp(firebaseConfig);
|
| 52 |
-
auth = getAuth(app);
|
| 53 |
-
db = getFirestore(app);
|
| 54 |
-
console.log("Firebase inicializado correctamente.");
|
| 55 |
-
|
| 56 |
-
} catch (error) {
|
| 57 |
-
console.error("ERROR CRÍTICO AL INICIAR FIREBASE:", error);
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
// --- Constantes del Sistema ---
|
| 61 |
const ADMIN_EMAIL = "photonicsupernova@gmail.com";
|
| 62 |
const VIDEO_COST = 10000;
|
|
@@ -65,7 +33,11 @@ const DEFAULT_VIDEO_TITLE = "Pelicula Completa";
|
|
| 65 |
|
| 66 |
export default function App() {
|
| 67 |
|
| 68 |
-
// ---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
const [videos, setVideos] = useState([]);
|
| 70 |
const [leaderboard, setLeaderboard] = useState([]);
|
| 71 |
const [user, setUser] = useState(null);
|
|
@@ -80,15 +52,41 @@ export default function App() {
|
|
| 80 |
const playerRef = useRef(null);
|
| 81 |
const coinIntervalRef = useRef(null);
|
| 82 |
const localCoinBufferRef = useRef(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
-
// --- Efectos ---
|
| 85 |
|
| 86 |
// 1. Auth
|
| 87 |
useEffect(() => {
|
| 88 |
-
if (!auth)
|
| 89 |
-
|
| 90 |
-
return;
|
| 91 |
-
}
|
| 92 |
const unsubscribeAuth = onAuthStateChanged(auth, (user) => {
|
| 93 |
if (user) {
|
| 94 |
setUser(user);
|
|
@@ -101,11 +99,11 @@ export default function App() {
|
|
| 101 |
setAuthReady(true);
|
| 102 |
});
|
| 103 |
return () => unsubscribeAuth();
|
| 104 |
-
}, []);
|
| 105 |
|
| 106 |
// 2. Perfil de Usuario
|
| 107 |
useEffect(() => {
|
| 108 |
-
if (!userId || !db) return;
|
| 109 |
const profileRef = doc(db, 'users', userId);
|
| 110 |
const unsubscribeProfile = onSnapshot(profileRef, (docSnap) => {
|
| 111 |
if (docSnap.exists()) {
|
|
@@ -121,11 +119,11 @@ export default function App() {
|
|
| 121 |
}
|
| 122 |
}, (error) => console.error("Error listening to profile:", error));
|
| 123 |
return () => unsubscribeProfile();
|
| 124 |
-
}, [userId, user]);
|
| 125 |
|
| 126 |
// 3. Cargar Lista de Videos
|
| 127 |
useEffect(() => {
|
| 128 |
-
if (!db) return;
|
| 129 |
const q = query(collection(db, 'videos'), orderBy('createdAt', 'desc'), limit(20));
|
| 130 |
const unsubscribe = onSnapshot(q, (snapshot) => {
|
| 131 |
const fetchedVideos = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
|
|
@@ -139,18 +137,18 @@ export default function App() {
|
|
| 139 |
}
|
| 140 |
});
|
| 141 |
return () => unsubscribe();
|
| 142 |
-
}, []);
|
| 143 |
|
| 144 |
// 4. LEADERBOARD
|
| 145 |
useEffect(() => {
|
| 146 |
-
if (!db) return;
|
| 147 |
const q = query(collection(db, 'users'), orderBy('coins', 'desc'), limit(20));
|
| 148 |
const unsubscribe = onSnapshot(q, (snapshot) => {
|
| 149 |
const usersList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
|
| 150 |
setLeaderboard(usersList);
|
| 151 |
});
|
| 152 |
return () => unsubscribe();
|
| 153 |
-
}, []);
|
| 154 |
|
| 155 |
// 5. API YouTube
|
| 156 |
useEffect(() => {
|
|
@@ -252,12 +250,22 @@ export default function App() {
|
|
| 252 |
await signOut(auth);
|
| 253 |
};
|
| 254 |
|
| 255 |
-
// --- Renderizado de Carga ---
|
| 256 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
return (
|
| 258 |
<div className="flex flex-col items-center justify-center h-full w-full">
|
| 259 |
<div className="spinner mb-4"></div>
|
| 260 |
-
<p className="text-lg text-gray-400">
|
|
|
|
|
|
|
| 261 |
</div>
|
| 262 |
);
|
| 263 |
}
|
|
|
|
| 22 |
limit
|
| 23 |
} from 'firebase/firestore';
|
| 24 |
|
| 25 |
+
// --- Variables Globales (se llenarán después) ---
|
| 26 |
let app, auth, db;
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
// --- Constantes del Sistema ---
|
| 29 |
const ADMIN_EMAIL = "photonicsupernova@gmail.com";
|
| 30 |
const VIDEO_COST = 10000;
|
|
|
|
| 33 |
|
| 34 |
export default function App() {
|
| 35 |
|
| 36 |
+
// --- Estado de Inicialización ---
|
| 37 |
+
const [firebaseReady, setFirebaseReady] = useState(false);
|
| 38 |
+
const [configError, setConfigError] = useState(null);
|
| 39 |
+
|
| 40 |
+
// --- Estados de la App ---
|
| 41 |
const [videos, setVideos] = useState([]);
|
| 42 |
const [leaderboard, setLeaderboard] = useState([]);
|
| 43 |
const [user, setUser] = useState(null);
|
|
|
|
| 52 |
const playerRef = useRef(null);
|
| 53 |
const coinIntervalRef = useRef(null);
|
| 54 |
const localCoinBufferRef = useRef(0);
|
| 55 |
+
|
| 56 |
+
// --- EFECTO 0: OBTENER CONFIGURACIÓN DEL SERVIDOR (RUNTIME) ---
|
| 57 |
+
useEffect(() => {
|
| 58 |
+
const initFirebase = async () => {
|
| 59 |
+
try {
|
| 60 |
+
// 1. Pedimos la config al servidor
|
| 61 |
+
const response = await fetch('/api/config');
|
| 62 |
+
if (!response.ok) throw new Error("Falló la carga de configuración");
|
| 63 |
+
|
| 64 |
+
const config = await response.json();
|
| 65 |
+
|
| 66 |
+
// 2. Iniciamos Firebase con los datos recibidos
|
| 67 |
+
if (!app) {
|
| 68 |
+
app = initializeApp(config);
|
| 69 |
+
auth = getAuth(app);
|
| 70 |
+
db = getFirestore(app);
|
| 71 |
+
console.log("Firebase conectado vía Secret Runtime");
|
| 72 |
+
}
|
| 73 |
+
setFirebaseReady(true);
|
| 74 |
+
|
| 75 |
+
} catch (error) {
|
| 76 |
+
console.error("Error crítico:", error);
|
| 77 |
+
setConfigError("No se pudo conectar con el servidor de autenticación.");
|
| 78 |
+
}
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
initFirebase();
|
| 82 |
+
}, []);
|
| 83 |
|
| 84 |
+
// --- Efectos (Solo corren si firebaseReady es true) ---
|
| 85 |
|
| 86 |
// 1. Auth
|
| 87 |
useEffect(() => {
|
| 88 |
+
if (!firebaseReady || !auth) return;
|
| 89 |
+
|
|
|
|
|
|
|
| 90 |
const unsubscribeAuth = onAuthStateChanged(auth, (user) => {
|
| 91 |
if (user) {
|
| 92 |
setUser(user);
|
|
|
|
| 99 |
setAuthReady(true);
|
| 100 |
});
|
| 101 |
return () => unsubscribeAuth();
|
| 102 |
+
}, [firebaseReady]);
|
| 103 |
|
| 104 |
// 2. Perfil de Usuario
|
| 105 |
useEffect(() => {
|
| 106 |
+
if (!firebaseReady || !userId || !db) return;
|
| 107 |
const profileRef = doc(db, 'users', userId);
|
| 108 |
const unsubscribeProfile = onSnapshot(profileRef, (docSnap) => {
|
| 109 |
if (docSnap.exists()) {
|
|
|
|
| 119 |
}
|
| 120 |
}, (error) => console.error("Error listening to profile:", error));
|
| 121 |
return () => unsubscribeProfile();
|
| 122 |
+
}, [userId, user, firebaseReady]);
|
| 123 |
|
| 124 |
// 3. Cargar Lista de Videos
|
| 125 |
useEffect(() => {
|
| 126 |
+
if (!firebaseReady || !db) return;
|
| 127 |
const q = query(collection(db, 'videos'), orderBy('createdAt', 'desc'), limit(20));
|
| 128 |
const unsubscribe = onSnapshot(q, (snapshot) => {
|
| 129 |
const fetchedVideos = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
|
|
|
|
| 137 |
}
|
| 138 |
});
|
| 139 |
return () => unsubscribe();
|
| 140 |
+
}, [firebaseReady]);
|
| 141 |
|
| 142 |
// 4. LEADERBOARD
|
| 143 |
useEffect(() => {
|
| 144 |
+
if (!firebaseReady || !db) return;
|
| 145 |
const q = query(collection(db, 'users'), orderBy('coins', 'desc'), limit(20));
|
| 146 |
const unsubscribe = onSnapshot(q, (snapshot) => {
|
| 147 |
const usersList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
|
| 148 |
setLeaderboard(usersList);
|
| 149 |
});
|
| 150 |
return () => unsubscribe();
|
| 151 |
+
}, [firebaseReady]);
|
| 152 |
|
| 153 |
// 5. API YouTube
|
| 154 |
useEffect(() => {
|
|
|
|
| 250 |
await signOut(auth);
|
| 251 |
};
|
| 252 |
|
| 253 |
+
// --- Renderizado de Carga / Error ---
|
| 254 |
+
if (configError) {
|
| 255 |
+
return (
|
| 256 |
+
<div className="flex flex-col items-center justify-center h-full w-full text-red-500">
|
| 257 |
+
<p>⚠️ {configError}</p>
|
| 258 |
+
</div>
|
| 259 |
+
);
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
if (!firebaseReady || !authReady) {
|
| 263 |
return (
|
| 264 |
<div className="flex flex-col items-center justify-center h-full w-full">
|
| 265 |
<div className="spinner mb-4"></div>
|
| 266 |
+
<p className="text-lg text-gray-400">
|
| 267 |
+
{!firebaseReady ? "Obteniendo configuración segura..." : "Conectando usuario..."}
|
| 268 |
+
</p>
|
| 269 |
</div>
|
| 270 |
);
|
| 271 |
}
|