diff --git a/frontend/src/components/ErrorBoundary.vue b/frontend/src/components/ErrorBoundary.vue index e92d806..3f0920d 100644 --- a/frontend/src/components/ErrorBoundary.vue +++ b/frontend/src/components/ErrorBoundary.vue @@ -1,6 +1,7 @@ + + diff --git a/frontend/src/components/layout/PublicLayout.vue b/frontend/src/components/layout/PublicLayout.vue index f7c0698..46033c3 100644 --- a/frontend/src/components/layout/PublicLayout.vue +++ b/frontend/src/components/layout/PublicLayout.vue @@ -1,16 +1,33 @@ + + diff --git a/frontend/src/components/teleport/WarpEditor.vue b/frontend/src/components/teleport/WarpEditor.vue index 1da6e27..0ce523d 100644 --- a/frontend/src/components/teleport/WarpEditor.vue +++ b/frontend/src/components/teleport/WarpEditor.vue @@ -1,6 +1,8 @@ + + diff --git a/frontend/src/views/admin/ChangelogView.vue b/frontend/src/views/admin/ChangelogView.vue index 4cb8731..b49aebf 100644 --- a/frontend/src/views/admin/ChangelogView.vue +++ b/frontend/src/views/admin/ChangelogView.vue @@ -1,7 +1,11 @@ + + diff --git a/frontend/src/views/admin/ConsoleView.vue b/frontend/src/views/admin/ConsoleView.vue index e7639be..ff8e715 100644 --- a/frontend/src/views/admin/ConsoleView.vue +++ b/frontend/src/views/admin/ConsoleView.vue @@ -2,18 +2,23 @@ import { ref, nextTick, onMounted, onUnmounted } from 'vue' import { useServerStore } from '@/stores/server' import { useWebSocket, type WebSocketMessage } from '@/composables/useWebSocket' -import { Send, Terminal, Trash2 } from 'lucide-vue-next' +import Panel from '@/components/ds/data/Panel.vue' +import ConsoleLine from '@/components/ds/data/ConsoleLine.vue' +import Button from '@/components/ds/core/Button.vue' +import Badge from '@/components/ds/core/Badge.vue' +import Icon from '@/components/ds/core/Icon.vue' +import Input from '@/components/ds/forms/Input.vue' const server = useServerStore() const ws = useWebSocket() -interface ConsoleLine { +interface LogLine { timestamp: string text: string type: 'info' | 'warning' | 'error' | 'command' | 'system' } -const lines = ref([]) +const lines = ref([]) const commandInput = ref('') const consoleEl = ref(null) const sending = ref(false) @@ -22,7 +27,7 @@ function now(): string { return new Date().toLocaleTimeString('en-US', { hour12: false }) } -function addLine(text: string, type: ConsoleLine['type'] = 'info') { +function addLine(text: string, type: LogLine['type'] = 'info') { lines.value.push({ timestamp: now(), text, type }) scrollToBottom() } @@ -60,13 +65,12 @@ function clearConsole() { lines.value = [] } -function lineColor(type: ConsoleLine['type']): string { +function lineLevel(type: LogLine['type']): 'cmd' | 'warn' | 'error' | 'info' { switch (type) { - case 'command': return 'text-oxide-400' - case 'warning': return 'text-yellow-400' - case 'error': return 'text-red-400' - case 'system': return 'text-neutral-500' - default: return 'text-neutral-300' + case 'command': return 'cmd' + case 'warning': return 'warn' + case 'error': return 'error' + default: return 'info' } } @@ -83,10 +87,10 @@ function handleWebSocketMessage(message: WebSocketMessage) { let unsubscribe: (() => void) | null = null onMounted(() => { - addLine('Corrosion Console initialized.', 'system') + addLine('Corrosion console initialized.', 'system') addLine('Type a command and press Enter to send it to the server.', 'system') if (server.connection?.connection_status !== 'connected') { - addLine('WARNING: Server is not connected. Commands will fail.', 'warning') + addLine('Warning: server is not connected. Commands will fail.', 'warning') } unsubscribe = ws.subscribe(handleWebSocketMessage) }) @@ -100,70 +104,127 @@ onUnmounted(() => { + + diff --git a/frontend/src/views/admin/FileManagerView.vue b/frontend/src/views/admin/FileManagerView.vue index 70b48f3..098d3b2 100644 --- a/frontend/src/views/admin/FileManagerView.vue +++ b/frontend/src/views/admin/FileManagerView.vue @@ -2,6 +2,7 @@ import { computed } from 'vue' import { VueFinder, RemoteDriver } from 'vuefinder' import { useAuthStore } from '@/stores/auth' +import Icon from '@/components/ds/core/Icon.vue' const auth = useAuthStore() @@ -26,18 +27,22 @@ const finderConfig = { + + diff --git a/frontend/src/views/admin/MigrationView.vue b/frontend/src/views/admin/MigrationView.vue index 6b5f1dd..9317521 100644 --- a/frontend/src/views/admin/MigrationView.vue +++ b/frontend/src/views/admin/MigrationView.vue @@ -2,8 +2,13 @@ import { ref, onMounted } from 'vue' import { useApi } from '@/composables/useApi' import { useAuthStore } from '@/stores/auth' -import { Download, Upload, FileText, Loader2 } from 'lucide-vue-next' import { safeFileSize, safeDate } from '@/utils/formatters' +import Panel from '@/components/ds/data/Panel.vue' +import Button from '@/components/ds/core/Button.vue' +import Badge from '@/components/ds/core/Badge.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' interface ExportRecord { id: string @@ -20,6 +25,8 @@ const isExporting = ref(false) const isImporting = ref(false) const exportType = ref<'full' | 'config_only' | 'store_only'>('full') const uploadFile = ref(null) +const importError = ref(null) +const importSuccess = ref(false) async function fetchExports() { exports.value = await api.get('/migration/exports') @@ -49,6 +56,8 @@ async function importData() { if (!confirm('Import data? This will overwrite existing configuration.')) return isImporting.value = true + importError.value = null + importSuccess.value = false try { const formData = new FormData() formData.append('file', uploadFile.value) @@ -63,18 +72,20 @@ async function importData() { throw new Error('Import failed') } - alert('Import successful') + importSuccess.value = true uploadFile.value = null } catch (err) { - alert(err instanceof Error ? err.message : 'Import failed') + importError.value = err instanceof Error ? err.message : 'Import failed' } finally { isImporting.value = false } } -function formatBytes(bytes: number): string { - return safeFileSize(bytes) -} +const EXPORT_TYPE_OPTIONS = [ + { value: 'full' as const, label: 'Full' }, + { value: 'config_only' as const, label: 'Config only' }, + { value: 'store_only' as const, label: 'Store only' }, +] onMounted(() => { fetchExports() @@ -82,110 +93,251 @@ onMounted(() => { + + diff --git a/frontend/src/views/admin/WipeCalendarView.vue b/frontend/src/views/admin/WipeCalendarView.vue index f27a853..480661b 100644 --- a/frontend/src/views/admin/WipeCalendarView.vue +++ b/frontend/src/views/admin/WipeCalendarView.vue @@ -1,7 +1,11 @@ + + diff --git a/frontend/src/views/admin/WipeHistoryView.vue b/frontend/src/views/admin/WipeHistoryView.vue index 722b493..012ab34 100644 --- a/frontend/src/views/admin/WipeHistoryView.vue +++ b/frontend/src/views/admin/WipeHistoryView.vue @@ -1,25 +1,24 @@ + + diff --git a/frontend/src/views/platform-admin/AdminDashboard.vue b/frontend/src/views/platform-admin/AdminDashboard.vue index f61f141..661aeec 100644 --- a/frontend/src/views/platform-admin/AdminDashboard.vue +++ b/frontend/src/views/platform-admin/AdminDashboard.vue @@ -2,8 +2,10 @@ import { ref, onMounted } from 'vue' import { useApi } from '@/composables/useApi' import { useRouter } from 'vue-router' -import { Key, KeyRound, Users, DollarSign, Server, UserPlus, ArrowRight, ScrollText, CreditCard, MonitorCog } from 'lucide-vue-next' import { safeCurrency, safeLocaleString } from '@/utils/formatters' +import Panel from '@/components/ds/data/Panel.vue' +import StatCard from '@/components/ds/data/StatCard.vue' +import Icon from '@/components/ds/core/Icon.vue' const api = useApi() const router = useRouter() @@ -21,26 +23,26 @@ const stats = ref(null) const isLoading = ref(false) const kpiCards = [ - { key: 'total_licenses' as const, label: 'Total Licenses', icon: Key, format: 'number' }, - { key: 'active_licenses' as const, label: 'Active Licenses', icon: KeyRound, format: 'number' }, - { key: 'total_users' as const, label: 'Total Users', icon: Users, format: 'number' }, - { key: 'module_mrr' as const, label: 'Module MRR', icon: DollarSign, format: 'currency' }, - { key: 'servers_online' as const, label: 'Servers Online', icon: Server, format: 'number' }, - { key: 'new_signups_this_week' as const, label: 'New Signups This Week', icon: UserPlus, format: 'number' }, + { key: 'total_licenses' as const, label: 'Total licenses', icon: 'key', format: 'number' }, + { key: 'active_licenses' as const, label: 'Active licenses', icon: 'key', format: 'number' }, + { key: 'total_users' as const, label: 'Total users', icon: 'users', format: 'number' }, + { key: 'module_mrr' as const, label: 'Module MRR', icon: 'dollar-sign', format: 'currency' }, + { key: 'servers_online' as const, label: 'Servers online', icon: 'server', format: 'number' }, + { key: 'new_signups_this_week' as const, label: 'New signups this week', icon: 'users', format: 'number' }, ] const quickLinks = [ - { label: 'Licenses', description: 'Manage license keys and activations', icon: Key, route: '/admin/licenses' }, - { label: 'Subscriptions', description: 'View module subscriptions and MRR', icon: CreditCard, route: '/admin/subscriptions' }, - { label: 'Users', description: 'Manage platform users and permissions', icon: Users, route: '/admin/users' }, - { label: 'Servers', description: 'Monitor connected game servers', icon: MonitorCog, route: '/admin/servers' }, + { label: 'Licenses', description: 'Manage license keys and activations', icon: 'key', route: '/admin/licenses' }, + { label: 'Subscriptions', description: 'View module subscriptions and MRR', icon: 'credit-card', route: '/admin/subscriptions' }, + { label: 'Users', description: 'Manage platform users and permissions', icon: 'users', route: '/admin/users' }, + { label: 'Servers', description: 'Monitor connected game servers', icon: 'server-cog', route: '/admin/servers' }, ] function formatValue(value: number | undefined, format: string): string { if (format === 'currency') { - return safeCurrency(value, '$', '\u2014') + return safeCurrency(value, '$', '—') } - return safeLocaleString(value, '\u2014') + return safeLocaleString(value, '—') } async function fetchStats() { @@ -60,58 +62,91 @@ onMounted(() => { + + diff --git a/frontend/src/views/platform-admin/AdminLicenses.vue b/frontend/src/views/platform-admin/AdminLicenses.vue index 58724ab..995884d 100644 --- a/frontend/src/views/platform-admin/AdminLicenses.vue +++ b/frontend/src/views/platform-admin/AdminLicenses.vue @@ -1,7 +1,14 @@ + + diff --git a/frontend/src/views/platform-admin/AdminServers.vue b/frontend/src/views/platform-admin/AdminServers.vue index d2445fa..259d92e 100644 --- a/frontend/src/views/platform-admin/AdminServers.vue +++ b/frontend/src/views/platform-admin/AdminServers.vue @@ -1,7 +1,13 @@ + + diff --git a/frontend/src/views/platform-admin/AdminSubscriptions.vue b/frontend/src/views/platform-admin/AdminSubscriptions.vue index 2e26732..0010030 100644 --- a/frontend/src/views/platform-admin/AdminSubscriptions.vue +++ b/frontend/src/views/platform-admin/AdminSubscriptions.vue @@ -1,8 +1,12 @@ + + diff --git a/frontend/src/views/platform-admin/AdminUsers.vue b/frontend/src/views/platform-admin/AdminUsers.vue index 4e40975..96d9f0b 100644 --- a/frontend/src/views/platform-admin/AdminUsers.vue +++ b/frontend/src/views/platform-admin/AdminUsers.vue @@ -1,7 +1,13 @@ + + diff --git a/frontend/src/views/public/ServerInfoView.vue b/frontend/src/views/public/ServerInfoView.vue index 5de6712..efb94c7 100644 --- a/frontend/src/views/public/ServerInfoView.vue +++ b/frontend/src/views/public/ServerInfoView.vue @@ -1,7 +1,14 @@ + + diff --git a/frontend/src/views/public/StatusPageView.vue b/frontend/src/views/public/StatusPageView.vue index cb93612..fbcee05 100644 --- a/frontend/src/views/public/StatusPageView.vue +++ b/frontend/src/views/public/StatusPageView.vue @@ -1,8 +1,16 @@ + + diff --git a/frontend/src/views/public/StoreView.vue b/frontend/src/views/public/StoreView.vue index 8988a67..465b644 100644 --- a/frontend/src/views/public/StoreView.vue +++ b/frontend/src/views/public/StoreView.vue @@ -2,8 +2,16 @@ import { ref, computed, onMounted } from 'vue' import { useRoute } from 'vue-router' import type { PublicStoreInfo, PublicStoreItem, StorePurchaseRequest, StorePurchaseResponse } from '@/types' -import { ShoppingCart, Package, Filter, X, AlertCircle, ExternalLink, Check } from 'lucide-vue-next' import { safeCurrency } from '@/utils/formatters' +import Panel from '@/components/ds/data/Panel.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 Input from '@/components/ds/forms/Input.vue' +import Select from '@/components/ds/forms/Select.vue' +import Logo from '@/components/ds/brand/Logo.vue' const route = useRoute() const subdomain = computed(() => route.params.subdomain as string) @@ -29,7 +37,7 @@ const categories = computed(() => { }) return Array.from(cats).map(name => ({ value: name, - label: name === 'all' ? 'All Items' : name + label: name === 'all' ? 'All items' : name })) }) @@ -142,14 +150,14 @@ function formatPrice(price: number): string { return safeCurrency(price, '$') } -function itemTypeBadgeClass(itemType: string): string { - const colors: Record = { - kit: 'bg-blue-500/15 text-blue-400', - rank: 'bg-purple-500/15 text-purple-400', - currency: 'bg-green-500/15 text-green-400', - custom_command: 'bg-orange-500/15 text-orange-400', +function itemTypeTone(itemType: string): 'info' | 'wiping' | 'online' | 'warn' | 'neutral' { + const tones: Record = { + kit: 'info', + rank: 'wiping', + currency: 'online', + custom_command: 'warn', } - return colors[itemType] || 'bg-neutral-700/50 text-neutral-400' + return tones[itemType] ?? 'neutral' } onMounted(() => { @@ -158,264 +166,631 @@ onMounted(() => { + +