import SwiftUI struct SessionConfigurationPanel: View { @State private var session = SessionStore.shared let title: String let subtitle: String let primaryActionTitle: String let allowsClearingStoredConfiguration: Bool var body: some View { @Bindable var session = session VStack(alignment: .leading, spacing: 18) { VStack(alignment: .leading, spacing: 6) { Text(title) .font(.system(size: 18, weight: .semibold)) .foregroundStyle(VelocityTheme.foreground) Text(subtitle) .font(.system(size: 12)) .foregroundStyle(VelocityTheme.mutedFg) } VStack(spacing: 14) { SessionInputField( label: "Backend endpoint", placeholder: "https://velocity.desineuron.in/api" ) { TextField("", text: $session.draftBaseURL, prompt: Text("https://velocity.desineuron.in/api")) .textInputAutocapitalization(.never) .autocorrectionDisabled() .keyboardType(.URL) } SessionInputField( label: "Dream Weaver endpoint", placeholder: "Leave blank to use the backend endpoint" ) { TextField("", text: $session.draftDreamWeaverBaseURL, prompt: Text("https://dreamweaver.desineuron.in")) .textInputAutocapitalization(.never) .autocorrectionDisabled() .keyboardType(.URL) } SessionInputField( label: "Dream Weaver gateway API key", placeholder: session.currentConfiguration.hasDreamWeaverAPIKey ? "Leave blank to keep the current gateway key" : "Optional unless the gateway enforces it" ) { SecureField( "", text: $session.draftDreamWeaverAPIKey, prompt: Text(session.currentConfiguration.hasDreamWeaverAPIKey ? "Leave blank to keep the current gateway key" : "Optional unless the gateway enforces it") ) .textInputAutocapitalization(.never) .autocorrectionDisabled() } VStack(alignment: .leading, spacing: 8) { Text("Authentication mode") .font(.system(size: 12, weight: .medium)) .foregroundStyle(VelocityTheme.mutedFg) Picker("Authentication mode", selection: $session.draftAuthMode) { ForEach(SessionAuthMode.allCases) { mode in Text(mode.rawValue).tag(mode) } } .pickerStyle(.segmented) } if session.draftAuthMode == .emailPassword { SessionInputField( label: "Operator email", placeholder: "operator@desineuron.in" ) { TextField("", text: $session.draftEmail, prompt: Text("operator@desineuron.in")) .textInputAutocapitalization(.never) .autocorrectionDisabled() .keyboardType(.emailAddress) } SessionInputField( label: "Password", placeholder: session.currentConfiguration.hasPassword ? "Leave blank to keep the current secret" : "Required" ) { SecureField( "", text: $session.draftPassword, prompt: Text(session.currentConfiguration.hasPassword ? "Leave blank to keep the current secret" : "Required") ) .textInputAutocapitalization(.never) .autocorrectionDisabled() } } else { SessionInputField( label: "Bearer token", placeholder: session.currentConfiguration.hasBearerToken ? "Leave blank to keep the current token" : "Required" ) { SecureField( "", text: $session.draftBearerToken, prompt: Text(session.currentConfiguration.hasBearerToken ? "Leave blank to keep the current token" : "Required") ) .textInputAutocapitalization(.never) .autocorrectionDisabled() } } } VStack(alignment: .leading, spacing: 4) { Text("Current source: \(session.configurationSourceDescription)") .font(.system(size: 12, weight: .medium)) .foregroundStyle(VelocityTheme.foreground) Text("Runtime overrides are saved on-device. Secrets are stored in Keychain; the backend endpoint, optional Dream Weaver endpoint, and operator email are stored in local app preferences.") .font(.system(size: 11)) .foregroundStyle(VelocityTheme.mutedFg) } if let message = session.statusMessage { SessionStatusBanner(message: message, accentColor: VelocityTheme.success) } if let error = session.errorMessage { SessionStatusBanner(message: error, accentColor: VelocityTheme.danger) } HStack(spacing: 12) { Button { Task { await session.saveDraft() } } label: { HStack(spacing: 8) { if session.isSaving { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) .tint(.white) } Text(primaryActionTitle) .font(.system(size: 14, weight: .semibold)) } .frame(maxWidth: .infinity) .padding(.vertical, 12) } .buttonStyle(SessionActionButtonStyle(background: VelocityTheme.accent)) .disabled(session.isSaving) Button("Reset form") { session.discardDraftChanges() } .buttonStyle(SessionSecondaryButtonStyle()) .disabled(session.isSaving || !session.hasUnsavedChanges) } if allowsClearingStoredConfiguration { Button("Clear stored session override") { Task { await session.clearStoredConfiguration() } } .buttonStyle(SessionDangerButtonStyle()) .disabled(session.isSaving || !session.isUsingStoredRuntimeConfiguration) } } .padding(20) .glassCard(cornerRadius: 20) } } private struct SessionInputField: View { let label: String let placeholder: String @ViewBuilder let field: Field var body: some View { VStack(alignment: .leading, spacing: 8) { Text(label) .font(.system(size: 12, weight: .medium)) .foregroundStyle(VelocityTheme.mutedFg) field .font(.system(size: 14)) .foregroundStyle(VelocityTheme.foreground) .padding(.horizontal, 14) .padding(.vertical, 12) .background( RoundedRectangle(cornerRadius: 12) .fill(VelocityTheme.surface) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(VelocityTheme.borderSubtle, lineWidth: 1) ) ) Text(placeholder) .font(.system(size: 10)) .foregroundStyle(VelocityTheme.mutedFg.opacity(0.9)) } } } private struct SessionStatusBanner: View { let message: String let accentColor: Color var body: some View { Text(message) .font(.system(size: 12, weight: .medium)) .foregroundStyle(accentColor) .padding(.horizontal, 14) .padding(.vertical, 12) .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: 12) .fill(accentColor.opacity(0.10)) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(accentColor.opacity(0.18), lineWidth: 1) ) ) } } private struct SessionActionButtonStyle: ButtonStyle { let background: Color func makeBody(configuration: Configuration) -> some View { configuration.label .foregroundStyle(.white) .background( RoundedRectangle(cornerRadius: 12) .fill(background.opacity(configuration.isPressed ? 0.82 : 1)) ) } } private struct SessionSecondaryButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.system(size: 14, weight: .medium)) .foregroundStyle(VelocityTheme.foreground) .padding(.horizontal, 18) .padding(.vertical, 12) .background( RoundedRectangle(cornerRadius: 12) .fill(VelocityTheme.surface.opacity(configuration.isPressed ? 0.78 : 1)) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(VelocityTheme.borderSubtle, lineWidth: 1) ) ) } } private struct SessionDangerButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.system(size: 13, weight: .medium)) .foregroundStyle(VelocityTheme.danger) .padding(.horizontal, 14) .padding(.vertical, 11) .background( RoundedRectangle(cornerRadius: 12) .fill(VelocityTheme.danger.opacity(configuration.isPressed ? 0.14 : 0.10)) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(VelocityTheme.danger.opacity(0.2), lineWidth: 1) ) ) } }