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
|
// Create Vue app
|
||||||
const app = createApp(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({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
// Default route - will be populated by modules
|
// Default routes
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'home',
|
||||||
|
|
@ -50,7 +60,9 @@ export async function createAppInstance() {
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: () => import('./pages/LoginDemo.vue'),
|
component: () => import('./pages/LoginDemo.vue'),
|
||||||
meta: { requiresAuth: false }
|
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)
|
this.modules.set(plugin.name, registration)
|
||||||
console.log(`📦 Registered module: ${plugin.name} v${plugin.version}`)
|
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
|
// Auto-install if enabled and not lazy
|
||||||
if (config.enabled && !config.lazy) {
|
if (config.enabled && !config.lazy) {
|
||||||
await this.install(plugin.name)
|
await this.install(plugin.name)
|
||||||
|
|
@ -98,12 +104,7 @@ export class PluginManager {
|
||||||
|
|
||||||
await plugin.install(this.app, { config: config.config })
|
await plugin.install(this.app, { config: config.config })
|
||||||
|
|
||||||
// Register routes if provided
|
// Routes are already registered during the register() phase
|
||||||
if (plugin.routes && this.router) {
|
|
||||||
for (const route of plugin.routes) {
|
|
||||||
this.router.addRoute(route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register services in DI container
|
// Register services in DI container
|
||||||
if (plugin.services) {
|
if (plugin.services) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,36 @@
|
||||||
<template>
|
<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 />
|
<ChatComponent />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useModuleReady } from '@/composables/useModuleReady'
|
||||||
import ChatComponent from '../components/ChatComponent.vue'
|
import ChatComponent from '../components/ChatComponent.vue'
|
||||||
|
|
||||||
|
// Simple reactive module loading - no complex logic needed
|
||||||
|
const { isReady, isLoading, error } = useModuleReady('chat')
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<!-- eslint-disable vue/multi-word-component-names -->
|
<!-- eslint-disable vue/multi-word-component-names -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import { useModuleReady } from '@/composables/useModuleReady'
|
||||||
import { useEvents } from '../composables/useEvents'
|
import { useEvents } from '../composables/useEvents'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
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 { RefreshCw, User, LogIn } from 'lucide-vue-next'
|
||||||
import { formatEventPrice } from '@/lib/utils/formatting'
|
import { formatEventPrice } from '@/lib/utils/formatting'
|
||||||
|
|
||||||
const { upcomingEvents, pastEvents, isLoading, error, refresh } = useEvents()
|
// Simple reactive module loading
|
||||||
const { isAuthenticated, userDisplay } = useAuth()
|
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 showPurchaseDialog = ref(false)
|
||||||
const selectedEvent = ref<{
|
const selectedEvent = ref<{
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -24,7 +38,15 @@ const selectedEvent = ref<{
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
|
|
||||||
function formatDate(dateStr: string) {
|
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: {
|
function handlePurchaseClick(event: {
|
||||||
|
|
@ -45,7 +67,30 @@ function handlePurchaseClick(event: {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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="flex justify-between items-center mb-6">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<h1 class="text-3xl font-bold text-foreground">Events</h1>
|
<h1 class="text-3xl font-bold text-foreground">Events</h1>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue