AeThex-Bot-Master/aethex-bot/public/federation.html
sirpiglr 150ec17072 Add pricing plans and integrate Stripe for server upgrades
Add new styling and HTML structure for pricing cards to federation.html, and configure Stripe webhook handling in webServer.js to manage subscription events and update server tiers in the database.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 2926813b-ebbf-4fd8-aa3c-58eb0dbcf182
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/0iqMcgO
Replit-Helium-Checkpoint-Created: true
2025-12-10 02:08:27 +00:00

833 lines
25 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Federation - AeThex | Warden</title>
<style>
:root {
--background: #030712;
--foreground: #f8fafc;
--card: rgba(15, 23, 42, 0.6);
--card-border: rgba(99, 102, 241, 0.15);
--card-border-hover: rgba(99, 102, 241, 0.4);
--primary: #6366f1;
--primary-light: #818cf8;
--secondary: rgba(30, 41, 59, 0.5);
--muted: #64748b;
--border: rgba(51, 65, 85, 0.5);
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Courier New', Courier, monospace;
background: var(--background);
color: var(--foreground);
min-height: 100vh;
line-height: 1.6;
}
.bg-grid {
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(99, 102, 241, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(99, 102, 241, 0.03) 1px, transparent 1px);
background-size: 64px 64px;
pointer-events: none;
z-index: -2;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
}
header {
background: rgba(3, 7, 18, 0.8);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 1rem 0;
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
color: var(--foreground);
}
.logo-icon { width: 40px; height: 40px; border-radius: 8px; }
.logo-text { font-size: 1.25rem; font-weight: 700; }
.nav-links {
display: flex;
gap: 2rem;
align-items: center;
}
.nav-links a {
color: var(--muted);
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
.nav-links a:hover, .nav-links a.active { color: var(--foreground); }
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1.25rem;
border-radius: 8px;
font-weight: 600;
text-decoration: none;
transition: all 0.2s;
border: none;
cursor: pointer;
font-size: 0.875rem;
font-family: inherit;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), #3b82f6);
color: white;
}
.btn-secondary {
background: var(--secondary);
border: 1px solid var(--border);
color: var(--foreground);
}
.btn-success { background: var(--success); color: white; }
.btn-danger { background: var(--danger); color: white; }
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.8rem; }
.hero {
padding: 3rem 0;
text-align: center;
}
.hero h1 {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.hero p {
color: var(--muted);
font-size: 1.1rem;
}
.text-gradient {
background: linear-gradient(135deg, var(--primary), #3b82f6, #06b6d4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
margin-bottom: 3rem;
}
.stat-card {
background: var(--card);
border: 1px solid var(--card-border);
border-radius: 16px;
padding: 1.5rem;
text-align: center;
}
.stat-value {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, var(--primary), #3b82f6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
color: var(--muted);
font-size: 0.9rem;
margin-top: 0.25rem;
}
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
border-bottom: 1px solid var(--border);
padding-bottom: 1rem;
}
.tab {
padding: 0.75rem 1.5rem;
border-radius: 8px;
cursor: pointer;
background: transparent;
border: 1px solid transparent;
color: var(--muted);
font-family: inherit;
font-weight: 500;
transition: all 0.2s;
}
.tab:hover { color: var(--foreground); }
.tab.active {
background: var(--card);
border-color: var(--primary);
color: var(--foreground);
}
.section { display: none; }
.section.active { display: block; }
.card {
background: var(--card);
border: 1px solid var(--card-border);
border-radius: 16px;
padding: 1.5rem;
margin-bottom: 1rem;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.card-title { font-size: 1.1rem; font-weight: 600; }
.table {
width: 100%;
border-collapse: collapse;
}
.table th, .table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid var(--border);
}
.table th {
color: var(--muted);
font-size: 0.85rem;
font-weight: 500;
}
.badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
}
.badge-low { background: rgba(255, 255, 0, 0.2); color: #ffd700; }
.badge-medium { background: rgba(255, 153, 0, 0.2); color: #ff9900; }
.badge-high { background: rgba(255, 51, 0, 0.2); color: #ff3300; }
.badge-critical { background: rgba(255, 0, 0, 0.2); color: #ff0000; }
.badge-pending { background: rgba(255, 193, 7, 0.2); color: var(--warning); }
.badge-approved { background: rgba(16, 185, 129, 0.2); color: var(--success); }
.server-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.server-card {
background: var(--card);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 1.25rem;
transition: all 0.2s;
}
.server-card:hover {
border-color: var(--card-border-hover);
transform: translateY(-2px);
}
.server-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.server-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: var(--secondary);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.server-name { font-weight: 600; }
.server-category { font-size: 0.8rem; color: var(--muted); }
.server-desc { font-size: 0.9rem; color: var(--muted); margin-bottom: 0.75rem; }
.server-members { font-size: 0.85rem; color: var(--primary-light); }
.leaderboard-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.leaderboard-rank {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
background: var(--secondary);
}
.leaderboard-rank.gold { background: linear-gradient(135deg, #ffd700, #ff8c00); color: #000; }
.leaderboard-rank.silver { background: linear-gradient(135deg, #c0c0c0, #a0a0a0); color: #000; }
.leaderboard-rank.bronze { background: linear-gradient(135deg, #cd7f32, #8b4513); color: #fff; }
.leaderboard-info { flex: 1; }
.leaderboard-name { font-weight: 600; }
.leaderboard-tier { font-size: 0.8rem; color: var(--muted); }
.leaderboard-score { font-weight: 700; color: var(--primary-light); }
.empty-state {
text-align: center;
padding: 3rem;
color: var(--muted);
}
.pricing-section {
margin-bottom: 3rem;
}
.pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.pricing-card {
background: var(--card);
border: 1px solid var(--card-border);
border-radius: 16px;
padding: 2rem;
text-align: center;
transition: all 0.2s;
}
.pricing-card:hover {
border-color: var(--card-border-hover);
transform: translateY(-4px);
}
.pricing-card.featured {
border-color: var(--primary);
background: linear-gradient(180deg, rgba(99, 102, 241, 0.1) 0%, var(--card) 100%);
}
.pricing-badge {
display: inline-block;
padding: 0.25rem 0.75rem;
background: var(--primary);
color: white;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
margin-bottom: 1rem;
}
.pricing-title { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem; }
.pricing-price { font-size: 2.5rem; font-weight: 700; color: var(--primary-light); }
.pricing-period { font-size: 0.9rem; color: var(--muted); }
.pricing-desc { color: var(--muted); margin: 1rem 0; font-size: 0.9rem; }
.pricing-features {
list-style: none;
text-align: left;
margin: 1.5rem 0;
}
.pricing-features li {
padding: 0.5rem 0;
color: var(--muted);
display: flex;
align-items: center;
gap: 0.5rem;
}
.pricing-features li::before {
content: '✓';
color: var(--success);
font-weight: 700;
}
.select-wrapper {
margin-bottom: 1rem;
}
.select-wrapper select {
width: 100%;
padding: 0.75rem;
border-radius: 8px;
background: var(--secondary);
border: 1px solid var(--border);
color: var(--foreground);
font-family: inherit;
font-size: 0.9rem;
}
@media (max-width: 768px) {
.stats-grid { grid-template-columns: 1fr; }
.nav-links { display: none; }
.tabs { overflow-x: auto; }
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<header>
<div class="container header-content">
<a href="/" class="logo">
<img src="/logo.png" alt="AeThex" class="logo-icon">
<span class="logo-text">AeThex | Warden</span>
</a>
<nav class="nav-links">
<a href="/">Home</a>
<a href="/features">Features</a>
<a href="/commands">Commands</a>
<a href="/federation" class="active">Federation</a>
<a href="/dashboard">Dashboard</a>
</nav>
<a href="https://discord.com/api/oauth2/authorize?client_id=578971245454950421&permissions=8&scope=bot%20applications.commands" class="btn btn-primary" target="_blank">Add to Server</a>
</div>
</header>
<main>
<section class="hero">
<div class="container">
<h1>The <span class="text-gradient">Federation</span></h1>
<p>A network of protected servers. Ban one, ban all.</p>
</div>
</section>
<section class="container pricing-section">
<h2 style="text-align: center; margin-bottom: 2rem;">Upgrade Your Protection</h2>
<div class="pricing-grid">
<div class="pricing-card">
<div class="pricing-title">Free</div>
<div class="pricing-price">$0</div>
<div class="pricing-period">forever</div>
<div class="pricing-desc">Basic protection for all federation members</div>
<ul class="pricing-features">
<li>Critical threat auto-bans</li>
<li>Server directory listing</li>
<li>Reputation tracking</li>
<li>Community support</li>
</ul>
<button class="btn btn-secondary" disabled>Current Plan</button>
</div>
<div class="pricing-card featured">
<div class="pricing-badge">RECOMMENDED</div>
<div class="pricing-title">Premium</div>
<div class="pricing-price">$50</div>
<div class="pricing-period">per month</div>
<div class="pricing-desc">Full protection from ALL threat levels</div>
<ul class="pricing-features">
<li>Auto-kick for ALL ban severities</li>
<li>Real-time threat alerts</li>
<li>Priority modlog notifications</li>
<li>Premium support badge</li>
</ul>
<div class="select-wrapper">
<select id="premiumGuildSelect">
<option value="">Select a server...</option>
</select>
</div>
<button class="btn btn-primary" onclick="upgradePlan('premium')">Upgrade to Premium</button>
</div>
<div class="pricing-card">
<div class="pricing-title">Featured Slot</div>
<div class="pricing-price">$200</div>
<div class="pricing-period">per week</div>
<div class="pricing-desc">Promote your server across the entire federation</div>
<ul class="pricing-features">
<li>Featured in all member servers</li>
<li>Cross-server promotion</li>
<li>Boost your member count</li>
<li>Priority directory placement</li>
</ul>
<div class="select-wrapper">
<select id="featuredGuildSelect">
<option value="">Select a server...</option>
</select>
</div>
<button class="btn btn-primary" onclick="upgradePlan('featured')">Get Featured</button>
</div>
</div>
</section>
<section class="container">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="totalServers">-</div>
<div class="stat-label">Member Servers</div>
</div>
<div class="stat-card">
<div class="stat-value" id="activeBans">-</div>
<div class="stat-label">Active Bans</div>
</div>
<div class="stat-card">
<div class="stat-value" id="pendingApps">-</div>
<div class="stat-label">Pending Applications</div>
</div>
</div>
<div class="tabs">
<button class="tab active" data-tab="servers">Servers</button>
<button class="tab" data-tab="bans">Global Bans</button>
<button class="tab" data-tab="applications">Applications</button>
<button class="tab" data-tab="leaderboard">Leaderboard</button>
</div>
<div id="servers" class="section active">
<div class="server-grid" id="serverGrid">
<div class="empty-state">Loading servers...</div>
</div>
</div>
<div id="bans" class="section">
<div class="card">
<div class="card-header">
<span class="card-title">Global Ban List</span>
</div>
<table class="table">
<thead>
<tr>
<th>User</th>
<th>Severity</th>
<th>Reason</th>
<th>Date</th>
</tr>
</thead>
<tbody id="banList">
<tr><td colspan="4" class="empty-state">Loading bans...</td></tr>
</tbody>
</table>
</div>
</div>
<div id="applications" class="section">
<div class="card">
<div class="card-header">
<span class="card-title">Pending Applications</span>
</div>
<table class="table">
<thead>
<tr>
<th>Server</th>
<th>Category</th>
<th>Members</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="appList">
<tr><td colspan="5" class="empty-state">Loading applications...</td></tr>
</tbody>
</table>
</div>
</div>
<div id="leaderboard" class="section">
<div class="card">
<div class="card-header">
<span class="card-title">Federation Reputation Leaders</span>
</div>
<div id="leaderboardList">
<div class="empty-state">Loading leaderboard...</div>
</div>
</div>
</div>
</section>
</main>
<script>
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.dataset.tab).classList.add('active');
});
});
async function loadStats() {
try {
const res = await fetch('/api/federation/stats');
const data = await res.json();
document.getElementById('totalServers').textContent = data.totalServers || 0;
document.getElementById('activeBans').textContent = data.activeBans || 0;
document.getElementById('pendingApps').textContent = data.pendingApplications || 0;
} catch (e) {
console.error('Failed to load stats:', e);
}
}
async function loadServers() {
try {
const res = await fetch('/api/federation/servers');
const data = await res.json();
const grid = document.getElementById('serverGrid');
if (!data.servers || data.servers.length === 0) {
grid.innerHTML = '<div class="empty-state">No servers in the federation yet. Use /federation membership apply to join!</div>';
return;
}
const categoryEmojis = { gaming: '🎮', creative: '🎨', development: '💻', education: '📚', community: '👥', business: '🏢' };
grid.innerHTML = data.servers.map(s => `
<div class="server-card">
<div class="server-header">
<div class="server-icon">${categoryEmojis[s.category] || '🌐'}</div>
<div>
<div class="server-name">${s.guild_name}</div>
<div class="server-category">${s.category || 'General'}</div>
</div>
</div>
<div class="server-desc">${s.description || 'No description'}</div>
<div class="server-members">${(s.member_count || 0).toLocaleString()} members</div>
</div>
`).join('');
} catch (e) {
console.error('Failed to load servers:', e);
}
}
async function loadBans() {
try {
const res = await fetch('/api/federation/bans?limit=50');
const data = await res.json();
const tbody = document.getElementById('banList');
if (!data.bans || data.bans.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="empty-state">No active bans</td></tr>';
return;
}
tbody.innerHTML = data.bans.map(b => `
<tr>
<td>${b.username || b.user_id}</td>
<td><span class="badge badge-${b.severity}">${b.severity.toUpperCase()}</span></td>
<td>${b.reason.substring(0, 50)}${b.reason.length > 50 ? '...' : ''}</td>
<td>${new Date(b.created_at).toLocaleDateString()}</td>
</tr>
`).join('');
} catch (e) {
console.error('Failed to load bans:', e);
}
}
async function loadApplications() {
try {
const res = await fetch('/api/federation/applications');
const data = await res.json();
const tbody = document.getElementById('appList');
if (!data.applications || data.applications.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">No applications</td></tr>';
return;
}
tbody.innerHTML = data.applications.map(a => `
<tr>
<td>${a.guild_name}</td>
<td>${a.category || 'General'}</td>
<td>${(a.member_count || 0).toLocaleString()}</td>
<td><span class="badge badge-${a.status}">${a.status.toUpperCase()}</span></td>
<td>
${a.status === 'pending' ? `
<button class="btn btn-success btn-sm" onclick="approveApp(${a.id})">Approve</button>
<button class="btn btn-danger btn-sm" onclick="rejectApp(${a.id})">Reject</button>
` : '-'}
</td>
</tr>
`).join('');
} catch (e) {
console.error('Failed to load applications:', e);
}
}
async function loadLeaderboard() {
try {
const res = await fetch('/api/federation/leaderboard?limit=20');
const data = await res.json();
const container = document.getElementById('leaderboardList');
if (!data.leaderboard || data.leaderboard.length === 0) {
container.innerHTML = '<div class="empty-state">No reputation data yet. Be active across federation servers!</div>';
return;
}
const tierEmojis = { newcomer: '🌱', member: '⭐', veteran: '🏆', elite: '💎', legend: '👑' };
container.innerHTML = data.leaderboard.map((l, i) => {
const rankClass = i === 0 ? 'gold' : i === 1 ? 'silver' : i === 2 ? 'bronze' : '';
return `
<div class="leaderboard-item">
<div class="leaderboard-rank ${rankClass}">${i + 1}</div>
<div class="leaderboard-info">
<div class="leaderboard-name">${l.discord_id}</div>
<div class="leaderboard-tier">${tierEmojis[l.rank_tier] || '🌱'} ${(l.rank_tier || 'newcomer').toUpperCase()}</div>
</div>
<div class="leaderboard-score">${(l.reputation_score || 0).toLocaleString()} rep</div>
</div>
`;
}).join('');
} catch (e) {
console.error('Failed to load leaderboard:', e);
}
}
async function approveApp(id) {
if (!confirm('Approve this application?')) return;
try {
await fetch(`/api/federation/applications/${id}/approve`, { method: 'POST' });
loadApplications();
loadStats();
loadServers();
} catch (e) {
alert('Failed to approve');
}
}
async function rejectApp(id) {
const reason = prompt('Rejection reason:');
if (!reason) return;
try {
await fetch(`/api/federation/applications/${id}/reject`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason })
});
loadApplications();
loadStats();
} catch (e) {
alert('Failed to reject');
}
}
async function loadUserGuilds() {
try {
const res = await fetch('/api/user');
if (!res.ok) return;
const data = await res.json();
if (!data.user?.guilds) return;
const adminGuilds = data.user.guilds.filter(g => g.isAdmin);
const premiumSelect = document.getElementById('premiumGuildSelect');
const featuredSelect = document.getElementById('featuredGuildSelect');
adminGuilds.forEach(g => {
const option1 = new Option(g.name, g.id);
const option2 = new Option(g.name, g.id);
premiumSelect.appendChild(option1);
featuredSelect.appendChild(option2);
});
} catch (e) {
console.error('Failed to load user guilds:', e);
}
}
async function upgradePlan(planType) {
const selectId = planType === 'premium' ? 'premiumGuildSelect' : 'featuredGuildSelect';
const guildId = document.getElementById(selectId).value;
if (!guildId) {
alert('Please select a server first');
return;
}
try {
const res = await fetch('/api/stripe/create-checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ guildId, planType })
});
if (!res.ok) {
const error = await res.json();
if (res.status === 401) {
alert('Please log in first using Discord');
window.location.href = '/auth/discord';
return;
}
throw new Error(error.error || 'Failed to create checkout');
}
const data = await res.json();
if (data.url) {
window.location.href = data.url;
}
} catch (e) {
alert('Failed to start checkout: ' + e.message);
}
}
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('success') === 'true') {
const plan = urlParams.get('plan');
alert(`Successfully upgraded to ${plan === 'premium' ? 'Premium' : 'Featured Slot'}! Your server is now protected.`);
window.history.replaceState({}, '', '/federation');
}
if (urlParams.get('canceled') === 'true') {
window.history.replaceState({}, '', '/federation');
}
loadStats();
loadServers();
loadBans();
loadApplications();
loadLeaderboard();
loadUserGuilds();
</script>
</body>
</html>