@shipfox/client-projects 12.0.2 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +49 -0
- package/dist/chrome.d.ts +5 -1
- package/dist/chrome.d.ts.map +1 -1
- package/dist/chrome.js +18 -26
- 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 +4 -2
- package/dist/components/model-provider-reminder-banner.js.map +1 -1
- package/dist/components/project-crumb.d.ts +3 -1
- package/dist/components/project-crumb.d.ts.map +1 -1
- package/dist/components/project-crumb.js +7 -5
- package/dist/components/project-crumb.js.map +1 -1
- package/dist/components/project-switcher.d.ts +2 -1
- package/dist/components/project-switcher.d.ts.map +1 -1
- package/dist/components/project-switcher.js +10 -8
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/core/definition.d.ts +6 -0
- package/dist/core/definition.d.ts.map +1 -1
- package/dist/core/definition.js.map +1 -1
- package/dist/core/project.d.ts +7 -0
- package/dist/core/project.d.ts.map +1 -1
- package/dist/core/project.js.map +1 -1
- package/dist/feature.d.ts +29 -5
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +26 -5
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/mappers.d.ts.map +1 -1
- package/dist/hooks/api/mappers.js +9 -1
- package/dist/hooks/api/mappers.js.map +1 -1
- package/dist/hooks/api/projects.d.ts +21 -2
- package/dist/hooks/api/projects.d.ts.map +1 -1
- package/dist/hooks/api/projects.js +175 -2
- package/dist/hooks/api/projects.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- 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 +97 -21
- package/dist/pages/create-project-page.js.map +1 -1
- package/dist/pages/create-project-page.stories.js +5 -3
- package/dist/pages/create-project-page.stories.js.map +1 -1
- package/dist/pages/project-settings-form-errors.d.ts +11 -0
- package/dist/pages/project-settings-form-errors.d.ts.map +1 -0
- package/dist/pages/project-settings-form-errors.js +16 -0
- package/dist/pages/project-settings-form-errors.js.map +1 -0
- package/dist/pages/project-settings-page.d.ts +2 -0
- package/dist/pages/project-settings-page.d.ts.map +1 -0
- package/dist/pages/project-settings-page.js +225 -0
- package/dist/pages/project-settings-page.js.map +1 -0
- package/dist/pages/project-settings-page.stories.d.ts +23 -0
- package/dist/pages/project-settings-page.stories.d.ts.map +1 -0
- package/dist/pages/project-settings-page.stories.js +206 -0
- package/dist/pages/project-settings-page.stories.js.map +1 -0
- package/dist/pages/projects-hub-page.js +11 -11
- package/dist/pages/projects-hub-page.js.map +1 -1
- package/dist/project-error.d.ts +1 -0
- package/dist/project-error.d.ts.map +1 -1
- package/dist/project-error.js +7 -0
- package/dist/project-error.js.map +1 -1
- package/dist/routes/inputs.d.ts +2 -2
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +5 -5
- package/dist/routes/inputs.js.map +1 -1
- package/dist/routes/project-index.d.ts +2 -2
- package/dist/routes/project-index.d.ts.map +1 -1
- package/dist/routes/project-index.js +3 -3
- package/dist/routes/project-index.js.map +1 -1
- package/dist/routes/project-settings-index.d.ts +10 -0
- package/dist/routes/project-settings-index.d.ts.map +1 -0
- package/dist/routes/project-settings-index.js +12 -0
- package/dist/routes/project-settings-index.js.map +1 -0
- package/dist/routes/project-settings.d.ts +6 -0
- package/dist/routes/project-settings.d.ts.map +1 -0
- package/dist/routes/project-settings.js +7 -0
- package/dist/routes/project-settings.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +11 -10
- package/src/chrome.tsx +27 -19
- package/src/components/model-provider-reminder-banner.tsx +3 -1
- package/src/components/project-crumb.tsx +18 -5
- package/src/components/project-switcher.test.tsx +7 -4
- package/src/components/project-switcher.tsx +16 -5
- package/src/core/definition.ts +7 -0
- package/src/core/project.ts +8 -0
- package/src/feature.ts +26 -4
- package/src/hooks/api/mappers.test.ts +19 -0
- package/src/hooks/api/mappers.ts +6 -0
- package/src/hooks/api/projects.test.ts +247 -1
- package/src/hooks/api/projects.ts +227 -2
- package/src/index.ts +9 -1
- package/src/pages/create-project-page.stories.tsx +5 -4
- package/src/pages/create-project-page.test.tsx +191 -12
- package/src/pages/create-project-page.tsx +102 -15
- package/src/pages/home-router.test.tsx +2 -2
- package/src/pages/project-settings-form-errors.ts +17 -0
- package/src/pages/project-settings-page.stories.tsx +216 -0
- package/src/pages/project-settings-page.test.tsx +74 -0
- package/src/pages/project-settings-page.tsx +220 -0
- package/src/pages/projects-hub-page.test.tsx +17 -11
- package/src/pages/projects-hub-page.tsx +9 -9
- package/src/project-error.test.ts +1 -0
- package/src/project-error.ts +8 -0
- package/src/routes/inputs.test.ts +8 -6
- package/src/routes/inputs.ts +7 -6
- package/src/routes/project-index.tsx +3 -3
- package/src/routes/project-settings-index.tsx +11 -0
- package/src/routes/project-settings.tsx +4 -0
- package/test/pages.tsx +26 -22
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { slugifyName, slugSchema } from '@shipfox/api-common-dto';
|
|
2
3
|
import { createProjectBodySchema } from '@shipfox/api-projects-dto';
|
|
3
4
|
import { useMaybeActiveWorkspace } from '@shipfox/client-auth';
|
|
4
5
|
import { ConnectionPicker, IntegrationIcon, RepositoryPicker, useRepositoriesInfiniteQuery, useSourceConnectionsQuery } from '@shipfox/client-integrations';
|
|
5
|
-
import { displayNameFieldError } from '@shipfox/client-ui';
|
|
6
|
+
import { displayNameFieldError, SlugField } from '@shipfox/client-ui';
|
|
6
7
|
import { Button } from '@shipfox/react-ui/button';
|
|
7
8
|
import { Callout } from '@shipfox/react-ui/callout';
|
|
8
9
|
import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-field';
|
|
@@ -14,8 +15,11 @@ import { Link, Navigate, useNavigate } from '@tanstack/react-router';
|
|
|
14
15
|
import { useEffect, useRef, useState } from 'react';
|
|
15
16
|
import { ModelProviderReminderBanner } from '#components/model-provider-reminder-banner.js';
|
|
16
17
|
import { projectNameFromRepository } from '#core/project.js';
|
|
17
|
-
import { useCreateProjectMutation } from '#hooks/api/projects.js';
|
|
18
|
+
import { getProject, useCreateProjectMutation, useProjectSlugAvailability } from '#hooks/api/projects.js';
|
|
18
19
|
import { projectErrorCopy } from '#project-error.js';
|
|
20
|
+
function isSlugValid(value) {
|
|
21
|
+
return slugSchema.safeParse(value).success;
|
|
22
|
+
}
|
|
19
23
|
export function CreateProjectPage() {
|
|
20
24
|
const workspace = useMaybeActiveWorkspace();
|
|
21
25
|
const navigate = useNavigate();
|
|
@@ -54,23 +58,42 @@ export function CreateProjectPage() {
|
|
|
54
58
|
const selectedRepository = repositories.find((repository)=>repository.externalRepositoryId === selectedRepositoryId);
|
|
55
59
|
const [nameTouched, setNameTouched] = useState(false);
|
|
56
60
|
const defaultProjectName = projectNameFromRepository(selectedRepository?.name ?? selectedRepositoryId ?? '');
|
|
61
|
+
const defaultProjectSlug = slugifyName(defaultProjectName, {
|
|
62
|
+
fallback: 'project'
|
|
63
|
+
});
|
|
57
64
|
const [formError, setFormError] = useState();
|
|
65
|
+
const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);
|
|
66
|
+
const [slugCheckEnabled, setSlugCheckEnabled] = useState(false);
|
|
67
|
+
const [slugConflict, setSlugConflict] = useState(false);
|
|
68
|
+
const workspaceId = workspace?.id;
|
|
69
|
+
const checkProjectSlugAvailability = useProjectSlugAvailability(workspaceId);
|
|
58
70
|
const form = useForm({
|
|
59
71
|
defaultValues: {
|
|
60
|
-
name: defaultProjectName
|
|
72
|
+
name: defaultProjectName,
|
|
73
|
+
slug: defaultProjectSlug
|
|
61
74
|
},
|
|
62
75
|
onSubmit: async ({ value })=>{
|
|
63
|
-
|
|
76
|
+
const projectSlug = slugManuallyEdited ? value.slug : slugifyName(value.name, {
|
|
77
|
+
fallback: 'project'
|
|
78
|
+
});
|
|
79
|
+
await createProjectFromForm(nameTouched ? value.name : defaultProjectName, projectSlug);
|
|
64
80
|
}
|
|
65
81
|
});
|
|
66
82
|
useEffect(()=>{
|
|
67
83
|
if (!nameTouched && form.state.values.name !== defaultProjectName) {
|
|
68
84
|
form.setFieldValue('name', defaultProjectName);
|
|
85
|
+
if (!slugManuallyEdited) {
|
|
86
|
+
setSlugConflict(false);
|
|
87
|
+
form.setFieldValue('slug', slugifyName(defaultProjectName, {
|
|
88
|
+
fallback: 'project'
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
69
91
|
}
|
|
70
92
|
}, [
|
|
71
93
|
defaultProjectName,
|
|
72
94
|
form,
|
|
73
|
-
nameTouched
|
|
95
|
+
nameTouched,
|
|
96
|
+
slugManuallyEdited
|
|
74
97
|
]);
|
|
75
98
|
function selectConnection(connectionId) {
|
|
76
99
|
setSelectedConnectionId(connectionId);
|
|
@@ -84,15 +107,16 @@ export function CreateProjectPage() {
|
|
|
84
107
|
}
|
|
85
108
|
if (!connectionsQuery.isError && connections.length === 0) {
|
|
86
109
|
return /*#__PURE__*/ _jsx(Navigate, {
|
|
87
|
-
to: "/
|
|
110
|
+
to: "/w/$workspaceSlug/integrations",
|
|
88
111
|
params: {
|
|
89
|
-
|
|
112
|
+
workspaceSlug: workspace.slug
|
|
90
113
|
},
|
|
91
114
|
replace: true
|
|
92
115
|
});
|
|
93
116
|
}
|
|
94
|
-
async function createProjectFromForm(projectName) {
|
|
117
|
+
async function createProjectFromForm(projectName, projectSlug) {
|
|
95
118
|
setFormError(undefined);
|
|
119
|
+
setSlugConflict(false);
|
|
96
120
|
if (!workspace) {
|
|
97
121
|
setFormError('Workspace is still loading. Try again in a moment.');
|
|
98
122
|
errorRef.current?.focus();
|
|
@@ -112,6 +136,7 @@ export function CreateProjectPage() {
|
|
|
112
136
|
const command = {
|
|
113
137
|
workspaceId: workspace.id,
|
|
114
138
|
name: projectName,
|
|
139
|
+
slug: projectSlug,
|
|
115
140
|
source: {
|
|
116
141
|
connectionId: selectedConnection.id,
|
|
117
142
|
externalRepositoryId: selectedRepository.externalRepositoryId
|
|
@@ -120,23 +145,35 @@ export function CreateProjectPage() {
|
|
|
120
145
|
const project = await createProject.mutateAsync(command);
|
|
121
146
|
toast.success('Project created.');
|
|
122
147
|
await navigate({
|
|
123
|
-
to: '/
|
|
148
|
+
to: '/w/$workspaceSlug/p/$projectSlug',
|
|
124
149
|
params: {
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
workspaceSlug: workspace.slug,
|
|
151
|
+
projectSlug: project.slug
|
|
127
152
|
}
|
|
128
153
|
});
|
|
129
154
|
} catch (error) {
|
|
130
155
|
const copy = projectErrorCopy(error);
|
|
131
156
|
if (copy.existingProjectId) {
|
|
132
157
|
toast.info('Project already exists.');
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
try {
|
|
159
|
+
const project = await getProject(copy.existingProjectId);
|
|
160
|
+
await navigate({
|
|
161
|
+
to: '/w/$workspaceSlug/p/$projectSlug',
|
|
162
|
+
params: {
|
|
163
|
+
workspaceSlug: workspace.slug,
|
|
164
|
+
projectSlug: project.slug
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
} catch (recoveryError) {
|
|
168
|
+
const recoveryCopy = projectErrorCopy(recoveryError);
|
|
169
|
+
setFormError(`${recoveryCopy.title}: ${recoveryCopy.message}`);
|
|
170
|
+
requestAnimationFrame(()=>errorRef.current?.focus());
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (copy.slugConflict) {
|
|
175
|
+
setSlugConflict(true);
|
|
176
|
+
requestAnimationFrame(()=>document.getElementById('project-slug')?.focus());
|
|
140
177
|
return;
|
|
141
178
|
}
|
|
142
179
|
setFormError(`${copy.title}: ${copy.message}`);
|
|
@@ -227,9 +264,9 @@ export function CreateProjectPage() {
|
|
|
227
264
|
size: "sm",
|
|
228
265
|
className: "shrink-0",
|
|
229
266
|
children: /*#__PURE__*/ _jsx(Link, {
|
|
230
|
-
to: "/
|
|
267
|
+
to: "/w/$workspaceSlug/integrations",
|
|
231
268
|
params: {
|
|
232
|
-
|
|
269
|
+
workspaceSlug: workspace.slug
|
|
233
270
|
},
|
|
234
271
|
children: "Add another integration"
|
|
235
272
|
})
|
|
@@ -324,14 +361,53 @@ export function CreateProjectPage() {
|
|
|
324
361
|
type: "text",
|
|
325
362
|
value: field.state.value,
|
|
326
363
|
onChange: (event)=>{
|
|
364
|
+
const nextName = event.target.value;
|
|
327
365
|
setNameTouched(true);
|
|
328
|
-
field.handleChange(
|
|
366
|
+
field.handleChange(nextName);
|
|
367
|
+
if (!slugManuallyEdited) {
|
|
368
|
+
setSlugCheckEnabled(true);
|
|
369
|
+
setSlugConflict(false);
|
|
370
|
+
form.setFieldValue('slug', slugifyName(nextName, {
|
|
371
|
+
fallback: 'project'
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
329
374
|
},
|
|
330
375
|
onBlur: field.handleBlur,
|
|
331
376
|
placeholder: "Platform"
|
|
332
377
|
})
|
|
333
378
|
})
|
|
334
379
|
}),
|
|
380
|
+
/*#__PURE__*/ _jsx(form.Field, {
|
|
381
|
+
name: "slug",
|
|
382
|
+
validators: {
|
|
383
|
+
onBlur: createProjectBodySchema.shape.slug,
|
|
384
|
+
onSubmit: createProjectBodySchema.shape.slug
|
|
385
|
+
},
|
|
386
|
+
children: (field)=>/*#__PURE__*/ _jsx(SlugField, {
|
|
387
|
+
id: "project-slug",
|
|
388
|
+
label: "Project slug",
|
|
389
|
+
name: "slug",
|
|
390
|
+
value: field.state.value,
|
|
391
|
+
onChange: (value)=>{
|
|
392
|
+
setSlugManuallyEdited(true);
|
|
393
|
+
setSlugCheckEnabled(true);
|
|
394
|
+
setSlugConflict(false);
|
|
395
|
+
field.handleChange(value);
|
|
396
|
+
},
|
|
397
|
+
onBlur: field.handleBlur,
|
|
398
|
+
error: slugConflict ? 'This project slug is already in use.' : fieldError(field),
|
|
399
|
+
description: /*#__PURE__*/ _jsx("span", {
|
|
400
|
+
className: "font-code",
|
|
401
|
+
children: `/w/${workspace.slug}/p/${field.state.value}`
|
|
402
|
+
}),
|
|
403
|
+
placeholder: "platform",
|
|
404
|
+
className: "font-code",
|
|
405
|
+
checkEnabled: slugCheckEnabled,
|
|
406
|
+
debounceMs: 0,
|
|
407
|
+
isValid: isSlugValid,
|
|
408
|
+
checkAvailability: checkProjectSlugAvailability
|
|
409
|
+
})
|
|
410
|
+
}),
|
|
335
411
|
/*#__PURE__*/ _jsx(Button, {
|
|
336
412
|
type: "submit",
|
|
337
413
|
iconRight: "chevronRight",
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"version":3,"sources":["../../src/pages/create-project-page.tsx"],"sourcesContent":["import {slugifyName, slugSchema} from '@shipfox/api-common-dto';\nimport {createProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {useMaybeActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionPicker,\n type IntegrationConnection,\n IntegrationIcon,\n RepositoryPicker,\n useRepositoriesInfiniteQuery,\n useSourceConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {displayNameFieldError, SlugField} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {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 {\n getProject,\n useCreateProjectMutation,\n useProjectSlugAvailability,\n} from '#hooks/api/projects.js';\nimport {projectErrorCopy} from '#project-error.js';\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function CreateProjectPage() {\n const workspace = useMaybeActiveWorkspace();\n const navigate = useNavigate();\n const createProject = useCreateProjectMutation();\n const errorRef = useRef<HTMLDivElement>(null);\n\n const connectionsQuery = useSourceConnectionsQuery(workspace?.id);\n const connections = connectionsQuery.data ?? [];\n\n const [selectedConnectionId, setSelectedConnectionId] = useState<string | undefined>();\n const singleConnectionId = connections.length === 1 ? connections[0]?.id : undefined;\n const effectiveSelectedConnectionId = selectedConnectionId ?? singleConnectionId;\n useEffect(() => {\n if (singleConnectionId && selectedConnectionId !== singleConnectionId) {\n setSelectedConnectionId(singleConnectionId);\n }\n }, [singleConnectionId, selectedConnectionId]);\n\n const selectedConnection: IntegrationConnection | undefined = connections.find(\n (connection) => connection.id === effectiveSelectedConnectionId,\n );\n\n const [repoFilter, setRepoFilter] = useState('');\n const debouncedRepoFilter = useDebouncedValue(repoFilter, 250);\n const trimmedFilter = debouncedRepoFilter.trim();\n\n const repositoriesQuery = useRepositoriesInfiniteQuery(\n effectiveSelectedConnectionId,\n trimmedFilter ? {search: trimmedFilter} : undefined,\n );\n const repositories = repositoriesQuery.data?.pages.flatMap((page) => page.repositories) ?? [];\n\n const [selectedRepositoryId, setSelectedRepositoryId] = useState<string | undefined>();\n useEffect(() => {\n if (!selectedRepositoryId && repositories[0]) {\n setSelectedRepositoryId(repositories[0].externalRepositoryId);\n }\n }, [repositories, selectedRepositoryId]);\n const selectedRepository = repositories.find(\n (repository) => repository.externalRepositoryId === selectedRepositoryId,\n );\n\n const [nameTouched, setNameTouched] = useState(false);\n const defaultProjectName = projectNameFromRepository(\n selectedRepository?.name ?? selectedRepositoryId ?? '',\n );\n const defaultProjectSlug = slugifyName(defaultProjectName, {fallback: 'project'});\n\n const [formError, setFormError] = useState<string | undefined>();\n const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);\n const [slugCheckEnabled, setSlugCheckEnabled] = useState(false);\n const [slugConflict, setSlugConflict] = useState(false);\n const workspaceId = workspace?.id;\n const checkProjectSlugAvailability = useProjectSlugAvailability(workspaceId);\n\n const form = useForm({\n defaultValues: {name: defaultProjectName, slug: defaultProjectSlug},\n onSubmit: async ({value}) => {\n const projectSlug = slugManuallyEdited\n ? value.slug\n : slugifyName(value.name, {fallback: 'project'});\n await createProjectFromForm(nameTouched ? value.name : defaultProjectName, projectSlug);\n },\n });\n\n useEffect(() => {\n if (!nameTouched && form.state.values.name !== defaultProjectName) {\n form.setFieldValue('name', defaultProjectName);\n if (!slugManuallyEdited) {\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(defaultProjectName, {fallback: 'project'}));\n }\n }\n }, [defaultProjectName, form, nameTouched, slugManuallyEdited]);\n\n function selectConnection(connectionId: string) {\n setSelectedConnectionId(connectionId);\n setSelectedRepositoryId(undefined);\n }\n\n if (connectionsQuery.isPending) {\n return <FullPageLoader />;\n }\n\n if (!workspace) {\n return <FullPageLoader />;\n }\n\n if (!connectionsQuery.isError && connections.length === 0) {\n return (\n <Navigate\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n replace\n />\n );\n }\n\n async function createProjectFromForm(projectName: string, projectSlug: string) {\n setFormError(undefined);\n setSlugConflict(false);\n if (!workspace) {\n setFormError('Workspace is still loading. Try again in a moment.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedConnection) {\n setFormError('Choose a source integration before creating a project.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedRepository) {\n setFormError('Choose a repository before creating a project.');\n errorRef.current?.focus();\n return;\n }\n\n try {\n const command: CreateProjectCommand = {\n workspaceId: workspace.id,\n name: projectName,\n slug: projectSlug,\n source: {\n connectionId: selectedConnection.id,\n externalRepositoryId: selectedRepository.externalRepositoryId,\n },\n };\n const project = await createProject.mutateAsync(command);\n toast.success('Project created.');\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (error) {\n const copy = projectErrorCopy(error);\n if (copy.existingProjectId) {\n toast.info('Project already exists.');\n try {\n const project = await getProject(copy.existingProjectId);\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (recoveryError) {\n const recoveryCopy = projectErrorCopy(recoveryError);\n setFormError(`${recoveryCopy.title}: ${recoveryCopy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n return;\n }\n if (copy.slugConflict) {\n setSlugConflict(true);\n requestAnimationFrame(() => document.getElementById('project-slug')?.focus());\n return;\n }\n setFormError(`${copy.title}: ${copy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n }\n\n const showRepoPicker = Boolean(selectedConnection);\n const filteredEmptyMessage = trimmedFilter\n ? `No repositories matching \"${repoFilter.trim()}\".`\n : 'No repositories visible to this connection.';\n\n return (\n <div className=\"flex w-full flex-col gap-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\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n >\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 const nextName = event.target.value;\n setNameTouched(true);\n field.handleChange(nextName);\n if (!slugManuallyEdited) {\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(nextName, {fallback: 'project'}));\n }\n }}\n onBlur={field.handleBlur}\n placeholder=\"Platform\"\n />\n </FormField>\n )}\n </form.Field>\n\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createProjectBodySchema.shape.slug,\n onSubmit: createProjectBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"project-slug\"\n label=\"Project slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => {\n setSlugManuallyEdited(true);\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n field.handleChange(value);\n }}\n onBlur={field.handleBlur}\n error={slugConflict ? 'This project slug is already in use.' : fieldError(field)}\n description={\n <span className=\"font-code\">\n {`/w/${workspace.slug}/p/${field.state.value}`}\n </span>\n }\n placeholder=\"platform\"\n className=\"font-code\"\n checkEnabled={slugCheckEnabled}\n debounceMs={0}\n isValid={isSlugValid}\n checkAvailability={checkProjectSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button\n type=\"submit\"\n iconRight=\"chevronRight\"\n isLoading={createProject.isPending}\n disabled={!selectedConnection || !selectedRepository}\n className=\"w-full\"\n >\n Create project\n </Button>\n </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":["slugifyName","slugSchema","createProjectBodySchema","useMaybeActiveWorkspace","ConnectionPicker","IntegrationIcon","RepositoryPicker","useRepositoriesInfiniteQuery","useSourceConnectionsQuery","displayNameFieldError","SlugField","Button","Callout","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","Link","Navigate","useNavigate","useEffect","useRef","useState","ModelProviderReminderBanner","projectNameFromRepository","getProject","useCreateProjectMutation","useProjectSlugAvailability","projectErrorCopy","isSlugValid","value","safeParse","success","CreateProjectPage","workspace","navigate","createProject","errorRef","connectionsQuery","id","connections","data","selectedConnectionId","setSelectedConnectionId","singleConnectionId","length","undefined","effectiveSelectedConnectionId","selectedConnection","find","connection","repoFilter","setRepoFilter","debouncedRepoFilter","useDebouncedValue","trimmedFilter","trim","repositoriesQuery","search","repositories","pages","flatMap","page","selectedRepositoryId","setSelectedRepositoryId","externalRepositoryId","selectedRepository","repository","nameTouched","setNameTouched","defaultProjectName","name","defaultProjectSlug","fallback","formError","setFormError","slugManuallyEdited","setSlugManuallyEdited","slugCheckEnabled","setSlugCheckEnabled","slugConflict","setSlugConflict","workspaceId","checkProjectSlugAvailability","form","defaultValues","slug","onSubmit","projectSlug","createProjectFromForm","state","values","setFieldValue","selectConnection","connectionId","isPending","isError","to","params","workspaceSlug","replace","projectName","current","focus","command","source","project","mutateAsync","error","copy","existingProjectId","info","recoveryError","recoveryCopy","title","message","requestAnimationFrame","document","getElementById","showRepoPicker","Boolean","filteredEmptyMessage","div","className","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","nextName","target","handleChange","handleBlur","placeholder","description","span","checkEnabled","debounceMs","isValid","checkAvailability","iconRight","disabled","provider","aria-hidden","bold","delayMs","debounced","setDebounced","handle","setTimeout","clearTimeout"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { createStore, Provider as JotaiProvider } from 'jotai';
|
|
|
8
8
|
import { useMemo } from 'react';
|
|
9
9
|
import { CreateProjectPage } from './create-project-page.js';
|
|
10
10
|
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
11
|
-
const WORKSPACE_PATH =
|
|
11
|
+
const WORKSPACE_PATH = '/w/acme/projects/new';
|
|
12
12
|
const GITHUB_CONNECTION_ID = '33333333-3333-4333-8333-333333333333';
|
|
13
13
|
const GITEA_CONNECTION_ID = '44444444-4444-4444-8444-444444444444';
|
|
14
14
|
const authState = {
|
|
@@ -24,6 +24,7 @@ const authState = {
|
|
|
24
24
|
{
|
|
25
25
|
id: WORKSPACE_ID,
|
|
26
26
|
name: 'Acme',
|
|
27
|
+
slug: 'acme',
|
|
27
28
|
membershipId: 'membership-1'
|
|
28
29
|
}
|
|
29
30
|
]
|
|
@@ -103,7 +104,7 @@ function createStoryRouter() {
|
|
|
103
104
|
});
|
|
104
105
|
const workspaceRoute = createRoute({
|
|
105
106
|
getParentRoute: ()=>rootRoute,
|
|
106
|
-
path: '/
|
|
107
|
+
path: '/w/$workspaceSlug',
|
|
107
108
|
component: Outlet
|
|
108
109
|
});
|
|
109
110
|
const createProjectRoute = createRoute({
|
|
@@ -123,7 +124,7 @@ function createStoryRouter() {
|
|
|
123
124
|
});
|
|
124
125
|
const projectRoute = createRoute({
|
|
125
126
|
getParentRoute: ()=>workspaceRoute,
|
|
126
|
-
path: '
|
|
127
|
+
path: 'p/$projectSlug',
|
|
127
128
|
component: ()=>/*#__PURE__*/ _jsx("div", {})
|
|
128
129
|
});
|
|
129
130
|
return createRouter({
|
|
@@ -173,6 +174,7 @@ function fetchForScenario(scenario) {
|
|
|
173
174
|
id: '99999999-9999-4999-8999-999999999999',
|
|
174
175
|
workspace_id: WORKSPACE_ID,
|
|
175
176
|
name: 'Platform',
|
|
177
|
+
slug: 'platform',
|
|
176
178
|
source: {
|
|
177
179
|
connection_id: GITHUB_CONNECTION_ID,
|
|
178
180
|
external_repository_id: 'platform'
|
|
@@ -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 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 repository({\n external_repository_id: 'observability-and-deployment-automation',\n name: 'observability-and-deployment-automation',\n full_name: 'acme-platform/observability-and-deployment-automation',\n default_branch: 'main',\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
|
+
{"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 = '/w/acme/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', slug: '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: '/w/$workspaceSlug',\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: 'p/$projectSlug',\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 slug: '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 repository({\n external_repository_id: 'observability-and-deployment-automation',\n name: 'observability-and-deployment-automation',\n full_name: 'acme-platform/observability-and-deployment-automation',\n default_branch: 'main',\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","slug","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","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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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ProjectSettingsField = 'name' | 'slug';
|
|
2
|
+
export type ProjectSettingsFormError = {
|
|
3
|
+
kind: 'field';
|
|
4
|
+
field: ProjectSettingsField;
|
|
5
|
+
message: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'form';
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function projectSettingsErrorToFormError(error: unknown): ProjectSettingsFormError;
|
|
11
|
+
//# sourceMappingURL=project-settings-form-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-settings-form-errors.d.ts","sourceRoot":"","sources":["../../src/pages/project-settings-form-errors.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,MAAM,MAAM,wBAAwB,GAChC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAC7D;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAC;AAEpC,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAQxF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ApiError } from '@shipfox/client-api';
|
|
2
|
+
export function projectSettingsErrorToFormError(error) {
|
|
3
|
+
if (error instanceof ApiError && error.code === 'slug-conflict') {
|
|
4
|
+
return {
|
|
5
|
+
kind: 'field',
|
|
6
|
+
field: 'slug',
|
|
7
|
+
message: 'That project slug is already taken.'
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
kind: 'form',
|
|
12
|
+
message: error instanceof Error ? error.message : 'Could not update project settings.'
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=project-settings-form-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/pages/project-settings-form-errors.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\n\nexport type ProjectSettingsField = 'name' | 'slug';\n\nexport type ProjectSettingsFormError =\n | {kind: 'field'; field: ProjectSettingsField; message: string}\n | {kind: 'form'; message: string};\n\nexport function projectSettingsErrorToFormError(error: unknown): ProjectSettingsFormError {\n if (error instanceof ApiError && error.code === 'slug-conflict') {\n return {kind: 'field', field: 'slug', message: 'That project slug is already taken.'};\n }\n return {\n kind: 'form',\n message: error instanceof Error ? error.message : 'Could not update project settings.',\n };\n}\n"],"names":["ApiError","projectSettingsErrorToFormError","error","code","kind","field","message","Error"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAsB;AAQ7C,OAAO,SAASC,gCAAgCC,KAAc;IAC5D,IAAIA,iBAAiBF,YAAYE,MAAMC,IAAI,KAAK,iBAAiB;QAC/D,OAAO;YAACC,MAAM;YAASC,OAAO;YAAQC,SAAS;QAAqC;IACtF;IACA,OAAO;QACLF,MAAM;QACNE,SAASJ,iBAAiBK,QAAQL,MAAMI,OAAO,GAAG;IACpD;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-settings-page.d.ts","sourceRoot":"","sources":["../../src/pages/project-settings-page.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,mBAAmB,gCA8BlC"}
|