@shipfox/client-projects 12.0.2 → 14.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.
Files changed (111) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +64 -0
  3. package/dist/chrome.d.ts +5 -1
  4. package/dist/chrome.d.ts.map +1 -1
  5. package/dist/chrome.js +18 -26
  6. package/dist/chrome.js.map +1 -1
  7. package/dist/components/model-provider-reminder-banner.d.ts.map +1 -1
  8. package/dist/components/model-provider-reminder-banner.js +4 -2
  9. package/dist/components/model-provider-reminder-banner.js.map +1 -1
  10. package/dist/components/project-crumb.d.ts +3 -1
  11. package/dist/components/project-crumb.d.ts.map +1 -1
  12. package/dist/components/project-crumb.js +7 -5
  13. package/dist/components/project-crumb.js.map +1 -1
  14. package/dist/components/project-switcher.d.ts +2 -1
  15. package/dist/components/project-switcher.d.ts.map +1 -1
  16. package/dist/components/project-switcher.js +10 -8
  17. package/dist/components/project-switcher.js.map +1 -1
  18. package/dist/core/definition.d.ts +6 -0
  19. package/dist/core/definition.d.ts.map +1 -1
  20. package/dist/core/definition.js.map +1 -1
  21. package/dist/core/project.d.ts +7 -0
  22. package/dist/core/project.d.ts.map +1 -1
  23. package/dist/core/project.js.map +1 -1
  24. package/dist/feature.d.ts +29 -5
  25. package/dist/feature.d.ts.map +1 -1
  26. package/dist/feature.js +26 -5
  27. package/dist/feature.js.map +1 -1
  28. package/dist/hooks/api/mappers.d.ts.map +1 -1
  29. package/dist/hooks/api/mappers.js +9 -1
  30. package/dist/hooks/api/mappers.js.map +1 -1
  31. package/dist/hooks/api/projects.d.ts +21 -2
  32. package/dist/hooks/api/projects.d.ts.map +1 -1
  33. package/dist/hooks/api/projects.js +175 -2
  34. package/dist/hooks/api/projects.js.map +1 -1
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/pages/create-project-page.d.ts.map +1 -1
  40. package/dist/pages/create-project-page.js +97 -21
  41. package/dist/pages/create-project-page.js.map +1 -1
  42. package/dist/pages/create-project-page.stories.js +5 -3
  43. package/dist/pages/create-project-page.stories.js.map +1 -1
  44. package/dist/pages/project-settings-form-errors.d.ts +11 -0
  45. package/dist/pages/project-settings-form-errors.d.ts.map +1 -0
  46. package/dist/pages/project-settings-form-errors.js +16 -0
  47. package/dist/pages/project-settings-form-errors.js.map +1 -0
  48. package/dist/pages/project-settings-page.d.ts +2 -0
  49. package/dist/pages/project-settings-page.d.ts.map +1 -0
  50. package/dist/pages/project-settings-page.js +225 -0
  51. package/dist/pages/project-settings-page.js.map +1 -0
  52. package/dist/pages/project-settings-page.stories.d.ts +23 -0
  53. package/dist/pages/project-settings-page.stories.d.ts.map +1 -0
  54. package/dist/pages/project-settings-page.stories.js +206 -0
  55. package/dist/pages/project-settings-page.stories.js.map +1 -0
  56. package/dist/pages/projects-hub-page.js +11 -11
  57. package/dist/pages/projects-hub-page.js.map +1 -1
  58. package/dist/project-error.d.ts +1 -0
  59. package/dist/project-error.d.ts.map +1 -1
  60. package/dist/project-error.js +7 -0
  61. package/dist/project-error.js.map +1 -1
  62. package/dist/routes/inputs.d.ts +2 -2
  63. package/dist/routes/inputs.d.ts.map +1 -1
  64. package/dist/routes/inputs.js +5 -5
  65. package/dist/routes/inputs.js.map +1 -1
  66. package/dist/routes/project-index.d.ts +2 -2
  67. package/dist/routes/project-index.d.ts.map +1 -1
  68. package/dist/routes/project-index.js +3 -3
  69. package/dist/routes/project-index.js.map +1 -1
  70. package/dist/routes/project-settings-index.d.ts +10 -0
  71. package/dist/routes/project-settings-index.d.ts.map +1 -0
  72. package/dist/routes/project-settings-index.js +12 -0
  73. package/dist/routes/project-settings-index.js.map +1 -0
  74. package/dist/routes/project-settings.d.ts +6 -0
  75. package/dist/routes/project-settings.d.ts.map +1 -0
  76. package/dist/routes/project-settings.js +7 -0
  77. package/dist/routes/project-settings.js.map +1 -0
  78. package/dist/tsconfig.test.tsbuildinfo +1 -1
  79. package/package.json +11 -10
  80. package/src/chrome.tsx +27 -19
  81. package/src/components/model-provider-reminder-banner.tsx +3 -1
  82. package/src/components/project-crumb.tsx +18 -5
  83. package/src/components/project-switcher.test.tsx +7 -4
  84. package/src/components/project-switcher.tsx +16 -5
  85. package/src/core/definition.ts +7 -0
  86. package/src/core/project.ts +8 -0
  87. package/src/feature.ts +26 -4
  88. package/src/hooks/api/mappers.test.ts +19 -0
  89. package/src/hooks/api/mappers.ts +6 -0
  90. package/src/hooks/api/projects.test.ts +247 -1
  91. package/src/hooks/api/projects.ts +227 -2
  92. package/src/index.ts +9 -1
  93. package/src/pages/create-project-page.stories.tsx +5 -4
  94. package/src/pages/create-project-page.test.tsx +191 -12
  95. package/src/pages/create-project-page.tsx +102 -15
  96. package/src/pages/home-router.test.tsx +2 -2
  97. package/src/pages/project-settings-form-errors.ts +17 -0
  98. package/src/pages/project-settings-page.stories.tsx +216 -0
  99. package/src/pages/project-settings-page.test.tsx +74 -0
  100. package/src/pages/project-settings-page.tsx +220 -0
  101. package/src/pages/projects-hub-page.test.tsx +17 -11
  102. package/src/pages/projects-hub-page.tsx +9 -9
  103. package/src/project-error.test.ts +1 -0
  104. package/src/project-error.ts +8 -0
  105. package/src/routes/inputs.test.ts +8 -6
  106. package/src/routes/inputs.ts +7 -6
  107. package/src/routes/project-index.tsx +3 -3
  108. package/src/routes/project-settings-index.tsx +11 -0
  109. package/src/routes/project-settings.tsx +4 -0
  110. package/test/pages.tsx +26 -22
  111. 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
