Auto commit: 2026-02-17T19:12:57.521Z
This commit is contained in:
parent
d10f6425ae
commit
9a708e280e
33
api/get_active_perks.php
Normal file
33
api/get_active_perks.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../db/config.php";
|
||||
header("Content-Type: application/json");
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$now = date("Y-m-d H:i:s");
|
||||
|
||||
// Get latest active perk for each type
|
||||
$stmt = $pdo->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()]);
|
||||
}
|
||||
55
api/shop.php
Normal file
55
api/shop.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../db/config.php";
|
||||
header("Content-Type: application/json");
|
||||
|
||||
$pdo = db();
|
||||
$action = $_POST["action"] ?? "";
|
||||
$username = $_POST["username"] ?? "";
|
||||
$value = $_POST["value"] ?? "";
|
||||
|
||||
if (!$username) {
|
||||
echo json_encode(["success" => 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()]);
|
||||
}
|
||||
9
db/migrations/20260217_create_shop_perks.sql
Normal file
9
db/migrations/20260217_create_shop_perks.sql
Normal file
@ -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
|
||||
);
|
||||
265
index.php
265
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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -1318,6 +1419,9 @@ $twitter_link = "https://twitter.com/";
|
||||
<div class="glass-card">
|
||||
<header class="brand">
|
||||
<div style="position: absolute; top: 1rem; right: 1rem; display: flex; gap: 0.5rem; z-index: 100;">
|
||||
<button onclick="openShopModal()" style="background: linear-gradient(135deg, #facc15, #fbbf24); border: none; color: #000; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 0 15px rgba(250, 204, 21, 0.4); transition: all 0.3s; z-index: 101;" title="Tienda de la Radio">
|
||||
<i class="bi bi-shop"></i>
|
||||
</button>
|
||||
<button onclick="toggleTheme()" id="theme-toggle" style="background: var(--glass-bg); border: 1px solid var(--glass-border); color: var(--text-color); width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; backdrop-filter: blur(5px); transition: all 0.3s;" title="Cambiar tema">
|
||||
<i class="bi bi-moon-fill"></i>
|
||||
</button>
|
||||
@ -1529,6 +1633,11 @@ $twitter_link = "https://twitter.com/";
|
||||
<h3 style="font-size: 1.2rem; margin-bottom: 1rem; color: var(--primary-color);">
|
||||
<i class="bi bi-chat-dots-fill"></i> CHAT EN VIVO
|
||||
</h3>
|
||||
<div id="pinned-message" class="pinned-message-container">
|
||||
<div class="pinned-label"><i class="bi bi-pin-angle-fill"></i> MENSAJE FIJADO</div>
|
||||
<div id="pinned-content" style="font-size: 0.85rem; font-weight: 600;"></div>
|
||||
<div id="pinned-author" style="font-size: 0.65rem; opacity: 0.7; margin-top: 4px;"></div>
|
||||
</div>
|
||||
<div id="chat-messages" style="flex: 1; overflow-y: auto; margin-bottom: 1rem; padding-right: 5px; display: flex; flex-direction: column; gap: 0.8rem;">
|
||||
<!-- Mensajes se cargarán aquí -->
|
||||
<div style="opacity: 0.5; font-size: 0.9rem; text-align: center; margin-top: 2rem;">Cargando mensajes...</div>
|
||||
@ -1736,6 +1845,40 @@ $twitter_link = "https://twitter.com/";
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Shop Modal -->
|
||||
<div id="shop-modal" class="shop-modal" onclick="closeShopModal()">
|
||||
<div class="shop-modal-content" onclick="event.stopPropagation()">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
|
||||
<h2 style="margin: 0; color: #facc15;"><i class="bi bi-shop"></i> Tienda Radio</h2>
|
||||
<div id="shop-user-points" style="background: rgba(250, 204, 21, 0.2); color: #facc15; padding: 5px 15px; border-radius: 20px; font-weight: 800; font-size: 0.9rem;">
|
||||
0 pts
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style="opacity: 0.7; font-size: 0.9rem; margin-bottom: 2rem;">Gasta tus puntos en perks globales para todos los oyentes.</p>
|
||||
|
||||
<div class="shop-item" onclick="promptBuyPinnedMessage()">
|
||||
<div class="shop-item-icon"><i class="bi bi-pin-angle-fill"></i></div>
|
||||
<div class="shop-item-info">
|
||||
<div class="shop-item-name">Mensaje Fijado</div>
|
||||
<div class="shop-item-desc">Tu mensaje al inicio del chat por 1 hora.</div>
|
||||
</div>
|
||||
<div class="shop-item-price">500 <i class="bi bi-lightning-fill"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="shop-item" onclick="promptBuyBackground()">
|
||||
<div class="shop-item-icon"><i class="bi bi-image-fill"></i></div>
|
||||
<div class="shop-item-info">
|
||||
<div class="shop-item-name">Cambiar Fondo</div>
|
||||
<div class="shop-item-desc">Cambia el fondo de la radio por 30 min.</div>
|
||||
</div>
|
||||
<div class="shop-item-price">1000 <i class="bi bi-lightning-fill"></i></div>
|
||||
</div>
|
||||
|
||||
<button onclick="closeShopModal()" style="width: 100%; margin-top: 1.5rem; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); color: white; padding: 0.8rem; border-radius: 12px; cursor: pointer; font-weight: 700;">Cerrar Tienda</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
|
||||
<audio id="radio-audio" src="https://listen.radioking.com/radio/828046/stream/897251" preload="auto" crossorigin="anonymous"></audio>
|
||||
<audio id="welcome-sound" src="https://assets.mixkit.co/active_storage/sfx/2013/2013-preview.mp3" preload="auto"></audio>
|
||||
@ -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 ---
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user