import ManaCore import SwiftUI /// Explore-Tab: Featured + Trending Sections, plus Browse-Link. struct ExploreView: View { @Binding var deepLinkSlug: String? @Environment(AuthClient.self) private var auth @State private var store: MarketplaceStore? @State private var path: [MarketplaceRoute] = [] init(deepLinkSlug: Binding = .constant(nil)) { _deepLinkSlug = deepLinkSlug } var body: some View { NavigationStack(path: $path) { ZStack { CardsTheme.background.ignoresSafeArea() content } .navigationTitle("Entdecken") .navigationDestination(for: MarketplaceRoute.self) { route in switch route { case .browse: BrowseView() case let .publicDeck(slug): PublicDeckView(slug: slug) } } .navigationDestination(for: String.self) { deckId in DeckDetailView(deckId: deckId) } .refreshable { await store?.loadExplore() } .task { if store == nil { store = MarketplaceStore(auth: auth) } await store?.loadExplore() } .onChange(of: deepLinkSlug) { _, newSlug in guard let slug = newSlug else { return } path = [.publicDeck(slug: slug)] deepLinkSlug = nil } } } @ViewBuilder private var content: some View { if let store { if store.isLoadingExplore, store.featured.isEmpty, store.trending.isEmpty { ProgressView() .tint(CardsTheme.primary) } else if let message = store.errorMessage, store.featured.isEmpty { ContentUnavailableView( "Marketplace nicht erreichbar", systemImage: "wifi.exclamationmark", description: Text(message) ) .foregroundStyle(CardsTheme.foreground) } else { ScrollView { VStack(alignment: .leading, spacing: 24) { if !store.featured.isEmpty { section(title: "Vorgestellt", items: store.featured) } if !store.trending.isEmpty { section(title: "Im Trend", items: store.trending) } NavigationLink(value: MarketplaceRoute.browse) { HStack { Label("Alle Decks durchsuchen", systemImage: "magnifyingglass") Spacer() Image(systemName: "chevron.right") .font(.footnote) } .padding() .background(CardsTheme.surface, in: RoundedRectangle(cornerRadius: 10)) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(CardsTheme.border, lineWidth: 1) ) .foregroundStyle(CardsTheme.foreground) } .buttonStyle(.plain) .padding(.horizontal, 16) } .padding(.vertical, 16) } } } } private func section(title: String, items: [PublicDeckEntry]) -> some View { VStack(alignment: .leading, spacing: 12) { Text(title) .font(.title3.weight(.semibold)) .foregroundStyle(CardsTheme.foreground) .padding(.horizontal, 16) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 12) { ForEach(items) { item in NavigationLink(value: MarketplaceRoute.publicDeck(slug: item.slug)) { PublicDeckCard(entry: item) } .buttonStyle(.plain) } } .padding(.horizontal, 16) } } } } /// Routen-Enum für die Marketplace-NavigationStack. enum MarketplaceRoute: Hashable { case browse case publicDeck(slug: String) } /// Public-Deck-Karten-Tile in Featured/Trending-Carousels und Browse-Grid. struct PublicDeckCard: View { let entry: PublicDeckEntry var body: some View { VStack(alignment: .leading, spacing: 8) { HStack { Text(entry.title) .font(.headline) .foregroundStyle(CardsTheme.foreground) .lineLimit(2) Spacer() if entry.isFeatured { Image(systemName: "star.fill") .font(.caption) .foregroundStyle(CardsTheme.warning) } } if let description = entry.description, !description.isEmpty { Text(description) .font(.caption) .foregroundStyle(CardsTheme.mutedForeground) .lineLimit(2) } HStack(spacing: 12) { Label("\(entry.cardCount)", systemImage: "rectangle.stack") Label("\(entry.starCount)", systemImage: "star") if entry.isPaid { Label("\(entry.priceCredits) Credits", systemImage: "creditcard") .foregroundStyle(CardsTheme.primary) } } .font(.caption2) .foregroundStyle(CardsTheme.mutedForeground) HStack(spacing: 4) { Text(entry.owner.displayName) .font(.caption2) .foregroundStyle(CardsTheme.mutedForeground) if entry.owner.verifiedMana { Image(systemName: "checkmark.seal.fill") .font(.caption2) .foregroundStyle(CardsTheme.primary) } } } .padding(12) .frame(width: 260, alignment: .leading) .background(CardsTheme.surface, in: RoundedRectangle(cornerRadius: 10)) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(CardsTheme.border, lineWidth: 1) ) } }