@shipfox/client-projects 4.0.0 → 6.0.1

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.
Files changed (109) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +78 -0
  3. package/dist/chrome.d.ts.map +1 -1
  4. package/dist/chrome.js +7 -10
  5. package/dist/chrome.js.map +1 -1
  6. package/dist/components/model-provider-reminder-banner.d.ts.map +1 -1
  7. package/dist/components/model-provider-reminder-banner.js +1 -1
  8. package/dist/components/model-provider-reminder-banner.js.map +1 -1
  9. package/dist/components/source-strip.d.ts +2 -2
  10. package/dist/components/source-strip.d.ts.map +1 -1
  11. package/dist/components/source-strip.js +2 -2
  12. package/dist/components/source-strip.js.map +1 -1
  13. package/dist/core/definition.d.ts +32 -0
  14. package/dist/core/definition.d.ts.map +1 -0
  15. package/dist/core/definition.js +3 -0
  16. package/dist/core/definition.js.map +1 -0
  17. package/dist/core/project.d.ts +26 -0
  18. package/dist/core/project.d.ts.map +1 -0
  19. package/dist/core/project.js +12 -0
  20. package/dist/core/project.js.map +1 -0
  21. package/dist/feature.d.ts +8 -10
  22. package/dist/feature.d.ts.map +1 -1
  23. package/dist/feature.js +11 -22
  24. package/dist/feature.js.map +1 -1
  25. package/dist/hooks/api/definitions.d.ts +12 -88
  26. package/dist/hooks/api/definitions.d.ts.map +1 -1
  27. package/dist/hooks/api/definitions.js +18 -10
  28. package/dist/hooks/api/definitions.js.map +1 -1
  29. package/dist/hooks/api/mappers.d.ts +17 -0
  30. package/dist/hooks/api/mappers.d.ts.map +1 -0
  31. package/dist/hooks/api/mappers.js +56 -0
  32. package/dist/hooks/api/mappers.js.map +1 -0
  33. package/dist/hooks/api/projects.d.ts +19 -81
  34. package/dist/hooks/api/projects.d.ts.map +1 -1
  35. package/dist/hooks/api/projects.js +72 -18
  36. package/dist/hooks/api/projects.js.map +1 -1
  37. package/dist/index.d.ts +3 -3
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +2 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/pages/create-project-page.d.ts.map +1 -1
  42. package/dist/pages/create-project-page.js +12 -37
  43. package/dist/pages/create-project-page.js.map +1 -1
  44. package/dist/pages/create-project-page.stories.d.ts.map +1 -1
  45. package/dist/pages/create-project-page.stories.js +4 -5
  46. package/dist/pages/create-project-page.stories.js.map +1 -1
  47. package/dist/pages/home-router.d.ts +4 -1
  48. package/dist/pages/home-router.d.ts.map +1 -1
  49. package/dist/pages/home-router.js +4 -2
  50. package/dist/pages/home-router.js.map +1 -1
  51. package/dist/pages/projects-hub-page.d.ts +3 -1
  52. package/dist/pages/projects-hub-page.d.ts.map +1 -1
  53. package/dist/pages/projects-hub-page.js +26 -30
  54. package/dist/pages/projects-hub-page.js.map +1 -1
  55. package/dist/routes/home.d.ts +3 -2
  56. package/dist/routes/home.d.ts.map +1 -1
  57. package/dist/routes/home.js +7 -2
  58. package/dist/routes/home.js.map +1 -1
  59. package/dist/routes/inputs.d.ts +6 -0
  60. package/dist/routes/inputs.d.ts.map +1 -0
  61. package/dist/routes/inputs.js +14 -0
  62. package/dist/routes/inputs.js.map +1 -0
  63. package/dist/routes/search.d.ts +5 -0
  64. package/dist/routes/search.d.ts.map +1 -0
  65. package/dist/routes/search.js +8 -0
  66. package/dist/routes/search.js.map +1 -0
  67. package/dist/tsconfig.test.tsbuildinfo +1 -1
  68. package/package.json +14 -17
  69. package/src/chrome.tsx +11 -6
  70. package/src/components/model-provider-reminder-banner.tsx +1 -3
  71. package/src/components/project-switcher.test.tsx +2 -1
  72. package/src/components/source-strip.tsx +5 -5
  73. package/src/core/definition.ts +31 -0
  74. package/src/core/project.ts +47 -0
  75. package/src/feature.ts +13 -23
  76. package/src/hooks/api/definitions.ts +54 -19
  77. package/src/hooks/api/mappers.ts +67 -0
  78. package/src/hooks/api/projects.test.ts +11 -4
  79. package/src/hooks/api/projects.ts +125 -28
  80. package/src/index.ts +10 -6
  81. package/src/pages/create-project-page.stories.tsx +7 -5
  82. package/src/pages/create-project-page.test.tsx +3 -1
  83. package/src/pages/create-project-page.tsx +16 -51
  84. package/src/pages/home-router.tsx +3 -2
  85. package/src/pages/projects-hub-page.test.tsx +13 -8
  86. package/src/pages/projects-hub-page.tsx +24 -34
  87. package/src/routes/home.tsx +6 -2
  88. package/src/routes/inputs.test.ts +14 -0
  89. package/src/routes/inputs.ts +15 -0
  90. package/src/routes/search.ts +8 -0
  91. package/test/pages.tsx +2 -17
  92. package/tsconfig.build.tsbuildinfo +1 -1
  93. package/dist/components/workspace-setup-guard.d.ts +0 -9
  94. package/dist/components/workspace-setup-guard.d.ts.map +0 -1
  95. package/dist/components/workspace-setup-guard.js +0 -160
  96. package/dist/components/workspace-setup-guard.js.map +0 -1
  97. package/dist/pages/project-workflows-page.d.ts +0 -4
  98. package/dist/pages/project-workflows-page.d.ts.map +0 -1
  99. package/dist/pages/project-workflows-page.js +0 -481
  100. package/dist/pages/project-workflows-page.js.map +0 -1
  101. package/dist/routes/workflows.d.ts +0 -5
  102. package/dist/routes/workflows.d.ts.map +0 -1
  103. package/dist/routes/workflows.js +0 -16
  104. package/dist/routes/workflows.js.map +0 -1
  105. package/src/components/workspace-setup-guard.test.tsx +0 -534
  106. package/src/components/workspace-setup-guard.tsx +0 -185
  107. package/src/pages/project-workflows-page.test.tsx +0 -258
  108. package/src/pages/project-workflows-page.tsx +0 -445
  109. package/src/routes/workflows.tsx +0 -10
@@ -10,21 +10,19 @@ import { FullPageLoader } from '@shipfox/react-ui/loader';
10
10
  import { toast } from '@shipfox/react-ui/toast';
11
11
  import { Header, Text } from '@shipfox/react-ui/typography';
12
12
  import { useForm } from '@tanstack/react-form';
13
- import { useQueryClient } from '@tanstack/react-query';
14
13
  import { Link, Navigate, useNavigate } from '@tanstack/react-router';
15
14
  import { useEffect, useRef, useState } from 'react';
16
15
  import { ModelProviderReminderBanner } from '#components/model-provider-reminder-banner.js';
17
- import { projectsQueryKeys, useCreateProjectMutation } from '#hooks/api/projects.js';
16
+ import { projectNameFromRepository } from '#core/project.js';
17
+ import { useCreateProjectMutation } from '#hooks/api/projects.js';
18
18
  import { projectErrorCopy } from '#project-error.js';
