import SwiftUI private let loginAccent = Color(red: 0.12, green: 0.40, blue: 0.31) private let loginGold = Color(red: 0.90, green: 0.79, blue: 0.60) struct LoginRootView: View { @ObservedObject var model: AppViewModel @Environment(\.horizontalSizeClass) private var horizontalSizeClass var body: some View { ScrollView { VStack(spacing: compactLayout ? 18 : 24) { LoginHeroPanel(model: model, compactLayout: compactLayout) PairingConsoleCard(model: model, compactLayout: compactLayout) TrustFootprintCard(model: model, compactLayout: compactLayout) } .frame(maxWidth: 1040) .padding(compactLayout ? 18 : 28) } .sheet(isPresented: $model.isScannerPresented) { QRScannerSheet( seededPayload: model.suggestedQRCodePayload, onCodeScanned: { payload in model.manualQRCodePayload = payload Task { await model.signIn(with: payload) } } ) } } 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 { ZStack(alignment: .bottomLeading) { RoundedRectangle(cornerRadius: 36, style: .continuous) .fill( LinearGradient( colors: [ Color(red: 0.13, green: 0.22, blue: 0.19), Color(red: 0.20, green: 0.41, blue: 0.33), loginGold ], startPoint: .topLeading, endPoint: .bottomTrailing ) ) VStack(alignment: .leading, spacing: compactLayout ? 16 : 18) { Text("Bind this device to your idp.global account") .font(.system(size: compactLayout ? 32 : 44, weight: .bold, design: .rounded)) .foregroundStyle(.white) Text("Scan the pairing QR from your account to turn this device into your approval and notification app.") .font(compactLayout ? .body : .title3) .foregroundStyle(.white.opacity(0.88)) if compactLayout { VStack(alignment: .leading, spacing: 10) { HeroTag(title: "Account binding") HeroTag(title: "QR pairing") HeroTag(title: "iPhone, iPad, Mac") } } else { HStack(spacing: 12) { HeroTag(title: "Account binding") HeroTag(title: "QR pairing") HeroTag(title: "iPhone, iPad, Mac") } } if model.isBootstrapping { ProgressView("Preparing preview pairing payload…") .tint(.white) } } .padding(compactLayout ? 22 : 32) } .frame(minHeight: compactLayout ? 280 : 320) } } private struct PairingConsoleCard: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { LoginCard(title: "Bind your account", subtitle: "Scan the QR code from your idp.global account or use the preview payload while backend wiring is still in progress.") { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { Text("Open your account pairing screen, then scan the QR code here.") .font(.headline) Text("If you are testing the preview build without the live backend yet, the seeded payload below will still bind the mock session.") .foregroundStyle(.secondary) } TextEditor(text: $model.manualQRCodePayload) .font(.body.monospaced()) .scrollContentBackground(.hidden) .padding(16) .frame(minHeight: compactLayout ? 130 : 150) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 24, style: .continuous)) if model.isAuthenticating { HStack(spacing: 10) { ProgressView() Text("Binding this device to your account…") .foregroundStyle(.secondary) } } Group { if compactLayout { VStack(spacing: 12) { primaryButtons secondaryButtons } } else { VStack(spacing: 12) { HStack(spacing: 12) { primaryButtons } HStack(spacing: 12) { secondaryButtons } } } } } } } @ViewBuilder private var primaryButtons: some View { Button { model.isScannerPresented = true } label: { Label("Bind With QR Code", systemImage: "qrcode.viewfinder") } .buttonStyle(.borderedProminent) Button { Task { await model.signInWithManualCode() } } label: { if model.isAuthenticating { ProgressView() } else { Label("Bind With Payload", systemImage: "arrow.right.circle.fill") } } .buttonStyle(.bordered) .disabled(model.isAuthenticating) } @ViewBuilder private var secondaryButtons: some View { Button { Task { await model.signInWithSuggestedCode() } } label: { Label("Use Preview QR", systemImage: "wand.and.stars") } .buttonStyle(.bordered) Text("This preview keeps the account-binding flow realistic while the live API is still being wired in.") .font(.footnote) .foregroundStyle(.secondary) .frame(maxWidth: .infinity, alignment: compactLayout ? .leading : .trailing) } } private struct TrustFootprintCard: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { LoginCard(title: "About this build", subtitle: "Keep the first-run screen simple, but still explain the trust context and preview status clearly.") { VStack(alignment: .leading, spacing: 16) { if compactLayout { VStack(spacing: 12) { trustFacts } } else { HStack(alignment: .top, spacing: 12) { trustFacts } } VStack(alignment: .leading, spacing: 8) { Text("Preview Pairing Payload") .font(.headline) Text(model.suggestedQRCodePayload.isEmpty ? "Preparing preview payload…" : model.suggestedQRCodePayload) .font(.footnote.monospaced()) .foregroundStyle(.secondary) .padding(14) .frame(maxWidth: .infinity, alignment: .leading) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 22, style: .continuous)) } } } } @ViewBuilder private var trustFacts: some View { TrustFactCard( icon: "person.badge.key.fill", title: "Account Binding", message: "This device binds to your idp.global account and becomes your place for approvals and alerts." ) TrustFactCard( icon: "person.2.badge.gearshape.fill", title: "Built by foss.global", message: "foss.global is the open-source collective behind idp.global and the current preview environment." ) TrustFactCard( icon: "bolt.badge.clock", title: "Preview Backend", message: "Login, requests, and notifications are mocked behind a clean service boundary until live integration is ready." ) } } private struct LoginCard: View { let title: String let subtitle: String let content: () -> Content init(title: String, subtitle: String, @ViewBuilder content: @escaping () -> Content) { self.title = title self.subtitle = subtitle self.content = content } var body: some View { VStack(alignment: .leading, spacing: 18) { VStack(alignment: .leading, spacing: 6) { Text(title) .font(.title2.weight(.semibold)) Text(subtitle) .foregroundStyle(.secondary) } content() } .padding(24) .frame(maxWidth: .infinity, alignment: .leading) .background(Color.white.opacity(0.68), in: RoundedRectangle(cornerRadius: 32, style: .continuous)) } } private struct HeroTag: View { let title: String var body: some View { Text(title) .font(.caption.weight(.semibold)) .foregroundStyle(.white) .padding(.horizontal, 12) .padding(.vertical, 9) .background(.white.opacity(0.14), in: RoundedRectangle(cornerRadius: 16, style: .continuous)) } } private struct TrustFactCard: View { let icon: String let title: String let message: String var body: some View { VStack(alignment: .leading, spacing: 12) { Image(systemName: icon) .font(.title2) .foregroundStyle(loginAccent) Text(title) .font(.headline) Text(message) .foregroundStyle(.secondary) } .padding(18) .frame(maxWidth: .infinity, alignment: .leading) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 24, style: .continuous)) } }