import SwiftUI private let loginAccent = AppTheme.accent struct LoginRootView: View { @ObservedObject var model: AppViewModel @Environment(\.horizontalSizeClass) private var horizontalSizeClass var body: some View { AppScrollScreen(compactLayout: compactLayout) { LoginHeroPanel(model: model, compactLayout: compactLayout) PairingConsoleCard(model: model, compactLayout: compactLayout) } .sheet(isPresented: $model.isScannerPresented) { QRScannerSheet( seededPayload: model.suggestedPairingPayload, title: "Scan linking QR", description: "Use the camera to scan the QR code from the web flow that activates this device as your passport.", navigationTitle: "Scan Linking QR", onCodeScanned: { payload in model.manualPairingPayload = payload Task { await model.signIn(with: payload, transport: .qr) } } ) } } private var compactLayout: Bool { #if os(iOS) horizontalSizeClass == .compact #else false #endif } } private struct LoginHeroPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { AppPanel(compactLayout: compactLayout, radius: AppLayout.largeCardRadius) { AppBadge(title: "Secure passport setup", tone: loginAccent) Text("Turn this device into a passport for your idp.global identity") .font(.system(size: compactLayout ? 28 : 36, weight: .bold, design: .rounded)) .lineLimit(3) Text("Scan a linking QR code or paste a payload to activate this device as your passport for identity proofs and security alerts.") .font(.subheadline) .foregroundStyle(.secondary) Divider() VStack(alignment: .leading, spacing: 14) { LoginFeatureRow(icon: "qrcode.viewfinder", title: "Scan a QR code from the web flow") LoginFeatureRow(icon: "doc.text.viewfinder", title: "Paste a payload when you already have one") LoginFeatureRow(icon: "iphone.gen3", title: "Handle identity checks and alerts here") } if model.isBootstrapping { ProgressView("Preparing preview passport...") .tint(loginAccent) } } } } private struct LoginFeatureRow: View { let icon: String let title: String var body: some View { HStack(alignment: .center, spacing: 12) { Image(systemName: icon) .font(.subheadline.weight(.semibold)) .foregroundStyle(loginAccent) .frame(width: 28, height: 28) Text(title) .font(.headline) Spacer(minLength: 0) } } } private struct PairingConsoleCard: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { AppSectionCard(title: "Set up passport", compactLayout: compactLayout) { VStack(alignment: .leading, spacing: 8) { Text("Link payload") .font(.subheadline.weight(.semibold)) AppTextEditorField( text: $model.manualPairingPayload, minHeight: compactLayout ? 132 : 150 ) } if model.isAuthenticating { HStack(spacing: 10) { ProgressView() Text("Activating this passport...") .foregroundStyle(.secondary) } } Text("NFC, QR, and OTP proof methods become available after this passport is active.") .font(.footnote) .foregroundStyle(.secondary) if compactLayout { VStack(spacing: 12) { primaryButtons secondaryButtons } } else { VStack(spacing: 12) { HStack(spacing: 12) { primaryButtons } secondaryButtons } } } } @ViewBuilder private var primaryButtons: some View { Button { model.isScannerPresented = true } label: { Label("Scan QR", systemImage: "qrcode.viewfinder") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) } @ViewBuilder private var secondaryButtons: some View { if compactLayout { VStack(spacing: 12) { usePayloadButton previewPayloadButton } } else { HStack(spacing: 12) { usePayloadButton previewPayloadButton } } } private var usePayloadButton: some View { Button { Task { await model.signInWithManualPayload() } } label: { if model.isAuthenticating { ProgressView() .frame(maxWidth: .infinity) } else { Label("Link with payload", systemImage: "arrow.right.circle") .frame(maxWidth: .infinity) } } .buttonStyle(.bordered) .controlSize(.large) .disabled(model.isAuthenticating) } private var previewPayloadButton: some View { Button { Task { await model.signInWithSuggestedPayload() } } label: { Label("Use preview passport", systemImage: "wand.and.stars") .frame(maxWidth: .infinity) } .buttonStyle(.bordered) .controlSize(.large) } }