import SwiftUI import OrgSocialKit /// First-run welcome gate. Shown when the user hasn't configured a publishing method yet. /// Offers two paths: jump straight into Settings with an existing account, or create one /// on host.org-social.org and auto-fill the config. struct WelcomeView: View { @State private var showSettings = false @State private var showCreate = false var onConfigured: () -> Void = {} var body: some View { VStack(spacing: 32) { Spacer() LogoView(size: 120) .allowsHitTesting(false) .accessibilityHidden(true) VStack(spacing: 8) { Text("Welcome to Org Social") .font(.largeTitle.weight(.bold)) .multilineTextAlignment(.center) Text("A decentralized social network based on plain text `social.org` files.") .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 32) } Spacer() VStack(spacing: 12) { Button { showCreate = true } label: { Label("Create my social.org", systemImage: "sparkles") .font(.body.weight(.semibold)) .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) Button { showSettings = true } label: { Label("I already have a social.org", systemImage: "gearshape") .frame(maxWidth: .infinity) } .buttonStyle(.bordered) .controlSize(.large) } .padding(.horizontal, 32) .padding(.bottom, 40) } .sheet(isPresented: $showSettings, onDismiss: onConfigured) { SettingsView() } .sheet(isPresented: $showCreate, onDismiss: onConfigured) { CreateAccountSheet() } } } /// Sign-up form that creates an account on host.org-social.org and writes the /// returned vfile token URL + public feed URL into UserDefaults. struct CreateAccountSheet: View { @Environment(\.dismiss) private var dismiss @AppStorage("vfileURL") private var vfileURL = "" @AppStorage("publicFeedURL") private var publicFeedURL = "" @AppStorage("uploadMethod") private var uploadMethod = "vfile" @State private var nickname = "" @State private var isCreating = false @State private var errorMessage: String? @State private var success = false private let client = HostSignupClient() var body: some View { NavigationStack { Form { Section { TextField("Nickname", text: $nickname) .autocorrectionDisabled() .textInputAutocapitalization(.never) } header: { Text("Choose a nickname") } footer: { Text("Your feed will be hosted at https://host.org-social.org//social.org. Pick something unique—no spaces.") } if let errorMessage { Section { Label(errorMessage, systemImage: "exclamationmark.triangle") .font(.footnote) .foregroundStyle(.red) } } if success { Section { Label("Account created", systemImage: "checkmark.circle.fill") .foregroundStyle(.green) Text(publicFeedURL) .font(.caption) .foregroundStyle(.secondary) } } } .navigationTitle("Create account") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .confirmationAction) { if isCreating { ProgressView() } else { Button("Create") { Task { await create() } } .disabled(nickname.trimmingCharacters(in: .whitespaces).isEmpty || success) } } } } } private func create() async { isCreating = true errorMessage = nil defer { isCreating = false } do { let result = try await client.signup(nick: nickname) vfileURL = result.vfileURL.absoluteString publicFeedURL = result.publicURL.absoluteString uploadMethod = "vfile" success = true try? await Task.sleep(for: .seconds(1)) dismiss() } catch { errorMessage = error.localizedDescription } } }