diff options
| author | Christian Cleberg <[email protected]> | 2026-08-07 04:12:50 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-08-07 04:12:50 -0500 |
| commit | 1f8f651a9c2ac2d6be231ff9f71ee3bcce9936d8 (patch) | |
| tree | 25cf810fa0e9a0ab343b60b59c26d8cc096c9a98 /Hutch/Views/Projects/ProjectsListView.swift | |
| parent | db40d346c28800a1eec384a5e4f3d7d090198e4d (diff) | |
| download | hutch-1f8f651a9c2ac2d6be231ff9f71ee3bcce9936d8.tar.gz hutch-1f8f651a9c2ac2d6be231ff9f71ee3bcce9936d8.tar.bz2 hutch-1f8f651a9c2ac2d6be231ff9f71ee3bcce9936d8.zip | |
Projects: discovery, create, edit, manage linked resources (#12, #13, #14, #15)
hub.sr.ht's GraphQL now exposes the project write API (create/update/
link/unlink) and a public `projects` discovery query, so these
previously-blocked issues can be built.
ProjectService gains: fetchPublicProjects (#12); createProject (#13);
updateProject with ProjectInput (#14); link/unlink for sources, trackers,
and mailing lists plus linkable-resource candidate fetches (#15); and hub
cache invalidation after every mutation.
UI: a Discover browser and a create form reached from the projects list;
an Edit form and a Manage Resources sheet on project detail, gated to
projects the user owns. Mutations degrade to a visible error if a field
isn't live yet.
Built against the master schema; live deployment on sr.ht/query is not yet
confirmed (introspection there requires a token).
Diffstat (limited to 'Hutch/Views/Projects/ProjectsListView.swift')
| -rw-r--r-- | Hutch/Views/Projects/ProjectsListView.swift | 58 |
1 files changed, 56 insertions, 2 deletions
diff --git a/Hutch/Views/Projects/ProjectsListView.swift b/Hutch/Views/Projects/ProjectsListView.swift index 783417c..1a34346 100644 --- a/Hutch/Views/Projects/ProjectsListView.swift +++ b/Hutch/Views/Projects/ProjectsListView.swift @@ -5,15 +5,38 @@ import SwiftUI final class ProjectsListViewModel { private(set) var projects: [Project] = [] private(set) var isLoading = false + private(set) var isSaving = false var error: String? + var saveError: String? var searchText = "" - private let service: ProjectService + let service: ProjectService init(service: ProjectService) { self.service = service } + func createProject(_ values: ProjectFormValues) async -> Bool { + guard !isSaving else { return false } + isSaving = true + saveError = nil + defer { isSaving = false } + + do { + _ = try await service.createProject( + name: values.name, + visibility: values.visibility, + description: values.description.isEmpty ? nil : values.description, + tags: values.tags + ) + await loadProjects(forceRefresh: true) + return true + } catch { + saveError = error.userFacingMessage + return false + } + } + var filteredProjects: [Project] { let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() guard !query.isEmpty else { return projects } @@ -46,11 +69,42 @@ final class ProjectsListViewModel { struct ProjectsListView: View { @Environment(AppState.self) private var appState @State private var viewModel: ProjectsListViewModel? + @State private var isPresentingCreate = false var body: some View { Group { if let viewModel { content(viewModel) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + NavigationLink { + DiscoverProjectsView() + } label: { + Image(systemName: "sparkle.magnifyingglass") + } + .accessibilityLabel("Discover public projects") + } + if appState.currentUser != nil { + ToolbarItem(placement: .topBarTrailing) { + Button { + isPresentingCreate = true + } label: { + Image(systemName: "plus") + } + .accessibilityLabel("Create project") + } + } + } + .sheet(isPresented: $isPresentingCreate) { + ProjectFormSheet( + title: "New Project", + confirmationTitle: "Create", + isSaving: viewModel.isSaving, + error: viewModel.saveError, + includeWebsite: false, + onSave: { await viewModel.createProject($0) } + ) + } } else { SRHTLoadingStateView(message: "Loading projects…") } @@ -70,7 +124,7 @@ struct ProjectsListView: View { List { ForEach(viewModel.filteredProjects) { project in NavigationLink { - ProjectDetailView(project: project) + ProjectDetailView(project: project, canManage: true) } label: { ProjectListRow(project: project) } |
