// // ConfirmationCardView.swift // Poche // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // import SwiftUI /// The confirmation card: what will be done, with resolved values in clear /// text, and two choices. This friction is what makes a 3B agent usable — /// never minimize it away (CLAUDE.md §6). struct ConfirmationCardView: View { let action: PendingAction let onConfirm: () -> Void let onModify: () -> Void var body: some View { VStack(alignment: .leading, spacing: Theme.spacingMedium) { HStack(spacing: Theme.spacingMedium) { RoundedRectangle(cornerRadius: 10) .fill(Theme.accentGradient) .frame(width: 36, height: 36) .overlay { Image(systemName: iconName) .font(.system(size: 16, weight: .semibold)) .foregroundStyle(.white) } VStack(alignment: .leading, spacing: 1) { Text(action.title) .font(.headline) Text("En attente de ta confirmation") .font(.caption) .foregroundStyle(.secondary) } } VStack(alignment: .leading, spacing: Theme.spacingSmall + 2) { ForEach(action.fields, id: \.label) { field in HStack(alignment: .firstTextBaseline, spacing: Theme.spacingMedium) { Text(field.label) .font(.subheadline) .foregroundStyle(.secondary) .frame(width: 90, alignment: .leading) Text(field.value) .font(.subheadline.weight(.medium)) } } } .padding(Theme.spacingMedium) .frame(maxWidth: .infinity, alignment: .leading) .background(Theme.background.opacity(0.6)) .clipShape(RoundedRectangle(cornerRadius: Theme.cardCornerRadius - 8)) if case .failed(let message) = action.status { Label(message, systemImage: "exclamationmark.triangle.fill") .font(.footnote) .foregroundStyle(.red) } HStack(spacing: Theme.spacingMedium) { Button(action: onConfirm) { Group { if action.status == .executing { ProgressView() .controlSize(.small) .tint(.white) } else { Text("Confirmer") .font(.subheadline.weight(.semibold)) } } .foregroundStyle(.white) .frame(maxWidth: .infinity) .padding(.vertical, Theme.spacingMedium - 2) .background(Theme.accentGradient) .clipShape(Capsule()) } .buttonStyle(.plain) .disabled(action.status == .executing) Button(action: onModify) { Text("Modifier") .font(.subheadline.weight(.semibold)) .foregroundStyle(.primary) .frame(maxWidth: .infinity) .padding(.vertical, Theme.spacingMedium - 2) .background(Theme.background) .clipShape(Capsule()) } .buttonStyle(.plain) .disabled(action.status == .executing) } } .padding(Theme.spacingMedium + 2) .background(Theme.surface) .clipShape(RoundedRectangle(cornerRadius: Theme.cardCornerRadius)) .overlay { RoundedRectangle(cornerRadius: Theme.cardCornerRadius) .strokeBorder(Theme.accentA.opacity(0.25)) } .shadow(color: .black.opacity(0.10), radius: 14, y: 6) } private var iconName: String { switch action.payload { case .reminder: "bell.fill" case .event: "calendar" case .note: "note.text" case .newTask: "checklist" case .taskUpdate: "pencil" } } }