@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +32 -0
- package/dist/hooks/api/projects.d.ts +4 -0
- package/dist/hooks/api/projects.d.ts.map +1 -1
- package/dist/hooks/api/projects.js +39 -0
- package/dist/hooks/api/projects.js.map +1 -1
- package/dist/pages/create-project-page.d.ts.map +1 -1
- package/dist/pages/create-project-page.js +58 -6
- package/dist/pages/create-project-page.js.map +1 -1
- package/dist/pages/projects-hub-page.d.ts.map +1 -1
- package/dist/pages/projects-hub-page.js +0 -4
- package/dist/pages/projects-hub-page.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/hooks/api/projects.test.ts +119 -0
- package/src/hooks/api/projects.ts +46 -0
- package/src/pages/create-project-page.test.tsx +125 -30
- package/src/pages/create-project-page.tsx +55 -5
- package/src/pages/projects-hub-page.test.tsx +4 -67
- package/src/pages/projects-hub-page.tsx +0 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/components/model-provider-reminder-banner.d.ts +0 -4
- package/dist/components/model-provider-reminder-banner.d.ts.map +0 -1
- package/dist/components/model-provider-reminder-banner.js +0 -73
- package/dist/components/model-provider-reminder-banner.js.map +0 -1
- package/src/components/model-provider-reminder-banner.tsx +0 -75
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
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":"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"}
|
|
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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-project-page.d.ts","sourceRoot":"","sources":["../../src/pages/create-project-page.tsx"],"names":[],"mappings":"
|
|
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:
|
|
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":";AAAA,SAAQA,WAAW,EAAEC,UAAU,QAAO,0BAA0B;AAChE,SAAQC,uBAAuB,QAAO,4BAA4B;AAClE,SAAQC,uBAAuB,QAAO,uBAAuB;AAC7D,SACEC,gBAAgB,EAEhBC,eAAe,EACfC,gBAAgB,EAChBC,4BAA4B,EAC5BC,yBAAyB,QACpB,+BAA+B;AACtC,SAAQC,qBAAqB,EAAEC,SAAS,QAAO,qBAAqB;AACpE,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,SAAS,EAAEC,cAAc,EAAEC,UAAU,QAAO,+BAA+B;AACnF,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,cAAc,QAAO,2BAA2B;AACxD,SAAQC,KAAK,EAAEC,YAAY,EAAEC,SAAS,EAAEC,WAAW,EAAEC,UAAU,QAAO,0BAA0B;AAChG,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAC1D,SAAQC,OAAO,QAAO,uBAAuB;AAC7C,SAAQC,IAAI,EAAEC,QAAQ,EAAEC,WAAW,QAAO,yBAAyB;AACnE,SAAQC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAClD,SAAQC,2BAA2B,QAAO,gDAAgD;AAC1F,SAAmCC,yBAAyB,QAAO,mBAAmB;AACtF,SACEC,UAAU,EACVC,wBAAwB,EACxBC,0BAA0B,QACrB,yBAAyB;AAChC,SAAQC,gBAAgB,QAAO,oBAAoB;AAEnD,SAASC,YAAYC,KAAa;IAChC,OAAOvC,WAAWwC,SAAS,CAACD,OAAOE,OAAO;AAC5C;AAEA,OAAO,SAASC;IACd,MAAMC,YAAYzC;IAClB,MAAM0C,WAAWhB;IACjB,MAAMiB,gBAAgBV;IACtB,MAAMW,WAAWhB,OAAuB;IAExC,MAAMiB,mBAAmBxC,0BAA0BoC,WAAWK;IAC9D,MAAMC,cAAcF,iBAAiBG,IAAI,IAAI,EAAE;IAE/C,MAAM,CAACC,sBAAsBC,wBAAwB,GAAGrB;IACxD,MAAMsB,qBAAqBJ,YAAYK,MAAM,KAAK,IAAIL,WAAW,CAAC,EAAE,EAAED,KAAKO;IAC3E,MAAMC,gCAAgCL,wBAAwBE;IAC9DxB,UAAU;QACR,IAAIwB,sBAAsBF,yBAAyBE,oBAAoB;YACrED,wBAAwBC;QAC1B;IACF,GAAG;QAACA;QAAoBF;KAAqB;IAE7C,MAAMM,qBAAwDR,YAAYS,IAAI,CAC5E,CAACC,aAAeA,WAAWX,EAAE,KAAKQ;IAGpC,MAAM,CAACI,YAAYC,cAAc,GAAG9B,SAAS;IAC7C,MAAM+B,sBAAsBC,kBAAkBH,YAAY;IAC1D,MAAMI,gBAAgBF,oBAAoBG,IAAI;IAE9C,MAAMC,oBAAoB5D,6BACxBkD,+BACAQ,gBAAgB;QAACG,QAAQH;IAAa,IAAIT;IAE5C,MAAMa,eAAeF,kBAAkBhB,IAAI,EAAEmB,MAAMC,QAAQ,CAACC,OAASA,KAAKH,YAAY,KAAK,EAAE;IAE7F,MAAM,CAACI,sBAAsBC,wBAAwB,GAAG1C;IACxDF,UAAU;QACR,IAAI,CAAC2C,wBAAwBJ,YAAY,CAAC,EAAE,EAAE;YAC5CK,wBAAwBL,YAAY,CAAC,EAAE,CAACM,oBAAoB;QAC9D;IACF,GAAG;QAACN;QAAcI;KAAqB;IACvC,MAAMG,qBAAqBP,aAAaV,IAAI,CAC1C,CAACkB,aAAeA,WAAWF,oBAAoB,KAAKF;IAGtD,MAAM,CAACK,aAAaC,eAAe,GAAG/C,SAAS;IAC/C,MAAMgD,qBAAqB9C,0BACzB0C,oBAAoBK,QAAQR,wBAAwB;IAEtD,MAAMS,qBAAqBlF,YAAYgF,oBAAoB;QAACG,UAAU;IAAS;IAE/E,MAAM,CAACC,WAAWC,aAAa,GAAGrD;IAClC,MAAM,CAACsD,oBAAoBC,sBAAsB,GAAGvD,SAAS;IAC7D,MAAM,CAACwD,kBAAkBC,oBAAoB,GAAGzD,SAAS;IACzD,MAAM,CAAC0D,cAAcC,gBAAgB,GAAG3D,SAAS;IACjD,MAAM4D,cAAchD,WAAWK;IAC/B,MAAM4C,+BAA+BxD,2BAA2BuD;IAEhE,MAAME,OAAOpE,QAAQ;QACnBqE,eAAe;YAACd,MAAMD;YAAoBgB,MAAMd;QAAkB;QAClEe,UAAU,OAAO,EAACzD,KAAK,EAAC;YACtB,MAAM0D,cAAcZ,qBAChB9C,MAAMwD,IAAI,GACVhG,YAAYwC,MAAMyC,IAAI,EAAE;gBAACE,UAAU;YAAS;YAChD,MAAMgB,sBAAsBrB,cAActC,MAAMyC,IAAI,GAAGD,oBAAoBkB;QAC7E;IACF;IAEApE,UAAU;QACR,IAAI,CAACgD,eAAegB,KAAKM,KAAK,CAACC,MAAM,CAACpB,IAAI,KAAKD,oBAAoB;YACjEc,KAAKQ,aAAa,CAAC,QAAQtB;YAC3B,IAAI,CAACM,oBAAoB;gBACvBK,gBAAgB;gBAChBG,KAAKQ,aAAa,CAAC,QAAQtG,YAAYgF,oBAAoB;oBAACG,UAAU;gBAAS;YACjF;QACF;IACF,GAAG;QAACH;QAAoBc;QAAMhB;QAAaQ;KAAmB;IAE9D,SAASiB,iBAAiBC,YAAoB;QAC5CnD,wBAAwBmD;QACxB9B,wBAAwBlB;IAC1B;IAEA,IAAIR,iBAAiByD,SAAS,EAAE;QAC9B,qBAAO,KAACxF;IACV;IAEA,IAAI,CAAC2B,WAAW;QACd,qBAAO,KAAC3B;IACV;IAEA,IAAI,CAAC+B,iBAAiB0D,OAAO,IAAIxD,YAAYK,MAAM,KAAK,GAAG;QACzD,qBACE,KAAC3B;YACC+E,IAAG;YACHC,QAAQ;gBAACC,eAAejE,UAAUoD,IAAI;YAAA;YACtCc,OAAO;;IAGb;IAEA,eAAeX,sBAAsBY,WAAmB,EAAEb,WAAmB;QAC3Eb,aAAa7B;QACbmC,gBAAgB;QAChB,IAAI,CAAC/C,WAAW;YACdyC,aAAa;YACbtC,SAASiE,OAAO,EAAEC;YAClB;QACF;QACA,IAAI,CAACvD,oBAAoB;YACvB2B,aAAa;YACbtC,SAASiE,OAAO,EAAEC;YAClB;QACF;QACA,IAAI,CAACrC,oBAAoB;YACvBS,aAAa;YACbtC,SAASiE,OAAO,EAAEC;YAClB;QACF;QAEA,IAAI;YACF,MAAMC,UAAgC;gBACpCtB,aAAahD,UAAUK,EAAE;gBACzBgC,MAAM8B;gBACNf,MAAME;gBACNiB,QAAQ;oBACNX,cAAc9C,mBAAmBT,EAAE;oBACnC0B,sBAAsBC,mBAAmBD,oBAAoB;gBAC/D;YACF;YACA,MAAMyC,UAAU,MAAMtE,cAAcuE,WAAW,CAACH;YAChD3F,MAAMmB,OAAO,CAAC;YACd,MAAMG,SAAS;gBACb8D,IAAI;gBACJC,QAAQ;oBAACC,eAAejE,UAAUoD,IAAI;oBAAEE,aAAakB,QAAQpB,IAAI;gBAAA;YACnE;QACF,EAAE,OAAOsB,OAAO;YACd,MAAMC,OAAOjF,iBAAiBgF;YAC9B,IAAIC,KAAKC,iBAAiB,EAAE;gBAC1BjG,MAAMkG,IAAI,CAAC;gBACX,IAAI;oBACF,MAAML,UAAU,MAAMjF,WAAWoF,KAAKC,iBAAiB;oBACvD,MAAM3E,SAAS;wBACb8D,IAAI;wBACJC,QAAQ;4BAACC,eAAejE,UAAUoD,IAAI;4BAAEE,aAAakB,QAAQpB,IAAI;wBAAA;oBACnE;gBACF,EAAE,OAAO0B,eAAe;oBACtB,MAAMC,eAAerF,iBAAiBoF;oBACtCrC,aAAa,GAAGsC,aAAaC,KAAK,CAAC,EAAE,EAAED,aAAaE,OAAO,EAAE;oBAC7DC,sBAAsB,IAAM/E,SAASiE,OAAO,EAAEC;gBAChD;gBACA;YACF;YACA,IAAIM,KAAK7B,YAAY,EAAE;gBACrBC,gBAAgB;gBAChBmC,sBAAsB,IAAMC,SAASC,cAAc,CAAC,iBAAiBf;gBACrE;YACF;YACA5B,aAAa,GAAGkC,KAAKK,KAAK,CAAC,EAAE,EAAEL,KAAKM,OAAO,EAAE;YAC7CC,sBAAsB,IAAM/E,SAASiE,OAAO,EAAEC;QAChD;IACF;IAEA,MAAMgB,iBAAiBC,QAAQxE;IAC/B,MAAMyE,uBAAuBlE,gBACzB,CAAC,0BAA0B,EAAEJ,WAAWK,IAAI,GAAG,EAAE,CAAC,GAClD;IAEJ,qBACE,MAACkE;QAAIC,WAAU;;0BACb,KAAC7G;gBAAOyB,IAAG;gBAAuBqF,SAAQ;0BAAK;;0BAI/C,KAACrG;gBAA4B2D,aAAahD,UAAUK,EAAE;;YAErDD,iBAAiB0D,OAAO,iBACvB,KAAC9F;gBAAQ2H,MAAK;gBAAQC,MAAK;0BACzB,cAAA,MAACJ;oBAAIC,WAAU;;sCACb,KAAC5G;4BAAKgH,MAAK;4BAAKJ,WAAU;sCAAuB;;sCAGjD,KAAC1H;4BACC8H,MAAK;4BACLH,SAAQ;4BACRI,SAAS,IAAM1F,iBAAiB2F,OAAO;4BACvCN,WAAU;sCACX;;;;iBAKH;0BAEJ,MAACvC;gBACCG,UAAU,CAAC2C;oBACTA,MAAMC,cAAc;oBACpBD,MAAME,eAAe;oBACrB,KAAKhD,KAAKiD,YAAY;gBACxB;gBACAC,UAAU;gBACVC,mBAAgB;gBAChBZ,WAAU;;kCAEV,MAACD;wBAAIC,WAAU;;4BACZnF,YAAYK,MAAM,GAAG,kBACpB,KAAC2F;gCAAQC,cAAW;0CAClB,cAAA,MAACjI;;sDACC,MAACG;;8DACC,KAACC;8DAAW;;gDACX4B,YAAYK,MAAM,KAAK,kBACtB,KAACpC;8DACC,cAAA,KAACR;wDAAOyI,OAAO;wDAACd,SAAQ;wDAAcG,MAAK;wDAAKJ,WAAU;kEACxD,cAAA,KAAC1G;4DACCgF,IAAG;4DACHC,QAAQ;gEAACC,eAAejE,UAAUoD,IAAI;4DAAA;sEACvC;;;qDAKH;;;sDAGN,KAAC5E;sDACC,cAAA,KAAChB;gDACC8C,aAAaA;gDACbE,sBAAsBK;gDACtB4F,UAAU9C;;;;;iCAKhB;4BAEH0B,+BACC,KAACiB;gCAAQC,cAAW;0CAClB,cAAA,MAACjI;;sDACC,MAACG;4CAAYgH,WAAU;;8DACrB,KAAC/G;8DAAW;;8DACZ,KAACH;oDAAakH,WAAU;8DACtB,cAAA,KAACrH;wDACCwH,MAAK;wDACLc,aAAY;wDACZH,cAAW;wDACX3G,OAAOqB;wDACP0F,UAAU,CAACX,QAAU9E,cAAc8E,MAAMY,MAAM,CAAChH,KAAK;wDACrD6F,WAAU;;;;;sDAIhB,KAACjH;sDACC,cAAA,KAACd;gDACC+D,cAAcA;gDACdI,sBAAsBA;gDACtB4E,UAAU3E;gDACV+E,WAAWtF,kBAAkBsC,SAAS;gDACtCiD,oBAAoBvF,kBAAkBuF,kBAAkB;gDACxDC,aAAaxF,kBAAkBwF,WAAW;gDAC1CC,YAAY,IAAMzF,kBAAkB0F,aAAa;gDACjDC,cAAc3B;;;;;iCAKpB;;;kCAGN,KAAC4B;wBAAM1B,WAAU;kCACf,cAAA,MAACnH;;8CACC,KAACG;8CACC,cAAA,KAACC;kDAAW;;;8CAEd,MAACF;oCAAUiH,WAAU;;wCAClBjD,0BACC,KAACxE;4CAAQ2H,MAAK;4CAAQC,MAAK;sDACzB,cAAA,KAACJ;gDAAI4B,KAAKjH;gDAAUkH,UAAU,CAAC;0DAC5B7E;;6CAGH;sDAEJ,KAAC8E;4CACCtG,YAAYF;4CACZyG,gBAAgBvF,oBAAoBwF;;sDAGtC,KAACtE,KAAKuE,KAAK;4CACTpF,MAAK;4CACLqF,YAAY;gDACVC,QAAQ,CAAC,EAAC/H,KAAK,EAAC,GACd/B,sBACE+B,OACA,gBACAtC,wBAAwBsK,KAAK,CAACvF,IAAI;gDAEtCgB,UAAU,CAAC,EAACzD,KAAK,EAAC,GAChB/B,sBACE+B,OACA,gBACAtC,wBAAwBsK,KAAK,CAACvF,IAAI;4CAExC;sDAEC,CAACwF,sBACA,KAAC5J;oDAAU6J,OAAM;oDAAezH,IAAG;oDAAeqE,OAAOvG,WAAW0J;8DAClE,cAAA,KAAC3J;wDACCmE,MAAK;wDACLuD,MAAK;wDACLhG,OAAOiI,MAAMrE,KAAK,CAAC5D,KAAK;wDACxB+G,UAAU,CAACX;4DACT,MAAM+B,WAAW/B,MAAMY,MAAM,CAAChH,KAAK;4DACnCuC,eAAe;4DACf0F,MAAMG,YAAY,CAACD;4DACnB,IAAI,CAACrF,oBAAoB;gEACvBG,oBAAoB;gEACpBE,gBAAgB;gEAChBG,KAAKQ,aAAa,CAAC,QAAQtG,YAAY2K,UAAU;oEAACxF,UAAU;gEAAS;4DACvE;wDACF;wDACAoF,QAAQE,MAAMI,UAAU;wDACxBvB,aAAY;;;;sDAMpB,KAACxD,KAAKuE,KAAK;4CACTpF,MAAK;4CACLqF,YAAY;gDACVC,QAAQrK,wBAAwBsK,KAAK,CAACxE,IAAI;gDAC1CC,UAAU/F,wBAAwBsK,KAAK,CAACxE,IAAI;4CAC9C;sDAEC,CAACyE,sBACA,KAAC/J;oDACCuC,IAAG;oDACHyH,OAAM;oDACNzF,MAAK;oDACLzC,OAAOiI,MAAMrE,KAAK,CAAC5D,KAAK;oDACxB+G,UAAU,CAAC/G;wDACT+C,sBAAsB;wDACtBE,oBAAoB;wDACpBE,gBAAgB;wDAChB8E,MAAMG,YAAY,CAACpI;oDACrB;oDACA+H,QAAQE,MAAMI,UAAU;oDACxBvD,OACE5B,eAAe,yCAAyC3E,WAAW0J;oDAErEK,2BACE,KAACC;wDAAK1C,WAAU;kEACb,CAAC,GAAG,EAAEzF,UAAUoD,IAAI,CAAC,GAAG,EAAEyE,MAAMrE,KAAK,CAAC5D,KAAK,EAAE;;oDAGlD8G,aAAY;oDACZjB,WAAU;oDACV2C,cAAcxF;oDACdyF,YAAY;oDACZC,SAAS3I;oDACT4I,mBAAmBtF;;;sDAKzB,KAAClF;4CACC6H,MAAK;4CACL4C,WAAU;4CACV3B,WAAW3G,cAAc2D,SAAS;4CAClC4E,UAAU,CAAC3H,sBAAsB,CAACkB;4CAClCyD,WAAU;sDACX;;;;;;;;;;;AASf;AAEA,SAAS6B,eAAe,EACtBtG,UAAU,EACVuG,cAAc,EAIf;IACC,qBACE,MAAC/B;QAAIC,WAAU;;YACZ8B,+BACC,KAAC9J;gBACC8G,QAAQvD,YAAY0H;gBACpBC,aAAW;gBACXlD,WAAU;iBAEV;0BACJ,KAAC5G;gBACCgH,MAAK;gBACL+C,IAAI;gBACJnD,WAAW8B,iBAAiB,qBAAqB;0BAEhDA,kBAAkB;;;;AAI3B;AAEA,SAASnG,kBAAqBxB,KAAQ,EAAEiJ,OAAe;IACrD,MAAM,CAACC,WAAWC,aAAa,GAAG3J,SAASQ;IAC3CV,UAAU;QACR,MAAM8J,SAASC,WAAW,IAAMF,aAAanJ,QAAQiJ;QACrD,OAAO,IAAMK,aAAaF;IAC5B,GAAG;QAACpJ;QAAOiJ;KAAQ;IACnB,OAAOC;AACT"}
|
|
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":"
|
|
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, {
|