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
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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user