// // PollsListView.swift // SportsTime // // View for listing user's polls // import SwiftUI struct PollsListView: View { @Environment(\.colorScheme) private var colorScheme @State private var polls: [TripPoll] = [] @State private var isLoading = false @State private var hasLoadedInitially = false @State private var errorMessage: String? @State private var showJoinPoll = false @State private var joinCode = "" @State private var pendingJoinCode: IdentifiableShareCode? var body: some View { Group { if isLoading && polls.isEmpty { ProgressView("Loading polls...") } else if polls.isEmpty { emptyState } else { pollsList } } .navigationTitle("Group Polls") .accessibilityIdentifier("polls.list") .toolbar { ToolbarItem(placement: .primaryAction) { Button { showJoinPoll = true } label: { Image(systemName: "link.badge.plus") .accessibilityLabel("Join a poll") } } } .refreshable { await loadPolls() } .task { guard !hasLoadedInitially else { return } hasLoadedInitially = true await loadPolls() } .alert("Join Poll", isPresented: $showJoinPoll) { TextField("Enter code", text: $joinCode) .textInputAutocapitalization(.characters) Button("Join") { let normalizedCode = joinCode .trimmingCharacters(in: .whitespacesAndNewlines) .uppercased() guard normalizedCode.count == 6 else { errorMessage = "Share code must be exactly 6 characters." return } pendingJoinCode = IdentifiableShareCode(id: normalizedCode) joinCode = "" } Button("Cancel", role: .cancel) { joinCode = "" } } message: { Text("Enter the 6-character poll code") } .navigationDestination(item: $pendingJoinCode) { code in PollDetailView(shareCode: code.value) } .navigationDestination(for: TripPoll.self) { poll in PollDetailView(poll: poll) } .alert( "Error", isPresented: Binding( get: { errorMessage != nil }, set: { if !$0 { errorMessage = nil } } ) ) { Button("OK", role: .cancel) { errorMessage = nil } } message: { Text(errorMessage ?? "Please try again.") } } @ViewBuilder private var emptyState: some View { ContentUnavailableView { Label("No Polls", systemImage: "chart.bar.doc.horizontal") } description: { Text("Create a poll from your saved trips to let friends vote on which trip to take.") } } @ViewBuilder private var pollsList: some View { List { ForEach(polls) { poll in NavigationLink(value: poll) { PollRowView(poll: poll) .contentShape(Rectangle()) } } } .listStyle(.plain) } private func loadPolls() async { isLoading = true errorMessage = nil do { polls = try await PollService.shared.fetchMyPolls() } catch let pollError as PollError { errorMessage = pollError.localizedDescription } catch { self.errorMessage = PollError.unknown(error).localizedDescription } isLoading = false } } // MARK: - Poll Row View private struct PollRowView: View { @Environment(\.colorScheme) private var colorScheme let poll: TripPoll var body: some View { VStack(alignment: .leading, spacing: 8) { HStack { Text(poll.title) .font(.headline) Spacer() Text(poll.shareCode) .font(.caption) .fontWeight(.semibold) .foregroundStyle(Theme.warmOrange) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Theme.warmOrange.opacity(0.15)) .clipShape(Capsule()) } HStack { Label("\(poll.tripSnapshots.count) trips", systemImage: "map") Spacer() Text(poll.createdAt, style: .date) } .font(.caption) .foregroundStyle(.secondary) } .padding(.vertical, 4) } } #Preview { NavigationStack { PollsListView() } }