import SwiftUI private let dashboardAccent = Color(red: 0.12, green: 0.40, blue: 0.31) private let dashboardGold = Color(red: 0.84, green: 0.71, blue: 0.48) private let dashboardBorder = Color.black.opacity(0.06) private let dashboardShadow = Color.black.opacity(0.05) private enum DashboardSpacing { static let compactOuterPadding: CGFloat = 16 static let regularOuterPadding: CGFloat = 28 static let compactTopPadding: CGFloat = 10 static let regularTopPadding: CGFloat = 18 static let compactBottomPadding: CGFloat = 120 static let regularBottomPadding: CGFloat = 56 static let compactStackSpacing: CGFloat = 20 static let regularStackSpacing: CGFloat = 28 static let compactContentWidth: CGFloat = 720 static let regularContentWidth: CGFloat = 980 static let compactSectionPadding: CGFloat = 18 static let regularSectionPadding: CGFloat = 24 static let compactRadius: CGFloat = 24 static let regularRadius: CGFloat = 28 } private extension View { func dashboardSurface(radius: CGFloat, fillOpacity: Double = 0.88) -> some View { background( Color.white.opacity(fillOpacity), in: RoundedRectangle(cornerRadius: radius, style: .continuous) ) .overlay( RoundedRectangle(cornerRadius: radius, style: .continuous) .stroke(dashboardBorder, lineWidth: 1) ) .shadow(color: dashboardShadow, radius: 14, y: 6) } } struct HomeRootView: View { @ObservedObject var model: AppViewModel @Environment(\.horizontalSizeClass) private var horizontalSizeClass var body: some View { ZStack { DashboardBackdrop() Group { if usesCompactNavigation { CompactHomeContainer(model: model) } else { RegularHomeContainer(model: model) } } } .sheet(isPresented: $model.isNotificationCenterPresented) { NotificationCenterSheet(model: model) } } private var usesCompactNavigation: Bool { #if os(iOS) horizontalSizeClass == .compact #else false #endif } } private struct CompactHomeContainer: View { @ObservedObject var model: AppViewModel var body: some View { TabView(selection: $model.selectedSection) { compactTab(for: .overview) compactTab(for: .requests) compactTab(for: .activity) compactTab(for: .account) } } @ViewBuilder private func compactTab(for section: AppSection) -> some View { NavigationStack { HomeSectionScreen(model: model, section: section, compactLayout: true) .navigationTitle(section.title) .toolbar { DashboardToolbar(model: model, compactLayout: true) } } .tag(section) .tabItem { Label(section.title, systemImage: section.systemImage) } } } private struct RegularHomeContainer: View { @ObservedObject var model: AppViewModel var body: some View { NavigationSplitView { Sidebar(model: model) } detail: { HomeSectionScreen(model: model, section: model.selectedSection, compactLayout: false) .navigationTitle(model.selectedSection.title) .toolbar { DashboardToolbar(model: model, compactLayout: false) } } .navigationSplitViewStyle(.balanced) } } private struct DashboardToolbar: ToolbarContent { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some ToolbarContent { if compactLayout { ToolbarItemGroup(placement: .primaryAction) { NotificationBellButton(model: model) Menu { Button { Task { await model.refreshDashboard() } } label: { Label("Refresh", systemImage: "arrow.clockwise") } Button { Task { await model.simulateIncomingRequest() } } label: { Label("Mock Request", systemImage: "sparkles.rectangle.stack.fill") } Button { Task { await model.sendTestNotification() } } label: { Label("Send Test Alert", systemImage: "paperplane.fill") } } label: { Image(systemName: "ellipsis.circle") } } } else { ToolbarItemGroup(placement: .primaryAction) { NotificationBellButton(model: model) Button { Task { await model.refreshDashboard() } } label: { Label("Refresh", systemImage: "arrow.clockwise") } .disabled(model.isRefreshing) Button { Task { await model.simulateIncomingRequest() } } label: { Label("Mock Request", systemImage: "sparkles.rectangle.stack.fill") } Button { Task { await model.sendTestNotification() } } label: { Label("Test Alert", systemImage: "paperplane.fill") } } } } } private struct HomeSectionScreen: View { @ObservedObject var model: AppViewModel let section: AppSection let compactLayout: Bool @State private var focusedRequest: ApprovalRequest? var body: some View { ScrollView { VStack(alignment: .leading, spacing: compactLayout ? DashboardSpacing.compactStackSpacing : DashboardSpacing.regularStackSpacing) { if let banner = model.bannerMessage { BannerCard(message: banner, compactLayout: compactLayout) } switch section { case .overview: OverviewPanel( model: model, compactLayout: compactLayout, onOpenRequest: { focusedRequest = $0 } ) case .requests: RequestsPanel( model: model, compactLayout: compactLayout, onOpenRequest: { focusedRequest = $0 } ) case .activity: ActivityPanel( model: model, compactLayout: compactLayout, onOpenRequest: { focusedRequest = $0 } ) case .account: AccountPanel(model: model, compactLayout: compactLayout) } } .padding(.horizontal, compactLayout ? DashboardSpacing.compactOuterPadding : DashboardSpacing.regularOuterPadding) .padding(.top, compactLayout ? DashboardSpacing.compactTopPadding : DashboardSpacing.regularTopPadding) .padding(.bottom, compactLayout ? DashboardSpacing.compactBottomPadding : DashboardSpacing.regularBottomPadding) .frame(maxWidth: compactLayout ? DashboardSpacing.compactContentWidth : DashboardSpacing.regularContentWidth, alignment: .leading) .frame(maxWidth: .infinity, alignment: compactLayout ? .leading : .center) } .scrollIndicators(.hidden) .sheet(item: $focusedRequest) { request in RequestDetailSheet(request: request, model: model) } } } private struct Sidebar: View { @ObservedObject var model: AppViewModel var body: some View { List { Section { SidebarStatusCard( profile: model.profile, pendingCount: model.pendingRequests.count, unreadCount: model.unreadNotificationCount ) } Section("Workspace") { ForEach(AppSection.allCases) { section in sidebarRow(for: section) } } } .navigationTitle("idp.global") } private func badgeCount(for section: AppSection) -> Int { switch section { case .overview: 0 case .requests: model.pendingRequests.count case .activity: 0 case .account: 0 } } @ViewBuilder private func sidebarRow(for section: AppSection) -> some View { Button { model.selectedSection = section } label: { HStack(spacing: 14) { Image(systemName: section.systemImage) .font(.headline) .frame(width: 30, height: 30) .background { if model.selectedSection == section { Circle() .fill(dashboardAccent.opacity(0.18)) } else { Circle() .fill(.thinMaterial) } } .foregroundStyle(model.selectedSection == section ? dashboardAccent : .primary) Text(section.title) .font(.headline) Spacer() if badgeCount(for: section) > 0 { Text("\(badgeCount(for: section))") .font(.caption.weight(.semibold)) .padding(.horizontal, 9) .padding(.vertical, 5) .background(.thinMaterial, in: Capsule()) } } .padding(.vertical, 4) .contentShape(Rectangle()) } .buttonStyle(.plain) .listRowBackground( model.selectedSection == section ? dashboardAccent.opacity(0.12) : Color.clear ) } } private struct SidebarStatusCard: View { let profile: MemberProfile? let pendingCount: Int let unreadCount: Int var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Digital Passport") .font(.title3.weight(.semibold)) Text(profile?.handle ?? "Not paired yet") .foregroundStyle(.secondary) HStack(spacing: 10) { SmallMetricPill(title: "Pending", value: "\(pendingCount)") SmallMetricPill(title: "Unread", value: "\(unreadCount)") } } .padding(.vertical, 6) } } private struct OverviewPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool let onOpenRequest: (ApprovalRequest) -> Void var body: some View { VStack(alignment: .leading, spacing: compactLayout ? 18 : 24) { if let profile = model.profile, let session = model.session { OverviewHero( profile: profile, session: session, pendingCount: model.pendingRequests.count, unreadCount: model.unreadNotificationCount, compactLayout: compactLayout ) } SectionCard( title: "Quick Actions", subtitle: "Refresh the bound session, seed a request, or test device alerts while the backend is still mocked.", compactLayout: compactLayout ) { QuickActionsDeck(model: model, compactLayout: compactLayout) } SectionCard( title: "Requests In Focus", subtitle: "Your passport is the identity surface. This queue is where anything asking for access should earn trust.", compactLayout: compactLayout ) { if model.pendingRequests.isEmpty { EmptyStateCopy( title: "Nothing waiting", systemImage: "checkmark.shield.fill", message: "Every pending approval has been handled." ) } else { VStack(spacing: 16) { if let featured = model.pendingRequests.first { FeaturedRequestCard( request: featured, compactLayout: compactLayout, onOpenRequest: { onOpenRequest(featured) } ) } ForEach(model.pendingRequests.dropFirst().prefix(2)) { request in RequestCard( request: request, compactLayout: compactLayout, isBusy: model.activeRequestID == request.id, onApprove: { Task { await model.approve(request) } }, onReject: { Task { await model.reject(request) } }, onOpenRequest: { onOpenRequest(request) } ) } } } } SectionCard( title: "Recent Activity", subtitle: "Keep the full timeline in its own view, and use the bell above for alerts that need device-level attention.", compactLayout: compactLayout ) { ActivityPreviewCard(model: model, compactLayout: compactLayout) } } } } private struct ActivityPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool let onOpenRequest: (ApprovalRequest) -> Void @State private var selectedNotificationID: AppNotification.ID? private var notificationIDs: [AppNotification.ID] { model.notifications.map(\.id) } private var selectedNotification: AppNotification? { if let selectedNotificationID, let match = model.notifications.first(where: { $0.id == selectedNotificationID }) { return match } return model.notifications.first } var body: some View { VStack(alignment: .leading, spacing: compactLayout ? 18 : 24) { if compactLayout { SectionCard( title: "Recent Activity", subtitle: "A dedicated home for approvals, pairing events, and system changes after they happen." ) { VStack(spacing: 16) { activityMetricRow if model.notifications.isEmpty { EmptyStateCopy( title: "No activity yet", systemImage: "clock.badge.xmark", message: "Once requests and pairing events arrive, the timeline will fill in here." ) } else { ForEach(model.notifications) { notification in NotificationCard( notification: notification, compactLayout: compactLayout, onMarkRead: { Task { await model.markNotificationRead(notification) } } ) } } } } } else { SectionCard( title: "Activity Timeline", subtitle: "Review what already happened across approvals, pairing, and system state without mixing it into the notification surface." ) { VStack(alignment: .leading, spacing: 18) { activityMetricRow if model.notifications.isEmpty { EmptyStateCopy( title: "No activity yet", systemImage: "clock.badge.xmark", message: "Once requests and pairing events arrive, the timeline will fill in here." ) } else { HStack(alignment: .top, spacing: 18) { VStack(alignment: .leading, spacing: 14) { Text("Timeline") .font(.headline) Text("The latest product and security events stay readable here, while the bell above stays focused on device notifications.") .foregroundStyle(.secondary) VStack(spacing: 12) { ForEach(model.notifications) { notification in NotificationFeedRow( notification: notification, isSelected: notification.id == selectedNotification?.id ) { selectedNotificationID = notification.id } } } } .frame(width: 390, alignment: .leading) .padding(18) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 28, style: .continuous)) if let notification = selectedNotification { NotificationWorkbenchDetail( notification: notification, permissionState: model.notificationPermission, onMarkRead: { Task { await model.markNotificationRead(notification) } } ) } } } } } } if !model.handledRequests.isEmpty { SectionCard( title: "Handled Requests", subtitle: "A compact audit trail for the approvals and rejections that already moved through the queue." ) { LazyVStack(spacing: 14) { ForEach(model.handledRequests.prefix(compactLayout ? 4 : 6)) { request in RequestCard( request: request, compactLayout: compactLayout, isBusy: false, onApprove: nil, onReject: nil, onOpenRequest: { onOpenRequest(request) } ) } } } } } .onChange(of: notificationIDs, initial: true) { _, _ in syncSelectedNotification() } } @ViewBuilder private var activityMetricRow: some View { if compactLayout { VStack(spacing: 10) { SmallMetricPill(title: "Events", value: "\(model.notifications.count)") SmallMetricPill(title: "Unread", value: "\(model.unreadNotificationCount)") SmallMetricPill(title: "Handled", value: "\(model.handledRequests.count)") } } else { HStack(spacing: 14) { NotificationMetricCard( title: "Events", value: "\(model.notifications.count)", subtitle: model.notifications.isEmpty ? "Quiet so far" : "Timeline active", accent: dashboardAccent ) NotificationMetricCard( title: "Unread", value: "\(model.unreadNotificationCount)", subtitle: model.unreadNotificationCount == 0 ? "Everything acknowledged" : "Still highlighted", accent: .orange ) NotificationMetricCard( title: "Handled", value: "\(model.handledRequests.count)", subtitle: model.handledRequests.isEmpty ? "No completed approvals yet" : "Recent decisions ready to review", accent: dashboardGold ) } } } private func syncSelectedNotification() { if let selectedNotificationID, notificationIDs.contains(selectedNotificationID) { return } selectedNotificationID = model.notifications.first?.id } } private struct RequestsPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool let onOpenRequest: (ApprovalRequest) -> Void @State private var selectedRequestID: ApprovalRequest.ID? private var requestIDs: [ApprovalRequest.ID] { model.requests.map(\.id) } private var selectedRequest: ApprovalRequest? { if let selectedRequestID, let match = model.requests.first(where: { $0.id == selectedRequestID }) { return match } return model.pendingRequests.first ?? model.handledRequests.first } var body: some View { VStack(alignment: .leading, spacing: compactLayout ? 18 : 24) { if compactLayout { SectionCard( title: "Approval Desk", subtitle: "Treat every request like a border checkpoint: verify the origin, timing, and scope before letting it through.", compactLayout: compactLayout ) { VStack(spacing: 16) { RequestQueueSummary( pendingCount: model.pendingRequests.count, elevatedCount: model.elevatedPendingCount, compactLayout: compactLayout ) if model.pendingRequests.isEmpty { EmptyStateCopy( title: "Queue is clear", systemImage: "checkmark.circle", message: "Use the toolbar to simulate another request if you want to keep testing." ) } else { ForEach(model.pendingRequests) { request in RequestCard( request: request, compactLayout: compactLayout, isBusy: model.activeRequestID == request.id, onApprove: { Task { await model.approve(request) } }, onReject: { Task { await model.reject(request) } }, onOpenRequest: { onOpenRequest(request) } ) } } } } SectionCard( title: "Decision Guide", subtitle: "What to check before approving high-sensitivity actions from your phone.", compactLayout: compactLayout ) { VStack(alignment: .leading, spacing: 14) { GuidanceRow( icon: "network.badge.shield.half.filled", title: "Confirm the origin", message: "The service hostname should match the product or automation you intentionally triggered." ) GuidanceRow( icon: "timer", title: "Look for short lifetimes", message: "Privileged grants should usually be limited in time instead of creating long-lived access." ) GuidanceRow( icon: "lock.shield", title: "Escalate mentally for elevated scopes", message: "Signing, publishing, and write scopes deserve a slower second look before approval." ) } } if !model.handledRequests.isEmpty { SectionCard( title: "Recently Handled", subtitle: "A compact audit trail of the latest approvals and rejections.", compactLayout: compactLayout ) { LazyVStack(spacing: 14) { ForEach(model.handledRequests.prefix(4)) { request in RequestCard( request: request, compactLayout: compactLayout, isBusy: false, onApprove: nil, onReject: nil, onOpenRequest: { onOpenRequest(request) } ) } } } } } else { SectionCard( title: "Approval Workbench", subtitle: "Use the queue on the left and a richer inline review on the right so each decision feels deliberate instead of mechanical." ) { VStack(alignment: .leading, spacing: 18) { RequestQueueSummary( pendingCount: model.pendingRequests.count, elevatedCount: model.elevatedPendingCount, compactLayout: compactLayout ) if model.requests.isEmpty { EmptyStateCopy( title: "Queue is clear", systemImage: "checkmark.circle", message: "Use the toolbar to simulate another request if you want to keep testing." ) } else { HStack(alignment: .top, spacing: 18) { VStack(alignment: .leading, spacing: 14) { Text("Queue") .font(.headline) Text("Pending and recently handled items stay visible here so you can sanity-check decisions without leaving the flow.") .foregroundStyle(.secondary) VStack(spacing: 12) { ForEach(model.requests) { request in RequestQueueRow( request: request, isSelected: request.id == selectedRequest?.id ) { selectedRequestID = request.id } } } } .frame(width: 390, alignment: .leading) .padding(18) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 28, style: .continuous)) if let request = selectedRequest { RequestWorkbenchDetail( request: request, isBusy: model.activeRequestID == request.id, onApprove: request.status == .pending ? { Task { await model.approve(request) } } : nil, onReject: request.status == .pending ? { Task { await model.reject(request) } } : nil, onOpenRequest: { onOpenRequest(request) } ) } } } } } SectionCard( title: "Operator Checklist", subtitle: "A calm review pattern for larger screens, especially when elevated scopes show up." ) { LazyVGrid( columns: [ GridItem(.flexible(), spacing: 14), GridItem(.flexible(), spacing: 14) ], alignment: .leading, spacing: 14 ) { GuidanceCard( icon: "network.badge.shield.half.filled", title: "Confirm the origin", message: "The hostname should map to the workflow or portal you intentionally triggered." ) GuidanceCard( icon: "timer", title: "Look for short lifetimes", message: "Elevated grants are safer when they expire quickly instead of becoming ambient access." ) GuidanceCard( icon: "lock.shield", title: "Escalate for signing and publish scopes", message: "If the action can sign, publish, or write, slow down and verify the target system twice." ) GuidanceCard( icon: "person.badge.shield.checkmark", title: "Match the device", message: "The request story should line up with the paired browser, CLI, or automation session you expect." ) } } } } .onChange(of: requestIDs, initial: true) { _, _ in syncSelectedRequest() } } private func syncSelectedRequest() { if let selectedRequestID, requestIDs.contains(selectedRequestID) { return } selectedRequestID = model.pendingRequests.first?.id ?? model.handledRequests.first?.id } } private struct NotificationsPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool @State private var selectedNotificationID: AppNotification.ID? private var notificationIDs: [AppNotification.ID] { model.notifications.map(\.id) } private var selectedNotification: AppNotification? { if let selectedNotificationID, let match = model.notifications.first(where: { $0.id == selectedNotificationID }) { return match } return model.notifications.first } var body: some View { VStack(alignment: .leading, spacing: compactLayout ? 18 : 24) { if compactLayout { SectionCard( title: "Notification Delivery", subtitle: "Control lock-screen delivery now, then evolve this into remote push once the backend is live.", compactLayout: compactLayout ) { NotificationPermissionCard(model: model, compactLayout: compactLayout) } SectionCard( title: "Alert Inbox", subtitle: "Unread alerts stay emphasized here until you explicitly clear them.", compactLayout: compactLayout ) { if model.notifications.isEmpty { EmptyStateCopy( title: "No alerts yet", systemImage: "bell.slash", message: "New pairing and approval alerts will accumulate here." ) } else { LazyVStack(spacing: 14) { ForEach(model.notifications) { notification in NotificationCard( notification: notification, compactLayout: compactLayout, onMarkRead: { Task { await model.markNotificationRead(notification) } } ) } } } } } else { SectionCard( title: "Delivery Posture", subtitle: "Keep delivery health, unread pressure, and the latest alert in one glance from the notification center." ) { VStack(alignment: .leading, spacing: 18) { HStack(spacing: 14) { NotificationMetricCard( title: "Unread", value: "\(model.unreadNotificationCount)", subtitle: model.unreadNotificationCount == 0 ? "Inbox clear" : "Needs triage", accent: .orange ) NotificationMetricCard( title: "Permission", value: model.notificationPermission.title, subtitle: model.notificationPermission == .allowed ? "Lock screen ready" : "Review device status", accent: dashboardAccent ) NotificationMetricCard( title: "Latest", value: model.latestNotification?.kind.title ?? "Quiet", subtitle: model.latestNotification?.sentAt.formatted(date: .omitted, time: .shortened) ?? "No recent events", accent: dashboardGold ) } NotificationPermissionCard(model: model, compactLayout: compactLayout) } } SectionCard( title: "Alert Inbox", subtitle: "Select an alert to inspect the message body, delivery state, and the right follow-up action." ) { if model.notifications.isEmpty { EmptyStateCopy( title: "No alerts yet", systemImage: "bell.slash", message: "New pairing and approval alerts will accumulate here." ) } else { HStack(alignment: .top, spacing: 18) { VStack(alignment: .leading, spacing: 14) { Text("Feed") .font(.headline) Text("Unread items stay visually lifted until you clear them, which makes it easier to scan the important changes first.") .foregroundStyle(.secondary) VStack(spacing: 12) { ForEach(model.notifications) { notification in NotificationFeedRow( notification: notification, isSelected: notification.id == selectedNotification?.id ) { selectedNotificationID = notification.id } } } } .frame(maxWidth: 340, alignment: .leading) .padding(18) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 28, style: .continuous)) if let notification = selectedNotification { NotificationWorkbenchDetail( notification: notification, permissionState: model.notificationPermission, onMarkRead: { Task { await model.markNotificationRead(notification) } } ) } } } } } } .onChange(of: notificationIDs, initial: true) { _, _ in syncSelectedNotification() } } private func syncSelectedNotification() { if let selectedNotificationID, notificationIDs.contains(selectedNotificationID) { return } selectedNotificationID = model.notifications.first?.id } } private struct AccountPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { VStack(alignment: .leading, spacing: compactLayout ? 18 : 24) { if let profile = model.profile, let session = model.session { AccountHero(profile: profile, session: session, compactLayout: compactLayout) SectionCard( title: "Session Security", subtitle: "The core trust facts for the currently paired session.", compactLayout: compactLayout ) { AccountFactGrid(profile: profile, session: session, compactLayout: compactLayout) } } SectionCard( title: "Mock Pairing Payload", subtitle: "Useful for testing QR flow while the real portal integration is still pending.", compactLayout: compactLayout ) { Text(model.suggestedQRCodePayload) .font(.body.monospaced()) .textSelection(.enabled) .padding(16) .frame(maxWidth: .infinity, alignment: .leading) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 24, style: .continuous)) } SectionCard( title: "Session Controls", subtitle: "Use this once you want to reset back to the login and pairing flow.", compactLayout: compactLayout ) { Button(role: .destructive) { model.signOut() } label: { Label("Sign Out", systemImage: "rectangle.portrait.and.arrow.right") } .buttonStyle(.bordered) } } } } private struct OverviewHero: View { let profile: MemberProfile let session: AuthSession let pendingCount: Int let unreadCount: Int let compactLayout: Bool var body: some View { ZStack(alignment: .topLeading) { RoundedRectangle(cornerRadius: 34, style: .continuous) .fill( LinearGradient( colors: [ Color(red: 0.07, green: 0.18, blue: 0.15), Color(red: 0.11, green: 0.28, blue: 0.24), Color(red: 0.29, green: 0.24, blue: 0.12) ], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .overlay( RoundedRectangle(cornerRadius: 34, style: .continuous) .strokeBorder(dashboardGold.opacity(0.55), lineWidth: 1.2) ) Circle() .fill(.white.opacity(0.08)) .frame(width: compactLayout ? 180 : 260, height: compactLayout ? 180 : 260) .offset(x: compactLayout ? 210 : 420, y: compactLayout ? -30 : -50) Image(systemName: "globe.europe.africa.fill") .font(.system(size: compactLayout ? 92 : 122)) .foregroundStyle(.white.opacity(0.07)) .offset(x: compactLayout ? 220 : 455, y: compactLayout ? 4 : 8) VStack(alignment: .leading, spacing: compactLayout ? 16 : 20) { passportHeader passportBody if !compactLayout { PassportMachineStrip(code: machineReadableCode) } passportMetrics } .padding(compactLayout ? 22 : 28) } .frame(minHeight: compactLayout ? 380 : 390) } private var passportHeader: some View { HStack(alignment: .top, spacing: 16) { VStack(alignment: .leading, spacing: 8) { Text("IDP.GLOBAL DIGITAL PASSPORT") .font(.caption.weight(.bold)) .tracking(1.8) .foregroundStyle(.white.opacity(0.78)) Text(profile.name) .font(.system(size: compactLayout ? 30 : 36, weight: .bold, design: .rounded)) .foregroundStyle(.white) Text("Bound to \(session.deviceName) for requests coming from \(session.originHost).") .font(compactLayout ? .subheadline : .title3) .foregroundStyle(.white.opacity(0.88)) } Spacer(minLength: 0) PassportDocumentBadge( number: documentNumber, issuedAt: session.pairedAt, compactLayout: compactLayout ) } } @ViewBuilder private var passportBody: some View { if compactLayout { VStack(alignment: .leading, spacing: 14) { HStack(alignment: .top, spacing: 14) { passportPortrait VStack(alignment: .leading, spacing: 10) { PassportField(label: "Holder", value: profile.name, emphasized: true) PassportField(label: "Handle", value: profile.handle, monospaced: true) PassportField(label: "Origin", value: session.originHost, monospaced: true) } } LazyVGrid( columns: [ GridItem(.flexible(), spacing: 10), GridItem(.flexible(), spacing: 10) ], spacing: 10 ) { PassportInlineFact(label: "Device", value: session.deviceName) PassportInlineFact(label: "Issued", value: session.pairedAt.formatted(date: .abbreviated, time: .shortened)) PassportInlineFact(label: "Organization", value: profile.organization) PassportInlineFact(label: "Token", value: "...\(session.tokenPreview)", monospaced: true) } } .padding(18) .background(.white.opacity(0.11), in: RoundedRectangle(cornerRadius: 28, style: .continuous)) } else { VStack(alignment: .leading, spacing: 16) { HStack(alignment: .top, spacing: 18) { passportPortrait HStack(alignment: .top, spacing: 14) { passportPrimaryFields passportSecondaryFields } } LazyVGrid( columns: [ GridItem(.flexible(), spacing: 12), GridItem(.flexible(), spacing: 12), GridItem(.flexible(), spacing: 12) ], spacing: 12 ) { PassportInlineFact(label: "Document No.", value: documentNumber, monospaced: true) PassportInlineFact(label: "Issued", value: session.pairedAt.formatted(date: .abbreviated, time: .shortened)) PassportInlineFact(label: "Membership", value: "\(profile.deviceCount) trusted devices") } } .padding(20) .background(.white.opacity(0.11), in: RoundedRectangle(cornerRadius: 28, style: .continuous)) } } private var passportMetrics: some View { Group { if compactLayout { VStack(spacing: 10) { passportMetricCards } } else { HStack(spacing: 12) { passportMetricCards } } } } @ViewBuilder private var passportMetricCards: some View { PassportMetricBadge( title: "Pending", value: "\(pendingCount)", subtitle: pendingCount == 0 ? "No approvals waiting" : "Requests still at the border" ) PassportMetricBadge( title: "Alerts", value: "\(unreadCount)", subtitle: unreadCount == 0 ? "Notification bell is clear" : "Unread device alerts" ) PassportMetricBadge( title: "Devices", value: "\(profile.deviceCount)", subtitle: "\(profile.organization) membership" ) } private var passportPortrait: some View { VStack(alignment: .leading, spacing: 12) { RoundedRectangle(cornerRadius: 26, style: .continuous) .fill(.white.opacity(0.12)) .frame(width: compactLayout ? 102 : 132, height: compactLayout ? 132 : 166) .overlay { VStack(spacing: 10) { Circle() .fill(.white.opacity(0.18)) .frame(width: compactLayout ? 52 : 64, height: compactLayout ? 52 : 64) .overlay { Text(holderInitials) .font(.system(size: compactLayout ? 24 : 28, weight: .bold, design: .rounded)) .foregroundStyle(.white) } Text("TRUSTED HOLDER") .font(.caption2.weight(.bold)) .tracking(1.2) .foregroundStyle(.white.opacity(0.72)) Text(compactLayout ? documentNumber : profile.handle) .font(.footnote.monospaced()) .foregroundStyle(.white.opacity(0.9)) .lineLimit(2) .minimumScaleFactor(0.7) } .padding(12) } Text("Issued \(session.pairedAt.formatted(date: .abbreviated, time: .shortened))") .font(.caption) .foregroundStyle(.white.opacity(0.74)) } } private var passportPrimaryFields: some View { VStack(alignment: .leading, spacing: 12) { PassportField(label: "Holder", value: profile.name, emphasized: true) PassportField(label: "Handle", value: profile.handle, monospaced: true) PassportField(label: "Organization", value: profile.organization) } } private var passportSecondaryFields: some View { VStack(alignment: .leading, spacing: 12) { PassportField(label: "Bound Device", value: session.deviceName) PassportField(label: "Origin", value: session.originHost, monospaced: true) PassportField(label: "Token Preview", value: "...\(session.tokenPreview)", monospaced: true) } } private var holderInitials: String { let parts = profile.name .split(separator: " ") .prefix(2) .compactMap { $0.first } let initials = String(parts) return initials.isEmpty ? "ID" : initials.uppercased() } private var documentNumber: String { "IDP-\(session.id.uuidString.prefix(8).uppercased())" } private var machineReadableCode: String { let normalizedName = sanitize(profile.name) let normalizedHandle = sanitize(profile.handle) let normalizedOrigin = sanitize(session.originHost) return "P<\(documentNumber)<\(normalizedName)<<\(normalizedHandle)<<\(normalizedOrigin)" } private func sanitize(_ value: String) -> String { value .uppercased() .map { character in character.isLetter || character.isNumber ? String(character) : "<" } .joined() } } private struct PassportDocumentBadge: View { let number: String let issuedAt: Date let compactLayout: Bool var body: some View { VStack(alignment: .trailing, spacing: 8) { StatusBadge(title: "Bound", tone: .white) VStack(alignment: .trailing, spacing: 4) { Text("Document No.") .font(.caption2.weight(.bold)) .tracking(1.0) .foregroundStyle(.white.opacity(0.72)) Text(number) .font((compactLayout ? Font.footnote : Font.body).monospaced().weight(.semibold)) .foregroundStyle(.white) } if !compactLayout { Text("Issued \(issuedAt.formatted(date: .abbreviated, time: .shortened))") .font(.caption) .foregroundStyle(.white.opacity(0.76)) } } .padding(.horizontal, compactLayout ? 12 : 14) .padding(.vertical, 10) .background(.white.opacity(0.10), in: RoundedRectangle(cornerRadius: 22, style: .continuous)) } } private struct PassportInlineFact: View { let label: String let value: String var monospaced: Bool = false var body: some View { VStack(alignment: .leading, spacing: 5) { Text(label.uppercased()) .font(.caption2.weight(.bold)) .tracking(1.0) .foregroundStyle(.white.opacity(0.72)) Text(value) .font(monospaced ? .subheadline.monospaced() : .subheadline.weight(.semibold)) .foregroundStyle(.white) .lineLimit(2) .minimumScaleFactor(0.7) } .padding(12) .frame(maxWidth: .infinity, alignment: .leading) .background(.white.opacity(0.09), in: RoundedRectangle(cornerRadius: 18, style: .continuous)) } } private struct PassportField: View { let label: String let value: String var monospaced: Bool = false var emphasized: Bool = false var body: some View { VStack(alignment: .leading, spacing: 4) { Text(label.uppercased()) .font(.caption2.weight(.bold)) .tracking(1.0) .foregroundStyle(.white.opacity(0.72)) Text(value) .font(valueFont) .foregroundStyle(.white) .lineLimit(2) .minimumScaleFactor(0.8) } .frame(maxWidth: .infinity, alignment: .leading) } private var valueFont: Font { if monospaced { return .body.monospaced() } return emphasized ? .headline : .body } } private struct PassportMetricBadge: View { let title: String let value: String let subtitle: String var body: some View { VStack(alignment: .leading, spacing: 8) { Text(title.uppercased()) .font(.caption.weight(.bold)) .tracking(1.0) .foregroundStyle(.white.opacity(0.72)) Text(value) .font(.title2.weight(.bold)) .foregroundStyle(.white) Text(subtitle) .font(.footnote) .foregroundStyle(.white.opacity(0.82)) } .padding(16) .frame(maxWidth: .infinity, alignment: .leading) .background(.white.opacity(0.10), in: RoundedRectangle(cornerRadius: 22, style: .continuous)) } } private struct PassportMachineStrip: View { let code: String var body: some View { Text(code) .font(.caption.monospaced().weight(.semibold)) .lineLimit(1) .minimumScaleFactor(0.5) .padding(.horizontal, 14) .padding(.vertical, 12) .frame(maxWidth: .infinity, alignment: .leading) .background(Color.black.opacity(0.22), in: RoundedRectangle(cornerRadius: 18, style: .continuous)) .foregroundStyle(.white.opacity(0.94)) } } private struct QuickActionsDeck: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { Group { if compactLayout { VStack(spacing: 12) { actionButtons } } else { HStack(alignment: .top, spacing: 14) { actionButtons } } } } @ViewBuilder private var actionButtons: some View { ActionTile( title: "Refresh State", subtitle: "Pull the latest requests and notifications from the mock service.", systemImage: "arrow.clockwise" ) { Task { await model.refreshDashboard() } } ActionTile( title: "Seed Request", subtitle: "Inject a new elevated approval flow to test the queue.", systemImage: "sparkles.rectangle.stack.fill" ) { Task { await model.simulateIncomingRequest() } } ActionTile( title: "Test Alert", subtitle: "Schedule a local notification so the phone behavior is easy to verify.", systemImage: "bell.badge.fill" ) { Task { await model.sendTestNotification() } } } } private struct ActionTile: View { let title: String let subtitle: String let systemImage: String let action: () -> Void var body: some View { Button(action: action) { VStack(alignment: .leading, spacing: 12) { Image(systemName: systemImage) .font(.title3.weight(.semibold)) .foregroundStyle(dashboardAccent) .frame(width: 42, height: 42) .background(dashboardAccent.opacity(0.10), in: RoundedRectangle(cornerRadius: 14, style: .continuous)) Text(title) .font(.headline) .foregroundStyle(.primary) Text(subtitle) .font(.subheadline) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) .padding(18) .background(Color.white.opacity(0.76), in: RoundedRectangle(cornerRadius: 24, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 24, style: .continuous) .stroke(dashboardAccent.opacity(0.08), lineWidth: 1) ) } .buttonStyle(.plain) } } private struct FeaturedRequestCard: View { let request: ApprovalRequest let compactLayout: Bool let onOpenRequest: () -> Void var body: some View { VStack(alignment: .leading, spacing: 16) { HStack(alignment: .center, spacing: 12) { Image(systemName: request.risk == .elevated ? "shield.lefthalf.filled.badge.checkmark" : request.kind.systemImage) .font(.title2) .foregroundStyle(request.risk == .elevated ? .orange : dashboardAccent) VStack(alignment: .leading, spacing: 4) { Text(request.trustHeadline) .font(.headline) Text(request.title) .font(.title3.weight(.semibold)) } Spacer() StatusBadge( title: request.risk.title, tone: request.risk == .routine ? .mint : .orange ) } Text(request.trustDetail) .foregroundStyle(.secondary) HStack(spacing: 8) { StatusBadge(title: request.kind.title, tone: .blue) StatusBadge(title: request.source, tone: .gray) StatusBadge(title: request.scopeSummary, tone: .green) } if compactLayout { VStack(alignment: .leading, spacing: 12) { Button("Review Full Context", action: onOpenRequest) .buttonStyle(.borderedProminent) Text(request.risk.guidance) .font(.footnote) .foregroundStyle(.secondary) } } else { HStack { Button("Review Full Context", action: onOpenRequest) .buttonStyle(.borderedProminent) Spacer() Text(request.risk.guidance) .font(.footnote) .foregroundStyle(.secondary) .multilineTextAlignment(.trailing) } } } .padding(compactLayout ? 18 : 22) .background( LinearGradient( colors: [ request.risk == .routine ? dashboardAccent.opacity(0.12) : Color.orange.opacity(0.16), Color.white.opacity(0.7) ], startPoint: .topLeading, endPoint: .bottomTrailing ), in: RoundedRectangle(cornerRadius: 28, style: .continuous) ) } } private struct RequestQueueSummary: View { let pendingCount: Int let elevatedCount: Int let compactLayout: Bool var body: some View { if compactLayout { VStack(spacing: 12) { HStack(spacing: 12) { pendingCard elevatedCard } postureCard } } else { HStack(spacing: 12) { pendingCard elevatedCard postureCard } } } private var pendingCard: some View { RequestSummaryMetricCard( title: "Pending", value: "\(pendingCount)", subtitle: pendingCount == 0 ? "Queue is clear" : "Still waiting on your call", accent: dashboardAccent ) } private var elevatedCard: some View { RequestSummaryMetricCard( title: "Elevated", value: "\(elevatedCount)", subtitle: elevatedCount == 0 ? "No privileged scopes" : "Needs slower review", accent: .orange ) } private var postureCard: some View { RequestSummaryMetricCard( title: "Posture", value: trustMode, subtitle: postureSummary, accent: dashboardGold ) } private var trustMode: String { if pendingCount == 0 { return "Clear" } if elevatedCount == 0 { return "Active" } return elevatedCount > 1 ? "Escalate" : "Guarded" } private var postureSummary: String { if pendingCount == 0 { return "Nothing at the border" } if elevatedCount == 0 { return "Routine traffic only" } return "Privileged access in queue" } } private struct RequestSummaryMetricCard: View { let title: String let value: String let subtitle: String let accent: Color var body: some View { VStack(alignment: .leading, spacing: 8) { Text(title.uppercased()) .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) Text(value) .font(.title3.weight(.semibold)) .foregroundStyle(.primary) Text(subtitle) .font(.footnote) .foregroundStyle(.secondary) } .padding(16) .frame(maxWidth: .infinity, alignment: .leading) .background(accent.opacity(0.10), in: RoundedRectangle(cornerRadius: 20, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 20, style: .continuous) .stroke(accent.opacity(0.08), lineWidth: 1) ) } } private struct NotificationPermissionCard: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { VStack(alignment: .leading, spacing: 18) { HStack(alignment: .top, spacing: 14) { Image(systemName: model.notificationPermission.systemImage) .font(.title2) .frame(width: 38, height: 38) .background(.thinMaterial, in: Circle()) .foregroundStyle(dashboardAccent) VStack(alignment: .leading, spacing: 5) { Text(model.notificationPermission.title) .font(.headline) Text(model.notificationPermission.summary) .foregroundStyle(.secondary) } } Group { if compactLayout { VStack(spacing: 12) { permissionButtons } } else { HStack(spacing: 12) { permissionButtons } } } } .padding(18) .dashboardSurface(radius: 24) } @ViewBuilder private var permissionButtons: some View { Button { Task { await model.requestNotificationAccess() } } label: { Label("Enable Notifications", systemImage: "bell.and.waves.left.and.right.fill") } .buttonStyle(.borderedProminent) Button { Task { await model.sendTestNotification() } } label: { Label("Send Test Alert", systemImage: "paperplane.fill") } .buttonStyle(.bordered) } } private struct ActivityPreviewCard: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { VStack(alignment: .leading, spacing: 16) { if let latest = model.latestNotification { NotificationCard( notification: latest, compactLayout: compactLayout, onMarkRead: { Task { await model.markNotificationRead(latest) } } ) } else { EmptyStateCopy( title: "No activity yet", systemImage: "clock.badge.xmark", message: "Once requests and pairing events arrive, the activity timeline will fill in here." ) } if compactLayout { VStack(alignment: .leading, spacing: 12) { Button { model.selectedSection = .activity } label: { Label("Open Activity", systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90") } .buttonStyle(.borderedProminent) Button { model.isNotificationCenterPresented = true } label: { Label("Open Notification Bell", systemImage: "bell") } .buttonStyle(.bordered) } } else { HStack(spacing: 12) { Button { model.selectedSection = .activity } label: { Label("Open Activity", systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90") } .buttonStyle(.borderedProminent) Button { model.isNotificationCenterPresented = true } label: { Label("Open Notifications", systemImage: "bell") } .buttonStyle(.bordered) Spacer() Text("Unread device alerts now live in the bell above instead of taking a full navigation slot.") .font(.footnote) .foregroundStyle(.secondary) .multilineTextAlignment(.trailing) } } } } } private struct NotificationBellButton: View { @ObservedObject var model: AppViewModel var body: some View { Button { model.isNotificationCenterPresented = true } label: { ZStack(alignment: .topTrailing) { Image(systemName: model.unreadNotificationCount == 0 ? "bell" : "bell.badge.fill") .font(.headline) .foregroundStyle(model.unreadNotificationCount == 0 ? .primary : dashboardAccent) if model.unreadNotificationCount > 0 { Text("\(min(model.unreadNotificationCount, 9))") .font(.caption2.weight(.bold)) .padding(.horizontal, 5) .padding(.vertical, 2) .background(Color.orange, in: Capsule()) .foregroundStyle(.white) .offset(x: 10, y: -10) } } .frame(width: 28, height: 28) } .accessibilityLabel("Notifications") } } private struct NotificationCenterSheet: View { @ObservedObject var model: AppViewModel @Environment(\.dismiss) private var dismiss @Environment(\.horizontalSizeClass) private var horizontalSizeClass var body: some View { NavigationStack { ScrollView { NotificationsPanel(model: model, compactLayout: compactLayout) .padding(.horizontal, compactLayout ? DashboardSpacing.compactOuterPadding : DashboardSpacing.regularOuterPadding) .padding(.top, compactLayout ? DashboardSpacing.compactTopPadding : DashboardSpacing.regularTopPadding) .padding(.bottom, compactLayout ? DashboardSpacing.compactBottomPadding : DashboardSpacing.regularBottomPadding) .frame(maxWidth: compactLayout ? DashboardSpacing.compactContentWidth : DashboardSpacing.regularContentWidth, alignment: .leading) .frame(maxWidth: .infinity, alignment: compactLayout ? .leading : .center) } .scrollIndicators(.hidden) .navigationTitle("Notifications") .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Done") { dismiss() } } } } #if os(iOS) .presentationDetents(compactLayout ? [.large] : [.medium, .large]) #endif } private var compactLayout: Bool { #if os(iOS) horizontalSizeClass == .compact #else false #endif } } private struct AccountHero: View { let profile: MemberProfile let session: AuthSession let compactLayout: Bool var body: some View { ZStack(alignment: .bottomLeading) { RoundedRectangle(cornerRadius: 32, style: .continuous) .fill( LinearGradient( colors: [ dashboardAccent.opacity(0.95), Color(red: 0.19, green: 0.49, blue: 0.40), dashboardGold.opacity(0.92) ], startPoint: .topLeading, endPoint: .bottomTrailing ) ) VStack(alignment: .leading, spacing: 14) { Text(profile.name) .font(.system(size: compactLayout ? 28 : 34, weight: .bold, design: .rounded)) .foregroundStyle(.white) Text(profile.handle) .font(.headline) .foregroundStyle(.white.opacity(0.84)) Text("Current trusted device: \(session.deviceName)") .foregroundStyle(.white.opacity(0.86)) } .padding(compactLayout ? 22 : 28) } .frame(minHeight: compactLayout ? 190 : 220) } } private struct AccountFactGrid: View { let profile: MemberProfile let session: AuthSession let compactLayout: Bool private var columns: [GridItem] { Array(repeating: GridItem(.flexible(), spacing: 12), count: compactLayout ? 1 : 2) } var body: some View { LazyVGrid(columns: columns, spacing: 12) { FactCard(label: "Organization", value: profile.organization) FactCard(label: "Origin", value: session.originHost) FactCard(label: "Paired At", value: session.pairedAt.formatted(date: .abbreviated, time: .shortened)) FactCard(label: "Token Preview", value: "…\(session.tokenPreview)") FactCard(label: "Trusted Devices", value: "\(profile.deviceCount)") FactCard(label: "Recovery", value: profile.recoverySummary) } } } private struct RequestCard: View { let request: ApprovalRequest let compactLayout: Bool let isBusy: Bool let onApprove: (() -> Void)? let onReject: (() -> Void)? let onOpenRequest: (() -> Void)? private var infoColumns: [GridItem] { Array(repeating: GridItem(.flexible(), spacing: 10), count: compactLayout ? 2 : 3) } var body: some View { VStack(alignment: .leading, spacing: 16) { HStack(alignment: .top, spacing: 14) { ZStack { Circle() .fill(requestAccent.opacity(0.14)) Image(systemName: request.kind.systemImage) .font(.title2) .foregroundStyle(requestAccent) } .frame(width: 46, height: 46) VStack(alignment: .leading, spacing: 8) { HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text(request.trustHeadline) .font(.subheadline.weight(.semibold)) .foregroundStyle(requestAccent) Text(request.title) .font(.headline) .foregroundStyle(.primary) } Spacer() StatusBadge( title: request.status.title, tone: statusTone ) } Text(request.subtitle) .foregroundStyle(.secondary) HStack(spacing: 8) { StatusBadge(title: request.kind.title, tone: .blue) StatusBadge(title: request.risk.title, tone: request.risk == .routine ? .mint : .orange) Text(request.createdAt, style: .relative) .font(.footnote) .foregroundStyle(.secondary) } } } LazyVGrid(columns: infoColumns, alignment: .leading, spacing: 10) { RequestFactPill(label: "Source", value: request.source, accent: dashboardAccent) RequestFactPill( label: "Requested", value: request.createdAt.formatted(date: .abbreviated, time: .shortened), accent: dashboardGold ) RequestFactPill(label: "Access", value: request.scopeSummary, accent: requestAccent) } VStack(alignment: .leading, spacing: 10) { Label(request.status == .pending ? "Decision posture" : "Decision record", systemImage: request.status.systemImage) .font(.headline) .foregroundStyle(.primary) Text(request.trustDetail) .foregroundStyle(.secondary) Text(reviewSummary) .font(.subheadline.weight(.semibold)) .foregroundStyle(requestAccent) } .padding(14) .frame(maxWidth: .infinity, alignment: .leading) .background(requestAccent.opacity(0.10), in: RoundedRectangle(cornerRadius: 22, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 22, style: .continuous) .stroke(requestAccent.opacity(0.08), lineWidth: 1) ) if !request.scopes.isEmpty { VStack(alignment: .leading, spacing: 10) { Text("Requested scopes") .font(.subheadline.weight(.semibold)) FlowScopes(scopes: request.scopes) } } VStack(spacing: 12) { if let onOpenRequest { Button { onOpenRequest() } label: { Label("Review Details", systemImage: "arrow.up.forward.app") } .buttonStyle(.bordered) .frame(maxWidth: .infinity, alignment: .leading) } if let onApprove, let onReject, request.status == .pending { if compactLayout { VStack(spacing: 10) { Button { onApprove() } label: { if isBusy { ProgressView() } else { Label("Approve Request", systemImage: "checkmark.circle.fill") } } .buttonStyle(.borderedProminent) .disabled(isBusy) Button(role: .destructive) { onReject() } label: { Label("Reject Request", systemImage: "xmark.circle.fill") } .buttonStyle(.bordered) .disabled(isBusy) } } else { HStack(spacing: 12) { Button { onApprove() } label: { if isBusy { ProgressView() } else { Label("Approve", systemImage: "checkmark.circle.fill") } } .buttonStyle(.borderedProminent) .disabled(isBusy) Button(role: .destructive) { onReject() } label: { Label("Reject", systemImage: "xmark.circle.fill") } .buttonStyle(.bordered) .disabled(isBusy) } } } } } .padding(compactLayout ? 18 : 20) .background( LinearGradient( colors: [ Color.white.opacity(0.92), requestAccent.opacity(0.05) ], startPoint: .topLeading, endPoint: .bottomTrailing ), in: RoundedRectangle(cornerRadius: 28, style: .continuous) ) .overlay( RoundedRectangle(cornerRadius: 28, style: .continuous) .stroke(requestAccent.opacity(0.10), lineWidth: 1) ) .shadow(color: dashboardShadow, radius: 12, y: 5) } private var statusTone: Color { switch request.status { case .pending: return .orange case .approved: return .green case .rejected: return .red } } private var requestAccent: Color { switch request.status { case .approved: return .green case .rejected: return .red case .pending: return request.risk == .routine ? dashboardAccent : .orange } } private var reviewSummary: String { switch request.status { case .pending: if request.risk == .elevated { return "This is privileged access. Let it through only if the origin and the moment both match what you just initiated." } return "This looks routine, but it still needs to match the browser, CLI, or device session you expect." case .approved: return "This request was already approved in the mock queue and is now part of the recent audit trail." case .rejected: return "This request was rejected and should remain a closed lane unless a new request is issued." } } } private struct RequestQueueRow: View { let request: ApprovalRequest let isSelected: Bool let action: () -> Void var body: some View { Button(action: action) { HStack(alignment: .top, spacing: 12) { ZStack { RoundedRectangle(cornerRadius: 16, style: .continuous) .fill(rowAccent.opacity(0.14)) Image(systemName: request.kind.systemImage) .font(.headline) .foregroundStyle(rowAccent) } .frame(width: 38, height: 38) VStack(alignment: .leading, spacing: 10) { HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text(request.title) .font(.headline) .foregroundStyle(.primary) .multilineTextAlignment(.leading) .lineLimit(2) Text(request.trustHeadline) .font(.subheadline.weight(.semibold)) .foregroundStyle(rowAccent) .lineLimit(1) } Spacer(minLength: 0) StatusBadge( title: request.status.title, tone: statusTone ) } Text(request.source) .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) Text(request.subtitle) .font(.footnote) .foregroundStyle(.secondary) .lineLimit(1) .multilineTextAlignment(.leading) HStack(spacing: 8) { StatusBadge(title: request.risk.title, tone: request.risk == .routine ? .mint : .orange) StatusBadge(title: request.scopeSummary, tone: .blue) Spacer() Text(request.createdAt, style: .relative) .font(.footnote) .foregroundStyle(.secondary) } } Image(systemName: isSelected ? "chevron.right.circle.fill" : "chevron.right") .font(.headline) .foregroundStyle(isSelected ? rowAccent : .secondary.opacity(0.7)) .padding(.top, 2) } .padding(16) .frame(maxWidth: .infinity, alignment: .leading) .background(backgroundStyle, in: RoundedRectangle(cornerRadius: 24, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 24, style: .continuous) .stroke(isSelected ? rowAccent.opacity(0.36) : Color.clear, lineWidth: 1.5) ) .overlay(alignment: .leading) { Capsule() .fill(rowAccent.opacity(isSelected ? 0.80 : 0.30)) .frame(width: 5) .padding(.vertical, 16) .padding(.leading, 8) } } .buttonStyle(.plain) } private var statusTone: Color { switch request.status { case .pending: .orange case .approved: .green case .rejected: .red } } private var backgroundStyle: Color { isSelected ? rowAccent.opacity(0.08) : Color.white.opacity(0.90) } private var rowAccent: Color { switch request.status { case .approved: .green case .rejected: .red case .pending: request.risk == .routine ? dashboardAccent : .orange } } } private struct RequestWorkbenchDetail: View { let request: ApprovalRequest let isBusy: Bool let onApprove: (() -> Void)? let onReject: (() -> Void)? let onOpenRequest: () -> Void private let columns = [ GridItem(.flexible(), spacing: 12), GridItem(.flexible(), spacing: 12) ] var body: some View { VStack(alignment: .leading, spacing: 18) { ZStack(alignment: .topLeading) { RoundedRectangle(cornerRadius: 30, style: .continuous) .fill( LinearGradient( colors: [ request.risk == .routine ? dashboardAccent.opacity(0.95) : Color.orange.opacity(0.92), dashboardGold.opacity(0.88) ], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .overlay( RoundedRectangle(cornerRadius: 30, style: .continuous) .strokeBorder(requestAccent.opacity(0.20), lineWidth: 1) ) VStack(alignment: .leading, spacing: 16) { HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 8) { StatusBadge(title: request.kind.title, tone: .white) StatusBadge(title: request.risk.title, tone: .white) StatusBadge(title: request.status.title, tone: .white) } Text(request.title) .font(.system(size: 30, weight: .bold, design: .rounded)) .foregroundStyle(.white) Text(request.trustHeadline) .font(.headline) .foregroundStyle(.white.opacity(0.84)) } Spacer(minLength: 0) VStack(alignment: .trailing, spacing: 6) { Text("REQUESTED") .font(.caption.weight(.bold)) .foregroundStyle(.white.opacity(0.72)) Text(request.createdAt.formatted(date: .abbreviated, time: .shortened)) .font(.subheadline.weight(.semibold)) .foregroundStyle(.white) } } Text(request.subtitle) .font(.title3) .foregroundStyle(.white.opacity(0.88)) HStack(spacing: 14) { Label(request.source, systemImage: "network") Label(request.scopeSummary, systemImage: "lock.shield") } .font(.subheadline) .foregroundStyle(.white.opacity(0.88)) Text(request.trustDetail) .font(.subheadline) .foregroundStyle(.white.opacity(0.82)) } .padding(24) } .frame(minHeight: 220) LazyVGrid(columns: columns, spacing: 12) { FactCard(label: "Source", value: request.source) FactCard(label: "Requested", value: request.createdAt.formatted(date: .abbreviated, time: .shortened)) FactCard(label: "Type", value: request.kind.title) FactCard(label: "Status", value: request.status.title) FactCard(label: "Risk", value: request.risk.summary) FactCard(label: "Access", value: request.scopeSummary) } HStack(alignment: .top, spacing: 12) { RequestSignalCard( title: "Trust Signals", subtitle: "The approval story should match the device, the product, and the moment you just triggered.", accent: requestAccent ) { VStack(alignment: .leading, spacing: 14) { GuidanceRow( icon: "network.badge.shield.half.filled", title: "Source must look familiar", message: "This request comes from \(request.source). Only approve if that host or product lines up with what you intended." ) GuidanceRow( icon: "person.badge.shield.checkmark", title: "Action should fit the session", message: request.trustDetail ) GuidanceRow( icon: request.risk == .routine ? "checkmark.shield" : "exclamationmark.shield", title: request.risk == .routine ? "Routine review is still a review" : "Elevated access deserves a pause", message: request.risk.guidance ) } } RequestSignalCard( title: "Access Envelope", subtitle: "These are the capabilities this request wants before it can proceed.", accent: dashboardGold ) { if request.scopes.isEmpty { Text("The mock backend did not provide explicit scopes for this request.") .foregroundStyle(.secondary) } else { FlowScopes(scopes: request.scopes) } } } RequestSignalCard( title: request.status == .pending ? "Decision Rail" : "Decision Record", subtitle: request.status == .pending ? "Use the actions below only once the request story matches the device in your hand." : "This request already moved through the queue, so this rail becomes a compact audit note.", accent: statusTone ) { VStack(alignment: .leading, spacing: 14) { Text(request.trustDetail) .foregroundStyle(.secondary) Text(decisionSummary) .font(.headline) HStack(spacing: 12) { Button { onOpenRequest() } label: { Label("Open Full Review", systemImage: "arrow.up.forward.app") } .buttonStyle(.bordered) Spacer() if let onApprove, let onReject, request.status == .pending { Button { onApprove() } label: { if isBusy { ProgressView() } else { Label("Approve", systemImage: "checkmark.circle.fill") } } .buttonStyle(.borderedProminent) .disabled(isBusy) Button(role: .destructive) { onReject() } label: { Label("Reject", systemImage: "xmark.circle.fill") } .buttonStyle(.bordered) .disabled(isBusy) } } } } } .frame(maxWidth: .infinity, alignment: .leading) } private var statusTone: Color { switch request.status { case .pending: return .orange case .approved: return .green case .rejected: return .red } } private var requestAccent: Color { request.risk == .routine ? dashboardAccent : .orange } private var decisionSummary: String { switch request.status { case .pending: return request.risk == .routine ? "Approve only if the origin and timing feel boringly expected." : "Privileged requests should feel unmistakably intentional before you approve them." case .approved: return "This request has already been approved and should now be treated as part of your recent decision history." case .rejected: return "This request was rejected and is now a record of a blocked access attempt." } } } private struct RequestFactPill: View { let label: String let value: String let accent: Color var body: some View { VStack(alignment: .leading, spacing: 4) { Text(label.uppercased()) .font(.caption2.weight(.semibold)) .foregroundStyle(.secondary) Text(value) .font(.subheadline.weight(.semibold)) .foregroundStyle(.primary) .lineLimit(2) .minimumScaleFactor(0.8) } .padding(.horizontal, 12) .padding(.vertical, 10) .frame(maxWidth: .infinity, alignment: .leading) .background(accent.opacity(0.10), in: RoundedRectangle(cornerRadius: 18, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 18, style: .continuous) .stroke(accent.opacity(0.08), lineWidth: 1) ) } } private struct RequestSignalCard: View { let title: String let subtitle: String let accent: Color let content: () -> Content init( title: String, subtitle: String, accent: Color, @ViewBuilder content: @escaping () -> Content ) { self.title = title self.subtitle = subtitle self.accent = accent self.content = content } var body: some View { VStack(alignment: .leading, spacing: 14) { HStack(alignment: .top, spacing: 12) { Circle() .fill(accent.opacity(0.16)) .frame(width: 34, height: 34) .overlay { Circle() .stroke(accent.opacity(0.30), lineWidth: 1) } VStack(alignment: .leading, spacing: 4) { Text(title) .font(.headline) Text(subtitle) .foregroundStyle(.secondary) } } content() } .padding(18) .frame(maxWidth: .infinity, alignment: .leading) .dashboardSurface(radius: 24) } } private struct RequestDetailSheet: View { let request: ApprovalRequest @ObservedObject var model: AppViewModel @Environment(\.dismiss) private var dismiss var body: some View { NavigationStack { ScrollView { VStack(alignment: .leading, spacing: 20) { RequestDetailHero(request: request) SectionCard( title: "Requested Access", subtitle: "The exact scopes or capabilities this action wants to receive." ) { if request.scopes.isEmpty { Text("No explicit scopes were provided by the mock backend.") .foregroundStyle(.secondary) } else { FlowScopes(scopes: request.scopes) } } SectionCard( title: "Trust Signals", subtitle: "The details to validate before you approve anything sensitive." ) { VStack(alignment: .leading, spacing: 12) { FactCard(label: "Source", value: request.source) FactCard(label: "Requested", value: request.createdAt.formatted(date: .abbreviated, time: .shortened)) FactCard(label: "Type", value: request.kind.title) FactCard(label: "Risk", value: request.risk.summary) } } SectionCard( title: "Decision Guidance", subtitle: "A short operator-minded reminder before you accept or reject this request." ) { Text(request.trustDetail) .foregroundStyle(.secondary) Text(request.risk.guidance) .font(.headline) } if request.status == .pending { VStack(spacing: 12) { Button { Task { await model.approve(request) dismiss() } } label: { if model.activeRequestID == request.id { ProgressView() } else { Label("Approve Request", systemImage: "checkmark.circle.fill") } } .buttonStyle(.borderedProminent) .disabled(model.activeRequestID == request.id) Button(role: .destructive) { Task { await model.reject(request) dismiss() } } label: { Label("Reject Request", systemImage: "xmark.circle.fill") } .buttonStyle(.bordered) .disabled(model.activeRequestID == request.id) } } } .padding(.horizontal, DashboardSpacing.compactOuterPadding) .padding(.top, DashboardSpacing.compactTopPadding) .padding(.bottom, DashboardSpacing.compactBottomPadding) .frame(maxWidth: DashboardSpacing.compactContentWidth, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading) } .navigationTitle("Review Request") .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Close") { dismiss() } } } } } } private struct RequestDetailHero: View { let request: ApprovalRequest var body: some View { ZStack(alignment: .bottomLeading) { RoundedRectangle(cornerRadius: 30, style: .continuous) .fill( LinearGradient( colors: [ request.risk == .routine ? dashboardAccent.opacity(0.92) : Color.orange.opacity(0.92), dashboardGold.opacity(0.88) ], startPoint: .topLeading, endPoint: .bottomTrailing ) ) VStack(alignment: .leading, spacing: 12) { Text(request.trustHeadline) .font(.headline) .foregroundStyle(.white.opacity(0.86)) Text(request.title) .font(.system(size: 30, weight: .bold, design: .rounded)) .foregroundStyle(.white) Text(request.subtitle) .foregroundStyle(.white.opacity(0.86)) } .padding(24) } .frame(minHeight: 210) } } private struct NotificationCard: View { let notification: AppNotification let compactLayout: Bool let onMarkRead: () -> Void var body: some View { VStack(alignment: .leading, spacing: 14) { HStack(alignment: .top, spacing: 14) { Image(systemName: notification.kind.systemImage) .font(.title3) .frame(width: 38, height: 38) .background(.thinMaterial, in: Circle()) .foregroundStyle(accentColor) VStack(alignment: .leading, spacing: 8) { HStack { Text(notification.title) .font(.headline) Spacer() if notification.isUnread { StatusBadge(title: "Unread", tone: .orange) } } Text(notification.kind.summary) .font(.footnote) .foregroundStyle(.secondary) } } Text(notification.message) .foregroundStyle(.secondary) Group { if compactLayout { VStack(alignment: .leading, spacing: 10) { timestampLabel if notification.isUnread { markReadButton } } } else { HStack { timestampLabel Spacer() if notification.isUnread { markReadButton } } } } } .padding(compactLayout ? 16 : 18) .dashboardSurface(radius: compactLayout ? 22 : 24) } private var timestampLabel: some View { Text(notification.sentAt.formatted(date: .abbreviated, time: .shortened)) .font(.footnote) .foregroundStyle(.secondary) } private var markReadButton: some View { Button { onMarkRead() } label: { Label("Mark Read", systemImage: "checkmark") } .buttonStyle(.bordered) } private var accentColor: Color { switch notification.kind { case .approval: .green case .security: .orange case .system: .blue } } } private struct NotificationMetricCard: View { let title: String let value: String let subtitle: String let accent: Color var body: some View { VStack(alignment: .leading, spacing: 10) { Text(title.uppercased()) .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) Text(value) .font(.title3.weight(.semibold)) .foregroundStyle(.primary) Text(subtitle) .font(.footnote) .foregroundStyle(.secondary) } .padding(18) .frame(maxWidth: .infinity, alignment: .leading) .background(accent.opacity(0.10), in: RoundedRectangle(cornerRadius: 20, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 20, style: .continuous) .stroke(accent.opacity(0.08), lineWidth: 1) ) } } private struct NotificationFeedRow: View { let notification: AppNotification let isSelected: Bool let action: () -> Void var body: some View { Button(action: action) { VStack(alignment: .leading, spacing: 10) { HStack(alignment: .top, spacing: 12) { Image(systemName: notification.kind.systemImage) .font(.headline) .foregroundStyle(accentColor) .frame(width: 34, height: 34) .background(.thinMaterial, in: Circle()) VStack(alignment: .leading, spacing: 4) { Text(notification.title) .font(.headline) .foregroundStyle(.primary) .multilineTextAlignment(.leading) Text(notification.kind.summary) .font(.subheadline) .foregroundStyle(.secondary) } Spacer(minLength: 0) if notification.isUnread { Circle() .fill(Color.orange) .frame(width: 10, height: 10) } } Text(notification.message) .font(.footnote) .foregroundStyle(.secondary) .lineLimit(2) .multilineTextAlignment(.leading) HStack { StatusBadge(title: notification.kind.title, tone: accentColor) Spacer() Text(notification.sentAt.formatted(date: .omitted, time: .shortened)) .font(.footnote) .foregroundStyle(.secondary) } } .padding(16) .frame(maxWidth: .infinity, alignment: .leading) .background(backgroundStyle, in: RoundedRectangle(cornerRadius: 24, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 24, style: .continuous) .stroke(isSelected ? accentColor.opacity(0.35) : Color.clear, lineWidth: 1.5) ) } .buttonStyle(.plain) } private var accentColor: Color { switch notification.kind { case .approval: .green case .security: .orange case .system: .blue } } private var backgroundStyle: Color { isSelected ? accentColor.opacity(0.10) : Color.white.opacity(0.58) } } private struct NotificationWorkbenchDetail: View { let notification: AppNotification let permissionState: NotificationPermissionState let onMarkRead: () -> Void private let columns = [ GridItem(.flexible(), spacing: 12), GridItem(.flexible(), spacing: 12) ] var body: some View { VStack(alignment: .leading, spacing: 18) { ZStack(alignment: .bottomLeading) { RoundedRectangle(cornerRadius: 30, style: .continuous) .fill( LinearGradient( colors: [ accentColor.opacity(0.95), accentColor.opacity(0.70), dashboardGold.opacity(0.82) ], startPoint: .topLeading, endPoint: .bottomTrailing ) ) VStack(alignment: .leading, spacing: 12) { HStack(spacing: 8) { StatusBadge(title: notification.kind.title, tone: .white) StatusBadge(title: notification.isUnread ? "Unread" : "Read", tone: .white) } Text(notification.title) .font(.system(size: 30, weight: .bold, design: .rounded)) .foregroundStyle(.white) Text(notification.message) .foregroundStyle(.white.opacity(0.9)) } .padding(24) } .frame(minHeight: 210) LazyVGrid(columns: columns, spacing: 12) { FactCard(label: "Category", value: notification.kind.summary) FactCard(label: "Sent", value: notification.sentAt.formatted(date: .abbreviated, time: .shortened)) FactCard(label: "Inbox State", value: notification.isUnread ? "Still highlighted" : "Already cleared") FactCard(label: "Delivery", value: permissionState.title) } VStack(alignment: .leading, spacing: 10) { Text("Delivery Context") .font(.headline) Text(permissionState.summary) .foregroundStyle(.secondary) Text(notification.isUnread ? "This alert is still asking for attention in the in-app feed." : "This alert has already been acknowledged in the mock inbox.") .font(.headline) } .padding(18) .frame(maxWidth: .infinity, alignment: .leading) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 26, style: .continuous)) if notification.isUnread { Button { onMarkRead() } label: { Label("Mark Read", systemImage: "checkmark") } .buttonStyle(.borderedProminent) } } .frame(maxWidth: .infinity, alignment: .leading) } private var accentColor: Color { switch notification.kind { case .approval: .green case .security: .orange case .system: .blue } } } private struct SectionCard: View { let title: String let subtitle: String let compactLayout: Bool let content: () -> Content init( title: String, subtitle: String, compactLayout: Bool = false, @ViewBuilder content: @escaping () -> Content ) { self.title = title self.subtitle = subtitle self.compactLayout = compactLayout 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(compactLayout ? DashboardSpacing.compactSectionPadding : DashboardSpacing.regularSectionPadding) .frame(maxWidth: .infinity, alignment: .leading) .dashboardSurface(radius: compactLayout ? DashboardSpacing.compactRadius : DashboardSpacing.regularRadius) } } private struct BannerCard: View { let message: String let compactLayout: Bool var body: some View { HStack(spacing: 12) { Image(systemName: "sparkles") .font(.title3) .foregroundStyle(dashboardAccent) Text(message) .font(compactLayout ? .subheadline.weight(.semibold) : .headline) } .padding(.horizontal, 16) .padding(.vertical, 12) .dashboardSurface(radius: 999, fillOpacity: 0.84) } } private struct SmallMetricPill: View { let title: String let value: String var body: some View { VStack(alignment: .leading, spacing: 4) { Text(title.uppercased()) .font(.caption2.weight(.semibold)) .foregroundStyle(.secondary) Text(value) .font(.headline) } .padding(.horizontal, 12) .padding(.vertical, 10) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 18, style: .continuous)) } } private struct HeroMetric: View { let title: String let value: String var body: some View { VStack(alignment: .leading, spacing: 6) { Text(title.uppercased()) .font(.caption.weight(.semibold)) .foregroundStyle(.white.opacity(0.72)) Text(value) .font(.title2.weight(.bold)) .foregroundStyle(.white) } .padding(.horizontal, 16) .padding(.vertical, 14) .frame(maxWidth: .infinity, alignment: .leading) .background(.white.opacity(0.12), in: RoundedRectangle(cornerRadius: 20, style: .continuous)) } } private struct GuidanceRow: View { let icon: String let title: String let message: String var body: some View { HStack(alignment: .top, spacing: 12) { Image(systemName: icon) .font(.title3) .frame(width: 32) .foregroundStyle(dashboardAccent) VStack(alignment: .leading, spacing: 4) { Text(title) .font(.headline) Text(message) .foregroundStyle(.secondary) } } } } private struct GuidanceCard: View { let icon: String let title: String let message: String var body: some View { VStack(alignment: .leading, spacing: 12) { Image(systemName: icon) .font(.title3) .foregroundStyle(dashboardAccent) Text(title) .font(.headline) Text(message) .foregroundStyle(.secondary) } .padding(18) .frame(maxWidth: .infinity, alignment: .leading) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 24, style: .continuous)) } } private struct FlowScopes: View { let scopes: [String] var body: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(scopes, id: \.self) { scope in Text(scope) .font(.caption.monospaced()) .padding(.horizontal, 10) .padding(.vertical, 8) .background(.thinMaterial, in: Capsule()) } } } } } private struct EmptyStateCopy: View { let title: String let systemImage: String let message: String var body: some View { ContentUnavailableView( title, systemImage: systemImage, description: Text(message) ) .frame(maxWidth: .infinity) .padding(.vertical, 10) } } private struct FactCard: View { let label: String let value: String var body: some View { VStack(alignment: .leading, spacing: 6) { Text(label.uppercased()) .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) Text(value) .font(.body) } .padding(14) .frame(maxWidth: .infinity, alignment: .leading) .dashboardSurface(radius: 18) } } private struct StatusBadge: View { let title: String let tone: Color var body: some View { Text(title) .font(.caption.weight(.semibold)) .lineLimit(1) .minimumScaleFactor(0.8) .fixedSize(horizontal: true, vertical: false) .padding(.horizontal, 10) .padding(.vertical, 6) .background(tone.opacity(0.14), in: Capsule()) .foregroundStyle(tone) } } private struct DashboardBackdrop: View { var body: some View { LinearGradient( colors: [ Color(red: 0.98, green: 0.98, blue: 0.97), Color.white, Color(red: 0.97, green: 0.98, blue: 0.99) ], startPoint: .topLeading, endPoint: .bottomTrailing ) .overlay(alignment: .topLeading) { Circle() .fill(dashboardAccent.opacity(0.10)) .frame(width: 360, height: 360) .blur(radius: 70) .offset(x: -120, y: -120) } .overlay(alignment: .bottomTrailing) { Circle() .fill(dashboardGold.opacity(0.12)) .frame(width: 420, height: 420) .blur(radius: 90) .offset(x: 140, y: 160) } .ignoresSafeArea() } }