@shipfox/client-projects 25.0.0 → 27.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.
@@ -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 {useChrome} from '@shipfox/client-shell/runtime';\nimport {QueryLoadError} 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 {Icon} from '@shipfox/react-ui/icon';\nimport {Input} from '@shipfox/react-ui/input';\nimport {\n Panel,\n PanelActions,\n PanelBody,\n PanelCell,\n PanelCellAction,\n PanelGrid,\n PanelHeader,\n} from '@shipfox/react-ui/panel';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {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 {WorkspaceSetupChecklist} = useChrome();\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 {WorkspaceSetupChecklist ? <WorkspaceSetupChecklist /> : null}\n <ModelProviderReminderBanner workspaceId={workspace.id} />\n\n <section aria-label=\"Projects\">\n <Panel>\n <PanelHeader className=\"flex-wrap max-[640px]:items-stretch\">\n <div className=\"min-w-0 flex-1 max-[640px]:basis-full\">\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 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 </div>\n <PanelActions className=\"max-[640px]:ml-0 max-[640px]:w-full\">\n <Button asChild iconLeft=\"addLine\" className=\"max-[640px]:w-full\">\n <Link to=\"/w/$workspaceSlug/projects/new\" params={{workspaceSlug: workspace.slug}}>\n New project\n </Link>\n </Button>\n </PanelActions>\n </PanelHeader>\n\n <PanelBody>\n {isInitialLoading || (search && hasNoData && query.isFetching) ? (\n <ProjectsSkeleton />\n ) : null}\n\n {query.isError && hasNoData ? (\n <QueryLoadError query={query} subject=\"projects\" variant=\"panel\" />\n ) : 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 <>\n <PanelGrid aria-label=\"Projects list\">\n {projects.map((project) => (\n <ProjectCell\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 </PanelGrid>\n {query.error && query.data ? (\n <div className=\"border-t border-border-neutral-base p-panel-compact\">\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 </div>\n ) : null}\n {query.hasNextPage ? (\n <div className=\"flex justify-center border-t border-border-neutral-base p-panel-compact\">\n <Button\n variant=\"secondary\"\n isLoading={query.isFetchingNextPage}\n onClick={() => query.fetchNextPage()}\n >\n Load more\n </Button>\n </div>\n ) : null}\n </>\n ) : null}\n </PanelBody>\n </Panel>\n </section>\n </div>\n );\n}\n\nfunction ProjectsSkeleton() {\n return (\n <PanelGrid role=\"status\" aria-label=\"Loading projects\">\n {[0, 1, 2, 3, 4, 5].map((row) => (\n <PanelCell key={row}>\n <div className=\"flex items-center gap-cluster px-row py-row\">\n <Skeleton className=\"size-24 shrink-0\" />\n <Skeleton className=\"h-16 w-1/2\" />\n </div>\n </PanelCell>\n ))}\n </PanelGrid>\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 variant=\"panel\"\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 variant=\"panel\"\n action={\n <Button size=\"sm\" variant=\"secondary\" onClick={onClear}>\n Clear search\n </Button>\n }\n />\n );\n}\n\nfunction ProjectCell({\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 <PanelCell>\n <PanelCellAction asChild>\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug\"\n params={{workspaceSlug, projectSlug: project.slug}}\n >\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 <span className=\"flex min-w-0 flex-1 items-center gap-inline\">\n <Text as=\"span\" size=\"md\" bold className=\"truncate\">\n {project.name}\n </Text>\n {status ? <ConnectionStatusBadge status={status} className=\"shrink-0\" /> : null}\n </span>\n </Link>\n </PanelCellAction>\n </PanelCell>\n );\n}\n"],"names":["useActiveWorkspace","ConnectionStatusBadge","IntegrationIcon","useIntegrationConnectionsQuery","useChrome","QueryLoadError","Button","Callout","EmptyState","Icon","Input","Panel","PanelActions","PanelBody","PanelCell","PanelCellAction","PanelGrid","PanelHeader","Skeleton","Text","Link","useNavigate","ModelProviderReminderBanner","useProjectsInfiniteQuery","ProjectsHubPage","search","workspace","WorkspaceSetupChecklist","navigate","query","id","undefined","projects","data","pages","flatMap","page","connectionsQuery","length","connectionsById","Map","map","connection","isInitialLoading","isPending","isSearching","Boolean","isFetching","isFetchingNextPage","hasNoData","div","className","workspaceId","section","aria-label","type","value","onChange","event","next","target","trim","replace","placeholder","iconLeft","name","iconRight","size","aria-hidden","asChild","to","params","workspaceSlug","slug","ProjectsSkeleton","isError","subject","variant","EmptyProjects","NoSearchResults","onClear","project","ProjectCell","get","source","connectionId","connectionsResolved","isSuccess","connectionsSettled","error","role","hasNextPage","isLoading","onClick","fetchNextPage","row","icon","title","description","action","status","lifecycleStatus","projectSlug","provider","span","as","bold"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,uBAAuB;AACxD,SACEC,qBAAqB,EAErBC,eAAe,EACfC,8BAA8B,QACzB,+BAA+B;AACtC,SAAQC,SAAS,QAAO,gCAAgC;AACxD,SAAQC,cAAc,QAAO,qBAAqB;AAClD,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SACEC,KAAK,EACLC,YAAY,EACZC,SAAS,EACTC,SAAS,EACTC,eAAe,EACfC,SAAS,EACTC,WAAW,QACN,0BAA0B;AACjC,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,IAAI,QAAO,+BAA+B;AAClD,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,YAAY1B;IAClB,MAAM,EAAC2B,uBAAuB,EAAC,GAAGvB;IAClC,MAAMwB,WAAWP;IACjB,MAAMQ,QAAQN,yBAAyBG,UAAUI,EAAE,EAAEL,UAAUM;IAC/D,MAAMC,WAAWH,MAAMI,IAAI,EAAEC,MAAMC,QAAQ,CAACC,OAASA,KAAKJ,QAAQ,KAAK,EAAE;IAEzE,8EAA8E;IAC9E,4EAA4E;IAC5E,sDAAsD;IACtD,MAAMK,mBAAmBlC,+BACvB6B,SAASM,MAAM,GAAG,IAAIZ,UAAUI,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,QAAQrB,WAAWI,MAAMkB,UAAU,IAAI,CAAClB,MAAMmB,kBAAkB;IACpF,MAAMC,YAAY,CAACpB,MAAMI,IAAI;IAE7B,qBACE,MAACiB;QAAIC,WAAU;;YACZxB,wCAA0B,KAACA,+BAA6B;0BACzD,KAACL;gBAA4B8B,aAAa1B,UAAUI,EAAE;;0BAEtD,KAACuB;gBAAQC,cAAW;0BAClB,cAAA,MAAC3C;;sCACC,MAACM;4BAAYkC,WAAU;;8CACrB,KAACD;oCAAIC,WAAU;8CACb,cAAA,KAACzC;wCACC6C,MAAK;wCACLC,OAAO/B;wCACPgC,UAAU,CAACC;4CACT,MAAMC,OAAOD,MAAME,MAAM,CAACJ,KAAK,CAACK,IAAI;4CACpCjC,SAAS;gDAACH,QAASkC,OAAO;oDAAClC,QAAQkC;gDAAI,IAAI,CAAC;gDAAaG,SAAS;4CAAI;wCACxE;wCACAC,aAAY;wCACZT,cAAW;wCACXU,wBAAU,KAACvD;4CAAKwD,MAAK;4CAAad,WAAU;;wCAC5Ce,WACErB,4BACE,KAACpC;4CACCwD,MAAK;4CACLE,MAAM;4CACNhB,WAAU;4CACViB,eAAY;6CAEZrC;;;8CAIV,KAACnB;oCAAauC,WAAU;8CACtB,cAAA,KAAC7C;wCAAO+D,OAAO;wCAACL,UAAS;wCAAUb,WAAU;kDAC3C,cAAA,KAAC/B;4CAAKkD,IAAG;4CAAiCC,QAAQ;gDAACC,eAAe9C,UAAU+C,IAAI;4CAAA;sDAAG;;;;;;sCAOzF,MAAC5D;;gCACE8B,oBAAqBlB,UAAUwB,aAAapB,MAAMkB,UAAU,iBAC3D,KAAC2B,wBACC;gCAEH7C,MAAM8C,OAAO,IAAI1B,0BAChB,KAAC5C;oCAAewB,OAAOA;oCAAO+C,SAAQ;oCAAWC,SAAQ;qCACvD;gCAEH,CAAClC,oBAAoB,CAACd,MAAM8C,OAAO,IAAI3C,SAASM,MAAM,KAAK,KAAK,CAACb,uBAChE,KAACqD;oCAAcN,eAAe9C,UAAU+C,IAAI;qCAC1C;gCAEH,CAAC5C,MAAMkB,UAAU,IAAI,CAAClB,MAAM8C,OAAO,IAAI3C,SAASM,MAAM,KAAK,KAAKb,uBAC/D,KAACsD;oCACCtD,QAAQA;oCACRuD,SAAS,IAAMpD,SAAS;4CAACH,QAAQ,CAAC;4CAAYqC,SAAS;wCAAI;qCAE3D;gCAEH9B,SAASM,MAAM,GAAG,kBACjB;;sDACE,KAACtB;4CAAUsC,cAAW;sDACnBtB,SAASS,GAAG,CAAC,CAACwC,wBACb,KAACC;oDACCD,SAASA;oDACTvC,YAAYH,gBAAgB4C,GAAG,CAACF,QAAQG,MAAM,CAACC,YAAY;oDAC3DC,qBAAqBjD,iBAAiBkD,SAAS;oDAC/CC,oBAAoBnD,iBAAiBkD,SAAS,IAAIlD,iBAAiBsC,OAAO;oDAE1EH,eAAe9C,UAAU+C,IAAI;mDADxBQ,QAAQnD,EAAE;;wCAKpBD,MAAM4D,KAAK,IAAI5D,MAAMI,IAAI,iBACxB,KAACiB;4CAAIC,WAAU;sDACb,cAAA,KAAC5C;gDAAQmF,MAAK;gDAAQnC,MAAK;0DACzB,cAAA,KAACpC;oDAAKgD,MAAK;8DAAK;;;6CAKlB;wCACHtC,MAAM8D,WAAW,iBAChB,KAACzC;4CAAIC,WAAU;sDACb,cAAA,KAAC7C;gDACCuE,SAAQ;gDACRe,WAAW/D,MAAMmB,kBAAkB;gDACnC6C,SAAS,IAAMhE,MAAMiE,aAAa;0DACnC;;6CAID;;qCAEJ;;;;;;;;AAMhB;AAEA,SAASpB;IACP,qBACE,KAAC1D;QAAU0E,MAAK;QAASpC,cAAW;kBACjC;YAAC;YAAG;YAAG;YAAG;YAAG;YAAG;SAAE,CAACb,GAAG,CAAC,CAACsD,oBACvB,KAACjF;0BACC,cAAA,MAACoC;oBAAIC,WAAU;;sCACb,KAACjC;4BAASiC,WAAU;;sCACpB,KAACjC;4BAASiC,WAAU;;;;eAHR4C;;AASxB;AAEA,SAASjB,cAAc,EAACN,aAAa,EAA0B;IAC7D,qBACE,KAAChE;QACCwF,MAAK;QACLC,OAAM;QACNC,aAAY;QACZrB,SAAQ;QACRsB,sBACE,KAAC7F;YAAO+D,OAAO;YAACH,WAAU;sBACxB,cAAA,KAAC9C;gBAAKkD,IAAG;gBAAiCC,QAAQ;oBAACC;gBAAa;0BAAG;;;;AAO7E;AAEA,SAASO,gBAAgB,EAACtD,MAAM,EAAEuD,OAAO,EAAwC;IAC/E,qBACE,KAACxE;QACCwF,MAAK;QACLC,OAAO,CAAC,mBAAmB,EAAExE,OAAO,CAAC,CAAC;QACtCyE,aAAY;QACZrB,SAAQ;QACRsB,sBACE,KAAC7F;YAAO6D,MAAK;YAAKU,SAAQ;YAAYgB,SAASb;sBAAS;;;AAMhE;AAEA,SAASE,YAAY,EACnBD,OAAO,EACPvC,UAAU,EACV4C,mBAAmB,EACnBE,kBAAkB,EAClBhB,aAAa,EAOd;IACC,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,MAAM4B,SAASd,sBAAuB5C,YAAY2D,mBAAmB,UAAWtE;IAEhF,qBACE,KAACjB;kBACC,cAAA,KAACC;YAAgBsD,OAAO;sBACtB,cAAA,MAACjD;gBACCkD,IAAG;gBACHC,QAAQ;oBAACC;oBAAe8B,aAAarB,QAAQR,IAAI;gBAAA;;oBAIhDe,mCACC,KAACtF;wBACCkF,QAAQ1C,YAAY6D;wBACpBnC,aAAW;wBACXjB,WAAU;uCAGZ,KAACjC;wBAASiC,WAAU;;kCAEtB,MAACqD;wBAAKrD,WAAU;;0CACd,KAAChC;gCAAKsF,IAAG;gCAAOtC,MAAK;gCAAKuC,IAAI;gCAACvD,WAAU;0CACtC8B,QAAQhB,IAAI;;4BAEdmC,uBAAS,KAACnG;gCAAsBmG,QAAQA;gCAAQjD,WAAU;iCAAgB;;;;;;;AAMvF"}
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 {useChrome} from '@shipfox/client-shell/runtime';\nimport {QueryLoadError} 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 {Icon} from '@shipfox/react-ui/icon';\nimport {Input} from '@shipfox/react-ui/input';\nimport {\n Panel,\n PanelActions,\n PanelBody,\n PanelCell,\n PanelCellAction,\n PanelGrid,\n PanelHeader,\n} from '@shipfox/react-ui/panel';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {Link, useNavigate} from '@tanstack/react-router';\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 {WorkspaceSetupChecklist} = useChrome();\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 {WorkspaceSetupChecklist ? <WorkspaceSetupChecklist /> : null}\n\n <section aria-label=\"Projects\">\n <Panel>\n <PanelHeader className=\"flex-wrap max-[640px]:items-stretch\">\n <div className=\"min-w-0 flex-1 max-[640px]:basis-full\">\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 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 </div>\n <PanelActions className=\"max-[640px]:ml-0 max-[640px]:w-full\">\n <Button asChild iconLeft=\"addLine\" className=\"max-[640px]:w-full\">\n <Link to=\"/w/$workspaceSlug/projects/new\" params={{workspaceSlug: workspace.slug}}>\n New project\n </Link>\n </Button>\n </PanelActions>\n </PanelHeader>\n\n <PanelBody>\n {isInitialLoading || (search && hasNoData && query.isFetching) ? (\n <ProjectsSkeleton />\n ) : null}\n\n {query.isError && hasNoData ? (\n <QueryLoadError query={query} subject=\"projects\" variant=\"panel\" />\n ) : 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 <>\n <PanelGrid aria-label=\"Projects list\">\n {projects.map((project) => (\n <ProjectCell\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 </PanelGrid>\n {query.error && query.data ? (\n <div className=\"border-t border-border-neutral-base p-panel-compact\">\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 </div>\n ) : null}\n {query.hasNextPage ? (\n <div className=\"flex justify-center border-t border-border-neutral-base p-panel-compact\">\n <Button\n variant=\"secondary\"\n isLoading={query.isFetchingNextPage}\n onClick={() => query.fetchNextPage()}\n >\n Load more\n </Button>\n </div>\n ) : null}\n </>\n ) : null}\n </PanelBody>\n </Panel>\n </section>\n </div>\n );\n}\n\nfunction ProjectsSkeleton() {\n return (\n <PanelGrid role=\"status\" aria-label=\"Loading projects\">\n {[0, 1, 2, 3, 4, 5].map((row) => (\n <PanelCell key={row}>\n <div className=\"flex items-center gap-cluster px-row py-row\">\n <Skeleton className=\"size-24 shrink-0\" />\n <Skeleton className=\"h-16 w-1/2\" />\n </div>\n </PanelCell>\n ))}\n </PanelGrid>\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 variant=\"panel\"\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 variant=\"panel\"\n action={\n <Button size=\"sm\" variant=\"secondary\" onClick={onClear}>\n Clear search\n </Button>\n }\n />\n );\n}\n\nfunction ProjectCell({\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 <PanelCell>\n <PanelCellAction asChild>\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug\"\n params={{workspaceSlug, projectSlug: project.slug}}\n >\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 <span className=\"flex min-w-0 flex-1 items-center gap-inline\">\n <Text as=\"span\" size=\"md\" bold className=\"truncate\">\n {project.name}\n </Text>\n {status ? <ConnectionStatusBadge status={status} className=\"shrink-0\" /> : null}\n </span>\n </Link>\n </PanelCellAction>\n </PanelCell>\n );\n}\n"],"names":["useActiveWorkspace","ConnectionStatusBadge","IntegrationIcon","useIntegrationConnectionsQuery","useChrome","QueryLoadError","Button","Callout","EmptyState","Icon","Input","Panel","PanelActions","PanelBody","PanelCell","PanelCellAction","PanelGrid","PanelHeader","Skeleton","Text","Link","useNavigate","useProjectsInfiniteQuery","ProjectsHubPage","search","workspace","WorkspaceSetupChecklist","navigate","query","id","undefined","projects","data","pages","flatMap","page","connectionsQuery","length","connectionsById","Map","map","connection","isInitialLoading","isPending","isSearching","Boolean","isFetching","isFetchingNextPage","hasNoData","div","className","section","aria-label","type","value","onChange","event","next","target","trim","replace","placeholder","iconLeft","name","iconRight","size","aria-hidden","asChild","to","params","workspaceSlug","slug","ProjectsSkeleton","isError","subject","variant","EmptyProjects","NoSearchResults","onClear","project","ProjectCell","get","source","connectionId","connectionsResolved","isSuccess","connectionsSettled","error","role","hasNextPage","isLoading","onClick","fetchNextPage","row","icon","title","description","action","status","lifecycleStatus","projectSlug","provider","span","as","bold"],"mappings":"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"}