eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1import SwiftUI
2
3// 🤖 The native quick form ("Fill in a form instead"): every onboarding
4// question at once, submitted as a fast-fill through
5// ElevenConversationModel.applyQuickForm — the same verbs and step records the
6// conversation would produce, after which the flow re-enters and walks itself
7// to the finish. Native flows skip the sign-in (access is the Eleven account)
8// and the photo step, so the primary button is Save, not Sign in: welcome asks
9// only the name; setup adds the space name, email, invites, and the extras.
10// Shared logic here; the hosts (ElevenChatScreen / ElevenConversationPane)
11// present it as each platform's sheet.
12public struct ElevenQuickFormView: View {
13 @ObservedObject var model: ElevenConversationModel
14 @Environment(\.dismiss) private var dismiss
15 @State private var name: String
16 @State private var spaceName: String
17 @State private var email = ""
18 @State private var membersInvite = true
19 @State private var ai = true
20 @State private var bots = true
21 @State private var apps = true
22 @State private var error: String?
23 @State private var saving = false
24
25 public init(model: ElevenConversationModel) {
26 self.model = model
27 _name = State(initialValue: model.instance.screenName)
28 _spaceName = State(initialValue: model.quickFormSpaceName)
29 }
30
31 private var title: String {
32 model.quickFormIsSetup ? "Set up your space" : "Introduce yourself"
33 }
34
35 public var body: some View {
36 #if os(macOS)
37 // Mac sheet chrome: title, the grouped form, then the button row —
38 // Cancel left, the prominent default action right.
39 VStack(spacing: 0) {
40 Text(title)
41 .font(.headline)
42 .padding(.top, 14)
43 .padding(.bottom, 4)
44 formBody
45 .formStyle(.grouped)
46 Divider()
47 HStack {
48 Button("Cancel") { dismiss() }
49 .keyboardShortcut(.cancelAction)
50 Spacer()
51 Button("Save", action: submit)
52 .keyboardShortcut(.defaultAction)
53 .buttonStyle(.borderedProminent)
54 .disabled(saving)
55 }
56 .padding(12)
57 }
58 .frame(minWidth: 400, minHeight: model.quickFormIsSetup ? 540 : 220)
59 #else
60 NavigationStack {
61 formBody
62 .navigationTitle(title)
63 .navigationBarTitleDisplayMode(.inline)
64 .toolbar {
65 ToolbarItem(placement: .cancellationAction) {
66 Button("Cancel") { dismiss() }
67 }
68 ToolbarItem(placement: .confirmationAction) {
69 Button("Save", action: submit)
70 .disabled(saving)
71 }
72 }
73 }
74 #endif
75 }
76
77 private var formBody: some View {
78 Form {
79 Section {
80 TextField("Your name", text: $name)
81 .accessibilityLabel("Your name")
82 }
83 if model.quickFormIsSetup {
84 Section("Your space") {
85 TextField("Space name", text: $spaceName)
86 .accessibilityLabel("Space name")
87 TextField("Contact email (optional)", text: $email)
88 .accessibilityLabel("Contact email (optional)")
89 #if os(iOS)
90 .keyboardType(.emailAddress)
91 .textInputAutocapitalization(.never)
92 .autocorrectionDisabled()
93 #endif
94 }
95 Section {
96 Toggle("Members can invite people", isOn: $membersInvite)
97 } footer: {
98 Text("Otherwise, new faces only arrive through you.")
99 }
100 Section("Extras") {
101 VStack(alignment: .leading, spacing: 3) {
102 Toggle("AI", isOn: $ai)
103 // The one-line reassurance, right under the first (and
104 // scariest-sounding) switch.
105 Text("Everything essential works with any of these off — change your mind anytime in Admin.")
106 .font(.footnote)
107 .foregroundStyle(.secondary)
108 }
109 Toggle("Bots", isOn: $bots)
110 Toggle("Apps", isOn: $apps)
111 }
112 }
113 if let error {
114 // Inline, occupying layout — never a floating overlay; the
115 // words carry the state (color is decoration, not the signal).
116 Section {
117 Text(error)
118 .font(.callout)
119 .foregroundStyle(.red)
120 .accessibilityLabel("Error: \(error)")
121 }
122 }
123 }
124 }
125
126 private func submit() {
127 // The same required checks the conversational steps enforce.
128 if cleanNameAnswer(name).isEmpty {
129 error = "Add your name to continue."
130 return
131 }
132 if model.quickFormIsSetup, spaceName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
133 error = "Give your space a name to continue."
134 return
135 }
136 saving = true
137 var v = QuickFormValues()
138 v.name = name
139 v.spaceName = spaceName
140 v.email = email
141 v.membersInvite = membersInvite
142 v.ai = ai
143 v.bots = bots
144 v.apps = apps
145 Task { @MainActor in
146 if let msg = await model.applyQuickForm(v) {
147 error = msg
148 saving = false
149 } else {
150 dismiss()
151 }
152 }
153 }
154}
155