diff --git a/api/get_active_perks.php b/api/get_active_perks.php new file mode 100644 index 0000000..4ddaef0 --- /dev/null +++ b/api/get_active_perks.php @@ -0,0 +1,33 @@ +prepare(" + SELECT perk_type, perk_value, user_name, expires_at + FROM shop_perks + WHERE expires_at > ? + ORDER BY created_at DESC + "); + $stmt->execute([$now]); + $all_active = $stmt->fetchAll(); + + $perks = [ + "pinned_message" => null, + "background" => null + ]; + + foreach ($all_active as $p) { + if (!$perks[$p["perk_type"]]) { + $perks[$p["perk_type"]] = $p; + } + } + + echo json_encode(["success" => true, "perks" => $perks]); +} catch (Exception $e) { + echo json_encode(["success" => false, "error" => $e->getMessage()]); +} diff --git a/api/shop.php b/api/shop.php new file mode 100644 index 0000000..793f102 --- /dev/null +++ b/api/shop.php @@ -0,0 +1,55 @@ + false, "error" => "Inicia sesión para comprar"]); + exit; +} + +$perks = [ + "pinned_message" => ["cost" => 500, "duration" => 3600], // 1 hour + "background" => ["cost" => 1000, "duration" => 1800], // 30 mins +]; + +if (!isset($perks[$action])) { + echo json_encode(["success" => false, "error" => "Producto no encontrado"]); + exit; +} + +$perk = $perks[$action]; + +try { + $pdo->beginTransaction(); + + // Check points + $stmt = $pdo->prepare("SELECT points FROM fans WHERE name = ?"); + $stmt->execute([$username]); + $user = $stmt->fetch(); + + if (!$user || $user["points"] < $perk["cost"]) { + echo json_encode(["success" => false, "error" => "No tienes suficientes puntos (" . $perk["cost"] . " requeridos)"]); + $pdo->rollBack(); + exit; + } + + // Deduct points + $stmt = $pdo->prepare("UPDATE fans SET points = points - ? WHERE name = ?"); + $stmt->execute([$perk["cost"], $username]); + + // Activate perk + $expires_at = date("Y-m-d H:i:s", time() + $perk["duration"]); + $stmt = $pdo->prepare("INSERT INTO shop_perks (perk_type, perk_value, user_name, expires_at) VALUES (?, ?, ?, ?)"); + $stmt->execute([$action, $value, $username, $expires_at]); + + $pdo->commit(); + echo json_encode(["success" => true, "message" => "¡Compra exitosa! Perk activado."]); +} catch (Exception $e) { + $pdo->rollBack(); + echo json_encode(["success" => false, "error" => $e->getMessage()]); +} diff --git a/db/migrations/20260217_create_shop_perks.sql b/db/migrations/20260217_create_shop_perks.sql new file mode 100644 index 0000000..76ad155 --- /dev/null +++ b/db/migrations/20260217_create_shop_perks.sql @@ -0,0 +1,9 @@ +-- Migration: Create shop_perks table +CREATE TABLE IF NOT EXISTS shop_perks ( + id INT AUTO_INCREMENT PRIMARY KEY, + perk_type VARCHAR(50) NOT NULL, -- "pinned_message", "background" + perk_value TEXT NOT NULL, -- The message text or the image URL + user_name VARCHAR(255) NOT NULL, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/index.php b/index.php index 6e96944..be6ed47 100644 --- a/index.php +++ b/index.php @@ -1291,6 +1291,107 @@ $twitter_link = "https://twitter.com/"; gap: 5px; font-weight: 700; } + + /* Shop Styles */ + .shop-modal { + display: none; + position: fixed; + z-index: 4000; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.8); + backdrop-filter: blur(10px); + justify-content: center; + align-items: center; + opacity: 0; + transition: opacity 0.3s ease; + } + .shop-modal.show { + display: flex; + opacity: 1; + } + .shop-modal-content { + background: rgba(20, 20, 20, 0.9); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 2.5rem; + border-radius: 32px; + width: 90%; + max-width: 500px; + transform: scale(0.8); + transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); + box-shadow: 0 20px 60px rgba(0,0,0,0.5); + } + .shop-modal.show .shop-modal-content { + transform: scale(1); + } + .shop-item { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 1.2rem; + border-radius: 20px; + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 15px; + transition: all 0.2s; + } + .shop-item:hover { + background: rgba(255, 255, 255, 0.1); + border-color: var(--primary-color); + } + .shop-item-icon { + width: 50px; + height: 50px; + background: rgba(56, 189, 248, 0.1); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + color: var(--primary-color); + } + .shop-item-info { + flex: 1; + } + .shop-item-name { + font-weight: 800; + font-size: 1rem; + color: white; + } + .shop-item-desc { + font-size: 0.75rem; + opacity: 0.6; + } + .shop-item-price { + font-weight: 800; + color: #facc15; + display: flex; + align-items: center; + gap: 4px; + } + .pinned-message-container { + background: linear-gradient(90deg, rgba(56, 189, 248, 0.2), rgba(0, 200, 83, 0.2)); + border: 1px solid var(--primary-color); + border-radius: 12px; + padding: 0.8rem; + margin-bottom: 1rem; + display: none; + animation: fadeIn 0.5s ease; + position: relative; + } + .pinned-label { + font-size: 0.6rem; + font-weight: 800; + color: var(--primary-color); + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 4px; + display: flex; + align-items: center; + gap: 4px; + } @@ -1318,6 +1419,9 @@ $twitter_link = "https://twitter.com/";
+ @@ -1529,6 +1633,11 @@ $twitter_link = "https://twitter.com/";

CHAT EN VIVO

+
+
MENSAJE FIJADO
+
+
+
Cargando mensajes...
@@ -1736,6 +1845,40 @@ $twitter_link = "https://twitter.com/";
+ +
+
+
+

Tienda Radio

+
+ 0 pts +
+
+ +

Gasta tus puntos en perks globales para todos los oyentes.

+ +
+
+
+
Mensaje Fijado
+
Tu mensaje al inicio del chat por 1 hora.
+
+
500
+
+ +
+
+
+
Cambiar Fondo
+
Cambia el fondo de la radio por 30 min.
+
+
1000
+
+ + +
+
+ @@ -2684,6 +2827,10 @@ $twitter_link = "https://twitter.com/"; const bar = document.getElementById('user-level-bar'); const rewards = document.getElementById('unlocked-rewards-msg'); + // Update shop points display + const shopPoints = document.getElementById('shop-user-points'); + if (shopPoints) shopPoints.innerText = `${parseInt(fan.total_likes).toLocaleString()} pts`; + container.style.display = 'block'; badge.innerText = `Nivel ${fan.level}`; badge.style.background = fan.level_color; @@ -2999,6 +3146,124 @@ $twitter_link = "https://twitter.com/"; if (!audio.paused) audio.play(); }, 5000); }); + + // --- Radio Shop Functionality --- + function openShopModal() { + const modal = document.getElementById('shop-modal'); + const userName = document.getElementById('user-name').value.trim(); + + if (!userName) { + alert('Por favor, ingresa tu nombre arriba para acceder a la tienda.'); + document.getElementById('user-name').focus(); + document.getElementById('user-name').style.borderColor = '#facc15'; + return; + } + + modal.style.display = 'flex'; + setTimeout(() => modal.classList.add('show'), 10); + fetchLeaderboard(); // This updates user points + } + + function closeShopModal() { + const modal = document.getElementById('shop-modal'); + modal.classList.remove('show'); + setTimeout(() => modal.style.display = 'none', 300); + } + + async function fetchActivePerks() { + try { + const response = await fetch('api/get_active_perks.php'); + const data = await response.json(); + + if (data.success) { + // Pinned Message + const pinned = data.perks.pinned_message; + const pinnedEl = document.getElementById('pinned-message'); + if (pinned) { + document.getElementById('pinned-content').innerText = pinned.perk_value; + document.getElementById('pinned-author').innerText = `Por: ${pinned.user_name}`; + pinnedEl.style.display = 'block'; + } else { + pinnedEl.style.display = 'none'; + } + + // Background + const background = data.perks.background; + if (background && bg) { + bg.style.backgroundImage = `url('${background.perk_value}')`; + } + } + } catch (error) { + console.error('Error fetching perks:', error); + } + } + + async function buyPerk(action, value) { + const userName = document.getElementById('user-name').value.trim(); + try { + const formData = new FormData(); + formData.append('action', action); + formData.append('username', userName); + formData.append('value', value); + + const response = await fetch('api/shop.php', { + method: 'POST', + body: formData + }); + const result = await response.json(); + + if (result.success) { + confetti({ + particleCount: 150, + spread: 70, + origin: { y: 0.6 }, + colors: ['#facc15', '#fbbf24', '#ffffff'] + }); + alert(result.message); + closeShopModal(); + fetchActivePerks(); + fetchLeaderboard(); + } else { + alert('Error: ' + result.error); + } + } catch (error) { + console.error('Buy error:', error); + alert('Error de conexión al realizar la compra.'); + } + } + + function promptBuyPinnedMessage() { + const msg = prompt('Escribe el mensaje que quieres fijar (máx 100 carac.):'); + if (msg && msg.trim()) { + if (msg.length > 100) { + alert('El mensaje es demasiado largo.'); + return; + } + buyPerk('pinned_message', msg.trim()); + } + } + + function promptBuyBackground() { + const query = prompt('¿De qué quieres que sea el nuevo fondo? (ej: cyberpunk, ocean, tropical):'); + if (query && query.trim()) { + // Fetch image from pexels first + fetch(`api/pexels.php?query=${encodeURIComponent(query.trim())}`) + .then(res => res.json()) + .then(data => { + if (data.success && data.url) { + if (confirm('¿Te gusta este fondo? Lo activaremos para todos por 1000 pts.')) { + buyPerk('background', data.url); + } + } else { + alert('No encontramos una imagen para esa búsqueda.'); + } + }); + } + } + + setInterval(fetchActivePerks, 10000); + fetchActivePerks(); + // --- End Radio Shop Functionality --- \ No newline at end of file