import SwiftUI struct OverviewPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { VStack(alignment: .leading, spacing: AppLayout.sectionSpacing(for: compactLayout)) { if let profile = model.profile, let session = model.session { OverviewHero( profile: profile, session: session, pendingCount: model.pendingRequests.count, unreadCount: model.unreadNotificationCount, compactLayout: compactLayout ) } } } } struct RequestsPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool let onOpenRequest: (ApprovalRequest) -> Void var body: some View { VStack(alignment: .leading, spacing: AppLayout.sectionSpacing(for: compactLayout)) { if model.requests.isEmpty { AppPanel(compactLayout: compactLayout) { EmptyStateCopy( title: "No checks waiting", systemImage: "checkmark.circle", message: "Identity proof requests from sites and devices appear here." ) } } else { RequestList( requests: model.requests, compactLayout: compactLayout, activeRequestID: model.activeRequestID, onApprove: { request in Task { await model.approve(request) } }, onReject: { request in Task { await model.reject(request) } }, onOpenRequest: onOpenRequest ) } } } } struct ActivityPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { VStack(alignment: .leading, spacing: AppLayout.sectionSpacing(for: compactLayout)) { if model.notifications.isEmpty { AppPanel(compactLayout: compactLayout) { EmptyStateCopy( title: "No proof activity yet", systemImage: "clock.badge.xmark", message: "Identity proofs and security events will appear here." ) } } else { NotificationList( notifications: model.notifications, compactLayout: compactLayout, onMarkRead: { notification in Task { await model.markNotificationRead(notification) } } ) } } } } struct NotificationsPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { VStack(alignment: .leading, spacing: AppLayout.sectionSpacing(for: compactLayout)) { AppSectionCard(title: "Delivery", compactLayout: compactLayout) { NotificationPermissionSummary(model: model, compactLayout: compactLayout) } AppSectionCard(title: "Alerts", compactLayout: compactLayout) { if model.notifications.isEmpty { EmptyStateCopy( title: "No alerts yet", systemImage: "bell.slash", message: "New passport and identity-proof alerts will accumulate here." ) } else { NotificationList( notifications: model.notifications, compactLayout: compactLayout, onMarkRead: { notification in Task { await model.markNotificationRead(notification) } } ) } } } } } struct AccountPanel: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { VStack(alignment: .leading, spacing: AppLayout.sectionSpacing(for: compactLayout)) { if let profile = model.profile, let session = model.session { AccountHero(profile: profile, session: session, compactLayout: compactLayout) AppSectionCard(title: "Session", compactLayout: compactLayout) { AccountFactsGrid(profile: profile, session: session, compactLayout: compactLayout) } } AppSectionCard(title: "Pairing payload", compactLayout: compactLayout) { AppTextSurface(text: model.suggestedPairingPayload, monospaced: true) } AppSectionCard(title: "Actions", 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 private var detailColumns: [GridItem] { Array(repeating: GridItem(.flexible(), spacing: 16), count: compactLayout ? 1 : 2) } private var metricColumns: [GridItem] { Array(repeating: GridItem(.flexible(), spacing: 16), count: 3) } var body: some View { AppPanel(compactLayout: compactLayout, radius: AppLayout.largeCardRadius) { AppBadge(title: "Digital passport", tone: dashboardAccent) VStack(alignment: .leading, spacing: 6) { Text(profile.name) .font(.system(size: compactLayout ? 30 : 38, weight: .bold, design: .rounded)) .lineLimit(2) Text("\(profile.handle) • \(profile.organization)") .font(.subheadline) .foregroundStyle(.secondary) } HStack(spacing: 8) { AppStatusTag(title: "Passport active", tone: dashboardAccent) AppStatusTag(title: session.pairingTransport.title, tone: dashboardGold) } Divider() LazyVGrid(columns: detailColumns, alignment: .leading, spacing: 16) { AppKeyValue(label: "Device", value: session.deviceName) AppKeyValue(label: "Origin", value: session.originHost, monospaced: true) AppKeyValue(label: "Linked", value: session.pairedAt.formatted(date: .abbreviated, time: .shortened)) AppKeyValue(label: "Token", value: "...\(session.tokenPreview)", monospaced: true) } Divider() LazyVGrid(columns: metricColumns, alignment: .leading, spacing: 16) { AppMetric(title: "Pending", value: "\(pendingCount)") AppMetric(title: "Alerts", value: "\(unreadCount)") AppMetric(title: "Devices", value: "\(profile.deviceCount)") } } } } private struct NotificationPermissionSummary: View { @ObservedObject var model: AppViewModel let compactLayout: Bool var body: some View { VStack(alignment: .leading, spacing: 14) { HStack(alignment: .top, spacing: 12) { Image(systemName: model.notificationPermission.systemImage) .font(.headline) .foregroundStyle(dashboardAccent) .frame(width: 28, height: 28) VStack(alignment: .leading, spacing: 4) { Text(model.notificationPermission.title) .font(.headline) Text(model.notificationPermission.summary) .font(.subheadline) .foregroundStyle(.secondary) } } if compactLayout { VStack(alignment: .leading, spacing: 12) { permissionButtons } } else { HStack(spacing: 12) { permissionButtons } } } } @ViewBuilder private var permissionButtons: some View { Button { Task { await model.requestNotificationAccess() } } label: { Label("Enable notifications", systemImage: "bell.and.waves.left.and.right.fill") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) Button { Task { await model.sendTestNotification() } } label: { Label("Send test alert", systemImage: "paperplane.fill") .frame(maxWidth: .infinity) } .buttonStyle(.bordered) } } private struct AccountHero: View { let profile: MemberProfile let session: AuthSession let compactLayout: Bool var body: some View { AppPanel(compactLayout: compactLayout, radius: AppLayout.largeCardRadius) { AppBadge(title: "Account", tone: dashboardAccent) Text(profile.name) .font(.system(size: compactLayout ? 28 : 34, weight: .bold, design: .rounded)) .lineLimit(2) Text(profile.handle) .font(.headline) .foregroundStyle(.secondary) Text("Active client: \(session.deviceName)") .font(.subheadline) .foregroundStyle(.secondary) } } } private struct AccountFactsGrid: View { let profile: MemberProfile let session: AuthSession let compactLayout: Bool private var columns: [GridItem] { Array(repeating: GridItem(.flexible(), spacing: 16), count: compactLayout ? 1 : 2) } var body: some View { LazyVGrid(columns: columns, alignment: .leading, spacing: 16) { AppKeyValue(label: "Organization", value: profile.organization) AppKeyValue(label: "Origin", value: session.originHost, monospaced: true) AppKeyValue(label: "Linked At", value: session.pairedAt.formatted(date: .abbreviated, time: .shortened)) AppKeyValue(label: "Method", value: session.pairingTransport.title) AppKeyValue(label: "Token", value: "...\(session.tokenPreview)", monospaced: true) AppKeyValue(label: "Recovery", value: profile.recoverySummary) if let signedGPSPosition = session.signedGPSPosition { AppKeyValue( label: "Signed GPS", value: "\(signedGPSPosition.coordinateSummary) \(signedGPSPosition.accuracySummary)", monospaced: true ) } AppKeyValue(label: "Trusted Devices", value: "\(profile.deviceCount)") } } } 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) } }