Spaces:
Running
Running
Update cointube.jsx
Browse files- cointube.jsx +20 -32
cointube.jsx
CHANGED
|
@@ -4,8 +4,7 @@ import {
|
|
| 4 |
getAuth,
|
| 5 |
onAuthStateChanged,
|
| 6 |
GoogleAuthProvider,
|
| 7 |
-
|
| 8 |
-
getRedirectResult, // Modificado para que funcione en Hugging Face
|
| 9 |
signOut
|
| 10 |
} from 'firebase/auth';
|
| 11 |
import {
|
|
@@ -22,8 +21,8 @@ import {
|
|
| 22 |
query,
|
| 23 |
orderBy,
|
| 24 |
limit,
|
| 25 |
-
writeBatch,
|
| 26 |
-
getDocs
|
| 27 |
} from 'firebase/firestore';
|
| 28 |
|
| 29 |
const firebaseConfig = {
|
|
@@ -41,8 +40,6 @@ const db = getFirestore(app);
|
|
| 41 |
|
| 42 |
const ADMIN_EMAIL = "dimensionalpulsar@gmail.com";
|
| 43 |
const VIDEO_COST = 10000;
|
| 44 |
-
const DEFAULT_VIDEO_ID = "1rghd2zPhFo";
|
| 45 |
-
const DEFAULT_VIDEO_TITLE = "Cuentos de Terror";
|
| 46 |
|
| 47 |
// Datos de EmailJS
|
| 48 |
const EMAILJS_SERVICE_ID = "service_v18p188";
|
|
@@ -70,9 +67,6 @@ export default function App() {
|
|
| 70 |
const initialLoadRef = useRef(true);
|
| 71 |
|
| 72 |
useEffect(() => {
|
| 73 |
-
// Esto captura el login después de la redirección de Google
|
| 74 |
-
getRedirectResult(auth).catch(error => console.error("Redirect Error:", error));
|
| 75 |
-
|
| 76 |
const unsubscribeAuth = onAuthStateChanged(auth, (user) => {
|
| 77 |
if (user) {
|
| 78 |
setUser(user);
|
|
@@ -96,8 +90,7 @@ export default function App() {
|
|
| 96 |
} else {
|
| 97 |
const newProfile = {
|
| 98 |
email: user.email,
|
| 99 |
-
coins: 0
|
| 100 |
-
watchedDefaultVideo: false
|
| 101 |
};
|
| 102 |
setDoc(profileRef, newProfile).catch(e => console.error(e));
|
| 103 |
setUserProfile(newProfile);
|
|
@@ -106,7 +99,7 @@ export default function App() {
|
|
| 106 |
return () => unsubscribeProfile();
|
| 107 |
}, [userId, user]);
|
| 108 |
|
| 109 |
-
//
|
| 110 |
useEffect(() => {
|
| 111 |
if (user?.email !== ADMIN_EMAIL) return;
|
| 112 |
|
|
@@ -138,13 +131,14 @@ export default function App() {
|
|
| 138 |
checkReset();
|
| 139 |
}, [user]);
|
| 140 |
|
|
|
|
| 141 |
useEffect(() => {
|
| 142 |
const q = query(collection(db, 'videos'), orderBy('createdAt', 'desc'), limit(50));
|
| 143 |
const unsubscribe = onSnapshot(q, (snapshot) => {
|
| 144 |
let fetchedVideos = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
|
| 145 |
|
| 146 |
if (fetchedVideos.length > 0) {
|
| 147 |
-
//
|
| 148 |
fetchedVideos = fetchedVideos.sort(() => Math.random() - 0.5);
|
| 149 |
|
| 150 |
setVideos(fetchedVideos);
|
|
@@ -155,10 +149,10 @@ export default function App() {
|
|
| 155 |
initialLoadRef.current = false;
|
| 156 |
}
|
| 157 |
} else {
|
| 158 |
-
setVideos([
|
| 159 |
if (initialLoadRef.current || !currentVideoId) {
|
| 160 |
-
setCurrentVideoId(
|
| 161 |
-
setCurrentVideoTitle(
|
| 162 |
initialLoadRef.current = false;
|
| 163 |
}
|
| 164 |
}
|
|
@@ -228,14 +222,7 @@ export default function App() {
|
|
| 228 |
const state = event.data;
|
| 229 |
if (state === window.YT.PlayerState.PLAYING) startCoinInterval();
|
| 230 |
else stopCoinInterval();
|
| 231 |
-
|
| 232 |
-
if (state === window.YT.PlayerState.ENDED) {
|
| 233 |
-
if (currentVideoId === DEFAULT_VIDEO_ID && userId) {
|
| 234 |
-
const profileRef = doc(db, 'users', userId);
|
| 235 |
-
updateDoc(profileRef, { watchedDefaultVideo: true });
|
| 236 |
-
}
|
| 237 |
-
}
|
| 238 |
-
}, [startCoinInterval, stopCoinInterval, currentVideoId, userId]);
|
| 239 |
|
| 240 |
useEffect(() => {
|
| 241 |
if (ytApiReady && userId && !playerRef.current && currentVideoId) {
|
|
@@ -282,7 +269,7 @@ export default function App() {
|
|
| 282 |
}
|
| 283 |
};
|
| 284 |
|
| 285 |
-
//
|
| 286 |
const handleMassEmail = async () => {
|
| 287 |
if (!window.emailjs) return alert("EmailJS no está cargado. Revisa tu index.html.");
|
| 288 |
if (!confirm("¿Enviar notificación de reseteo a todos los usuarios?")) return;
|
|
@@ -303,11 +290,11 @@ export default function App() {
|
|
| 303 |
alert(`Correos en proceso de envío para ${count} usuarios.`);
|
| 304 |
};
|
| 305 |
|
|
|
|
| 306 |
const handleGoogleLogin = async () => {
|
| 307 |
const provider = new GoogleAuthProvider();
|
| 308 |
try {
|
| 309 |
-
|
| 310 |
-
await signInWithRedirect(auth, provider);
|
| 311 |
} catch (error) {
|
| 312 |
console.error("Error Google:", error);
|
| 313 |
}
|
|
@@ -419,7 +406,6 @@ export default function App() {
|
|
| 419 |
Subir Video
|
| 420 |
</button>
|
| 421 |
|
| 422 |
-
{/* NUEVO: BOTON EMAIL MASIVO */}
|
| 423 |
{user.email === ADMIN_EMAIL && (
|
| 424 |
<button
|
| 425 |
onClick={handleMassEmail}
|
|
@@ -436,25 +422,27 @@ export default function App() {
|
|
| 436 |
{currentVideoId ? (
|
| 437 |
<div id="player" className="w-full h-full"></div>
|
| 438 |
) : (
|
| 439 |
-
<div className="text-gray-500 animate-pulse">
|
| 440 |
)}
|
| 441 |
</div>
|
| 442 |
<div className="mt-3 p-3 bg-gray-800 rounded">
|
| 443 |
-
<h3 className="text-white font-medium">{currentVideoTitle || "
|
| 444 |
</div>
|
| 445 |
</div>
|
| 446 |
<aside className="lg:w-1/3 flex flex-col gap-4 h-full overflow-hidden">
|
| 447 |
<div className="bg-gray-800 rounded p-4 flex-1 flex flex-col min-h-[200px]">
|
| 448 |
<h3 className="text-gray-400 text-sm uppercase font-bold mb-2">Lista de Reproducción</h3>
|
| 449 |
<div className="overflow-y-auto flex-1 pr-2 custom-scrollbar space-y-2">
|
| 450 |
-
{videos.map((video) => (
|
| 451 |
<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'}`}>
|
| 452 |
<div className="text-sm text-white truncate flex-1">{video.title}</div>
|
| 453 |
{user?.email === ADMIN_EMAIL && (
|
| 454 |
<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>
|
| 455 |
)}
|
| 456 |
</button>
|
| 457 |
-
))
|
|
|
|
|
|
|
| 458 |
</div>
|
| 459 |
</div>
|
| 460 |
|
|
|
|
| 4 |
getAuth,
|
| 5 |
onAuthStateChanged,
|
| 6 |
GoogleAuthProvider,
|
| 7 |
+
signInWithPopup,
|
|
|
|
| 8 |
signOut
|
| 9 |
} from 'firebase/auth';
|
| 10 |
import {
|
|
|
|
| 21 |
query,
|
| 22 |
orderBy,
|
| 23 |
limit,
|
| 24 |
+
writeBatch,
|
| 25 |
+
getDocs
|
| 26 |
} from 'firebase/firestore';
|
| 27 |
|
| 28 |
const firebaseConfig = {
|
|
|
|
| 40 |
|
| 41 |
const ADMIN_EMAIL = "dimensionalpulsar@gmail.com";
|
| 42 |
const VIDEO_COST = 10000;
|
|
|
|
|
|
|
| 43 |
|
| 44 |
// Datos de EmailJS
|
| 45 |
const EMAILJS_SERVICE_ID = "service_v18p188";
|
|
|
|
| 67 |
const initialLoadRef = useRef(true);
|
| 68 |
|
| 69 |
useEffect(() => {
|
|
|
|
|
|
|
|
|
|
| 70 |
const unsubscribeAuth = onAuthStateChanged(auth, (user) => {
|
| 71 |
if (user) {
|
| 72 |
setUser(user);
|
|
|
|
| 90 |
} else {
|
| 91 |
const newProfile = {
|
| 92 |
email: user.email,
|
| 93 |
+
coins: 0
|
|
|
|
| 94 |
};
|
| 95 |
setDoc(profileRef, newProfile).catch(e => console.error(e));
|
| 96 |
setUserProfile(newProfile);
|
|
|
|
| 99 |
return () => unsubscribeProfile();
|
| 100 |
}, [userId, user]);
|
| 101 |
|
| 102 |
+
// Lógica de Reseteo Diario (Para el admin)
|
| 103 |
useEffect(() => {
|
| 104 |
if (user?.email !== ADMIN_EMAIL) return;
|
| 105 |
|
|
|
|
| 131 |
checkReset();
|
| 132 |
}, [user]);
|
| 133 |
|
| 134 |
+
// Lista de videos (Randomizada y sin video por defecto)
|
| 135 |
useEffect(() => {
|
| 136 |
const q = query(collection(db, 'videos'), orderBy('createdAt', 'desc'), limit(50));
|
| 137 |
const unsubscribe = onSnapshot(q, (snapshot) => {
|
| 138 |
let fetchedVideos = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
|
| 139 |
|
| 140 |
if (fetchedVideos.length > 0) {
|
| 141 |
+
// Randomizar la lista de videos
|
| 142 |
fetchedVideos = fetchedVideos.sort(() => Math.random() - 0.5);
|
| 143 |
|
| 144 |
setVideos(fetchedVideos);
|
|
|
|
| 149 |
initialLoadRef.current = false;
|
| 150 |
}
|
| 151 |
} else {
|
| 152 |
+
setVideos([]);
|
| 153 |
if (initialLoadRef.current || !currentVideoId) {
|
| 154 |
+
setCurrentVideoId(null);
|
| 155 |
+
setCurrentVideoTitle("Sin videos disponibles");
|
| 156 |
initialLoadRef.current = false;
|
| 157 |
}
|
| 158 |
}
|
|
|
|
| 222 |
const state = event.data;
|
| 223 |
if (state === window.YT.PlayerState.PLAYING) startCoinInterval();
|
| 224 |
else stopCoinInterval();
|
| 225 |
+
}, [startCoinInterval, stopCoinInterval]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
useEffect(() => {
|
| 228 |
if (ytApiReady && userId && !playerRef.current && currentVideoId) {
|
|
|
|
| 269 |
}
|
| 270 |
};
|
| 271 |
|
| 272 |
+
// Función para Email Masivo
|
| 273 |
const handleMassEmail = async () => {
|
| 274 |
if (!window.emailjs) return alert("EmailJS no está cargado. Revisa tu index.html.");
|
| 275 |
if (!confirm("¿Enviar notificación de reseteo a todos los usuarios?")) return;
|
|
|
|
| 290 |
alert(`Correos en proceso de envío para ${count} usuarios.`);
|
| 291 |
};
|
| 292 |
|
| 293 |
+
// Uso de Popup original restaurado
|
| 294 |
const handleGoogleLogin = async () => {
|
| 295 |
const provider = new GoogleAuthProvider();
|
| 296 |
try {
|
| 297 |
+
await signInWithPopup(auth, provider);
|
|
|
|
| 298 |
} catch (error) {
|
| 299 |
console.error("Error Google:", error);
|
| 300 |
}
|
|
|
|
| 406 |
Subir Video
|
| 407 |
</button>
|
| 408 |
|
|
|
|
| 409 |
{user.email === ADMIN_EMAIL && (
|
| 410 |
<button
|
| 411 |
onClick={handleMassEmail}
|
|
|
|
| 422 |
{currentVideoId ? (
|
| 423 |
<div id="player" className="w-full h-full"></div>
|
| 424 |
) : (
|
| 425 |
+
<div className="text-gray-500 animate-pulse">Esperando videos... ¡Sube el tuyo!</div>
|
| 426 |
)}
|
| 427 |
</div>
|
| 428 |
<div className="mt-3 p-3 bg-gray-800 rounded">
|
| 429 |
+
<h3 className="text-white font-medium">{currentVideoTitle || "Aún no hay videos"}</h3>
|
| 430 |
</div>
|
| 431 |
</div>
|
| 432 |
<aside className="lg:w-1/3 flex flex-col gap-4 h-full overflow-hidden">
|
| 433 |
<div className="bg-gray-800 rounded p-4 flex-1 flex flex-col min-h-[200px]">
|
| 434 |
<h3 className="text-gray-400 text-sm uppercase font-bold mb-2">Lista de Reproducción</h3>
|
| 435 |
<div className="overflow-y-auto flex-1 pr-2 custom-scrollbar space-y-2">
|
| 436 |
+
{videos.length > 0 ? videos.map((video) => (
|
| 437 |
<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'}`}>
|
| 438 |
<div className="text-sm text-white truncate flex-1">{video.title}</div>
|
| 439 |
{user?.email === ADMIN_EMAIL && (
|
| 440 |
<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>
|
| 441 |
)}
|
| 442 |
</button>
|
| 443 |
+
)) : (
|
| 444 |
+
<div className="text-center text-gray-500 text-sm py-4">No hay videos en la red.</div>
|
| 445 |
+
)}
|
| 446 |
</div>
|
| 447 |
</div>
|
| 448 |
|