@shipfox/client-projects 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +1 -0
- package/.storybook/preview.css +7 -0
- package/.storybook/preview.tsx +56 -0
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +207 -0
- package/LICENSE +21 -0
- package/dist/chrome.d.ts +3 -0
- package/dist/chrome.d.ts.map +1 -0
- package/dist/chrome.js +42 -0
- package/dist/chrome.js.map +1 -0
- package/dist/components/model-provider-reminder-banner.d.ts +4 -0
- package/dist/components/model-provider-reminder-banner.d.ts.map +1 -0
- package/dist/components/model-provider-reminder-banner.js +73 -0
- package/dist/components/model-provider-reminder-banner.js.map +1 -0
- package/dist/components/project-crumb.d.ts +7 -0
- package/dist/components/project-crumb.d.ts.map +1 -0
- package/dist/components/project-crumb.js +93 -0
- package/dist/components/project-crumb.js.map +1 -0
- package/dist/components/project-switcher.d.ts +7 -0
- package/dist/components/project-switcher.d.ts.map +1 -0
- package/dist/components/project-switcher.js +138 -0
- package/dist/components/project-switcher.js.map +1 -0
- package/dist/components/source-strip.d.ts +14 -0
- package/dist/components/source-strip.d.ts.map +1 -0
- package/dist/components/source-strip.js +98 -0
- package/dist/components/source-strip.js.map +1 -0
- package/dist/components/workspace-setup-guard.d.ts +9 -0
- package/dist/components/workspace-setup-guard.d.ts.map +1 -0
- package/dist/components/workspace-setup-guard.js +160 -0
- package/dist/components/workspace-setup-guard.js.map +1 -0
- package/dist/feature.d.ts +35 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +45 -0
- package/dist/feature.js.map +1 -0
- package/dist/hooks/api/definitions.d.ts +97 -0
- package/dist/hooks/api/definitions.d.ts.map +1 -0
- package/dist/hooks/api/definitions.js +55 -0
- package/dist/hooks/api/definitions.js.map +1 -0
- package/dist/hooks/api/projects.d.ts +93 -0
- package/dist/hooks/api/projects.d.ts.map +1 -0
- package/dist/hooks/api/projects.js +79 -0
- package/dist/hooks/api/projects.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/create-project-page.d.ts +2 -0
- package/dist/pages/create-project-page.d.ts.map +1 -0
- package/dist/pages/create-project-page.js +406 -0
- package/dist/pages/create-project-page.js.map +1 -0
- package/dist/pages/create-project-page.stories.d.ts +24 -0
- package/dist/pages/create-project-page.stories.d.ts.map +1 -0
- package/dist/pages/create-project-page.stories.js +345 -0
- package/dist/pages/create-project-page.stories.js.map +1 -0
- package/dist/pages/home-router.d.ts +2 -0
- package/dist/pages/home-router.d.ts.map +1 -0
- package/dist/pages/home-router.js +7 -0
- package/dist/pages/home-router.js.map +1 -0
- package/dist/pages/project-workflows-page.d.ts +4 -0
- package/dist/pages/project-workflows-page.d.ts.map +1 -0
- package/dist/pages/project-workflows-page.js +481 -0
- package/dist/pages/project-workflows-page.js.map +1 -0
- package/dist/pages/projects-hub-page.d.ts +2 -0
- package/dist/pages/projects-hub-page.d.ts.map +1 -0
- package/dist/pages/projects-hub-page.js +256 -0
- package/dist/pages/projects-hub-page.js.map +1 -0
- package/dist/project-error.d.ts +7 -0
- package/dist/project-error.d.ts.map +1 -0
- package/dist/project-error.js +93 -0
- package/dist/project-error.js.map +1 -0
- package/dist/routes/create-project.d.ts +6 -0
- package/dist/routes/create-project.d.ts.map +1 -0
- package/dist/routes/create-project.js +7 -0
- package/dist/routes/create-project.js.map +1 -0
- package/dist/routes/home.d.ts +6 -0
- package/dist/routes/home.d.ts.map +1 -0
- package/dist/routes/home.js +7 -0
- package/dist/routes/home.js.map +1 -0
- package/dist/routes/project-index.d.ts +10 -0
- package/dist/routes/project-index.d.ts.map +1 -0
- package/dist/routes/project-index.js +15 -0
- package/dist/routes/project-index.js.map +1 -0
- package/dist/routes/workflows.d.ts +5 -0
- package/dist/routes/workflows.d.ts.map +1 -0
- package/dist/routes/workflows.js +16 -0
- package/dist/routes/workflows.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +120 -0
- package/src/chrome.tsx +26 -0
- package/src/components/model-provider-reminder-banner.tsx +78 -0
- package/src/components/project-crumb.tsx +70 -0
- package/src/components/project-switcher.test.tsx +91 -0
- package/src/components/project-switcher.tsx +127 -0
- package/src/components/source-strip.tsx +91 -0
- package/src/components/workspace-setup-guard.test.tsx +534 -0
- package/src/components/workspace-setup-guard.tsx +185 -0
- package/src/feature.ts +44 -0
- package/src/hooks/api/definitions.ts +50 -0
- package/src/hooks/api/projects.test.ts +78 -0
- package/src/hooks/api/projects.ts +75 -0
- package/src/index.ts +15 -0
- package/src/pages/create-project-page.stories.tsx +344 -0
- package/src/pages/create-project-page.test.tsx +317 -0
- package/src/pages/create-project-page.tsx +386 -0
- package/src/pages/home-router.test.tsx +27 -0
- package/src/pages/home-router.tsx +5 -0
- package/src/pages/project-workflows-page.test.tsx +258 -0
- package/src/pages/project-workflows-page.tsx +445 -0
- package/src/pages/projects-hub-page.test.tsx +313 -0
- package/src/pages/projects-hub-page.tsx +246 -0
- package/src/project-error.test.ts +59 -0
- package/src/project-error.ts +106 -0
- package/src/routes/create-project.tsx +4 -0
- package/src/routes/home.tsx +4 -0
- package/src/routes/project-index.tsx +11 -0
- package/src/routes/workflows.tsx +10 -0
- package/test/pages.tsx +151 -0
- package/test/setup.ts +3 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +70 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createProjectBodySchema } from '@shipfox/api-projects-dto';
|
|
3
|
+
import { useMaybeActiveWorkspace } from '@shipfox/client-auth';
|
|
4
|
+
import { ConnectionPicker, IntegrationIcon, RepositoryPicker, useRepositoriesInfiniteQuery, useSourceConnectionsQuery } from '@shipfox/client-integrations';
|
|
5
|
+
import { displayNameFieldError } from '@shipfox/client-ui';
|
|
6
|
+
import { Button } from '@shipfox/react-ui/button';
|
|
7
|
+
import { Callout } from '@shipfox/react-ui/callout';
|
|
8
|
+
import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-field';
|
|
9
|
+
import { FullPageLoader } from '@shipfox/react-ui/loader';
|
|
10
|
+
import { toast } from '@shipfox/react-ui/toast';
|
|
11
|
+
import { Header, Text } from '@shipfox/react-ui/typography';
|
|
12
|
+
import { useForm } from '@tanstack/react-form';
|
|
13
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
14
|
+
import { Link, Navigate, useNavigate } from '@tanstack/react-router';
|
|
15
|
+
import { useEffect, useRef, useState } from 'react';
|
|
16
|
+
import { ModelProviderReminderBanner } from '#components/model-provider-reminder-banner.js';
|
|
17
|
+
import { projectsQueryKeys, useCreateProjectMutation } from '#hooks/api/projects.js';
|
|
18
|
+
import { projectErrorCopy } from '#project-error.js';
|
|
19
|
+
const REPOSITORY_NAME_SPLIT_RE = /[/-]/;
|
|
20
|
+
export function CreateProjectPage() {
|
|
21
|
+
const workspace = useMaybeActiveWorkspace();
|
|
22
|
+
const queryClient = useQueryClient();
|
|
23
|
+
const navigate = useNavigate();
|
|
24
|
+
const createProject = useCreateProjectMutation();
|
|
25
|
+
const errorRef = useRef(null);
|
|
26
|
+
const connectionsQuery = useSourceConnectionsQuery(workspace?.id);
|
|
27
|
+
const connections = connectionsQuery.data?.connections ?? [];
|
|
28
|
+
const [selectedConnectionId, setSelectedConnectionId] = useState();
|
|
29
|
+
const singleConnectionId = connections.length === 1 ? connections[0]?.id : undefined;
|
|
30
|
+
const effectiveSelectedConnectionId = selectedConnectionId ?? singleConnectionId;
|
|
31
|
+
useEffect(()=>{
|
|
32
|
+
if (singleConnectionId && selectedConnectionId !== singleConnectionId) {
|
|
33
|
+
setSelectedConnectionId(singleConnectionId);
|
|
34
|
+
}
|
|
35
|
+
}, [
|
|
36
|
+
singleConnectionId,
|
|
37
|
+
selectedConnectionId
|
|
38
|
+
]);
|
|
39
|
+
const selectedConnection = connections.find((connection)=>connection.id === effectiveSelectedConnectionId);
|
|
40
|
+
const [repoFilter, setRepoFilter] = useState('');
|
|
41
|
+
const debouncedRepoFilter = useDebouncedValue(repoFilter, 250);
|
|
42
|
+
const trimmedFilter = debouncedRepoFilter.trim();
|
|
43
|
+
const repositoriesQuery = useRepositoriesInfiniteQuery(effectiveSelectedConnectionId, trimmedFilter ? {
|
|
44
|
+
search: trimmedFilter
|
|
45
|
+
} : undefined);
|
|
46
|
+
const repositories = repositoriesQuery.data?.pages.flatMap((page)=>page.repositories) ?? [];
|
|
47
|
+
const [selectedRepositoryId, setSelectedRepositoryId] = useState();
|
|
48
|
+
useEffect(()=>{
|
|
49
|
+
if (!selectedRepositoryId && repositories[0]) {
|
|
50
|
+
setSelectedRepositoryId(repositories[0].external_repository_id);
|
|
51
|
+
}
|
|
52
|
+
}, [
|
|
53
|
+
repositories,
|
|
54
|
+
selectedRepositoryId
|
|
55
|
+
]);
|
|
56
|
+
const selectedRepository = repositories.find((repository)=>repository.external_repository_id === selectedRepositoryId);
|
|
57
|
+
const [nameTouched, setNameTouched] = useState(false);
|
|
58
|
+
const defaultProjectName = projectNameFromRepository(selectedRepository?.name ?? selectedRepositoryId ?? '');
|
|
59
|
+
const [formError, setFormError] = useState();
|
|
60
|
+
const form = useForm({
|
|
61
|
+
defaultValues: {
|
|
62
|
+
name: defaultProjectName
|
|
63
|
+
},
|
|
64
|
+
onSubmit: async ({ value })=>{
|
|
65
|
+
await createProjectFromForm(nameTouched ? value.name : defaultProjectName);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
useEffect(()=>{
|
|
69
|
+
if (!nameTouched && form.state.values.name !== defaultProjectName) {
|
|
70
|
+
form.setFieldValue('name', defaultProjectName);
|
|
71
|
+
}
|
|
72
|
+
}, [
|
|
73
|
+
defaultProjectName,
|
|
74
|
+
form,
|
|
75
|
+
nameTouched
|
|
76
|
+
]);
|
|
77
|
+
function selectConnection(connectionId) {
|
|
78
|
+
setSelectedConnectionId(connectionId);
|
|
79
|
+
setSelectedRepositoryId(undefined);
|
|
80
|
+
}
|
|
81
|
+
if (connectionsQuery.isPending) {
|
|
82
|
+
return /*#__PURE__*/ _jsx(FullPageLoader, {});
|
|
83
|
+
}
|
|
84
|
+
if (!workspace) {
|
|
85
|
+
return /*#__PURE__*/ _jsx(FullPageLoader, {});
|
|
86
|
+
}
|
|
87
|
+
if (!connectionsQuery.isError && connections.length === 0) {
|
|
88
|
+
return /*#__PURE__*/ _jsx(Navigate, {
|
|
89
|
+
to: "/workspaces/$wid/integrations",
|
|
90
|
+
params: {
|
|
91
|
+
wid: workspace.id
|
|
92
|
+
},
|
|
93
|
+
replace: true
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async function createProjectFromForm(projectName) {
|
|
97
|
+
setFormError(undefined);
|
|
98
|
+
if (!workspace) {
|
|
99
|
+
setFormError('Workspace is still loading. Try again in a moment.');
|
|
100
|
+
errorRef.current?.focus();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!selectedConnection) {
|
|
104
|
+
setFormError('Choose a source integration before creating a project.');
|
|
105
|
+
errorRef.current?.focus();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!selectedRepository) {
|
|
109
|
+
setFormError('Choose a repository before creating a project.');
|
|
110
|
+
errorRef.current?.focus();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const projectBody = createProjectBodySchema.parse({
|
|
115
|
+
workspace_id: workspace.id,
|
|
116
|
+
name: projectName,
|
|
117
|
+
source: {
|
|
118
|
+
connection_id: selectedConnection.id,
|
|
119
|
+
external_repository_id: selectedRepository.external_repository_id
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const project = await createProject.mutateAsync(projectBody);
|
|
123
|
+
setWorkspaceProjectExists(queryClient, workspace.id, project);
|
|
124
|
+
queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);
|
|
125
|
+
await queryClient.invalidateQueries({
|
|
126
|
+
queryKey: projectsQueryKeys.list(workspace.id)
|
|
127
|
+
});
|
|
128
|
+
toast.success('Project created.');
|
|
129
|
+
await navigate({
|
|
130
|
+
to: '/workspaces/$wid/projects/$pid',
|
|
131
|
+
params: {
|
|
132
|
+
wid: workspace.id,
|
|
133
|
+
pid: project.id
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const copy = projectErrorCopy(error);
|
|
138
|
+
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
|
+
toast.info('Project already exists.');
|
|
147
|
+
await navigate({
|
|
148
|
+
to: '/workspaces/$wid/projects/$pid',
|
|
149
|
+
params: {
|
|
150
|
+
wid: workspace.id,
|
|
151
|
+
pid: copy.existingProjectId
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
setFormError(`${copy.title}: ${copy.message}`);
|
|
157
|
+
requestAnimationFrame(()=>errorRef.current?.focus());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const showRepoPicker = Boolean(selectedConnection);
|
|
161
|
+
const filteredEmptyMessage = trimmedFilter ? `No repositories matching "${repoFilter.trim()}".` : 'No repositories visible to this connection.';
|
|
162
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
163
|
+
className: "flex w-full flex-col gap-24",
|
|
164
|
+
children: [
|
|
165
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
166
|
+
className: "flex flex-col gap-6",
|
|
167
|
+
children: [
|
|
168
|
+
/*#__PURE__*/ _jsx(Header, {
|
|
169
|
+
id: "create-project-title",
|
|
170
|
+
variant: "h1",
|
|
171
|
+
children: "Create project"
|
|
172
|
+
}),
|
|
173
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
174
|
+
size: "sm",
|
|
175
|
+
className: "text-foreground-neutral-muted",
|
|
176
|
+
children: "A Shipfox project starts from a Git repository. Choose the repository Shipfox should track, then give the project a name."
|
|
177
|
+
})
|
|
178
|
+
]
|
|
179
|
+
}),
|
|
180
|
+
/*#__PURE__*/ _jsx(ModelProviderReminderBanner, {
|
|
181
|
+
workspaceId: workspace.id
|
|
182
|
+
}),
|
|
183
|
+
connectionsQuery.isError ? /*#__PURE__*/ _jsx(Callout, {
|
|
184
|
+
role: "alert",
|
|
185
|
+
type: "error",
|
|
186
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
187
|
+
className: "flex w-full flex-wrap items-center justify-between gap-12",
|
|
188
|
+
children: [
|
|
189
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
190
|
+
size: "sm",
|
|
191
|
+
className: "min-w-[240px] flex-1",
|
|
192
|
+
children: "Could not load source integrations. Refresh the integrations list to continue."
|
|
193
|
+
}),
|
|
194
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
195
|
+
size: "sm",
|
|
196
|
+
variant: "secondary",
|
|
197
|
+
onClick: ()=>connectionsQuery.refetch(),
|
|
198
|
+
className: "shrink-0",
|
|
199
|
+
children: "Refresh integrations"
|
|
200
|
+
})
|
|
201
|
+
]
|
|
202
|
+
})
|
|
203
|
+
}) : null,
|
|
204
|
+
/*#__PURE__*/ _jsxs("form", {
|
|
205
|
+
onSubmit: (event)=>{
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
event.stopPropagation();
|
|
208
|
+
void form.handleSubmit();
|
|
209
|
+
},
|
|
210
|
+
noValidate: true,
|
|
211
|
+
"aria-labelledby": "create-project-title",
|
|
212
|
+
className: "grid items-start gap-32 lg:grid-cols-[minmax(0,1fr)_340px]",
|
|
213
|
+
children: [
|
|
214
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
215
|
+
className: "flex min-w-0 flex-col gap-32",
|
|
216
|
+
children: [
|
|
217
|
+
/*#__PURE__*/ _jsxs("section", {
|
|
218
|
+
className: "flex flex-col gap-16",
|
|
219
|
+
"aria-label": "Source integration",
|
|
220
|
+
children: [
|
|
221
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
222
|
+
className: "flex items-start justify-between gap-16",
|
|
223
|
+
children: [
|
|
224
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
225
|
+
className: "flex flex-col gap-4",
|
|
226
|
+
children: [
|
|
227
|
+
/*#__PURE__*/ _jsx(Header, {
|
|
228
|
+
variant: "h3",
|
|
229
|
+
children: "Source integration"
|
|
230
|
+
}),
|
|
231
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
232
|
+
size: "sm",
|
|
233
|
+
className: "text-foreground-neutral-muted",
|
|
234
|
+
children: "Choose the integration that can access the repository."
|
|
235
|
+
})
|
|
236
|
+
]
|
|
237
|
+
}),
|
|
238
|
+
connections.length === 1 ? /*#__PURE__*/ _jsx(Button, {
|
|
239
|
+
asChild: true,
|
|
240
|
+
variant: "transparent",
|
|
241
|
+
size: "sm",
|
|
242
|
+
className: "shrink-0",
|
|
243
|
+
children: /*#__PURE__*/ _jsx(Link, {
|
|
244
|
+
to: "/workspaces/$wid/integrations",
|
|
245
|
+
params: {
|
|
246
|
+
wid: workspace.id
|
|
247
|
+
},
|
|
248
|
+
children: "Add another integration"
|
|
249
|
+
})
|
|
250
|
+
}) : null
|
|
251
|
+
]
|
|
252
|
+
}),
|
|
253
|
+
connections.length > 0 ? /*#__PURE__*/ _jsx(ConnectionPicker, {
|
|
254
|
+
connections: connections,
|
|
255
|
+
selectedConnectionId: effectiveSelectedConnectionId,
|
|
256
|
+
onSelect: selectConnection
|
|
257
|
+
}) : null
|
|
258
|
+
]
|
|
259
|
+
}),
|
|
260
|
+
showRepoPicker ? /*#__PURE__*/ _jsxs("section", {
|
|
261
|
+
className: "flex flex-col gap-16",
|
|
262
|
+
"aria-label": "Repository",
|
|
263
|
+
children: [
|
|
264
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
265
|
+
className: "flex flex-col gap-4",
|
|
266
|
+
children: [
|
|
267
|
+
/*#__PURE__*/ _jsx(Header, {
|
|
268
|
+
variant: "h3",
|
|
269
|
+
children: "Repository"
|
|
270
|
+
}),
|
|
271
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
272
|
+
size: "sm",
|
|
273
|
+
className: "text-foreground-neutral-muted",
|
|
274
|
+
children: "Select the repository this project tracks."
|
|
275
|
+
})
|
|
276
|
+
]
|
|
277
|
+
}),
|
|
278
|
+
/*#__PURE__*/ _jsx(RepositoryPicker, {
|
|
279
|
+
repositories: repositories,
|
|
280
|
+
selectedRepositoryId: selectedRepositoryId,
|
|
281
|
+
onSelect: setSelectedRepositoryId,
|
|
282
|
+
isLoading: repositoriesQuery.isPending,
|
|
283
|
+
isFetchingNextPage: repositoriesQuery.isFetchingNextPage,
|
|
284
|
+
hasNextPage: repositoriesQuery.hasNextPage,
|
|
285
|
+
onLoadMore: ()=>repositoriesQuery.fetchNextPage(),
|
|
286
|
+
emptyMessage: filteredEmptyMessage,
|
|
287
|
+
searchValue: repoFilter,
|
|
288
|
+
onSearchChange: setRepoFilter
|
|
289
|
+
})
|
|
290
|
+
]
|
|
291
|
+
}) : null
|
|
292
|
+
]
|
|
293
|
+
}),
|
|
294
|
+
/*#__PURE__*/ _jsx("aside", {
|
|
295
|
+
className: "lg:sticky lg:top-32",
|
|
296
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
297
|
+
className: "flex flex-col gap-18 rounded-8 border border-border-neutral-base bg-background-neutral-base p-20",
|
|
298
|
+
children: [
|
|
299
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
300
|
+
className: "flex flex-col gap-4",
|
|
301
|
+
children: [
|
|
302
|
+
/*#__PURE__*/ _jsx(Header, {
|
|
303
|
+
variant: "h3",
|
|
304
|
+
children: "Project details"
|
|
305
|
+
}),
|
|
306
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
307
|
+
size: "sm",
|
|
308
|
+
className: "text-foreground-neutral-muted",
|
|
309
|
+
children: "Confirm the source and create the project."
|
|
310
|
+
})
|
|
311
|
+
]
|
|
312
|
+
}),
|
|
313
|
+
formError ? /*#__PURE__*/ _jsx(Callout, {
|
|
314
|
+
role: "alert",
|
|
315
|
+
type: "error",
|
|
316
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
317
|
+
ref: errorRef,
|
|
318
|
+
tabIndex: -1,
|
|
319
|
+
children: formError
|
|
320
|
+
})
|
|
321
|
+
}) : null,
|
|
322
|
+
/*#__PURE__*/ _jsx(ProjectSummary, {
|
|
323
|
+
connection: selectedConnection,
|
|
324
|
+
repositoryName: selectedRepository?.full_name
|
|
325
|
+
}),
|
|
326
|
+
/*#__PURE__*/ _jsx(form.Field, {
|
|
327
|
+
name: "name",
|
|
328
|
+
validators: {
|
|
329
|
+
onBlur: ({ value })=>displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),
|
|
330
|
+
onSubmit: ({ value })=>displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name)
|
|
331
|
+
},
|
|
332
|
+
children: (field)=>/*#__PURE__*/ _jsx(FormField, {
|
|
333
|
+
label: "Project name",
|
|
334
|
+
id: "project-name",
|
|
335
|
+
error: fieldError(field),
|
|
336
|
+
children: /*#__PURE__*/ _jsx(FormFieldInput, {
|
|
337
|
+
name: "name",
|
|
338
|
+
type: "text",
|
|
339
|
+
value: field.state.value,
|
|
340
|
+
onChange: (event)=>{
|
|
341
|
+
setNameTouched(true);
|
|
342
|
+
field.handleChange(event.target.value);
|
|
343
|
+
},
|
|
344
|
+
onBlur: field.handleBlur,
|
|
345
|
+
placeholder: "Platform"
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
}),
|
|
349
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
350
|
+
type: "submit",
|
|
351
|
+
iconRight: "chevronRight",
|
|
352
|
+
isLoading: createProject.isPending,
|
|
353
|
+
disabled: !selectedConnection || !selectedRepository,
|
|
354
|
+
className: "w-full",
|
|
355
|
+
children: "Create project"
|
|
356
|
+
})
|
|
357
|
+
]
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
]
|
|
361
|
+
})
|
|
362
|
+
]
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
function ProjectSummary({ connection, repositoryName }) {
|
|
366
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
367
|
+
className: "flex min-w-0 items-center gap-10",
|
|
368
|
+
children: [
|
|
369
|
+
repositoryName ? /*#__PURE__*/ _jsx(IntegrationIcon, {
|
|
370
|
+
source: connection?.provider,
|
|
371
|
+
"aria-hidden": true,
|
|
372
|
+
className: "size-20 shrink-0 text-foreground-neutral-base"
|
|
373
|
+
}) : null,
|
|
374
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
375
|
+
size: "sm",
|
|
376
|
+
bold: true,
|
|
377
|
+
className: repositoryName ? 'min-w-0 truncate' : 'text-foreground-neutral-muted',
|
|
378
|
+
children: repositoryName ?? 'Pick a repository'
|
|
379
|
+
})
|
|
380
|
+
]
|
|
381
|
+
});
|
|
382
|
+
}
|
|
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
|
+
function useDebouncedValue(value, delayMs) {
|
|
387
|
+
const [debounced, setDebounced] = useState(value);
|
|
388
|
+
useEffect(()=>{
|
|
389
|
+
const handle = setTimeout(()=>setDebounced(value), delayMs);
|
|
390
|
+
return ()=>clearTimeout(handle);
|
|
391
|
+
}, [
|
|
392
|
+
value,
|
|
393
|
+
delayMs
|
|
394
|
+
]);
|
|
395
|
+
return debounced;
|
|
396
|
+
}
|
|
397
|
+
function setWorkspaceProjectExists(queryClient, workspaceId, project) {
|
|
398
|
+
queryClient.setQueryData(projectsQueryKeys.exists(workspaceId), {
|
|
399
|
+
projects: [
|
|
400
|
+
project
|
|
401
|
+
],
|
|
402
|
+
next_cursor: null
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
//# sourceMappingURL=create-project-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/pages/create-project-page.tsx"],"sourcesContent":["import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';\nimport {\n createProjectBodySchema,\n type ListProjectsResponseDto,\n type ProjectResponseDto,\n} from '@shipfox/api-projects-dto';\nimport {useMaybeActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionPicker,\n IntegrationIcon,\n RepositoryPicker,\n useRepositoriesInfiniteQuery,\n useSourceConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {displayNameFieldError} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {type QueryClient, useQueryClient} from '@tanstack/react-query';\nimport {Link, Navigate, useNavigate} from '@tanstack/react-router';\nimport {useEffect, useRef, useState} from 'react';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport {projectsQueryKeys, useCreateProjectMutation} from '#hooks/api/projects.js';\nimport {projectErrorCopy} from '#project-error.js';\n\nconst REPOSITORY_NAME_SPLIT_RE = /[/-]/;\n\nexport function CreateProjectPage() {\n const workspace = useMaybeActiveWorkspace();\n const queryClient = useQueryClient();\n const navigate = useNavigate();\n const createProject = useCreateProjectMutation();\n const errorRef = useRef<HTMLDivElement>(null);\n\n const connectionsQuery = useSourceConnectionsQuery(workspace?.id);\n const connections = connectionsQuery.data?.connections ?? [];\n\n const [selectedConnectionId, setSelectedConnectionId] = useState<string | undefined>();\n const singleConnectionId = connections.length === 1 ? connections[0]?.id : undefined;\n const effectiveSelectedConnectionId = selectedConnectionId ?? singleConnectionId;\n useEffect(() => {\n if (singleConnectionId && selectedConnectionId !== singleConnectionId) {\n setSelectedConnectionId(singleConnectionId);\n }\n }, [singleConnectionId, selectedConnectionId]);\n\n const selectedConnection: IntegrationConnectionDto | undefined = connections.find(\n (connection) => connection.id === effectiveSelectedConnectionId,\n );\n\n const [repoFilter, setRepoFilter] = useState('');\n const debouncedRepoFilter = useDebouncedValue(repoFilter, 250);\n const trimmedFilter = debouncedRepoFilter.trim();\n\n const repositoriesQuery = useRepositoriesInfiniteQuery(\n effectiveSelectedConnectionId,\n trimmedFilter ? {search: trimmedFilter} : undefined,\n );\n const repositories = repositoriesQuery.data?.pages.flatMap((page) => page.repositories) ?? [];\n\n const [selectedRepositoryId, setSelectedRepositoryId] = useState<string | undefined>();\n useEffect(() => {\n if (!selectedRepositoryId && repositories[0]) {\n setSelectedRepositoryId(repositories[0].external_repository_id);\n }\n }, [repositories, selectedRepositoryId]);\n const selectedRepository = repositories.find(\n (repository) => repository.external_repository_id === selectedRepositoryId,\n );\n\n const [nameTouched, setNameTouched] = useState(false);\n const defaultProjectName = projectNameFromRepository(\n selectedRepository?.name ?? selectedRepositoryId ?? '',\n );\n\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {name: defaultProjectName},\n onSubmit: async ({value}) => {\n await createProjectFromForm(nameTouched ? value.name : defaultProjectName);\n },\n });\n\n useEffect(() => {\n if (!nameTouched && form.state.values.name !== defaultProjectName) {\n form.setFieldValue('name', defaultProjectName);\n }\n }, [defaultProjectName, form, nameTouched]);\n\n function selectConnection(connectionId: string) {\n setSelectedConnectionId(connectionId);\n setSelectedRepositoryId(undefined);\n }\n\n if (connectionsQuery.isPending) {\n return <FullPageLoader />;\n }\n\n if (!workspace) {\n return <FullPageLoader />;\n }\n\n if (!connectionsQuery.isError && connections.length === 0) {\n return <Navigate to=\"/workspaces/$wid/integrations\" params={{wid: workspace.id}} replace />;\n }\n\n async function createProjectFromForm(projectName: string) {\n setFormError(undefined);\n if (!workspace) {\n setFormError('Workspace is still loading. Try again in a moment.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedConnection) {\n setFormError('Choose a source integration before creating a project.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedRepository) {\n setFormError('Choose a repository before creating a project.');\n errorRef.current?.focus();\n return;\n }\n\n try {\n const projectBody = createProjectBodySchema.parse({\n workspace_id: workspace.id,\n name: projectName,\n source: {\n connection_id: selectedConnection.id,\n external_repository_id: selectedRepository.external_repository_id,\n },\n });\n const project = await createProject.mutateAsync(projectBody);\n setWorkspaceProjectExists(queryClient, workspace.id, project);\n queryClient.setQueryData(projectsQueryKeys.detail(project.id), project);\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(workspace.id)});\n toast.success('Project created.');\n await navigate({\n to: '/workspaces/$wid/projects/$pid',\n params: {wid: workspace.id, pid: project.id},\n });\n } catch (error) {\n const copy = projectErrorCopy(error);\n if (copy.existingProjectId) {\n await queryClient.invalidateQueries({\n queryKey: projectsQueryKeys.exists(workspace.id),\n refetchType: 'active',\n });\n await queryClient.invalidateQueries({queryKey: projectsQueryKeys.list(workspace.id)});\n toast.info('Project already exists.');\n await navigate({\n to: '/workspaces/$wid/projects/$pid',\n params: {wid: workspace.id, pid: copy.existingProjectId},\n });\n return;\n }\n setFormError(`${copy.title}: ${copy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n }\n\n const showRepoPicker = Boolean(selectedConnection);\n const filteredEmptyMessage = trimmedFilter\n ? `No repositories matching \"${repoFilter.trim()}\".`\n : 'No repositories visible to this connection.';\n\n return (\n <div className=\"flex w-full flex-col gap-24\">\n <header className=\"flex flex-col gap-6\">\n <Header id=\"create-project-title\" variant=\"h1\">\n Create project\n </Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n A Shipfox project starts from a Git repository. Choose the repository Shipfox should\n track, then give the project a name.\n </Text>\n </header>\n\n <ModelProviderReminderBanner workspaceId={workspace.id} />\n\n {connectionsQuery.isError ? (\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex w-full flex-wrap items-center justify-between gap-12\">\n <Text size=\"sm\" className=\"min-w-[240px] flex-1\">\n Could not load source integrations. Refresh the integrations list to continue.\n </Text>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => connectionsQuery.refetch()}\n className=\"shrink-0\"\n >\n Refresh integrations\n </Button>\n </div>\n </Callout>\n ) : null}\n\n <form\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n noValidate\n aria-labelledby=\"create-project-title\"\n className=\"grid items-start gap-32 lg:grid-cols-[minmax(0,1fr)_340px]\"\n >\n <div className=\"flex min-w-0 flex-col gap-32\">\n <section className=\"flex flex-col gap-16\" aria-label=\"Source integration\">\n <div className=\"flex items-start justify-between gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Source integration</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Choose the integration that can access the repository.\n </Text>\n </div>\n\n {connections.length === 1 ? (\n <Button asChild variant=\"transparent\" size=\"sm\" className=\"shrink-0\">\n <Link to=\"/workspaces/$wid/integrations\" params={{wid: workspace.id}}>\n Add another integration\n </Link>\n </Button>\n ) : null}\n </div>\n\n {connections.length > 0 ? (\n <ConnectionPicker\n connections={connections}\n selectedConnectionId={effectiveSelectedConnectionId}\n onSelect={selectConnection}\n />\n ) : null}\n </section>\n\n {showRepoPicker ? (\n <section className=\"flex flex-col gap-16\" aria-label=\"Repository\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Repository</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Select the repository this project tracks.\n </Text>\n </div>\n\n <RepositoryPicker\n repositories={repositories}\n selectedRepositoryId={selectedRepositoryId}\n onSelect={setSelectedRepositoryId}\n isLoading={repositoriesQuery.isPending}\n isFetchingNextPage={repositoriesQuery.isFetchingNextPage}\n hasNextPage={repositoriesQuery.hasNextPage}\n onLoadMore={() => repositoriesQuery.fetchNextPage()}\n emptyMessage={filteredEmptyMessage}\n searchValue={repoFilter}\n onSearchChange={setRepoFilter}\n />\n </section>\n ) : null}\n </div>\n\n <aside className=\"lg:sticky lg:top-32\">\n <div className=\"flex flex-col gap-18 rounded-8 border border-border-neutral-base bg-background-neutral-base p-20\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Project details</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Confirm the source and create the project.\n </Text>\n </div>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n <div ref={errorRef} tabIndex={-1}>\n {formError}\n </div>\n </Callout>\n ) : null}\n\n <ProjectSummary\n connection={selectedConnection}\n repositoryName={selectedRepository?.full_name}\n />\n\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Project name\" id=\"project-name\" error={fieldError(field)}>\n <FormFieldInput\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => {\n setNameTouched(true);\n field.handleChange(event.target.value);\n }}\n onBlur={field.handleBlur}\n placeholder=\"Platform\"\n />\n </FormField>\n )}\n </form.Field>\n\n <Button\n type=\"submit\"\n iconRight=\"chevronRight\"\n isLoading={createProject.isPending}\n disabled={!selectedConnection || !selectedRepository}\n className=\"w-full\"\n >\n Create project\n </Button>\n </div>\n </aside>\n </form>\n </div>\n );\n}\n\nfunction ProjectSummary({\n connection,\n repositoryName,\n}: {\n connection: IntegrationConnectionDto | undefined;\n repositoryName: string | undefined;\n}) {\n return (\n <div className=\"flex min-w-0 items-center gap-10\">\n {repositoryName ? (\n <IntegrationIcon\n source={connection?.provider}\n aria-hidden\n className=\"size-20 shrink-0 text-foreground-neutral-base\"\n />\n ) : null}\n <Text\n size=\"sm\"\n bold\n className={repositoryName ? 'min-w-0 truncate' : 'text-foreground-neutral-muted'}\n >\n {repositoryName ?? 'Pick a repository'}\n </Text>\n </div>\n );\n}\n\nfunction projectNameFromRepository(repositoryId: string): string {\n return repositoryId\n .trim()\n .split(REPOSITORY_NAME_SPLIT_RE)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ');\n}\n\nfunction useDebouncedValue<T>(value: T, delayMs: number): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const handle = setTimeout(() => setDebounced(value), delayMs);\n return () => clearTimeout(handle);\n }, [value, delayMs]);\n return debounced;\n}\n\nfunction setWorkspaceProjectExists(\n queryClient: QueryClient,\n workspaceId: string,\n project: ProjectResponseDto,\n) {\n queryClient.setQueryData<ListProjectsResponseDto>(projectsQueryKeys.exists(workspaceId), {\n projects: [project],\n next_cursor: null,\n });\n}\n"],"names":["createProjectBodySchema","useMaybeActiveWorkspace","ConnectionPicker","IntegrationIcon","RepositoryPicker","useRepositoriesInfiniteQuery","useSourceConnectionsQuery","displayNameFieldError","Button","Callout","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","useQueryClient","Link","Navigate","useNavigate","useEffect","useRef","useState","ModelProviderReminderBanner","projectsQueryKeys","useCreateProjectMutation","projectErrorCopy","REPOSITORY_NAME_SPLIT_RE","CreateProjectPage","workspace","queryClient","navigate","createProject","errorRef","connectionsQuery","id","connections","data","selectedConnectionId","setSelectedConnectionId","singleConnectionId","length","undefined","effectiveSelectedConnectionId","selectedConnection","find","connection","repoFilter","setRepoFilter","debouncedRepoFilter","useDebouncedValue","trimmedFilter","trim","repositoriesQuery","search","repositories","pages","flatMap","page","selectedRepositoryId","setSelectedRepositoryId","external_repository_id","selectedRepository","repository","nameTouched","setNameTouched","defaultProjectName","projectNameFromRepository","name","formError","setFormError","form","defaultValues","onSubmit","value","createProjectFromForm","state","values","setFieldValue","selectConnection","connectionId","isPending","isError","to","params","wid","replace","projectName","current","focus","projectBody","parse","workspace_id","source","connection_id","project","mutateAsync","setWorkspaceProjectExists","setQueryData","detail","invalidateQueries","queryKey","list","success","pid","error","copy","existingProjectId","exists","refetchType","info","title","message","requestAnimationFrame","showRepoPicker","Boolean","filteredEmptyMessage","div","className","header","variant","size","workspaceId","role","type","onClick","refetch","event","preventDefault","stopPropagation","handleSubmit","noValidate","aria-labelledby","section","aria-label","asChild","onSelect","isLoading","isFetchingNextPage","hasNextPage","onLoadMore","fetchNextPage","emptyMessage","searchValue","onSearchChange","aside","ref","tabIndex","ProjectSummary","repositoryName","full_name","Field","validators","onBlur","shape","field","label","onChange","handleChange","target","handleBlur","placeholder","iconRight","disabled","provider","aria-hidden","bold","repositoryId","split","filter","map","part","charAt","toUpperCase","slice","join","delayMs","debounced","setDebounced","handle","setTimeout","clearTimeout","projects","next_cursor"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
type Scenario = 'playground' | 'long-names' | 'repository-loading' | 'empty-repositories' | 'connections-error';
|
|
3
|
+
interface CreateProjectPageStoryProps {
|
|
4
|
+
scenario: Scenario;
|
|
5
|
+
}
|
|
6
|
+
declare function CreateProjectPageStory({ scenario }: CreateProjectPageStoryProps): import("react").JSX.Element;
|
|
7
|
+
declare const meta: {
|
|
8
|
+
title: string;
|
|
9
|
+
component: typeof CreateProjectPageStory;
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: string;
|
|
12
|
+
};
|
|
13
|
+
args: {
|
|
14
|
+
scenario: "playground";
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
export declare const Playground: Story;
|
|
20
|
+
export declare const LongNames: Story;
|
|
21
|
+
export declare const RepositoryLoading: Story;
|
|
22
|
+
export declare const EmptyRepositories: Story;
|
|
23
|
+
export declare const ConnectionsError: Story;
|
|
24
|
+
//# sourceMappingURL=create-project-page.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-project-page.stories.d.ts","sourceRoot":"","sources":["../../src/pages/create-project-page.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAmBrD,KAAK,QAAQ,GACT,YAAY,GACZ,YAAY,GACZ,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,CAAC;AAExB,UAAU,2BAA2B;IACnC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAiBD,iBAAS,sBAAsB,CAAC,EAAC,QAAQ,EAAC,EAAE,2BAA2B,+BA6BtE;AAED,QAAA,MAAM,IAAI;;;;;;;;;CAKqC,CAAC;AAEhD,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,SAAS,EAAE,KAEvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAE9B,CAAC"}
|