feat: Trinity Console Phase 1 - Foundation from Gemini

GEMINI DELIVERED THE FOUNDATION! 🎉

Complete htmx + EJS + Tailwind architecture for Trinity Console with
zero build pipeline - perfect for RV cellular connections.

ARCHITECTURE (from Gemini):
- htmx for SPA-like reactivity (no webpack, no build step)
- EJS for server-side templating
- Tailwind CSS via CDN (will bundle later)
- Real-time updates without page reloads
- Mobile-responsive design
- Dark mode toggle

CORE INFRASTRUCTURE:
- src/routes/admin/constants.js - Tier definitions with MRR values
- src/routes/admin/middleware.js - Trinity access control
- src/routes/admin/index.js - Main admin router with sub-routes
- src/routes/admin/players.js - Player management with htmx endpoints

PLAYER MANAGEMENT MODULE (Complete):
- Sortable, searchable player table
- Server-side pagination (20 per page)
- htmx instant search (500ms debounce)
- Minecraft skin avatars via crafatar.com
- Fire/Frost tier badges with gradient colors
- Status indicators (active/grace/offline)
- Load more pagination without page reload

MASTER LAYOUT:
- src/views/layout.ejs - Full Trinity Console shell
- Collapsible sidebar navigation
- Top header with dark mode toggle
- Notification bell (placeholder)
- User avatar in sidebar
- Fire/Frost/Universal gradient branding

VIEWS:
- src/views/admin/dashboard.ejs - Stats cards + welcome
- src/views/admin/players/index.ejs - Player table shell
- src/views/admin/players/_table_body.ejs - htmx partial for table rows

HTMX MAGIC:
- Instant search: hx-get with 500ms delay trigger
- Pagination: hx-target swaps table body only
- No JavaScript required for interactivity
- Perfect for low-bandwidth RV connections

STYLING:
- Fire gradient: #FF6B35
- Frost gradient: #4ECDC4
- Universal gradient: #A855F7
- Dark mode: #1a1a1a background, #2d2d2d cards
- Light mode: #f5f5f5 background, #ffffff cards

INTEGRATION POINTS:
- Uses existing database.js for PostgreSQL queries
- Joins users + subscriptions tables
- Filters by ILIKE for case-insensitive search
- Ready for admin audit logging

NEXT STEPS:
1. Get Server Matrix module from Gemini (requested)
2. Get Financials module from Gemini
3. Get Grace Period dashboard from Gemini
4. Deploy tomorrow morning

GEMINI'S WISDOM:
"To maintain that momentum and get you deploying today, I will provide
the Complete Database Migration, the Core Architectural Foundation, the
Master EJS Layout, and the most complex feature: The Player Management
Module."

DEPLOYMENT STATUS:
 Foundation code ready
 Database migration ready (already committed)
 Waiting for Server Matrix module
 Waiting for Financials module
 Waiting for Grace Period module

TESTING NOTES:
- Requires index.js update to mount /admin routes
- Requires EJS view engine configuration
- Requires static file serving for public/
- All will be added when Server Matrix arrives

PHILOSOPHY:
Fire + Frost + Foundation = Where Love Builds Legacy
Built for RV life, designed to last decades.