- await createProjectFromForm(nameTouched ? value.name : defaultProjectName);
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: "/workspaces/$wid/integrations",
110
+ to: "/w/$workspaceSlug/integrations",
88
111
  params: {
89
- wid: workspace.id
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: '/workspaces/$wid/projects/$pid',
148
+ to: '/w/$workspaceSlug/p/$projectSlug',
124
149
  params: {
125
- wid: workspace.id,
126
- pid: project.id
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
- await navigate({
134
- to: '/workspaces/$wid/projects/$pid',
135
- params: {
136
- wid: workspace.id,
137
- pid: copy.existingProjectId
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: "/workspaces/$wid/integrations",
267
+ to: "/w/$workspaceSlug/integrations",
231
268
  params: {
232
- wid: workspace.id
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(event.target.value);
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":";AAAA,SAAQA,WAAW,EAAEC,UAAU,QAAO,0BAA0B;AAChE,SAAQC,uBAAuB,QAAO,4BAA4B;AAClE,SAAQC,uBAAuB,QAAO,uBAAuB;AAC7D,SACEC,gBAAgB,EAEhBC,eAAe,EACfC,gBAAgB,EAChBC,4BAA4B,EAC5BC,yBAAyB,QACpB,+BAA+B;AACtC,SAAQC,qBAAqB,EAAEC,SAAS,QAAO,qBAAqB;AACpE,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,SAAS,EAAEC,cAAc,EAAEC,UAAU,QAAO,+BAA+B;AACnF,SAAQC,cAAc,QAAO,2BAA2B;AACxD,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAC1D,SAAQC,OAAO,QAAO,uBAAuB;AAC7C,SAAQC,IAAI,EAAEC,QAAQ,EAAEC,WAAW,QAAO,yBAAyB;AACnE,SAAQC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAClD,SAAQC,2BAA2B,QAAO,gDAAgD;AAC1F,SAAmCC,yBAAyB,QAAO,mBAAmB;AACtF,SACEC,UAAU,EACVC,wBAAwB,EACxBC,0BAA0B,QACrB,yBAAyB;AAChC,SAAQC,gBAAgB,QAAO,oBAAoB;AAEnD,SAASC,YAAYC,KAAa;IAChC,OAAOjC,WAAWkC,SAAS,CAACD,OAAOE,OAAO;AAC5C;AAEA,OAAO,SAASC;IACd,MAAMC,YAAYnC;IAClB,MAAMoC,WAAWhB;IACjB,MAAMiB,gBAAgBV;IACtB,MAAMW,WAAWhB,OAAuB;IAExC,MAAMiB,mBAAmBlC,0BAA0B8B,WAAWK;IAC9D,MAAMC,cAAcF,iBAAiBG,IAAI,IAAI,EAAE;IAE/C,MAAM,CAACC,sBAAsBC,wBAAwB,GAAGrB;IACxD,MAAMsB,qBAAqBJ,YAAYK,MAAM,KAAK,IAAIL,WAAW,CAAC,EAAE,EAAED,KAAKO;IAC3E,MAAMC,gCAAgCL,wBAAwBE;IAC9DxB,UAAU;QACR,IAAIwB,sBAAsBF,yBAAyBE,oBAAoB;YACrED,wBAAwBC;QAC1B;IACF,GAAG;QAACA;QAAoBF;KAAqB;IAE7C,MAAMM,qBAAwDR,YAAYS,IAAI,CAC5E,CAACC,aAAeA,WAAWX,EAAE,KAAKQ;IAGpC,MAAM,CAACI,YAAYC,cAAc,GAAG9B,SAAS;IAC7C,MAAM+B,sBAAsBC,kBAAkBH,YAAY;IAC1D,MAAMI,gBAAgBF,oBAAoBG,IAAI;IAE9C,MAAMC,oBAAoBtD,6BACxB4C,+BACAQ,gBAAgB;QAACG,QAAQH;IAAa,IAAIT;IAE5C,MAAMa,eAAeF,kBAAkBhB,IAAI,EAAEmB,MAAMC,QAAQ,CAACC,OAASA,KAAKH,YAAY,KAAK,EAAE;IAE7F,MAAM,CAACI,sBAAsBC,wBAAwB,GAAG1C;IACxDF,UAAU;QACR,IAAI,CAAC2C,wBAAwBJ,YAAY,CAAC,EAAE,EAAE;YAC5CK,wBAAwBL,YAAY,CAAC,EAAE,CAACM,oBAAoB;QAC9D;IACF,GAAG;QAACN;QAAcI;KAAqB;IACvC,MAAMG,qBAAqBP,aAAaV,IAAI,CAC1C,CAACkB,aAAeA,WAAWF,oBAAoB,KAAKF;IAGtD,MAAM,CAACK,aAAaC,eAAe,GAAG/C,SAAS;IAC/C,MAAMgD,qBAAqB9C,0BACzB0C,oBAAoBK,QAAQR,wBAAwB;IAEtD,MAAMS,qBAAqB5E,YAAY0E,oBAAoB;QAACG,UAAU;IAAS;IAE/E,MAAM,CAACC,WAAWC,aAAa,GAAGrD;IAClC,MAAM,CAACsD,oBAAoBC,sBAAsB,GAAGvD,SAAS;IAC7D,MAAM,CAACwD,kBAAkBC,oBAAoB,GAAGzD,SAAS;IACzD,MAAM,CAAC0D,cAAcC,gBAAgB,GAAG3D,SAAS;IACjD,MAAM4D,cAAchD,WAAWK;IAC/B,MAAM4C,+BAA+BxD,2BAA2BuD;IAEhE,MAAME,OAAOpE,QAAQ;QACnBqE,eAAe;YAACd,MAAMD;YAAoBgB,MAAMd;QAAkB;QAClEe,UAAU,OAAO,EAACzD,KAAK,EAAC;YACtB,MAAM0D,cAAcZ,qBAChB9C,MAAMwD,IAAI,GACV1F,YAAYkC,MAAMyC,IAAI,EAAE;gBAACE,UAAU;YAAS;YAChD,MAAMgB,sBAAsBrB,cAActC,MAAMyC,IAAI,GAAGD,oBAAoBkB;QAC7E;IACF;IAEApE,UAAU;QACR,IAAI,CAACgD,eAAegB,KAAKM,KAAK,CAACC,MAAM,CAACpB,IAAI,KAAKD,oBAAoB;YACjEc,KAAKQ,aAAa,CAAC,QAAQtB;YAC3B,IAAI,CAACM,oBAAoB;gBACvBK,gBAAgB;gBAChBG,KAAKQ,aAAa,CAAC,QAAQhG,YAAY0E,oBAAoB;oBAACG,UAAU;gBAAS;YACjF;QACF;IACF,GAAG;QAACH;QAAoBc;QAAMhB;QAAaQ;KAAmB;IAE9D,SAASiB,iBAAiBC,YAAoB;QAC5CnD,wBAAwBmD;QACxB9B,wBAAwBlB;IAC1B;IAEA,IAAIR,iBAAiByD,SAAS,EAAE;QAC9B,qBAAO,KAACnF;IACV;IAEA,IAAI,CAACsB,WAAW;QACd,qBAAO,KAACtB;IACV;IAEA,IAAI,CAAC0B,iBAAiB0D,OAAO,IAAIxD,YAAYK,MAAM,KAAK,GAAG;QACzD,qBACE,KAAC3B;YACC+E,IAAG;YACHC,QAAQ;gBAACC,eAAejE,UAAUoD,IAAI;YAAA;YACtCc,OAAO;;IAGb;IAEA,eAAeX,sBAAsBY,WAAmB,EAAEb,WAAmB;QAC3Eb,aAAa7B;QACbmC,gBAAgB;QAChB,IAAI,CAAC/C,WAAW;YACdyC,aAAa;YACbtC,SAASiE,OAAO,EAAEC;YAClB;QACF;QACA,IAAI,CAACvD,oBAAoB;YACvB2B,aAAa;YACbtC,SAASiE,OAAO,EAAEC;YAClB;QACF;QACA,IAAI,CAACrC,oBAAoB;YACvBS,aAAa;YACbtC,SAASiE,OAAO,EAAEC;YAClB;QACF;QAEA,IAAI;YACF,MAAMC,UAAgC;gBACpCtB,aAAahD,UAAUK,EAAE;gBACzBgC,MAAM8B;gBACNf,MAAME;gBACNiB,QAAQ;oBACNX,cAAc9C,mBAAmBT,EAAE;oBACnC0B,sBAAsBC,mBAAmBD,oBAAoB;gBAC/D;YACF;YACA,MAAMyC,UAAU,MAAMtE,cAAcuE,WAAW,CAACH;YAChD3F,MAAMmB,OAAO,CAAC;YACd,MAAMG,SAAS;gBACb8D,IAAI;gBACJC,QAAQ;oBAACC,eAAejE,UAAUoD,IAAI;oBAAEE,aAAakB,QAAQpB,IAAI;gBAAA;YACnE;QACF,EAAE,OAAOsB,OAAO;YACd,MAAMC,OAAOjF,iBAAiBgF;YAC9B,IAAIC,KAAKC,iBAAiB,EAAE;gBAC1BjG,MAAMkG,IAAI,CAAC;gBACX,IAAI;oBACF,MAAML,UAAU,MAAMjF,WAAWoF,KAAKC,iBAAiB;oBACvD,MAAM3E,SAAS;wBACb8D,IAAI;wBACJC,QAAQ;4BAACC,eAAejE,UAAUoD,IAAI;4BAAEE,aAAakB,QAAQpB,IAAI;wBAAA;oBACnE;gBACF,EAAE,OAAO0B,eAAe;oBACtB,MAAMC,eAAerF,iBAAiBoF;oBACtCrC,aAAa,GAAGsC,aAAaC,KAAK,CAAC,EAAE,EAAED,aAAaE,OAAO,EAAE;oBAC7DC,sBAAsB,IAAM/E,SAASiE,OAAO,EAAEC;gBAChD;gBACA;YACF;YACA,IAAIM,KAAK7B,YAAY,EAAE;gBACrBC,gBAAgB;gBAChBmC,sBAAsB,IAAMC,SAASC,cAAc,CAAC,iBAAiBf;gBACrE;YACF;YACA5B,aAAa,GAAGkC,KAAKK,KAAK,CAAC,EAAE,EAAEL,KAAKM,OAAO,EAAE;YAC7CC,sBAAsB,IAAM/E,SAASiE,OAAO,EAAEC;QAChD;IACF;IAEA,MAAMgB,iBAAiBC,QAAQxE;IAC/B,MAAMyE,uBAAuBlE,gBACzB,CAAC,0BAA0B,EAAEJ,WAAWK,IAAI,GAAG,EAAE,CAAC,GAClD;IAEJ,qBACE,MAACkE;QAAIC,WAAU;;0BACb,MAACC;gBAAOD,WAAU;;kCAChB,KAAC7G;wBAAOyB,IAAG;wBAAuBsF,SAAQ;kCAAK;;kCAG/C,KAAC9G;wBAAK+G,MAAK;wBAAKH,WAAU;kCAAgC;;;;0BAM5D,KAACpG;gBAA4B2D,aAAahD,UAAUK,EAAE;;YAErDD,iBAAiB0D,OAAO,iBACvB,KAACxF;gBAAQuH,MAAK;gBAAQC,MAAK;0BACzB,cAAA,MAACN;oBAAIC,WAAU;;sCACb,KAAC5G;4BAAK+G,MAAK;4BAAKH,WAAU;sCAAuB;;sCAGjD,KAACpH;4BACCuH,MAAK;4BACLD,SAAQ;4BACRI,SAAS,IAAM3F,iBAAiB4F,OAAO;4BACvCP,WAAU;sCACX;;;;iBAKH;0BAEJ,MAACvC;gBACCG,UAAU,CAAC4C;oBACTA,MAAMC,cAAc;oBACpBD,MAAME,eAAe;oBACrB,KAAKjD,KAAKkD,YAAY;gBACxB;gBACAC,UAAU;gBACVC,mBAAgB;gBAChBb,WAAU;;kCAEV,MAACD;wBAAIC,WAAU;;0CACb,MAACc;gCAAQd,WAAU;gCAAuBe,cAAW;;kDACnD,MAAChB;wCAAIC,WAAU;;0DACb,MAACD;gDAAIC,WAAU;;kEACb,KAAC7G;wDAAO+G,SAAQ;kEAAK;;kEACrB,KAAC9G;wDAAK+G,MAAK;wDAAKH,WAAU;kEAAgC;;;;4CAK3DnF,YAAYK,MAAM,KAAK,kBACtB,KAACtC;gDAAOoI,OAAO;gDAACd,SAAQ;gDAAcC,MAAK;gDAAKH,WAAU;0DACxD,cAAA,KAAC1G;oDACCgF,IAAG;oDACHC,QAAQ;wDAACC,eAAejE,UAAUoD,IAAI;oDAAA;8DACvC;;iDAID;;;oCAGL9C,YAAYK,MAAM,GAAG,kBACpB,KAAC7C;wCACCwC,aAAaA;wCACbE,sBAAsBK;wCACtB6F,UAAU/C;yCAEV;;;4BAGL0B,+BACC,MAACkB;gCAAQd,WAAU;gCAAuBe,cAAW;;kDACnD,MAAChB;wCAAIC,WAAU;;0DACb,KAAC7G;gDAAO+G,SAAQ;0DAAK;;0DACrB,KAAC9G;gDAAK+G,MAAK;gDAAKH,WAAU;0DAAgC;;;;kDAK5D,KAACzH;wCACCyD,cAAcA;wCACdI,sBAAsBA;wCACtB6E,UAAU5E;wCACV6E,WAAWpF,kBAAkBsC,SAAS;wCACtC+C,oBAAoBrF,kBAAkBqF,kBAAkB;wCACxDC,aAAatF,kBAAkBsF,WAAW;wCAC1CC,YAAY,IAAMvF,kBAAkBwF,aAAa;wCACjDC,cAAczB;wCACd0B,aAAahG;wCACbiG,gBAAgBhG;;;iCAGlB;;;kCAGN,KAACiG;wBAAM1B,WAAU;kCACf,cAAA,MAACD;4BAAIC,WAAU;;8CACb,MAACD;oCAAIC,WAAU;;sDACb,KAAC7G;4CAAO+G,SAAQ;sDAAK;;sDACrB,KAAC9G;4CAAK+G,MAAK;4CAAKH,WAAU;sDAAgC;;;;gCAK3DjD,0BACC,KAAClE;oCAAQuH,MAAK;oCAAQC,MAAK;8CACzB,cAAA,KAACN;wCAAI4B,KAAKjH;wCAAUkH,UAAU,CAAC;kDAC5B7E;;qCAGH;8CAEJ,KAAC8E;oCACCtG,YAAYF;oCACZyG,gBAAgBvF,oBAAoBwF;;8CAGtC,KAACtE,KAAKuE,KAAK;oCACTpF,MAAK;oCACLqF,YAAY;wCACVC,QAAQ,CAAC,EAAC/H,KAAK,EAAC,GACdzB,sBAAsByB,OAAO,gBAAgBhC,wBAAwBgK,KAAK,CAACvF,IAAI;wCACjFgB,UAAU,CAAC,EAACzD,KAAK,EAAC,GAChBzB,sBAAsByB,OAAO,gBAAgBhC,wBAAwBgK,KAAK,CAACvF,IAAI;oCACnF;8CAEC,CAACwF,sBACA,KAACtJ;4CAAUuJ,OAAM;4CAAezH,IAAG;4CAAeqE,OAAOjG,WAAWoJ;sDAClE,cAAA,KAACrJ;gDACC6D,MAAK;gDACLyD,MAAK;gDACLlG,OAAOiI,MAAMrE,KAAK,CAAC5D,KAAK;gDACxBmI,UAAU,CAAC9B;oDACT,MAAM+B,WAAW/B,MAAMgC,MAAM,CAACrI,KAAK;oDACnCuC,eAAe;oDACf0F,MAAMK,YAAY,CAACF;oDACnB,IAAI,CAACtF,oBAAoB;wDACvBG,oBAAoB;wDACpBE,gBAAgB;wDAChBG,KAAKQ,aAAa,CAAC,QAAQhG,YAAYsK,UAAU;4DAACzF,UAAU;wDAAS;oDACvE;gDACF;gDACAoF,QAAQE,MAAMM,UAAU;gDACxBC,aAAY;;;;8CAMpB,KAAClF,KAAKuE,KAAK;oCACTpF,MAAK;oCACLqF,YAAY;wCACVC,QAAQ/J,wBAAwBgK,KAAK,CAACxE,IAAI;wCAC1CC,UAAUzF,wBAAwBgK,KAAK,CAACxE,IAAI;oCAC9C;8CAEC,CAACyE,sBACA,KAACzJ;4CACCiC,IAAG;4CACHyH,OAAM;4CACNzF,MAAK;4CACLzC,OAAOiI,MAAMrE,KAAK,CAAC5D,KAAK;4CACxBmI,UAAU,CAACnI;gDACT+C,sBAAsB;gDACtBE,oBAAoB;gDACpBE,gBAAgB;gDAChB8E,MAAMK,YAAY,CAACtI;4CACrB;4CACA+H,QAAQE,MAAMM,UAAU;4CACxBzD,OAAO5B,eAAe,yCAAyCrE,WAAWoJ;4CAC1EQ,2BACE,KAACC;gDAAK7C,WAAU;0DACb,CAAC,GAAG,EAAEzF,UAAUoD,IAAI,CAAC,GAAG,EAAEyE,MAAMrE,KAAK,CAAC5D,KAAK,EAAE;;4CAGlDwI,aAAY;4CACZ3C,WAAU;4CACV8C,cAAc3F;4CACd4F,YAAY;4CACZC,SAAS9I;4CACT+I,mBAAmBzF;;;8CAKzB,KAAC5E;oCACCyH,MAAK;oCACL6C,WAAU;oCACVhC,WAAWzG,cAAc2D,SAAS;oCAClC+E,UAAU,CAAC9H,sBAAsB,CAACkB;oCAClCyD,WAAU;8CACX;;;;;;;;;AAQb;AAEA,SAAS6B,eAAe,EACtBtG,UAAU,EACVuG,cAAc,EAIf;IACC,qBACE,MAAC/B;QAAIC,WAAU;;YACZ8B,+BACC,KAACxJ;gBACCwG,QAAQvD,YAAY6H;gBACpBC,aAAW;gBACXrD,WAAU;iBAEV;0BACJ,KAAC5G;gBACC+G,MAAK;gBACLmD,IAAI;gBACJtD,WAAW8B,iBAAiB,qBAAqB;0BAEhDA,kBAAkB;;;;AAI3B;AAEA,SAASnG,kBAAqBxB,KAAQ,EAAEoJ,OAAe;IACrD,MAAM,CAACC,WAAWC,aAAa,GAAG9J,SAASQ;IAC3CV,UAAU;QACR,MAAMiK,SAASC,WAAW,IAAMF,aAAatJ,QAAQoJ;QACrD,OAAO,IAAMK,aAAaF;IAC5B,GAAG;QAACvJ;QAAOoJ;KAAQ;IACnB,OAAOC;AACT"}
@@ -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 = `/workspaces/${WORKSPACE_ID}/projects/new`;
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: '/workspaces/$wid',
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: 'projects/$pid',
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,2 @@
1
+ export declare function ProjectSettingsPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=project-settings-page.d.ts.map
@@ -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"}