feat(redesign): re-skin admin-ops/platform-admin/public views to DS (Phase D batch 4 — panel re-skin complete)
All checks were successful
Test Asgard Runner / test (push) Successful in 3s

Final re-skin batch: admin ops (Console/FileManager[VueFinder preserved]/WipeCalendar/WipeHistory/Changelog/Migration), platform-admin (Dashboard/Licenses/Servers/Subscriptions/Users), public product pages (ServerInfo/StatusPage/StoreView) + PublicLayout, WarpEditor, ErrorBoundary. All logic/store/router/WebSocket/handlers preserved. Marketing views (Landing/Pricing/FAQ/HowItWorks/Roadmap/EarlyAccess + MarketingLayout) intentionally deferred to the dedicated marketing-site redesign. Build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-06-11 02:55:02 -04:00
parent 376ed9a98d
commit 29615cb4f3
17 changed files with 2843 additions and 1301 deletions

View File

@@ -1,7 +1,14 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { Server, Users, Calendar, MessageCircle, Loader2, ExternalLink } from 'lucide-vue-next'
import Panel from '@/components/ds/data/Panel.vue'
import StatCard from '@/components/ds/data/StatCard.vue'
import Badge from '@/components/ds/core/Badge.vue'
import Button from '@/components/ds/core/Button.vue'
import Icon from '@/components/ds/core/Icon.vue'
import Alert from '@/components/ds/feedback/Alert.vue'
import EmptyState from '@/components/ds/feedback/EmptyState.vue'
import Logo from '@/components/ds/brand/Logo.vue'
interface ServerInfo {
server_name: string
@@ -51,99 +58,225 @@ onMounted(() => {
</script>
<template>
<div class="min-h-screen bg-neutral-950">
<!-- Loading State -->
<div v-if="isLoading" class="flex items-center justify-center min-h-screen">
<Loader2 class="w-8 h-8 text-oxide-500 animate-spin" />
<div class="si-page">
<!-- Loading -->
<div v-if="isLoading" class="si-state">
<Icon name="loader" :size="28" class="si-spin" />
</div>
<!-- Error State -->
<div v-else-if="error" class="flex items-center justify-center min-h-screen p-6">
<div class="bg-neutral-900 border border-neutral-800 rounded-lg p-8 max-w-md text-center">
<Server class="w-12 h-12 text-neutral-600 mx-auto mb-4" />
<h1 class="text-xl font-bold text-neutral-100 mb-2">Server Not Found</h1>
<p class="text-sm text-neutral-400">{{ error }}</p>
</div>
<!-- Error -->
<div v-else-if="error" class="si-state">
<EmptyState
icon="server"
title="Server not found"
:description="error"
/>
</div>
<!-- Server Info -->
<div v-else-if="serverInfo" class="max-w-4xl mx-auto p-6 space-y-6">
<!-- Header Image -->
<div v-if="serverInfo.header_image" class="rounded-lg overflow-hidden">
<img :src="serverInfo.header_image" :alt="serverInfo.server_name" class="w-full h-64 object-cover" />
</div>
<!-- Content -->
<template v-else-if="serverInfo">
<!-- Sticky nav bar -->
<header class="si-bar">
<div class="si-bar__inner">
<Logo :size="22" :wordmark="true" />
</div>
</header>
<!-- Server Name & Stats -->
<div class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
<div class="flex items-start justify-between mb-4">
<div>
<h1 class="text-3xl font-bold text-neutral-100 mb-2">{{ serverInfo.server_name }}</h1>
<div class="flex items-center gap-2 text-neutral-400">
<Users class="w-4 h-4" />
<span class="text-sm">{{ serverInfo.player_count }}/{{ serverInfo.max_players }} players online</span>
<main class="si-main">
<!-- Hero image -->
<div v-if="serverInfo.header_image" class="si-hero">
<img
:src="serverInfo.header_image"
:alt="serverInfo.server_name"
class="si-hero__img"
/>
</div>
<!-- Identity + connect -->
<Panel>
<div class="si-identity">
<div class="si-identity__left">
<h1 class="si-title">{{ serverInfo.server_name }}</h1>
<p v-if="serverInfo.description" class="si-desc">
{{ serverInfo.description }}
</p>
</div>
<Button icon="external-link" size="md" @click="copyConnectUrl">
Connect
</Button>
</div>
<button
@click="copyConnectUrl"
class="flex items-center gap-2 px-4 py-2 bg-oxide-500 hover:bg-oxide-600 text-white font-medium rounded-lg transition-colors"
>
<ExternalLink class="w-4 h-4" />
Connect
</button>
</Panel>
<!-- KPIs -->
<div class="si-kpis">
<StatCard
icon="users"
label="Players online"
:value="String(serverInfo.player_count)"
:unit="'/' + serverInfo.max_players"
/>
<StatCard
v-if="serverInfo.wipe_schedule"
icon="calendar"
label="Wipe schedule"
:value="serverInfo.wipe_schedule"
/>
</div>
<p v-if="serverInfo.description" class="text-neutral-300 leading-relaxed">
{{ serverInfo.description }}
</p>
</div>
<!-- MOTD -->
<Panel v-if="serverInfo.motd" title="Message of the day">
<p class="si-motd">{{ serverInfo.motd }}</p>
</Panel>
<!-- MOTD -->
<div v-if="serverInfo.motd" class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
<h2 class="text-lg font-bold text-neutral-100 mb-3">Message of the Day</h2>
<p class="text-neutral-300 whitespace-pre-line">{{ serverInfo.motd }}</p>
</div>
<!-- Wipe Schedule -->
<div v-if="serverInfo.wipe_schedule" class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
<div class="flex items-center gap-2 mb-3">
<Calendar class="w-5 h-5 text-oxide-500" />
<h2 class="text-lg font-bold text-neutral-100">Wipe Schedule</h2>
</div>
<p class="text-neutral-300">{{ serverInfo.wipe_schedule }}</p>
</div>
<!-- Mods -->
<div v-if="serverInfo.mods.length > 0" class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
<h2 class="text-lg font-bold text-neutral-100 mb-3">Active Mods</h2>
<div class="flex flex-wrap gap-2">
<span
v-for="mod in serverInfo.mods"
:key="mod"
class="px-3 py-1 bg-neutral-800 border border-neutral-700 rounded-full text-sm text-neutral-300"
>
{{ mod }}
</span>
</div>
</div>
<!-- Discord -->
<div v-if="serverInfo.discord_invite" class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<MessageCircle class="w-5 h-5 text-oxide-500" />
<h2 class="text-lg font-bold text-neutral-100">Join our Discord</h2>
<!-- Active mods -->
<Panel v-if="serverInfo.mods.length > 0" title="Active mods">
<div class="si-mods">
<Badge
v-for="mod in serverInfo.mods"
:key="mod"
tone="neutral"
size="lg"
>{{ mod }}</Badge>
</div>
<a
:href="serverInfo.discord_invite"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-2 px-4 py-2 bg-neutral-800 hover:bg-neutral-700 text-neutral-200 font-medium rounded-lg transition-colors"
>
<ExternalLink class="w-4 h-4" />
Join
</a>
</div>
</div>
</div>
</Panel>
<!-- Discord -->
<Panel v-if="serverInfo.discord_invite" title="Community">
<Alert tone="info" title="Join our Discord">
<template #actions>
<a
:href="serverInfo.discord_invite"
target="_blank"
rel="noopener noreferrer"
>
<Button size="sm" variant="secondary" icon="external-link">Join Discord</Button>
</a>
</template>
</Alert>
</Panel>
</main>
</template>
</div>
</template>
<style scoped>
.si-page {
min-height: 100vh;
background: var(--surface-canvas);
}
/* Loading / error centering */
.si-state {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
/* Spinner */
.si-spin {
color: var(--accent);
animation: si-rotate 0.7s linear infinite;
}
@keyframes si-rotate {
to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
.si-spin { animation: none; }
}
/* Sticky brand bar */
.si-bar {
position: sticky;
top: 0;
z-index: 20;
background: var(--surface-base);
box-shadow: 0 1px 0 var(--border-subtle);
}
.si-bar__inner {
max-width: 860px;
margin: 0 auto;
padding: var(--space-4) var(--space-6);
display: flex;
align-items: center;
}
/* Main content */
.si-main {
max-width: 860px;
margin: 0 auto;
padding: var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-4);
}
/* Hero image */
.si-hero {
border-radius: var(--radius-lg);
overflow: hidden;
}
.si-hero__img {
width: 100%;
height: 240px;
object-fit: cover;
display: block;
}
/* Identity block inside panel */
.si-identity {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--space-4);
flex-wrap: wrap;
}
.si-identity__left {
display: flex;
flex-direction: column;
gap: var(--space-2);
min-width: 0;
}
.si-title {
font-size: var(--text-3xl);
font-weight: 700;
letter-spacing: -0.02em;
color: var(--text-primary);
line-height: 1.1;
}
.si-desc {
font-size: var(--text-sm);
color: var(--text-secondary);
line-height: 1.6;
max-width: 560px;
}
/* KPI row */
.si-kpis {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: var(--space-3);
}
/* MOTD */
.si-motd {
font-size: var(--text-sm);
color: var(--text-secondary);
line-height: 1.65;
white-space: pre-line;
}
/* Mods */
.si-mods {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
</style>