@shipfox/client-projects 14.0.0 → 15.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 (39) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +32 -0
  3. package/dist/components/model-provider-reminder-banner.js +1 -1
  4. package/dist/components/model-provider-reminder-banner.js.map +1 -1
  5. package/dist/components/project-crumb.js +4 -4
  6. package/dist/components/project-crumb.js.map +1 -1
  7. package/dist/components/project-switcher.js +2 -2
  8. package/dist/components/project-switcher.js.map +1 -1
  9. package/dist/components/source-strip.js +3 -3
  10. package/dist/components/source-strip.js.map +1 -1
  11. package/dist/feature.d.ts +18 -18
  12. package/dist/feature.d.ts.map +1 -1
  13. package/dist/hooks/api/definitions.d.ts +1 -1
  14. package/dist/hooks/api/definitions.d.ts.map +1 -1
  15. package/dist/hooks/api/projects.d.ts +1 -1
  16. package/dist/hooks/api/projects.d.ts.map +1 -1
  17. package/dist/pages/create-project-page.js +13 -13
  18. package/dist/pages/create-project-page.js.map +1 -1
  19. package/dist/pages/create-project-page.stories.d.ts.map +1 -1
  20. package/dist/pages/project-settings-page.js +3 -3
  21. package/dist/pages/project-settings-page.js.map +1 -1
  22. package/dist/pages/project-settings-page.stories.d.ts.map +1 -1
  23. package/dist/pages/projects-hub-page.js +13 -13
  24. package/dist/pages/projects-hub-page.js.map +1 -1
  25. package/dist/routes/create-project.d.ts.map +1 -1
  26. package/dist/routes/home.d.ts.map +1 -1
  27. package/dist/routes/project-index.d.ts.map +1 -1
  28. package/dist/routes/project-settings-index.d.ts.map +1 -1
  29. package/dist/routes/project-settings.d.ts.map +1 -1
  30. package/dist/tsconfig.test.tsbuildinfo +1 -1
  31. package/package.json +7 -7
  32. package/src/components/model-provider-reminder-banner.tsx +1 -1
  33. package/src/components/project-crumb.tsx +4 -4
  34. package/src/components/project-switcher.tsx +2 -2
  35. package/src/components/source-strip.tsx +3 -3
  36. package/src/pages/create-project-page.tsx +13 -13
  37. package/src/pages/project-settings-page.tsx +3 -3
  38. package/src/pages/projects-hub-page.tsx +14 -14
  39. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1 +1 @@
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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"}
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-section\">\n <header className=\"flex flex-col gap-inline\">\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-cluster\">\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-region lg:grid-cols-[minmax(0,1fr)_340px]\"\n >\n <div className=\"flex min-w-0 flex-col gap-region\">\n <section className=\"flex flex-col gap-group\" aria-label=\"Source integration\">\n <div className=\"flex items-start justify-between gap-group\">\n <div className=\"flex flex-col gap-tight\">\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-group\" aria-label=\"Repository\">\n <div className=\"flex flex-col gap-tight\">\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-group rounded-8 border border-border-neutral-base bg-background-neutral-base p-panel\">\n <div className=\"flex flex-col gap-tight\">\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-inline\">\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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-project-page.stories.d.ts","sourceRoot":"","sources":["../../src/pages/create-project-page.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAmBrD,KAAK,QAAQ,GACT,YAAY,GACZ,YAAY,GACZ,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,CAAC;AAExB,UAAU,2BAA2B;IACnC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAcD,iBAAS,sBAAsB,CAAC,EAAC,QAAQ,EAAC,EAAE,2BAA2B,+BA6BtE;AAED,QAAA,MAAM,IAAI;;;;;;;;;CAKqC,CAAC;AAEhD,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,SAAS,EAAE,KAEvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAE9B,CAAC"}
1
+ {"version":3,"file":"create-project-page.stories.d.ts","sourceRoot":"","sources":["../../src/pages/create-project-page.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAmBrD,KAAK,QAAQ,GACT,YAAY,GACZ,YAAY,GACZ,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,CAAC;AAExB,UAAU,2BAA2B;IACnC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAcD,iBAAS,sBAAsB,CAAC,EAAC,QAAQ,EAAC,EAAE,2BAA2B,+BA6BtE;AAED,QAAA,MAAM,IAAI;;;;QAGK,MAAM;;;;;CAE0B,CAAC;eAEjC,IAAI;AACnB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,SAAS,EAAE,KAEvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAE9B,CAAC"}
@@ -118,10 +118,10 @@ function ProjectSettingsForm({ project }) {
118
118
  return /*#__PURE__*/ _jsxs(_Fragment, {
119
119
  children: [
120
120
  /*#__PURE__*/ _jsxs("div", {
121
- className: "flex min-w-0 flex-col gap-24",
121
+ className: "flex min-w-0 flex-col gap-section",
122
122
  children: [
123
123
  /*#__PURE__*/ _jsxs("header", {
124
- className: "flex flex-col gap-6",
124
+ className: "flex flex-col gap-inline",
125
125
  children: [
126
126
  /*#__PURE__*/ _jsx(Header, {
127
127
  variant: "h1",
@@ -140,7 +140,7 @@ function ProjectSettingsForm({ project }) {
140
140
  children: formError
141
141
  }) : null,
142
142
  /*#__PURE__*/ _jsxs("form", {
143
- className: "flex max-w-[560px] flex-col gap-16",
143
+ className: "flex max-w-[560px] flex-col gap-group",
144
144
  noValidate: true,
145
145
  onSubmit: (event)=>{
146
146
  event.preventDefault();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/project-settings-page.tsx"],"sourcesContent":["import {slugSchema} from '@shipfox/api-common-dto';\nimport {createProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {ApiError} from '@shipfox/client-api';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {\n displayNameFieldError,\n QueryLoadError,\n SlugChangeWarning,\n SlugField,\n} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\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 {useNavigate} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {useMaybeActiveProjectQuery} from '#chrome.js';\nimport type {Project} from '#core/project.js';\nimport {useProjectSlugAvailability, useUpdateProjectMutation} from '#hooks/api/projects.js';\nimport {projectSettingsErrorToFormError} from './project-settings-form-errors.js';\n\ninterface ProjectSettingsValues {\n name: string;\n slug: string;\n}\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function ProjectSettingsPage() {\n const projectQuery = useMaybeActiveProjectQuery();\n if (projectQuery.isPending) return <FullPageLoader />;\n if (projectQuery.isError && projectQuery.data === undefined) {\n if (projectQuery.error instanceof ApiError && projectQuery.error.status === 404) {\n return (\n <EmptyState\n icon=\"errorWarningLine\"\n tone=\"error\"\n title=\"Project not found\"\n description=\"This project doesn't exist, or you don't have access to it.\"\n />\n );\n }\n return <QueryLoadError query={projectQuery} subject=\"project\" />;\n }\n const project = projectQuery.data;\n if (!project) {\n return (\n <EmptyState\n icon=\"errorWarningLine\"\n tone=\"error\"\n title=\"Project not found\"\n description=\"This project doesn't exist, or you don't have access to it.\"\n />\n );\n }\n return (\n <ProjectSettingsForm key={`${project.id}:${project.name}:${project.slug}`} project={project} />\n );\n}\n\nfunction ProjectSettingsForm({project}: {project: Project}) {\n const workspace = useActiveWorkspace();\n const navigate = useNavigate();\n const updateProject = useUpdateProjectMutation();\n const [warningOpen, setWarningOpen] = useState(false);\n const [pendingValues, setPendingValues] = useState<ProjectSettingsValues>();\n const [formError, setFormError] = useState<string>();\n\n const checkProjectSlugAvailability = useProjectSlugAvailability(workspace.id, project.id);\n\n const form = useForm({\n defaultValues: {name: project.name, slug: project.slug},\n onSubmit: async ({value}) => {\n if (value.slug !== project.slug) {\n setPendingValues(value);\n setWarningOpen(true);\n return;\n }\n await save(value);\n },\n });\n\n async function save(values: ProjectSettingsValues) {\n setFormError(undefined);\n const nameChanged = values.name !== project.name;\n const slugChanged = values.slug !== project.slug;\n if (!nameChanged && !slugChanged) return;\n const command = {\n projectId: project.id,\n ...(nameChanged ? {name: values.name} : {}),\n ...(slugChanged ? {slug: values.slug} : {}),\n };\n\n try {\n const updated = await updateProject.mutateAsync(command);\n setPendingValues(undefined);\n setWarningOpen(false);\n toast.success('Project settings saved.');\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug/settings/general',\n params: {workspaceSlug: workspace.slug, projectSlug: updated.slug},\n });\n } catch (error) {\n setPendingValues(undefined);\n setWarningOpen(false);\n const mapped = projectSettingsErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (previous) => ({\n ...previous,\n errorMap: {...previous.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n }\n\n return (\n <>\n <div className=\"flex min-w-0 flex-col gap-24\">\n <header className=\"flex flex-col gap-6\">\n <Header variant=\"h1\">General</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Update the project name and the slug used in its URLs.\n </Text>\n </header>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n\n <form\n className=\"flex max-w-[560px] flex-col gap-16\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\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-settings-name\" error={fieldError(field)}>\n <FormFieldInput\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\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-settings-slug\"\n label=\"Project slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => field.handleChange(value)}\n onBlur={field.handleBlur}\n error={fieldError(field)}\n description={\n <span className=\"break-all font-code\">\n /w/{workspace.slug}/p/{field.state.value}\n </span>\n }\n placeholder=\"platform\"\n className=\"font-code\"\n currentSlug={project.slug}\n checkEnabled\n debounceMs={0}\n isValid={isSlugValid}\n checkAvailability={checkProjectSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button type=\"submit\" isLoading={updateProject.isPending} className=\"self-start\">\n Save changes\n </Button>\n </form>\n </div>\n\n <SlugChangeWarning\n open={warningOpen}\n onOpenChange={setWarningOpen}\n entityLabel=\"project\"\n isLoading={updateProject.isPending}\n onConfirm={() => {\n if (pendingValues) void save(pendingValues);\n }}\n />\n </>\n );\n}\n"],"names":["slugSchema","createProjectBodySchema","ApiError","useActiveWorkspace","displayNameFieldError","QueryLoadError","SlugChangeWarning","SlugField","Button","Callout","EmptyState","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","useNavigate","useState","useMaybeActiveProjectQuery","useProjectSlugAvailability","useUpdateProjectMutation","projectSettingsErrorToFormError","isSlugValid","value","safeParse","success","ProjectSettingsPage","projectQuery","isPending","isError","data","undefined","error","status","icon","tone","title","description","query","subject","project","ProjectSettingsForm","id","name","slug","workspace","navigate","updateProject","warningOpen","setWarningOpen","pendingValues","setPendingValues","formError","setFormError","checkProjectSlugAvailability","form","defaultValues","onSubmit","save","values","nameChanged","slugChanged","command","projectId","updated","mutateAsync","to","params","workspaceSlug","projectSlug","mapped","kind","setFieldMeta","field","previous","errorMap","onServer","message","div","className","header","variant","size","role","type","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","shape","label","state","onChange","handleChange","target","handleBlur","span","placeholder","currentSlug","checkEnabled","debounceMs","isValid","checkAvailability","isLoading","open","onOpenChange","entityLabel","onConfirm"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/project-settings-page.tsx"],"sourcesContent":["import {slugSchema} from '@shipfox/api-common-dto';\nimport {createProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {ApiError} from '@shipfox/client-api';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {\n displayNameFieldError,\n QueryLoadError,\n SlugChangeWarning,\n SlugField,\n} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\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 {useNavigate} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {useMaybeActiveProjectQuery} from '#chrome.js';\nimport type {Project} from '#core/project.js';\nimport {useProjectSlugAvailability, useUpdateProjectMutation} from '#hooks/api/projects.js';\nimport {projectSettingsErrorToFormError} from './project-settings-form-errors.js';\n\ninterface ProjectSettingsValues {\n name: string;\n slug: string;\n}\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function ProjectSettingsPage() {\n const projectQuery = useMaybeActiveProjectQuery();\n if (projectQuery.isPending) return <FullPageLoader />;\n if (projectQuery.isError && projectQuery.data === undefined) {\n if (projectQuery.error instanceof ApiError && projectQuery.error.status === 404) {\n return (\n <EmptyState\n icon=\"errorWarningLine\"\n tone=\"error\"\n title=\"Project not found\"\n description=\"This project doesn't exist, or you don't have access to it.\"\n />\n );\n }\n return <QueryLoadError query={projectQuery} subject=\"project\" />;\n }\n const project = projectQuery.data;\n if (!project) {\n return (\n <EmptyState\n icon=\"errorWarningLine\"\n tone=\"error\"\n title=\"Project not found\"\n description=\"This project doesn't exist, or you don't have access to it.\"\n />\n );\n }\n return (\n <ProjectSettingsForm key={`${project.id}:${project.name}:${project.slug}`} project={project} />\n );\n}\n\nfunction ProjectSettingsForm({project}: {project: Project}) {\n const workspace = useActiveWorkspace();\n const navigate = useNavigate();\n const updateProject = useUpdateProjectMutation();\n const [warningOpen, setWarningOpen] = useState(false);\n const [pendingValues, setPendingValues] = useState<ProjectSettingsValues>();\n const [formError, setFormError] = useState<string>();\n\n const checkProjectSlugAvailability = useProjectSlugAvailability(workspace.id, project.id);\n\n const form = useForm({\n defaultValues: {name: project.name, slug: project.slug},\n onSubmit: async ({value}) => {\n if (value.slug !== project.slug) {\n setPendingValues(value);\n setWarningOpen(true);\n return;\n }\n await save(value);\n },\n });\n\n async function save(values: ProjectSettingsValues) {\n setFormError(undefined);\n const nameChanged = values.name !== project.name;\n const slugChanged = values.slug !== project.slug;\n if (!nameChanged && !slugChanged) return;\n const command = {\n projectId: project.id,\n ...(nameChanged ? {name: values.name} : {}),\n ...(slugChanged ? {slug: values.slug} : {}),\n };\n\n try {\n const updated = await updateProject.mutateAsync(command);\n setPendingValues(undefined);\n setWarningOpen(false);\n toast.success('Project settings saved.');\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug/settings/general',\n params: {workspaceSlug: workspace.slug, projectSlug: updated.slug},\n });\n } catch (error) {\n setPendingValues(undefined);\n setWarningOpen(false);\n const mapped = projectSettingsErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (previous) => ({\n ...previous,\n errorMap: {...previous.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n }\n\n return (\n <>\n <div className=\"flex min-w-0 flex-col gap-section\">\n <header className=\"flex flex-col gap-inline\">\n <Header variant=\"h1\">General</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Update the project name and the slug used in its URLs.\n </Text>\n </header>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n\n <form\n className=\"flex max-w-[560px] flex-col gap-group\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\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-settings-name\" error={fieldError(field)}>\n <FormFieldInput\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\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-settings-slug\"\n label=\"Project slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => field.handleChange(value)}\n onBlur={field.handleBlur}\n error={fieldError(field)}\n description={\n <span className=\"break-all font-code\">\n /w/{workspace.slug}/p/{field.state.value}\n </span>\n }\n placeholder=\"platform\"\n className=\"font-code\"\n currentSlug={project.slug}\n checkEnabled\n debounceMs={0}\n isValid={isSlugValid}\n checkAvailability={checkProjectSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button type=\"submit\" isLoading={updateProject.isPending} className=\"self-start\">\n Save changes\n </Button>\n </form>\n </div>\n\n <SlugChangeWarning\n open={warningOpen}\n onOpenChange={setWarningOpen}\n entityLabel=\"project\"\n isLoading={updateProject.isPending}\n onConfirm={() => {\n if (pendingValues) void save(pendingValues);\n }}\n />\n </>\n );\n}\n"],"names":["slugSchema","createProjectBodySchema","ApiError","useActiveWorkspace","displayNameFieldError","QueryLoadError","SlugChangeWarning","SlugField","Button","Callout","EmptyState","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","useNavigate","useState","useMaybeActiveProjectQuery","useProjectSlugAvailability","useUpdateProjectMutation","projectSettingsErrorToFormError","isSlugValid","value","safeParse","success","ProjectSettingsPage","projectQuery","isPending","isError","data","undefined","error","status","icon","tone","title","description","query","subject","project","ProjectSettingsForm","id","name","slug","workspace","navigate","updateProject","warningOpen","setWarningOpen","pendingValues","setPendingValues","formError","setFormError","checkProjectSlugAvailability","form","defaultValues","onSubmit","save","values","nameChanged","slugChanged","command","projectId","updated","mutateAsync","to","params","workspaceSlug","projectSlug","mapped","kind","setFieldMeta","field","previous","errorMap","onServer","message","div","className","header","variant","size","role","type","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","shape","label","state","onChange","handleChange","target","handleBlur","span","placeholder","currentSlug","checkEnabled","debounceMs","isValid","checkAvailability","isLoading","open","onOpenChange","entityLabel","onConfirm"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"project-settings-page.stories.d.ts","sourceRoot":"","sources":["../../src/pages/project-settings-page.stories.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAmBrD,KAAK,QAAQ,GAAG,SAAS,GAAG,aAAa,GAAG,YAAY,CAAC;AAezD,UAAU,6BAA6B;IACrC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAcD,iBAAS,wBAAwB,CAAC,EAAC,QAAQ,EAAC,EAAE,6BAA6B,+BA6B1E;AAED,QAAA,MAAM,IAAI;;;;;;;;;CAKuC,CAAC;AAElD,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAgB/B,CAAC"}
1
+ {"version":3,"file":"project-settings-page.stories.d.ts","sourceRoot":"","sources":["../../src/pages/project-settings-page.stories.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAmBrD,KAAK,QAAQ,GAAG,SAAS,GAAG,aAAa,GAAG,YAAY,CAAC;AAezD,UAAU,6BAA6B;IACrC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAcD,iBAAS,wBAAwB,CAAC,EAAC,QAAQ,EAAC,EAAE,6BAA6B,+BA6B1E;AAED,QAAA,MAAM,IAAI;;;;QAGK,MAAM;;;;;CAE4B,CAAC;eAEnC,IAAI;AACnB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAgB/B,CAAC"}
@@ -30,20 +30,20 @@ export function ProjectsHubPage({ search = '' }) {
30
30
  const isSearching = Boolean(search) && query.isFetching && !query.isFetchingNextPage;
31
31
  const hasNoData = !query.data;
32
32
  return /*#__PURE__*/ _jsxs("div", {
33
- className: "flex w-full flex-col gap-24",
33
+ className: "flex w-full flex-col gap-section",
34
34
  children: [
35
35
  /*#__PURE__*/ _jsxs("header", {
36
- className: "flex flex-col gap-16",
36
+ className: "flex flex-col gap-group",
37
37
  children: [
38
38
  /*#__PURE__*/ _jsx("div", {
39
- className: "flex items-start justify-between gap-24 max-[640px]:flex-col",
39
+ className: "flex items-start justify-between gap-section max-[640px]:flex-col",
40
40
  children: /*#__PURE__*/ _jsx(Header, {
41
41
  variant: "h2",
42
42
  children: "Projects"
43
43
  })
44
44
  }),
45
45
  /*#__PURE__*/ _jsxs("div", {
46
- className: "flex items-center gap-12 max-[640px]:flex-col max-[640px]:items-stretch",
46
+ className: "flex items-center gap-cluster max-[640px]:flex-col max-[640px]:items-stretch",
47
47
  children: [
48
48
  /*#__PURE__*/ _jsx(Input, {
49
49
  type: "search",
@@ -106,10 +106,11 @@ export function ProjectsHubPage({ search = '' }) {
106
106
  })
107
107
  }) : null,
108
108
  projects.length > 0 ? /*#__PURE__*/ _jsxs("section", {
109
+ className: "flex flex-col gap-group",
109
110
  "aria-label": "Projects list",
110
111
  children: [
111
112
  /*#__PURE__*/ _jsx("ul", {
112
- className: "grid grid-cols-2 gap-12 max-[760px]:grid-cols-1",
113
+ className: "grid grid-cols-2 gap-cluster max-[760px]:grid-cols-1",
113
114
  children: projects.map((project)=>/*#__PURE__*/ _jsx(ProjectCard, {
114
115
  project: project,
115
116
  connection: connectionsById.get(project.source.connectionId),
@@ -121,14 +122,13 @@ export function ProjectsHubPage({ search = '' }) {
121
122
  query.error && query.data ? /*#__PURE__*/ _jsx(Callout, {
122
123
  role: "alert",
123
124
  type: "error",
124
- className: "mt-16",
125
125
  children: /*#__PURE__*/ _jsx(Text, {
126
126
  size: "sm",
127
127
  children: "Could not load the next page. Existing projects are still shown."
128
128
  })
129
129
  }) : null,
130
130
  query.hasNextPage ? /*#__PURE__*/ _jsx("div", {
131
- className: "mt-16 flex justify-center",
131
+ className: "flex justify-center",
132
132
  children: /*#__PURE__*/ _jsx(Button, {
133
133
  variant: "secondary",
134
134
  isLoading: query.isFetchingNextPage,
@@ -145,7 +145,7 @@ function ProjectsSkeleton() {
145
145
  return /*#__PURE__*/ _jsx("ul", {
146
146
  role: "status",
147
147
  "aria-label": "Loading projects",
148
- className: "grid grid-cols-2 gap-12 max-[760px]:grid-cols-1",
148
+ className: "grid grid-cols-2 gap-cluster max-[760px]:grid-cols-1",
149
149
  children: [
150
150
  0,
151
151
  1,
@@ -155,9 +155,9 @@ function ProjectsSkeleton() {
155
155
  5
156
156
  ].map((row)=>/*#__PURE__*/ _jsx("li", {
157
157
  children: /*#__PURE__*/ _jsx(Card, {
158
- className: "h-full p-16",
158
+ className: "h-full p-panel-compact",
159
159
  children: /*#__PURE__*/ _jsxs("div", {
160
- className: "flex items-center gap-12",
160
+ className: "flex items-center gap-cluster",
161
161
  children: [
162
162
  /*#__PURE__*/ _jsx(Skeleton, {
163
163
  className: "size-24 shrink-0"
@@ -216,9 +216,9 @@ function ProjectCard({ project, connection, connectionsResolved, connectionsSett
216
216
  },
217
217
  className: "block h-full rounded-8 focus-visible:shadow-button-neutral-focus focus-visible:outline-none",
218
218
  children: /*#__PURE__*/ _jsx(Card, {
219
- className: "h-full p-16 transition-colors hover:bg-background-components-hover",
219
+ className: "h-full p-panel-compact transition-colors hover:bg-background-components-hover",
220
220
  children: /*#__PURE__*/ _jsxs("div", {
221
- className: "flex min-w-0 items-center gap-12",
221
+ className: "flex min-w-0 items-center gap-cluster",
222
222
  children: [
223
223
  connectionsSettled ? /*#__PURE__*/ _jsx(IntegrationIcon, {
224
224
  source: connection?.provider,
@@ -228,7 +228,7 @@ function ProjectCard({ project, connection, connectionsResolved, connectionsSett
228
228
  className: "size-24 shrink-0"
229
229
  }),
230
230
  /*#__PURE__*/ _jsxs("div", {
231
- className: "flex min-w-0 flex-1 items-center gap-8",
231
+ className: "flex min-w-0 flex-1 items-center gap-inline",
232
232
  children: [
233
233
  /*#__PURE__*/ _jsx(Text, {
234
234
  size: "md",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/projects-hub-page.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionStatusBadge,\n type IntegrationConnection,\n IntegrationIcon,\n useIntegrationConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Card} from '@shipfox/react-ui/card';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Input} from '@shipfox/react-ui/input';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport type {Project} from '#core/project.js';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport function ProjectsHubPage({search = ''}: {search?: string}) {\n const workspace = useActiveWorkspace();\n const navigate = useNavigate();\n const query = useProjectsInfiniteQuery(workspace.id, search || undefined);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // The provider logo and connection health live on the integration connection,\n // not the project, so resolve them once for the whole list and index by id.\n // Skip the fetch when there are no cards to annotate.\n const connectionsQuery = useIntegrationConnectionsQuery(\n projects.length > 0 ? workspace.id : undefined,\n );\n const connectionsById = new Map(\n (connectionsQuery.data ?? []).map((connection) => [connection.id, connection]),\n );\n\n const isInitialLoading = query.isPending;\n const isSearching = Boolean(search) && query.isFetching && !query.isFetchingNextPage;\n const hasNoData = !query.data;\n\n return (\n <div className=\"flex w-full flex-col gap-24\">\n <header className=\"flex flex-col gap-16\">\n <div className=\"flex items-start justify-between gap-24 max-[640px]:flex-col\">\n <Header variant=\"h2\">Projects</Header>\n </div>\n <div className=\"flex items-center gap-12 max-[640px]:flex-col max-[640px]:items-stretch\">\n <Input\n type=\"search\"\n value={search}\n onChange={(event) => {\n const next = event.target.value.trim();\n navigate({search: (next ? {search: next} : {}) as never, replace: true});\n }}\n placeholder=\"Search projects…\"\n aria-label=\"Search projects\"\n className=\"flex-1\"\n iconLeft={<Icon name=\"searchLine\" className=\"size-16\" />}\n iconRight={\n isSearching ? (\n <Icon\n name=\"spinner\"\n size={16}\n className=\"text-foreground-neutral-muted\"\n aria-hidden=\"true\"\n />\n ) : undefined\n }\n />\n <Button asChild iconLeft=\"addLine\" className=\"shrink-0 max-[640px]:w-full\">\n <Link to=\"/w/$workspaceSlug/projects/new\" params={{workspaceSlug: workspace.slug}}>\n New project\n </Link>\n </Button>\n </div>\n </header>\n\n <ModelProviderReminderBanner workspaceId={workspace.id} />\n\n {isInitialLoading || (search && hasNoData && query.isFetching) ? <ProjectsSkeleton /> : null}\n\n {query.isError && hasNoData ? <QueryLoadError query={query} subject=\"projects\" /> : null}\n\n {!isInitialLoading && !query.isError && projects.length === 0 && !search ? (\n <EmptyProjects workspaceSlug={workspace.slug} />\n ) : null}\n\n {!query.isFetching && !query.isError && projects.length === 0 && search ? (\n <NoSearchResults\n search={search}\n onClear={() => navigate({search: {} as never, replace: true})}\n />\n ) : null}\n\n {projects.length > 0 ? (\n <section aria-label=\"Projects list\">\n <ul className=\"grid grid-cols-2 gap-12 max-[760px]:grid-cols-1\">\n {projects.map((project) => (\n <ProjectCard\n project={project}\n connection={connectionsById.get(project.source.connectionId)}\n connectionsResolved={connectionsQuery.isSuccess}\n connectionsSettled={connectionsQuery.isSuccess || connectionsQuery.isError}\n key={project.id}\n workspaceSlug={workspace.slug}\n />\n ))}\n </ul>\n {query.error && query.data ? (\n <Callout role=\"alert\" type=\"error\" className=\"mt-16\">\n <Text size=\"sm\">\n Could not load the next page. Existing projects are still shown.\n </Text>\n </Callout>\n ) : null}\n {query.hasNextPage ? (\n <div className=\"mt-16 flex justify-center\">\n <Button\n variant=\"secondary\"\n isLoading={query.isFetchingNextPage}\n onClick={() => query.fetchNextPage()}\n >\n Load more\n </Button>\n </div>\n ) : null}\n </section>\n ) : null}\n </div>\n );\n}\n\nfunction ProjectsSkeleton() {\n return (\n <ul\n role=\"status\"\n aria-label=\"Loading projects\"\n className=\"grid grid-cols-2 gap-12 max-[760px]:grid-cols-1\"\n >\n {[0, 1, 2, 3, 4, 5].map((row) => (\n <li key={row}>\n <Card className=\"h-full p-16\">\n <div className=\"flex items-center gap-12\">\n <Skeleton className=\"size-24 shrink-0\" />\n <Skeleton className=\"h-16 w-1/2\" />\n </div>\n </Card>\n </li>\n ))}\n </ul>\n );\n}\n\nfunction EmptyProjects({workspaceSlug}: {workspaceSlug: string}) {\n return (\n <EmptyState\n icon=\"folderLine\"\n title=\"Create your first project\"\n description=\"Connect a repository-backed project to start building workflows.\"\n action={\n <Button asChild iconRight=\"chevronRight\">\n <Link to=\"/w/$workspaceSlug/projects/new\" params={{workspaceSlug}}>\n Create project\n </Link>\n </Button>\n }\n />\n );\n}\n\nfunction NoSearchResults({search, onClear}: {search: string; onClear: () => void}) {\n return (\n <EmptyState\n icon=\"searchLine\"\n title={`No projects match “${search}”`}\n description=\"Try a different search, or clear it to see all projects.\"\n action={\n <Button size=\"sm\" variant=\"secondary\" onClick={onClear}>\n Clear search\n </Button>\n }\n />\n );\n}\n\nfunction ProjectCard({\n project,\n connection,\n connectionsResolved,\n connectionsSettled,\n workspaceSlug,\n}: {\n project: Project;\n connection: IntegrationConnection | undefined;\n connectionsResolved: boolean;\n connectionsSettled: boolean;\n workspaceSlug: string;\n}) {\n // On a resolved fetch, `active` carries no badge while a missing connection\n // reads as an error so a broken source is still flagged. An unresolved or\n // failed fetch shows nothing, so a fetch failure never flags every card.\n const status = connectionsResolved ? (connection?.lifecycleStatus ?? 'error') : undefined;\n\n return (\n <li>\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug\"\n params={{workspaceSlug, projectSlug: project.slug}}\n className=\"block h-full rounded-8 focus-visible:shadow-button-neutral-focus focus-visible:outline-none\"\n >\n <Card className=\"h-full p-16 transition-colors hover:bg-background-components-hover\">\n <div className=\"flex min-w-0 items-center gap-12\">\n {/* Settle on success or error: a failed fetch falls back to the\n neutral provider icon rather than spinning forever. */}\n {connectionsSettled ? (\n <IntegrationIcon\n source={connection?.provider}\n aria-hidden\n className=\"size-24 shrink-0 text-foreground-neutral-base\"\n />\n ) : (\n <Skeleton className=\"size-24 shrink-0\" />\n )}\n <div className=\"flex min-w-0 flex-1 items-center gap-8\">\n <Text size=\"md\" bold className=\"truncate\">\n {project.name}\n </Text>\n {status ? <ConnectionStatusBadge status={status} className=\"shrink-0\" /> : null}\n </div>\n </div>\n </Card>\n </Link>\n </li>\n );\n}\n"],"names":["useActiveWorkspace","ConnectionStatusBadge","IntegrationIcon","useIntegrationConnectionsQuery","QueryLoadError","Button","Callout","Card","EmptyState","Icon","Input","Skeleton","Header","Text","Link","useNavigate","ModelProviderReminderBanner","useProjectsInfiniteQuery","ProjectsHubPage","search","workspace","navigate","query","id","undefined","projects","data","pages","flatMap","page","connectionsQuery","length","connectionsById","Map","map","connection","isInitialLoading","isPending","isSearching","Boolean","isFetching","isFetchingNextPage","hasNoData","div","className","header","variant","type","value","onChange","event","next","target","trim","replace","placeholder","aria-label","iconLeft","name","iconRight","size","aria-hidden","asChild","to","params","workspaceSlug","slug","workspaceId","ProjectsSkeleton","isError","subject","EmptyProjects","NoSearchResults","onClear","section","ul","project","ProjectCard","get","source","connectionId","connectionsResolved","isSuccess","connectionsSettled","error","role","hasNextPage","isLoading","onClick","fetchNextPage","row","li","icon","title","description","action","status","lifecycleStatus","projectSlug","provider","bold"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,uBAAuB;AACxD,SACEC,qBAAqB,EAErBC,eAAe,EACfC,8BAA8B,QACzB,+BAA+B;AACtC,SAAQC,cAAc,QAAO,qBAAqB;AAClD,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAC1D,SAAQC,IAAI,EAAEC,WAAW,QAAO,yBAAyB;AACzD,SAAQC,2BAA2B,QAAO,gDAAgD;AAE1F,SAAQC,wBAAwB,QAAO,yBAAyB;AAEhE,OAAO,SAASC,gBAAgB,EAACC,SAAS,EAAE,EAAoB;IAC9D,MAAMC,YAAYpB;IAClB,MAAMqB,WAAWN;IACjB,MAAMO,QAAQL,yBAAyBG,UAAUG,EAAE,EAAEJ,UAAUK;IAC/D,MAAMC,WAAWH,MAAMI,IAAI,EAAEC,MAAMC,QAAQ,CAACC,OAASA,KAAKJ,QAAQ,KAAK,EAAE;IAEzE,8EAA8E;IAC9E,4EAA4E;IAC5E,sDAAsD;IACtD,MAAMK,mBAAmB3B,+BACvBsB,SAASM,MAAM,GAAG,IAAIX,UAAUG,EAAE,GAAGC;IAEvC,MAAMQ,kBAAkB,IAAIC,IAC1B,AAACH,CAAAA,iBAAiBJ,IAAI,IAAI,EAAE,AAAD,EAAGQ,GAAG,CAAC,CAACC,aAAe;YAACA,WAAWZ,EAAE;YAAEY;SAAW;IAG/E,MAAMC,mBAAmBd,MAAMe,SAAS;IACxC,MAAMC,cAAcC,QAAQpB,WAAWG,MAAMkB,UAAU,IAAI,CAAClB,MAAMmB,kBAAkB;IACpF,MAAMC,YAAY,CAACpB,MAAMI,IAAI;IAE7B,qBACE,MAACiB;QAAIC,WAAU;;0BACb,MAACC;gBAAOD,WAAU;;kCAChB,KAACD;wBAAIC,WAAU;kCACb,cAAA,KAAChC;4BAAOkC,SAAQ;sCAAK;;;kCAEvB,MAACH;wBAAIC,WAAU;;0CACb,KAAClC;gCACCqC,MAAK;gCACLC,OAAO7B;gCACP8B,UAAU,CAACC;oCACT,MAAMC,OAAOD,MAAME,MAAM,CAACJ,KAAK,CAACK,IAAI;oCACpChC,SAAS;wCAACF,QAASgC,OAAO;4CAAChC,QAAQgC;wCAAI,IAAI,CAAC;wCAAaG,SAAS;oCAAI;gCACxE;gCACAC,aAAY;gCACZC,cAAW;gCACXZ,WAAU;gCACVa,wBAAU,KAAChD;oCAAKiD,MAAK;oCAAad,WAAU;;gCAC5Ce,WACErB,4BACE,KAAC7B;oCACCiD,MAAK;oCACLE,MAAM;oCACNhB,WAAU;oCACViB,eAAY;qCAEZrC;;0CAGR,KAACnB;gCAAOyD,OAAO;gCAACL,UAAS;gCAAUb,WAAU;0CAC3C,cAAA,KAAC9B;oCAAKiD,IAAG;oCAAiCC,QAAQ;wCAACC,eAAe7C,UAAU8C,IAAI;oCAAA;8CAAG;;;;;;;0BAOzF,KAAClD;gBAA4BmD,aAAa/C,UAAUG,EAAE;;YAErDa,oBAAqBjB,UAAUuB,aAAapB,MAAMkB,UAAU,iBAAI,KAAC4B,wBAAsB;YAEvF9C,MAAM+C,OAAO,IAAI3B,0BAAY,KAACtC;gBAAekB,OAAOA;gBAAOgD,SAAQ;iBAAgB;YAEnF,CAAClC,oBAAoB,CAACd,MAAM+C,OAAO,IAAI5C,SAASM,MAAM,KAAK,KAAK,CAACZ,uBAChE,KAACoD;gBAAcN,eAAe7C,UAAU8C,IAAI;iBAC1C;YAEH,CAAC5C,MAAMkB,UAAU,IAAI,CAAClB,MAAM+C,OAAO,IAAI5C,SAASM,MAAM,KAAK,KAAKZ,uBAC/D,KAACqD;gBACCrD,QAAQA;gBACRsD,SAAS,IAAMpD,SAAS;wBAACF,QAAQ,CAAC;wBAAYmC,SAAS;oBAAI;iBAE3D;YAEH7B,SAASM,MAAM,GAAG,kBACjB,MAAC2C;gBAAQlB,cAAW;;kCAClB,KAACmB;wBAAG/B,WAAU;kCACXnB,SAASS,GAAG,CAAC,CAAC0C,wBACb,KAACC;gCACCD,SAASA;gCACTzC,YAAYH,gBAAgB8C,GAAG,CAACF,QAAQG,MAAM,CAACC,YAAY;gCAC3DC,qBAAqBnD,iBAAiBoD,SAAS;gCAC/CC,oBAAoBrD,iBAAiBoD,SAAS,IAAIpD,iBAAiBuC,OAAO;gCAE1EJ,eAAe7C,UAAU8C,IAAI;+BADxBU,QAAQrD,EAAE;;oBAKpBD,MAAM8D,KAAK,IAAI9D,MAAMI,IAAI,iBACxB,KAACpB;wBAAQ+E,MAAK;wBAAQtC,MAAK;wBAAQH,WAAU;kCAC3C,cAAA,KAAC/B;4BAAK+C,MAAK;sCAAK;;yBAIhB;oBACHtC,MAAMgE,WAAW,iBAChB,KAAC3C;wBAAIC,WAAU;kCACb,cAAA,KAACvC;4BACCyC,SAAQ;4BACRyC,WAAWjE,MAAMmB,kBAAkB;4BACnC+C,SAAS,IAAMlE,MAAMmE,aAAa;sCACnC;;yBAID;;iBAEJ;;;AAGV;AAEA,SAASrB;IACP,qBACE,KAACO;QACCU,MAAK;QACL7B,cAAW;QACXZ,WAAU;kBAET;YAAC;YAAG;YAAG;YAAG;YAAG;YAAG;SAAE,CAACV,GAAG,CAAC,CAACwD,oBACvB,KAACC;0BACC,cAAA,KAACpF;oBAAKqC,WAAU;8BACd,cAAA,MAACD;wBAAIC,WAAU;;0CACb,KAACjC;gCAASiC,WAAU;;0CACpB,KAACjC;gCAASiC,WAAU;;;;;eAJjB8C;;AAWjB;AAEA,SAASnB,cAAc,EAACN,aAAa,EAA0B;IAC7D,qBACE,KAACzD;QACCoF,MAAK;QACLC,OAAM;QACNC,aAAY;QACZC,sBACE,KAAC1F;YAAOyD,OAAO;YAACH,WAAU;sBACxB,cAAA,KAAC7C;gBAAKiD,IAAG;gBAAiCC,QAAQ;oBAACC;gBAAa;0BAAG;;;;AAO7E;AAEA,SAASO,gBAAgB,EAACrD,MAAM,EAAEsD,OAAO,EAAwC;IAC/E,qBACE,KAACjE;QACCoF,MAAK;QACLC,OAAO,CAAC,mBAAmB,EAAE1E,OAAO,CAAC,CAAC;QACtC2E,aAAY;QACZC,sBACE,KAAC1F;YAAOuD,MAAK;YAAKd,SAAQ;YAAY0C,SAASf;sBAAS;;;AAMhE;AAEA,SAASI,YAAY,EACnBD,OAAO,EACPzC,UAAU,EACV8C,mBAAmB,EACnBE,kBAAkB,EAClBlB,aAAa,EAOd;IACC,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,MAAM+B,SAASf,sBAAuB9C,YAAY8D,mBAAmB,UAAWzE;IAEhF,qBACE,KAACmE;kBACC,cAAA,KAAC7E;YACCiD,IAAG;YACHC,QAAQ;gBAACC;gBAAeiC,aAAatB,QAAQV,IAAI;YAAA;YACjDtB,WAAU;sBAEV,cAAA,KAACrC;gBAAKqC,WAAU;0BACd,cAAA,MAACD;oBAAIC,WAAU;;wBAGZuC,mCACC,KAACjF;4BACC6E,QAAQ5C,YAAYgE;4BACpBtC,aAAW;4BACXjB,WAAU;2CAGZ,KAACjC;4BAASiC,WAAU;;sCAEtB,MAACD;4BAAIC,WAAU;;8CACb,KAAC/B;oCAAK+C,MAAK;oCAAKwC,IAAI;oCAACxD,WAAU;8CAC5BgC,QAAQlB,IAAI;;gCAEdsC,uBAAS,KAAC/F;oCAAsB+F,QAAQA;oCAAQpD,WAAU;qCAAgB;;;;;;;;AAOzF"}
1
+ {"version":3,"sources":["../../src/pages/projects-hub-page.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionStatusBadge,\n type IntegrationConnection,\n IntegrationIcon,\n useIntegrationConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Card} from '@shipfox/react-ui/card';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Input} from '@shipfox/react-ui/input';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport type {Project} from '#core/project.js';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport function ProjectsHubPage({search = ''}: {search?: string}) {\n const workspace = useActiveWorkspace();\n const navigate = useNavigate();\n const query = useProjectsInfiniteQuery(workspace.id, search || undefined);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // The provider logo and connection health live on the integration connection,\n // not the project, so resolve them once for the whole list and index by id.\n // Skip the fetch when there are no cards to annotate.\n const connectionsQuery = useIntegrationConnectionsQuery(\n projects.length > 0 ? workspace.id : undefined,\n );\n const connectionsById = new Map(\n (connectionsQuery.data ?? []).map((connection) => [connection.id, connection]),\n );\n\n const isInitialLoading = query.isPending;\n const isSearching = Boolean(search) && query.isFetching && !query.isFetchingNextPage;\n const hasNoData = !query.data;\n\n return (\n <div className=\"flex w-full flex-col gap-section\">\n <header className=\"flex flex-col gap-group\">\n <div className=\"flex items-start justify-between gap-section max-[640px]:flex-col\">\n <Header variant=\"h2\">Projects</Header>\n </div>\n <div className=\"flex items-center gap-cluster max-[640px]:flex-col max-[640px]:items-stretch\">\n <Input\n type=\"search\"\n value={search}\n onChange={(event) => {\n const next = event.target.value.trim();\n navigate({search: (next ? {search: next} : {}) as never, replace: true});\n }}\n placeholder=\"Search projects…\"\n aria-label=\"Search projects\"\n className=\"flex-1\"\n iconLeft={<Icon name=\"searchLine\" className=\"size-16\" />}\n iconRight={\n isSearching ? (\n <Icon\n name=\"spinner\"\n size={16}\n className=\"text-foreground-neutral-muted\"\n aria-hidden=\"true\"\n />\n ) : undefined\n }\n />\n <Button asChild iconLeft=\"addLine\" className=\"shrink-0 max-[640px]:w-full\">\n <Link to=\"/w/$workspaceSlug/projects/new\" params={{workspaceSlug: workspace.slug}}>\n New project\n </Link>\n </Button>\n </div>\n </header>\n\n <ModelProviderReminderBanner workspaceId={workspace.id} />\n\n {isInitialLoading || (search && hasNoData && query.isFetching) ? <ProjectsSkeleton /> : null}\n\n {query.isError && hasNoData ? <QueryLoadError query={query} subject=\"projects\" /> : null}\n\n {!isInitialLoading && !query.isError && projects.length === 0 && !search ? (\n <EmptyProjects workspaceSlug={workspace.slug} />\n ) : null}\n\n {!query.isFetching && !query.isError && projects.length === 0 && search ? (\n <NoSearchResults\n search={search}\n onClear={() => navigate({search: {} as never, replace: true})}\n />\n ) : null}\n\n {projects.length > 0 ? (\n <section className=\"flex flex-col gap-group\" aria-label=\"Projects list\">\n <ul className=\"grid grid-cols-2 gap-cluster max-[760px]:grid-cols-1\">\n {projects.map((project) => (\n <ProjectCard\n project={project}\n connection={connectionsById.get(project.source.connectionId)}\n connectionsResolved={connectionsQuery.isSuccess}\n connectionsSettled={connectionsQuery.isSuccess || connectionsQuery.isError}\n key={project.id}\n workspaceSlug={workspace.slug}\n />\n ))}\n </ul>\n {query.error && query.data ? (\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">\n Could not load the next page. Existing projects are still shown.\n </Text>\n </Callout>\n ) : null}\n {query.hasNextPage ? (\n <div className=\"flex justify-center\">\n <Button\n variant=\"secondary\"\n isLoading={query.isFetchingNextPage}\n onClick={() => query.fetchNextPage()}\n >\n Load more\n </Button>\n </div>\n ) : null}\n </section>\n ) : null}\n </div>\n );\n}\n\nfunction ProjectsSkeleton() {\n return (\n <ul\n role=\"status\"\n aria-label=\"Loading projects\"\n className=\"grid grid-cols-2 gap-cluster max-[760px]:grid-cols-1\"\n >\n {[0, 1, 2, 3, 4, 5].map((row) => (\n <li key={row}>\n <Card className=\"h-full p-panel-compact\">\n <div className=\"flex items-center gap-cluster\">\n <Skeleton className=\"size-24 shrink-0\" />\n <Skeleton className=\"h-16 w-1/2\" />\n </div>\n </Card>\n </li>\n ))}\n </ul>\n );\n}\n\nfunction EmptyProjects({workspaceSlug}: {workspaceSlug: string}) {\n return (\n <EmptyState\n icon=\"folderLine\"\n title=\"Create your first project\"\n description=\"Connect a repository-backed project to start building workflows.\"\n action={\n <Button asChild iconRight=\"chevronRight\">\n <Link to=\"/w/$workspaceSlug/projects/new\" params={{workspaceSlug}}>\n Create project\n </Link>\n </Button>\n }\n />\n );\n}\n\nfunction NoSearchResults({search, onClear}: {search: string; onClear: () => void}) {\n return (\n <EmptyState\n icon=\"searchLine\"\n title={`No projects match “${search}”`}\n description=\"Try a different search, or clear it to see all projects.\"\n action={\n <Button size=\"sm\" variant=\"secondary\" onClick={onClear}>\n Clear search\n </Button>\n }\n />\n );\n}\n\nfunction ProjectCard({\n project,\n connection,\n connectionsResolved,\n connectionsSettled,\n workspaceSlug,\n}: {\n project: Project;\n connection: IntegrationConnection | undefined;\n connectionsResolved: boolean;\n connectionsSettled: boolean;\n workspaceSlug: string;\n}) {\n // On a resolved fetch, `active` carries no badge while a missing connection\n // reads as an error so a broken source is still flagged. An unresolved or\n // failed fetch shows nothing, so a fetch failure never flags every card.\n const status = connectionsResolved ? (connection?.lifecycleStatus ?? 'error') : undefined;\n\n return (\n <li>\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug\"\n params={{workspaceSlug, projectSlug: project.slug}}\n className=\"block h-full rounded-8 focus-visible:shadow-button-neutral-focus focus-visible:outline-none\"\n >\n <Card className=\"h-full p-panel-compact transition-colors hover:bg-background-components-hover\">\n <div className=\"flex min-w-0 items-center gap-cluster\">\n {/* Settle on success or error: a failed fetch falls back to the\n neutral provider icon rather than spinning forever. */}\n {connectionsSettled ? (\n <IntegrationIcon\n source={connection?.provider}\n aria-hidden\n className=\"size-24 shrink-0 text-foreground-neutral-base\"\n />\n ) : (\n <Skeleton className=\"size-24 shrink-0\" />\n )}\n <div className=\"flex min-w-0 flex-1 items-center gap-inline\">\n <Text size=\"md\" bold className=\"truncate\">\n {project.name}\n </Text>\n {status ? <ConnectionStatusBadge status={status} className=\"shrink-0\" /> : null}\n </div>\n </div>\n </Card>\n </Link>\n </li>\n );\n}\n"],"names":["useActiveWorkspace","ConnectionStatusBadge","IntegrationIcon","useIntegrationConnectionsQuery","QueryLoadError","Button","Callout","Card","EmptyState","Icon","Input","Skeleton","Header","Text","Link","useNavigate","ModelProviderReminderBanner","useProjectsInfiniteQuery","ProjectsHubPage","search","workspace","navigate","query","id","undefined","projects","data","pages","flatMap","page","connectionsQuery","length","connectionsById","Map","map","connection","isInitialLoading","isPending","isSearching","Boolean","isFetching","isFetchingNextPage","hasNoData","div","className","header","variant","type","value","onChange","event","next","target","trim","replace","placeholder","aria-label","iconLeft","name","iconRight","size","aria-hidden","asChild","to","params","workspaceSlug","slug","workspaceId","ProjectsSkeleton","isError","subject","EmptyProjects","NoSearchResults","onClear","section","ul","project","ProjectCard","get","source","connectionId","connectionsResolved","isSuccess","connectionsSettled","error","role","hasNextPage","isLoading","onClick","fetchNextPage","row","li","icon","title","description","action","status","lifecycleStatus","projectSlug","provider","bold"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-project.d.ts","sourceRoot":"","sources":["../../src/routes/create-project.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,iBAAiB,EAAC,MAAM,+BAA+B,CAAC;;;;AAEhE,wBAA2D"}
1
+ {"version":3,"file":"create-project.d.ts","sourceRoot":"","sources":["../../src/routes/create-project.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,iBAAiB,EAAC,MAAM,+BAA+B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../src/routes/home.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAC,sBAAsB,EAAC,MAAM,aAAa,CAAC;;;;;AAEnD,wBAGG"}
1
+ {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../src/routes/home.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAC,sBAAsB,EAAC,MAAM,aAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"project-index.d.ts","sourceRoot":"","sources":["../../src/routes/project-index.tsx"],"names":[],"mappings":";sCAIyB;QAAC,MAAM,EAAE;YAAC,aAAa,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC;;AAD/E,wBAOG"}
1
+ {"version":3,"file":"project-index.d.ts","sourceRoot":"","sources":["../../src/routes/project-index.tsx"],"names":[],"mappings":";sCAIyB;QAAC,MAAM,EAAE;YAAC,aAAa,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"project-settings-index.d.ts","sourceRoot":"","sources":["../../src/routes/project-settings-index.tsx"],"names":[],"mappings":";sCAIyB;QAAC,MAAM,EAAE;YAAC,aAAa,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC;;AAD/E,wBAOG"}
1
+ {"version":3,"file":"project-settings-index.d.ts","sourceRoot":"","sources":["../../src/routes/project-settings-index.tsx"],"names":[],"mappings":";sCAIyB;QAAC,MAAM,EAAE;YAAC,aAAa,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"project-settings.d.ts","sourceRoot":"","sources":["../../src/routes/project-settings.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;;;;AAEpE,wBAA6D"}
1
+ {"version":3,"file":"project-settings.d.ts","sourceRoot":"","sources":["../../src/routes/project-settings.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC"}