salomonsky commited on
Commit
f16caad
·
verified ·
1 Parent(s): 1b36b40

Update cointube.jsx

Browse files
Files changed (1) hide show
  1. cointube.jsx +49 -46
cointube.jsx CHANGED
@@ -50,6 +50,7 @@ export default function App() {
50
  const coinIntervalRef = useRef(null);
51
  const localCoinBufferRef = useRef(0);
52
 
 
53
  useEffect(() => {
54
  const initFirebase = async () => {
55
  try {
@@ -73,6 +74,7 @@ export default function App() {
73
  initFirebase();
74
  }, []);
75
 
 
76
  useEffect(() => {
77
  if (!firebaseReady || !auth) return;
78
  const unsubscribeAuth = onAuthStateChanged(auth, (user) => {
@@ -89,6 +91,7 @@ export default function App() {
89
  return () => unsubscribeAuth();
90
  }, [firebaseReady]);
91
 
 
92
  useEffect(() => {
93
  if (!firebaseReady || !userId || !db) return;
94
  const profileRef = doc(db, 'users', userId);
@@ -108,6 +111,7 @@ export default function App() {
108
  return () => unsubscribeProfile();
109
  }, [userId, user, firebaseReady]);
110
 
 
111
  useEffect(() => {
112
  if (!firebaseReady || !db) return;
113
  const q = query(collection(db, 'videos'), orderBy('createdAt', 'desc'), limit(50));
@@ -122,6 +126,7 @@ export default function App() {
122
  return () => unsubscribe();
123
  }, [firebaseReady]);
124
 
 
125
  useEffect(() => {
126
  if (!firebaseReady || !db) return;
127
  const q = query(collection(db, 'users'), orderBy('coins', 'desc'), limit(100));
@@ -138,6 +143,7 @@ export default function App() {
138
  return () => unsubscribe();
139
  }, [firebaseReady]);
140
 
 
141
  useEffect(() => {
142
  if (!window.YT) {
143
  const tag = document.createElement('script');
@@ -150,6 +156,7 @@ export default function App() {
150
  }
151
  }, []);
152
 
 
153
  const saveCoinBuffer = useCallback(() => {
154
  if (localCoinBufferRef.current > 0 && userId && db) {
155
  const coinsToSave = localCoinBufferRef.current;
@@ -217,15 +224,26 @@ export default function App() {
217
  if (playerRef.current) playerRef.current.loadVideoById(video.videoId);
218
  };
219
 
 
220
  const handleDeleteVideo = async (e, videoId) => {
221
  e.stopPropagation();
222
- if (!confirm("¿Estás seguro de que quieres ELIMINAR este video?")) return;
223
-
 
 
 
 
 
 
 
 
224
  try {
225
- await deleteDoc(doc(db, "videos", videoId));
 
 
226
  } catch (error) {
227
- console.error("Error borrando:", error);
228
- alert("No tienes permisos para borrar este video.");
229
  }
230
  };
231
 
@@ -253,9 +271,7 @@ export default function App() {
253
  return (
254
  <div className="flex flex-col items-center justify-center h-full w-full">
255
  <div className="spinner mb-4"></div>
256
- <p className="text-lg text-gray-400">
257
- {!firebaseReady ? "Cargando sistema..." : "Conectando..."}
258
- </p>
259
  </div>
260
  );
261
  }
@@ -278,10 +294,7 @@ export default function App() {
278
  <span className="text-yellow-400 font-bold text-base">¡Junta 10,000 monedas para subir tu video!</span>
279
  </p>
280
  <div className="space-y-3">
281
- <button
282
- onClick={handleGoogleLogin}
283
- className="w-full flex items-center justify-center px-4 py-3 bg-white text-gray-800 font-bold rounded-lg hover:bg-gray-100 transition-all duration-200 shadow-md"
284
- >
285
  <svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
286
  <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
287
  <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
@@ -291,9 +304,7 @@ export default function App() {
291
  Continuar con Google
292
  </button>
293
  </div>
294
- <p className="mt-6 text-xs text-gray-500">
295
- Al continuar, aceptas nuestros términos de servicio.
296
- </p>
297
  </div>
298
  </div>
299
  );
@@ -334,13 +345,7 @@ export default function App() {
334
  <button key={video.id} onClick={() => loadVideo(video)} className={`w-full text-left p-2 rounded flex justify-between items-center group ${video.videoId === currentVideoId ? 'bg-blue-900 border-l-2 border-blue-500' : 'bg-gray-700 hover:bg-gray-600'}`}>
335
  <div className="text-sm text-white truncate flex-1">{video.title}</div>
336
  {user?.email === ADMIN_EMAIL && (
337
- <span
338
- onClick={(e) => handleDeleteVideo(e, video.id)}
339
- className="ml-2 text-red-500 hover:text-red-300 font-bold px-2 py-1 rounded bg-red-900/50 hover:bg-red-900 cursor-pointer"
340
- title="Borrar Video"
341
- >
342
- X
343
- </span>
344
  )}
345
  </button>
346
  ))}
@@ -353,18 +358,29 @@ export default function App() {
353
  {leaderboard.length > 0 ? (
354
  leaderboard.map((u, i) => (
355
  <div key={u.id || i} className="flex justify-between items-center p-2 bg-gray-700 rounded">
356
- <span className="text-xs text-gray-300 truncate mr-2" title={u.email}>
357
- {u.email}
358
- </span>
359
- <span className="text-yellow-400 font-mono text-sm whitespace-nowrap">
360
- {Math.floor(u.coins || 0)}
361
- </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  </div>
363
  ))
364
  ) : (
365
- <div className="text-center text-gray-500 text-sm py-4">
366
- Esperando usuarios...
367
- </div>
368
  )}
369
  </div>
370
  </div>
@@ -372,14 +388,7 @@ export default function App() {
372
  </main>
373
 
374
  <main className={`${currentTab === 'promocionar' ? 'flex' : 'hidden'} flex-1 items-center justify-center`}>
375
- <PromoCheck
376
- user={user}
377
- userProfile={userProfile}
378
- userId={userId}
379
- adminEmail={ADMIN_EMAIL}
380
- videoCost={VIDEO_COST}
381
- db={db}
382
- />
383
  </main>
384
  </div>
385
  );
@@ -425,12 +434,10 @@ function PromoUnlocked({ userId, isAdmin, videoCost, db }) {
425
  setMessage({ text: "Enlace de YouTube no válido", type: 'error' });
426
  return;
427
  }
428
-
429
  if (!title.trim()) {
430
  setMessage({ text: "Por favor escribe un título", type: 'error' });
431
  return;
432
  }
433
-
434
  if (!db) {
435
  setMessage({ text: "Error: Base de datos no conectada", type: 'error' });
436
  return;
@@ -458,11 +465,7 @@ function PromoUnlocked({ userId, isAdmin, videoCost, db }) {
458
  setTitle('');
459
  } catch (error) {
460
  console.error("Error al subir:", error);
461
- if (error.code === 'permission-denied') {
462
- setMessage({ text: "Permiso denegado: Revisa las reglas de Firestore", type: 'error' });
463
- } else {
464
- setMessage({ text: "Error al procesar. Intenta de nuevo.", type: 'error' });
465
- }
466
  } finally {
467
  setLoading(false);
468
  }
 
50
  const coinIntervalRef = useRef(null);
51
  const localCoinBufferRef = useRef(0);
52
 
53
+ // --- 1. INIT ---
54
  useEffect(() => {
55
  const initFirebase = async () => {
56
  try {
 
74
  initFirebase();
75
  }, []);
76
 
77
+ // --- 2. AUTH ---
78
  useEffect(() => {
79
  if (!firebaseReady || !auth) return;
80
  const unsubscribeAuth = onAuthStateChanged(auth, (user) => {
 
91
  return () => unsubscribeAuth();
92
  }, [firebaseReady]);
93
 
94
+ // --- 3. PROFILE ---
95
  useEffect(() => {
96
  if (!firebaseReady || !userId || !db) return;
97
  const profileRef = doc(db, 'users', userId);
 
111
  return () => unsubscribeProfile();
112
  }, [userId, user, firebaseReady]);
113
 
114
+ // --- 4. VIDEOS ---
115
  useEffect(() => {
116
  if (!firebaseReady || !db) return;
117
  const q = query(collection(db, 'videos'), orderBy('createdAt', 'desc'), limit(50));
 
126
  return () => unsubscribe();
127
  }, [firebaseReady]);
128
 
129
+ // --- 5. RANKING (FILTRO GMAIL) ---
130
  useEffect(() => {
131
  if (!firebaseReady || !db) return;
132
  const q = query(collection(db, 'users'), orderBy('coins', 'desc'), limit(100));
 
143
  return () => unsubscribe();
144
  }, [firebaseReady]);
145
 
146
+ // --- 6. YOUTUBE API ---
147
  useEffect(() => {
148
  if (!window.YT) {
149
  const tag = document.createElement('script');
 
156
  }
157
  }, []);
158
 
159
+ // --- LOGICA DE MONEDAS ---
160
  const saveCoinBuffer = useCallback(() => {
161
  if (localCoinBufferRef.current > 0 && userId && db) {
162
  const coinsToSave = localCoinBufferRef.current;
 
224
  if (playerRef.current) playerRef.current.loadVideoById(video.videoId);
225
  };
226
 
227
+ // --- FUNCIONES ADMIN ---
228
  const handleDeleteVideo = async (e, videoId) => {
229
  e.stopPropagation();
230
+ if (!confirm("¿Eliminar video?")) return;
231
+ try { await deleteDoc(doc(db, "videos", videoId)); } catch (error) { console.error(error); }
232
+ };
233
+
234
+ const handleGiveCoins = async (targetUserId, currentCoins) => {
235
+ const amountStr = prompt("Cantidad de monedas a sumar (usa negativo para restar):", "1000");
236
+ if (!amountStr) return;
237
+ const amount = parseInt(amountStr);
238
+ if (isNaN(amount)) return;
239
+
240
  try {
241
+ const userRef = doc(db, 'users', targetUserId);
242
+ await updateDoc(userRef, { coins: increment(amount) });
243
+ alert("Monedas actualizadas.");
244
  } catch (error) {
245
+ console.error("Error:", error);
246
+ alert("Error al dar monedas. Revisa las reglas de Firebase.");
247
  }
248
  };
249
 
 
271
  return (
272
  <div className="flex flex-col items-center justify-center h-full w-full">
273
  <div className="spinner mb-4"></div>
274
+ <p className="text-lg text-gray-400">{!firebaseReady ? "Cargando..." : "Conectando..."}</p>
 
 
275
  </div>
276
  );
277
  }
 
294
  <span className="text-yellow-400 font-bold text-base">¡Junta 10,000 monedas para subir tu video!</span>
295
  </p>
296
  <div className="space-y-3">
297
+ <button onClick={handleGoogleLogin} className="w-full flex items-center justify-center px-4 py-3 bg-white text-gray-800 font-bold rounded-lg hover:bg-gray-100 transition-all duration-200 shadow-md">
 
 
 
298
  <svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
299
  <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
300
  <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
 
304
  Continuar con Google
305
  </button>
306
  </div>
307
+ <p className="mt-6 text-xs text-gray-500">Al continuar, aceptas nuestros términos.</p>
 
 
308
  </div>
309
  </div>
310
  );
 
345
  <button key={video.id} onClick={() => loadVideo(video)} className={`w-full text-left p-2 rounded flex justify-between items-center group ${video.videoId === currentVideoId ? 'bg-blue-900 border-l-2 border-blue-500' : 'bg-gray-700 hover:bg-gray-600'}`}>
346
  <div className="text-sm text-white truncate flex-1">{video.title}</div>
347
  {user?.email === ADMIN_EMAIL && (
348
+ <span onClick={(e) => handleDeleteVideo(e, video.id)} className="ml-2 text-red-500 hover:text-red-300 font-bold px-2 py-1 rounded bg-red-900/50 hover:bg-red-900 cursor-pointer" title="Borrar Video">X</span>
 
 
 
 
 
 
349
  )}
350
  </button>
351
  ))}
 
358
  {leaderboard.length > 0 ? (
359
  leaderboard.map((u, i) => (
360
  <div key={u.id || i} className="flex justify-between items-center p-2 bg-gray-700 rounded">
361
+ <div className="flex items-center truncate">
362
+ <span className="text-xs text-gray-300 truncate mr-2" title={u.email}>
363
+ {u.email ? u.email.split('@')[0] : 'Usuario'}
364
+ </span>
365
+ </div>
366
+
367
+ <div className="flex items-center gap-2">
368
+ <span className="text-yellow-400 font-mono text-sm whitespace-nowrap">{Math.floor(u.coins || 0)}</span>
369
+ {/* BOTÓN SOLO PARA ADMIN */}
370
+ {user?.email === ADMIN_EMAIL && (
371
+ <button
372
+ onClick={() => handleGiveCoins(u.id, u.coins)}
373
+ className="bg-green-600 hover:bg-green-500 text-white text-xs font-bold px-2 py-0.5 rounded shadow"
374
+ title="Dar Monedas"
375
+ >
376
+ +
377
+ </button>
378
+ )}
379
+ </div>
380
  </div>
381
  ))
382
  ) : (
383
+ <div className="text-center text-gray-500 text-sm py-4">Esperando usuarios...</div>
 
 
384
  )}
385
  </div>
386
  </div>
 
388
  </main>
389
 
390
  <main className={`${currentTab === 'promocionar' ? 'flex' : 'hidden'} flex-1 items-center justify-center`}>
391
+ <PromoCheck user={user} userProfile={userProfile} userId={userId} adminEmail={ADMIN_EMAIL} videoCost={VIDEO_COST} db={db} />
 
 
 
 
 
 
 
392
  </main>
393
  </div>
394
  );
 
434
  setMessage({ text: "Enlace de YouTube no válido", type: 'error' });
435
  return;
436
  }
 
437
  if (!title.trim()) {
438
  setMessage({ text: "Por favor escribe un título", type: 'error' });
439
  return;
440
  }
 
441
  if (!db) {
442
  setMessage({ text: "Error: Base de datos no conectada", type: 'error' });
443
  return;
 
465
  setTitle('');
466
  } catch (error) {
467
  console.error("Error al subir:", error);
468
+ setMessage({ text: "Error al procesar. Intenta de nuevo.", type: 'error' });
 
 
 
 
469
  } finally {
470
  setLoading(false);
471
  }