19
- const REPOSITORY_NAME_SPLIT_RE = /[/-]/;
20
19
  export function CreateProjectPage() {
21
20
  const workspace = useMaybeActiveWorkspace();
22
- const queryClient = useQueryClient();
23
21
  const navigate = useNavigate();
24
22
  const createProject = useCreateProjectMutation();
25
23
  const errorRef = useRef(null);
26
24
  const connectionsQuery = useSourceConnectionsQuery(workspace?.id);
27
- const connections = connectionsQuery.data?.connections ?? [];
25
+ const connections = connectionsQuery.data ?? [];
28
26
  const [selectedConnectionId, setSelectedConnectionId] = useState();
29
27
  const singleConnectionId = connections.length === 1 ? connections[0]?.id : undefined;
30
28
  const effectiveSelectedConnectionId = selectedConnectionId ?? singleConnectionId;
@@ -47,13 +45,13 @@ export function CreateProjectPage() {
47
45
  const [selectedRepositoryId, setSelectedRepositoryId] = useState();
48
46
  useEffect(()=>{
49
47
  if (!selectedRepositoryId && repositories[0]) {
50
- setSelectedRepositoryId(repositories[0].external_repository_id);
48
+ setSelectedRepositoryId(repositories[0].externalRepositoryId);
51
49
  }
52
50
  }, [
53
51
  repositories,
54
52
  selectedRepositoryId
55
53
  ]);
56
- const selectedRepository = repositories.find((repository)=>repository.external_repository_id === selectedRepositoryId);
54
+ const selectedRepository = repositories.find((repository)=>repository.externalRepositoryId === selectedRepositoryId);
57
55
  const [nameTouched, setNameTouched] = useState(false);
58
56
  const defaultProjectName = projectNameFromRepository(selectedRepository?.name ?? selectedRepositoryId ?? '');
59
57
  const [formError, setFormError] = useState();
@@ -111,20 +109,15 @@ export function CreateProjectPage() {
111
109
  return;
112
110
  }
113
111
  try {
114
- const projectBody = createProjectBodySchema.parse({
115
- workspace_id: workspace.id,
112
+ const command = {
113
+ workspaceId: workspace.id,
116
114
  name: projectName,
117
115
  source: {
118
- connection_id: selectedConnection.id,
119
- external_repository_id: selectedRepository.external_repository_id
116
+ connectionId: selectedConnection.id,
117
+ externalRepositoryId: selectedRepository.externalRepositoryId
120
118
  }
121
- });
122
- const project = await createProject.mutateAsync(projectBody);
123
- setWorkspaceProjectExists(queryClient, workspace.id, project);
124
- queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);
125
- await queryClient.invalidateQueries({
126
- queryKey: projectsQueryKeys.list(workspace.id)
127
- });
119
+ };
120
+ const project = await createProject.mutateAsync(command);
128
121
  toast.success('Project created.');
129
122
  await navigate({
130
123
  to: '/workspaces/$wid/projects/$pid',
@@ -136,13 +129,6 @@ export function CreateProjectPage() {
136
129
  } catch (error) {
137
130
  const copy = projectErrorCopy(error);
138
131
  if (copy.existingProjectId) {
139
- await queryClient.invalidateQueries({
140
- queryKey: projectsQueryKeys.exists(workspace.id),
141
- refetchType: 'active'
142
- });
143
- await queryClient.invalidateQueries({
144
- queryKey: projectsQueryKeys.list(workspace.id)
145
- });
146
132
  toast.info('Project already exists.');
147
133
  await navigate({
148
134
  to: '/workspaces/$wid/projects/$pid',
@@ -321,7 +307,7 @@ export function CreateProjectPage() {
321
307
  }) : null,
322
308
  /*#__PURE__*/ _jsx(ProjectSummary, {
323
309
  connection: selectedConnection,
324
- repositoryName: selectedRepository?.full_name
310
+ repositoryName: selectedRepository?.fullName
325
311
  }),
326
312
  /*#__PURE__*/ _jsx(form.Field, {
327
313
  name: "name",
@@ -380,9 +366,6 @@ function ProjectSummary({ connection, repositoryName }) {
380
366
  ]
381
367
  });
382
368
  }
383
- function projectNameFromRepository(repositoryId) {
384
- return repositoryId.trim().split(REPOSITORY_NAME_SPLIT_RE).filter(Boolean).map((part)=>part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
385
- }
386
369
  function useDebouncedValue(value, delayMs) {
387
370
  const [debounced, setDebounced] = useState(value);
388
371
  useEffect(()=>{
@@ -394,13 +377,5 @@ function useDebouncedValue(value, delayMs) {
394
377
  ]);
395
378
  return debounced;
396
379
  }
397
- function setWorkspaceProjectExists(queryClient, workspaceId, project) {
398
- queryClient.setQueryData(projectsQueryKeys.exists(workspaceId), {
399
- projects: [
400
- project
401
- ],
402
- next_cursor: null
403
- });
404
- }
405
380
 
406
381
  //# sourceMappingURL=create-project-page.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/create-project-page.tsx"],"sourcesContent":["import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';\nimport {\n createProjectBodySchema,\n type ListProjectsResponseDto,\n type ProjectResponseDto,\n} from '@shipfox/api-projects-dto';\nimport {useMaybeActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionPicker,\n IntegrationIcon,\n RepositoryPicker,\n useRepositoriesInfiniteQuery,\n useSourceConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {displayNameFieldError} 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 {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {type QueryClient, useQueryClient} from '@tanstack/react-query';\nimport {Link, Navigate, useNavigate} from '@tanstack/react-router';\nimport {useEffect, useRef, useState} from 'react';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport {projectsQueryKeys, useCreateProjectMutation} from '#hooks/api/projects.js';\nimport {projectErrorCopy} from '#project-error.js';\n\nconst REPOSITORY_NAME_SPLIT_RE = /[/-]/;\n\nexport function CreateProjectPage() {\n const workspace = useMaybeActiveWorkspace();\n const queryClient = useQueryClient();\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?.connections ?? [];\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: IntegrationConnectionDto | 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].external_repository_id);\n }\n }, [repositories, selectedRepositoryId]);\n const selectedRepository = repositories.find(\n (repository) => repository.external_repository_id === selectedRepositoryId,\n );\n\n const [nameTouched, setNameTouched] = useState(false);\n const defaultProjectName = projectNameFromRepository(\n selectedRepository?.name ?? selectedRepositoryId ?? '',\n );\n\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {name: defaultProjectName},\n onSubmit: async ({value}) => {\n await createProjectFromForm(nameTouched ? value.name : defaultProjectName);\n },\n });\n\n useEffect(() => {\n if (!nameTouched && form.state.values.name !== defaultProjectName) {\n form.setFieldValue('name', defaultProjectName);\n }\n }, [defaultProjectName, form, nameTouched]);\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 <Navigate to=\"/workspaces/$wid/integrations\" params={{wid: workspace.id}} replace />;\n }\n\n async function createProjectFromForm(projectName: string) {\n setFormError(undefined);\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 projectBody = createProjectBodySchema.parse({\n workspace_id: workspace.id,\n name: projectName,\n source: {\n connection_id: selectedConnection.id,\n external_repository_id: selectedRepository.external_repository_id,\n },\n });\n const project = await createProject.mutateAsync(projectBody);\n setWorkspaceProjectExists(queryClient, workspace.id, project);\n queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(workspace.id)});\n toast.success('Project created.');\n await navigate({\n to: '/workspaces/$wid/projects/$pid',\n params: {wid: workspace.id, pid: project.id},\n });\n } catch (error) {\n const copy = projectErrorCopy(error);\n if (copy.existingProjectId) {\n await queryClient.invalidateQueries({\n queryKey: projectsQueryKeys.exists(workspace.id),\n refetchType: 'active',\n });\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(workspace.id)});\n toast.info('Project already exists.');\n await navigate({\n to: '/workspaces/$wid/projects/$pid',\n params: {wid: workspace.id, pid: copy.existingProjectId},\n });\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-24\">\n <header className=\"flex flex-col gap-6\">\n <Header id=\"create-project-title\" variant=\"h1\">\n Create project\n </Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n A Shipfox project starts from a Git repository. Choose the repository Shipfox should\n track, then give the project a name.\n </Text>\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-12\">\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-32 lg:grid-cols-[minmax(0,1fr)_340px]\"\n >\n <div className=\"flex min-w-0 flex-col gap-32\">\n <section className=\"flex flex-col gap-16\" aria-label=\"Source integration\">\n <div className=\"flex items-start justify-between gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Source integration</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Choose the integration that can access the repository.\n </Text>\n </div>\n\n {connections.length === 1 ? (\n <Button asChild variant=\"transparent\" size=\"sm\" className=\"shrink-0\">\n <Link to=\"/workspaces/$wid/integrations\" params={{wid: workspace.id}}>\n Add another integration\n </Link>\n </Button>\n ) : null}\n </div>\n\n {connections.length > 0 ? (\n <ConnectionPicker\n connections={connections}\n selectedConnectionId={effectiveSelectedConnectionId}\n onSelect={selectConnection}\n />\n ) : null}\n </section>\n\n {showRepoPicker ? (\n <section className=\"flex flex-col gap-16\" aria-label=\"Repository\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Repository</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Select the repository this project tracks.\n </Text>\n </div>\n\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 searchValue={repoFilter}\n onSearchChange={setRepoFilter}\n />\n </section>\n ) : null}\n </div>\n\n <aside className=\"lg:sticky lg:top-32\">\n <div className=\"flex flex-col gap-18 rounded-8 border border-border-neutral-base bg-background-neutral-base p-20\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Project details</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Confirm the source and create the project.\n </Text>\n </div>\n\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?.full_name}\n />\n\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\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 setNameTouched(true);\n field.handleChange(event.target.value);\n }}\n onBlur={field.handleBlur}\n placeholder=\"Platform\"\n />\n </FormField>\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 </div>\n </aside>\n </form>\n </div>\n );\n}\n\nfunction ProjectSummary({\n connection,\n repositoryName,\n}: {\n connection: IntegrationConnectionDto | undefined;\n repositoryName: string | undefined;\n}) {\n return (\n <div className=\"flex min-w-0 items-center gap-10\">\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 projectNameFromRepository(repositoryId: string): string {\n return repositoryId\n .trim()\n .split(REPOSITORY_NAME_SPLIT_RE)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ');\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\nfunction setWorkspaceProjectExists(\n queryClient: QueryClient,\n workspaceId: string,\n project: ProjectResponseDto,\n) {\n queryClient.setQueryData<ListProjectsResponseDto>(projectsQueryKeys.exists(workspaceId), {\n projects: [project],\n next_cursor: null,\n });\n}\n"],"names":["createProjectBodySchema","useMaybeActiveWorkspace","ConnectionPicker","IntegrationIcon","RepositoryPicker","useRepositoriesInfiniteQuery","useSourceConnectionsQuery","displayNameFieldError","Button","Callout","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","useQueryClient","Link","Navigate","useNavigate","useEffect","useRef","useState","ModelProviderReminderBanner","projectsQueryKeys","useCreateProjectMutation","projectErrorCopy","REPOSITORY_NAME_SPLIT_RE","CreateProjectPage","workspace","queryClient","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","external_repository_id","selectedRepository","repository","nameTouched","setNameTouched","defaultProjectName","projectNameFromRepository","name","formError","setFormError","form","defaultValues","onSubmit","value","createProjectFromForm","state","values","setFieldValue","selectConnection","connectionId","isPending","isError","to","params","wid","replace","projectName","current","focus","projectBody","parse","workspace_id","source","connection_id","project","mutateAsync","setWorkspaceProjectExists","setQueryData","detail","invalidateQueries","queryKey","list","success","pid","error","copy","existingProjectId","exists","refetchType","info","title","message","requestAnimationFrame","showRepoPicker","Boolean","filteredEmptyMessage","div","className","header","variant","size","workspaceId","role","type","onClick","refetch","event","preventDefault","stopPropagation","handleSubmit","noValidate","aria-labelledby","section","aria-label","asChild","onSelect","isLoading","isFetchingNextPage","hasNextPage","onLoadMore","fetchNextPage","emptyMessage","searchValue","onSearchChange","aside","ref","tabIndex","ProjectSummary","repositoryName","full_name","Field","validators","onBlur","shape","field","label","onChange","handleChange","target","handleBlur","placeholder","iconRight","disabled","provider","aria-hidden","bold","repositoryId","split","filter","map","part","charAt","toUpperCase","slice","join","delayMs","debounced","setDebounced","handle","setTimeout","clearTimeout","projects","next_cursor"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/create-project-page.tsx"],"sourcesContent":["import {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} 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 {FullPageLoader} from '@shipfox/react-ui/loader';\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 {useCreateProjectMutation} from '#hooks/api/projects.js';\nimport {projectErrorCopy} from '#project-error.js';\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\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {name: defaultProjectName},\n onSubmit: async ({value}) => {\n await createProjectFromForm(nameTouched ? value.name : defaultProjectName);\n },\n });\n\n useEffect(() => {\n if (!nameTouched && form.state.values.name !== defaultProjectName) {\n form.setFieldValue('name', defaultProjectName);\n }\n }, [defaultProjectName, form, nameTouched]);\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 <Navigate to=\"/workspaces/$wid/integrations\" params={{wid: workspace.id}} replace />;\n }\n\n async function createProjectFromForm(projectName: string) {\n setFormError(undefined);\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 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: '/workspaces/$wid/projects/$pid',\n params: {wid: workspace.id, pid: project.id},\n });\n } catch (error) {\n const copy = projectErrorCopy(error);\n if (copy.existingProjectId) {\n toast.info('Project already exists.');\n await navigate({\n to: '/workspaces/$wid/projects/$pid',\n params: {wid: workspace.id, pid: copy.existingProjectId},\n });\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-24\">\n <header className=\"flex flex-col gap-6\">\n <Header id=\"create-project-title\" variant=\"h1\">\n Create project\n </Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n A Shipfox project starts from a Git repository. Choose the repository Shipfox should\n track, then give the project a name.\n </Text>\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-12\">\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-32 lg:grid-cols-[minmax(0,1fr)_340px]\"\n >\n <div className=\"flex min-w-0 flex-col gap-32\">\n <section className=\"flex flex-col gap-16\" aria-label=\"Source integration\">\n <div className=\"flex items-start justify-between gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Source integration</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Choose the integration that can access the repository.\n </Text>\n </div>\n\n {connections.length === 1 ? (\n <Button asChild variant=\"transparent\" size=\"sm\" className=\"shrink-0\">\n <Link to=\"/workspaces/$wid/integrations\" params={{wid: workspace.id}}>\n Add another integration\n </Link>\n </Button>\n ) : null}\n </div>\n\n {connections.length > 0 ? (\n <ConnectionPicker\n connections={connections}\n selectedConnectionId={effectiveSelectedConnectionId}\n onSelect={selectConnection}\n />\n ) : null}\n </section>\n\n {showRepoPicker ? (\n <section className=\"flex flex-col gap-16\" aria-label=\"Repository\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Repository</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Select the repository this project tracks.\n </Text>\n </div>\n\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 searchValue={repoFilter}\n onSearchChange={setRepoFilter}\n />\n </section>\n ) : null}\n </div>\n\n <aside className=\"lg:sticky lg:top-32\">\n <div className=\"flex flex-col gap-18 rounded-8 border border-border-neutral-base bg-background-neutral-base p-20\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Project details</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Confirm the source and create the project.\n </Text>\n </div>\n\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(value, 'Project name', createProjectBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\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 setNameTouched(true);\n field.handleChange(event.target.value);\n }}\n onBlur={field.handleBlur}\n placeholder=\"Platform\"\n />\n </FormField>\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 </div>\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-10\">\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":["createProjectBodySchema","useMaybeActiveWorkspace","ConnectionPicker","IntegrationIcon","RepositoryPicker","useRepositoriesInfiniteQuery","useSourceConnectionsQuery","displayNameFieldError","Button","Callout","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","Link","Navigate","useNavigate","useEffect","useRef","useState","ModelProviderReminderBanner","projectNameFromRepository","useCreateProjectMutation","projectErrorCopy","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","formError","setFormError","form","defaultValues","onSubmit","value","createProjectFromForm","state","values","setFieldValue","selectConnection","connectionId","isPending","isError","to","params","wid","replace","projectName","current","focus","command","workspaceId","source","project","mutateAsync","success","pid","error","copy","existingProjectId","info","title","message","requestAnimationFrame","showRepoPicker","Boolean","filteredEmptyMessage","div","className","header","variant","size","role","type","onClick","refetch","event","preventDefault","stopPropagation","handleSubmit","noValidate","aria-labelledby","section","aria-label","asChild","onSelect","isLoading","isFetchingNextPage","hasNextPage","onLoadMore","fetchNextPage","emptyMessage","searchValue","onSearchChange","aside","ref","tabIndex","ProjectSummary","repositoryName","fullName","Field","validators","onBlur","shape","field","label","onChange","handleChange","target","handleBlur","placeholder","iconRight","disabled","provider","aria-hidden","bold","delayMs","debounced","setDebounced","handle","setTimeout","clearTimeout"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-project-page.stories.d.ts","sourceRoot":"","sources":["../../src/pages/create-project-page.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAmBrD,KAAK,QAAQ,GACT,YAAY,GACZ,YAAY,GACZ,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,CAAC;AAExB,UAAU,2BAA2B;IACnC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAiBD,iBAAS,sBAAsB,CAAC,EAAC,QAAQ,EAAC,EAAE,2BAA2B,+BA6BtE;AAED,QAAA,MAAM,IAAI;;;;;;;;;CAKqC,CAAC;AAEhD,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,SAAS,EAAE,KAEvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAE9B,CAAC"}
1
+ {"version":3,"file":"create-project-page.stories.d.ts","sourceRoot":"","sources":["../../src/pages/create-project-page.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAmBrD,KAAK,QAAQ,GACT,YAAY,GACZ,YAAY,GACZ,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,CAAC;AAExB,UAAU,2BAA2B;IACnC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAcD,iBAAS,sBAAsB,CAAC,EAAC,QAAQ,EAAC,EAAE,2BAA2B,+BA6BtE;AAED,QAAA,MAAM,IAAI;;;;;;;;;CAKqC,CAAC;AAEhD,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,SAAS,EAAE,KAEvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAE9B,CAAC"}
@@ -18,10 +18,7 @@ const authState = {
18
18
  id: '22222222-2222-4222-8222-222222222222',
19
19
  email: 'platform@example.com',
20
20
  name: 'Platform Engineer',
21
- email_verified_at: '2026-01-01T00:00:00.000Z',
22
- status: 'active',
23
- created_at: '2026-01-01T00:00:00.000Z',
24
- updated_at: '2026-01-01T00:00:00.000Z'
21
+ emailVerifiedAt: '2026-01-01T00:00:00.000Z'
25
22
  },
26
23
  workspaces: [
27
24
  {
@@ -154,7 +151,8 @@ function fetchForScenario(scenario) {
154
151
  configs: [
155
152
  modelProviderConfig()
156
153
  ],
157
- default_provider_id: 'openai'
154
+ default_provider_id: 'openai',
155
+ default_harness_id: null
158
156
  }));
159
157
  }
160
158
  if (url.pathname === '/integration-connections') {
@@ -309,6 +307,7 @@ function repository(overrides) {
309
307
  }
310
308
  function modelProviderConfig() {
311
309
  return {
310
+ kind: 'builtin',
312
311
  workspace_id: WORKSPACE_ID,
313
312
  provider_id: 'openai',
314
313
  default_model: 'gpt-5.5-pro',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/create-project-page.stories.tsx"],"sourcesContent":["import type {IntegrationConnectionDto, RepositoryDto} from '@shipfox/api-integration-core-dto';\nimport {configureApiClient} from '@shipfox/client-api';\nimport {type AuthState, authStateAtom} from '@shipfox/client-auth';\nimport {Toaster} from '@shipfox/react-ui/toast';\nimport type {Meta, StoryObj} from '@storybook/react';\nimport {QueryClient, QueryClientProvider} from '@tanstack/react-query';\nimport {\n createMemoryHistory,\n createRootRoute,\n createRoute,\n createRouter,\n Outlet,\n RouterProvider,\n} from '@tanstack/react-router';\nimport {createStore, Provider as JotaiProvider} from 'jotai';\nimport {useMemo} from 'react';\nimport {CreateProjectPage} from './create-project-page.js';\n\nconst WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';\nconst WORKSPACE_PATH = `/workspaces/${WORKSPACE_ID}/projects/new`;\nconst GITHUB_CONNECTION_ID = '33333333-3333-4333-8333-333333333333';\nconst GITEA_CONNECTION_ID = '44444444-4444-4444-8444-444444444444';\n\ntype Scenario =\n | 'playground'\n | 'long-names'\n | 'repository-loading'\n | 'empty-repositories'\n | 'connections-error';\n\ninterface CreateProjectPageStoryProps {\n scenario: Scenario;\n}\n\nconst authState: AuthState = {\n status: 'authenticated',\n token: 'token',\n user: {\n id: '22222222-2222-4222-8222-222222222222',\n email: 'platform@example.com',\n name: 'Platform Engineer',\n email_verified_at: '2026-01-01T00:00:00.000Z',\n status: 'active',\n created_at: '2026-01-01T00:00:00.000Z',\n updated_at: '2026-01-01T00:00:00.000Z',\n },\n workspaces: [{id: WORKSPACE_ID, name: 'Acme', membershipId: 'membership-1'}],\n};\n\nfunction CreateProjectPageStory({scenario}: CreateProjectPageStoryProps) {\n configureApiClient({\n baseUrl: 'https://api.example.test',\n fetchImpl: fetchForScenario(scenario),\n });\n\n const queryClient = useMemo(\n () => new QueryClient({defaultOptions: {queries: {retry: false}}}),\n [],\n );\n const store = useMemo(() => {\n const nextStore = createStore();\n nextStore.set(authStateAtom, authState);\n return nextStore;\n }, []);\n const router = useMemo(() => createStoryRouter(), []);\n\n return (\n <QueryClientProvider client={queryClient}>\n <JotaiProvider store={store}>\n <div className=\"min-h-screen bg-background-neutral-background px-24 py-32\">\n <div className=\"mx-auto w-full max-w-[1120px]\">\n <RouterProvider router={router} />\n </div>\n </div>\n <Toaster />\n </JotaiProvider>\n </QueryClientProvider>\n );\n}\n\nconst meta = {\n title: 'Projects/CreateProjectPage',\n component: CreateProjectPageStory,\n parameters: {layout: 'fullscreen'},\n args: {scenario: 'playground'},\n} satisfies Meta<typeof CreateProjectPageStory>;\n\nexport default meta;\ntype Story = StoryObj<typeof meta>;\n\nexport const Playground: Story = {};\n\nexport const LongNames: Story = {\n args: {scenario: 'long-names'},\n};\n\nexport const RepositoryLoading: Story = {\n args: {scenario: 'repository-loading'},\n};\n\nexport const EmptyRepositories: Story = {\n args: {scenario: 'empty-repositories'},\n};\n\nexport const ConnectionsError: Story = {\n args: {scenario: 'connections-error'},\n};\n\nfunction createStoryRouter() {\n const rootRoute = createRootRoute({component: Outlet});\n const workspaceRoute = createRoute({\n getParentRoute: () => rootRoute,\n path: '/workspaces/$wid',\n component: Outlet,\n });\n const createProjectRoute = createRoute({\n getParentRoute: () => workspaceRoute,\n path: 'projects/new',\n component: CreateProjectPage,\n });\n const integrationsRoute = createRoute({\n getParentRoute: () => workspaceRoute,\n path: 'integrations',\n component: () => <div />,\n });\n const modelProvidersRoute = createRoute({\n getParentRoute: () => workspaceRoute,\n path: 'settings/agents',\n component: () => <div />,\n });\n const projectRoute = createRoute({\n getParentRoute: () => workspaceRoute,\n path: 'projects/$pid',\n component: () => <div />,\n });\n\n return createRouter({\n history: createMemoryHistory({initialEntries: [WORKSPACE_PATH]}),\n routeTree: rootRoute.addChildren([\n workspaceRoute.addChildren([\n createProjectRoute,\n integrationsRoute,\n modelProvidersRoute,\n projectRoute,\n ]),\n ]),\n });\n}\n\nfunction fetchForScenario(scenario: Scenario): typeof fetch {\n return (input, init) => {\n const url = requestUrl(input);\n const method = input instanceof Request ? input.method : (init?.method ?? 'GET');\n if (url.pathname === `/workspaces/${WORKSPACE_ID}/agent/model-providers`) {\n return Promise.resolve(\n jsonResponse({configs: [modelProviderConfig()], default_provider_id: 'openai'}),\n );\n }\n if (url.pathname === '/integration-connections') {\n if (scenario === 'connections-error') return Promise.resolve(errorResponse());\n return Promise.resolve(jsonResponse({connections: connectionsForScenario(scenario)}));\n }\n if (\n url.pathname.startsWith('/integration-connections/') &&\n url.pathname.endsWith('/repositories')\n ) {\n if (scenario === 'repository-loading') return new Promise<Response>(() => undefined);\n return Promise.resolve(\n jsonResponse({repositories: repositoriesForScenario(scenario), next_cursor: null}),\n );\n }\n if (url.pathname === '/projects' && method === 'POST') {\n return Promise.resolve(\n jsonResponse({\n id: '99999999-9999-4999-8999-999999999999',\n workspace_id: WORKSPACE_ID,\n name: 'Platform',\n source: {\n connection_id: GITHUB_CONNECTION_ID,\n external_repository_id: 'platform',\n },\n created_at: '2026-01-01T00:00:00.000Z',\n updated_at: '2026-01-01T00:00:00.000Z',\n }),\n );\n }\n return Promise.resolve(jsonResponse({}, {status: 404}));\n };\n}\n\nfunction connectionsForScenario(scenario: Scenario): IntegrationConnectionDto[] {\n if (scenario === 'long-names') {\n return [\n connection({\n id: GITHUB_CONNECTION_ID,\n provider: 'github',\n display_name: 'acme-platform-engineering-production',\n external_account_id: 'acme-platform-engineering-production',\n }),\n connection({\n id: GITEA_CONNECTION_ID,\n provider: 'gitea',\n display_name: 'git.internal.acme.example/platform-tools',\n external_account_id: 'git.internal.acme.example/platform-tools',\n }),\n ];\n }\n\n return [\n connection({\n id: GITHUB_CONNECTION_ID,\n provider: 'github',\n display_name: 'acme',\n external_account_id: 'acme',\n }),\n connection({\n id: GITEA_CONNECTION_ID,\n provider: 'gitea',\n display_name: 'git.acme.internal',\n external_account_id: 'git.acme.internal',\n }),\n ];\n}\n\nfunction repositoriesForScenario(scenario: Scenario): RepositoryDto[] {\n if (scenario === 'empty-repositories') return [];\n if (scenario === 'long-names') {\n return [\n repository({\n external_repository_id: 'acme-platform-infrastructure-control-plane',\n name: 'acme-platform-infrastructure-control-plane',\n full_name: 'acme-engineering/acme-platform-infrastructure-control-plane',\n default_branch: 'production',\n }),\n repository({\n external_repository_id: 'workflow-automation-and-runner-orchestration',\n name: 'workflow-automation-and-runner-orchestration',\n full_name: 'acme-engineering/workflow-automation-and-runner-orchestration',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'customer-deployment-pipelines',\n name: 'customer-deployment-pipelines',\n full_name: 'acme-solutions/customer-deployment-pipelines',\n default_branch: 'release',\n }),\n ];\n }\n\n return [\n repository({\n external_repository_id: 'platform',\n name: 'platform',\n full_name: 'acme/platform',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'api',\n name: 'api',\n full_name: 'acme/api',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'client',\n name: 'client',\n full_name: 'acme/client',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'runners',\n name: 'runners',\n full_name: 'acme/runners',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'workflows',\n name: 'workflows',\n full_name: 'acme/workflows',\n default_branch: 'develop',\n }),\n ];\n}\n\nfunction connection(\n overrides: Partial<IntegrationConnectionDto> & Pick<IntegrationConnectionDto, 'id'>,\n): IntegrationConnectionDto {\n return {\n workspace_id: WORKSPACE_ID,\n provider: 'github',\n external_account_id: 'acme',\n slug: 'github_acme',\n display_name: 'GitHub',\n lifecycle_status: 'active',\n capabilities: ['source_control'],\n created_at: '2026-01-01T00:00:00.000Z',\n updated_at: '2026-01-01T00:00:00.000Z',\n ...overrides,\n };\n}\n\nfunction repository(overrides: Partial<RepositoryDto>): RepositoryDto {\n return {\n connection_id: GITHUB_CONNECTION_ID,\n external_repository_id: 'platform',\n owner: 'acme',\n name: 'platform',\n full_name: 'acme/platform',\n default_branch: 'main',\n visibility: 'private',\n clone_url: 'https://github.example.test/acme/platform.git',\n html_url: 'https://github.example.test/acme/platform',\n ...overrides,\n };\n}\n\nfunction modelProviderConfig() {\n return {\n workspace_id: WORKSPACE_ID,\n provider_id: 'openai',\n default_model: 'gpt-5.5-pro',\n credential_fields: ['api_key'],\n lifecycle_status: 'active',\n created_at: '2026-01-01T00:00:00.000Z',\n updated_at: '2026-01-01T00:00:00.000Z',\n };\n}\n\nfunction requestUrl(input: RequestInfo | URL): URL {\n if (input instanceof Request) return new URL(input.url);\n if (input instanceof URL) return input;\n return new URL(input, 'https://api.example.test');\n}\n\nfunction jsonResponse(body: unknown, init: ResponseInit = {}) {\n return new Response(JSON.stringify(body), {\n status: 200,\n headers: {'content-type': 'application/json'},\n ...init,\n });\n}\n\nfunction errorResponse() {\n return jsonResponse({code: 'server-error'}, {status: 500});\n}\n"],"names":["configureApiClient","authStateAtom","Toaster","QueryClient","QueryClientProvider","createMemoryHistory","createRootRoute","createRoute","createRouter","Outlet","RouterProvider","createStore","Provider","JotaiProvider","useMemo","CreateProjectPage","WORKSPACE_ID","WORKSPACE_PATH","GITHUB_CONNECTION_ID","GITEA_CONNECTION_ID","authState","status","token","user","id","email","name","email_verified_at","created_at","updated_at","workspaces","membershipId","CreateProjectPageStory","scenario","baseUrl","fetchImpl","fetchForScenario","queryClient","defaultOptions","queries","retry","store","nextStore","set","router","createStoryRouter","client","div","className","meta","title","component","parameters","layout","args","Playground","LongNames","RepositoryLoading","EmptyRepositories","ConnectionsError","rootRoute","workspaceRoute","getParentRoute","path","createProjectRoute","integrationsRoute","modelProvidersRoute","projectRoute","history","initialEntries","routeTree","addChildren","input","init","url","requestUrl","method","Request","pathname","Promise","resolve","jsonResponse","configs","modelProviderConfig","default_provider_id","errorResponse","connections","connectionsForScenario","startsWith","endsWith","undefined","repositories","repositoriesForScenario","next_cursor","workspace_id","source","connection_id","external_repository_id","connection","provider","display_name","external_account_id","repository","full_name","default_branch","overrides","slug","lifecycle_status","capabilities","owner","visibility","clone_url","html_url","provider_id","default_model","credential_fields","URL","body","Response","JSON","stringify","headers","code"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/create-project-page.stories.tsx"],"sourcesContent":["import type {IntegrationConnectionDto, RepositoryDto} from '@shipfox/api-integration-core-dto';\nimport {configureApiClient} from '@shipfox/client-api';\nimport {type AuthState, authStateAtom} from '@shipfox/client-auth';\nimport {Toaster} from '@shipfox/react-ui/toast';\nimport type {Meta, StoryObj} from '@storybook/react';\nimport {QueryClient, QueryClientProvider} from '@tanstack/react-query';\nimport {\n createMemoryHistory,\n createRootRoute,\n createRoute,\n createRouter,\n Outlet,\n RouterProvider,\n} from '@tanstack/react-router';\nimport {createStore, Provider as JotaiProvider} from 'jotai';\nimport {useMemo} from 'react';\nimport {CreateProjectPage} from './create-project-page.js';\n\nconst WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';\nconst WORKSPACE_PATH = `/workspaces/${WORKSPACE_ID}/projects/new`;\nconst GITHUB_CONNECTION_ID = '33333333-3333-4333-8333-333333333333';\nconst GITEA_CONNECTION_ID = '44444444-4444-4444-8444-444444444444';\n\ntype Scenario =\n | 'playground'\n | 'long-names'\n | 'repository-loading'\n | 'empty-repositories'\n | 'connections-error';\n\ninterface CreateProjectPageStoryProps {\n scenario: Scenario;\n}\n\nconst authState: AuthState = {\n status: 'authenticated',\n token: 'token',\n user: {\n id: '22222222-2222-4222-8222-222222222222',\n email: 'platform@example.com',\n name: 'Platform Engineer',\n emailVerifiedAt: '2026-01-01T00:00:00.000Z',\n },\n workspaces: [{id: WORKSPACE_ID, name: 'Acme', membershipId: 'membership-1'}],\n};\n\nfunction CreateProjectPageStory({scenario}: CreateProjectPageStoryProps) {\n configureApiClient({\n baseUrl: 'https://api.example.test',\n fetchImpl: fetchForScenario(scenario),\n });\n\n const queryClient = useMemo(\n () => new QueryClient({defaultOptions: {queries: {retry: false}}}),\n [],\n );\n const store = useMemo(() => {\n const nextStore = createStore();\n nextStore.set(authStateAtom, authState);\n return nextStore;\n }, []);\n const router = useMemo(() => createStoryRouter(), []);\n\n return (\n <QueryClientProvider client={queryClient}>\n <JotaiProvider store={store}>\n <div className=\"min-h-screen bg-background-neutral-background px-24 py-32\">\n <div className=\"mx-auto w-full max-w-[1120px]\">\n <RouterProvider router={router} />\n </div>\n </div>\n <Toaster />\n </JotaiProvider>\n </QueryClientProvider>\n );\n}\n\nconst meta = {\n title: 'Projects/CreateProjectPage',\n component: CreateProjectPageStory,\n parameters: {layout: 'fullscreen'},\n args: {scenario: 'playground'},\n} satisfies Meta<typeof CreateProjectPageStory>;\n\nexport default meta;\ntype Story = StoryObj<typeof meta>;\n\nexport const Playground: Story = {};\n\nexport const LongNames: Story = {\n args: {scenario: 'long-names'},\n};\n\nexport const RepositoryLoading: Story = {\n args: {scenario: 'repository-loading'},\n};\n\nexport const EmptyRepositories: Story = {\n args: {scenario: 'empty-repositories'},\n};\n\nexport const ConnectionsError: Story = {\n args: {scenario: 'connections-error'},\n};\n\nfunction createStoryRouter() {\n const rootRoute = createRootRoute({component: Outlet});\n const workspaceRoute = createRoute({\n getParentRoute: () => rootRoute,\n path: '/workspaces/$wid',\n component: Outlet,\n });\n const createProjectRoute = createRoute({\n getParentRoute: () => workspaceRoute,\n path: 'projects/new',\n component: CreateProjectPage,\n });\n const integrationsRoute = createRoute({\n getParentRoute: () => workspaceRoute,\n path: 'integrations',\n component: () => <div />,\n });\n const modelProvidersRoute = createRoute({\n getParentRoute: () => workspaceRoute,\n path: 'settings/agents',\n component: () => <div />,\n });\n const projectRoute = createRoute({\n getParentRoute: () => workspaceRoute,\n path: 'projects/$pid',\n component: () => <div />,\n });\n\n return createRouter({\n history: createMemoryHistory({initialEntries: [WORKSPACE_PATH]}),\n routeTree: rootRoute.addChildren([\n workspaceRoute.addChildren([\n createProjectRoute,\n integrationsRoute,\n modelProvidersRoute,\n projectRoute,\n ]),\n ]),\n });\n}\n\nfunction fetchForScenario(scenario: Scenario): typeof fetch {\n return (input, init) => {\n const url = requestUrl(input);\n const method = input instanceof Request ? input.method : (init?.method ?? 'GET');\n if (url.pathname === `/workspaces/${WORKSPACE_ID}/agent/model-providers`) {\n return Promise.resolve(\n jsonResponse({\n configs: [modelProviderConfig()],\n default_provider_id: 'openai',\n default_harness_id: null,\n }),\n );\n }\n if (url.pathname === '/integration-connections') {\n if (scenario === 'connections-error') return Promise.resolve(errorResponse());\n return Promise.resolve(jsonResponse({connections: connectionsForScenario(scenario)}));\n }\n if (\n url.pathname.startsWith('/integration-connections/') &&\n url.pathname.endsWith('/repositories')\n ) {\n if (scenario === 'repository-loading') return new Promise<Response>(() => undefined);\n return Promise.resolve(\n jsonResponse({repositories: repositoriesForScenario(scenario), next_cursor: null}),\n );\n }\n if (url.pathname === '/projects' && method === 'POST') {\n return Promise.resolve(\n jsonResponse({\n id: '99999999-9999-4999-8999-999999999999',\n workspace_id: WORKSPACE_ID,\n name: 'Platform',\n source: {\n connection_id: GITHUB_CONNECTION_ID,\n external_repository_id: 'platform',\n },\n created_at: '2026-01-01T00:00:00.000Z',\n updated_at: '2026-01-01T00:00:00.000Z',\n }),\n );\n }\n return Promise.resolve(jsonResponse({}, {status: 404}));\n };\n}\n\nfunction connectionsForScenario(scenario: Scenario): IntegrationConnectionDto[] {\n if (scenario === 'long-names') {\n return [\n connection({\n id: GITHUB_CONNECTION_ID,\n provider: 'github',\n display_name: 'acme-platform-engineering-production',\n external_account_id: 'acme-platform-engineering-production',\n }),\n connection({\n id: GITEA_CONNECTION_ID,\n provider: 'gitea',\n display_name: 'git.internal.acme.example/platform-tools',\n external_account_id: 'git.internal.acme.example/platform-tools',\n }),\n ];\n }\n\n return [\n connection({\n id: GITHUB_CONNECTION_ID,\n provider: 'github',\n display_name: 'acme',\n external_account_id: 'acme',\n }),\n connection({\n id: GITEA_CONNECTION_ID,\n provider: 'gitea',\n display_name: 'git.acme.internal',\n external_account_id: 'git.acme.internal',\n }),\n ];\n}\n\nfunction repositoriesForScenario(scenario: Scenario): RepositoryDto[] {\n if (scenario === 'empty-repositories') return [];\n if (scenario === 'long-names') {\n return [\n repository({\n external_repository_id: 'acme-platform-infrastructure-control-plane',\n name: 'acme-platform-infrastructure-control-plane',\n full_name: 'acme-engineering/acme-platform-infrastructure-control-plane',\n default_branch: 'production',\n }),\n repository({\n external_repository_id: 'workflow-automation-and-runner-orchestration',\n name: 'workflow-automation-and-runner-orchestration',\n full_name: 'acme-engineering/workflow-automation-and-runner-orchestration',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'customer-deployment-pipelines',\n name: 'customer-deployment-pipelines',\n full_name: 'acme-solutions/customer-deployment-pipelines',\n default_branch: 'release',\n }),\n ];\n }\n\n return [\n repository({\n external_repository_id: 'platform',\n name: 'platform',\n full_name: 'acme/platform',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'api',\n name: 'api',\n full_name: 'acme/api',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'client',\n name: 'client',\n full_name: 'acme/client',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'runners',\n name: 'runners',\n full_name: 'acme/runners',\n default_branch: 'main',\n }),\n repository({\n external_repository_id: 'workflows',\n name: 'workflows',\n full_name: 'acme/workflows',\n default_branch: 'develop',\n }),\n ];\n}\n\nfunction connection(\n overrides: Partial<IntegrationConnectionDto> & Pick<IntegrationConnectionDto, 'id'>,\n): IntegrationConnectionDto {\n return {\n workspace_id: WORKSPACE_ID,\n provider: 'github',\n external_account_id: 'acme',\n slug: 'github_acme',\n display_name: 'GitHub',\n lifecycle_status: 'active',\n capabilities: ['source_control'],\n created_at: '2026-01-01T00:00:00.000Z',\n updated_at: '2026-01-01T00:00:00.000Z',\n ...overrides,\n };\n}\n\nfunction repository(overrides: Partial<RepositoryDto>): RepositoryDto {\n return {\n connection_id: GITHUB_CONNECTION_ID,\n external_repository_id: 'platform',\n owner: 'acme',\n name: 'platform',\n full_name: 'acme/platform',\n default_branch: 'main',\n visibility: 'private',\n clone_url: 'https://github.example.test/acme/platform.git',\n html_url: 'https://github.example.test/acme/platform',\n ...overrides,\n };\n}\n\nfunction modelProviderConfig() {\n return {\n kind: 'builtin',\n workspace_id: WORKSPACE_ID,\n provider_id: 'openai',\n default_model: 'gpt-5.5-pro',\n credential_fields: ['api_key'],\n lifecycle_status: 'active',\n created_at: '2026-01-01T00:00:00.000Z',\n updated_at: '2026-01-01T00:00:00.000Z',\n };\n}\n\nfunction requestUrl(input: RequestInfo | URL): URL {\n if (input instanceof Request) return new URL(input.url);\n if (input instanceof URL) return input;\n return new URL(input, 'https://api.example.test');\n}\n\nfunction jsonResponse(body: unknown, init: ResponseInit = {}) {\n return new Response(JSON.stringify(body), {\n status: 200,\n headers: {'content-type': 'application/json'},\n ...init,\n });\n}\n\nfunction errorResponse() {\n return jsonResponse({code: 'server-error'}, {status: 500});\n}\n"],"names":["configureApiClient","authStateAtom","Toaster","QueryClient","QueryClientProvider","createMemoryHistory","createRootRoute","createRoute","createRouter","Outlet","RouterProvider","createStore","Provider","JotaiProvider","useMemo","CreateProjectPage","WORKSPACE_ID","WORKSPACE_PATH","GITHUB_CONNECTION_ID","GITEA_CONNECTION_ID","authState","status","token","user","id","email","name","emailVerifiedAt","workspaces","membershipId","CreateProjectPageStory","scenario","baseUrl","fetchImpl","fetchForScenario","queryClient","defaultOptions","queries","retry","store","nextStore","set","router","createStoryRouter","client","div","className","meta","title","component","parameters","layout","args","Playground","LongNames","RepositoryLoading","EmptyRepositories","ConnectionsError","rootRoute","workspaceRoute","getParentRoute","path","createProjectRoute","integrationsRoute","modelProvidersRoute","projectRoute","history","initialEntries","routeTree","addChildren","input","init","url","requestUrl","method","Request","pathname","Promise","resolve","jsonResponse","configs","modelProviderConfig","default_provider_id","default_harness_id","errorResponse","connections","connectionsForScenario","startsWith","endsWith","undefined","repositories","repositoriesForScenario","next_cursor","workspace_id","source","connection_id","external_repository_id","created_at","updated_at","connection","provider","display_name","external_account_id","repository","full_name","default_branch","overrides","slug","lifecycle_status","capabilities","owner","visibility","clone_url","html_url","kind","provider_id","default_model","credential_fields","URL","body","Response","JSON","stringify","headers","code"],"mappings":"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"}
@@ -1,2 +1,5 @@
1
- export declare function HomeRouter(): import("react").JSX.Element;
1
+ import type { ProjectsSearch } from '#routes/search.js';
2
+ export declare function HomeRouter({ search }: {
3
+ search?: ProjectsSearch;
4
+ }): import("react").JSX.Element;
2
5
  //# sourceMappingURL=home-router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"home-router.d.ts","sourceRoot":"","sources":["../../src/pages/home-router.tsx"],"names":[],"mappings":"AAEA,wBAAgB,UAAU,gCAEzB"}
1
+ {"version":3,"file":"home-router.d.ts","sourceRoot":"","sources":["../../src/pages/home-router.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAC;AAEtD,wBAAgB,UAAU,CAAC,EAAC,MAAW,EAAC,EAAE;IAAC,MAAM,CAAC,EAAE,cAAc,CAAA;CAAC,+BAElE"}
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { ProjectsHubPage } from '#pages/projects-hub-page.js';
3
- export function HomeRouter() {
4
- return /*#__PURE__*/ _jsx(ProjectsHubPage, {});
3
+ export function HomeRouter({ search = {} }) {
4
+ return /*#__PURE__*/ _jsx(ProjectsHubPage, {
5
+ search: search.search ?? ''
6
+ });
5
7
  }
6
8
 
7
9
  //# sourceMappingURL=home-router.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/home-router.tsx"],"sourcesContent":["import {ProjectsHubPage} from '#pages/projects-hub-page.js';\n\nexport function HomeRouter() {\n return <ProjectsHubPage />;\n}\n"],"names":["ProjectsHubPage","HomeRouter"],"mappings":";AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAE5D,OAAO,SAASC;IACd,qBAAO,KAACD;AACV"}
1
+ {"version":3,"sources":["../../src/pages/home-router.tsx"],"sourcesContent":["import {ProjectsHubPage} from '#pages/projects-hub-page.js';\nimport type {ProjectsSearch} from '#routes/search.js';\n\nexport function HomeRouter({search = {}}: {search?: ProjectsSearch}) {\n return <ProjectsHubPage search={search.search ?? ''} />;\n}\n"],"names":["ProjectsHubPage","HomeRouter","search"],"mappings":";AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAG5D,OAAO,SAASC,WAAW,EAACC,SAAS,CAAC,CAAC,EAA4B;IACjE,qBAAO,KAACF;QAAgBE,QAAQA,OAAOA,MAAM,IAAI;;AACnD"}
@@ -1,2 +1,4 @@
1
- export declare function ProjectsHubPage(): import("react").JSX.Element;
1
+ export declare function ProjectsHubPage({ search }: {
2
+ search?: string;
3
+ }): import("react").JSX.Element;
2
4
  //# sourceMappingURL=projects-hub-page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"projects-hub-page.d.ts","sourceRoot":"","sources":["../../src/pages/projects-hub-page.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,eAAe,gCA8G9B"}
1
+ {"version":3,"file":"projects-hub-page.d.ts","sourceRoot":"","sources":["../../src/pages/projects-hub-page.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,eAAe,CAAC,EAAC,MAAW,EAAC,EAAE;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAC,+BA8G/D"}
@@ -10,28 +10,24 @@ import { Icon } from '@shipfox/react-ui/icon';
10
10
  import { Input } from '@shipfox/react-ui/input';
11
11
  import { Skeleton } from '@shipfox/react-ui/skeleton';
12
12
  import { Header, Text } from '@shipfox/react-ui/typography';
13
- import { Link } from '@tanstack/react-router';
14
- import { useEffect, useState } from 'react';
13
+ import { Link, useNavigate } from '@tanstack/react-router';
15
14
  import { ModelProviderReminderBanner } from '#components/model-provider-reminder-banner.js';
16
15
  import { useProjectsInfiniteQuery } from '#hooks/api/projects.js';
17
- export function ProjectsHubPage() {
16
+ export function ProjectsHubPage({ search = '' }) {
18
17
  const workspace = useActiveWorkspace();
19
- const [searchInput, setSearchInput] = useState('');
20
- const trimmedInput = searchInput.trim();
21
- const debouncedSearch = useDebouncedValue(trimmedInput, 250);
22
- const query = useProjectsInfiniteQuery(workspace.id, debouncedSearch || undefined);
18
+ const navigate = useNavigate();
19
+ const query = useProjectsInfiniteQuery(workspace.id, search || undefined);
23
20
  const projects = query.data?.pages.flatMap((page)=>page.projects) ?? [];
24
21
  // The provider logo and connection health live on the integration connection,
25
22
  // not the project, so resolve them once for the whole list and index by id.
26
23
  // Skip the fetch when there are no cards to annotate.
27
24
  const connectionsQuery = useIntegrationConnectionsQuery(projects.length > 0 ? workspace.id : undefined);
28
- const connectionsById = new Map((connectionsQuery.data?.connections ?? []).map((connection)=>[
25
+ const connectionsById = new Map((connectionsQuery.data ?? []).map((connection)=>[
29
26
  connection.id,
30
27
  connection
31
28
  ]));
32
29
  const isInitialLoading = query.isPending;
33
- const isDebouncePending = trimmedInput !== debouncedSearch;
34
- const isSearching = Boolean(trimmedInput) && (isDebouncePending || query.isFetching && !query.isFetchingNextPage);
30
+ const isSearching = Boolean(search) && query.isFetching && !query.isFetchingNextPage;
35
31
  const hasNoData = !query.data;
36
32
  return /*#__PURE__*/ _jsxs("div", {
37
33
  className: "flex w-full flex-col gap-24",
@@ -51,8 +47,16 @@ export function ProjectsHubPage() {
51
47
  children: [
52
48
  /*#__PURE__*/ _jsx(Input, {
53
49
  type: "search",
54
- value: searchInput,
55
- onChange: (event)=>setSearchInput(event.target.value),
50
+ value: search,
51
+ onChange: (event)=>{
52
+ const next = event.target.value.trim();
53
+ navigate({
54
+ search: next ? {
55
+ search: next
56
+ } : {},
57
+ replace: true
58
+ });
59
+ },
56
60
  placeholder: "Search projects…",
57
61
  "aria-label": "Search projects",
58
62
  className: "flex-1",
@@ -86,17 +90,20 @@ export function ProjectsHubPage() {
86
90
  /*#__PURE__*/ _jsx(ModelProviderReminderBanner, {
87
91
  workspaceId: workspace.id
88
92
  }),
89
- isInitialLoading || debouncedSearch && hasNoData && query.isFetching ? /*#__PURE__*/ _jsx(ProjectsSkeleton, {}) : null,
93
+ isInitialLoading || search && hasNoData && query.isFetching ? /*#__PURE__*/ _jsx(ProjectsSkeleton, {}) : null,
90
94
  query.isError && hasNoData ? /*#__PURE__*/ _jsx(QueryLoadError, {
91
95
  query: query,
92
96
  subject: "projects"
93
97
  }) : null,
94
- !isInitialLoading && !query.isError && projects.length === 0 && !debouncedSearch ? /*#__PURE__*/ _jsx(EmptyProjects, {
98
+ !isInitialLoading && !query.isError && projects.length === 0 && !search ? /*#__PURE__*/ _jsx(EmptyProjects, {
95
99
  workspaceId: workspace.id
96
100
  }) : null,
97
- !query.isFetching && !query.isError && projects.length === 0 && debouncedSearch ? /*#__PURE__*/ _jsx(NoSearchResults, {
98
- search: debouncedSearch,
99
- onClear: ()=>setSearchInput('')
101
+ !query.isFetching && !query.isError && projects.length === 0 && search ? /*#__PURE__*/ _jsx(NoSearchResults, {
102
+ search: search,
103
+ onClear: ()=>navigate({
104
+ search: {},
105
+ replace: true
106
+ })
100
107
  }) : null,
101
108
  projects.length > 0 ? /*#__PURE__*/ _jsxs("section", {
102
109
  "aria-label": "Projects list",
@@ -105,7 +112,7 @@ export function ProjectsHubPage() {
105
112
  className: "grid grid-cols-2 gap-12 max-[760px]:grid-cols-1",
106
113
  children: projects.map((project)=>/*#__PURE__*/ _jsx(ProjectCard, {
107
114
  project: project,
108
- connection: connectionsById.get(project.source.connection_id),
115
+ connection: connectionsById.get(project.source.connectionId),
109
116
  connectionsResolved: connectionsQuery.isSuccess,
110
117
  connectionsSettled: connectionsQuery.isSuccess || connectionsQuery.isError,
111
118
  workspaceId: workspace.id
@@ -134,17 +141,6 @@ export function ProjectsHubPage() {
134
141
  ]
135
142
  });
136
143
  }
137
- function useDebouncedValue(value, delayMs) {
138
- const [debounced, setDebounced] = useState(value);
139
- useEffect(()=>{
140
- const handle = setTimeout(()=>setDebounced(value), delayMs);
141
- return ()=>clearTimeout(handle);
142
- }, [
143
- value,
144
- delayMs
145
- ]);
146
- return debounced;
147
- }
148
144
  function ProjectsSkeleton() {
149
145
  return /*#__PURE__*/ _jsx("ul", {
150
146
  role: "status",
@@ -210,7 +206,7 @@ function ProjectCard({ project, connection, connectionsResolved, connectionsSett
210
206
  // On a resolved fetch, `active` carries no badge while a missing connection
211
207
  // reads as an error so a broken source is still flagged. An unresolved or
212
208
  // failed fetch shows nothing, so a fetch failure never flags every card.
213
- const status = connectionsResolved ? connection?.lifecycle_status ?? 'error' : undefined;
209
+ const status = connectionsResolved ? connection?.lifecycleStatus ?? 'error' : undefined;
214
210
  return /*#__PURE__*/ _jsx("li", {
215
211
  children: /*#__PURE__*/ _jsx(Link, {
216
212
  to: "/workspaces/$wid/projects/$pid",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/projects-hub-page.tsx"],"sourcesContent":["import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';\nimport type {ProjectResponseDto} from '@shipfox/api-projects-dto';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionStatusBadge,\n IntegrationIcon,\n useIntegrationConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Card} from '@shipfox/react-ui/card';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Input} from '@shipfox/react-ui/input';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {Link} from '@tanstack/react-router';\nimport {useEffect, useState} from 'react';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport function ProjectsHubPage() {\n const workspace = useActiveWorkspace();\n const [searchInput, setSearchInput] = useState('');\n const trimmedInput = searchInput.trim();\n const debouncedSearch = useDebouncedValue(trimmedInput, 250);\n const query = useProjectsInfiniteQuery(workspace.id, debouncedSearch || undefined);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // The provider logo and connection health live on the integration connection,\n // not the project, so resolve them once for the whole list and index by id.\n // Skip the fetch when there are no cards to annotate.\n const connectionsQuery = useIntegrationConnectionsQuery(\n projects.length > 0 ? workspace.id : undefined,\n );\n const connectionsById = new Map(\n (connectionsQuery.data?.connections ?? []).map((connection) => [connection.id, connection]),\n );\n\n const isInitialLoading = query.isPending;\n const isDebouncePending = trimmedInput !== debouncedSearch;\n const isSearching =\n Boolean(trimmedInput) && (isDebouncePending || (query.isFetching && !query.isFetchingNextPage));\n const hasNoData = !query.data;\n\n return (\n <div className=\"flex w-full flex-col gap-24\">\n <header className=\"flex flex-col gap-16\">\n <div className=\"flex items-start justify-between gap-24 max-[640px]:flex-col\">\n <Header variant=\"h2\">Projects</Header>\n </div>\n <div className=\"flex items-center gap-12 max-[640px]:flex-col max-[640px]:items-stretch\">\n <Input\n type=\"search\"\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder=\"Search projects…\"\n aria-label=\"Search projects\"\n className=\"flex-1\"\n iconLeft={<Icon name=\"searchLine\" className=\"size-16\" />}\n iconRight={\n isSearching ? (\n <Icon\n name=\"spinner\"\n size={16}\n className=\"text-foreground-neutral-muted\"\n aria-hidden=\"true\"\n />\n ) : undefined\n }\n />\n <Button asChild iconLeft=\"addLine\" className=\"shrink-0 max-[640px]:w-full\">\n <Link to=\"/workspaces/$wid/projects/new\" params={{wid: workspace.id}}>\n New project\n </Link>\n </Button>\n </div>\n </header>\n\n <ModelProviderReminderBanner workspaceId={workspace.id} />\n\n {isInitialLoading || (debouncedSearch && hasNoData && query.isFetching) ? (\n <ProjectsSkeleton />\n ) : null}\n\n {query.isError && hasNoData ? <QueryLoadError query={query} subject=\"projects\" /> : null}\n\n {!isInitialLoading && !query.isError && projects.length === 0 && !debouncedSearch ? (\n <EmptyProjects workspaceId={workspace.id} />\n ) : null}\n\n {!query.isFetching && !query.isError && projects.length === 0 && debouncedSearch ? (\n <NoSearchResults search={debouncedSearch} onClear={() => setSearchInput('')} />\n ) : null}\n\n {projects.length > 0 ? (\n <section aria-label=\"Projects list\">\n <ul className=\"grid grid-cols-2 gap-12 max-[760px]:grid-cols-1\">\n {projects.map((project) => (\n <ProjectCard\n project={project}\n connection={connectionsById.get(project.source.connection_id)}\n connectionsResolved={connectionsQuery.isSuccess}\n connectionsSettled={connectionsQuery.isSuccess || connectionsQuery.isError}\n key={project.id}\n workspaceId={workspace.id}\n />\n ))}\n </ul>\n {query.error && query.data ? (\n <Callout role=\"alert\" type=\"error\" className=\"mt-16\">\n <Text size=\"sm\">\n Could not load the next page. Existing projects are still shown.\n </Text>\n </Callout>\n ) : null}\n {query.hasNextPage ? (\n <div className=\"mt-16 flex justify-center\">\n <Button\n variant=\"secondary\"\n isLoading={query.isFetchingNextPage}\n onClick={() => query.fetchNextPage()}\n >\n Load more\n </Button>\n </div>\n ) : null}\n </section>\n ) : null}\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\nfunction ProjectsSkeleton() {\n return (\n <ul\n role=\"status\"\n aria-label=\"Loading projects\"\n className=\"grid grid-cols-2 gap-12 max-[760px]:grid-cols-1\"\n >\n {[0, 1, 2, 3, 4, 5].map((row) => (\n <li key={row}>\n <Card className=\"h-full p-16\">\n <div className=\"flex items-center gap-12\">\n <Skeleton className=\"size-24 shrink-0\" />\n <Skeleton className=\"h-16 w-1/2\" />\n </div>\n </Card>\n </li>\n ))}\n </ul>\n );\n}\n\nfunction EmptyProjects({workspaceId}: {workspaceId: string}) {\n return (\n <EmptyState\n icon=\"folderLine\"\n title=\"Create your first project\"\n description=\"Connect a repository-backed project to start building workflows.\"\n action={\n <Button asChild iconRight=\"chevronRight\">\n <Link to=\"/workspaces/$wid/projects/new\" params={{wid: workspaceId}}>\n Create project\n </Link>\n </Button>\n }\n />\n );\n}\n\nfunction NoSearchResults({search, onClear}: {search: string; onClear: () => void}) {\n return (\n <EmptyState\n icon=\"searchLine\"\n title={`No projects match “${search}”`}\n description=\"Try a different search, or clear it to see all projects.\"\n action={\n <Button size=\"sm\" variant=\"secondary\" onClick={onClear}>\n Clear search\n </Button>\n }\n />\n );\n}\n\nfunction ProjectCard({\n project,\n connection,\n connectionsResolved,\n connectionsSettled,\n workspaceId,\n}: {\n project: ProjectResponseDto;\n connection: IntegrationConnectionDto | undefined;\n connectionsResolved: boolean;\n connectionsSettled: boolean;\n workspaceId: string;\n}) {\n // On a resolved fetch, `active` carries no badge while a missing connection\n // reads as an error so a broken source is still flagged. An unresolved or\n // failed fetch shows nothing, so a fetch failure never flags every card.\n const status = connectionsResolved ? (connection?.lifecycle_status ?? 'error') : undefined;\n\n return (\n <li>\n <Link\n to=\"/workspaces/$wid/projects/$pid\"\n params={{wid: workspaceId, pid: project.id}}\n className=\"block h-full rounded-8 focus-visible:shadow-button-neutral-focus focus-visible:outline-none\"\n >\n <Card className=\"h-full p-16 transition-colors hover:bg-background-components-hover\">\n <div className=\"flex min-w-0 items-center gap-12\">\n {/* Settle on success or error: a failed fetch falls back to the\n neutral provider icon rather than spinning forever. */}\n {connectionsSettled ? (\n <IntegrationIcon\n source={connection?.provider}\n aria-hidden\n className=\"size-24 shrink-0 text-foreground-neutral-base\"\n />\n ) : (\n <Skeleton className=\"size-24 shrink-0\" />\n )}\n <div className=\"flex min-w-0 flex-1 items-center gap-8\">\n <Text size=\"md\" bold className=\"truncate\">\n {project.name}\n </Text>\n {status ? <ConnectionStatusBadge status={status} className=\"shrink-0\" /> : null}\n </div>\n </div>\n </Card>\n </Link>\n </li>\n );\n}\n"],"names":["useActiveWorkspace","ConnectionStatusBadge","IntegrationIcon","useIntegrationConnectionsQuery","QueryLoadError","Button","Callout","Card","EmptyState","Icon","Input","Skeleton","Header","Text","Link","useEffect","useState","ModelProviderReminderBanner","useProjectsInfiniteQuery","ProjectsHubPage","workspace","searchInput","setSearchInput","trimmedInput","trim","debouncedSearch","useDebouncedValue","query","id","undefined","projects","data","pages","flatMap","page","connectionsQuery","length","connectionsById","Map","connections","map","connection","isInitialLoading","isPending","isDebouncePending","isSearching","Boolean","isFetching","isFetchingNextPage","hasNoData","div","className","header","variant","type","value","onChange","event","target","placeholder","aria-label","iconLeft","name","iconRight","size","aria-hidden","asChild","to","params","wid","workspaceId","ProjectsSkeleton","isError","subject","EmptyProjects","NoSearchResults","search","onClear","section","ul","project","ProjectCard","get","source","connection_id","connectionsResolved","isSuccess","connectionsSettled","error","role","hasNextPage","isLoading","onClick","fetchNextPage","delayMs","debounced","setDebounced","handle","setTimeout","clearTimeout","row","li","icon","title","description","action","status","lifecycle_status","pid","provider","bold"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/projects-hub-page.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionStatusBadge,\n type IntegrationConnection,\n IntegrationIcon,\n useIntegrationConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Card} from '@shipfox/react-ui/card';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Input} from '@shipfox/react-ui/input';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport type {Project} from '#core/project.js';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport function ProjectsHubPage({search = ''}: {search?: string}) {\n const workspace = useActiveWorkspace();\n const navigate = useNavigate();\n const query = useProjectsInfiniteQuery(workspace.id, search || undefined);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // The provider logo and connection health live on the integration connection,\n // not the project, so resolve them once for the whole list and index by id.\n // Skip the fetch when there are no cards to annotate.\n const connectionsQuery = useIntegrationConnectionsQuery(\n projects.length > 0 ? workspace.id : undefined,\n );\n const connectionsById = new Map(\n (connectionsQuery.data ?? []).map((connection) => [connection.id, connection]),\n );\n\n const isInitialLoading = query.isPending;\n const isSearching = Boolean(search) && query.isFetching && !query.isFetchingNextPage;\n const hasNoData = !query.data;\n\n return (\n <div className=\"flex w-full flex-col gap-24\">\n <header className=\"flex flex-col gap-16\">\n <div className=\"flex items-start justify-between gap-24 max-[640px]:flex-col\">\n <Header variant=\"h2\">Projects</Header>\n </div>\n <div className=\"flex items-center gap-12 max-[640px]:flex-col max-[640px]:items-stretch\">\n <Input\n type=\"search\"\n value={search}\n onChange={(event) => {\n const next = event.target.value.trim();\n navigate({search: (next ? {search: next} : {}) as never, replace: true});\n }}\n placeholder=\"Search projects…\"\n aria-label=\"Search projects\"\n className=\"flex-1\"\n iconLeft={<Icon name=\"searchLine\" className=\"size-16\" />}\n iconRight={\n isSearching ? (\n <Icon\n name=\"spinner\"\n size={16}\n className=\"text-foreground-neutral-muted\"\n aria-hidden=\"true\"\n />\n ) : undefined\n }\n />\n <Button asChild iconLeft=\"addLine\" className=\"shrink-0 max-[640px]:w-full\">\n <Link to=\"/workspaces/$wid/projects/new\" params={{wid: workspace.id}}>\n New project\n </Link>\n </Button>\n </div>\n </header>\n\n <ModelProviderReminderBanner workspaceId={workspace.id} />\n\n {isInitialLoading || (search && hasNoData && query.isFetching) ? <ProjectsSkeleton /> : null}\n\n {query.isError && hasNoData ? <QueryLoadError query={query} subject=\"projects\" /> : null}\n\n {!isInitialLoading && !query.isError && projects.length === 0 && !search ? (\n <EmptyProjects workspaceId={workspace.id} />\n ) : null}\n\n {!query.isFetching && !query.isError && projects.length === 0 && search ? (\n <NoSearchResults\n search={search}\n onClear={() => navigate({search: {} as never, replace: true})}\n />\n ) : null}\n\n {projects.length > 0 ? (\n <section aria-label=\"Projects list\">\n <ul className=\"grid grid-cols-2 gap-12 max-[760px]:grid-cols-1\">\n {projects.map((project) => (\n <ProjectCard\n project={project}\n connection={connectionsById.get(project.source.connectionId)}\n connectionsResolved={connectionsQuery.isSuccess}\n connectionsSettled={connectionsQuery.isSuccess || connectionsQuery.isError}\n key={project.id}\n workspaceId={workspace.id}\n />\n ))}\n </ul>\n {query.error && query.data ? (\n <Callout role=\"alert\" type=\"error\" className=\"mt-16\">\n <Text size=\"sm\">\n Could not load the next page. Existing projects are still shown.\n </Text>\n </Callout>\n ) : null}\n {query.hasNextPage ? (\n <div className=\"mt-16 flex justify-center\">\n <Button\n variant=\"secondary\"\n isLoading={query.isFetchingNextPage}\n onClick={() => query.fetchNextPage()}\n >\n Load more\n </Button>\n </div>\n ) : null}\n </section>\n ) : null}\n </div>\n );\n}\n\nfunction ProjectsSkeleton() {\n return (\n <ul\n role=\"status\"\n aria-label=\"Loading projects\"\n className=\"grid grid-cols-2 gap-12 max-[760px]:grid-cols-1\"\n >\n {[0, 1, 2, 3, 4, 5].map((row) => (\n <li key={row}>\n <Card className=\"h-full p-16\">\n <div className=\"flex items-center gap-12\">\n <Skeleton className=\"size-24 shrink-0\" />\n <Skeleton className=\"h-16 w-1/2\" />\n </div>\n </Card>\n </li>\n ))}\n </ul>\n );\n}\n\nfunction EmptyProjects({workspaceId}: {workspaceId: string}) {\n return (\n <EmptyState\n icon=\"folderLine\"\n title=\"Create your first project\"\n description=\"Connect a repository-backed project to start building workflows.\"\n action={\n <Button asChild iconRight=\"chevronRight\">\n <Link to=\"/workspaces/$wid/projects/new\" params={{wid: workspaceId}}>\n Create project\n </Link>\n </Button>\n }\n />\n );\n}\n\nfunction NoSearchResults({search, onClear}: {search: string; onClear: () => void}) {\n return (\n <EmptyState\n icon=\"searchLine\"\n title={`No projects match “${search}”`}\n description=\"Try a different search, or clear it to see all projects.\"\n action={\n <Button size=\"sm\" variant=\"secondary\" onClick={onClear}>\n Clear search\n </Button>\n }\n />\n );\n}\n\nfunction ProjectCard({\n project,\n connection,\n connectionsResolved,\n connectionsSettled,\n workspaceId,\n}: {\n project: Project;\n connection: IntegrationConnection | undefined;\n connectionsResolved: boolean;\n connectionsSettled: boolean;\n workspaceId: string;\n}) {\n // On a resolved fetch, `active` carries no badge while a missing connection\n // reads as an error so a broken source is still flagged. An unresolved or\n // failed fetch shows nothing, so a fetch failure never flags every card.\n const status = connectionsResolved ? (connection?.lifecycleStatus ?? 'error') : undefined;\n\n return (\n <li>\n <Link\n to=\"/workspaces/$wid/projects/$pid\"\n params={{wid: workspaceId, pid: project.id}}\n className=\"block h-full rounded-8 focus-visible:shadow-button-neutral-focus focus-visible:outline-none\"\n >\n <Card className=\"h-full p-16 transition-colors hover:bg-background-components-hover\">\n <div className=\"flex min-w-0 items-center gap-12\">\n {/* Settle on success or error: a failed fetch falls back to the\n neutral provider icon rather than spinning forever. */}\n {connectionsSettled ? (\n <IntegrationIcon\n source={connection?.provider}\n aria-hidden\n className=\"size-24 shrink-0 text-foreground-neutral-base\"\n />\n ) : (\n <Skeleton className=\"size-24 shrink-0\" />\n )}\n <div className=\"flex min-w-0 flex-1 items-center gap-8\">\n <Text size=\"md\" bold className=\"truncate\">\n {project.name}\n </Text>\n {status ? <ConnectionStatusBadge status={status} className=\"shrink-0\" /> : null}\n </div>\n </div>\n </Card>\n </Link>\n </li>\n );\n}\n"],"names":["useActiveWorkspace","ConnectionStatusBadge","IntegrationIcon","useIntegrationConnectionsQuery","QueryLoadError","Button","Callout","Card","EmptyState","Icon","Input","Skeleton","Header","Text","Link","useNavigate","ModelProviderReminderBanner","useProjectsInfiniteQuery","ProjectsHubPage","search","workspace","navigate","query","id","undefined","projects","data","pages","flatMap","page","connectionsQuery","length","connectionsById","Map","map","connection","isInitialLoading","isPending","isSearching","Boolean","isFetching","isFetchingNextPage","hasNoData","div","className","header","variant","type","value","onChange","event","next","target","trim","replace","placeholder","aria-label","iconLeft","name","iconRight","size","aria-hidden","asChild","to","params","wid","workspaceId","ProjectsSkeleton","isError","subject","EmptyProjects","NoSearchResults","onClear","section","ul","project","ProjectCard","get","source","connectionId","connectionsResolved","isSuccess","connectionsSettled","error","role","hasNextPage","isLoading","onClick","fetchNextPage","row","li","icon","title","description","action","status","lifecycleStatus","pid","provider","bold"],"mappings":"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"}