feat(auth): Guest-Mode + Login-optionale Surface
RootView ohne Hard-Login-Gate — TabBar zeigt sich immer, beim Start wechselt App bei .signedOut automatisch in den anonymen .guest-Modus (mana-swift-core v1.2.0). Auth-Sheets (Login, SignUp, Forgot, Reset) hängen jetzt als ManaAuthGate-Modifier am Root. AccountView zeigt im Guest-Modus eine eigene CTA-Surface („Anmelden / Konto erstellen" + Hinweis was Login bringt). signOut nutzt keepGuestMode: true → App bleibt nach Logout anonym nutzbar, Marketplace und lokale Daten gehen nicht verloren. DeckListView: Empty-State im Guest-Mode mit Login-CTA + Marketplace- Hinweis. Toolbar-„+"-Button via authGate.require gewrappt — Tap aus dem Guest-Modus öffnet erst das Sign-In-Sheet, danach den Editor. DeckListStore.refresh() skippt im Guest-Mode (kein 401-Spam). Cache wird so wie er ist gerendert (heute leer, später Marketplace-Klone). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
da6679770b
commit
8ca7bd3636
5 changed files with 276 additions and 160 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import ManaAuthUI
|
||||
import ManaCore
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
|
@ -15,13 +16,17 @@ enum DeckRoute: Hashable {
|
|||
/// `cards/apps/web/src/routes/decks/+page.svelte`.
|
||||
struct DeckListView: View {
|
||||
@Environment(AuthClient.self) private var auth
|
||||
@Environment(ManaAuthGate.self) private var authGate
|
||||
@Environment(\.modelContext) private var context
|
||||
@Query(sort: \CachedDeck.updatedAt, order: .reverse) private var decks: [CachedDeck]
|
||||
|
||||
@Binding var showCreate: Bool
|
||||
|
||||
private var isGuest: Bool {
|
||||
if case .signedIn = auth.status { false } else { true }
|
||||
}
|
||||
|
||||
@State private var store: DeckListStore?
|
||||
@State private var showAccount = false
|
||||
@State private var pendingShares: [PendingShare] = []
|
||||
@State private var path = NavigationPath()
|
||||
|
||||
|
|
@ -67,16 +72,6 @@ struct DeckListView: View {
|
|||
.onAppear {
|
||||
pendingShares = PendingShareStore.readAll()
|
||||
}
|
||||
.sheet(isPresented: $showAccount) {
|
||||
NavigationStack {
|
||||
AccountView()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Fertig") { showAccount = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +99,7 @@ struct DeckListView: View {
|
|||
}
|
||||
|
||||
private var subscribedDecks: [CachedDeck] {
|
||||
decks.filter { $0.isFromMarketplace }
|
||||
decks.filter(\.isFromMarketplace)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
@ -172,7 +167,10 @@ struct DeckListView: View {
|
|||
.foregroundStyle(CardsTheme.mutedForeground)
|
||||
}
|
||||
.padding(14)
|
||||
.background(CardsTheme.primary.opacity(0.08), in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
.background(
|
||||
CardsTheme.primary.opacity(0.08),
|
||||
in: RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.stroke(CardsTheme.primary.opacity(0.18), lineWidth: 1)
|
||||
|
|
@ -207,7 +205,10 @@ struct DeckListView: View {
|
|||
.foregroundStyle(CardsTheme.mutedForeground)
|
||||
}
|
||||
.padding(14)
|
||||
.background(CardsTheme.warning.opacity(0.12), in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
.background(
|
||||
CardsTheme.warning.opacity(0.12),
|
||||
in: RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
|
@ -231,13 +232,31 @@ struct DeckListView: View {
|
|||
Text(message)
|
||||
.foregroundStyle(CardsTheme.mutedForeground)
|
||||
}
|
||||
} else if isGuest {
|
||||
ContentUnavailableView {
|
||||
Label("Cardecky ohne Konto", systemImage: "person.crop.circle.dashed")
|
||||
.foregroundStyle(CardsTheme.foreground)
|
||||
} description: {
|
||||
Text(
|
||||
"Browse den Marketplace im Entdecken-Tab — kein Konto nötig. Für eigene Decks und Cloud-Sync logge dich ein."
|
||||
)
|
||||
.foregroundStyle(CardsTheme.mutedForeground)
|
||||
} actions: {
|
||||
Button("Anmelden / Konto erstellen") {
|
||||
authGate.require(reason: "deck-list-empty") {}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(CardsTheme.primary)
|
||||
}
|
||||
} else {
|
||||
ContentUnavailableView {
|
||||
Label("Noch keine Decks", systemImage: "rectangle.stack")
|
||||
.foregroundStyle(CardsTheme.foreground)
|
||||
} description: {
|
||||
Text("Tippe unten auf »+«, um dein erstes Deck zu erstellen, oder browse den Marketplace im Entdecken-Tab.")
|
||||
.foregroundStyle(CardsTheme.mutedForeground)
|
||||
Text(
|
||||
"Tippe unten auf »+«, um dein erstes Deck zu erstellen, oder browse den Marketplace im Entdecken-Tab."
|
||||
)
|
||||
.foregroundStyle(CardsTheme.mutedForeground)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -252,7 +271,9 @@ struct DeckListView: View {
|
|||
if #unavailable(iOS 26.0) {
|
||||
ToolbarItemGroup(placement: .bottomBar) {
|
||||
Button {
|
||||
showCreate = true
|
||||
authGate.require(reason: "deck-create-toolbar") {
|
||||
showCreate = true
|
||||
}
|
||||
} label: {
|
||||
Label("Deck hinzufügen", systemImage: "plus")
|
||||
.labelStyle(.iconOnly)
|
||||
|
|
@ -262,19 +283,5 @@ struct DeckListView: View {
|
|||
Spacer()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
showAccount = true
|
||||
} label: {
|
||||
Image(systemName: accountIcon)
|
||||
.foregroundStyle(CardsTheme.primary)
|
||||
}
|
||||
.accessibilityLabel("Account")
|
||||
}
|
||||
}
|
||||
|
||||
private var accountIcon: String {
|
||||
if case .signedIn = auth.status { return "person.crop.circle.fill" }
|
||||
return "person.crop.circle.badge.exclamationmark"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue