import SwiftUI struct InviteCodesView: View { @EnvironmentObject var viewModel: PDSViewModel @State private var isCreatingCode = false @State private var isRefreshing = false @State private var showDisabledCodes = false // Split codes into active and disabled var activeCodes: [InviteCode] { return viewModel.inviteCodes.filter { !$0.disabled } } var disabledCodes: [InviteCode] { return viewModel.inviteCodes.filter { $0.disabled } } var filteredCodes: [InviteCode] { if showDisabledCodes { return viewModel.inviteCodes } else { return viewModel.inviteCodes.filter { !$0.disabled } } } var body: some View { NavigationView { Group { if viewModel.isLoading && viewModel.inviteCodes.isEmpty { ProgressView("Loading invite codes...") .frame(maxWidth: .infinity, maxHeight: .infinity) } else if filteredCodes.isEmpty { VStack { Text("No invite codes found") .foregroundColor(.secondary) Button("Refresh") { refreshInviteCodes() } .padding() } .frame(maxWidth: .infinity, maxHeight: .infinity) } else { VStack(spacing: 0) { List { // Active codes section Section(header: Text("Active Codes")) { ForEach(activeCodes) { code in InviteCodeRow(code: code) .contextMenu { Button(role: .destructive) { Task { await viewModel.pdsService.disableInviteCode(code.id) } } label: { Label("Disable Code", systemImage: "xmark.circle") } } } } // Disabled codes section (only shown if toggle is on) if showDisabledCodes && !disabledCodes.isEmpty { Section(header: Text("Disabled Codes")) { ForEach(disabledCodes) { code in InviteCodeRow(code: code) } } } } Toggle("Show Disabled Codes (\(disabledCodes.count))", isOn: $showDisabledCodes) .padding() .background(Color(.systemBackground)) } } } .navigationTitle("Invite Codes") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { isCreatingCode = true Task { await viewModel.pdsService.createInviteCode() isCreatingCode = false } } label: { if isCreatingCode { ProgressView() } else { Label("Create Code", systemImage: "plus") } } .disabled(isCreatingCode) } ToolbarItem(placement: .navigationBarLeading) { Button { refreshInviteCodes() } label: { if isRefreshing { ProgressView() } else { Label("Refresh", systemImage: "arrow.clockwise") } } .disabled(isRefreshing) } } .refreshable { await viewModel.pdsService.fetchInviteCodes() } } .task { // Only fetch if we don't already have data if viewModel.inviteCodes.isEmpty && !viewModel.isLoading { await viewModel.pdsService.fetchInviteCodes() } } .onAppear { // Always attempt to refresh when view appears if !viewModel.isLoading { refreshInviteCodes() } } } private func refreshInviteCodes() { isRefreshing = true Task { await viewModel.pdsService.fetchInviteCodes() isRefreshing = false } } } struct InviteCodeRow: View { let code: InviteCode @State private var copySuccess = false var formattedDate: String { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .short return formatter.string(from: code.createdAt) } var body: some View { VStack(alignment: .leading, spacing: 8) { HStack { Text(code.id) .font(.system(.headline, design: .monospaced)) .foregroundColor(code.disabled ? .gray : .primary) Spacer() if code.disabled { Text("DISABLED") .font(.caption) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.red.opacity(0.2)) .foregroundColor(.red) .cornerRadius(4) } else { Button { UIPasteboard.general.string = code.id withAnimation { copySuccess = true } // Reset the success message after a delay DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { withAnimation { copySuccess = false } } } label: { HStack(spacing: 4) { Image(systemName: copySuccess ? "checkmark" : "doc.on.doc") if copySuccess { Text("Copied!") } } .foregroundColor(copySuccess ? .green : .blue) .padding(.horizontal, 8) .padding(.vertical, 4) .background( (copySuccess ? Color.green : Color.blue) .opacity(0.1) .cornerRadius(4) ) } .buttonStyle(BorderlessButtonStyle()) } } HStack { Text("Created: \(formattedDate)") .font(.caption) .foregroundColor(.secondary) Spacer() if let uses = code.uses { if uses.isEmpty { Text("Unused") .font(.caption) .foregroundColor(.secondary) .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color.green.opacity(0.1)) .cornerRadius(4) } else { Text("Uses: \(uses.count)") .font(.caption) .foregroundColor(.secondary) .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color.blue.opacity(0.1)) .cornerRadius(4) } } } } .padding(.vertical, 6) .opacity(code.disabled ? 0.7 : 1.0) } }