@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +78 -0
- package/dist/chrome.d.ts.map +1 -1
- package/dist/chrome.js +7 -10
- package/dist/chrome.js.map +1 -1
- package/dist/components/model-provider-reminder-banner.d.ts.map +1 -1
- package/dist/components/model-provider-reminder-banner.js +1 -1
- package/dist/components/model-provider-reminder-banner.js.map +1 -1
- package/dist/components/source-strip.d.ts +2 -2
- package/dist/components/source-strip.d.ts.map +1 -1
- package/dist/components/source-strip.js +2 -2
- package/dist/components/source-strip.js.map +1 -1
- package/dist/core/definition.d.ts +32 -0
- package/dist/core/definition.d.ts.map +1 -0
- package/dist/core/definition.js +3 -0
- package/dist/core/definition.js.map +1 -0
- package/dist/core/project.d.ts +26 -0
- package/dist/core/project.d.ts.map +1 -0
- package/dist/core/project.js +12 -0
- package/dist/core/project.js.map +1 -0
- package/dist/feature.d.ts +8 -10
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +11 -22
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/definitions.d.ts +12 -88
- package/dist/hooks/api/definitions.d.ts.map +1 -1
- package/dist/hooks/api/definitions.js +18 -10
- package/dist/hooks/api/definitions.js.map +1 -1
- package/dist/hooks/api/mappers.d.ts +17 -0
- package/dist/hooks/api/mappers.d.ts.map +1 -0
- package/dist/hooks/api/mappers.js +56 -0
- package/dist/hooks/api/mappers.js.map +1 -0
- package/dist/hooks/api/projects.d.ts +19 -81
- package/dist/hooks/api/projects.d.ts.map +1 -1
- package/dist/hooks/api/projects.js +72 -18
- package/dist/hooks/api/projects.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/pages/create-project-page.d.ts.map +1 -1
- package/dist/pages/create-project-page.js +12 -37
- package/dist/pages/create-project-page.js.map +1 -1
- package/dist/pages/create-project-page.stories.d.ts.map +1 -1
- package/dist/pages/create-project-page.stories.js +4 -5
- package/dist/pages/create-project-page.stories.js.map +1 -1
- package/dist/pages/home-router.d.ts +4 -1
- package/dist/pages/home-router.d.ts.map +1 -1
- package/dist/pages/home-router.js +4 -2
- package/dist/pages/home-router.js.map +1 -1
- package/dist/pages/projects-hub-page.d.ts +3 -1
- package/dist/pages/projects-hub-page.d.ts.map +1 -1
- package/dist/pages/projects-hub-page.js +26 -30
- package/dist/pages/projects-hub-page.js.map +1 -1
- package/dist/routes/home.d.ts +3 -2
- package/dist/routes/home.d.ts.map +1 -1
- package/dist/routes/home.js +7 -2
- package/dist/routes/home.js.map +1 -1
- package/dist/routes/inputs.d.ts +6 -0
- package/dist/routes/inputs.d.ts.map +1 -0
- package/dist/routes/inputs.js +14 -0
- package/dist/routes/inputs.js.map +1 -0
- package/dist/routes/search.d.ts +5 -0
- package/dist/routes/search.d.ts.map +1 -0
- package/dist/routes/search.js +8 -0
- package/dist/routes/search.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +14 -17
- package/src/chrome.tsx +11 -6
- package/src/components/model-provider-reminder-banner.tsx +1 -3
- package/src/components/project-switcher.test.tsx +2 -1
- package/src/components/source-strip.tsx +5 -5
- package/src/core/definition.ts +31 -0
- package/src/core/project.ts +47 -0
- package/src/feature.ts +13 -23
- package/src/hooks/api/definitions.ts +54 -19
- package/src/hooks/api/mappers.ts +67 -0
- package/src/hooks/api/projects.test.ts +11 -4
- package/src/hooks/api/projects.ts +125 -28
- package/src/index.ts +10 -6
- package/src/pages/create-project-page.stories.tsx +7 -5
- package/src/pages/create-project-page.test.tsx +3 -1
- package/src/pages/create-project-page.tsx +16 -51
- package/src/pages/home-router.tsx +3 -2
- package/src/pages/projects-hub-page.test.tsx +13 -8
- package/src/pages/projects-hub-page.tsx +24 -34
- package/src/routes/home.tsx +6 -2
- package/src/routes/inputs.test.ts +14 -0
- package/src/routes/inputs.ts +15 -0
- package/src/routes/search.ts +8 -0
- package/test/pages.tsx +2 -17
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/components/workspace-setup-guard.d.ts +0 -9
- package/dist/components/workspace-setup-guard.d.ts.map +0 -1
- package/dist/components/workspace-setup-guard.js +0 -160
- package/dist/components/workspace-setup-guard.js.map +0 -1
- package/dist/pages/project-workflows-page.d.ts +0 -4
- package/dist/pages/project-workflows-page.d.ts.map +0 -1
- package/dist/pages/project-workflows-page.js +0 -481
- package/dist/pages/project-workflows-page.js.map +0 -1
- package/dist/routes/workflows.d.ts +0 -5
- package/dist/routes/workflows.d.ts.map +0 -1
- package/dist/routes/workflows.js +0 -16
- package/dist/routes/workflows.js.map +0 -1
- package/src/components/workspace-setup-guard.test.tsx +0 -534
- package/src/components/workspace-setup-guard.tsx +0 -185
- package/src/pages/project-workflows-page.test.tsx +0 -258
- package/src/pages/project-workflows-page.tsx +0 -445
- 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 {
|
|
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
|
|
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].
|
|
48
|
+
setSelectedRepositoryId(repositories[0].externalRepositoryId);
|
|
51
49
|
}
|
|
52
50
|
}, [
|
|
53
51
|
repositories,
|
|
54
52
|
selectedRepositoryId
|
|
55
53
|
]);
|
|
56
|
-
const selectedRepository = repositories.find((repository)=>repository.
|
|
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
|
|
115
|
-
|
|
112
|
+
const command = {
|
|
113
|
+
workspaceId: workspace.id,
|
|
116
114
|
name: projectName,
|
|
117
115
|
source: {
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
connectionId: selectedConnection.id,
|
|
117
|
+
externalRepositoryId: selectedRepository.externalRepositoryId
|
|
120
118
|
}
|
|
121
|
-
}
|
|
122
|
-
const project = await createProject.mutateAsync(
|
|
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?.
|
|
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":";AACA,SACEA,uBAAuB,QAGlB,4BAA4B;AACnC,SAAQC,uBAAuB,QAAO,uBAAuB;AAC7D,SACEC,gBAAgB,EAChBC,eAAe,EACfC,gBAAgB,EAChBC,4BAA4B,EAC5BC,yBAAyB,QACpB,+BAA+B;AACtC,SAAQC,qBAAqB,QAAO,qBAAqB;AACzD,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,SAAS,EAAEC,cAAc,EAAEC,UAAU,QAAO,+BAA+B;AACnF,SAAQC,cAAc,QAAO,2BAA2B;AACxD,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAC1D,SAAQC,OAAO,QAAO,uBAAuB;AAC7C,SAA0BC,cAAc,QAAO,wBAAwB;AACvE,SAAQC,IAAI,EAAEC,QAAQ,EAAEC,WAAW,QAAO,yBAAyB;AACnE,SAAQC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAClD,SAAQC,2BAA2B,QAAO,gDAAgD;AAC1F,SAAQC,iBAAiB,EAAEC,wBAAwB,QAAO,yBAAyB;AACnF,SAAQC,gBAAgB,QAAO,oBAAoB;AAEnD,MAAMC,2BAA2B;AAEjC,OAAO,SAASC;IACd,MAAMC,YAAY9B;IAClB,MAAM+B,cAAcd;IACpB,MAAMe,WAAWZ;IACjB,MAAMa,gBAAgBP;IACtB,MAAMQ,WAAWZ,OAAuB;IAExC,MAAMa,mBAAmB9B,0BAA0ByB,WAAWM;IAC9D,MAAMC,cAAcF,iBAAiBG,IAAI,EAAED,eAAe,EAAE;IAE5D,MAAM,CAACE,sBAAsBC,wBAAwB,GAAGjB;IACxD,MAAMkB,qBAAqBJ,YAAYK,MAAM,KAAK,IAAIL,WAAW,CAAC,EAAE,EAAED,KAAKO;IAC3E,MAAMC,gCAAgCL,wBAAwBE;IAC9DpB,UAAU;QACR,IAAIoB,sBAAsBF,yBAAyBE,oBAAoB;YACrED,wBAAwBC;QAC1B;IACF,GAAG;QAACA;QAAoBF;KAAqB;IAE7C,MAAMM,qBAA2DR,YAAYS,IAAI,CAC/E,CAACC,aAAeA,WAAWX,EAAE,KAAKQ;IAGpC,MAAM,CAACI,YAAYC,cAAc,GAAG1B,SAAS;IAC7C,MAAM2B,sBAAsBC,kBAAkBH,YAAY;IAC1D,MAAMI,gBAAgBF,oBAAoBG,IAAI;IAE9C,MAAMC,oBAAoBlD,6BACxBwC,+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,GAAGtC;IACxDF,UAAU;QACR,IAAI,CAACuC,wBAAwBJ,YAAY,CAAC,EAAE,EAAE;YAC5CK,wBAAwBL,YAAY,CAAC,EAAE,CAACM,sBAAsB;QAChE;IACF,GAAG;QAACN;QAAcI;KAAqB;IACvC,MAAMG,qBAAqBP,aAAaV,IAAI,CAC1C,CAACkB,aAAeA,WAAWF,sBAAsB,KAAKF;IAGxD,MAAM,CAACK,aAAaC,eAAe,GAAG3C,SAAS;IAC/C,MAAM4C,qBAAqBC,0BACzBL,oBAAoBM,QAAQT,wBAAwB;IAGtD,MAAM,CAACU,WAAWC,aAAa,GAAGhD;IAElC,MAAMiD,OAAOxD,QAAQ;QACnByD,eAAe;YAACJ,MAAMF;QAAkB;QACxCO,UAAU,OAAO,EAACC,KAAK,EAAC;YACtB,MAAMC,sBAAsBX,cAAcU,MAAMN,IAAI,GAAGF;QACzD;IACF;IAEA9C,UAAU;QACR,IAAI,CAAC4C,eAAeO,KAAKK,KAAK,CAACC,MAAM,CAACT,IAAI,KAAKF,oBAAoB;YACjEK,KAAKO,aAAa,CAAC,QAAQZ;QAC7B;IACF,GAAG;QAACA;QAAoBK;QAAMP;KAAY;IAE1C,SAASe,iBAAiBC,YAAoB;QAC5CzC,wBAAwByC;QACxBpB,wBAAwBlB;IAC1B;IAEA,IAAIR,iBAAiB+C,SAAS,EAAE;QAC9B,qBAAO,KAACtE;IACV;IAEA,IAAI,CAACkB,WAAW;QACd,qBAAO,KAAClB;IACV;IAEA,IAAI,CAACuB,iBAAiBgD,OAAO,IAAI9C,YAAYK,MAAM,KAAK,GAAG;QACzD,qBAAO,KAACvB;YAASiE,IAAG;YAAgCC,QAAQ;gBAACC,KAAKxD,UAAUM,EAAE;YAAA;YAAGmD,OAAO;;IAC1F;IAEA,eAAeX,sBAAsBY,WAAmB;QACtDjB,aAAa5B;QACb,IAAI,CAACb,WAAW;YACdyC,aAAa;YACbrC,SAASuD,OAAO,EAAEC;YAClB;QACF;QACA,IAAI,CAAC7C,oBAAoB;YACvB0B,aAAa;YACbrC,SAASuD,OAAO,EAAEC;YAClB;QACF;QACA,IAAI,CAAC3B,oBAAoB;YACvBQ,aAAa;YACbrC,SAASuD,OAAO,EAAEC;YAClB;QACF;QAEA,IAAI;YACF,MAAMC,cAAc5F,wBAAwB6F,KAAK,CAAC;gBAChDC,cAAc/D,UAAUM,EAAE;gBAC1BiC,MAAMmB;gBACNM,QAAQ;oBACNC,eAAelD,mBAAmBT,EAAE;oBACpC0B,wBAAwBC,mBAAmBD,sBAAsB;gBACnE;YACF;YACA,MAAMkC,UAAU,MAAM/D,cAAcgE,WAAW,CAACN;YAChDO,0BAA0BnE,aAAaD,UAAUM,EAAE,EAAE4D;YACrDjE,YAAYoE,YAAY,CAAC1E,kBAAkB2E,MAAM,CAACJ,QAAQ5D,EAAE,GAAG4D;YAC/D,MAAMjE,YAAYsE,iBAAiB,CAAC;gBAACC,UAAU7E,kBAAkB8E,IAAI,CAACzE,UAAUM,EAAE;YAAC;YACnFvB,MAAM2F,OAAO,CAAC;YACd,MAAMxE,SAAS;gBACboD,IAAI;gBACJC,QAAQ;oBAACC,KAAKxD,UAAUM,EAAE;oBAAEqE,KAAKT,QAAQ5D,EAAE;gBAAA;YAC7C;QACF,EAAE,OAAOsE,OAAO;YACd,MAAMC,OAAOhF,iBAAiB+E;YAC9B,IAAIC,KAAKC,iBAAiB,EAAE;gBAC1B,MAAM7E,YAAYsE,iBAAiB,CAAC;oBAClCC,UAAU7E,kBAAkBoF,MAAM,CAAC/E,UAAUM,EAAE;oBAC/C0E,aAAa;gBACf;gBACA,MAAM/E,YAAYsE,iBAAiB,CAAC;oBAACC,UAAU7E,kBAAkB8E,IAAI,CAACzE,UAAUM,EAAE;gBAAC;gBACnFvB,MAAMkG,IAAI,CAAC;gBACX,MAAM/E,SAAS;oBACboD,IAAI;oBACJC,QAAQ;wBAACC,KAAKxD,UAAUM,EAAE;wBAAEqE,KAAKE,KAAKC,iBAAiB;oBAAA;gBACzD;gBACA;YACF;YACArC,aAAa,GAAGoC,KAAKK,KAAK,CAAC,EAAE,EAAEL,KAAKM,OAAO,EAAE;YAC7CC,sBAAsB,IAAMhF,SAASuD,OAAO,EAAEC;QAChD;IACF;IAEA,MAAMyB,iBAAiBC,QAAQvE;IAC/B,MAAMwE,uBAAuBjE,gBACzB,CAAC,0BAA0B,EAAEJ,WAAWK,IAAI,GAAG,EAAE,CAAC,GAClD;IAEJ,qBACE,MAACiE;QAAIC,WAAU;;0BACb,MAACC;gBAAOD,WAAU;;kCAChB,KAACzG;wBAAOsB,IAAG;wBAAuBqF,SAAQ;kCAAK;;kCAG/C,KAAC1G;wBAAK2G,MAAK;wBAAKH,WAAU;kCAAgC;;;;0BAM5D,KAAC/F;gBAA4BmG,aAAa7F,UAAUM,EAAE;;YAErDD,iBAAiBgD,OAAO,iBACvB,KAAC3E;gBAAQoH,MAAK;gBAAQC,MAAK;0BACzB,cAAA,MAACP;oBAAIC,WAAU;;sCACb,KAACxG;4BAAK2G,MAAK;4BAAKH,WAAU;sCAAuB;;sCAGjD,KAAChH;4BACCmH,MAAK;4BACLD,SAAQ;4BACRK,SAAS,IAAM3F,iBAAiB4F,OAAO;4BACvCR,WAAU;sCACX;;;;iBAKH;0BAEJ,MAAC/C;gBACCE,UAAU,CAACsD;oBACTA,MAAMC,cAAc;oBACpBD,MAAME,eAAe;oBACrB,KAAK1D,KAAK2D,YAAY;gBACxB;gBACAC,UAAU;gBACVC,mBAAgB;gBAChBd,WAAU;;kCAEV,MAACD;wBAAIC,WAAU;;0CACb,MAACe;gCAAQf,WAAU;gCAAuBgB,cAAW;;kDACnD,MAACjB;wCAAIC,WAAU;;0DACb,MAACD;gDAAIC,WAAU;;kEACb,KAACzG;wDAAO2G,SAAQ;kEAAK;;kEACrB,KAAC1G;wDAAK2G,MAAK;wDAAKH,WAAU;kEAAgC;;;;4CAK3DlF,YAAYK,MAAM,KAAK,kBACtB,KAACnC;gDAAOiI,OAAO;gDAACf,SAAQ;gDAAcC,MAAK;gDAAKH,WAAU;0DACxD,cAAA,KAACrG;oDAAKkE,IAAG;oDAAgCC,QAAQ;wDAACC,KAAKxD,UAAUM,EAAE;oDAAA;8DAAG;;iDAItE;;;oCAGLC,YAAYK,MAAM,GAAG,kBACpB,KAACzC;wCACCoC,aAAaA;wCACbE,sBAAsBK;wCACtB6F,UAAUzD;yCAEV;;;4BAGLmC,+BACC,MAACmB;gCAAQf,WAAU;gCAAuBgB,cAAW;;kDACnD,MAACjB;wCAAIC,WAAU;;0DACb,KAACzG;gDAAO2G,SAAQ;0DAAK;;0DACrB,KAAC1G;gDAAK2G,MAAK;gDAAKH,WAAU;0DAAgC;;;;kDAK5D,KAACpH;wCACCqD,cAAcA;wCACdI,sBAAsBA;wCACtB6E,UAAU5E;wCACV6E,WAAWpF,kBAAkB4B,SAAS;wCACtCyD,oBAAoBrF,kBAAkBqF,kBAAkB;wCACxDC,aAAatF,kBAAkBsF,WAAW;wCAC1CC,YAAY,IAAMvF,kBAAkBwF,aAAa;wCACjDC,cAAc1B;wCACd2B,aAAahG;wCACbiG,gBAAgBhG;;;iCAGlB;;;kCAGN,KAACiG;wBAAM3B,WAAU;kCACf,cAAA,MAACD;4BAAIC,WAAU;;8CACb,MAACD;oCAAIC,WAAU;;sDACb,KAACzG;4CAAO2G,SAAQ;sDAAK;;sDACrB,KAAC1G;4CAAK2G,MAAK;4CAAKH,WAAU;sDAAgC;;;;gCAK3DjD,0BACC,KAAC9D;oCAAQoH,MAAK;oCAAQC,MAAK;8CACzB,cAAA,KAACP;wCAAI6B,KAAKjH;wCAAUkH,UAAU,CAAC;kDAC5B9E;;qCAGH;8CAEJ,KAAC+E;oCACCtG,YAAYF;oCACZyG,gBAAgBvF,oBAAoBwF;;8CAGtC,KAAC/E,KAAKgF,KAAK;oCACTnF,MAAK;oCACLoF,YAAY;wCACVC,QAAQ,CAAC,EAAC/E,KAAK,EAAC,GACdrE,sBAAsBqE,OAAO,gBAAgB5E,wBAAwB4J,KAAK,CAACtF,IAAI;wCACjFK,UAAU,CAAC,EAACC,KAAK,EAAC,GAChBrE,sBAAsBqE,OAAO,gBAAgB5E,wBAAwB4J,KAAK,CAACtF,IAAI;oCACnF;8CAEC,CAACuF,sBACA,KAACnJ;4CAAUoJ,OAAM;4CAAezH,IAAG;4CAAesE,OAAO/F,WAAWiJ;sDAClE,cAAA,KAAClJ;gDACC2D,MAAK;gDACLwD,MAAK;gDACLlD,OAAOiF,MAAM/E,KAAK,CAACF,KAAK;gDACxBmF,UAAU,CAAC9B;oDACT9D,eAAe;oDACf0F,MAAMG,YAAY,CAAC/B,MAAMgC,MAAM,CAACrF,KAAK;gDACvC;gDACA+E,QAAQE,MAAMK,UAAU;gDACxBC,aAAY;;;;8CAMpB,KAAC3J;oCACCsH,MAAK;oCACLsC,WAAU;oCACVzB,WAAWzG,cAAciD,SAAS;oCAClCkF,UAAU,CAACvH,sBAAsB,CAACkB;oCAClCwD,WAAU;8CACX;;;;;;;;;AAQb;AAEA,SAAS8B,eAAe,EACtBtG,UAAU,EACVuG,cAAc,EAIf;IACC,qBACE,MAAChC;QAAIC,WAAU;;YACZ+B,+BACC,KAACpJ;gBACC4F,QAAQ/C,YAAYsH;gBACpBC,aAAW;gBACX/C,WAAU;iBAEV;0BACJ,KAACxG;gBACC2G,MAAK;gBACL6C,IAAI;gBACJhD,WAAW+B,iBAAiB,qBAAqB;0BAEhDA,kBAAkB;;;;AAI3B;AAEA,SAASlF,0BAA0BoG,YAAoB;IACrD,OAAOA,aACJnH,IAAI,GACJoH,KAAK,CAAC7I,0BACN8I,MAAM,CAACtD,SACPuD,GAAG,CAAC,CAACC,OAASA,KAAKC,MAAM,CAAC,GAAGC,WAAW,KAAKF,KAAKG,KAAK,CAAC,IACxDC,IAAI,CAAC;AACV;AAEA,SAAS7H,kBAAqBwB,KAAQ,EAAEsG,OAAe;IACrD,MAAM,CAACC,WAAWC,aAAa,GAAG5J,SAASoD;IAC3CtD,UAAU;QACR,MAAM+J,SAASC,WAAW,IAAMF,aAAaxG,QAAQsG;QACrD,OAAO,IAAMK,aAAaF;IAC5B,GAAG;QAACzG;QAAOsG;KAAQ;IACnB,OAAOC;AACT;AAEA,SAAShF,0BACPnE,WAAwB,EACxB4F,WAAmB,EACnB3B,OAA2B;IAE3BjE,YAAYoE,YAAY,CAA0B1E,kBAAkBoF,MAAM,CAACc,cAAc;QACvF4D,UAAU;YAACvF;SAAQ;QACnBwF,aAAa;IACf;AACF"}
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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;
|
|
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 +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":"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
|
|
20
|
-
const
|
|
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
|
|
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
|
|
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:
|
|
55
|
-
onChange: (event)=>
|
|
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 ||
|
|
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 && !
|
|
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 &&
|
|
98
|
-
search:
|
|
99
|
-
onClear: ()=>
|
|
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.
|
|
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?.
|
|
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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"}
|