@shipfox/client-projects 24.0.0 → 26.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 28 files with swc (422.77ms)
2
+ Successfully compiled: 27 files with swc (159.19ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @shipfox/client-projects
2
2
 
3
+ ## 26.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 48e8c4a: Wire the workspace setup checklist into the workspace home and top bar, land the first project on the home, and retire the model-provider reminder banner.
8
+ - Updated dependencies [79e1ed7]
9
+ - Updated dependencies [79e1ed7]
10
+ - Updated dependencies [79e1ed7]
11
+ - Updated dependencies [870523f]
12
+ - @shipfox/client-ui@26.0.0
13
+ - @shipfox/react-ui@2.3.0
14
+ - @shipfox/api-definitions-dto@16.1.0
15
+ - @shipfox/client-agent@26.0.0
16
+ - @shipfox/client-auth@26.0.0
17
+ - @shipfox/client-integrations@26.0.0
18
+ - @shipfox/client-shell@26.0.0
19
+
20
+ ## 25.0.0
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies [568c90b]
25
+ - Updated dependencies [117edfd]
26
+ - Updated dependencies [af27652]
27
+ - Updated dependencies [f57bcc3]
28
+ - @shipfox/api-integration-core-dto@16.0.0
29
+ - @shipfox/api-definitions-dto@16.0.0
30
+ - @shipfox/client-shell@25.0.0
31
+ - @shipfox/client-integrations@25.0.0
32
+ - @shipfox/client-agent@25.0.0
33
+ - @shipfox/client-auth@25.0.0
34
+
3
35
  ## 24.0.0
4
36
 
5
37
  ### Patch Changes
@@ -39,6 +39,10 @@ export declare function isProjectSlugAvailable({ queryClient, workspaceId, proje
39
39
  export declare function useProjectSlugAvailability(workspaceId: string | undefined, currentProjectId?: string): (projectSlug: string) => Promise<boolean>;
40
40
  export declare function projectsInfiniteQueryOptions(workspaceId: string | undefined, search?: string, limit?: number): ProjectListInfiniteQueryOptions;
41
41
  export declare function projectExistenceQueryOptions(workspaceId: string | undefined): ProjectExistenceQueryOptions;
42
+ export declare function readWorkspaceHasNoProject({ queryClient, workspaceId, }: {
43
+ queryClient: QueryClient;
44
+ workspaceId: string;
45
+ }): Promise<boolean>;
42
46
  export declare function projectQueryOptions(projectId: string | undefined): ProjectDetailQueryOptions;
43
47
  export declare function projectSlugQueryOptions(workspaceId: string | undefined, projectSlug: string | undefined): ProjectSlugQueryOptions;
44
48
  export declare function useProjectsInfiniteQuery(workspaceId: string | undefined, search?: string, limit?: number): import("@tanstack/react-query").UseInfiniteQueryResult<InfiniteData<ProjectList, string | undefined>, Error>;
@@ -1 +1 @@
1
- {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/projects.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/projects.ts"],"names":[],"mappings":"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"}
@@ -213,6 +213,45 @@ export function projectExistenceQueryOptions(workspaceId) {
213
213
  staleTime: 30_000
214
214
  });
215
215
  }
216
+ export async function readWorkspaceHasNoProject({ queryClient, workspaceId }) {
217
+ const options = projectExistenceQueryOptions(workspaceId);
218
+ // A cached non-empty list already rules out the first-project landing, so
219
+ // skip the fresh read entirely: the snapshot only steers navigation for the
220
+ // empty-workspace case, and a failed fresh read here would otherwise report
221
+ // a global error on every create in an established workspace without
222
+ // changing the landing.
223
+ const cached = queryClient.getQueryData(options.queryKey);
224
+ if (cached !== undefined && cached.projects.length > 0) return false;
225
+ try {
226
+ // The landing decision must not trust fresh-but-stale existence data
227
+ // (e.g. a project created in another tab inside the 30s staleTime window),
228
+ // so force a fresh read for this check. The snapshot also runs without
229
+ // retry so a slow or failing existence endpoint cannot stall the submit
230
+ // handler on the app-default retry backoff; the catch falls back to cache.
231
+ const data = await queryClient.fetchQuery({
232
+ ...options,
233
+ staleTime: 0,
234
+ retry: false
235
+ });
236
+ return data.projects.length === 0;
237
+ } catch (error) {
238
+ reportExistenceReadFailure(error);
239
+ const fallback = queryClient.getQueryData(options.queryKey);
240
+ if (fallback !== undefined) return fallback.projects.length === 0;
241
+ // Unknown existence keeps the existing project navigation.
242
+ return false;
243
+ }
244
+ }
245
+ /**
246
+ * The existence snapshot is a best-effort pre-flight read: a failure must not
247
+ * block project creation, but it still reaches the global error-reporting
248
+ * pipeline so a degrading existence endpoint does not silently misroute the
249
+ * first-project landing.
250
+ */ function reportExistenceReadFailure(error) {
251
+ globalThis.reportError?.(new Error('Failed to read workspace project existence for the first-project landing.', {
252
+ cause: error
253
+ }));
254
+ }
216
255
  export function projectQueryOptions(projectId) {
217
256
  return queryOptions({
218
257
  queryKey: projectId ? projectsQueryKeys.detail(projectId) : [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/projects.ts"],"sourcesContent":["import {\n createProjectBodySchema,\n listProjectsResponseSchema,\n projectResponseSchema,\n updateProjectBodySchema,\n} from '@shipfox/api-projects-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {\n type InfiniteData,\n keepPreviousData,\n type QueryClient,\n queryOptions,\n type UseInfiniteQueryOptions,\n type UseQueryOptions,\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query';\nimport {useCallback} from 'react';\nimport type {\n CreateProjectCommand,\n Project,\n ProjectList,\n UpdateProjectCommand,\n} from '#core/project.js';\nimport {toProject, toProjectList} from './mappers.js';\n\nconst PROJECT_LIST_STALE_TIME = 30_000;\n\nexport const projectsQueryKeys = {\n all: ['projects'] as const,\n list: (workspaceId: string, search = '') =>\n [...projectsQueryKeys.all, 'list', workspaceId, search] as const,\n slug: (workspaceId: string, projectSlug: string) =>\n [...projectsQueryKeys.all, 'slug', workspaceId, projectSlug] as const,\n exists: (workspaceId: string) => [...projectsQueryKeys.all, 'exists', workspaceId] as const,\n detail: (projectId: string) => [...projectsQueryKeys.all, 'detail', projectId] as const,\n};\n\ntype ProjectListQueryKey =\n | ReturnType<typeof projectsQueryKeys.list>\n | readonly ['projects', 'list'];\ntype ProjectExistenceQueryKey =\n | ReturnType<typeof projectsQueryKeys.exists>\n | readonly ['projects', 'exists'];\ntype ProjectSlugQueryKey =\n | ReturnType<typeof projectsQueryKeys.slug>\n | readonly ['projects', 'slug'];\ntype ProjectDetailQueryKey =\n | ReturnType<typeof projectsQueryKeys.detail>\n | readonly ['projects', 'detail'];\n\ntype ProjectListInfiniteQueryOptions = UseInfiniteQueryOptions<\n ProjectList,\n Error,\n InfiniteData<ProjectList, string | undefined>,\n ProjectListQueryKey,\n string | undefined\n>;\ntype ProjectExistenceQueryOptions = UseQueryOptions<\n ProjectList,\n Error,\n ProjectList,\n ProjectExistenceQueryKey\n>;\ntype ProjectSlugQueryOptions = UseQueryOptions<\n Project | null,\n Error,\n Project | null,\n ProjectSlugQueryKey\n>;\ntype ProjectDetailQueryOptions = UseQueryOptions<Project, Error, Project, ProjectDetailQueryKey>;\n\nexport async function listProjects({\n workspaceId,\n limit = 50,\n cursor,\n search,\n signal,\n}: {\n workspaceId: string;\n limit?: number;\n cursor?: string | undefined;\n search?: string | undefined;\n signal?: AbortSignal | undefined;\n}): Promise<ProjectList> {\n const params = new URLSearchParams({workspace_id: workspaceId, limit: String(limit)});\n if (cursor) params.set('cursor', cursor);\n if (search) params.set('search', search);\n return toProjectList(\n await checkedApiRequest(listProjectsResponseSchema, `/projects?${params.toString()}`, {signal}),\n );\n}\n\nexport async function resolveProjectSlug({\n queryClient,\n workspaceId,\n projectSlug,\n}: {\n queryClient: QueryClient;\n workspaceId: string;\n projectSlug: string;\n}): Promise<string | undefined> {\n const queryKey = projectsQueryKeys.list(workspaceId);\n let data = queryClient.getQueryData<InfiniteData<ProjectList, string | undefined>>(queryKey);\n\n if (!data) {\n await queryClient.fetchInfiniteQuery(projectsInfiniteQueryOptions(workspaceId));\n data = queryClient.getQueryData<InfiniteData<ProjectList, string | undefined>>(queryKey);\n }\n\n if (data) {\n const project = data.pages\n .flatMap((page) => page.projects)\n .find((candidate) => candidate.slug === projectSlug);\n const dataUpdatedAt = queryClient.getQueryState(queryKey)?.dataUpdatedAt ?? 0;\n if (project && Date.now() - dataUpdatedAt < PROJECT_LIST_STALE_TIME) {\n cacheResolvedProject(queryClient, workspaceId, projectSlug, project);\n return project.id;\n }\n const cursor = data.pages.at(-1)?.nextCursor;\n if (project || !cursor) {\n return await refetchAndResolveProjectSlug(queryClient, queryKey, workspaceId, projectSlug);\n }\n return await resolveProjectSlugBySearch(queryClient, workspaceId, projectSlug);\n }\n\n return undefined;\n}\n\nasync function refetchAndResolveProjectSlug(\n queryClient: QueryClient,\n queryKey: ReturnType<typeof projectsQueryKeys.list>,\n workspaceId: string,\n projectSlug: string,\n): Promise<string | undefined> {\n await queryClient.refetchQueries({queryKey, type: 'all'});\n const data = queryClient.getQueryData<InfiniteData<ProjectList, string | undefined>>(queryKey);\n\n if (!data) return undefined;\n const project = data.pages\n .flatMap((page) => page.projects)\n .find((candidate) => candidate.slug === projectSlug);\n if (project) {\n cacheResolvedProject(queryClient, workspaceId, projectSlug, project);\n return project.id;\n }\n\n if (!data.pages.at(-1)?.nextCursor) return undefined;\n return await resolveProjectSlugBySearch(queryClient, workspaceId, projectSlug);\n}\n\nfunction cacheResolvedProject(\n queryClient: QueryClient,\n workspaceId: string,\n projectSlug: string,\n project: Project,\n): void {\n queryClient.setQueryData(projectsQueryKeys.slug(workspaceId, projectSlug), project);\n queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);\n}\n\nasync function resolveProjectSlugBySearch(\n queryClient: QueryClient,\n workspaceId: string,\n projectSlug: string,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n const project = await findProjectBySlug({workspaceId, projectSlug, signal});\n if (project) {\n cacheResolvedProject(queryClient, workspaceId, projectSlug, project);\n return project.id;\n }\n return undefined;\n}\n\nasync function findProjectBySlug({\n workspaceId,\n projectSlug,\n signal,\n}: {\n workspaceId: string;\n projectSlug: string;\n signal?: AbortSignal | undefined;\n}): Promise<Project | undefined> {\n let result = await listProjects({workspaceId, search: projectSlug, limit: 100, signal});\n while (true) {\n const project = result.projects.find((candidate) => candidate.slug === projectSlug);\n if (project) return project;\n if (!result.nextCursor) return undefined;\n result = await listProjects({\n workspaceId,\n search: projectSlug,\n limit: 100,\n cursor: result.nextCursor,\n signal,\n });\n }\n}\n\nexport async function getProject(projectId: string): Promise<Project> {\n return toProject(await checkedApiRequest(projectResponseSchema, `/projects/${projectId}`));\n}\n\nexport async function createProject(command: CreateProjectCommand): Promise<Project> {\n const body = createProjectBodySchema.parse({\n workspace_id: command.workspaceId,\n name: command.name,\n slug: command.slug,\n source: {\n connection_id: command.source.connectionId,\n external_repository_id: command.source.externalRepositoryId,\n },\n });\n return toProject(\n await checkedApiRequest(projectResponseSchema, '/projects', {method: 'POST', body}),\n );\n}\n\nexport async function updateProject(command: UpdateProjectCommand): Promise<Project> {\n const body = updateProjectBodySchema.parse({name: command.name, slug: command.slug});\n return toProject(\n await checkedApiRequest(projectResponseSchema, `/projects/${command.projectId}`, {\n method: 'PATCH',\n body,\n }),\n );\n}\n\nexport function isProjectSlugAvailable({\n queryClient,\n workspaceId,\n projectSlug,\n currentProjectId,\n}: {\n queryClient: QueryClient;\n workspaceId: string;\n projectSlug: string;\n currentProjectId?: string | undefined;\n}): boolean {\n const cachedLists = queryClient.getQueriesData<InfiniteData<ProjectList, string | undefined>>({\n queryKey: [...projectsQueryKeys.all, 'list', workspaceId],\n });\n const conflict = cachedLists\n .flatMap(([, data]) => data?.pages.flatMap((page) => page.projects) ?? [])\n .some((project) => project.slug === projectSlug && project.id !== currentProjectId);\n return !conflict;\n}\n\nexport function useProjectSlugAvailability(\n workspaceId: string | undefined,\n currentProjectId?: string,\n): (projectSlug: string) => Promise<boolean> {\n const queryClient = useQueryClient();\n return useCallback(\n async (projectSlug: string) => {\n if (!workspaceId) return false;\n if (!isProjectSlugAvailable({queryClient, workspaceId, projectSlug, currentProjectId})) {\n return false;\n }\n const project = await findProjectBySlug({workspaceId, projectSlug});\n return project === undefined || project.id === currentProjectId;\n },\n [currentProjectId, queryClient, workspaceId],\n );\n}\n\nexport function projectsInfiniteQueryOptions(\n workspaceId: string | undefined,\n search?: string,\n limit = 50,\n): ProjectListInfiniteQueryOptions {\n const normalizedSearch = search?.trim() ?? '';\n return {\n queryKey: workspaceId\n ? projectsQueryKeys.list(workspaceId, normalizedSearch)\n : ([...projectsQueryKeys.all, 'list'] as const),\n enabled: Boolean(workspaceId),\n initialPageParam: undefined as string | undefined,\n queryFn: ({pageParam, signal}: {pageParam: string | undefined; signal: AbortSignal}) =>\n listProjects({\n workspaceId: workspaceId ?? '',\n limit,\n cursor: pageParam,\n search: normalizedSearch || undefined,\n signal,\n }),\n getNextPageParam: (lastPage: ProjectList) => lastPage.nextCursor ?? undefined,\n placeholderData: keepPreviousData,\n staleTime: PROJECT_LIST_STALE_TIME,\n };\n}\n\nexport function projectExistenceQueryOptions(\n workspaceId: string | undefined,\n): ProjectExistenceQueryOptions {\n return queryOptions({\n queryKey: workspaceId\n ? projectsQueryKeys.exists(workspaceId)\n : ([...projectsQueryKeys.all, 'exists'] as const),\n enabled: Boolean(workspaceId),\n queryFn: ({signal}) => listProjects({workspaceId: workspaceId ?? '', limit: 1, signal}),\n staleTime: 30_000,\n });\n}\n\nexport function projectQueryOptions(projectId: string | undefined): ProjectDetailQueryOptions {\n return queryOptions({\n queryKey: projectId\n ? projectsQueryKeys.detail(projectId)\n : ([...projectsQueryKeys.all, 'detail'] as const),\n enabled: Boolean(projectId),\n queryFn: () => getProject(projectId ?? ''),\n });\n}\n\nexport function projectSlugQueryOptions(\n workspaceId: string | undefined,\n projectSlug: string | undefined,\n): ProjectSlugQueryOptions {\n return queryOptions({\n queryKey:\n workspaceId && projectSlug\n ? projectsQueryKeys.slug(workspaceId, projectSlug)\n : ([...projectsQueryKeys.all, 'slug'] as const),\n enabled: Boolean(workspaceId && projectSlug),\n queryFn: async ({signal, client}) => {\n const resolvedWorkspaceId = workspaceId ?? '';\n const resolvedProjectSlug = projectSlug ?? '';\n const project = await findProjectBySlug({\n workspaceId: resolvedWorkspaceId,\n projectSlug: resolvedProjectSlug,\n signal,\n });\n if (project) {\n cacheResolvedProject(client, resolvedWorkspaceId, resolvedProjectSlug, project);\n }\n return project ?? null;\n },\n staleTime: 30_000,\n });\n}\n\nexport function useProjectsInfiniteQuery(\n workspaceId: string | undefined,\n search?: string,\n limit = 50,\n) {\n return useInfiniteQuery(projectsInfiniteQueryOptions(workspaceId, search, limit));\n}\nexport function useProjectQuery(projectId: string | undefined) {\n return useQuery(projectQueryOptions(projectId));\n}\nexport function useProjectSlugQuery(\n workspaceId: string | undefined,\n projectSlug: string | undefined,\n) {\n return useQuery(projectSlugQueryOptions(workspaceId, projectSlug));\n}\nexport function useCreateProjectMutation() {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: createProject,\n onSuccess: async (project) => {\n queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);\n queryClient.setQueryData<ProjectList>(projectsQueryKeys.exists(project.workspaceId), {\n projects: [project],\n nextCursor: null,\n });\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(project.workspaceId)});\n },\n onError: async (_error, command) => {\n await queryClient.invalidateQueries({\n queryKey: projectsQueryKeys.exists(command.workspaceId),\n refetchType: 'active',\n });\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(command.workspaceId)});\n },\n });\n}\n\nexport function useUpdateProjectMutation() {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: updateProject,\n onSuccess: async (project) => {\n queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);\n queryClient.setQueryData(projectsQueryKeys.slug(project.workspaceId, project.slug), project);\n await queryClient.invalidateQueries({\n queryKey: [...projectsQueryKeys.all, 'slug', project.workspaceId],\n });\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(project.workspaceId)});\n },\n });\n}\n"],"names":["createProjectBodySchema","listProjectsResponseSchema","projectResponseSchema","updateProjectBodySchema","checkedApiRequest","keepPreviousData","queryOptions","useInfiniteQuery","useMutation","useQuery","useQueryClient","useCallback","toProject","toProjectList","PROJECT_LIST_STALE_TIME","projectsQueryKeys","all","list","workspaceId","search","slug","projectSlug","exists","detail","projectId","listProjects","limit","cursor","signal","params","URLSearchParams","workspace_id","String","set","toString","resolveProjectSlug","queryClient","queryKey","data","getQueryData","fetchInfiniteQuery","projectsInfiniteQueryOptions","project","pages","flatMap","page","projects","find","candidate","dataUpdatedAt","getQueryState","Date","now","cacheResolvedProject","id","at","nextCursor","refetchAndResolveProjectSlug","resolveProjectSlugBySearch","undefined","refetchQueries","type","setQueryData","findProjectBySlug","result","getProject","createProject","command","body","parse","name","source","connection_id","connectionId","external_repository_id","externalRepositoryId","method","updateProject","isProjectSlugAvailable","currentProjectId","cachedLists","getQueriesData","conflict","some","useProjectSlugAvailability","normalizedSearch","trim","enabled","Boolean","initialPageParam","queryFn","pageParam","getNextPageParam","lastPage","placeholderData","staleTime","projectExistenceQueryOptions","projectQueryOptions","projectSlugQueryOptions","client","resolvedWorkspaceId","resolvedProjectSlug","useProjectsInfiniteQuery","useProjectQuery","useProjectSlugQuery","useCreateProjectMutation","mutationFn","onSuccess","invalidateQueries","onError","_error","refetchType","useUpdateProjectMutation"],"mappings":"AAAA,SACEA,uBAAuB,EACvBC,0BAA0B,EAC1BC,qBAAqB,EACrBC,uBAAuB,QAClB,4BAA4B;AACnC,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAEEC,gBAAgB,EAEhBC,YAAY,EAGZC,gBAAgB,EAChBC,WAAW,EACXC,QAAQ,EACRC,cAAc,QACT,wBAAwB;AAC/B,SAAQC,WAAW,QAAO,QAAQ;AAOlC,SAAQC,SAAS,EAAEC,aAAa,QAAO,eAAe;AAEtD,MAAMC,0BAA0B;AAEhC,OAAO,MAAMC,oBAAoB;IAC/BC,KAAK;QAAC;KAAW;IACjBC,MAAM,CAACC,aAAqBC,SAAS,EAAE,GACrC;eAAIJ,kBAAkBC,GAAG;YAAE;YAAQE;YAAaC;SAAO;IACzDC,MAAM,CAACF,aAAqBG,cAC1B;eAAIN,kBAAkBC,GAAG;YAAE;YAAQE;YAAaG;SAAY;IAC9DC,QAAQ,CAACJ,cAAwB;eAAIH,kBAAkBC,GAAG;YAAE;YAAUE;SAAY;IAClFK,QAAQ,CAACC,YAAsB;eAAIT,kBAAkBC,GAAG;YAAE;YAAUQ;SAAU;AAChF,EAAE;AAoCF,OAAO,eAAeC,aAAa,EACjCP,WAAW,EACXQ,QAAQ,EAAE,EACVC,MAAM,EACNR,MAAM,EACNS,MAAM,EAOP;IACC,MAAMC,SAAS,IAAIC,gBAAgB;QAACC,cAAcb;QAAaQ,OAAOM,OAAON;IAAM;IACnF,IAAIC,QAAQE,OAAOI,GAAG,CAAC,UAAUN;IACjC,IAAIR,QAAQU,OAAOI,GAAG,CAAC,UAAUd;IACjC,OAAON,cACL,MAAMT,kBAAkBH,4BAA4B,CAAC,UAAU,EAAE4B,OAAOK,QAAQ,IAAI,EAAE;QAACN;IAAM;AAEjG;AAEA,OAAO,eAAeO,mBAAmB,EACvCC,WAAW,EACXlB,WAAW,EACXG,WAAW,EAKZ;IACC,MAAMgB,WAAWtB,kBAAkBE,IAAI,CAACC;IACxC,IAAIoB,OAAOF,YAAYG,YAAY,CAAgDF;IAEnF,IAAI,CAACC,MAAM;QACT,MAAMF,YAAYI,kBAAkB,CAACC,6BAA6BvB;QAClEoB,OAAOF,YAAYG,YAAY,CAAgDF;IACjF;IAEA,IAAIC,MAAM;QACR,MAAMI,UAAUJ,KAAKK,KAAK,CACvBC,OAAO,CAAC,CAACC,OAASA,KAAKC,QAAQ,EAC/BC,IAAI,CAAC,CAACC,YAAcA,UAAU5B,IAAI,KAAKC;QAC1C,MAAM4B,gBAAgBb,YAAYc,aAAa,CAACb,WAAWY,iBAAiB;QAC5E,IAAIP,WAAWS,KAAKC,GAAG,KAAKH,gBAAgBnC,yBAAyB;YACnEuC,qBAAqBjB,aAAalB,aAAaG,aAAaqB;YAC5D,OAAOA,QAAQY,EAAE;QACnB;QACA,MAAM3B,SAASW,KAAKK,KAAK,CAACY,EAAE,CAAC,CAAC,IAAIC;QAClC,IAAId,WAAW,CAACf,QAAQ;YACtB,OAAO,MAAM8B,6BAA6BrB,aAAaC,UAAUnB,aAAaG;QAChF;QACA,OAAO,MAAMqC,2BAA2BtB,aAAalB,aAAaG;IACpE;IAEA,OAAOsC;AACT;AAEA,eAAeF,6BACbrB,WAAwB,EACxBC,QAAmD,EACnDnB,WAAmB,EACnBG,WAAmB;IAEnB,MAAMe,YAAYwB,cAAc,CAAC;QAACvB;QAAUwB,MAAM;IAAK;IACvD,MAAMvB,OAAOF,YAAYG,YAAY,CAAgDF;IAErF,IAAI,CAACC,MAAM,OAAOqB;IAClB,MAAMjB,UAAUJ,KAAKK,KAAK,CACvBC,OAAO,CAAC,CAACC,OAASA,KAAKC,QAAQ,EAC/BC,IAAI,CAAC,CAACC,YAAcA,UAAU5B,IAAI,KAAKC;IAC1C,IAAIqB,SAAS;QACXW,qBAAqBjB,aAAalB,aAAaG,aAAaqB;QAC5D,OAAOA,QAAQY,EAAE;IACnB;IAEA,IAAI,CAAChB,KAAKK,KAAK,CAACY,EAAE,CAAC,CAAC,IAAIC,YAAY,OAAOG;IAC3C,OAAO,MAAMD,2BAA2BtB,aAAalB,aAAaG;AACpE;AAEA,SAASgC,qBACPjB,WAAwB,EACxBlB,WAAmB,EACnBG,WAAmB,EACnBqB,OAAgB;IAEhBN,YAAY0B,YAAY,CAAC/C,kBAAkBK,IAAI,CAACF,aAAaG,cAAcqB;IAC3EN,YAAY0B,YAAY,CAAC/C,kBAAkBQ,MAAM,CAACmB,QAAQY,EAAE,GAAGZ;AACjE;AAEA,eAAegB,2BACbtB,WAAwB,EACxBlB,WAAmB,EACnBG,WAAmB,EACnBO,MAAoB;IAEpB,MAAMc,UAAU,MAAMqB,kBAAkB;QAAC7C;QAAaG;QAAaO;IAAM;IACzE,IAAIc,SAAS;QACXW,qBAAqBjB,aAAalB,aAAaG,aAAaqB;QAC5D,OAAOA,QAAQY,EAAE;IACnB;IACA,OAAOK;AACT;AAEA,eAAeI,kBAAkB,EAC/B7C,WAAW,EACXG,WAAW,EACXO,MAAM,EAKP;IACC,IAAIoC,SAAS,MAAMvC,aAAa;QAACP;QAAaC,QAAQE;QAAaK,OAAO;QAAKE;IAAM;IACrF,MAAO,KAAM;QACX,MAAMc,UAAUsB,OAAOlB,QAAQ,CAACC,IAAI,CAAC,CAACC,YAAcA,UAAU5B,IAAI,KAAKC;QACvE,IAAIqB,SAAS,OAAOA;QACpB,IAAI,CAACsB,OAAOR,UAAU,EAAE,OAAOG;QAC/BK,SAAS,MAAMvC,aAAa;YAC1BP;YACAC,QAAQE;YACRK,OAAO;YACPC,QAAQqC,OAAOR,UAAU;YACzB5B;QACF;IACF;AACF;AAEA,OAAO,eAAeqC,WAAWzC,SAAiB;IAChD,OAAOZ,UAAU,MAAMR,kBAAkBF,uBAAuB,CAAC,UAAU,EAAEsB,WAAW;AAC1F;AAEA,OAAO,eAAe0C,cAAcC,OAA6B;IAC/D,MAAMC,OAAOpE,wBAAwBqE,KAAK,CAAC;QACzCtC,cAAcoC,QAAQjD,WAAW;QACjCoD,MAAMH,QAAQG,IAAI;QAClBlD,MAAM+C,QAAQ/C,IAAI;QAClBmD,QAAQ;YACNC,eAAeL,QAAQI,MAAM,CAACE,YAAY;YAC1CC,wBAAwBP,QAAQI,MAAM,CAACI,oBAAoB;QAC7D;IACF;IACA,OAAO/D,UACL,MAAMR,kBAAkBF,uBAAuB,aAAa;QAAC0E,QAAQ;QAAQR;IAAI;AAErF;AAEA,OAAO,eAAeS,cAAcV,OAA6B;IAC/D,MAAMC,OAAOjE,wBAAwBkE,KAAK,CAAC;QAACC,MAAMH,QAAQG,IAAI;QAAElD,MAAM+C,QAAQ/C,IAAI;IAAA;IAClF,OAAOR,UACL,MAAMR,kBAAkBF,uBAAuB,CAAC,UAAU,EAAEiE,QAAQ3C,SAAS,EAAE,EAAE;QAC/EoD,QAAQ;QACRR;IACF;AAEJ;AAEA,OAAO,SAASU,uBAAuB,EACrC1C,WAAW,EACXlB,WAAW,EACXG,WAAW,EACX0D,gBAAgB,EAMjB;IACC,MAAMC,cAAc5C,YAAY6C,cAAc,CAAgD;QAC5F5C,UAAU;eAAItB,kBAAkBC,GAAG;YAAE;YAAQE;SAAY;IAC3D;IACA,MAAMgE,WAAWF,YACdpC,OAAO,CAAC,CAAC,GAAGN,KAAK,GAAKA,MAAMK,MAAMC,QAAQ,CAACC,OAASA,KAAKC,QAAQ,KAAK,EAAE,EACxEqC,IAAI,CAAC,CAACzC,UAAYA,QAAQtB,IAAI,KAAKC,eAAeqB,QAAQY,EAAE,KAAKyB;IACpE,OAAO,CAACG;AACV;AAEA,OAAO,SAASE,2BACdlE,WAA+B,EAC/B6D,gBAAyB;IAEzB,MAAM3C,cAAc1B;IACpB,OAAOC,YACL,OAAOU;QACL,IAAI,CAACH,aAAa,OAAO;QACzB,IAAI,CAAC4D,uBAAuB;YAAC1C;YAAalB;YAAaG;YAAa0D;QAAgB,IAAI;YACtF,OAAO;QACT;QACA,MAAMrC,UAAU,MAAMqB,kBAAkB;YAAC7C;YAAaG;QAAW;QACjE,OAAOqB,YAAYiB,aAAajB,QAAQY,EAAE,KAAKyB;IACjD,GACA;QAACA;QAAkB3C;QAAalB;KAAY;AAEhD;AAEA,OAAO,SAASuB,6BACdvB,WAA+B,EAC/BC,MAAe,EACfO,QAAQ,EAAE;IAEV,MAAM2D,mBAAmBlE,QAAQmE,UAAU;IAC3C,OAAO;QACLjD,UAAUnB,cACNH,kBAAkBE,IAAI,CAACC,aAAamE,oBACnC;eAAItE,kBAAkBC,GAAG;YAAE;SAAO;QACvCuE,SAASC,QAAQtE;QACjBuE,kBAAkB9B;QAClB+B,SAAS,CAAC,EAACC,SAAS,EAAE/D,MAAM,EAAuD,GACjFH,aAAa;gBACXP,aAAaA,eAAe;gBAC5BQ;gBACAC,QAAQgE;gBACRxE,QAAQkE,oBAAoB1B;gBAC5B/B;YACF;QACFgE,kBAAkB,CAACC,WAA0BA,SAASrC,UAAU,IAAIG;QACpEmC,iBAAiBzF;QACjB0F,WAAWjF;IACb;AACF;AAEA,OAAO,SAASkF,6BACd9E,WAA+B;IAE/B,OAAOZ,aAAa;QAClB+B,UAAUnB,cACNH,kBAAkBO,MAAM,CAACJ,eACxB;eAAIH,kBAAkBC,GAAG;YAAE;SAAS;QACzCuE,SAASC,QAAQtE;QACjBwE,SAAS,CAAC,EAAC9D,MAAM,EAAC,GAAKH,aAAa;gBAACP,aAAaA,eAAe;gBAAIQ,OAAO;gBAAGE;YAAM;QACrFmE,WAAW;IACb;AACF;AAEA,OAAO,SAASE,oBAAoBzE,SAA6B;IAC/D,OAAOlB,aAAa;QAClB+B,UAAUb,YACNT,kBAAkBQ,MAAM,CAACC,aACxB;eAAIT,kBAAkBC,GAAG;YAAE;SAAS;QACzCuE,SAASC,QAAQhE;QACjBkE,SAAS,IAAMzB,WAAWzC,aAAa;IACzC;AACF;AAEA,OAAO,SAAS0E,wBACdhF,WAA+B,EAC/BG,WAA+B;IAE/B,OAAOf,aAAa;QAClB+B,UACEnB,eAAeG,cACXN,kBAAkBK,IAAI,CAACF,aAAaG,eACnC;eAAIN,kBAAkBC,GAAG;YAAE;SAAO;QACzCuE,SAASC,QAAQtE,eAAeG;QAChCqE,SAAS,OAAO,EAAC9D,MAAM,EAAEuE,MAAM,EAAC;YAC9B,MAAMC,sBAAsBlF,eAAe;YAC3C,MAAMmF,sBAAsBhF,eAAe;YAC3C,MAAMqB,UAAU,MAAMqB,kBAAkB;gBACtC7C,aAAakF;gBACb/E,aAAagF;gBACbzE;YACF;YACA,IAAIc,SAAS;gBACXW,qBAAqB8C,QAAQC,qBAAqBC,qBAAqB3D;YACzE;YACA,OAAOA,WAAW;QACpB;QACAqD,WAAW;IACb;AACF;AAEA,OAAO,SAASO,yBACdpF,WAA+B,EAC/BC,MAAe,EACfO,QAAQ,EAAE;IAEV,OAAOnB,iBAAiBkC,6BAA6BvB,aAAaC,QAAQO;AAC5E;AACA,OAAO,SAAS6E,gBAAgB/E,SAA6B;IAC3D,OAAOf,SAASwF,oBAAoBzE;AACtC;AACA,OAAO,SAASgF,oBACdtF,WAA+B,EAC/BG,WAA+B;IAE/B,OAAOZ,SAASyF,wBAAwBhF,aAAaG;AACvD;AACA,OAAO,SAASoF;IACd,MAAMrE,cAAc1B;IACpB,OAAOF,YAAY;QACjBkG,YAAYxC;QACZyC,WAAW,OAAOjE;YAChBN,YAAY0B,YAAY,CAAC/C,kBAAkBQ,MAAM,CAACmB,QAAQY,EAAE,GAAGZ;YAC/DN,YAAY0B,YAAY,CAAc/C,kBAAkBO,MAAM,CAACoB,QAAQxB,WAAW,GAAG;gBACnF4B,UAAU;oBAACJ;iBAAQ;gBACnBc,YAAY;YACd;YACA,MAAMpB,YAAYwE,iBAAiB,CAAC;gBAACvE,UAAUtB,kBAAkBE,IAAI,CAACyB,QAAQxB,WAAW;YAAC;QAC5F;QACA2F,SAAS,OAAOC,QAAQ3C;YACtB,MAAM/B,YAAYwE,iBAAiB,CAAC;gBAClCvE,UAAUtB,kBAAkBO,MAAM,CAAC6C,QAAQjD,WAAW;gBACtD6F,aAAa;YACf;YACA,MAAM3E,YAAYwE,iBAAiB,CAAC;gBAACvE,UAAUtB,kBAAkBE,IAAI,CAACkD,QAAQjD,WAAW;YAAC;QAC5F;IACF;AACF;AAEA,OAAO,SAAS8F;IACd,MAAM5E,cAAc1B;IAEpB,OAAOF,YAAY;QACjBkG,YAAY7B;QACZ8B,WAAW,OAAOjE;YAChBN,YAAY0B,YAAY,CAAC/C,kBAAkBQ,MAAM,CAACmB,QAAQY,EAAE,GAAGZ;YAC/DN,YAAY0B,YAAY,CAAC/C,kBAAkBK,IAAI,CAACsB,QAAQxB,WAAW,EAAEwB,QAAQtB,IAAI,GAAGsB;YACpF,MAAMN,YAAYwE,iBAAiB,CAAC;gBAClCvE,UAAU;uBAAItB,kBAAkBC,GAAG;oBAAE;oBAAQ0B,QAAQxB,WAAW;iBAAC;YACnE;YACA,MAAMkB,YAAYwE,iBAAiB,CAAC;gBAACvE,UAAUtB,kBAAkBE,IAAI,CAACyB,QAAQxB,WAAW;YAAC;QAC5F;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/hooks/api/projects.ts"],"sourcesContent":["import {\n createProjectBodySchema,\n listProjectsResponseSchema,\n projectResponseSchema,\n updateProjectBodySchema,\n} from '@shipfox/api-projects-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {\n type InfiniteData,\n keepPreviousData,\n type QueryClient,\n queryOptions,\n type UseInfiniteQueryOptions,\n type UseQueryOptions,\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query';\nimport {useCallback} from 'react';\nimport type {\n CreateProjectCommand,\n Project,\n ProjectList,\n UpdateProjectCommand,\n} from '#core/project.js';\nimport {toProject, toProjectList} from './mappers.js';\n\nconst PROJECT_LIST_STALE_TIME = 30_000;\n\nexport const projectsQueryKeys = {\n all: ['projects'] as const,\n list: (workspaceId: string, search = '') =>\n [...projectsQueryKeys.all, 'list', workspaceId, search] as const,\n slug: (workspaceId: string, projectSlug: string) =>\n [...projectsQueryKeys.all, 'slug', workspaceId, projectSlug] as const,\n exists: (workspaceId: string) => [...projectsQueryKeys.all, 'exists', workspaceId] as const,\n detail: (projectId: string) => [...projectsQueryKeys.all, 'detail', projectId] as const,\n};\n\ntype ProjectListQueryKey =\n | ReturnType<typeof projectsQueryKeys.list>\n | readonly ['projects', 'list'];\ntype ProjectExistenceQueryKey =\n | ReturnType<typeof projectsQueryKeys.exists>\n | readonly ['projects', 'exists'];\ntype ProjectSlugQueryKey =\n | ReturnType<typeof projectsQueryKeys.slug>\n | readonly ['projects', 'slug'];\ntype ProjectDetailQueryKey =\n | ReturnType<typeof projectsQueryKeys.detail>\n | readonly ['projects', 'detail'];\n\ntype ProjectListInfiniteQueryOptions = UseInfiniteQueryOptions<\n ProjectList,\n Error,\n InfiniteData<ProjectList, string | undefined>,\n ProjectListQueryKey,\n string | undefined\n>;\ntype ProjectExistenceQueryOptions = UseQueryOptions<\n ProjectList,\n Error,\n ProjectList,\n ProjectExistenceQueryKey\n>;\ntype ProjectSlugQueryOptions = UseQueryOptions<\n Project | null,\n Error,\n Project | null,\n ProjectSlugQueryKey\n>;\ntype ProjectDetailQueryOptions = UseQueryOptions<Project, Error, Project, ProjectDetailQueryKey>;\n\nexport async function listProjects({\n workspaceId,\n limit = 50,\n cursor,\n search,\n signal,\n}: {\n workspaceId: string;\n limit?: number;\n cursor?: string | undefined;\n search?: string | undefined;\n signal?: AbortSignal | undefined;\n}): Promise<ProjectList> {\n const params = new URLSearchParams({workspace_id: workspaceId, limit: String(limit)});\n if (cursor) params.set('cursor', cursor);\n if (search) params.set('search', search);\n return toProjectList(\n await checkedApiRequest(listProjectsResponseSchema, `/projects?${params.toString()}`, {signal}),\n );\n}\n\nexport async function resolveProjectSlug({\n queryClient,\n workspaceId,\n projectSlug,\n}: {\n queryClient: QueryClient;\n workspaceId: string;\n projectSlug: string;\n}): Promise<string | undefined> {\n const queryKey = projectsQueryKeys.list(workspaceId);\n let data = queryClient.getQueryData<InfiniteData<ProjectList, string | undefined>>(queryKey);\n\n if (!data) {\n await queryClient.fetchInfiniteQuery(projectsInfiniteQueryOptions(workspaceId));\n data = queryClient.getQueryData<InfiniteData<ProjectList, string | undefined>>(queryKey);\n }\n\n if (data) {\n const project = data.pages\n .flatMap((page) => page.projects)\n .find((candidate) => candidate.slug === projectSlug);\n const dataUpdatedAt = queryClient.getQueryState(queryKey)?.dataUpdatedAt ?? 0;\n if (project && Date.now() - dataUpdatedAt < PROJECT_LIST_STALE_TIME) {\n cacheResolvedProject(queryClient, workspaceId, projectSlug, project);\n return project.id;\n }\n const cursor = data.pages.at(-1)?.nextCursor;\n if (project || !cursor) {\n return await refetchAndResolveProjectSlug(queryClient, queryKey, workspaceId, projectSlug);\n }\n return await resolveProjectSlugBySearch(queryClient, workspaceId, projectSlug);\n }\n\n return undefined;\n}\n\nasync function refetchAndResolveProjectSlug(\n queryClient: QueryClient,\n queryKey: ReturnType<typeof projectsQueryKeys.list>,\n workspaceId: string,\n projectSlug: string,\n): Promise<string | undefined> {\n await queryClient.refetchQueries({queryKey, type: 'all'});\n const data = queryClient.getQueryData<InfiniteData<ProjectList, string | undefined>>(queryKey);\n\n if (!data) return undefined;\n const project = data.pages\n .flatMap((page) => page.projects)\n .find((candidate) => candidate.slug === projectSlug);\n if (project) {\n cacheResolvedProject(queryClient, workspaceId, projectSlug, project);\n return project.id;\n }\n\n if (!data.pages.at(-1)?.nextCursor) return undefined;\n return await resolveProjectSlugBySearch(queryClient, workspaceId, projectSlug);\n}\n\nfunction cacheResolvedProject(\n queryClient: QueryClient,\n workspaceId: string,\n projectSlug: string,\n project: Project,\n): void {\n queryClient.setQueryData(projectsQueryKeys.slug(workspaceId, projectSlug), project);\n queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);\n}\n\nasync function resolveProjectSlugBySearch(\n queryClient: QueryClient,\n workspaceId: string,\n projectSlug: string,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n const project = await findProjectBySlug({workspaceId, projectSlug, signal});\n if (project) {\n cacheResolvedProject(queryClient, workspaceId, projectSlug, project);\n return project.id;\n }\n return undefined;\n}\n\nasync function findProjectBySlug({\n workspaceId,\n projectSlug,\n signal,\n}: {\n workspaceId: string;\n projectSlug: string;\n signal?: AbortSignal | undefined;\n}): Promise<Project | undefined> {\n let result = await listProjects({workspaceId, search: projectSlug, limit: 100, signal});\n while (true) {\n const project = result.projects.find((candidate) => candidate.slug === projectSlug);\n if (project) return project;\n if (!result.nextCursor) return undefined;\n result = await listProjects({\n workspaceId,\n search: projectSlug,\n limit: 100,\n cursor: result.nextCursor,\n signal,\n });\n }\n}\n\nexport async function getProject(projectId: string): Promise<Project> {\n return toProject(await checkedApiRequest(projectResponseSchema, `/projects/${projectId}`));\n}\n\nexport async function createProject(command: CreateProjectCommand): Promise<Project> {\n const body = createProjectBodySchema.parse({\n workspace_id: command.workspaceId,\n name: command.name,\n slug: command.slug,\n source: {\n connection_id: command.source.connectionId,\n external_repository_id: command.source.externalRepositoryId,\n },\n });\n return toProject(\n await checkedApiRequest(projectResponseSchema, '/projects', {method: 'POST', body}),\n );\n}\n\nexport async function updateProject(command: UpdateProjectCommand): Promise<Project> {\n const body = updateProjectBodySchema.parse({name: command.name, slug: command.slug});\n return toProject(\n await checkedApiRequest(projectResponseSchema, `/projects/${command.projectId}`, {\n method: 'PATCH',\n body,\n }),\n );\n}\n\nexport function isProjectSlugAvailable({\n queryClient,\n workspaceId,\n projectSlug,\n currentProjectId,\n}: {\n queryClient: QueryClient;\n workspaceId: string;\n projectSlug: string;\n currentProjectId?: string | undefined;\n}): boolean {\n const cachedLists = queryClient.getQueriesData<InfiniteData<ProjectList, string | undefined>>({\n queryKey: [...projectsQueryKeys.all, 'list', workspaceId],\n });\n const conflict = cachedLists\n .flatMap(([, data]) => data?.pages.flatMap((page) => page.projects) ?? [])\n .some((project) => project.slug === projectSlug && project.id !== currentProjectId);\n return !conflict;\n}\n\nexport function useProjectSlugAvailability(\n workspaceId: string | undefined,\n currentProjectId?: string,\n): (projectSlug: string) => Promise<boolean> {\n const queryClient = useQueryClient();\n return useCallback(\n async (projectSlug: string) => {\n if (!workspaceId) return false;\n if (!isProjectSlugAvailable({queryClient, workspaceId, projectSlug, currentProjectId})) {\n return false;\n }\n const project = await findProjectBySlug({workspaceId, projectSlug});\n return project === undefined || project.id === currentProjectId;\n },\n [currentProjectId, queryClient, workspaceId],\n );\n}\n\nexport function projectsInfiniteQueryOptions(\n workspaceId: string | undefined,\n search?: string,\n limit = 50,\n): ProjectListInfiniteQueryOptions {\n const normalizedSearch = search?.trim() ?? '';\n return {\n queryKey: workspaceId\n ? projectsQueryKeys.list(workspaceId, normalizedSearch)\n : ([...projectsQueryKeys.all, 'list'] as const),\n enabled: Boolean(workspaceId),\n initialPageParam: undefined as string | undefined,\n queryFn: ({pageParam, signal}: {pageParam: string | undefined; signal: AbortSignal}) =>\n listProjects({\n workspaceId: workspaceId ?? '',\n limit,\n cursor: pageParam,\n search: normalizedSearch || undefined,\n signal,\n }),\n getNextPageParam: (lastPage: ProjectList) => lastPage.nextCursor ?? undefined,\n placeholderData: keepPreviousData,\n staleTime: PROJECT_LIST_STALE_TIME,\n };\n}\n\nexport function projectExistenceQueryOptions(\n workspaceId: string | undefined,\n): ProjectExistenceQueryOptions {\n return queryOptions({\n queryKey: workspaceId\n ? projectsQueryKeys.exists(workspaceId)\n : ([...projectsQueryKeys.all, 'exists'] as const),\n enabled: Boolean(workspaceId),\n queryFn: ({signal}) => listProjects({workspaceId: workspaceId ?? '', limit: 1, signal}),\n staleTime: 30_000,\n });\n}\n\nexport async function readWorkspaceHasNoProject({\n queryClient,\n workspaceId,\n}: {\n queryClient: QueryClient;\n workspaceId: string;\n}): Promise<boolean> {\n const options = projectExistenceQueryOptions(workspaceId);\n // A cached non-empty list already rules out the first-project landing, so\n // skip the fresh read entirely: the snapshot only steers navigation for the\n // empty-workspace case, and a failed fresh read here would otherwise report\n // a global error on every create in an established workspace without\n // changing the landing.\n const cached = queryClient.getQueryData<ProjectList>(options.queryKey);\n if (cached !== undefined && cached.projects.length > 0) return false;\n try {\n // The landing decision must not trust fresh-but-stale existence data\n // (e.g. a project created in another tab inside the 30s staleTime window),\n // so force a fresh read for this check. The snapshot also runs without\n // retry so a slow or failing existence endpoint cannot stall the submit\n // handler on the app-default retry backoff; the catch falls back to cache.\n const data = await queryClient.fetchQuery({...options, staleTime: 0, retry: false});\n return data.projects.length === 0;\n } catch (error) {\n reportExistenceReadFailure(error);\n const fallback = queryClient.getQueryData<ProjectList>(options.queryKey);\n if (fallback !== undefined) return fallback.projects.length === 0;\n // Unknown existence keeps the existing project navigation.\n return false;\n }\n}\n\n/**\n * The existence snapshot is a best-effort pre-flight read: a failure must not\n * block project creation, but it still reaches the global error-reporting\n * pipeline so a degrading existence endpoint does not silently misroute the\n * first-project landing.\n */\nfunction reportExistenceReadFailure(error: unknown): void {\n globalThis.reportError?.(\n new Error('Failed to read workspace project existence for the first-project landing.', {\n cause: error,\n }),\n );\n}\n\nexport function projectQueryOptions(projectId: string | undefined): ProjectDetailQueryOptions {\n return queryOptions({\n queryKey: projectId\n ? projectsQueryKeys.detail(projectId)\n : ([...projectsQueryKeys.all, 'detail'] as const),\n enabled: Boolean(projectId),\n queryFn: () => getProject(projectId ?? ''),\n });\n}\n\nexport function projectSlugQueryOptions(\n workspaceId: string | undefined,\n projectSlug: string | undefined,\n): ProjectSlugQueryOptions {\n return queryOptions({\n queryKey:\n workspaceId && projectSlug\n ? projectsQueryKeys.slug(workspaceId, projectSlug)\n : ([...projectsQueryKeys.all, 'slug'] as const),\n enabled: Boolean(workspaceId && projectSlug),\n queryFn: async ({signal, client}) => {\n const resolvedWorkspaceId = workspaceId ?? '';\n const resolvedProjectSlug = projectSlug ?? '';\n const project = await findProjectBySlug({\n workspaceId: resolvedWorkspaceId,\n projectSlug: resolvedProjectSlug,\n signal,\n });\n if (project) {\n cacheResolvedProject(client, resolvedWorkspaceId, resolvedProjectSlug, project);\n }\n return project ?? null;\n },\n staleTime: 30_000,\n });\n}\n\nexport function useProjectsInfiniteQuery(\n workspaceId: string | undefined,\n search?: string,\n limit = 50,\n) {\n return useInfiniteQuery(projectsInfiniteQueryOptions(workspaceId, search, limit));\n}\nexport function useProjectQuery(projectId: string | undefined) {\n return useQuery(projectQueryOptions(projectId));\n}\nexport function useProjectSlugQuery(\n workspaceId: string | undefined,\n projectSlug: string | undefined,\n) {\n return useQuery(projectSlugQueryOptions(workspaceId, projectSlug));\n}\nexport function useCreateProjectMutation() {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: createProject,\n onSuccess: async (project) => {\n queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);\n queryClient.setQueryData<ProjectList>(projectsQueryKeys.exists(project.workspaceId), {\n projects: [project],\n nextCursor: null,\n });\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(project.workspaceId)});\n },\n onError: async (_error, command) => {\n await queryClient.invalidateQueries({\n queryKey: projectsQueryKeys.exists(command.workspaceId),\n refetchType: 'active',\n });\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(command.workspaceId)});\n },\n });\n}\n\nexport function useUpdateProjectMutation() {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: updateProject,\n onSuccess: async (project) => {\n queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);\n queryClient.setQueryData(projectsQueryKeys.slug(project.workspaceId, project.slug), project);\n await queryClient.invalidateQueries({\n queryKey: [...projectsQueryKeys.all, 'slug', project.workspaceId],\n });\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(project.workspaceId)});\n },\n });\n}\n"],"names":["createProjectBodySchema","listProjectsResponseSchema","projectResponseSchema","updateProjectBodySchema","checkedApiRequest","keepPreviousData","queryOptions","useInfiniteQuery","useMutation","useQuery","useQueryClient","useCallback","toProject","toProjectList","PROJECT_LIST_STALE_TIME","projectsQueryKeys","all","list","workspaceId","search","slug","projectSlug","exists","detail","projectId","listProjects","limit","cursor","signal","params","URLSearchParams","workspace_id","String","set","toString","resolveProjectSlug","queryClient","queryKey","data","getQueryData","fetchInfiniteQuery","projectsInfiniteQueryOptions","project","pages","flatMap","page","projects","find","candidate","dataUpdatedAt","getQueryState","Date","now","cacheResolvedProject","id","at","nextCursor","refetchAndResolveProjectSlug","resolveProjectSlugBySearch","undefined","refetchQueries","type","setQueryData","findProjectBySlug","result","getProject","createProject","command","body","parse","name","source","connection_id","connectionId","external_repository_id","externalRepositoryId","method","updateProject","isProjectSlugAvailable","currentProjectId","cachedLists","getQueriesData","conflict","some","useProjectSlugAvailability","normalizedSearch","trim","enabled","Boolean","initialPageParam","queryFn","pageParam","getNextPageParam","lastPage","placeholderData","staleTime","projectExistenceQueryOptions","readWorkspaceHasNoProject","options","cached","length","fetchQuery","retry","error","reportExistenceReadFailure","fallback","globalThis","reportError","Error","cause","projectQueryOptions","projectSlugQueryOptions","client","resolvedWorkspaceId","resolvedProjectSlug","useProjectsInfiniteQuery","useProjectQuery","useProjectSlugQuery","useCreateProjectMutation","mutationFn","onSuccess","invalidateQueries","onError","_error","refetchType","useUpdateProjectMutation"],"mappings":"AAAA,SACEA,uBAAuB,EACvBC,0BAA0B,EAC1BC,qBAAqB,EACrBC,uBAAuB,QAClB,4BAA4B;AACnC,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAEEC,gBAAgB,EAEhBC,YAAY,EAGZC,gBAAgB,EAChBC,WAAW,EACXC,QAAQ,EACRC,cAAc,QACT,wBAAwB;AAC/B,SAAQC,WAAW,QAAO,QAAQ;AAOlC,SAAQC,SAAS,EAAEC,aAAa,QAAO,eAAe;AAEtD,MAAMC,0BAA0B;AAEhC,OAAO,MAAMC,oBAAoB;IAC/BC,KAAK;QAAC;KAAW;IACjBC,MAAM,CAACC,aAAqBC,SAAS,EAAE,GACrC;eAAIJ,kBAAkBC,GAAG;YAAE;YAAQE;YAAaC;SAAO;IACzDC,MAAM,CAACF,aAAqBG,cAC1B;eAAIN,kBAAkBC,GAAG;YAAE;YAAQE;YAAaG;SAAY;IAC9DC,QAAQ,CAACJ,cAAwB;eAAIH,kBAAkBC,GAAG;YAAE;YAAUE;SAAY;IAClFK,QAAQ,CAACC,YAAsB;eAAIT,kBAAkBC,GAAG;YAAE;YAAUQ;SAAU;AAChF,EAAE;AAoCF,OAAO,eAAeC,aAAa,EACjCP,WAAW,EACXQ,QAAQ,EAAE,EACVC,MAAM,EACNR,MAAM,EACNS,MAAM,EAOP;IACC,MAAMC,SAAS,IAAIC,gBAAgB;QAACC,cAAcb;QAAaQ,OAAOM,OAAON;IAAM;IACnF,IAAIC,QAAQE,OAAOI,GAAG,CAAC,UAAUN;IACjC,IAAIR,QAAQU,OAAOI,GAAG,CAAC,UAAUd;IACjC,OAAON,cACL,MAAMT,kBAAkBH,4BAA4B,CAAC,UAAU,EAAE4B,OAAOK,QAAQ,IAAI,EAAE;QAACN;IAAM;AAEjG;AAEA,OAAO,eAAeO,mBAAmB,EACvCC,WAAW,EACXlB,WAAW,EACXG,WAAW,EAKZ;IACC,MAAMgB,WAAWtB,kBAAkBE,IAAI,CAACC;IACxC,IAAIoB,OAAOF,YAAYG,YAAY,CAAgDF;IAEnF,IAAI,CAACC,MAAM;QACT,MAAMF,YAAYI,kBAAkB,CAACC,6BAA6BvB;QAClEoB,OAAOF,YAAYG,YAAY,CAAgDF;IACjF;IAEA,IAAIC,MAAM;QACR,MAAMI,UAAUJ,KAAKK,KAAK,CACvBC,OAAO,CAAC,CAACC,OAASA,KAAKC,QAAQ,EAC/BC,IAAI,CAAC,CAACC,YAAcA,UAAU5B,IAAI,KAAKC;QAC1C,MAAM4B,gBAAgBb,YAAYc,aAAa,CAACb,WAAWY,iBAAiB;QAC5E,IAAIP,WAAWS,KAAKC,GAAG,KAAKH,gBAAgBnC,yBAAyB;YACnEuC,qBAAqBjB,aAAalB,aAAaG,aAAaqB;YAC5D,OAAOA,QAAQY,EAAE;QACnB;QACA,MAAM3B,SAASW,KAAKK,KAAK,CAACY,EAAE,CAAC,CAAC,IAAIC;QAClC,IAAId,WAAW,CAACf,QAAQ;YACtB,OAAO,MAAM8B,6BAA6BrB,aAAaC,UAAUnB,aAAaG;QAChF;QACA,OAAO,MAAMqC,2BAA2BtB,aAAalB,aAAaG;IACpE;IAEA,OAAOsC;AACT;AAEA,eAAeF,6BACbrB,WAAwB,EACxBC,QAAmD,EACnDnB,WAAmB,EACnBG,WAAmB;IAEnB,MAAMe,YAAYwB,cAAc,CAAC;QAACvB;QAAUwB,MAAM;IAAK;IACvD,MAAMvB,OAAOF,YAAYG,YAAY,CAAgDF;IAErF,IAAI,CAACC,MAAM,OAAOqB;IAClB,MAAMjB,UAAUJ,KAAKK,KAAK,CACvBC,OAAO,CAAC,CAACC,OAASA,KAAKC,QAAQ,EAC/BC,IAAI,CAAC,CAACC,YAAcA,UAAU5B,IAAI,KAAKC;IAC1C,IAAIqB,SAAS;QACXW,qBAAqBjB,aAAalB,aAAaG,aAAaqB;QAC5D,OAAOA,QAAQY,EAAE;IACnB;IAEA,IAAI,CAAChB,KAAKK,KAAK,CAACY,EAAE,CAAC,CAAC,IAAIC,YAAY,OAAOG;IAC3C,OAAO,MAAMD,2BAA2BtB,aAAalB,aAAaG;AACpE;AAEA,SAASgC,qBACPjB,WAAwB,EACxBlB,WAAmB,EACnBG,WAAmB,EACnBqB,OAAgB;IAEhBN,YAAY0B,YAAY,CAAC/C,kBAAkBK,IAAI,CAACF,aAAaG,cAAcqB;IAC3EN,YAAY0B,YAAY,CAAC/C,kBAAkBQ,MAAM,CAACmB,QAAQY,EAAE,GAAGZ;AACjE;AAEA,eAAegB,2BACbtB,WAAwB,EACxBlB,WAAmB,EACnBG,WAAmB,EACnBO,MAAoB;IAEpB,MAAMc,UAAU,MAAMqB,kBAAkB;QAAC7C;QAAaG;QAAaO;IAAM;IACzE,IAAIc,SAAS;QACXW,qBAAqBjB,aAAalB,aAAaG,aAAaqB;QAC5D,OAAOA,QAAQY,EAAE;IACnB;IACA,OAAOK;AACT;AAEA,eAAeI,kBAAkB,EAC/B7C,WAAW,EACXG,WAAW,EACXO,MAAM,EAKP;IACC,IAAIoC,SAAS,MAAMvC,aAAa;QAACP;QAAaC,QAAQE;QAAaK,OAAO;QAAKE;IAAM;IACrF,MAAO,KAAM;QACX,MAAMc,UAAUsB,OAAOlB,QAAQ,CAACC,IAAI,CAAC,CAACC,YAAcA,UAAU5B,IAAI,KAAKC;QACvE,IAAIqB,SAAS,OAAOA;QACpB,IAAI,CAACsB,OAAOR,UAAU,EAAE,OAAOG;QAC/BK,SAAS,MAAMvC,aAAa;YAC1BP;YACAC,QAAQE;YACRK,OAAO;YACPC,QAAQqC,OAAOR,UAAU;YACzB5B;QACF;IACF;AACF;AAEA,OAAO,eAAeqC,WAAWzC,SAAiB;IAChD,OAAOZ,UAAU,MAAMR,kBAAkBF,uBAAuB,CAAC,UAAU,EAAEsB,WAAW;AAC1F;AAEA,OAAO,eAAe0C,cAAcC,OAA6B;IAC/D,MAAMC,OAAOpE,wBAAwBqE,KAAK,CAAC;QACzCtC,cAAcoC,QAAQjD,WAAW;QACjCoD,MAAMH,QAAQG,IAAI;QAClBlD,MAAM+C,QAAQ/C,IAAI;QAClBmD,QAAQ;YACNC,eAAeL,QAAQI,MAAM,CAACE,YAAY;YAC1CC,wBAAwBP,QAAQI,MAAM,CAACI,oBAAoB;QAC7D;IACF;IACA,OAAO/D,UACL,MAAMR,kBAAkBF,uBAAuB,aAAa;QAAC0E,QAAQ;QAAQR;IAAI;AAErF;AAEA,OAAO,eAAeS,cAAcV,OAA6B;IAC/D,MAAMC,OAAOjE,wBAAwBkE,KAAK,CAAC;QAACC,MAAMH,QAAQG,IAAI;QAAElD,MAAM+C,QAAQ/C,IAAI;IAAA;IAClF,OAAOR,UACL,MAAMR,kBAAkBF,uBAAuB,CAAC,UAAU,EAAEiE,QAAQ3C,SAAS,EAAE,EAAE;QAC/EoD,QAAQ;QACRR;IACF;AAEJ;AAEA,OAAO,SAASU,uBAAuB,EACrC1C,WAAW,EACXlB,WAAW,EACXG,WAAW,EACX0D,gBAAgB,EAMjB;IACC,MAAMC,cAAc5C,YAAY6C,cAAc,CAAgD;QAC5F5C,UAAU;eAAItB,kBAAkBC,GAAG;YAAE;YAAQE;SAAY;IAC3D;IACA,MAAMgE,WAAWF,YACdpC,OAAO,CAAC,CAAC,GAAGN,KAAK,GAAKA,MAAMK,MAAMC,QAAQ,CAACC,OAASA,KAAKC,QAAQ,KAAK,EAAE,EACxEqC,IAAI,CAAC,CAACzC,UAAYA,QAAQtB,IAAI,KAAKC,eAAeqB,QAAQY,EAAE,KAAKyB;IACpE,OAAO,CAACG;AACV;AAEA,OAAO,SAASE,2BACdlE,WAA+B,EAC/B6D,gBAAyB;IAEzB,MAAM3C,cAAc1B;IACpB,OAAOC,YACL,OAAOU;QACL,IAAI,CAACH,aAAa,OAAO;QACzB,IAAI,CAAC4D,uBAAuB;YAAC1C;YAAalB;YAAaG;YAAa0D;QAAgB,IAAI;YACtF,OAAO;QACT;QACA,MAAMrC,UAAU,MAAMqB,kBAAkB;YAAC7C;YAAaG;QAAW;QACjE,OAAOqB,YAAYiB,aAAajB,QAAQY,EAAE,KAAKyB;IACjD,GACA;QAACA;QAAkB3C;QAAalB;KAAY;AAEhD;AAEA,OAAO,SAASuB,6BACdvB,WAA+B,EAC/BC,MAAe,EACfO,QAAQ,EAAE;IAEV,MAAM2D,mBAAmBlE,QAAQmE,UAAU;IAC3C,OAAO;QACLjD,UAAUnB,cACNH,kBAAkBE,IAAI,CAACC,aAAamE,oBACnC;eAAItE,kBAAkBC,GAAG;YAAE;SAAO;QACvCuE,SAASC,QAAQtE;QACjBuE,kBAAkB9B;QAClB+B,SAAS,CAAC,EAACC,SAAS,EAAE/D,MAAM,EAAuD,GACjFH,aAAa;gBACXP,aAAaA,eAAe;gBAC5BQ;gBACAC,QAAQgE;gBACRxE,QAAQkE,oBAAoB1B;gBAC5B/B;YACF;QACFgE,kBAAkB,CAACC,WAA0BA,SAASrC,UAAU,IAAIG;QACpEmC,iBAAiBzF;QACjB0F,WAAWjF;IACb;AACF;AAEA,OAAO,SAASkF,6BACd9E,WAA+B;IAE/B,OAAOZ,aAAa;QAClB+B,UAAUnB,cACNH,kBAAkBO,MAAM,CAACJ,eACxB;eAAIH,kBAAkBC,GAAG;YAAE;SAAS;QACzCuE,SAASC,QAAQtE;QACjBwE,SAAS,CAAC,EAAC9D,MAAM,EAAC,GAAKH,aAAa;gBAACP,aAAaA,eAAe;gBAAIQ,OAAO;gBAAGE;YAAM;QACrFmE,WAAW;IACb;AACF;AAEA,OAAO,eAAeE,0BAA0B,EAC9C7D,WAAW,EACXlB,WAAW,EAIZ;IACC,MAAMgF,UAAUF,6BAA6B9E;IAC7C,0EAA0E;IAC1E,4EAA4E;IAC5E,4EAA4E;IAC5E,qEAAqE;IACrE,wBAAwB;IACxB,MAAMiF,SAAS/D,YAAYG,YAAY,CAAc2D,QAAQ7D,QAAQ;IACrE,IAAI8D,WAAWxC,aAAawC,OAAOrD,QAAQ,CAACsD,MAAM,GAAG,GAAG,OAAO;IAC/D,IAAI;QACF,qEAAqE;QACrE,2EAA2E;QAC3E,uEAAuE;QACvE,wEAAwE;QACxE,2EAA2E;QAC3E,MAAM9D,OAAO,MAAMF,YAAYiE,UAAU,CAAC;YAAC,GAAGH,OAAO;YAAEH,WAAW;YAAGO,OAAO;QAAK;QACjF,OAAOhE,KAAKQ,QAAQ,CAACsD,MAAM,KAAK;IAClC,EAAE,OAAOG,OAAO;QACdC,2BAA2BD;QAC3B,MAAME,WAAWrE,YAAYG,YAAY,CAAc2D,QAAQ7D,QAAQ;QACvE,IAAIoE,aAAa9C,WAAW,OAAO8C,SAAS3D,QAAQ,CAACsD,MAAM,KAAK;QAChE,2DAA2D;QAC3D,OAAO;IACT;AACF;AAEA;;;;;CAKC,GACD,SAASI,2BAA2BD,KAAc;IAChDG,WAAWC,WAAW,GACpB,IAAIC,MAAM,6EAA6E;QACrFC,OAAON;IACT;AAEJ;AAEA,OAAO,SAASO,oBAAoBtF,SAA6B;IAC/D,OAAOlB,aAAa;QAClB+B,UAAUb,YACNT,kBAAkBQ,MAAM,CAACC,aACxB;eAAIT,kBAAkBC,GAAG;YAAE;SAAS;QACzCuE,SAASC,QAAQhE;QACjBkE,SAAS,IAAMzB,WAAWzC,aAAa;IACzC;AACF;AAEA,OAAO,SAASuF,wBACd7F,WAA+B,EAC/BG,WAA+B;IAE/B,OAAOf,aAAa;QAClB+B,UACEnB,eAAeG,cACXN,kBAAkBK,IAAI,CAACF,aAAaG,eACnC;eAAIN,kBAAkBC,GAAG;YAAE;SAAO;QACzCuE,SAASC,QAAQtE,eAAeG;QAChCqE,SAAS,OAAO,EAAC9D,MAAM,EAAEoF,MAAM,EAAC;YAC9B,MAAMC,sBAAsB/F,eAAe;YAC3C,MAAMgG,sBAAsB7F,eAAe;YAC3C,MAAMqB,UAAU,MAAMqB,kBAAkB;gBACtC7C,aAAa+F;gBACb5F,aAAa6F;gBACbtF;YACF;YACA,IAAIc,SAAS;gBACXW,qBAAqB2D,QAAQC,qBAAqBC,qBAAqBxE;YACzE;YACA,OAAOA,WAAW;QACpB;QACAqD,WAAW;IACb;AACF;AAEA,OAAO,SAASoB,yBACdjG,WAA+B,EAC/BC,MAAe,EACfO,QAAQ,EAAE;IAEV,OAAOnB,iBAAiBkC,6BAA6BvB,aAAaC,QAAQO;AAC5E;AACA,OAAO,SAAS0F,gBAAgB5F,SAA6B;IAC3D,OAAOf,SAASqG,oBAAoBtF;AACtC;AACA,OAAO,SAAS6F,oBACdnG,WAA+B,EAC/BG,WAA+B;IAE/B,OAAOZ,SAASsG,wBAAwB7F,aAAaG;AACvD;AACA,OAAO,SAASiG;IACd,MAAMlF,cAAc1B;IACpB,OAAOF,YAAY;QACjB+G,YAAYrD;QACZsD,WAAW,OAAO9E;YAChBN,YAAY0B,YAAY,CAAC/C,kBAAkBQ,MAAM,CAACmB,QAAQY,EAAE,GAAGZ;YAC/DN,YAAY0B,YAAY,CAAc/C,kBAAkBO,MAAM,CAACoB,QAAQxB,WAAW,GAAG;gBACnF4B,UAAU;oBAACJ;iBAAQ;gBACnBc,YAAY;YACd;YACA,MAAMpB,YAAYqF,iBAAiB,CAAC;gBAACpF,UAAUtB,kBAAkBE,IAAI,CAACyB,QAAQxB,WAAW;YAAC;QAC5F;QACAwG,SAAS,OAAOC,QAAQxD;YACtB,MAAM/B,YAAYqF,iBAAiB,CAAC;gBAClCpF,UAAUtB,kBAAkBO,MAAM,CAAC6C,QAAQjD,WAAW;gBACtD0G,aAAa;YACf;YACA,MAAMxF,YAAYqF,iBAAiB,CAAC;gBAACpF,UAAUtB,kBAAkBE,IAAI,CAACkD,QAAQjD,WAAW;YAAC;QAC5F;IACF;AACF;AAEA,OAAO,SAAS2G;IACd,MAAMzF,cAAc1B;IAEpB,OAAOF,YAAY;QACjB+G,YAAY1C;QACZ2C,WAAW,OAAO9E;YAChBN,YAAY0B,YAAY,CAAC/C,kBAAkBQ,MAAM,CAACmB,QAAQY,EAAE,GAAGZ;YAC/DN,YAAY0B,YAAY,CAAC/C,kBAAkBK,IAAI,CAACsB,QAAQxB,WAAW,EAAEwB,QAAQtB,IAAI,GAAGsB;YACpF,MAAMN,YAAYqF,iBAAiB,CAAC;gBAClCpF,UAAU;uBAAItB,kBAAkBC,GAAG;oBAAE;oBAAQ0B,QAAQxB,WAAW;iBAAC;YACnE;YACA,MAAMkB,YAAYqF,iBAAiB,CAAC;gBAACpF,UAAUtB,kBAAkBE,IAAI,CAACyB,QAAQxB,WAAW;YAAC;QAC5F;IACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-project-page.d.ts","sourceRoot":"","sources":["../../src/pages/create-project-page.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,iBAAiB,gCAyXhC"}
1
+ {"version":3,"file":"create-project-page.d.ts","sourceRoot":"","sources":["../../src/pages/create-project-page.tsx"],"names":[],"mappings":"AA0CA,wBAAgB,iBAAiB,gCAqahC"}
@@ -13,11 +13,11 @@ import { Panel, PanelActions, PanelBody, PanelHeader, PanelTitle } from '@shipfo
13
13
  import { toast } from '@shipfox/react-ui/toast';
14
14
  import { Header, Text } from '@shipfox/react-ui/typography';
15
15
  import { useForm } from '@tanstack/react-form';
16
+ import { useQueryClient } from '@tanstack/react-query';
16
17
  import { Link, Navigate, useNavigate } from '@tanstack/react-router';
17
18
  import { useEffect, useRef, useState } from 'react';
18
- import { ModelProviderReminderBanner } from '#components/model-provider-reminder-banner.js';
19
19
  import { projectNameFromRepository } from '#core/project.js';
20
- import { getProject, useCreateProjectMutation, useProjectSlugAvailability } from '#hooks/api/projects.js';
20
+ import { getProject, projectsInfiniteQueryOptions, readWorkspaceHasNoProject, useCreateProjectMutation, useProjectSlugAvailability } from '#hooks/api/projects.js';
21
21
  import { projectErrorCopy } from '#project-error.js';
22
22
  function isSlugValid(value) {
23
23
  return slugSchema.safeParse(value).success;
@@ -25,8 +25,13 @@ function isSlugValid(value) {
25
25
  export function CreateProjectPage() {
26
26
  const workspace = useMaybeActiveWorkspace();
27
27
  const navigate = useNavigate();
28
+ const queryClient = useQueryClient();
28
29
  const createProject = useCreateProjectMutation();
29
30
  const errorRef = useRef(null);
31
+ // The submit flow awaits an existence snapshot before the create mutation, so
32
+ // the pending state must cover the whole handler, not just the mutation.
33
+ const submittingRef = useRef(false);
34
+ const [submitting, setSubmitting] = useState(false);
30
35
  const connectionsQuery = useSourceConnectionsQuery(workspace?.id);
31
36
  const connections = connectionsQuery.data ?? [];
32
37
  const [selectedConnectionId, setSelectedConnectionId] = useState();
@@ -134,7 +139,16 @@ export function CreateProjectPage() {
134
139
  errorRef.current?.focus();
135
140
  return;
136
141
  }
142
+ if (submittingRef.current) return;
143
+ submittingRef.current = true;
144
+ setSubmitting(true);
137
145
  try {
146
+ // Snapshot project existence before the mutation: after a successful
147
+ // create the workspace has one project and "first" is already false.
148
+ const wasFirstProject = await readWorkspaceHasNoProject({
149
+ queryClient,
150
+ workspaceId: workspace.id
151
+ });
138
152
  const command = {
139
153
  workspaceId: workspace.id,
140
154
  name: projectName,
@@ -146,6 +160,44 @@ export function CreateProjectPage() {
146
160
  };
147
161
  const project = await createProject.mutateAsync(command);
148
162
  toast.success('Project created.');
163
+ if (wasFirstProject) {
164
+ // The setup checklist panel is the first thing on the home, so the
165
+ // first project lands where the Get-started guide lives. Seed the
166
+ // workspace list so the home does not re-render the pre-create empty
167
+ // state, then fetch the authoritative list: the seeded entry has no
168
+ // queryFn of its own (no observer mounted it), so refetching it would
169
+ // fail and a project created concurrently (another tab, import, or
170
+ // API) would stay hidden for the stale window. Fetching with
171
+ // staleTime: 0 forces a fresh read that replaces the seed; a failed
172
+ // fetch is swallowed so the seeded project remains the fallback and
173
+ // the home never shows a stale empty state.
174
+ const listQueryKey = projectsInfiniteQueryOptions(workspace.id).queryKey;
175
+ queryClient.setQueryData(listQueryKey, {
176
+ pages: [
177
+ {
178
+ projects: [
179
+ project
180
+ ],
181
+ nextCursor: null
182
+ }
183
+ ],
184
+ pageParams: [
185
+ undefined
186
+ ]
187
+ });
188
+ await queryClient.fetchInfiniteQuery({
189
+ ...projectsInfiniteQueryOptions(workspace.id),
190
+ staleTime: 0,
191
+ retry: false
192
+ }).catch(()=>undefined);
193
+ await navigate({
194
+ to: '/w/$workspaceSlug',
195
+ params: {
196
+ workspaceSlug: workspace.slug
197
+ }
198
+ });
199
+ return;
200
+ }
149
201
  await navigate({
150
202
  to: '/w/$workspaceSlug/p/$projectSlug',
151
203
  params: {
@@ -180,6 +232,9 @@ export function CreateProjectPage() {
180
232
  }
181
233
  setFormError(`${copy.title}: ${copy.message}`);
182
234
  requestAnimationFrame(()=>errorRef.current?.focus());
235
+ } finally{
236
+ submittingRef.current = false;
237
+ setSubmitting(false);
183
238
  }
184
239
  }
185
240
  const showRepoPicker = Boolean(selectedConnection);
@@ -192,9 +247,6 @@ export function CreateProjectPage() {
192
247
  variant: "h1",
193
248
  children: "Create project"
194
249
  }),
195
- /*#__PURE__*/ _jsx(ModelProviderReminderBanner, {
196
- workspaceId: workspace.id
197
- }),
198
250
  connectionsQuery.isError ? /*#__PURE__*/ _jsx(Callout, {
199
251
  role: "alert",
200
252
  type: "error",
@@ -395,7 +447,7 @@ export function CreateProjectPage() {
395
447
  /*#__PURE__*/ _jsx(Button, {
396
448
  type: "submit",
397
449
  iconRight: "chevronRight",
398
- isLoading: createProject.isPending,
450
+ isLoading: submitting,
399
451
  disabled: !selectedConnection || !selectedRepository,
400
452
  className: "w-full",
401
453
  children: "Create project"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/create-project-page.tsx"],"sourcesContent":["import {slugifyName, slugSchema} from '@shipfox/api-common-dto';\nimport {createProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {useMaybeActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionPicker,\n type IntegrationConnection,\n IntegrationIcon,\n RepositoryPicker,\n useRepositoriesInfiniteQuery,\n useSourceConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {displayNameFieldError, SlugField} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Input} from '@shipfox/react-ui/input';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {Panel, PanelActions, PanelBody, PanelHeader, PanelTitle} from '@shipfox/react-ui/panel';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, Navigate, useNavigate} from '@tanstack/react-router';\nimport {useEffect, useRef, useState} from 'react';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport {type CreateProjectCommand, projectNameFromRepository} from '#core/project.js';\nimport {\n getProject,\n useCreateProjectMutation,\n useProjectSlugAvailability,\n} from '#hooks/api/projects.js';\nimport {projectErrorCopy} from '#project-error.js';\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function CreateProjectPage() {\n const workspace = useMaybeActiveWorkspace();\n const navigate = useNavigate();\n const createProject = useCreateProjectMutation();\n const errorRef = useRef<HTMLDivElement>(null);\n\n const connectionsQuery = useSourceConnectionsQuery(workspace?.id);\n const connections = connectionsQuery.data ?? [];\n\n const [selectedConnectionId, setSelectedConnectionId] = useState<string | undefined>();\n const singleConnectionId = connections.length === 1 ? connections[0]?.id : undefined;\n const effectiveSelectedConnectionId = selectedConnectionId ?? singleConnectionId;\n useEffect(() => {\n if (singleConnectionId && selectedConnectionId !== singleConnectionId) {\n setSelectedConnectionId(singleConnectionId);\n }\n }, [singleConnectionId, selectedConnectionId]);\n\n const selectedConnection: IntegrationConnection | undefined = connections.find(\n (connection) => connection.id === effectiveSelectedConnectionId,\n );\n\n const [repoFilter, setRepoFilter] = useState('');\n const debouncedRepoFilter = useDebouncedValue(repoFilter, 250);\n const trimmedFilter = debouncedRepoFilter.trim();\n\n const repositoriesQuery = useRepositoriesInfiniteQuery(\n effectiveSelectedConnectionId,\n trimmedFilter ? {search: trimmedFilter} : undefined,\n );\n const repositories = repositoriesQuery.data?.pages.flatMap((page) => page.repositories) ?? [];\n\n const [selectedRepositoryId, setSelectedRepositoryId] = useState<string | undefined>();\n useEffect(() => {\n if (!selectedRepositoryId && repositories[0]) {\n setSelectedRepositoryId(repositories[0].externalRepositoryId);\n }\n }, [repositories, selectedRepositoryId]);\n const selectedRepository = repositories.find(\n (repository) => repository.externalRepositoryId === selectedRepositoryId,\n );\n\n const [nameTouched, setNameTouched] = useState(false);\n const defaultProjectName = projectNameFromRepository(\n selectedRepository?.name ?? selectedRepositoryId ?? '',\n );\n const defaultProjectSlug = slugifyName(defaultProjectName, {fallback: 'project'});\n\n const [formError, setFormError] = useState<string | undefined>();\n const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);\n const [slugCheckEnabled, setSlugCheckEnabled] = useState(false);\n const [slugConflict, setSlugConflict] = useState(false);\n const workspaceId = workspace?.id;\n const checkProjectSlugAvailability = useProjectSlugAvailability(workspaceId);\n\n const form = useForm({\n defaultValues: {name: defaultProjectName, slug: defaultProjectSlug},\n onSubmit: async ({value}) => {\n const projectSlug = slugManuallyEdited\n ? value.slug\n : slugifyName(value.name, {fallback: 'project'});\n await createProjectFromForm(nameTouched ? value.name : defaultProjectName, projectSlug);\n },\n });\n\n useEffect(() => {\n if (!nameTouched && form.state.values.name !== defaultProjectName) {\n form.setFieldValue('name', defaultProjectName);\n if (!slugManuallyEdited) {\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(defaultProjectName, {fallback: 'project'}));\n }\n }\n }, [defaultProjectName, form, nameTouched, slugManuallyEdited]);\n\n function selectConnection(connectionId: string) {\n setSelectedConnectionId(connectionId);\n setSelectedRepositoryId(undefined);\n }\n\n if (connectionsQuery.isPending) {\n return <FullPageLoader />;\n }\n\n if (!workspace) {\n return <FullPageLoader />;\n }\n\n if (!connectionsQuery.isError && connections.length === 0) {\n return (\n <Navigate\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n replace\n />\n );\n }\n\n async function createProjectFromForm(projectName: string, projectSlug: string) {\n setFormError(undefined);\n setSlugConflict(false);\n if (!workspace) {\n setFormError('Workspace is still loading. Try again in a moment.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedConnection) {\n setFormError('Choose a source integration before creating a project.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedRepository) {\n setFormError('Choose a repository before creating a project.');\n errorRef.current?.focus();\n return;\n }\n\n try {\n const command: CreateProjectCommand = {\n workspaceId: workspace.id,\n name: projectName,\n slug: projectSlug,\n source: {\n connectionId: selectedConnection.id,\n externalRepositoryId: selectedRepository.externalRepositoryId,\n },\n };\n const project = await createProject.mutateAsync(command);\n toast.success('Project created.');\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (error) {\n const copy = projectErrorCopy(error);\n if (copy.existingProjectId) {\n toast.info('Project already exists.');\n try {\n const project = await getProject(copy.existingProjectId);\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (recoveryError) {\n const recoveryCopy = projectErrorCopy(recoveryError);\n setFormError(`${recoveryCopy.title}: ${recoveryCopy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n return;\n }\n if (copy.slugConflict) {\n setSlugConflict(true);\n requestAnimationFrame(() => document.getElementById('project-slug')?.focus());\n return;\n }\n setFormError(`${copy.title}: ${copy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n }\n\n const showRepoPicker = Boolean(selectedConnection);\n const filteredEmptyMessage = trimmedFilter\n ? `No repositories matching \"${repoFilter.trim()}\".`\n : 'No repositories visible to this connection.';\n\n return (\n <div className=\"flex w-full flex-col gap-section\">\n <Header id=\"create-project-title\" variant=\"h1\">\n Create project\n </Header>\n\n <ModelProviderReminderBanner workspaceId={workspace.id} />\n\n {connectionsQuery.isError ? (\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex w-full flex-wrap items-center justify-between gap-cluster\">\n <Text size=\"sm\" className=\"min-w-[240px] flex-1\">\n Could not load source integrations. Refresh the integrations list to continue.\n </Text>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => connectionsQuery.refetch()}\n className=\"shrink-0\"\n >\n Refresh integrations\n </Button>\n </div>\n </Callout>\n ) : null}\n\n <form\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n noValidate\n aria-labelledby=\"create-project-title\"\n className=\"grid items-start gap-region lg:grid-cols-[minmax(0,1fr)_340px]\"\n >\n <div className=\"flex min-w-0 flex-col gap-region\">\n {connections.length > 0 ? (\n <section aria-label=\"Source integration\">\n <Panel>\n <PanelHeader>\n <PanelTitle>Source integration</PanelTitle>\n {connections.length === 1 ? (\n <PanelActions>\n <Button asChild variant=\"transparent\" size=\"sm\" className=\"shrink-0\">\n <Link\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n >\n Add another integration\n </Link>\n </Button>\n </PanelActions>\n ) : null}\n </PanelHeader>\n\n <PanelBody>\n <ConnectionPicker\n connections={connections}\n selectedConnectionId={effectiveSelectedConnectionId}\n onSelect={selectConnection}\n />\n </PanelBody>\n </Panel>\n </section>\n ) : null}\n\n {showRepoPicker ? (\n <section aria-label=\"Repository\">\n <Panel>\n <PanelHeader className=\"flex-wrap\">\n <PanelTitle>Repository</PanelTitle>\n <PanelActions className=\"min-w-0 max-[640px]:ml-0 max-[640px]:basis-full\">\n <Input\n type=\"search\"\n placeholder=\"Search repositories…\"\n aria-label=\"Search repositories\"\n value={repoFilter}\n onChange={(event) => setRepoFilter(event.target.value)}\n className=\"w-full max-w-[320px]\"\n />\n </PanelActions>\n </PanelHeader>\n <PanelBody>\n <RepositoryPicker\n repositories={repositories}\n selectedRepositoryId={selectedRepositoryId}\n onSelect={setSelectedRepositoryId}\n isLoading={repositoriesQuery.isPending}\n isFetchingNextPage={repositoriesQuery.isFetchingNextPage}\n hasNextPage={repositoriesQuery.hasNextPage}\n onLoadMore={() => repositoriesQuery.fetchNextPage()}\n emptyMessage={filteredEmptyMessage}\n />\n </PanelBody>\n </Panel>\n </section>\n ) : null}\n </div>\n\n <aside className=\"lg:sticky lg:top-32\">\n <Panel>\n <PanelHeader>\n <PanelTitle>Project details</PanelTitle>\n </PanelHeader>\n <PanelBody className=\"gap-group p-panel\">\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n <div ref={errorRef} tabIndex={-1}>\n {formError}\n </div>\n </Callout>\n ) : null}\n\n <ProjectSummary\n connection={selectedConnection}\n repositoryName={selectedRepository?.fullName}\n />\n\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(\n value,\n 'Project name',\n createProjectBodySchema.shape.name,\n ),\n onSubmit: ({value}) =>\n displayNameFieldError(\n value,\n 'Project name',\n createProjectBodySchema.shape.name,\n ),\n }}\n >\n {(field) => (\n <FormField label=\"Project name\" id=\"project-name\" error={fieldError(field)}>\n <FormFieldInput\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => {\n const nextName = event.target.value;\n setNameTouched(true);\n field.handleChange(nextName);\n if (!slugManuallyEdited) {\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(nextName, {fallback: 'project'}));\n }\n }}\n onBlur={field.handleBlur}\n placeholder=\"Platform\"\n />\n </FormField>\n )}\n </form.Field>\n\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createProjectBodySchema.shape.slug,\n onSubmit: createProjectBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"project-slug\"\n label=\"Project slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => {\n setSlugManuallyEdited(true);\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n field.handleChange(value);\n }}\n onBlur={field.handleBlur}\n error={\n slugConflict ? 'This project slug is already in use.' : fieldError(field)\n }\n description={\n <span className=\"font-code\">\n {`/w/${workspace.slug}/p/${field.state.value}`}\n </span>\n }\n placeholder=\"platform\"\n className=\"font-code\"\n checkEnabled={slugCheckEnabled}\n debounceMs={0}\n isValid={isSlugValid}\n checkAvailability={checkProjectSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button\n type=\"submit\"\n iconRight=\"chevronRight\"\n isLoading={createProject.isPending}\n disabled={!selectedConnection || !selectedRepository}\n className=\"w-full\"\n >\n Create project\n </Button>\n </PanelBody>\n </Panel>\n </aside>\n </form>\n </div>\n );\n}\n\nfunction ProjectSummary({\n connection,\n repositoryName,\n}: {\n connection: IntegrationConnection | undefined;\n repositoryName: string | undefined;\n}) {\n return (\n <div className=\"flex min-w-0 items-center gap-inline\">\n {repositoryName ? (\n <IntegrationIcon\n source={connection?.provider}\n aria-hidden\n className=\"size-20 shrink-0 text-foreground-neutral-base\"\n />\n ) : null}\n <Text\n size=\"sm\"\n bold\n className={repositoryName ? 'min-w-0 truncate' : 'text-foreground-neutral-muted'}\n >\n {repositoryName ?? 'Pick a repository'}\n </Text>\n </div>\n );\n}\n\nfunction useDebouncedValue<T>(value: T, delayMs: number): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const handle = setTimeout(() => setDebounced(value), delayMs);\n return () => clearTimeout(handle);\n }, [value, delayMs]);\n return debounced;\n}\n"],"names":["slugifyName","slugSchema","createProjectBodySchema","useMaybeActiveWorkspace","ConnectionPicker","IntegrationIcon","RepositoryPicker","useRepositoriesInfiniteQuery","useSourceConnectionsQuery","displayNameFieldError","SlugField","Button","Callout","FormField","FormFieldInput","fieldError","Input","FullPageLoader","Panel","PanelActions","PanelBody","PanelHeader","PanelTitle","toast","Header","Text","useForm","Link","Navigate","useNavigate","useEffect","useRef","useState","ModelProviderReminderBanner","projectNameFromRepository","getProject","useCreateProjectMutation","useProjectSlugAvailability","projectErrorCopy","isSlugValid","value","safeParse","success","CreateProjectPage","workspace","navigate","createProject","errorRef","connectionsQuery","id","connections","data","selectedConnectionId","setSelectedConnectionId","singleConnectionId","length","undefined","effectiveSelectedConnectionId","selectedConnection","find","connection","repoFilter","setRepoFilter","debouncedRepoFilter","useDebouncedValue","trimmedFilter","trim","repositoriesQuery","search","repositories","pages","flatMap","page","selectedRepositoryId","setSelectedRepositoryId","externalRepositoryId","selectedRepository","repository","nameTouched","setNameTouched","defaultProjectName","name","defaultProjectSlug","fallback","formError","setFormError","slugManuallyEdited","setSlugManuallyEdited","slugCheckEnabled","setSlugCheckEnabled","slugConflict","setSlugConflict","workspaceId","checkProjectSlugAvailability","form","defaultValues","slug","onSubmit","projectSlug","createProjectFromForm","state","values","setFieldValue","selectConnection","connectionId","isPending","isError","to","params","workspaceSlug","replace","projectName","current","focus","command","source","project","mutateAsync","error","copy","existingProjectId","info","recoveryError","recoveryCopy","title","message","requestAnimationFrame","document","getElementById","showRepoPicker","Boolean","filteredEmptyMessage","div","className","variant","role","type","size","onClick","refetch","event","preventDefault","stopPropagation","handleSubmit","noValidate","aria-labelledby","section","aria-label","asChild","onSelect","placeholder","onChange","target","isLoading","isFetchingNextPage","hasNextPage","onLoadMore","fetchNextPage","emptyMessage","aside","ref","tabIndex","ProjectSummary","repositoryName","fullName","Field","validators","onBlur","shape","field","label","nextName","handleChange","handleBlur","description","span","checkEnabled","debounceMs","isValid","checkAvailability","iconRight","disabled","provider","aria-hidden","bold","delayMs","debounced","setDebounced","handle","setTimeout","clearTimeout"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/create-project-page.tsx"],"sourcesContent":["import {slugifyName, slugSchema} from '@shipfox/api-common-dto';\nimport {createProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {useMaybeActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionPicker,\n type IntegrationConnection,\n IntegrationIcon,\n RepositoryPicker,\n useRepositoriesInfiniteQuery,\n useSourceConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {displayNameFieldError, SlugField} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Input} from '@shipfox/react-ui/input';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {Panel, PanelActions, PanelBody, PanelHeader, PanelTitle} from '@shipfox/react-ui/panel';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {type InfiniteData, useQueryClient} from '@tanstack/react-query';\nimport {Link, Navigate, useNavigate} from '@tanstack/react-router';\nimport {useEffect, useRef, useState} from 'react';\nimport {\n type CreateProjectCommand,\n type ProjectList,\n projectNameFromRepository,\n} from '#core/project.js';\nimport {\n getProject,\n projectsInfiniteQueryOptions,\n readWorkspaceHasNoProject,\n useCreateProjectMutation,\n useProjectSlugAvailability,\n} from '#hooks/api/projects.js';\nimport {projectErrorCopy} from '#project-error.js';\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function CreateProjectPage() {\n const workspace = useMaybeActiveWorkspace();\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const createProject = useCreateProjectMutation();\n const errorRef = useRef<HTMLDivElement>(null);\n // The submit flow awaits an existence snapshot before the create mutation, so\n // the pending state must cover the whole handler, not just the mutation.\n const submittingRef = useRef(false);\n const [submitting, setSubmitting] = useState(false);\n\n const connectionsQuery = useSourceConnectionsQuery(workspace?.id);\n const connections = connectionsQuery.data ?? [];\n\n const [selectedConnectionId, setSelectedConnectionId] = useState<string | undefined>();\n const singleConnectionId = connections.length === 1 ? connections[0]?.id : undefined;\n const effectiveSelectedConnectionId = selectedConnectionId ?? singleConnectionId;\n useEffect(() => {\n if (singleConnectionId && selectedConnectionId !== singleConnectionId) {\n setSelectedConnectionId(singleConnectionId);\n }\n }, [singleConnectionId, selectedConnectionId]);\n\n const selectedConnection: IntegrationConnection | undefined = connections.find(\n (connection) => connection.id === effectiveSelectedConnectionId,\n );\n\n const [repoFilter, setRepoFilter] = useState('');\n const debouncedRepoFilter = useDebouncedValue(repoFilter, 250);\n const trimmedFilter = debouncedRepoFilter.trim();\n\n const repositoriesQuery = useRepositoriesInfiniteQuery(\n effectiveSelectedConnectionId,\n trimmedFilter ? {search: trimmedFilter} : undefined,\n );\n const repositories = repositoriesQuery.data?.pages.flatMap((page) => page.repositories) ?? [];\n\n const [selectedRepositoryId, setSelectedRepositoryId] = useState<string | undefined>();\n useEffect(() => {\n if (!selectedRepositoryId && repositories[0]) {\n setSelectedRepositoryId(repositories[0].externalRepositoryId);\n }\n }, [repositories, selectedRepositoryId]);\n const selectedRepository = repositories.find(\n (repository) => repository.externalRepositoryId === selectedRepositoryId,\n );\n\n const [nameTouched, setNameTouched] = useState(false);\n const defaultProjectName = projectNameFromRepository(\n selectedRepository?.name ?? selectedRepositoryId ?? '',\n );\n const defaultProjectSlug = slugifyName(defaultProjectName, {fallback: 'project'});\n\n const [formError, setFormError] = useState<string | undefined>();\n const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);\n const [slugCheckEnabled, setSlugCheckEnabled] = useState(false);\n const [slugConflict, setSlugConflict] = useState(false);\n const workspaceId = workspace?.id;\n const checkProjectSlugAvailability = useProjectSlugAvailability(workspaceId);\n\n const form = useForm({\n defaultValues: {name: defaultProjectName, slug: defaultProjectSlug},\n onSubmit: async ({value}) => {\n const projectSlug = slugManuallyEdited\n ? value.slug\n : slugifyName(value.name, {fallback: 'project'});\n await createProjectFromForm(nameTouched ? value.name : defaultProjectName, projectSlug);\n },\n });\n\n useEffect(() => {\n if (!nameTouched && form.state.values.name !== defaultProjectName) {\n form.setFieldValue('name', defaultProjectName);\n if (!slugManuallyEdited) {\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(defaultProjectName, {fallback: 'project'}));\n }\n }\n }, [defaultProjectName, form, nameTouched, slugManuallyEdited]);\n\n function selectConnection(connectionId: string) {\n setSelectedConnectionId(connectionId);\n setSelectedRepositoryId(undefined);\n }\n\n if (connectionsQuery.isPending) {\n return <FullPageLoader />;\n }\n\n if (!workspace) {\n return <FullPageLoader />;\n }\n\n if (!connectionsQuery.isError && connections.length === 0) {\n return (\n <Navigate\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n replace\n />\n );\n }\n\n async function createProjectFromForm(projectName: string, projectSlug: string) {\n setFormError(undefined);\n setSlugConflict(false);\n if (!workspace) {\n setFormError('Workspace is still loading. Try again in a moment.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedConnection) {\n setFormError('Choose a source integration before creating a project.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedRepository) {\n setFormError('Choose a repository before creating a project.');\n errorRef.current?.focus();\n return;\n }\n if (submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n\n try {\n // Snapshot project existence before the mutation: after a successful\n // create the workspace has one project and \"first\" is already false.\n const wasFirstProject = await readWorkspaceHasNoProject({\n queryClient,\n workspaceId: workspace.id,\n });\n const command: CreateProjectCommand = {\n workspaceId: workspace.id,\n name: projectName,\n slug: projectSlug,\n source: {\n connectionId: selectedConnection.id,\n externalRepositoryId: selectedRepository.externalRepositoryId,\n },\n };\n const project = await createProject.mutateAsync(command);\n toast.success('Project created.');\n if (wasFirstProject) {\n // The setup checklist panel is the first thing on the home, so the\n // first project lands where the Get-started guide lives. Seed the\n // workspace list so the home does not re-render the pre-create empty\n // state, then fetch the authoritative list: the seeded entry has no\n // queryFn of its own (no observer mounted it), so refetching it would\n // fail and a project created concurrently (another tab, import, or\n // API) would stay hidden for the stale window. Fetching with\n // staleTime: 0 forces a fresh read that replaces the seed; a failed\n // fetch is swallowed so the seeded project remains the fallback and\n // the home never shows a stale empty state.\n const listQueryKey = projectsInfiniteQueryOptions(workspace.id).queryKey;\n queryClient.setQueryData<InfiniteData<ProjectList, string | undefined>>(listQueryKey, {\n pages: [{projects: [project], nextCursor: null}],\n pageParams: [undefined],\n });\n await queryClient\n .fetchInfiniteQuery({\n ...projectsInfiniteQueryOptions(workspace.id),\n staleTime: 0,\n retry: false,\n })\n .catch(() => undefined);\n await navigate({\n to: '/w/$workspaceSlug',\n params: {workspaceSlug: workspace.slug},\n });\n return;\n }\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (error) {\n const copy = projectErrorCopy(error);\n if (copy.existingProjectId) {\n toast.info('Project already exists.');\n try {\n const project = await getProject(copy.existingProjectId);\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (recoveryError) {\n const recoveryCopy = projectErrorCopy(recoveryError);\n setFormError(`${recoveryCopy.title}: ${recoveryCopy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n return;\n }\n if (copy.slugConflict) {\n setSlugConflict(true);\n requestAnimationFrame(() => document.getElementById('project-slug')?.focus());\n return;\n }\n setFormError(`${copy.title}: ${copy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n\n const showRepoPicker = Boolean(selectedConnection);\n const filteredEmptyMessage = trimmedFilter\n ? `No repositories matching \"${repoFilter.trim()}\".`\n : 'No repositories visible to this connection.';\n\n return (\n <div className=\"flex w-full flex-col gap-section\">\n <Header id=\"create-project-title\" variant=\"h1\">\n Create project\n </Header>\n\n {connectionsQuery.isError ? (\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex w-full flex-wrap items-center justify-between gap-cluster\">\n <Text size=\"sm\" className=\"min-w-[240px] flex-1\">\n Could not load source integrations. Refresh the integrations list to continue.\n </Text>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => connectionsQuery.refetch()}\n className=\"shrink-0\"\n >\n Refresh integrations\n </Button>\n </div>\n </Callout>\n ) : null}\n\n <form\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n noValidate\n aria-labelledby=\"create-project-title\"\n className=\"grid items-start gap-region lg:grid-cols-[minmax(0,1fr)_340px]\"\n >\n <div className=\"flex min-w-0 flex-col gap-region\">\n {connections.length > 0 ? (\n <section aria-label=\"Source integration\">\n <Panel>\n <PanelHeader>\n <PanelTitle>Source integration</PanelTitle>\n {connections.length === 1 ? (\n <PanelActions>\n <Button asChild variant=\"transparent\" size=\"sm\" className=\"shrink-0\">\n <Link\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n >\n Add another integration\n </Link>\n </Button>\n </PanelActions>\n ) : null}\n </PanelHeader>\n\n <PanelBody>\n <ConnectionPicker\n connections={connections}\n selectedConnectionId={effectiveSelectedConnectionId}\n onSelect={selectConnection}\n />\n </PanelBody>\n </Panel>\n </section>\n ) : null}\n\n {showRepoPicker ? (\n <section aria-label=\"Repository\">\n <Panel>\n <PanelHeader className=\"flex-wrap\">\n <PanelTitle>Repository</PanelTitle>\n <PanelActions className=\"min-w-0 max-[640px]:ml-0 max-[640px]:basis-full\">\n <Input\n type=\"search\"\n placeholder=\"Search repositories…\"\n aria-label=\"Search repositories\"\n value={repoFilter}\n onChange={(event) => setRepoFilter(event.target.value)}\n className=\"w-full max-w-[320px]\"\n />\n </PanelActions>\n </PanelHeader>\n <PanelBody>\n <RepositoryPicker\n repositories={repositories}\n selectedRepositoryId={selectedRepositoryId}\n onSelect={setSelectedRepositoryId}\n isLoading={repositoriesQuery.isPending}\n isFetchingNextPage={repositoriesQuery.isFetchingNextPage}\n hasNextPage={repositoriesQuery.hasNextPage}\n onLoadMore={() => repositoriesQuery.fetchNextPage()}\n emptyMessage={filteredEmptyMessage}\n />\n </PanelBody>\n </Panel>\n </section>\n ) : null}\n </div>\n\n <aside className=\"lg:sticky lg:top-32\">\n <Panel>\n <PanelHeader>\n <PanelTitle>Project details</PanelTitle>\n </PanelHeader>\n <PanelBody className=\"gap-group p-panel\">\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n <div ref={errorRef} tabIndex={-1}>\n {formError}\n </div>\n </Callout>\n ) : null}\n\n <ProjectSummary\n connection={selectedConnection}\n repositoryName={selectedRepository?.fullName}\n />\n\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(\n value,\n 'Project name',\n createProjectBodySchema.shape.name,\n ),\n onSubmit: ({value}) =>\n displayNameFieldError(\n value,\n 'Project name',\n createProjectBodySchema.shape.name,\n ),\n }}\n >\n {(field) => (\n <FormField label=\"Project name\" id=\"project-name\" error={fieldError(field)}>\n <FormFieldInput\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => {\n const nextName = event.target.value;\n setNameTouched(true);\n field.handleChange(nextName);\n if (!slugManuallyEdited) {\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(nextName, {fallback: 'project'}));\n }\n }}\n onBlur={field.handleBlur}\n placeholder=\"Platform\"\n />\n </FormField>\n )}\n </form.Field>\n\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createProjectBodySchema.shape.slug,\n onSubmit: createProjectBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"project-slug\"\n label=\"Project slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => {\n setSlugManuallyEdited(true);\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n field.handleChange(value);\n }}\n onBlur={field.handleBlur}\n error={\n slugConflict ? 'This project slug is already in use.' : fieldError(field)\n }\n description={\n <span className=\"font-code\">\n {`/w/${workspace.slug}/p/${field.state.value}`}\n </span>\n }\n placeholder=\"platform\"\n className=\"font-code\"\n checkEnabled={slugCheckEnabled}\n debounceMs={0}\n isValid={isSlugValid}\n checkAvailability={checkProjectSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button\n type=\"submit\"\n iconRight=\"chevronRight\"\n isLoading={submitting}\n disabled={!selectedConnection || !selectedRepository}\n className=\"w-full\"\n >\n Create project\n </Button>\n </PanelBody>\n </Panel>\n </aside>\n </form>\n </div>\n );\n}\n\nfunction ProjectSummary({\n connection,\n repositoryName,\n}: {\n connection: IntegrationConnection | undefined;\n repositoryName: string | undefined;\n}) {\n return (\n <div className=\"flex min-w-0 items-center gap-inline\">\n {repositoryName ? (\n <IntegrationIcon\n source={connection?.provider}\n aria-hidden\n className=\"size-20 shrink-0 text-foreground-neutral-base\"\n />\n ) : null}\n <Text\n size=\"sm\"\n bold\n className={repositoryName ? 'min-w-0 truncate' : 'text-foreground-neutral-muted'}\n >\n {repositoryName ?? 'Pick a repository'}\n </Text>\n </div>\n );\n}\n\nfunction useDebouncedValue<T>(value: T, delayMs: number): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const handle = setTimeout(() => setDebounced(value), delayMs);\n return () => clearTimeout(handle);\n }, [value, delayMs]);\n return debounced;\n}\n"],"names":["slugifyName","slugSchema","createProjectBodySchema","useMaybeActiveWorkspace","ConnectionPicker","IntegrationIcon","RepositoryPicker","useRepositoriesInfiniteQuery","useSourceConnectionsQuery","displayNameFieldError","SlugField","Button","Callout","FormField","FormFieldInput","fieldError","Input","FullPageLoader","Panel","PanelActions","PanelBody","PanelHeader","PanelTitle","toast","Header","Text","useForm","useQueryClient","Link","Navigate","useNavigate","useEffect","useRef","useState","projectNameFromRepository","getProject","projectsInfiniteQueryOptions","readWorkspaceHasNoProject","useCreateProjectMutation","useProjectSlugAvailability","projectErrorCopy","isSlugValid","value","safeParse","success","CreateProjectPage","workspace","navigate","queryClient","createProject","errorRef","submittingRef","submitting","setSubmitting","connectionsQuery","id","connections","data","selectedConnectionId","setSelectedConnectionId","singleConnectionId","length","undefined","effectiveSelectedConnectionId","selectedConnection","find","connection","repoFilter","setRepoFilter","debouncedRepoFilter","useDebouncedValue","trimmedFilter","trim","repositoriesQuery","search","repositories","pages","flatMap","page","selectedRepositoryId","setSelectedRepositoryId","externalRepositoryId","selectedRepository","repository","nameTouched","setNameTouched","defaultProjectName","name","defaultProjectSlug","fallback","formError","setFormError","slugManuallyEdited","setSlugManuallyEdited","slugCheckEnabled","setSlugCheckEnabled","slugConflict","setSlugConflict","workspaceId","checkProjectSlugAvailability","form","defaultValues","slug","onSubmit","projectSlug","createProjectFromForm","state","values","setFieldValue","selectConnection","connectionId","isPending","isError","to","params","workspaceSlug","replace","projectName","current","focus","wasFirstProject","command","source","project","mutateAsync","listQueryKey","queryKey","setQueryData","projects","nextCursor","pageParams","fetchInfiniteQuery","staleTime","retry","catch","error","copy","existingProjectId","info","recoveryError","recoveryCopy","title","message","requestAnimationFrame","document","getElementById","showRepoPicker","Boolean","filteredEmptyMessage","div","className","variant","role","type","size","onClick","refetch","event","preventDefault","stopPropagation","handleSubmit","noValidate","aria-labelledby","section","aria-label","asChild","onSelect","placeholder","onChange","target","isLoading","isFetchingNextPage","hasNextPage","onLoadMore","fetchNextPage","emptyMessage","aside","ref","tabIndex","ProjectSummary","repositoryName","fullName","Field","validators","onBlur","shape","field","label","nextName","handleChange","handleBlur","description","span","checkEnabled","debounceMs","isValid","checkAvailability","iconRight","disabled","provider","aria-hidden","bold","delayMs","debounced","setDebounced","handle","setTimeout","clearTimeout"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"projects-hub-page.d.ts","sourceRoot":"","sources":["../../src/pages/projects-hub-page.tsx"],"names":[],"mappings":"AA8BA,wBAAgB,eAAe,CAAC,EAAC,MAAW,EAAC,EAAE;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAC,+BA0H/D"}
1
+ {"version":3,"file":"projects-hub-page.d.ts","sourceRoot":"","sources":["../../src/pages/projects-hub-page.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,eAAe,CAAC,EAAC,MAAW,EAAC,EAAE;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAC,+BAyH/D"}
@@ -12,7 +12,6 @@ import { Panel, PanelActions, PanelBody, PanelCell, PanelCellAction, PanelGrid,
12
12
  import { Skeleton } from '@shipfox/react-ui/skeleton';
13
13
  import { Text } from '@shipfox/react-ui/typography';
14
14
  import { Link, useNavigate } from '@tanstack/react-router';
15
- import { ModelProviderReminderBanner } from '#components/model-provider-reminder-banner.js';
16
15
  import { useProjectsInfiniteQuery } from '#hooks/api/projects.js';
17
16
  export function ProjectsHubPage({ search = '' }) {
18
17
  const workspace = useActiveWorkspace();
@@ -35,9 +34,6 @@ export function ProjectsHubPage({ search = '' }) {
35
34
  className: "flex w-full flex-col gap-section",
36
35
  children: [
37
36
  WorkspaceSetupChecklist ? /*#__PURE__*/ _jsx(WorkspaceSetupChecklist, {}) : null,
38
- /*#__PURE__*/ _jsx(ModelProviderReminderBanner, {
39
- workspaceId: workspace.id
40
- }),
41
37
  /*#__PURE__*/ _jsx("section", {
42
38
  "aria-label": "Projects",
43
39
  children: /*#__PURE__*/ _jsxs(Panel, {