/
SaiRaz
/
ReviewTasksBot
Обзор
Документация
Войти
/
SaiRaz
/
ReviewTasksBot
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
static/index.html
612 строк
21 KB
Sa1Raz
Update index.html
13 дек 2025, 12:25
Не верифицирован
13 дек 2025, 12:25
a786666
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ReviewCash — Кабинет</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet"> <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script> <script src="https://telegram.org/js/telegram-web-app.js"></script> <style> :root { --radius: 16px; --transition: 260ms cubic-bezier(.4, 0, .2, 1); --accent: #00eaff; --accent2: #00b4ff; --muted: #8fc7d4; --dark-bg1: #03040a; --dark-bg2: #071018; --light-bg1: #ffffff; --light-bg2: #eef7ff; --card-dark: rgba(255, 255, 255, 0.06); --card-light: rgba(0, 40, 70, 0.06); --shadow-dark: 0 10px 35px rgba(0, 0, 0, 0.45); --shadow-light: 0 8px 20px rgba(0, 0, 0, 0.10); --modal-dark: #0e1a24; --modal-light: #fff; } body { margin: 0; font-family: Inter, system-ui; color: #dff7ff; background: radial-gradient(900px 600px at 15% 10%, rgba(0, 255, 230, 0.03), transparent), linear-gradient(180deg, var(--dark-bg1), var(--dark-bg2)); transition: background var(--transition), color var(--transition); } body.light { color: #062029; background: radial-gradient(1200px 900px at 50% 0%, rgba(0, 120, 180, 0.05), transparent), linear-gradient(180deg, var(--light-bg1), var(--light-bg2)); } .wrap { max-width: 1020px; margin: 24px auto 140px; padding: 14px; animation: fadeIn 0.5s ease forwards; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .loader { position: fixed; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #000c; backdrop-filter: blur(6px); z-index: 9999; animation: fadeIn .3s ease; color: white; } .loader.hidden { display: none; } .loader-brand { font-size: 32px; font-weight: 900; background: linear-gradient(90deg, var(--accent), var(--accent2)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .loader-spinner { width: 38px; height: 38px; border: 4px solid var(--accent); border-top-color: transparent; border-radius: 50%; margin-top: 18px; animation: spin 0.9s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .card { padding: 18px; border-radius: 20px; background: var(--card-dark); border: 1px solid rgba(255, 255, 255, 0.06); box-shadow: var(--shadow-dark); backdrop-filter: blur(6px); animation: pop .35s ease; } body.light .card { background: rgba(255, 255, 255, 0.9); border-color: var(--card-light); box-shadow: var(--shadow-light); } .btn { padding: 12px 18px; border-radius: 14px; font-weight: 800; cursor: pointer; border: none; background: linear-gradient(90deg, var(--accent), var(--accent2)); color: #001; transition: transform .18s, box-shadow .18s; box-shadow: 0 12px 30px rgba(0, 255, 255, 0.2); } .btn:active { transform: scale(.97); } .btn.small { padding: 9px 12px; border-radius: 12px; } .btn.ghost { background: transparent; border: 1px solid rgba(255, 255, 255, 0.15); color: inherit; box-shadow: none; } body.light .btn.ghost { border-color: rgba(0, 40, 60, 0.25); } .btn.pulse { animation: btnPulse 2.4s infinite; } @keyframes btnPulse { 0% { box-shadow: 0 0 8px rgba(0, 255, 255, 0.4); } 50% { box-shadow: 0 0 20px rgba(0, 255, 255, 0.2); } 100% { box-shadow: 0 0 8px rgba(0, 255, 255, 0.4); } } .task-item { margin-bottom: 12px; display: flex; justify-content: space-between; gap: 14px; padding: 16px; border-radius: 16px; background: rgba(255, 255, 255, 0.04); transition: transform .2s, background .25s; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35); } .task-item:hover { transform: translateY(-3px); background: rgba(255, 255, 255, 0.07); } body.light .task-item { background: rgba(0, 0, 0, 0.07); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12); } .modal-bg { display: none; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.65); backdrop-filter: blur(4px); justify-content: center; align-items: center; z-index: 10000; animation: fadeIn .25s ease; } .modal-bg.open { display: flex; } .modal { width: 92%; max-width: 760px; padding: 20px; border-radius: 22px; background: var(--modal-dark); color: white; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55); animation: pop .25s ease; } body.light .modal { background: var(--modal-light); color: #062029; } input, textarea { width: 100%; padding: 12px; border-radius: 14px; border: 1px solid rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.05); color: inherit; transition: border var(--transition), background var(--transition); } body.light input { background: rgba(0, 0, 0, 0.05); border: 1px solid rgba(0, 40, 60, 0.2); } input:focus { border-color: var(--accent); outline: none; background: rgba(0, 255, 255, 0.07); } body.light input:focus { background: rgba(0, 120, 180, 0.07); } .fab { position: fixed; right: 16px; bottom: 100px; width: 64px; height: 64px; border-radius: 50%; background: linear-gradient(90deg, var(--accent), var(--accent2)); color: #001; font-size: 30px; font-weight: 900; display: flex; justify-content: center; align-items: center; box-shadow: 0 16px 50px rgba(0, 255, 255, 0.25); cursor: pointer; transition: transform .2s; } .fab:hover { transform: scale(1.08); } .fab:active { transform: scale(.95); } @media (max-width: 540px) { .avatar { width: 66px; height: 66px; } .fab { width: 56px; height: 56px; bottom: 92px; } } </style> </head> <body> <div id="loader" class="loader" role="status" aria-live="polite"> <div class="loader-brand">ReviewCash</div> <div class="loader-sub small">Подготовка приложения...</div> <div class="loader-spinner" aria-hidden="true"></div> </div> <main id="app" class="wrap" style="display: none;" aria-hidden="true"> <section id="profileCard" class="card" aria-labelledby="profile-heading"> <h2 id="profile-heading" class="sr-only">Профиль</h2> <div class="header-row"> <div class="avatar" aria-hidden="true"><img id="avatarImg" src="" alt="Avatar"></div> <div class="profile-info"> <div class="name" id="userName">Загрузка...</div> <div class="uid small" id="userHandle">—</div> <div style="margin-top: 12px;"> <div class="small">Баланс:</div> <div class="balance" id="balance">0 ₽</div> </div> </div> <div class="profile-actions" style="text-align: right;"> <button id="topupBtn" class="btn pulse" title="Пополнить баланс">Пополнить</button> <button id="withdrawBtn" class="btn ghost small" title="Вывести средства" style="margin-top: 10px;">Вывести</button> </div> </div> <div class="profile-bottom row" style="margin-top: 12px;"> <button id="supportBtn" class="btn ghost small">Поддержка</button> <button id="themeToggle" class="btn ghost small">Тема</button> <button id="refreshBtn" class="btn ghost small">Обновить</button> </div> </section> <section id="tasksCard" class="card" aria-labelledby="tasks-heading"> <h2 id="tasks-heading" style="font-weight: 900; font-size: 18px;">Задания</h2> <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 8px;"> <div class="sub-tabs" id="subTabs" role="tablist"> <button class="sub-tab active" data-sub="available" role="tab" aria-selected="true">Доступные</button> <button class="sub-tab" data-sub="my" role="tab" aria-selected="false">Мои</button> </div> <div class="small" id="tasksCount">—</div> </div> <div id="tasksList" style="margin-top: 12px;" role="list" aria-live="polite"> <div class="small">Загрузка...</div> </div> </section> </main> <button class="fab" id="fab" title="Создать задание" aria-label="Создать задание">+</button> <nav class="tabbar" role="tablist" aria-label="Главная навигация"> <div class="nav-item active" data-nav="tasks" role="tab" aria-selected="true">Задания</div> <div class="nav-item" data-nav="profile" role="tab" aria-selected="false">Профиль</div> </nav> <div id="topupModal" class="modal-bg" aria-hidden="true"> <div class="modal" role="dialog" aria-modal="true" aria-labelledby="tp-h"> <button id="closeTopup" aria-label="Закрыть" class="modal-close">×</button> <h3 id="tp-h">Пополнение</h3> <div class="small">Минимальная сумма пополнения: <b id="minTopup">150</b> ₽</div> <div style="display: flex; gap: 10px; margin-top: 12px;"> <input id="topupAmount" type="number" placeholder="Сумма" min="150" style="flex: 1;"/> <button id="createTopupBtn" class="btn">Создать заявку</button> </div> <div id="topupStep" style="display: none; margin-top: 14px;"> <div class="small">Используйте QR-код или вручную введите платежные реквизиты.</div> <img id="qrImg" style="display: block; margin: 12px auto; max-width: 260px; border-radius: 10px;" src="" alt="QR code"/> <input id="manualCode" readonly/> <div style="display: flex; gap: 8px; margin-top: 10px;"> <a id="payLink" class="btn" target="_blank" rel="noopener noreferrer">Открыть банковское приложение</a> <button id="confirmPaidBtn" class="btn ghost">Оплатил</button> </div> <div id="topupMsg" class="small" style="margin-top: 8px;"></div> </div> </div> </div> <div id="withdrawModal" class="modal-bg" aria-hidden="true"> <div class="modal" role="dialog" aria-modal="true"> <button id="closeWithdraw" aria-label="Закрыть" class="modal-close">×</button> <h3>Вывод средств</h3> <div class="small">Минимальная сумма вывода: 300 ₽</div> <input id="wAmount" type="number" placeholder="Сумма"/> <input id="wName" type="text" placeholder="Имя получателя"/> <input id="wDetails" type="text" placeholder="Банковские реквизиты"/> <div style="display: flex; gap: 10px; margin-top: 10px;"> <button id="sendWithdraw" class="btn">Отправить заявку</button> <button id="cancelWithdraw" class="btn ghost">Отмена</button> </div> <div id="withdrawMsg" class="small" style="margin-top: 8px;"></div> </div> </div> <div id="createModal" class="modal-bg" aria-hidden="true"> <div class="modal" role="dialog" aria-modal="true"> <button id="closeCreate" aria-label="Закрыть" class="modal-close">×</button> <h3>Создать задание</h3> <div class="small">Тип задания:</div> <select id="typeSelect" class="small"> <option disabled selected>Выберите тип задания</option> <option value="review_ya">Яндекс Отзывы</option> <option value="review_google">Google Отзывы</option> <option value="subscription_tg">Телеграм Подписки</option> </select> <br><br> <label for="taskUrl">URL сайта:</label> <input id="taskUrl" type="text" placeholder="URL адрес"/> <br><br> <label for="taskQuantity">Количество:</label> <input id="taskQuantity" type="number" placeholder="Количество заданий"/> <br><br> <button id="submitTask" class="btn">Создать задание</button> <div id="createMsg" class="small" style="margin-top: 8px;"></div> </div> </div> <script> (() => { const $ = selector => document.querySelector(selector); const $$ = selector => [...document.querySelectorAll(selector)]; const loader = $("#loader"); const app = $("#app"); const profileCard = $("#profileCard"); const tasksCard = $("#tasksCard"); const tabbar = $(".tabbar"); const fab = $(".fab"); const avatarImg = $("#avatarImg"); const userName = $("#userName"); const userHandle = $("#userHandle"); const balance = $("#balance"); const topupBtn = $("#topupBtn"); const withdrawBtn = $("#withdrawBtn"); const supportBtn = $("#supportBtn"); const themeToggle = $("#themeToggle"); const refreshBtn = $("#refreshBtn"); const topupModal = $("#topupModal"); const closeTopup = $("#closeTopup"); const topupAmount = $("#topupAmount"); const createTopupBtn = $("#createTopupBtn"); const topupStep = $("#topupStep"); const qrImg = $("#qrImg"); const manualCode = $("#manualCode"); const payLink = $("#payLink"); const confirmPaidBtn = $("#confirmPaidBtn"); const topupMsg = $("#topupMsg"); const minTopup = $("#minTopup"); const withdrawModal = $("#withdrawModal"); const closeWithdraw = $("#closeWithdraw"); const cancelWithdraw = $("#cancelWithdraw"); const sendWithdraw = $("#sendWithdraw"); const wAmount = $("#wAmount"); const wName = $("#wName"); const wDetails = $("#wDetails"); const withdrawMsg = $("#withdrawMsg"); const createModal = $("#createModal"); const closeCreate = $("#closeCreate"); const cancelCreate = $("#cancelCreate"); const submitTask = $("#submitTask"); const createMsg = $("#createMsg"); const typeSelect = $("#typeSelect"); const taskUrl = $("#taskUrl"); const taskQuantity = $("#taskQuantity"); let tgUser = {}; let socket = null; let selectedType = null; let pendingRequests = new Set(); const CREATOR_PRICES = { review_ya: 120, review_google: 65, subscription_tg: 10 }; const WORKER_PRICES = { review_ya: 100, review_google: 50, subscription_tg: 5 }; function applyTheme() { const isLight = localStorage.getItem("rc_theme") === "light"; document.body.classList.toggle("light", isLight); themeToggle.textContent = isLight ? "Темная" : "Светлая"; } applyTheme(); function formatMoney(amount) { return `${Number(amount).toFixed(2)} ₽`; } function showLoader() { loader.classList.remove("hidden"); app.style.display = "none"; app.setAttribute("aria-hidden", "true"); } function hideLoader() { loader.classList.add("hidden"); app.style.display = "block"; app.removeAttribute("aria-hidden"); } function renderBalance(newBalance) { balance.textContent = formatMoney(newBalance); } function loadProfile() { showLoader(); fetch("/api/profile") .then(response => response.json()) .then(data => { renderBalance(data.balance); userName.textContent = data.name || "Пользователь"; userHandle.textContent = data.handle || "-"; avatarImg.src = data.avatar || "/static/default_avatar.png"; hideLoader(); }) .catch(error => { console.error("Error loading profile:", error); hideLoader(); }); } function loadTasks() { showLoader(); fetch("/api/tasks") .then(response => response.json()) .then(tasks => { const list = $("#tasksList"); list.innerHTML = ""; tasks.forEach(task => { const taskDiv = document.createElement("div"); taskDiv.classList.add("task-item"); taskDiv.innerHTML = ` <p>Название: ${task.title}</p> <p>Описание: ${task.description}</p> <p>Цена: ${formatMoney(task.price)}</p> `; list.appendChild(taskDiv); }); hideLoader(); }) .catch(error => { console.error("Error loading tasks:", error); hideLoader(); }); } function openModal(modalId) { $(modalId).classList.add("open"); } function closeModal(modalId) { $(modalId).classList.remove("open"); } function validateForm(formData) { const errors = []; Object.keys(formData).forEach(key => { if (!formData[key]) { errors.push(`Поле "${key}" обязательно.`); } }); return errors.join("<br>"); } topupBtn.addEventListener("click", () => openModal("#topupModal")); closeTopup.addEventListener("click", () => closeModal("#topupModal")); createTopupBtn.addEventListener("click", () => { const amount = parseFloat(topupAmount.value); if (amount >= 150) { topupStep.style.display = "block"; qrImg.src = `/generate_qr/${amount}`; payLink.href = `/payment/${amount}`; } else { topupMsg.textContent = "Минимальная сумма пополнения: 150 ₽."; } }); withdrawBtn.addEventListener("click", () => openModal("#withdrawModal")); closeWithdraw.addEventListener("click", () => closeModal("#withdrawModal")); cancelWithdraw.addEventListener("click", () => closeModal("#withdrawModal")); sendWithdraw.addEventListener("click", () => { const formData = { amount: parseFloat(wAmount.value), recipient: wName.value.trim(), details: wDetails.value.trim() }; const validationErrors = validateForm(formData); if (validationErrors) { withdrawMsg.textContent = validationErrors; } else { fetch("/api/withdraw", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData) }).then(response => { if (response.ok) { closeModal("#withdrawModal"); loadProfile(); } else { withdrawMsg.textContent = "Ошибка вывода средств."; } }); } }); fab.addEventListener("click", () => openModal("#createModal")); closeCreate.addEventListener("click", () => closeModal("#createModal")); cancelCreate.addEventListener("click", () => closeModal("#createModal")); submitTask.addEventListener("click", () => { const formData = { type: typeSelect.value, url: taskUrl.value.trim(), quantity: parseInt(taskQuantity.value) }; const validationErrors = validateForm(formData); if (validationErrors) { createMsg.textContent = validationErrors; } else { fetch("/api/create_task", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData) }).then(response => { if (response.ok) { closeModal("#createModal"); loadTasks(); } else { createMsg.textContent = "Ошибка создания задания."; } }); } }); themeToggle.addEventListener("click", () => { const isLight = document.body.classList.contains("light"); localStorage.setItem("rc_theme", isLight ? "dark" : "light"); applyTheme(); }); refreshBtn.addEventListener("click", () => { loadProfile(); loadTasks(); }); loadProfile(); loadTasks(); })(); </script> </body> </html>