Fix blank page issue on module route refresh

- Pre-register all module routes automatically from module definitions in router configuration
- Add useModuleReady composable for clean reactive loading states during module initialization
- Update ChatPage and EventsPage with proper loading/error states and computed service access
- Remove duplicate route registration from plugin manager install phase
- Maintain modular architecture while ensuring routes are available immediately on app startup

Resolves blank pages and Vue Router warnings when refreshing on /chat, /events, /my-tickets routes.
Users now see proper loading indicators instead of blank screens during module initialization.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
padreug 2025-09-06 16:33:32 +02:00
parent 85635cfc96
commit 7145af3f83
5 changed files with 140 additions and 15 deletions

View file

@ -34,11 +34,21 @@ export async function createAppInstance() {
// Create Vue app
const app = createApp(App)
// Create router
// Collect all module routes automatically to avoid duplication
const moduleRoutes = [
// Extract routes from modules directly
...baseModule.routes || [],
...nostrFeedModule.routes || [],
...chatModule.routes || [],
...eventsModule.routes || [],
...marketModule.routes || []
].filter(Boolean)
// Create router with all routes available immediately
const router = createRouter({
history: createWebHistory(),
routes: [
// Default route - will be populated by modules
// Default routes
{
path: '/',
name: 'home',
@ -50,7 +60,9 @@ export async function createAppInstance() {
name: 'login',
component: () => import('./pages/LoginDemo.vue'),
meta: { requiresAuth: false }
}
},
// Pre-register module routes
...moduleRoutes
]
})

View file

@ -0,0 +1,40 @@
import { ref, onMounted } from 'vue'
/**
* Simple composable to handle module loading and readiness
* Uses Vue's reactivity system - no polling, no complex logic
*/
export function useModuleReady(moduleName: string) {
const isReady = ref(false)
const isLoading = ref(true)
const error = ref<string | null>(null)
onMounted(async () => {
try {
// Dynamic import to avoid circular dependencies
const { pluginManager } = await import('@/core/plugin-manager')
// Install module if not already installed
if (!pluginManager.isInstalled(moduleName)) {
console.log(`🔄 Installing ${moduleName} module...`)
await pluginManager.install(moduleName)
}
// Module is ready - Vue reactivity handles the rest
isReady.value = true
console.log(`${moduleName} module ready`)
} catch (err) {
console.error(`❌ Failed to load ${moduleName} module:`, err)
error.value = err instanceof Error ? err.message : 'Failed to load module'
} finally {
isLoading.value = false
}
})
return {
isReady,
isLoading,
error
}
}

View file

@ -53,6 +53,12 @@ export class PluginManager {
this.modules.set(plugin.name, registration)
console.log(`📦 Registered module: ${plugin.name} v${plugin.version}`)
// Routes are now pre-registered during router creation
// This registration step is kept for potential dynamic route additions in the future
if (plugin.routes && this.router) {
console.log(`🛤️ ${plugin.name} routes already pre-registered (${plugin.routes.length} routes)`)
}
// Auto-install if enabled and not lazy
if (config.enabled && !config.lazy) {
await this.install(plugin.name)
@ -98,12 +104,7 @@ export class PluginManager {
await plugin.install(this.app, { config: config.config })
// Register routes if provided
if (plugin.routes && this.router) {
for (const route of plugin.routes) {
this.router.addRoute(route)
}
}
// Routes are already registered during the register() phase
// Register services in DI container
if (plugin.services) {

View file

@ -1,9 +1,36 @@
<template>
<div class="h-[calc(100vh-3.5rem)] lg:h-[calc(100vh-4rem)] xl:h-[calc(100vh-5rem)] w-full">
<!-- Loading State -->
<div v-if="isLoading" class="flex flex-col items-center justify-center min-h-screen">
<div class="flex flex-col items-center space-y-4">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
<div class="text-center space-y-2">
<h2 class="text-xl font-semibold">Loading Chat...</h2>
<p class="text-sm text-muted-foreground">Setting up encrypted messaging and connecting to Nostr relays...</p>
</div>
</div>
</div>
<!-- Error State -->
<div v-else-if="error" class="flex flex-col items-center justify-center min-h-screen">
<div class="text-center space-y-4">
<h2 class="text-xl font-semibold text-red-600">Failed to load chat</h2>
<p class="text-muted-foreground">{{ error }}</p>
<button @click="location.reload()" class="px-4 py-2 bg-primary text-primary-foreground rounded">
Retry
</button>
</div>
</div>
<!-- Chat Content - Only render when module is ready -->
<div v-else class="h-[calc(100vh-3.5rem)] lg:h-[calc(100vh-4rem)] xl:h-[calc(100vh-5rem)] w-full">
<ChatComponent />
</div>
</template>
<script setup lang="ts">
import { useModuleReady } from '@/composables/useModuleReady'
import ChatComponent from '../components/ChatComponent.vue'
// Simple reactive module loading - no complex logic needed
const { isReady, isLoading, error } = useModuleReady('chat')
</script>

View file

@ -1,6 +1,7 @@
<!-- eslint-disable vue/multi-word-component-names -->
<script setup lang="ts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { useModuleReady } from '@/composables/useModuleReady'
import { useEvents } from '../composables/useEvents'
import { useAuth } from '@/composables/useAuth'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
@ -13,8 +14,21 @@ import PurchaseTicketDialog from '../components/PurchaseTicketDialog.vue'
import { RefreshCw, User, LogIn } from 'lucide-vue-next'
import { formatEventPrice } from '@/lib/utils/formatting'
const { upcomingEvents, pastEvents, isLoading, error, refresh } = useEvents()
const { isAuthenticated, userDisplay } = useAuth()
// Simple reactive module loading
const { isReady: moduleReady, isLoading: moduleLoading, error: moduleError } = useModuleReady('events')
// Only call services when module is ready - prevents service injection errors
const eventsData = computed(() => moduleReady.value ? useEvents() : null)
const authData = computed(() => moduleReady.value ? useAuth() : null)
// Reactive service data
const upcomingEvents = computed(() => eventsData.value?.upcomingEvents.value ?? [])
const pastEvents = computed(() => eventsData.value?.pastEvents.value ?? [])
const isLoading = computed(() => eventsData.value?.isLoading.value ?? false)
const error = computed(() => eventsData.value?.error.value ?? null)
const refresh = () => eventsData.value?.refresh()
const isAuthenticated = computed(() => authData.value?.isAuthenticated.value ?? false)
const userDisplay = computed(() => authData.value?.userDisplay.value ?? null)
const showPurchaseDialog = ref(false)
const selectedEvent = ref<{
id: string
@ -24,7 +38,15 @@ const selectedEvent = ref<{
} | null>(null)
function formatDate(dateStr: string) {
return format(new Date(dateStr), 'PPP')
if (!dateStr) return 'Date not available'
const date = new Date(dateStr)
if (isNaN(date.getTime())) {
return 'Invalid date'
}
// Format like "October 5th, 2025" to match the clean UI
return format(date, 'MMMM do, yyyy')
}
function handlePurchaseClick(event: {
@ -45,7 +67,30 @@ function handlePurchaseClick(event: {
</script>
<template>
<div class="container mx-auto py-8 px-4">
<!-- Module Loading State -->
<div v-if="moduleLoading" class="flex flex-col items-center justify-center min-h-screen">
<div class="flex flex-col items-center space-y-4">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
<div class="text-center space-y-2">
<h2 class="text-xl font-semibold">Loading Events...</h2>
<p class="text-sm text-muted-foreground">Loading event management and ticketing system...</p>
</div>
</div>
</div>
<!-- Module Error State -->
<div v-else-if="moduleError" class="flex flex-col items-center justify-center min-h-screen">
<div class="text-center space-y-4">
<h2 class="text-xl font-semibold text-red-600">Failed to load events</h2>
<p class="text-muted-foreground">{{ moduleError }}</p>
<button @click="location.reload()" class="px-4 py-2 bg-primary text-primary-foreground rounded">
Retry
</button>
</div>
</div>
<!-- Events Content - Only render when module is ready -->
<div v-else class="container mx-auto py-8 px-4">
<div class="flex justify-between items-center mb-6">
<div class="space-y-1">
<h1 class="text-3xl font-bold text-foreground">Events</h1>