Delete miniapp/index.html

This commit is contained in:
Egor
2025-10-01 02:08:17 +03:00
committed by GitHub
parent 59efd837cd
commit 1ed418b176
-724
View File
@@ -1,724 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#2481cc">
<title>VPN Subscription</title>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
:root {
--tg-theme-bg-color: #ffffff;
--tg-theme-text-color: #000000;
--tg-theme-hint-color: #999999;
--tg-theme-link-color: #2481cc;
--tg-theme-button-color: #2481cc;
--tg-theme-button-text-color: #ffffff;
--tg-theme-secondary-bg-color: #f0f0f0;
--primary: var(--tg-theme-button-color);
--text-primary: var(--tg-theme-text-color);
--text-secondary: var(--tg-theme-hint-color);
--bg-primary: var(--tg-theme-bg-color);
--bg-secondary: var(--tg-theme-secondary-bg-color);
--border-color: rgba(0, 0, 0, 0.08);
--shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
--radius: 12px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
padding: 16px;
padding-bottom: 32px;
min-height: 100vh;
overflow-x: hidden;
}
.container {
max-width: 480px;
margin: 0 auto;
}
/* Header */
.header {
text-align: center;
margin-bottom: 24px;
}
.logo {
font-size: 28px;
font-weight: 700;
color: var(--primary);
margin-bottom: 4px;
}
.subtitle {
font-size: 14px;
color: var(--text-secondary);
}
/* Loading State */
.loading {
text-align: center;
padding: 60px 20px;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--bg-secondary);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Card */
.card {
background: var(--bg-secondary);
border-radius: var(--radius);
padding: 16px;
margin-bottom: 12px;
box-shadow: var(--shadow);
}
.card-title {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
/* User Info */
.user-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.user-avatar {
width: 56px;
height: 56px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary), #1a6fb8);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 700;
flex-shrink: 0;
}
.user-info {
flex: 1;
min-width: 0;
}
.user-name {
font-size: 18px;
font-weight: 600;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.status-active {
background: #d4f4dd;
color: #0f5132;
}
.status-expired {
background: #f8d7da;
color: #842029;
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 12px;
}
.stat-item {
text-align: center;
padding: 16px 12px;
background: white;
border-radius: 10px;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: var(--primary);
margin-bottom: 4px;
}
.stat-label {
font-size: 12px;
color: var(--text-secondary);
}
/* Info Items */
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid var(--border-color);
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-size: 14px;
color: var(--text-secondary);
}
.info-value {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
text-align: right;
}
/* Buttons */
.btn {
width: 100%;
padding: 14px 20px;
border: none;
border-radius: var(--radius);
font-size: 15px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.2s;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:active {
transform: scale(0.98);
opacity: 0.9;
}
.btn-secondary {
background: white;
color: var(--primary);
border: 1px solid var(--border-color);
}
.btn-icon {
width: 20px;
height: 20px;
}
/* App Installation */
.app-section {
margin-top: 24px;
}
.platform-selector {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-bottom: 16px;
}
.platform-btn {
padding: 12px 8px;
background: white;
border: 2px solid var(--border-color);
border-radius: var(--radius);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.platform-btn.active {
border-color: var(--primary);
background: rgba(36, 129, 204, 0.1);
color: var(--primary);
}
.app-card {
background: white;
border-radius: var(--radius);
padding: 16px;
margin-bottom: 12px;
border: 2px solid var(--border-color);
}
.app-card.featured {
border-color: var(--primary);
background: rgba(36, 129, 204, 0.02);
}
.app-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.app-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: var(--primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 700;
}
.app-name {
font-size: 16px;
font-weight: 600;
}
.featured-badge {
display: inline-block;
background: var(--primary);
color: white;
font-size: 10px;
font-weight: 700;
padding: 3px 8px;
border-radius: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.app-steps {
margin-top: 12px;
}
.step {
margin-bottom: 12px;
}
.step-title {
font-size: 13px;
font-weight: 600;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 8px;
}
.step-number {
width: 20px;
height: 20px;
background: var(--primary);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
}
.step-description {
font-size: 13px;
color: var(--text-secondary);
line-height: 1.5;
margin-bottom: 8px;
}
.step-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.step-btn {
padding: 8px 14px;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
display: inline-block;
}
/* Error State */
.error {
text-align: center;
padding: 40px 20px;
}
.error-icon {
font-size: 48px;
margin-bottom: 12px;
}
.error-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
.error-text {
font-size: 14px;
color: var(--text-secondary);
}
/* Hidden */
.hidden {
display: none !important;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<div class="logo">RemnaWave VPN</div>
<div class="subtitle">Secure & Fast Connection</div>
</div>
<!-- Loading State -->
<div id="loadingState" class="loading">
<div class="spinner"></div>
<div>Loading your subscription...</div>
</div>
<!-- Error State -->
<div id="errorState" class="error hidden">
<div class="error-icon">⚠️</div>
<div class="error-title">Subscription Not Found</div>
<div class="error-text">Please contact support to activate your subscription</div>
</div>
<!-- Main Content -->
<div id="mainContent" class="hidden">
<!-- User Card -->
<div class="card">
<div class="user-header">
<div class="user-avatar" id="userAvatar">U</div>
<div class="user-info">
<div class="user-name" id="userName">Loading...</div>
<span class="status-badge status-active" id="statusBadge">Active</span>
</div>
</div>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value" id="daysLeft">-</div>
<div class="stat-label">Days Left</div>
</div>
<div class="stat-item">
<div class="stat-value" id="serversCount">-</div>
<div class="stat-label">Servers</div>
</div>
</div>
<div class="info-item">
<span class="info-label">Expires</span>
<span class="info-value" id="expiresAt">-</span>
</div>
<div class="info-item">
<span class="info-label">Traffic Used</span>
<span class="info-value" id="trafficUsed">-</span>
</div>
<div class="info-item">
<span class="info-label">Traffic Limit</span>
<span class="info-value" id="trafficLimit">-</span>
</div>
</div>
<!-- Action Buttons -->
<button class="btn btn-primary" id="connectBtn">
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
Connect to VPN
</button>
<button class="btn btn-secondary" id="copyBtn" style="margin-top: 8px;">
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
</svg>
Copy Subscription Link
</button>
<!-- App Installation Section -->
<div class="app-section">
<div class="card">
<div class="card-title">Installation Guide</div>
<div class="platform-selector">
<button class="platform-btn" data-platform="ios">iOS</button>
<button class="platform-btn active" data-platform="android">Android</button>
<button class="platform-btn" data-platform="pc">PC</button>
<button class="platform-btn" data-platform="tv">TV</button>
</div>
<div id="appsContainer">
<!-- Apps will be rendered here -->
</div>
</div>
</div>
</div>
</div>
<script>
// Initialize Telegram WebApp
const tg = window.Telegram.WebApp;
tg.expand();
tg.ready();
// Apply Telegram theme
if (tg.themeParams) {
Object.keys(tg.themeParams).forEach(key => {
document.documentElement.style.setProperty(`--tg-theme-${key.replace(/_/g, '-')}`, tg.themeParams[key]);
});
}
// State
let userData = null;
let appsConfig = null;
let currentPlatform = 'android';
// Initialize
async function init() {
try {
// Get user data from Telegram
const telegramUser = tg.initDataUnsafe?.user;
if (!telegramUser) {
throw new Error('No Telegram user data');
}
// Load apps config
await loadAppsConfig();
// Fetch user subscription data from your API
// Replace this URL with your actual API endpoint
const response = await fetch(`/api/subscription/${telegramUser.id}`);
if (!response.ok) {
throw new Error('Subscription not found');
}
userData = await response.json();
// Render UI
renderUserData();
detectPlatform();
renderApps();
document.getElementById('loadingState').classList.add('hidden');
document.getElementById('mainContent').classList.remove('hidden');
} catch (error) {
console.error('Initialization error:', error);
document.getElementById('loadingState').classList.add('hidden');
document.getElementById('errorState').classList.remove('hidden');
}
}
async function loadAppsConfig() {
const response = await fetch('/assets/app-config.json');
appsConfig = await response.json();
}
function renderUserData() {
if (!userData?.response) return;
const { user } = userData.response;
// User info
const avatar = user.username?.[0]?.toUpperCase() || 'U';
document.getElementById('userAvatar').textContent = avatar;
document.getElementById('userName').textContent = user.username || 'User';
// Status
const statusBadge = document.getElementById('statusBadge');
statusBadge.textContent = user.userStatus;
statusBadge.className = `status-badge status-${user.userStatus.toLowerCase()}`;
// Stats
const expiresDate = new Date(user.expiresAt);
const daysLeft = Math.ceil((expiresDate - new Date()) / (1000 * 60 * 60 * 24));
document.getElementById('daysLeft').textContent = daysLeft > 0 ? daysLeft : '0';
const serversCount = userData.response.links?.length || 0;
document.getElementById('serversCount').textContent = serversCount;
// Details
document.getElementById('expiresAt').textContent = expiresDate.toLocaleDateString();
document.getElementById('trafficUsed').textContent = user.trafficUsed || '0 GB';
document.getElementById('trafficLimit').textContent = user.trafficLimit === '0' ? 'Unlimited' : user.trafficLimit;
}
function detectPlatform() {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.includes('iphone') || userAgent.includes('ipad')) {
currentPlatform = 'ios';
} else if (userAgent.includes('android')) {
currentPlatform = 'android';
} else {
currentPlatform = 'pc';
}
document.querySelector(`.platform-btn[data-platform="${currentPlatform}"]`)?.classList.add('active');
}
function renderApps() {
const apps = appsConfig?.[currentPlatform] || [];
const container = document.getElementById('appsContainer');
container.innerHTML = apps.map(app => `
<div class="app-card ${app.isFeatured ? 'featured' : ''}">
<div class="app-header">
<div class="app-icon">${app.name[0]}</div>
<div>
<div class="app-name">${app.name}</div>
${app.isFeatured ? '<span class="featured-badge">Recommended</span>' : ''}
</div>
</div>
<div class="app-steps">
${renderAppSteps(app)}
</div>
</div>
`).join('');
}
function renderAppSteps(app) {
let html = '';
let stepNum = 1;
if (app.installationStep) {
html += `
<div class="step">
<div class="step-title">
<span class="step-number">${stepNum}</span>
Download & Install
</div>
${app.installationStep.description ? `<div class="step-description">${getLocalizedText(app.installationStep.description)}</div>` : ''}
${app.installationStep.buttons ? `
<div class="step-buttons">
${app.installationStep.buttons.map(btn => `
<a href="${btn.buttonLink}" class="step-btn" target="_blank">
${getLocalizedText(btn.buttonText)}
</a>
`).join('')}
</div>
` : ''}
</div>
`;
stepNum++;
}
if (app.addSubscriptionStep) {
html += `
<div class="step">
<div class="step-title">
<span class="step-number">${stepNum}</span>
Add Subscription
</div>
<div class="step-description">${getLocalizedText(app.addSubscriptionStep.description)}</div>
</div>
`;
stepNum++;
}
if (app.connectAndUseStep) {
html += `
<div class="step">
<div class="step-title">
<span class="step-number">${stepNum}</span>
Connect & Use
</div>
<div class="step-description">${getLocalizedText(app.connectAndUseStep.description)}</div>
</div>
`;
}
return html;
}
function getLocalizedText(textObj) {
if (typeof textObj === 'string') return textObj;
return textObj.ru || textObj.en || '';
}
// Event Listeners
document.querySelectorAll('.platform-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.platform-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentPlatform = btn.dataset.platform;
renderApps();
});
});
document.getElementById('connectBtn')?.addEventListener('click', () => {
if (userData?.response?.subscriptionUrl) {
const app = appsConfig?.[currentPlatform]?.find(a => a.isFeatured) || appsConfig?.[currentPlatform]?.[0];
if (app?.urlScheme) {
window.location.href = app.urlScheme + userData.response.subscriptionUrl;
} else {
window.location.href = userData.response.subscriptionUrl;
}
}
});
document.getElementById('copyBtn')?.addEventListener('click', () => {
if (userData?.response?.subscriptionUrl) {
navigator.clipboard.writeText(userData.response.subscriptionUrl).then(() => {
tg.showPopup({
title: 'Copied!',
message: 'Subscription link copied to clipboard',
buttons: [{type: 'ok'}]
});
});
}
});
// Start
init();
</script>
</body>
</html>