@shipfox/client-projects 14.0.0 → 14.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +15 -0
- package/dist/components/model-provider-reminder-banner.js +1 -1
- package/dist/components/model-provider-reminder-banner.js.map +1 -1
- package/dist/components/project-crumb.js +4 -4
- package/dist/components/project-crumb.js.map +1 -1
- package/dist/components/project-switcher.js +2 -2
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/source-strip.js +3 -3
- package/dist/components/source-strip.js.map +1 -1
- package/dist/pages/create-project-page.js +13 -13
- package/dist/pages/create-project-page.js.map +1 -1
- package/dist/pages/project-settings-page.js +3 -3
- package/dist/pages/project-settings-page.js.map +1 -1
- package/dist/pages/projects-hub-page.js +13 -13
- package/dist/pages/projects-hub-page.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/components/model-provider-reminder-banner.tsx +1 -1
- package/src/components/project-crumb.tsx +4 -4
- package/src/components/project-switcher.tsx +2 -2
- package/src/components/source-strip.tsx +3 -3
- package/src/pages/create-project-page.tsx +13 -13
- package/src/pages/project-settings-page.tsx +3 -3
- package/src/pages/projects-hub-page.tsx +14 -14
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -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-
|
|
33
|
+
className: "flex w-full flex-col gap-section",
|
|
34
34
|
children: [
|
|
35
35
|
/*#__PURE__*/ _jsxs("header", {
|
|
36
|
-
className: "flex flex-col gap-
|
|
36
|
+
className: "flex flex-col gap-group",
|
|
37
37
|
children: [
|
|
38
38
|
/*#__PURE__*/ _jsx("div", {
|
|
39
|
-
className: "flex items-start justify-between gap-
|
|
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-
|
|
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-
|
|
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: "
|
|
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-
|
|
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-
|
|
158
|
+
className: "h-full p-panel-compact",
|
|
159
159
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
160
|
-
className: "flex items-center gap-
|
|
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-
|
|
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-
|
|
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-
|
|
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":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/pages/projects-hub-page.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionStatusBadge,\n type IntegrationConnection,\n IntegrationIcon,\n useIntegrationConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Card} from '@shipfox/react-ui/card';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Input} from '@shipfox/react-ui/input';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport type {Project} from '#core/project.js';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport function ProjectsHubPage({search = ''}: {search?: string}) {\n const workspace = useActiveWorkspace();\n const navigate = useNavigate();\n const query = useProjectsInfiniteQuery(workspace.id, search || undefined);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // The provider logo and connection health live on the integration connection,\n // not the project, so resolve them once for the whole list and index by id.\n // Skip the fetch when there are no cards to annotate.\n const connectionsQuery = useIntegrationConnectionsQuery(\n projects.length > 0 ? workspace.id : undefined,\n );\n const connectionsById = new Map(\n (connectionsQuery.data ?? []).map((connection) => [connection.id, connection]),\n );\n\n const isInitialLoading = query.isPending;\n const isSearching = Boolean(search) && query.isFetching && !query.isFetchingNextPage;\n const hasNoData = !query.data;\n\n return (\n <div className=\"flex w-full flex-col gap-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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"}
|