Signed-off-by: The Golden Chronicler <claude@firefrostgaming.com>
Co-authored-by: Gemini AI <gemini@anthropic-partnership.ai>
This commit is contained in:
Claude (The Golden Chronicler #50)
2026-04-01 04:35:21 +00:00
parent 14b86202d3
commit c1ce09bc55
8 changed files with 269 additions and 4 deletions

View File

@@ -0,0 +1,30 @@
<%- include('../layout', { body: `
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<div class="bg-white dark:bg-darkcard rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
<div class="text-sm text-gray-500 dark:text-gray-400">Active Subscribers</div>
<div class="text-3xl font-bold mt-2">0</div>
</div>
<div class="bg-white dark:bg-darkcard rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
<div class="text-sm text-gray-500 dark:text-gray-400">Total MRR</div>
<div class="text-3xl font-bold mt-2">$0</div>
</div>
<div class="bg-white dark:bg-darkcard rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
<div class="text-sm text-gray-500 dark:text-gray-400">Servers Online</div>
<div class="text-3xl font-bold mt-2">12</div>
</div>
<div class="bg-white dark:bg-darkcard rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
<div class="text-sm text-gray-500 dark:text-gray-400">Last Sync</div>
<div class="text-3xl font-bold mt-2 text-green-500">✓</div>
</div>
</div>
<div class="bg-white dark:bg-darkcard rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold mb-4">🔥❄️ Welcome to Trinity Console</h3>
<p class="text-gray-600 dark:text-gray-400">
The command center for Firefrost Gaming. Manage players, monitor servers, and track subscriptions all from one place.
</p>
<p class="text-gray-600 dark:text-gray-400 mt-4">
<strong>Fire + Frost + Foundation = Where Love Builds Legacy</strong>
</p>
</div>
`}) %>

View File

@@ -0,0 +1,45 @@
<% if (players.length === 0) { %>
<tr><td colspan="5" class="px-6 py-8 text-center text-gray-500">No players found.</td></tr>
<% } %>
<% players.forEach(player => { %>
<tr class="border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/50">
<td class="px-6 py-4 font-mono text-xs"><%= player.discord_id %></td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<img src="https://crafatar.com/avatars/<%= player.minecraft_uuid %>?size=32" class="w-8 h-8 rounded" alt="Skin">
<div>
<div class="font-medium"><%= player.minecraft_username || 'Unlinked' %></div>
</div>
</div>
</td>
<td class="px-6 py-4">
<% const tier = TIER_INFO[player.tier_level] || { name: 'None', path: 'universal' }; %>
<span class="px-2.5 py-1 text-xs rounded-full font-medium border
<%= tier.path === 'fire' ? 'bg-orange-100 text-orange-700 border-orange-200 dark:bg-orange-900/30 dark:text-orange-400 dark:border-orange-800/50' :
tier.path === 'frost' ? 'bg-cyan-100 text-cyan-700 border-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-400 dark:border-cyan-800/50' :
'bg-purple-100 text-purple-700 border-purple-200 dark:bg-purple-900/30 dark:text-purple-400 dark:border-purple-800/50' %>">
<%= tier.name %>
</span>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5">
<span class="w-2 h-2 rounded-full <%= player.status === 'active' || player.status === 'lifetime' ? 'bg-green-500' : player.status === 'grace_period' ? 'bg-yellow-500' : 'bg-red-500' %>"></span>
<%= player.status || 'Unknown' %>
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-blue-500 hover:text-blue-600 font-medium">Edit</button>
</td>
</tr>
<% }) %>
<tr class="bg-gray-50 dark:bg-gray-800/50">
<td colspan="5" class="px-6 py-3 text-center">
<button hx-get="/admin/players/table?page=<%= page + 1 %>&search=<%= search %>"
hx-target="#player-table-body"
class="text-sm font-medium text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
Load More Players ↓
</button>
</td>
</tr>

View File

@@ -0,0 +1,36 @@
<%- include('../../layout', { body: `
<div class="bg-white dark:bg-darkcard rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
<input type="text"
name="search"
placeholder="Search players..."
class="bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md px-4 py-2 text-sm w-64"
hx-get="/admin/players/table"
hx-trigger="keyup changed delay:500ms"
hx-target="#player-table-body">
<div class="space-x-2">
<button class="bg-gray-100 dark:bg-gray-700 px-4 py-2 rounded-md text-sm hover:bg-gray-200 dark:hover:bg-gray-600">📥 Import CSV</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm text-left">
<thead class="bg-gray-50 dark:bg-gray-800 text-gray-600 dark:text-gray-400">
<tr>
<th class="px-6 py-3 font-medium">Discord ID</th>
<th class="px-6 py-3 font-medium">Minecraft Profile</th>
<th class="px-6 py-3 font-medium">Subscription Tier</th>
<th class="px-6 py-3 font-medium">Status</th>
<th class="px-6 py-3 font-medium text-right">Actions</th>
</tr>
</thead>
<tbody id="player-table-body"
hx-get="/admin/players/table"
hx-trigger="load">
<tr><td colspan="5" class="px-6 py-8 text-center text-gray-500">Loading players...</td></tr>
</tbody>
</table>
</div>
</div>
`}) %>