feat: Complete Phase 1 frontend — WebSocket + Wipe feature end-to-end

Implements full-stack vertical slice for wipe management with real-time updates.

WebSocket Integration:
- useWebSocket composable with auto-reconnect (exponential backoff up to 30s)
- JWT authentication via query parameter
- Automatic connection on auth state change
- Bi-directional messaging support
- Message handler subscription pattern
- Vite dev proxy configured for WebSocket (ws: true)

Toast Notification System:
- Pinia store with convenience methods (success/error/warning/info)
- Vue component with Lucide icons and Tailwind styling
- Auto-dismiss with configurable duration (5s default, 8s for errors)
- Manual dismiss with X button
- Smooth slide-in transitions from bottom-right
- Stack multiple toasts with proper spacing

Wipe Store Implementation:
- All API methods: fetchProfiles, fetchSchedules, fetchHistory
- Trigger wipe with optimistic UI update
- Dry-run simulation endpoint
- Profile CRUD operations (create, update, delete)
- WebSocket event listeners for real-time status updates
- Toast notifications on wipe_started, wipe_completed, wipe_failed
- Automatic history refresh on completion events
- Error handling with user-facing messages

Real-time Event Flow:
1. User triggers wipe → POST /api/wipes/trigger
2. Backend publishes NATS event: corrosion.{license_id}.wipe_started
3. WebSocket forwards event to frontend
4. Wipe store updates history array
5. Toast notification shows "Wipe started"
6. Progress events update status in real-time
7. Completion event triggers success toast + history refresh

Files Created:
- frontend/src/composables/useWebSocket.ts (208 LOC)
- frontend/src/stores/toast.ts (63 LOC)
- frontend/src/components/ToastNotification.vue (47 LOC)

Files Modified:
- frontend/src/stores/wipe.ts (273 LOC, was 42 LOC — 5 TODO methods → fully implemented)
- frontend/src/App.vue (added ToastNotification component)
- frontend/vite.config.ts (enabled WebSocket proxy)

TypeScript: Strict mode, zero build errors
Frontend builds:  929ms, 45.86 kB gzip

Phase 1 Status: ~80% complete
-  WebSocket/NATS real-time layer
-  Wipe feature production-ready
- ⏸️ Remaining stores (plugins, chat, players) still stubbed

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-02-15 12:17:31 -05:00
parent 8320591cf4
commit c5d057146a
6 changed files with 600 additions and 9 deletions

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
import { useToastStore } from '@/stores/toast'
import { CheckCircle, XCircle, AlertCircle, Info, X } from 'lucide-vue-next'
const toastStore = useToastStore()
const iconMap = {
success: CheckCircle,
error: XCircle,
warning: AlertCircle,
info: Info,
}
const colorMap = {
success: 'bg-emerald-500 text-white',
error: 'bg-red-500 text-white',
warning: 'bg-amber-500 text-white',
info: 'bg-blue-500 text-white',
}
</script>
<template>
<div class="fixed bottom-6 right-6 z-[9999] space-y-3 max-w-sm w-full pointer-events-none">
<TransitionGroup
enter-active-class="transition-all duration-300"
enter-from-class="opacity-0 translate-x-full"
enter-to-class="opacity-100 translate-x-0"
leave-active-class="transition-all duration-200"
leave-from-class="opacity-100 translate-x-0"
leave-to-class="opacity-0 translate-x-full"
move-class="transition-all duration-300"
>
<div
v-for="toast in toastStore.toasts"
:key="toast.id"
class="pointer-events-auto rounded-lg shadow-lg p-4 flex items-center gap-3"
:class="colorMap[toast.type]"
>
<component :is="iconMap[toast.type]" class="w-5 h-5 flex-shrink-0" />
<p class="flex-1 font-medium">{{ toast.message }}</p>
<button
@click="toastStore.removeToast(toast.id)"
class="flex-shrink-0 p-1 hover:bg-white/20 rounded transition-colors"
>
<X class="w-4 h-4" />
</button>
</div>
</TransitionGroup>
</div>
</template>