salomonsky commited on
Commit
a666969
·
verified ·
1 Parent(s): 8a62c01

Update cointube.jsx

Browse files
Files changed (1) hide show
  1. cointube.jsx +108 -67
cointube.jsx CHANGED
@@ -1,5 +1,3 @@
1
- JavaScript
2
-
3
  import React, { useState, useEffect, useRef, useCallback } from 'react';
4
  import { initializeApp } from 'firebase/app';
5
  import {
@@ -8,8 +6,7 @@ import {
8
  GoogleAuthProvider,
9
  signInWithPopup,
10
  signOut,
11
- signInWithCustomToken,
12
- signInAnonymously
13
  } from 'firebase/auth';
14
  import {
15
  getFirestore,
@@ -25,6 +22,7 @@ import {
25
  setLogLevel
26
  } from 'firebase/firestore';
27
 
 
28
  const firebaseConfig = {
29
  apiKey: "AIzaSyCwJjVJGuOmA_PKRbaTnQDrK-Q07NI_utc",
30
  authDomain: "insights-5c2d6.firebaseapp.com",
@@ -44,16 +42,24 @@ try {
44
  console.error("Error inicializando Firebase:", error);
45
  }
46
 
 
47
  const DEFAULT_VIDEO_ID = "UtEhewStfMA";
48
- const DEFAULT_VIDEO_TITLE = "Cómo Programar un Cohete";
49
 
50
  export default function App() {
51
 
 
 
 
 
 
 
 
 
52
  const [user, setUser] = useState(null);
53
  const [userId, setUserId] = useState(null);
54
  const [authReady, setAuthReady] = useState(false);
55
  const [userProfile, setUserProfile] = useState(null);
56
- const [videos, setVideos] = useState([]);
57
  const [currentTab, setCurrentTab] = useState('ganar');
58
  const [currentVideoId, setCurrentVideoId] = useState(DEFAULT_VIDEO_ID);
59
  const [currentVideoTitle, setCurrentVideoTitle] = useState(DEFAULT_VIDEO_TITLE);
@@ -63,9 +69,11 @@ export default function App() {
63
  const coinIntervalRef = useRef(null);
64
  const localCoinBufferRef = useRef(0);
65
 
 
 
 
66
  useEffect(() => {
67
  if (!auth) return;
68
-
69
  const unsubscribeAuth = onAuthStateChanged(auth, (user) => {
70
  if (user) {
71
  setUser(user);
@@ -77,17 +85,14 @@ export default function App() {
77
  }
78
  setAuthReady(true);
79
  });
80
-
81
  setAuthReady(true);
82
-
83
  return () => unsubscribeAuth();
84
  }, []);
85
 
 
86
  useEffect(() => {
87
  if (!userId || !db) return;
88
-
89
  const profileRef = doc(db, 'users', userId);
90
-
91
  const unsubscribeProfile = onSnapshot(profileRef, (docSnap) => {
92
  if (docSnap.exists()) {
93
  setUserProfile(docSnap.data());
@@ -96,16 +101,14 @@ export default function App() {
96
  setDoc(profileRef, newProfile).catch(e => console.error("Error creating profile:", e));
97
  setUserProfile(newProfile);
98
  }
99
- }, (error) => {
100
- console.error("Error listening to profile:", error);
101
- });
102
-
103
  return () => unsubscribeProfile();
104
  }, [userId]);
105
 
 
 
106
  useEffect(() => {
107
  if (!db) return;
108
-
109
  const videosRef = collection(db, 'videos');
110
  const q = query(videosRef);
111
 
@@ -119,7 +122,7 @@ export default function App() {
119
  }).catch(e => console.error("Error adding default video:", e));
120
  } else {
121
  const videoList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
122
- setVideos(videoList);
123
  }
124
  }, (error) => {
125
  console.error("Error listening to videos:", error);
@@ -127,31 +130,29 @@ export default function App() {
127
 
128
  return () => unsubscribeVideos();
129
  }, []);
 
130
 
 
131
  useEffect(() => {
132
  if (!window.YT) {
133
  const tag = document.createElement('script');
134
  tag.src = "https://www.youtube.com/iframe_api";
135
  const firstScriptTag = document.getElementsByTagName('script')[0];
136
  firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
137
-
138
- window.onYouTubeIframeAPIReady = () => {
139
- setYtApiReady(true);
140
- };
141
  } else {
142
  setYtApiReady(true);
143
  }
144
  }, []);
145
 
 
146
  const saveCoinBuffer = useCallback(() => {
147
  if (localCoinBufferRef.current > 0 && userId) {
148
  const coinsToSave = localCoinBufferRef.current;
149
  localCoinBufferRef.current = 0;
150
  const profileRef = doc(db, 'users', userId);
151
  updateDoc(profileRef, { coins: increment(coinsToSave) })
152
- .catch(e => {
153
- localCoinBufferRef.current += coinsToSave;
154
- });
155
  }
156
  }, [userId]);
157
 
@@ -159,9 +160,7 @@ export default function App() {
159
  if (coinIntervalRef.current) return;
160
  coinIntervalRef.current = setInterval(() => {
161
  localCoinBufferRef.current++;
162
- if (localCoinBufferRef.current >= 5) {
163
- saveCoinBuffer();
164
- }
165
  }, 1000);
166
  }, [saveCoinBuffer]);
167
 
@@ -173,6 +172,7 @@ export default function App() {
173
  }
174
  }, [saveCoinBuffer]);
175
 
 
176
  const onPlayerReady = useCallback((event) => {
177
  playerRef.current = event.target;
178
  playerRef.current.playVideo();
@@ -180,11 +180,9 @@ export default function App() {
180
 
181
  const onPlayerStateChange = useCallback((event) => {
182
  const state = event.data;
183
- if (state === window.YT.PlayerState.PLAYING) {
184
- startCoinInterval();
185
- } else {
186
- stopCoinInterval();
187
- }
188
  if (state === window.YT.PlayerState.ENDED) {
189
  if (currentVideoId === DEFAULT_VIDEO_ID && userId) {
190
  const profileRef = doc(db, 'users', userId);
@@ -193,20 +191,15 @@ export default function App() {
193
  }
194
  }, [startCoinInterval, stopCoinInterval, currentVideoId, userId]);
195
 
 
196
  useEffect(() => {
197
  if (ytApiReady && userId && !playerRef.current) {
198
  playerRef.current = new window.YT.Player('player', {
199
  height: '100%',
200
  width: '100%',
201
  videoId: currentVideoId,
202
- playerVars: {
203
- 'playsinline': 1, 'autoplay': 1, 'controls': 1,
204
- 'modestbranding': 1, 'rel': 0
205
- },
206
- events: {
207
- 'onReady': onPlayerReady,
208
- 'onStateChange': onPlayerStateChange
209
- }
210
  });
211
  }
212
  if (!userId && playerRef.current) {
@@ -219,17 +212,24 @@ export default function App() {
219
  stopCoinInterval();
220
  setCurrentVideoId(video.videoId);
221
  setCurrentVideoTitle(video.title);
222
- if (playerRef.current) {
223
- playerRef.current.loadVideoById(video.videoId);
224
- }
225
  };
226
 
 
227
  const handleGoogleLogin = async () => {
228
  const provider = new GoogleAuthProvider();
229
  try {
230
  await signInWithPopup(auth, provider);
231
  } catch (error) {
232
- console.error("Error en el inicio de sesión con Google:", error);
 
 
 
 
 
 
 
 
233
  }
234
  };
235
 
@@ -238,6 +238,7 @@ export default function App() {
238
  await signOut(auth);
239
  };
240
 
 
241
  if (!authReady) {
242
  return (
243
  <div className="flex flex-col items-center justify-center h-full w-full">
@@ -247,27 +248,70 @@ export default function App() {
247
  );
248
  }
249
 
 
250
  if (authReady && !user) {
251
  return (
252
- <div className="flex flex-col items-center justify-center h-full w-full">
253
- <p className="text-lg text-gray-400 mb-6">Por favor, inicia sesión para continuar.</p>
254
- <button
255
- onClick={handleGoogleLogin}
256
- className="px-6 py-2 bg-blue-600 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 transition-colors"
257
- >
258
- Iniciar sesión con Google
259
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  </div>
261
  );
262
  }
263
 
 
264
  return (
265
  <div className="h-full w-full max-w-7xl mx-auto flex flex-col p-4 md:p-6">
266
  <header className="flex flex-col md:flex-row justify-between items-center pb-4 mb-4 border-b border-gray-700">
267
  <h1 className="text-3xl font-bold text-white">Coin<span className="text-blue-500">Tube</span></h1>
268
  <div className="flex items-center space-x-4 mt-4 md:mt-0">
269
  <div className="flex flex-col items-end">
270
- <span className="text-sm text-gray-400">{user?.email}</span>
 
271
  <span className="text-lg font-bold text-yellow-400">
272
  {userProfile?.coins || 0} Monedas
273
  </span>
@@ -310,20 +354,16 @@ export default function App() {
310
  <aside className="lg:w-1/3 flex flex-col bg-gray-800 rounded-lg shadow-lg p-4">
311
  <h2 className="text-xl font-semibold mb-4 pb-2 border-b border-gray-700">Videos Disponibles</h2>
312
  <div className="flex-1 overflow-y-auto space-y-2 pr-2">
313
- {videos.length === 0 ? (
314
- <p className="text-gray-400">Cargando videos...</p>
315
- ) : (
316
- videos.map((video) => (
317
- <button
318
- key={video.id}
319
- onClick={() => loadVideo(video)}
320
- className="w-full text-left px-4 py-3 bg-gray-700 rounded-lg hover:bg-gray-600 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
321
- disabled={video.videoId === currentVideoId}
322
- >
323
- {video.title || `Video ID: ${video.videoId}`}
324
- </button>
325
- ))
326
- )}
327
  </div>
328
  </aside>
329
  </main>
@@ -339,6 +379,7 @@ export default function App() {
339
  );
340
  }
341
 
 
342
  function PromoLocked() {
343
  return (
344
  <div className="text-center p-8 bg-gray-800 rounded-lg shadow-xl max-w-lg">
 
 
 
1
  import React, { useState, useEffect, useRef, useCallback } from 'react';
2
  import { initializeApp } from 'firebase/app';
3
  import {
 
6
  GoogleAuthProvider,
7
  signInWithPopup,
8
  signOut,
9
+ signInAnonymously
 
10
  } from 'firebase/auth';
11
  import {
12
  getFirestore,
 
22
  setLogLevel
23
  } from 'firebase/firestore';
24
 
25
+ // --- Constantes de Firebase ---
26
  const firebaseConfig = {
27
  apiKey: "AIzaSyCwJjVJGuOmA_PKRbaTnQDrK-Q07NI_utc",
28
  authDomain: "insights-5c2d6.firebaseapp.com",
 
42
  console.error("Error inicializando Firebase:", error);
43
  }
44
 
45
+ // --- Constantes del Video (Agregadas) ---
46
  const DEFAULT_VIDEO_ID = "UtEhewStfMA";
47
+ const DEFAULT_VIDEO_TITLE = "Pelicula Completa"; // Usando el título de tu lista
48
 
49
  export default function App() {
50
 
51
+ // --- Estado de la Lista de Videos (CORREGIDO - Dentro del componente) ---
52
+ const [videos, setVideos] = useState([
53
+ { id: '1', title: 'Pelicula Completa', videoId: 'UtEhewStfMA' },
54
+ { id: '2', title: 'Documental Comida', videoId: 'lwiNN7WUw50' },
55
+ { id: '3', title: 'Cine de Arte', videoId: 'GxEx6Kgo6Es' },
56
+ ]);
57
+
58
+ // --- Resto de Estados ---
59
  const [user, setUser] = useState(null);
60
  const [userId, setUserId] = useState(null);
61
  const [authReady, setAuthReady] = useState(false);
62
  const [userProfile, setUserProfile] = useState(null);
 
63
  const [currentTab, setCurrentTab] = useState('ganar');
64
  const [currentVideoId, setCurrentVideoId] = useState(DEFAULT_VIDEO_ID);
65
  const [currentVideoTitle, setCurrentVideoTitle] = useState(DEFAULT_VIDEO_TITLE);
 
69
  const coinIntervalRef = useRef(null);
70
  const localCoinBufferRef = useRef(0);
71
 
72
+ // --- Efectos ---
73
+
74
+ // 1. Auth
75
  useEffect(() => {
76
  if (!auth) return;
 
77
  const unsubscribeAuth = onAuthStateChanged(auth, (user) => {
78
  if (user) {
79
  setUser(user);
 
85
  }
86
  setAuthReady(true);
87
  });
 
88
  setAuthReady(true);
 
89
  return () => unsubscribeAuth();
90
  }, []);
91
 
92
+ // 2. Perfil de Usuario
93
  useEffect(() => {
94
  if (!userId || !db) return;
 
95
  const profileRef = doc(db, 'users', userId);
 
96
  const unsubscribeProfile = onSnapshot(profileRef, (docSnap) => {
97
  if (docSnap.exists()) {
98
  setUserProfile(docSnap.data());
 
101
  setDoc(profileRef, newProfile).catch(e => console.error("Error creating profile:", e));
102
  setUserProfile(newProfile);
103
  }
104
+ }, (error) => console.error("Error listening to profile:", error));
 
 
 
105
  return () => unsubscribeProfile();
106
  }, [userId]);
107
 
108
+ // 3. Videos de Firebase (ELIMINADO / Comentado para usar lista fija)
109
+ /*
110
  useEffect(() => {
111
  if (!db) return;
 
112
  const videosRef = collection(db, 'videos');
113
  const q = query(videosRef);
114
 
 
122
  }).catch(e => console.error("Error adding default video:", e));
123
  } else {
124
  const videoList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
125
+ // Si usas la lista fija, no hagas setVideos(videoList) aquí.
126
  }
127
  }, (error) => {
128
  console.error("Error listening to videos:", error);
 
130
 
131
  return () => unsubscribeVideos();
132
  }, []);
133
+ */
134
 
135
+ // 4. API YouTube
136
  useEffect(() => {
137
  if (!window.YT) {
138
  const tag = document.createElement('script');
139
  tag.src = "https://www.youtube.com/iframe_api";
140
  const firstScriptTag = document.getElementsByTagName('script')[0];
141
  firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
142
+ window.onYouTubeIframeAPIReady = () => setYtApiReady(true);
 
 
 
143
  } else {
144
  setYtApiReady(true);
145
  }
146
  }, []);
147
 
148
+ // --- Lógica de Monedas ---
149
  const saveCoinBuffer = useCallback(() => {
150
  if (localCoinBufferRef.current > 0 && userId) {
151
  const coinsToSave = localCoinBufferRef.current;
152
  localCoinBufferRef.current = 0;
153
  const profileRef = doc(db, 'users', userId);
154
  updateDoc(profileRef, { coins: increment(coinsToSave) })
155
+ .catch(e => { localCoinBufferRef.current += coinsToSave; });
 
 
156
  }
157
  }, [userId]);
158
 
 
160
  if (coinIntervalRef.current) return;
161
  coinIntervalRef.current = setInterval(() => {
162
  localCoinBufferRef.current++;
163
+ if (localCoinBufferRef.current >= 5) saveCoinBuffer();
 
 
164
  }, 1000);
165
  }, [saveCoinBuffer]);
166
 
 
172
  }
173
  }, [saveCoinBuffer]);
174
 
175
+ // --- Player Events ---
176
  const onPlayerReady = useCallback((event) => {
177
  playerRef.current = event.target;
178
  playerRef.current.playVideo();
 
180
 
181
  const onPlayerStateChange = useCallback((event) => {
182
  const state = event.data;
183
+ if (state === window.YT.PlayerState.PLAYING) startCoinInterval();
184
+ else stopCoinInterval();
185
+
 
 
186
  if (state === window.YT.PlayerState.ENDED) {
187
  if (currentVideoId === DEFAULT_VIDEO_ID && userId) {
188
  const profileRef = doc(db, 'users', userId);
 
191
  }
192
  }, [startCoinInterval, stopCoinInterval, currentVideoId, userId]);
193
 
194
+ // 5. Inicializar Player
195
  useEffect(() => {
196
  if (ytApiReady && userId && !playerRef.current) {
197
  playerRef.current = new window.YT.Player('player', {
198
  height: '100%',
199
  width: '100%',
200
  videoId: currentVideoId,
201
+ playerVars: { 'playsinline': 1, 'autoplay': 1, 'controls': 1, 'modestbranding': 1, 'rel': 0 },
202
+ events: { 'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange }
 
 
 
 
 
 
203
  });
204
  }
205
  if (!userId && playerRef.current) {
 
212
  stopCoinInterval();
213
  setCurrentVideoId(video.videoId);
214
  setCurrentVideoTitle(video.title);
215
+ if (playerRef.current) playerRef.current.loadVideoById(video.videoId);
 
 
216
  };
217
 
218
+ // --- FUNCIONES DE LOGIN ---
219
  const handleGoogleLogin = async () => {
220
  const provider = new GoogleAuthProvider();
221
  try {
222
  await signInWithPopup(auth, provider);
223
  } catch (error) {
224
+ console.error("Error Google:", error);
225
+ }
226
+ };
227
+
228
+ const handleAnonymousLogin = async () => {
229
+ try {
230
+ await signInAnonymously(auth);
231
+ } catch (error) {
232
+ console.error("Error Anónimo:", error);
233
  }
234
  };
235
 
 
238
  await signOut(auth);
239
  };
240
 
241
+ // --- Renderizado de Carga ---
242
  if (!authReady) {
243
  return (
244
  <div className="flex flex-col items-center justify-center h-full w-full">
 
248
  );
249
  }
250
 
251
+ // --- Renderizado de Login ---
252
  if (authReady && !user) {
253
  return (
254
+ <div className="flex flex-col items-center justify-center h-full w-full bg-gray-900 p-4">
255
+ <div className="max-w-md w-full bg-gray-800 rounded-2xl shadow-2xl p-8 text-center border border-gray-700">
256
+
257
+ {/* ICONO */}
258
+ <div className="flex justify-center mb-6">
259
+ <div className="bg-blue-600 p-4 rounded-full shadow-lg">
260
+ <svg className="w-12 h-12 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
261
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path>
262
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
263
+ </svg>
264
+ </div>
265
+ </div>
266
+
267
+ {/* TÍTULO */}
268
+ <h1 className="text-3xl font-bold text-white mb-2 tracking-tight">Bienvenido a CoinTube</h1>
269
+
270
+ {/* DESCRIPCIÓN */}
271
+ <p className="text-gray-400 mb-8 text-sm leading-relaxed">
272
+ Descubre contenido increíble, gana monedas virtuales por cada minuto que ves y promociona tus propios videos.
273
+ </p>
274
+
275
+ {/* BOTONES */}
276
+ <div className="space-y-3">
277
+ <button
278
+ onClick={handleGoogleLogin}
279
+ 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"
280
+ >
281
+ <svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
282
+ <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"/>
283
+ <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"/>
284
+ <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
285
+ <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
286
+ </svg>
287
+ Continuar con Google
288
+ </button>
289
+
290
+ <button
291
+ onClick={handleAnonymousLogin}
292
+ className="w-full px-4 py-3 bg-gray-700 text-gray-200 font-semibold rounded-lg hover:bg-gray-600 border border-gray-600 transition-all duration-200"
293
+ >
294
+ Entrar como Invitado
295
+ </button>
296
+ </div>
297
+
298
+ <p className="mt-6 text-xs text-gray-500">
299
+ Al continuar, aceptas nuestros términos de servicio ficticios.
300
+ </p>
301
+ </div>
302
  </div>
303
  );
304
  }
305
 
306
+ // --- App Principal ---
307
  return (
308
  <div className="h-full w-full max-w-7xl mx-auto flex flex-col p-4 md:p-6">
309
  <header className="flex flex-col md:flex-row justify-between items-center pb-4 mb-4 border-b border-gray-700">
310
  <h1 className="text-3xl font-bold text-white">Coin<span className="text-blue-500">Tube</span></h1>
311
  <div className="flex items-center space-x-4 mt-4 md:mt-0">
312
  <div className="flex flex-col items-end">
313
+ {/* Muestra 'Invitado' si no hay email */}
314
+ <span className="text-sm text-gray-400">{user?.email || 'Invitado'}</span>
315
  <span className="text-lg font-bold text-yellow-400">
316
  {userProfile?.coins || 0} Monedas
317
  </span>
 
354
  <aside className="lg:w-1/3 flex flex-col bg-gray-800 rounded-lg shadow-lg p-4">
355
  <h2 className="text-xl font-semibold mb-4 pb-2 border-b border-gray-700">Videos Disponibles</h2>
356
  <div className="flex-1 overflow-y-auto space-y-2 pr-2">
357
+ {videos.map((video) => (
358
+ <button
359
+ key={video.id}
360
+ onClick={() => loadVideo(video)}
361
+ className="w-full text-left px-4 py-3 bg-gray-700 rounded-lg hover:bg-gray-600 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
362
+ disabled={video.videoId === currentVideoId}
363
+ >
364
+ {video.title || `Video ID: ${video.videoId}`}
365
+ </button>
366
+ ))}
 
 
 
 
367
  </div>
368
  </aside>
369
  </main>
 
379
  );
380
  }
381
 
382
+ // --- Sub-Componentes ---
383
  function PromoLocked() {
384
  return (
385
  <div className="text-center p-8 bg-gray-800 rounded-lg shadow-xl max-w-lg">