diff --git a/clients/app/src/commonMain/kotlin/at/mocode/clients/app/App.kt.backup b/clients/app/src/commonMain/kotlin/at/mocode/clients/app/App.kt.backup new file mode 100644 index 00000000..ed5cfd60 --- /dev/null +++ b/clients/app/src/commonMain/kotlin/at/mocode/clients/app/App.kt.backup @@ -0,0 +1,65 @@ +package at.mocode.clients.app + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import at.mocode.clients.shared.commonui.components.AppHeader +import at.mocode.clients.shared.commonui.components.AppScaffold +import at.mocode.clients.shared.commonui.theme.AppTheme +import at.mocode.clients.shared.navigation.AppScreen +import at.mocode.clients.pingfeature.PingScreen +import at.mocode.clients.pingfeature.PingViewModel +import at.mocode.clients.authfeature.LoginScreen +import at.mocode.clients.authfeature.AuthTokenManager +import androidx.compose.runtime.collectAsState + +@Composable +fun App() { + var currentScreen: AppScreen by remember { mutableStateOf(AppScreen.Home) } + // Create a single PingViewModel instance for the lifetime of the App composition. + val pingViewModel: PingViewModel = remember { PingViewModel() } + // Create a single AuthTokenManager instance for the lifetime of the App composition. + val authTokenManager: AuthTokenManager = remember { AuthTokenManager() } + // Observe authentication state + val authState by authTokenManager.authState.collectAsState() + + AppTheme { + AppScaffold( + header = { + AppHeader( + title = "Meldestelle", + onNavigateToPing = { currentScreen = AppScreen.Ping }, + onNavigateToLogin = { currentScreen = AppScreen.Login }, + onLogout = { + authTokenManager.clearToken() + currentScreen = AppScreen.Home + }, + isAuthenticated = authState.isAuthenticated, + username = authState.username, + userPermissions = authState.permissions.map { it.name } + ) + }, + { paddingValues -> + Box(modifier = Modifier.padding(paddingValues)) { + when (currentScreen) { + is AppScreen.Home -> { + LandingScreen(authTokenManager = authTokenManager) + } + + is AppScreen.Login -> { + LoginScreen( + authTokenManager = authTokenManager, + onLoginSuccess = { currentScreen = AppScreen.Home } + ) + } + + is AppScreen.Ping -> { + PingScreen(viewModel = pingViewModel) + } + } + } + } + ) + } +} diff --git a/clients/app/src/commonMain/kotlin/at/mocode/clients/app/LandingScreen.kt.backup b/clients/app/src/commonMain/kotlin/at/mocode/clients/app/LandingScreen.kt.backup new file mode 100644 index 00000000..035a6e29 --- /dev/null +++ b/clients/app/src/commonMain/kotlin/at/mocode/clients/app/LandingScreen.kt.backup @@ -0,0 +1,232 @@ +package at.mocode.clients.app + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import at.mocode.clients.authfeature.AuthTokenManager +import at.mocode.clients.authfeature.Permission + +@Composable +fun LandingScreen( + authTokenManager: AuthTokenManager? = null +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top + ) { + Text( + text = "Willkommen bei Meldestelle", + style = MaterialTheme.typography.headlineLarge, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.primary + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) { + Column( + modifier = Modifier.padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Eine moderne, skalierbare Frontend-Architektur", + style = MaterialTheme.typography.headlineSmall, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Medium + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Diese Anwendung demonstriert eine \"Shell + Feature-Module\"-Architektur " + + "basierend auf Kotlin Multiplatform. Sie spiegelt die DDD-Struktur des Backends " + + "wider und ist als native Desktop-Anwendung (JVM) und Web-Anwendung (JS/Wasm) lauffähig.", + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + lineHeight = MaterialTheme.typography.bodyLarge.lineHeight * 1.2 + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = "🚀 Technologien:", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + TechItem("Kotlin Multiplatform") + TechItem("Jetpack Compose Multiplatform") + TechItem("Material Design 3") + TechItem("Ktor Client") + TechItem("Domain-Driven Design") + } + } + } + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = "Verwenden Sie das Ping Service Menü oben, um die API-Funktionalität zu testen.", + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + // Permission-based UI demonstration + authTokenManager?.let { tokenManager -> + val authState by tokenManager.authState.collectAsState() + + if (authState.isAuthenticated && authState.permissions.isNotEmpty()) { + Spacer(modifier = Modifier.height(32.dp)) + + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) + ) { + Column( + modifier = Modifier.padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "🔐 Verfügbare Funktionen", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Admin features (visible only to users with delete permissions) + if (tokenManager.isAdmin()) { + PermissionCard( + title = "👑 Administrator-Bereich", + description = "Vollzugriff auf alle System-Funktionen", + permissions = listOf("Alle Berechtigungen", "System-Verwaltung", "Benutzer-Management"), + backgroundColor = MaterialTheme.colorScheme.errorContainer, + textColor = MaterialTheme.colorScheme.onErrorContainer + ) + } + + // Management features (visible to users with create/update permissions) + if (tokenManager.canCreate() || tokenManager.canUpdate()) { + PermissionCard( + title = "✏️ Verwaltung", + description = "Erstellen und bearbeiten von Daten", + permissions = buildList { + if (tokenManager.hasPermission(Permission.PERSON_CREATE)) add("Personen erstellen") + if (tokenManager.hasPermission(Permission.PERSON_UPDATE)) add("Personen bearbeiten") + if (tokenManager.hasPermission(Permission.VEREIN_CREATE)) add("Vereine erstellen") + if (tokenManager.hasPermission(Permission.VEREIN_UPDATE)) add("Vereine bearbeiten") + if (tokenManager.hasPermission(Permission.PFERD_CREATE)) add("Pferde erstellen") + if (tokenManager.hasPermission(Permission.PFERD_UPDATE)) add("Pferde bearbeiten") + if (tokenManager.hasPermission(Permission.VERANSTALTUNG_CREATE)) add("Veranstaltungen erstellen") + if (tokenManager.hasPermission(Permission.VERANSTALTUNG_UPDATE)) add("Veranstaltungen bearbeiten") + }, + backgroundColor = MaterialTheme.colorScheme.primaryContainer, + textColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + + // Read-only features (visible to all authenticated users) + if (tokenManager.canRead()) { + PermissionCard( + title = "👁️ Ansicht", + description = "Nur-Lese-Zugriff auf Daten", + permissions = buildList { + if (tokenManager.hasPermission(Permission.PERSON_READ)) add("Personen anzeigen") + if (tokenManager.hasPermission(Permission.VEREIN_READ)) add("Vereine anzeigen") + if (tokenManager.hasPermission(Permission.PFERD_READ)) add("Pferde anzeigen") + if (tokenManager.hasPermission(Permission.VERANSTALTUNG_READ)) add("Veranstaltungen anzeigen") + }, + backgroundColor = MaterialTheme.colorScheme.surfaceVariant, + textColor = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + } + } + } +} + +@Composable +private fun TechItem(text: String) { + Text( + text = "• $text", + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(vertical = 2.dp) + ) +} + +@Composable +private fun PermissionCard( + title: String, + description: String, + permissions: List, + backgroundColor: androidx.compose.ui.graphics.Color, + textColor: androidx.compose.ui.graphics.Color +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + colors = CardDefaults.cardColors( + containerColor = backgroundColor + ) + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = textColor + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = description, + style = MaterialTheme.typography.bodyMedium, + color = textColor + ) + + if (permissions.isNotEmpty()) { + Spacer(modifier = Modifier.height(12.dp)) + + permissions.forEach { permission -> + Text( + text = "✓ $permission", + style = MaterialTheme.typography.bodySmall, + color = textColor, + modifier = Modifier.padding(vertical = 2.dp) + ) + } + } + } + } +} diff --git a/clients/app/src/commonMain/kotlin/screens/DevelopmentScreen.kt.backup b/clients/app/src/commonMain/kotlin/screens/DevelopmentScreen.kt.backup new file mode 100644 index 00000000..4272c41b --- /dev/null +++ b/clients/app/src/commonMain/kotlin/screens/DevelopmentScreen.kt.backup @@ -0,0 +1,202 @@ +package screens + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import at.mocode.clients.shared.presentation.store.AppStore +import at.mocode.clients.shared.presentation.state.AppState +import at.mocode.clients.pingfeature.PingViewModel +import at.mocode.ping.api.HealthResponse +import at.mocode.ping.api.PingResponse +import at.mocode.ping.api.EnhancedPingResponse + +@Composable +fun DevelopmentScreen(appStore: AppStore) { + Column( + modifier = Modifier.fillMaxSize().padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + "🚀 Meldestelle Development Mode", + style = MaterialTheme.typography.headlineMedium + ) + + // Backend Connectivity Tests + BackendTestSection() + + // Ping Service Test + PingTestSection() + + // State Debugging + StateDebugSection(appStore) + } +} + +@Composable +fun BackendTestSection() { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text("🌐 Backend Connectivity", style = MaterialTheme.typography.titleMedium) + + var testStatus by remember { mutableStateOf("Not tested") } + var isLoading by remember { mutableStateOf(false) } + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button( + onClick = { + // TODO: Test Gateway Connection + isLoading = true + testStatus = "Testing..." + }, + enabled = !isLoading + ) { + Text("Test Gateway") + } + + Button( + onClick = { + // TODO: Test Ping Service Direct + isLoading = true + testStatus = "Testing direct connection..." + }, + enabled = !isLoading + ) { + Text("Test Ping Service") + } + } + + if (isLoading) { + CircularProgressIndicator(modifier = Modifier.padding(8.dp)) + } + + Text("Status: $testStatus") + } + } +} + +@Composable +fun PingTestSection() { + val pingViewModel = remember { PingViewModel() } + val uiState = pingViewModel.uiState + + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text("🏓 Ping Service Integration", style = MaterialTheme.typography.titleMedium) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button( + onClick = { pingViewModel.performHealthCheck() }, + enabled = !uiState.isLoading + ) { + Text("Health Check") + } + + Button( + onClick = { pingViewModel.performSimplePing() }, + enabled = !uiState.isLoading + ) { + Text("Simple Ping") + } + + Button( + onClick = { pingViewModel.performEnhancedPing(true) }, + enabled = !uiState.isLoading + ) { + Text("Test Circuit Breaker") + } + } + + if (uiState.isLoading) { + LinearProgressIndicator(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)) + } + + // Results Display + uiState.healthResponse?.let { health -> + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ) + ) { + Column(modifier = Modifier.padding(8.dp)) { + Text("✅ Health Check Result:") + Text("Status: ${health.status}") + Text("Service: ${health.service}") + Text("Healthy: ${health.healthy}") + Text("Timestamp: ${health.timestamp}") + } + } + } + + uiState.simplePingResponse?.let { ping -> + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) + ) { + Column(modifier = Modifier.padding(8.dp)) { + Text("🏓 Simple Ping Result:") + Text("Status: ${ping.status}") + Text("Service: ${ping.service}") + Text("Timestamp: ${ping.timestamp}") + } + } + } + + uiState.enhancedPingResponse?.let { ping -> + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.tertiaryContainer + ) + ) { + Column(modifier = Modifier.padding(8.dp)) { + Text("⚡ Enhanced Ping Result:") + Text("Status: ${ping.status}") + Text("Circuit Breaker: ${ping.circuitBreakerState}") + Text("Response Time: ${ping.responseTime}ms") + Text("Service: ${ping.service}") + } + } + } + + uiState.errorMessage?.let { error -> + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer + ) + ) { + Text( + "❌ Error: $error", + color = MaterialTheme.colorScheme.onErrorContainer, + modifier = Modifier.padding(8.dp) + ) + } + } + } + } +} + +@Composable +fun StateDebugSection(appStore: AppStore) { + val appState by appStore.state.collectAsState() + + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text("🔍 App State Debug", style = MaterialTheme.typography.titleMedium) + + Text("Auth State: ${if(appState.auth.isAuthenticated) "✅ Authenticated" else "❌ Not Authenticated"}") + Text("Current Route: ${appState.navigation.currentRoute}") + Text("Dark Mode: ${if(appState.ui.isDarkMode) "🌙 Enabled" else "☀️ Disabled"}") + Text("Online: ${if(appState.network.isOnline) "🟢 Online" else "🔴 Offline"}") + + Button( + onClick = { + appStore.dispatch(at.mocode.clients.shared.presentation.actions.AppAction.UI.ToggleDarkMode) + } + ) { + Text("Toggle Dark Mode") + } + } + } +} diff --git a/clients/app/webpack.config.d/webpack.config.js b/clients/app/webpack.config.d/webpack.config.js index d5f52ed9..1d7730e3 100644 --- a/clients/app/webpack.config.d/webpack.config.js +++ b/clients/app/webpack.config.d/webpack.config.js @@ -1,39 +1,19 @@ -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const path = require('path'); +// HTML template will be handled by Kotlin/JS build system +// No need for custom HtmlWebpackPlugin configuration -// Template-Pfad für deine index.html -const templatePath = path.resolve(__dirname, '../../../../clients/app/src/jsMain/resources/index.html'); - -// Erweitere die bestehende Kotlin/JS Webpack-Konfiguration -config.plugins.push(new HtmlWebpackPlugin({ - template: templatePath, - filename: 'index.html', - inject: 'body', - scriptLoading: 'blocking', - // Optimierung hinzufügen - minify: false - /*{ - removeComments: true, - collapseWhitespace: true, - removeRedundantAttributes: true, - removeEmptyAttributes: true, - useShortDoctype: true, - removeStyleLinkTypeAttributes: true, - keepClosingSlash: true, - minifyJS: true, - minifyCSS: true, - minifyURLs: true, - }*/ -})); - -// Bundle-Analyse für Development +// Bundle-Analyse für Development (optional, only if package is available) if (process.env.ANALYZE_BUNDLE === 'true') { - const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; - config.plugins.push(new BundleAnalyzerPlugin({ - analyzerMode: 'static', - openAnalyzer: false, - reportFilename: 'bundle-report.html' - })); + try { + const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; + config.plugins.push(new BundleAnalyzerPlugin({ + analyzerMode: 'static', + openAnalyzer: false, + reportFilename: 'bundle-report.html' + })); + console.log('Bundle analyzer enabled'); + } catch (e) { + console.log('Bundle analyzer not available (webpack-bundle-analyzer not installed)'); + } } // Weitere Optimierungen hinzufügen (erweitert bestehende config)