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:
parent
85635cfc96
commit
7145af3f83
5 changed files with 140 additions and 15 deletions
18
src/app.ts
18
src/app.ts
|
|
@ -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
|
||||
]
|
||||
})
|
||||
|
||||
|
|
|
|||
40
src/composables/useModuleReady.ts
Normal file
40
src/composables/useModuleReady.ts
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue