refactor(web): Komplettumstellung auf WASM, Altlasten aus Gradle und Architektur-Tests entfernt
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
# Journal: Korrektur Architecture-Tests & Build-Stabilisierung
|
||||||
|
|
||||||
|
**Datum:** 18. April 2026
|
||||||
|
**Badge:** 🏗️ [Lead Architect] & 🧐 [QA Specialist]
|
||||||
|
|
||||||
|
## 🛡️ Status Quo: Build-Fehler nach WASM-Transition
|
||||||
|
|
||||||
|
Nach der vollständigen Umstellung der `meldestelle-web` Shell auf ein reines `wasmJs`-Target schlug der Gesamt-Build
|
||||||
|
fehl. Das Modul `platform:architecture-tests` konnte die Abhängigkeit zur Web-Shell nicht mehr auflösen, da es als
|
||||||
|
JVM-Modul konzipiert ist und eine kompatible Java-Variante der Shell erwartete.
|
||||||
|
|
||||||
|
## 🛠️ Durchgeführte Maßnahmen
|
||||||
|
|
||||||
|
### 1. Korrektur der Architecture-Tests
|
||||||
|
|
||||||
|
* **Problem:** ArchUnit (das für die Architektur-Tests verwendet wird) ist eine JVM-Bibliothek. Da die `meldestelle-web`
|
||||||
|
Shell nun kein JVM-Target mehr besitzt, kann sie nicht in diesen Test-Zyklus eingebunden werden.
|
||||||
|
* **Lösung:** Die Abhängigkeit zu `projects.frontend.shells.meldestelleWeb` wurde in
|
||||||
|
`platform/architecture-tests/build.gradle.kts` auskommentiert/entfernt.
|
||||||
|
* **Begründung:** Die Web-Shell enthält primär Entry-Point-Logik für den Browser. Die fachliche Architektur (Features,
|
||||||
|
Core, Domain) wird weiterhin über die anderen Modul-Abhängigkeiten geprüft.
|
||||||
|
|
||||||
|
### 2. Synchronisation der WASM-Infrastruktur
|
||||||
|
|
||||||
|
* **Aktion:** Durchführung von `./gradlew kotlinWasmUpgradeYarnLock`.
|
||||||
|
* **Ergebnis:** Die `yarn.lock` wurde an die neuen Target-Konfigurationen angepasst, was den Fehler
|
||||||
|
`kotlinWasmStoreYarnLock` behob.
|
||||||
|
|
||||||
|
## ✅ Verifizierung
|
||||||
|
|
||||||
|
* `./gradlew clean :platform:architecture-tests:test`: **Erfolgreich**. Die Architektur-Tests für die verbleibenden
|
||||||
|
JVM-kompatiblen Module (Desktop, Core, Features) laufen grün durch.
|
||||||
|
* `./gradlew clean build`: **Erfolgreich**. Der gesamte Projekt-Build (700+ Tasks) läuft ohne Fehler durch.
|
||||||
|
|
||||||
|
## 🚀 Fazit
|
||||||
|
|
||||||
|
Die architektonische Härtung (JVM für Desktop, WASM für Web) ist nun auch in der Build-Infrastruktur und den
|
||||||
|
Qualitäts-Checks (ArchUnit) konsistent abgebildet.
|
||||||
|
|
||||||
|
---
|
||||||
|
🧹 **[Curator]**: Dokumentiert als finaler Fix der WASM-Transition-Phase.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Session Journal: 18. April 2026 (Abschluss WASM-Transition & Onboarding-Refactoring)
|
||||||
|
|
||||||
|
## 🏗️ [Lead Architect] Status-Bericht
|
||||||
|
|
||||||
|
Diese Session markiert den Abschluss der **"Total WASM Transition"**. Wir haben das Projekt von der technischen Schuld
|
||||||
|
redundanter JS-Targets befreit und die Architektur auf **JVM (Desktop)** und **wasmJs (Web)** gehärtet. Parallel dazu
|
||||||
|
wurde das Onboarding in das neue Plug-and-Play Modul `device-initialization` (ADR-0024) überführt.
|
||||||
|
|
||||||
|
### 🛡️ Verifizierte Fakten (Stand: 18.04.2026, 15:45 Uhr)
|
||||||
|
|
||||||
|
1. **Plattform-Konsolidierung:**
|
||||||
|
* `js(IR)` Targets aus allen Modulen (Core, Features, Contracts, Shells) entfernt.
|
||||||
|
* Alle `src/jsMain/` und `src/jsTest/` Verzeichnisse wurden gelöscht.
|
||||||
|
* Der Build läuft nun ohne "Unresolved platforms: [js]" Fehler durch.
|
||||||
|
2. **Shell-Härtung:**
|
||||||
|
* `meldestelle-desktop`: Reines JVM-Modul (WASM entfernt).
|
||||||
|
* `meldestelle-web`: Reines WASM-Modul (JVM entfernt).
|
||||||
|
* Die Web-Shell startet wieder direkt mit der Landing-Page (Veranstaltungs-Cards), ohne das Desktop-Onboarding-Gate.
|
||||||
|
3. **Onboarding (Geräte-Initialisierung):**
|
||||||
|
* Neues Modul `device-initialization` ist aktiv.
|
||||||
|
* ViewModel-basierte Logik (StateFlow) implementiert.
|
||||||
|
* Domain-Sprache auf "Geräte-Initialisierung" vereinheitlicht.
|
||||||
|
4. **Build-Stabilität:**
|
||||||
|
* `platform:architecture-tests` für WASM-Kompatibilität angepasst (reine WASM-Shells werden ignoriert).
|
||||||
|
* `yarn.lock` für die neue WASM-Infrastruktur synchronisiert.
|
||||||
|
* `./gradlew clean build` ist erfolgreich (700+ Tasks).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧹 [Curator] Artefakte & Dokumentation
|
||||||
|
|
||||||
|
Folgende Dokumente wurden in dieser Session erstellt/aktualisiert:
|
||||||
|
|
||||||
|
* **Roadmap:** `docs/01_Architecture/MASTER_ROADMAP.md` (Phase 14: WASM Transition abgeschlossen).
|
||||||
|
* **Journal:** `docs/99_Journal/2026-04-18_WASM-Transition-Welle-1-3_Abschluss.md` (Technisches Log).
|
||||||
|
* **Journal:** `docs/99_Journal/2026-04-18_DeviceInitialization-PlugAndPlay.md` (Refactoring Log).
|
||||||
|
* **Journal:** `docs/99_Journal/2026-04-18_Web-Shell-Korrektur-Fokus.md` (Recovery Log).
|
||||||
|
* **Journal:** `docs/99_Journal/2026-04-18_Final-Shell-Hardening.md` (Target-Hardening Log).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Nächster Fokus
|
||||||
|
|
||||||
|
Die Architektur ist nun "sauber". In der nächsten Session können wir mit der fachlichen Wiederherstellung der restlichen
|
||||||
|
Module (Turnier-Feature, Nennung-Feature) auf Basis der neuen Plug-and-Play Struktur fortfahren.
|
||||||
|
|
||||||
|
**Session beendet.** 🫡
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Journal: Korrektur Web-Shell (Fokus-Wiederherstellung)
|
||||||
|
|
||||||
|
**Datum:** 18. April 2026
|
||||||
|
**Agent:** 🏗️ [Lead Architect]
|
||||||
|
|
||||||
|
## 🛡️ Analyse: Fehlgeleitete Implementierung
|
||||||
|
|
||||||
|
Nach einer kritischen Überprüfung wurde festgestellt, dass die vorherige "Recovery" der Web-Shell fälschlicherweise
|
||||||
|
Desktop-Paradigmen (Geräte-Initialisierung) in die Web-App erzwungen hat. Dies widerspricht der fachlichen Ausrichtung
|
||||||
|
der Web-Shell (Online-Nennungen für Reiter).
|
||||||
|
|
||||||
|
## 🚀 Korrektur-Maßnahmen
|
||||||
|
|
||||||
|
### 1. Architektur-Bereinigung
|
||||||
|
|
||||||
|
- **Gradle:** Entfernung des `jvm()` Targets aus `meldestelle-web/build.gradle.kts`. Die Shell ist nun ein reines
|
||||||
|
WASM-Projekt.
|
||||||
|
- **Dependencies:** Entfernung des `device-initialization` Moduls. Web-Nutzer benötigen keine lokale
|
||||||
|
Geräte-Konfiguration oder mDNS-Discovery.
|
||||||
|
|
||||||
|
### 2. UI-Rückbau (Landing-Page Fokus)
|
||||||
|
|
||||||
|
- **WebMainScreen.kt:** Das künstliche `isConfigured`-Gate wurde entfernt.
|
||||||
|
- **Status:** Die App startet nun wieder direkt mit der `LandingPage` (Begrüßung und Veranstaltungs-Cards für Neumarkt).
|
||||||
|
- **Cleanup:** Entfernung ungenutzter Imports und redundanter Koin-Parameter.
|
||||||
|
|
||||||
|
### 3. Koin-Setup
|
||||||
|
|
||||||
|
- Bereinigung der `main.kt` (Entfernung des `deviceInitializationModule`).
|
||||||
|
|
||||||
|
## ✅ Verifizierung
|
||||||
|
|
||||||
|
- `./gradlew :frontend:shells:meldestelle-web:compileKotlinWasmJs -PenableWasm=true` abgeschlossen mit **BUILD
|
||||||
|
SUCCESSFUL**.
|
||||||
|
- Manuelle Prüfung der Dateistruktur: Keine Desktop-Artefakte mehr in der Web-Shell.
|
||||||
|
|
||||||
|
## 🧹 [Curator] Fazit
|
||||||
|
|
||||||
|
Die Web-Shell wurde erfolgreich von "eigensinnigen" Fehlentscheidungen bereinigt und auf ihren fachlichen Kern (
|
||||||
|
Landing-Page & Nennungs-Workflow) zurückgeführt. Die architektonische Trennung zwischen Desktop-Zentrale (mit
|
||||||
|
Onboarding) und Web-Client ist wiederhergestellt.
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
@file:OptIn(ExperimentalWasmDsl::class)
|
|
||||||
|
|
||||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +21,6 @@ plugins {
|
|||||||
alias(libs.plugins.composeCompiler)
|
alias(libs.plugins.composeCompiler)
|
||||||
alias(libs.plugins.composeMultiplatform)
|
alias(libs.plugins.composeMultiplatform)
|
||||||
alias(libs.plugins.kotlinSerialization)
|
alias(libs.plugins.kotlinSerialization)
|
||||||
// id("org.jetbrains.compose.hot-reload")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -42,15 +38,6 @@ val packageVer = "$vMajor.$vMinor.$vPatch"
|
|||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm()
|
||||||
|
|
||||||
wasmJs {
|
|
||||||
binaries.library()
|
|
||||||
browser {
|
|
||||||
testTask {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
jvmMain.dependencies {
|
jvmMain.dependencies {
|
||||||
// Core-Module
|
// Core-Module
|
||||||
|
|||||||
@@ -9,62 +9,55 @@ plugins {
|
|||||||
alias(libs.plugins.kotlinSerialization)
|
alias(libs.plugins.kotlinSerialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isWasmEnabled = findProperty("enableWasm")?.toString()?.toBoolean() ?: false
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
wasmJs {
|
||||||
|
browser {
|
||||||
if (isWasmEnabled) {
|
testTask {
|
||||||
wasmJs {
|
enabled = false
|
||||||
browser {
|
|
||||||
testTask {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
binaries.executable()
|
|
||||||
}
|
}
|
||||||
|
binaries.executable()
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain.dependencies {}
|
commonMain.dependencies {}
|
||||||
|
|
||||||
if (isWasmEnabled) {
|
wasmJsMain.dependencies {
|
||||||
wasmJsMain.dependencies {
|
// Core-Module
|
||||||
// Core-Module
|
implementation(projects.frontend.core.domain)
|
||||||
implementation(projects.frontend.core.domain)
|
implementation(projects.frontend.core.designSystem)
|
||||||
implementation(projects.frontend.core.designSystem)
|
implementation(projects.frontend.core.navigation)
|
||||||
implementation(projects.frontend.core.navigation)
|
implementation(projects.frontend.core.network)
|
||||||
implementation(projects.frontend.core.network)
|
implementation(projects.frontend.core.auth)
|
||||||
implementation(projects.frontend.core.auth)
|
|
||||||
|
|
||||||
// Feature-Module (die öffentlich sein dürfen)
|
// Feature-Module (die öffentlich sein dürfen)
|
||||||
implementation(projects.frontend.features.veranstaltungFeature)
|
implementation(projects.frontend.features.veranstaltungFeature)
|
||||||
implementation(projects.frontend.features.turnierFeature)
|
implementation(projects.frontend.features.turnierFeature)
|
||||||
implementation(projects.frontend.features.nennungFeature)
|
implementation(projects.frontend.features.nennungFeature)
|
||||||
implementation(projects.frontend.features.billingFeature)
|
implementation(projects.frontend.features.billingFeature)
|
||||||
|
|
||||||
// Compose Multiplatform
|
// Compose Multiplatform
|
||||||
implementation(compose.runtime)
|
implementation(compose.runtime)
|
||||||
implementation(compose.foundation)
|
implementation(compose.foundation)
|
||||||
implementation(compose.material3)
|
implementation(compose.material3)
|
||||||
implementation(compose.ui)
|
implementation(compose.ui)
|
||||||
implementation(compose.components.resources)
|
implementation(compose.components.resources)
|
||||||
implementation(libs.compose.materialIconsExtended)
|
implementation(libs.compose.materialIconsExtended)
|
||||||
|
|
||||||
// DI (Koin)
|
// DI (Koin)
|
||||||
implementation(libs.koin.core)
|
implementation(libs.koin.core)
|
||||||
implementation(libs.koin.compose)
|
implementation(libs.koin.compose)
|
||||||
implementation(libs.koin.compose.viewmodel)
|
implementation(libs.koin.compose.viewmodel)
|
||||||
|
|
||||||
// Bundles
|
// Bundles
|
||||||
implementation(libs.bundles.kmp.common)
|
implementation(libs.bundles.kmp.common)
|
||||||
implementation(libs.bundles.compose.common)
|
implementation(libs.bundles.compose.common)
|
||||||
}
|
}
|
||||||
|
|
||||||
wasmJsTest.dependencies {
|
wasmJsTest.dependencies {
|
||||||
// Core-Module
|
// Core-Module
|
||||||
implementation(projects.frontend.core.domain)
|
implementation(projects.frontend.core.domain)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+287
-264
@@ -21,334 +21,357 @@ import kotlinx.coroutines.launch
|
|||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WebMainScreen() {
|
fun WebMainScreen() {
|
||||||
val billingViewModel: BillingViewModel = koinViewModel()
|
MainAppContent()
|
||||||
val nennungRepository: NennungRemoteRepository = koinInject()
|
}
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
var currentScreen by remember { mutableStateOf<WebScreen>(WebScreen.Landing) }
|
|
||||||
|
|
||||||
Scaffold(
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
topBar = {
|
@Composable
|
||||||
TopAppBar(
|
fun MainAppContent() {
|
||||||
title = { Text("Meldestelle Online", fontWeight = FontWeight.Bold) },
|
val billingViewModel: BillingViewModel = koinViewModel()
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
val nennungRepository: NennungRemoteRepository = koinInject()
|
||||||
containerColor = AppColors.Primary,
|
val scope = rememberCoroutineScope()
|
||||||
titleContentColor = Color.White
|
var currentScreen by remember { mutableStateOf<WebScreen>(WebScreen.Landing) }
|
||||||
)
|
|
||||||
)
|
Scaffold(
|
||||||
}
|
topBar = {
|
||||||
) { padding ->
|
TopAppBar(
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(padding)) {
|
title = { Text("Meldestelle Online", fontWeight = FontWeight.Bold) },
|
||||||
when (val screen = currentScreen) {
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
is WebScreen.Landing -> LandingPage(
|
containerColor = AppColors.Primary,
|
||||||
onVeranstaltungClick = { vId ->
|
titleContentColor = Color.White
|
||||||
// Für den Prototyp zeigen wir einfach die Turniere dieser Veranstaltung
|
)
|
||||||
},
|
)
|
||||||
onNennenClick = { vId, tId ->
|
|
||||||
currentScreen = WebScreen.Nennung(vId, tId)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
is WebScreen.Nennung -> OnlineNennungFormular(
|
|
||||||
turnierNr = screen.turnierId.toString(),
|
|
||||||
onNennenAbgeschickt = { payload ->
|
|
||||||
scope.launch {
|
|
||||||
val result = nennungRepository.sendeNennung(screen.turnierId.toString(), payload)
|
|
||||||
if (result.isSuccess) {
|
|
||||||
currentScreen = WebScreen.Erfolg(payload.email)
|
|
||||||
} else {
|
|
||||||
// Hier könnte man eine Fehlermeldung anzeigen
|
|
||||||
println("Fehler beim Senden der Nennung: ${result.exceptionOrNull()?.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onBack = { currentScreen = WebScreen.Landing }
|
|
||||||
)
|
|
||||||
is WebScreen.Erfolg -> Erfolgsscreen(
|
|
||||||
email = screen.email,
|
|
||||||
onBack = { currentScreen = WebScreen.Landing }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
) { padding ->
|
||||||
|
Box(modifier = Modifier.fillMaxSize().padding(padding)) {
|
||||||
|
when (val screen = currentScreen) {
|
||||||
|
is WebScreen.Landing -> LandingPage(
|
||||||
|
onVeranstaltungClick = { vId ->
|
||||||
|
// Für den Prototyp zeigen wir einfach die Turniere dieser Veranstaltung
|
||||||
|
},
|
||||||
|
onNennenClick = { vId, tId ->
|
||||||
|
currentScreen = WebScreen.Nennung(vId, tId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
is WebScreen.Nennung -> OnlineNennungFormular(
|
||||||
|
turnierNr = screen.turnierId.toString(),
|
||||||
|
onNennenAbgeschickt = { payload ->
|
||||||
|
scope.launch {
|
||||||
|
val result = nennungRepository.sendeNennung(screen.turnierId.toString(), payload)
|
||||||
|
if (result.isSuccess) {
|
||||||
|
currentScreen = WebScreen.Erfolg(payload.email)
|
||||||
|
} else {
|
||||||
|
// Hier könnte man eine Fehlermeldung anzeigen
|
||||||
|
println("Fehler beim Senden der Nennung: ${result.exceptionOrNull()?.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBack = { currentScreen = WebScreen.Landing }
|
||||||
|
)
|
||||||
|
|
||||||
|
is WebScreen.Erfolg -> Erfolgsscreen(
|
||||||
|
email = screen.email,
|
||||||
|
onBack = { currentScreen = WebScreen.Landing }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class WebScreen {
|
sealed class WebScreen {
|
||||||
data object Landing : WebScreen()
|
data object Landing : WebScreen()
|
||||||
data class Nennung(val veranstaltungId: Long, val turnierId: Long) : WebScreen()
|
data class Nennung(val veranstaltungId: Long, val turnierId: Long) : WebScreen()
|
||||||
data class Erfolg(val email: String) : WebScreen()
|
data class Erfolg(val email: String) : WebScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Erfolgsscreen(email: String, onBack: () -> Unit) {
|
fun Erfolgsscreen(email: String, onBack: () -> Unit) {
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) {
|
||||||
Card(
|
Card(
|
||||||
colors = CardDefaults.cardColors(containerColor = AppColors.PrimaryContainer),
|
colors = CardDefaults.cardColors(containerColor = AppColors.PrimaryContainer),
|
||||||
modifier = Modifier.fillMaxWidth().padding(16.dp)
|
modifier = Modifier.fillMaxWidth().padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(modifier = Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Text("Nennung erfolgreich eingegangen!", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
Text(
|
||||||
Spacer(Modifier.height(16.dp))
|
"Nennung erfolgreich eingegangen!",
|
||||||
Text("Eine Bestätigungsmail wurde an $email gesendet.", style = MaterialTheme.typography.bodyLarge)
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
Spacer(Modifier.height(24.dp))
|
fontWeight = FontWeight.Bold
|
||||||
Button(onClick = onBack) {
|
)
|
||||||
Text("Zurück zur Startseite")
|
Spacer(Modifier.height(16.dp))
|
||||||
}
|
Text("Eine Bestätigungsmail wurde an $email gesendet.", style = MaterialTheme.typography.bodyLarge)
|
||||||
}
|
Spacer(Modifier.height(24.dp))
|
||||||
|
Button(onClick = onBack) {
|
||||||
|
Text("Zurück zur Startseite")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LandingPage(
|
fun LandingPage(
|
||||||
onVeranstaltungClick: (Long) -> Unit,
|
onVeranstaltungClick: (Long) -> Unit,
|
||||||
onNennenClick: (Long, Long) -> Unit
|
onNennenClick: (Long, Long) -> Unit
|
||||||
) {
|
) {
|
||||||
val veranstaltungen = remember {
|
val veranstaltungen = remember {
|
||||||
listOf(
|
listOf(
|
||||||
VeranstaltungWebModel(
|
VeranstaltungWebModel(
|
||||||
id = 1,
|
id = 1,
|
||||||
name = "CSN-B* Neumarkt",
|
name = "CSN-B* Neumarkt",
|
||||||
ort = "Neumarkt am Wallersee",
|
ort = "Neumarkt am Wallersee",
|
||||||
datum = "24. - 26. April 2026",
|
datum = "24. - 26. April 2026",
|
||||||
turniere = listOf(
|
turniere = listOf(
|
||||||
TurnierWebModel(101, "Springturnier Neumarkt", "Ausschreibung_Neumarkt.pdf"),
|
TurnierWebModel(101, "Springturnier Neumarkt", "Ausschreibung_Neumarkt.pdf"),
|
||||||
TurnierWebModel(102, "Dressurturnier Neumarkt", "Ausschreibung_Dressur.pdf")
|
TurnierWebModel(102, "Dressurturnier Neumarkt", "Ausschreibung_Dressur.pdf")
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize().padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
"Willkommen bei der Meldestelle Online",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
color = AppColors.OnBackgroundLight
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Hier finden Sie aktuelle Reitturniere und können Ihre Nennungen online abgeben.",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
item {
|
||||||
modifier = Modifier.fillMaxSize().padding(16.dp),
|
Text(
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
"Aktuelle Veranstaltungen",
|
||||||
) {
|
style = MaterialTheme.typography.titleLarge,
|
||||||
item {
|
fontWeight = FontWeight.Bold,
|
||||||
Text(
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
"Willkommen bei der Meldestelle Online",
|
)
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
|
||||||
color = AppColors.OnBackgroundLight
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
"Hier finden Sie aktuelle Reitturniere und können Ihre Nennungen online abgeben.",
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
modifier = Modifier.padding(top = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
Text(
|
|
||||||
"Aktuelle Veranstaltungen",
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier.padding(vertical = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
items(veranstaltungen) { veranstaltung ->
|
|
||||||
VeranstaltungsCardWeb(
|
|
||||||
veranstaltung = veranstaltung,
|
|
||||||
onNennenClick = { tId -> onNennenClick(veranstaltung.id, tId) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items(veranstaltungen) { veranstaltung ->
|
||||||
|
VeranstaltungsCardWeb(
|
||||||
|
veranstaltung = veranstaltung,
|
||||||
|
onNennenClick = { tId -> onNennenClick(veranstaltung.id, tId) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VeranstaltungsCardWeb(
|
fun VeranstaltungsCardWeb(
|
||||||
veranstaltung: VeranstaltungWebModel,
|
veranstaltung: VeranstaltungWebModel,
|
||||||
onNennenClick: (Long) -> Unit
|
onNennenClick: (Long) -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(containerColor = Color.White),
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text(veranstaltung.name, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
Text(veranstaltung.name, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
||||||
Text("${veranstaltung.datum} | ${veranstaltung.ort}", style = MaterialTheme.typography.bodyMedium, color = Color.Gray)
|
Text(
|
||||||
|
"${veranstaltung.datum} | ${veranstaltung.ort}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
Text("Turniere dieser Veranstaltung:", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
|
Text(
|
||||||
|
"Turniere dieser Veranstaltung:",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
|
||||||
veranstaltung.turniere.forEach { turnier ->
|
veranstaltung.turniere.forEach { turnier ->
|
||||||
TurnierCardWeb(
|
TurnierCardWeb(
|
||||||
turnier = turnier,
|
turnier = turnier,
|
||||||
onNennenClick = { onNennenClick(turnier.id) }
|
onNennenClick = { onNennenClick(turnier.id) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TurnierCardWeb(
|
fun TurnierCardWeb(
|
||||||
turnier: TurnierWebModel,
|
turnier: TurnierWebModel,
|
||||||
onNennenClick: () -> Unit
|
onNennenClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
OutlinedCard(
|
OutlinedCard(
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||||
colors = CardDefaults.outlinedCardColors(containerColor = AppColors.BackgroundLight)
|
colors = CardDefaults.outlinedCardColors(containerColor = AppColors.BackgroundLight)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
Row(
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
modifier = Modifier.padding(12.dp),
|
Text(turnier.name, fontWeight = FontWeight.Bold)
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
}
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(turnier.name, fontWeight = FontWeight.Bold)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
TextButton(onClick = { /* PDF öffnen Logik */ }) {
|
TextButton(onClick = { /* PDF öffnen Logik */ }) {
|
||||||
Icon(Icons.Default.Description, contentDescription = null)
|
Icon(Icons.Default.Description, contentDescription = null)
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
Text("Ausschreibung")
|
Text("Ausschreibung")
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = onNennenClick,
|
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = AppColors.Success)
|
|
||||||
) {
|
|
||||||
Icon(Icons.AutoMirrored.Filled.OpenInNew, contentDescription = null)
|
|
||||||
Spacer(Modifier.width(4.dp))
|
|
||||||
Text("Online-Nennen")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onNennenClick,
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = AppColors.Success)
|
||||||
|
) {
|
||||||
|
Icon(Icons.AutoMirrored.Filled.OpenInNew, contentDescription = null)
|
||||||
|
Spacer(Modifier.width(4.dp))
|
||||||
|
Text("Online-Nennen")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NennungWebFormular(
|
fun NennungWebFormular(
|
||||||
veranstaltungId: Long,
|
veranstaltungId: Long,
|
||||||
turnierId: Long,
|
turnierId: Long,
|
||||||
billingViewModel: BillingViewModel,
|
billingViewModel: BillingViewModel,
|
||||||
onBack: () -> Unit
|
onBack: () -> Unit
|
||||||
) {
|
) {
|
||||||
var statusMessage by remember { mutableStateOf<String?>(null) }
|
var statusMessage by remember { mutableStateOf<String?>(null) }
|
||||||
val uiState by billingViewModel.uiState.collectAsState()
|
val uiState by billingViewModel.uiState.collectAsState()
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
|
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
|
||||||
Text("Online-Nennung", style = MaterialTheme.typography.headlineMedium)
|
Text("Online-Nennung", style = MaterialTheme.typography.headlineMedium)
|
||||||
Text("Turnier ID: $turnierId", style = MaterialTheme.typography.bodyMedium)
|
Text("Turnier ID: $turnierId", style = MaterialTheme.typography.bodyMedium)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
if (statusMessage == null) {
|
if (statusMessage == null) {
|
||||||
// Vereinfachtes Formular für den Prototyp
|
// Vereinfachtes Formular für den Prototyp
|
||||||
var reiter by remember { mutableStateOf("") }
|
var reiter by remember { mutableStateOf("") }
|
||||||
var pferd by remember { mutableStateOf("") }
|
var pferd by remember { mutableStateOf("") }
|
||||||
var bewerbe by remember { mutableStateOf("") }
|
var bewerbe by remember { mutableStateOf("") }
|
||||||
var email by remember { mutableStateOf("") }
|
var email by remember { mutableStateOf("") }
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = reiter,
|
value = reiter,
|
||||||
onValueChange = { reiter = it },
|
onValueChange = { reiter = it },
|
||||||
label = { Text("Reiter Name / ZNS-Nummer") },
|
label = { Text("Reiter Name / ZNS-Nummer") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = pferd,
|
value = pferd,
|
||||||
onValueChange = { pferd = it },
|
onValueChange = { pferd = it },
|
||||||
label = { Text("Pferd Name / Kopfnummer") },
|
label = { Text("Pferd Name / Kopfnummer") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = bewerbe,
|
value = bewerbe,
|
||||||
onValueChange = { bewerbe = it },
|
onValueChange = { bewerbe = it },
|
||||||
label = { Text("Bewerbe (z.B. 1, 2, 5)") },
|
label = { Text("Bewerbe (z.B. 1, 2, 5)") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = email,
|
value = email,
|
||||||
onValueChange = { email = it },
|
onValueChange = { email = it },
|
||||||
label = { Text("E-Mail für Bestätigung (optional)") },
|
label = { Text("E-Mail für Bestätigung (optional)") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
if (uiState.error != null) {
|
if (uiState.error != null) {
|
||||||
Text(uiState.error!!, color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(bottom = 8.dp))
|
Text(uiState.error!!, color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(bottom = 8.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
OutlinedButton(onClick = onBack, enabled = !uiState.isLoading) { Text("Abbrechen") }
|
OutlinedButton(onClick = onBack, enabled = !uiState.isLoading) { Text("Abbrechen") }
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
// Wir simulieren eine Buchung beim Nennen
|
// Wir simulieren eine Buchung beim Nennen
|
||||||
billingViewModel.loadKonto(veranstaltungId.toString(), reiter, reiter)
|
billingViewModel.loadKonto(veranstaltungId.toString(), reiter, reiter)
|
||||||
// In einem echten Flow würden wir auf das geladene Konto warten und dann buchen
|
// In einem echten Flow würden wir auf das geladene Konto warten und dann buchen
|
||||||
// Hier setzen wir direkt die Erfolgsmeldung für die Demo
|
// Hier setzen wir direkt die Erfolgsmeldung für die Demo
|
||||||
statusMessage = "Nennung erfolgreich abgeschickt! Eine Bestätigung wurde an $email gesendet."
|
statusMessage = "Nennung erfolgreich abgeschickt! Eine Bestätigung wurde an $email gesendet."
|
||||||
},
|
},
|
||||||
enabled = reiter.isNotBlank() && pferd.isNotBlank() && bewerbe.isNotBlank() && !uiState.isLoading
|
enabled = reiter.isNotBlank() && pferd.isNotBlank() && bewerbe.isNotBlank() && !uiState.isLoading
|
||||||
) {
|
) {
|
||||||
if (uiState.isLoading) {
|
if (uiState.isLoading) {
|
||||||
CircularProgressIndicator(modifier = Modifier.size(24.dp), color = Color.White)
|
CircularProgressIndicator(modifier = Modifier.size(24.dp), color = Color.White)
|
||||||
} else {
|
} else {
|
||||||
Text("Jetzt Nennen")
|
Text("Jetzt Nennen")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Card(
|
|
||||||
colors = CardDefaults.cardColors(containerColor = AppColors.PrimaryContainer),
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(statusMessage!!, color = AppColors.OnPrimaryContainer)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
if (uiState.selectedKonto != null) {
|
|
||||||
Text("Aktueller Saldo: ${uiState.selectedKonto!!.saldoCent / 100.0} €", fontWeight = FontWeight.Bold)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = { billingViewModel.downloadRechnung() },
|
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = AppColors.Secondary)
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.Description, contentDescription = null)
|
|
||||||
Spacer(Modifier.width(8.dp))
|
|
||||||
Text("Rechnung herunterladen")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uiState.pdfData != null) {
|
|
||||||
Text("PDF generiert (${uiState.pdfData!!.size} Bytes)", style = MaterialTheme.typography.labelSmall, modifier = Modifier.padding(top = 4.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(onClick = onBack) { Text("Zurück zur Übersicht") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(containerColor = AppColors.PrimaryContainer),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(statusMessage!!, color = AppColors.OnPrimaryContainer)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (uiState.selectedKonto != null) {
|
||||||
|
Text("Aktueller Saldo: ${uiState.selectedKonto!!.saldoCent / 100.0} €", fontWeight = FontWeight.Bold)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { billingViewModel.downloadRechnung() },
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = AppColors.Secondary)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Description, contentDescription = null)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text("Rechnung herunterladen")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uiState.pdfData != null) {
|
||||||
|
Text(
|
||||||
|
"PDF generiert (${uiState.pdfData!!.size} Bytes)",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(onClick = onBack) { Text("Zurück zur Übersicht") }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class VeranstaltungWebModel(
|
data class VeranstaltungWebModel(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
val ort: String,
|
val ort: String,
|
||||||
val datum: String,
|
val datum: String,
|
||||||
val turniere: List<TurnierWebModel>
|
val turniere: List<TurnierWebModel>
|
||||||
)
|
)
|
||||||
|
|
||||||
data class TurnierWebModel(
|
data class TurnierWebModel(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
val pdfUrl: String
|
val pdfUrl: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package at.mocode.web
|
|||||||
|
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.window.ComposeViewport
|
import androidx.compose.ui.window.ComposeViewport
|
||||||
|
import at.mocode.frontend.core.auth.di.authModule
|
||||||
import at.mocode.frontend.core.designsystem.theme.AppTheme
|
import at.mocode.frontend.core.designsystem.theme.AppTheme
|
||||||
import at.mocode.frontend.core.network.networkModule
|
import at.mocode.frontend.core.network.networkModule
|
||||||
import at.mocode.frontend.features.billing.di.billingModule
|
import at.mocode.frontend.features.billing.di.billingModule
|
||||||
@@ -11,14 +12,15 @@ import org.koin.core.context.startKoin
|
|||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
fun main() {
|
fun main() {
|
||||||
startKoin {
|
startKoin {
|
||||||
modules(
|
modules(
|
||||||
networkModule,
|
networkModule,
|
||||||
billingModule,
|
authModule,
|
||||||
nennungFeatureModule,
|
billingModule,
|
||||||
turnierFeatureModule,
|
nennungFeatureModule,
|
||||||
)
|
turnierFeatureModule,
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ComposeViewport("compose-target") {
|
ComposeViewport("compose-target") {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Meldestelle Web</title>
|
<title>Meldestelle Web</title>
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
#compose-target {
|
|
||||||
width: 100%;
|
#compose-target {
|
||||||
height: 100%;
|
width: 100%;
|
||||||
}
|
height: 100%;
|
||||||
</style>
|
}
|
||||||
<script type="application/javascript" src="meldestelle-web.js"></script>
|
</style>
|
||||||
|
<script type="application/javascript" src="meldestelle-web.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="compose-target"></div>
|
<div id="compose-target"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -39,5 +39,5 @@ dependencies {
|
|||||||
implementation(projects.frontend.core.sync)
|
implementation(projects.frontend.core.sync)
|
||||||
|
|
||||||
implementation(projects.frontend.shells.meldestelleDesktop)
|
implementation(projects.frontend.shells.meldestelleDesktop)
|
||||||
implementation(projects.frontend.shells.meldestelleWeb)
|
// implementation(projects.frontend.shells.meldestelleWeb) // WASM-only modules cannot be tested with ArchUnit (JVM-only)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user