@shipfox/client-projects 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +1 -0
- package/.storybook/preview.css +7 -0
- package/.storybook/preview.tsx +56 -0
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +207 -0
- package/LICENSE +21 -0
- package/dist/chrome.d.ts +3 -0
- package/dist/chrome.d.ts.map +1 -0
- package/dist/chrome.js +42 -0
- package/dist/chrome.js.map +1 -0
- package/dist/components/model-provider-reminder-banner.d.ts +4 -0
- package/dist/components/model-provider-reminder-banner.d.ts.map +1 -0
- package/dist/components/model-provider-reminder-banner.js +73 -0
- package/dist/components/model-provider-reminder-banner.js.map +1 -0
- package/dist/components/project-crumb.d.ts +7 -0
- package/dist/components/project-crumb.d.ts.map +1 -0
- package/dist/components/project-crumb.js +93 -0
- package/dist/components/project-crumb.js.map +1 -0
- package/dist/components/project-switcher.d.ts +7 -0
- package/dist/components/project-switcher.d.ts.map +1 -0
- package/dist/components/project-switcher.js +138 -0
- package/dist/components/project-switcher.js.map +1 -0
- package/dist/components/source-strip.d.ts +14 -0
- package/dist/components/source-strip.d.ts.map +1 -0
- package/dist/components/source-strip.js +98 -0
- package/dist/components/source-strip.js.map +1 -0
- package/dist/components/workspace-setup-guard.d.ts +9 -0
- package/dist/components/workspace-setup-guard.d.ts.map +1 -0
- package/dist/components/workspace-setup-guard.js +160 -0
- package/dist/components/workspace-setup-guard.js.map +1 -0
- package/dist/feature.d.ts +35 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +45 -0
- package/dist/feature.js.map +1 -0
- package/dist/hooks/api/definitions.d.ts +97 -0
- package/dist/hooks/api/definitions.d.ts.map +1 -0
- package/dist/hooks/api/definitions.js +55 -0
- package/dist/hooks/api/definitions.js.map +1 -0
- package/dist/hooks/api/projects.d.ts +93 -0
- package/dist/hooks/api/projects.d.ts.map +1 -0
- package/dist/hooks/api/projects.js +79 -0
- package/dist/hooks/api/projects.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/create-project-page.d.ts +2 -0
- package/dist/pages/create-project-page.d.ts.map +1 -0
- package/dist/pages/create-project-page.js +406 -0
- package/dist/pages/create-project-page.js.map +1 -0
- package/dist/pages/create-project-page.stories.d.ts +24 -0
- package/dist/pages/create-project-page.stories.d.ts.map +1 -0
- package/dist/pages/create-project-page.stories.js +345 -0
- package/dist/pages/create-project-page.stories.js.map +1 -0
- package/dist/pages/home-router.d.ts +2 -0
- package/dist/pages/home-router.d.ts.map +1 -0
- package/dist/pages/home-router.js +7 -0
- package/dist/pages/home-router.js.map +1 -0
- package/dist/pages/project-workflows-page.d.ts +4 -0
- package/dist/pages/project-workflows-page.d.ts.map +1 -0
- package/dist/pages/project-workflows-page.js +481 -0
- package/dist/pages/project-workflows-page.js.map +1 -0
- package/dist/pages/projects-hub-page.d.ts +2 -0
- package/dist/pages/projects-hub-page.d.ts.map +1 -0
- package/dist/pages/projects-hub-page.js +256 -0
- package/dist/pages/projects-hub-page.js.map +1 -0
- package/dist/project-error.d.ts +7 -0
- package/dist/project-error.d.ts.map +1 -0
- package/dist/project-error.js +93 -0
- package/dist/project-error.js.map +1 -0
- package/dist/routes/create-project.d.ts +6 -0
- package/dist/routes/create-project.d.ts.map +1 -0
- package/dist/routes/create-project.js +7 -0
- package/dist/routes/create-project.js.map +1 -0
- package/dist/routes/home.d.ts +6 -0
- package/dist/routes/home.d.ts.map +1 -0
- package/dist/routes/home.js +7 -0
- package/dist/routes/home.js.map +1 -0
- package/dist/routes/project-index.d.ts +10 -0
- package/dist/routes/project-index.d.ts.map +1 -0
- package/dist/routes/project-index.js +15 -0
- package/dist/routes/project-index.js.map +1 -0
- package/dist/routes/workflows.d.ts +5 -0
- package/dist/routes/workflows.d.ts.map +1 -0
- package/dist/routes/workflows.js +16 -0
- package/dist/routes/workflows.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +120 -0
- package/src/chrome.tsx +26 -0
- package/src/components/model-provider-reminder-banner.tsx +78 -0
- package/src/components/project-crumb.tsx +70 -0
- package/src/components/project-switcher.test.tsx +91 -0
- package/src/components/project-switcher.tsx +127 -0
- package/src/components/source-strip.tsx +91 -0
- package/src/components/workspace-setup-guard.test.tsx +534 -0
- package/src/components/workspace-setup-guard.tsx +185 -0
- package/src/feature.ts +44 -0
- package/src/hooks/api/definitions.ts +50 -0
- package/src/hooks/api/projects.test.ts +78 -0
- package/src/hooks/api/projects.ts +75 -0
- package/src/index.ts +15 -0
- package/src/pages/create-project-page.stories.tsx +344 -0
- package/src/pages/create-project-page.test.tsx +317 -0
- package/src/pages/create-project-page.tsx +386 -0
- package/src/pages/home-router.test.tsx +27 -0
- package/src/pages/home-router.tsx +5 -0
- package/src/pages/project-workflows-page.test.tsx +258 -0
- package/src/pages/project-workflows-page.tsx +445 -0
- package/src/pages/projects-hub-page.test.tsx +313 -0
- package/src/pages/projects-hub-page.tsx +246 -0
- package/src/project-error.test.ts +59 -0
- package/src/project-error.ts +106 -0
- package/src/routes/create-project.tsx +4 -0
- package/src/routes/home.tsx +4 -0
- package/src/routes/project-index.tsx +11 -0
- package/src/routes/workflows.tsx +10 -0
- package/test/pages.tsx +151 -0
- package/test/setup.ts +3 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +70 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@shipfox/react-ui/command';
|
|
3
|
+
import { Icon } from '@shipfox/react-ui/icon';
|
|
4
|
+
import { useNavigate } from '@tanstack/react-router';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { useProjectsInfiniteQuery } from '#hooks/api/projects.js';
|
|
7
|
+
export function ProjectSwitcher({ workspaceId, activeProjectId, onSelect }) {
|
|
8
|
+
const navigate = useNavigate();
|
|
9
|
+
const [searchValue, setSearchValue] = useState('');
|
|
10
|
+
const query = useProjectsInfiniteQuery(workspaceId);
|
|
11
|
+
const projects = query.data?.pages.flatMap((page)=>page.projects) ?? [];
|
|
12
|
+
// Eagerly fetch additional pages once the popover opens; the switcher should
|
|
13
|
+
// show the full list, not just the first page.
|
|
14
|
+
useEffect(()=>{
|
|
15
|
+
if (query.hasNextPage && !query.isFetchingNextPage) {
|
|
16
|
+
query.fetchNextPage();
|
|
17
|
+
}
|
|
18
|
+
}, [
|
|
19
|
+
query.hasNextPage,
|
|
20
|
+
query.isFetchingNextPage,
|
|
21
|
+
query.fetchNextPage
|
|
22
|
+
]);
|
|
23
|
+
const handleSelect = (projectId)=>{
|
|
24
|
+
navigate({
|
|
25
|
+
to: '/workspaces/$wid/projects/$pid',
|
|
26
|
+
params: {
|
|
27
|
+
wid: workspaceId,
|
|
28
|
+
pid: projectId
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
onSelect?.();
|
|
32
|
+
};
|
|
33
|
+
const handleSelectAll = ()=>{
|
|
34
|
+
navigate({
|
|
35
|
+
to: '/workspaces/$wid',
|
|
36
|
+
params: {
|
|
37
|
+
wid: workspaceId
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
onSelect?.();
|
|
41
|
+
};
|
|
42
|
+
const handleCreate = ()=>{
|
|
43
|
+
navigate({
|
|
44
|
+
to: '/workspaces/$wid/projects/new',
|
|
45
|
+
params: {
|
|
46
|
+
wid: workspaceId
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
onSelect?.();
|
|
50
|
+
};
|
|
51
|
+
const normalizedSearch = searchValue.trim().toLowerCase();
|
|
52
|
+
const hasSearchMatch = normalizedSearch.length === 0 || 'all projects'.includes(normalizedSearch) || projects.some((project)=>[
|
|
53
|
+
project.id,
|
|
54
|
+
project.name
|
|
55
|
+
].some((value)=>value.toLowerCase().includes(normalizedSearch)));
|
|
56
|
+
const shouldCreateFromEmptySearch = !query.isLoading && !query.isError && !hasSearchMatch;
|
|
57
|
+
const handleCommandKeyDown = (event)=>{
|
|
58
|
+
if (event.key !== 'Enter' || !shouldCreateFromEmptySearch) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
handleCreate();
|
|
63
|
+
};
|
|
64
|
+
return /*#__PURE__*/ _jsxs(Command, {
|
|
65
|
+
onKeyDown: handleCommandKeyDown,
|
|
66
|
+
children: [
|
|
67
|
+
/*#__PURE__*/ _jsx(CommandInput, {
|
|
68
|
+
placeholder: "Search projects...",
|
|
69
|
+
value: searchValue,
|
|
70
|
+
onValueChange: setSearchValue
|
|
71
|
+
}),
|
|
72
|
+
/*#__PURE__*/ _jsxs(CommandList, {
|
|
73
|
+
children: [
|
|
74
|
+
query.isError ? /*#__PURE__*/ _jsx(CommandEmpty, {
|
|
75
|
+
children: "Couldn't load projects."
|
|
76
|
+
}) : query.isLoading ? /*#__PURE__*/ _jsx(CommandEmpty, {
|
|
77
|
+
children: "Loading projects..."
|
|
78
|
+
}) : projects.length === 0 ? /*#__PURE__*/ _jsx(CommandEmpty, {
|
|
79
|
+
children: "No projects yet."
|
|
80
|
+
}) : /*#__PURE__*/ _jsx(CommandEmpty, {
|
|
81
|
+
children: "No projects found."
|
|
82
|
+
}),
|
|
83
|
+
/*#__PURE__*/ _jsx(CommandGroup, {
|
|
84
|
+
children: /*#__PURE__*/ _jsxs(CommandItem, {
|
|
85
|
+
value: "__all",
|
|
86
|
+
keywords: [
|
|
87
|
+
'all projects'
|
|
88
|
+
],
|
|
89
|
+
onSelect: handleSelectAll,
|
|
90
|
+
children: [
|
|
91
|
+
/*#__PURE__*/ _jsx(Icon, {
|
|
92
|
+
name: "check",
|
|
93
|
+
className: `size-16 mr-8 ${activeProjectId ? 'opacity-0' : 'opacity-100'}`
|
|
94
|
+
}),
|
|
95
|
+
"All projects"
|
|
96
|
+
]
|
|
97
|
+
})
|
|
98
|
+
}),
|
|
99
|
+
projects.length > 0 ? /*#__PURE__*/ _jsx(CommandGroup, {
|
|
100
|
+
heading: "Projects",
|
|
101
|
+
children: projects.map((project)=>/*#__PURE__*/ _jsxs(CommandItem, {
|
|
102
|
+
value: project.id,
|
|
103
|
+
keywords: [
|
|
104
|
+
project.name
|
|
105
|
+
],
|
|
106
|
+
onSelect: ()=>handleSelect(project.id),
|
|
107
|
+
children: [
|
|
108
|
+
/*#__PURE__*/ _jsx(Icon, {
|
|
109
|
+
name: "check",
|
|
110
|
+
className: `size-16 mr-8 ${activeProjectId === project.id ? 'opacity-100' : 'opacity-0'}`
|
|
111
|
+
}),
|
|
112
|
+
project.name
|
|
113
|
+
]
|
|
114
|
+
}, project.id))
|
|
115
|
+
}) : null
|
|
116
|
+
]
|
|
117
|
+
}),
|
|
118
|
+
/*#__PURE__*/ _jsx(CommandSeparator, {}),
|
|
119
|
+
/*#__PURE__*/ _jsx(CommandGroup, {
|
|
120
|
+
forceMount: true,
|
|
121
|
+
children: /*#__PURE__*/ _jsxs(CommandItem, {
|
|
122
|
+
value: "__create",
|
|
123
|
+
onSelect: handleCreate,
|
|
124
|
+
forceMount: true,
|
|
125
|
+
children: [
|
|
126
|
+
/*#__PURE__*/ _jsx(Icon, {
|
|
127
|
+
name: "addLine",
|
|
128
|
+
className: "size-16"
|
|
129
|
+
}),
|
|
130
|
+
"Create project"
|
|
131
|
+
]
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
]
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
//# sourceMappingURL=project-switcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/project-switcher.tsx"],"sourcesContent":["import {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@shipfox/react-ui/command';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {useNavigate} from '@tanstack/react-router';\nimport {type KeyboardEvent, useEffect, useState} from 'react';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport interface ProjectSwitcherProps {\n workspaceId: string;\n activeProjectId?: string | undefined;\n onSelect?: () => void;\n}\n\nexport function ProjectSwitcher({workspaceId, activeProjectId, onSelect}: ProjectSwitcherProps) {\n const navigate = useNavigate();\n const [searchValue, setSearchValue] = useState('');\n const query = useProjectsInfiniteQuery(workspaceId);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // Eagerly fetch additional pages once the popover opens; the switcher should\n // show the full list, not just the first page.\n useEffect(() => {\n if (query.hasNextPage && !query.isFetchingNextPage) {\n query.fetchNextPage();\n }\n }, [query.hasNextPage, query.isFetchingNextPage, query.fetchNextPage]);\n\n const handleSelect = (projectId: string) => {\n navigate({\n to: '/workspaces/$wid/projects/$pid',\n params: {wid: workspaceId, pid: projectId},\n });\n onSelect?.();\n };\n\n const handleSelectAll = () => {\n navigate({to: '/workspaces/$wid', params: {wid: workspaceId}});\n onSelect?.();\n };\n\n const handleCreate = () => {\n navigate({to: '/workspaces/$wid/projects/new', params: {wid: workspaceId}});\n onSelect?.();\n };\n\n const normalizedSearch = searchValue.trim().toLowerCase();\n const hasSearchMatch =\n normalizedSearch.length === 0 ||\n 'all projects'.includes(normalizedSearch) ||\n projects.some((project) =>\n [project.id, project.name].some((value) => value.toLowerCase().includes(normalizedSearch)),\n );\n const shouldCreateFromEmptySearch = !query.isLoading && !query.isError && !hasSearchMatch;\n\n const handleCommandKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Enter' || !shouldCreateFromEmptySearch) {\n return;\n }\n\n event.preventDefault();\n handleCreate();\n };\n\n return (\n <Command onKeyDown={handleCommandKeyDown}>\n <CommandInput\n placeholder=\"Search projects...\"\n value={searchValue}\n onValueChange={setSearchValue}\n />\n <CommandList>\n {query.isError ? (\n <CommandEmpty>Couldn't load projects.</CommandEmpty>\n ) : query.isLoading ? (\n <CommandEmpty>Loading projects...</CommandEmpty>\n ) : projects.length === 0 ? (\n <CommandEmpty>No projects yet.</CommandEmpty>\n ) : (\n <CommandEmpty>No projects found.</CommandEmpty>\n )}\n <CommandGroup>\n <CommandItem value=\"__all\" keywords={['all projects']} onSelect={handleSelectAll}>\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${activeProjectId ? 'opacity-0' : 'opacity-100'}`}\n />\n All projects\n </CommandItem>\n </CommandGroup>\n {projects.length > 0 ? (\n <CommandGroup heading=\"Projects\">\n {projects.map((project) => (\n <CommandItem\n key={project.id}\n value={project.id}\n keywords={[project.name]}\n onSelect={() => handleSelect(project.id)}\n >\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${\n activeProjectId === project.id ? 'opacity-100' : 'opacity-0'\n }`}\n />\n {project.name}\n </CommandItem>\n ))}\n </CommandGroup>\n ) : null}\n </CommandList>\n <CommandSeparator />\n <CommandGroup forceMount>\n <CommandItem value=\"__create\" onSelect={handleCreate} forceMount>\n <Icon name=\"addLine\" className=\"size-16\" />\n Create project\n </CommandItem>\n </CommandGroup>\n </Command>\n );\n}\n"],"names":["Command","CommandEmpty","CommandGroup","CommandInput","CommandItem","CommandList","CommandSeparator","Icon","useNavigate","useEffect","useState","useProjectsInfiniteQuery","ProjectSwitcher","workspaceId","activeProjectId","onSelect","navigate","searchValue","setSearchValue","query","projects","data","pages","flatMap","page","hasNextPage","isFetchingNextPage","fetchNextPage","handleSelect","projectId","to","params","wid","pid","handleSelectAll","handleCreate","normalizedSearch","trim","toLowerCase","hasSearchMatch","length","includes","some","project","id","name","value","shouldCreateFromEmptySearch","isLoading","isError","handleCommandKeyDown","event","key","preventDefault","onKeyDown","placeholder","onValueChange","keywords","className","heading","map","forceMount"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DefinitionSyncSummaryDto } from '@shipfox/api-definitions-dto';
|
|
2
|
+
/**
|
|
3
|
+
* We cannot cheaply resolve `external_repository_id` → `owner/repo`
|
|
4
|
+
* (no by-id endpoint; `listRepositories` is paginated by connection).
|
|
5
|
+
* The strip surfaces the raw id as a `<Code>` chip with a tooltip carrying the
|
|
6
|
+
* full string.
|
|
7
|
+
*/
|
|
8
|
+
export declare function SourceStrip({ connectionId, externalRepositoryId, sync, isPending, }: {
|
|
9
|
+
connectionId: string;
|
|
10
|
+
externalRepositoryId: string;
|
|
11
|
+
sync: DefinitionSyncSummaryDto | null | undefined;
|
|
12
|
+
isPending: boolean;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=source-strip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-strip.d.ts","sourceRoot":"","sources":["../../src/components/source-strip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,8BAA8B,CAAC;AAS3E;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAC1B,YAAY,EACZ,oBAAoB,EACpB,IAAI,EACJ,SAAS,GACV,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,IAAI,EAAE,wBAAwB,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,SAAS,EAAE,OAAO,CAAC;CACpB,+BAmCA"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useActiveWorkspace } from '@shipfox/client-auth';
|
|
3
|
+
import { useSourceConnectionsQuery } from '@shipfox/client-integrations';
|
|
4
|
+
import { StatusBadge } from '@shipfox/react-ui/badge';
|
|
5
|
+
import { Icon } from '@shipfox/react-ui/icon';
|
|
6
|
+
import { Skeleton } from '@shipfox/react-ui/skeleton';
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '@shipfox/react-ui/tooltip';
|
|
8
|
+
import { Code, Text } from '@shipfox/react-ui/typography';
|
|
9
|
+
/**
|
|
10
|
+
* We cannot cheaply resolve `external_repository_id` → `owner/repo`
|
|
11
|
+
* (no by-id endpoint; `listRepositories` is paginated by connection).
|
|
12
|
+
* The strip surfaces the raw id as a `<Code>` chip with a tooltip carrying the
|
|
13
|
+
* full string.
|
|
14
|
+
*/ export function SourceStrip({ connectionId, externalRepositoryId, sync, isPending }) {
|
|
15
|
+
const workspace = useActiveWorkspace();
|
|
16
|
+
const connectionsQuery = useSourceConnectionsQuery(workspace.id);
|
|
17
|
+
const connection = connectionsQuery.data?.connections.find((c)=>c.id === connectionId);
|
|
18
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
19
|
+
className: "flex flex-col gap-8 rounded-8 border border-border-neutral-base bg-background-neutral-base px-14 py-10 sm:flex-row sm:items-center sm:justify-between",
|
|
20
|
+
"aria-label": "Project source",
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
23
|
+
className: "flex min-w-0 items-center gap-10",
|
|
24
|
+
children: [
|
|
25
|
+
/*#__PURE__*/ _jsx(Icon, {
|
|
26
|
+
name: providerIconName(connection?.provider),
|
|
27
|
+
className: "size-20 shrink-0"
|
|
28
|
+
}),
|
|
29
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
30
|
+
className: "flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center sm:gap-10",
|
|
31
|
+
children: [
|
|
32
|
+
connectionsQuery.isPending ? /*#__PURE__*/ _jsx(Skeleton, {
|
|
33
|
+
className: "h-16 w-160"
|
|
34
|
+
}) : /*#__PURE__*/ _jsx(Text, {
|
|
35
|
+
size: "sm",
|
|
36
|
+
bold: true,
|
|
37
|
+
className: "truncate",
|
|
38
|
+
children: connection?.display_name ?? 'Connected source'
|
|
39
|
+
}),
|
|
40
|
+
/*#__PURE__*/ _jsxs(Tooltip, {
|
|
41
|
+
children: [
|
|
42
|
+
/*#__PURE__*/ _jsx(TooltipTrigger, {
|
|
43
|
+
asChild: true,
|
|
44
|
+
children: /*#__PURE__*/ _jsx(Code, {
|
|
45
|
+
variant: "label",
|
|
46
|
+
className: "truncate text-foreground-neutral-muted",
|
|
47
|
+
children: externalRepositoryId
|
|
48
|
+
})
|
|
49
|
+
}),
|
|
50
|
+
/*#__PURE__*/ _jsx(TooltipContent, {
|
|
51
|
+
children: externalRepositoryId
|
|
52
|
+
})
|
|
53
|
+
]
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
}),
|
|
59
|
+
/*#__PURE__*/ _jsx("div", {
|
|
60
|
+
className: "shrink-0",
|
|
61
|
+
children: /*#__PURE__*/ _jsx(SyncBadge, {
|
|
62
|
+
sync: sync,
|
|
63
|
+
isPending: isPending
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function providerIconName(provider) {
|
|
70
|
+
if (provider === 'github') return 'github';
|
|
71
|
+
return 'componentLine';
|
|
72
|
+
}
|
|
73
|
+
function SyncBadge({ sync, isPending }) {
|
|
74
|
+
if (isPending) return /*#__PURE__*/ _jsx(StatusBadge, {
|
|
75
|
+
variant: "neutral",
|
|
76
|
+
children: "Loading"
|
|
77
|
+
});
|
|
78
|
+
if (sync === undefined) return /*#__PURE__*/ _jsx(StatusBadge, {
|
|
79
|
+
variant: "neutral",
|
|
80
|
+
children: "Unavailable"
|
|
81
|
+
});
|
|
82
|
+
if (sync === null) return /*#__PURE__*/ _jsx(StatusBadge, {
|
|
83
|
+
variant: "neutral",
|
|
84
|
+
children: "No sync"
|
|
85
|
+
});
|
|
86
|
+
const variantByStatus = {
|
|
87
|
+
pending: 'neutral',
|
|
88
|
+
syncing: 'info',
|
|
89
|
+
succeeded: 'success',
|
|
90
|
+
failed: 'error'
|
|
91
|
+
};
|
|
92
|
+
return /*#__PURE__*/ _jsx(StatusBadge, {
|
|
93
|
+
variant: variantByStatus[sync.status],
|
|
94
|
+
children: sync.status
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=source-strip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/source-strip.tsx"],"sourcesContent":["import type {DefinitionSyncSummaryDto} from '@shipfox/api-definitions-dto';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {useSourceConnectionsQuery} from '@shipfox/client-integrations';\nimport {StatusBadge} from '@shipfox/react-ui/badge';\nimport {Icon, type IconName} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\n\n/**\n * We cannot cheaply resolve `external_repository_id` → `owner/repo`\n * (no by-id endpoint; `listRepositories` is paginated by connection).\n * The strip surfaces the raw id as a `<Code>` chip with a tooltip carrying the\n * full string.\n */\nexport function SourceStrip({\n connectionId,\n externalRepositoryId,\n sync,\n isPending,\n}: {\n connectionId: string;\n externalRepositoryId: string;\n sync: DefinitionSyncSummaryDto | null | undefined;\n isPending: boolean;\n}) {\n const workspace = useActiveWorkspace();\n const connectionsQuery = useSourceConnectionsQuery(workspace.id);\n const connection = connectionsQuery.data?.connections.find((c) => c.id === connectionId);\n\n return (\n <section\n className=\"flex flex-col gap-8 rounded-8 border border-border-neutral-base bg-background-neutral-base px-14 py-10 sm:flex-row sm:items-center sm:justify-between\"\n aria-label=\"Project source\"\n >\n <div className=\"flex min-w-0 items-center gap-10\">\n <Icon name={providerIconName(connection?.provider)} className=\"size-20 shrink-0\" />\n <div className=\"flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center sm:gap-10\">\n {connectionsQuery.isPending ? (\n <Skeleton className=\"h-16 w-160\" />\n ) : (\n <Text size=\"sm\" bold className=\"truncate\">\n {connection?.display_name ?? 'Connected source'}\n </Text>\n )}\n <Tooltip>\n <TooltipTrigger asChild>\n <Code variant=\"label\" className=\"truncate text-foreground-neutral-muted\">\n {externalRepositoryId}\n </Code>\n </TooltipTrigger>\n <TooltipContent>{externalRepositoryId}</TooltipContent>\n </Tooltip>\n </div>\n </div>\n <div className=\"shrink-0\">\n <SyncBadge sync={sync} isPending={isPending} />\n </div>\n </section>\n );\n}\n\nfunction providerIconName(provider: string | undefined): IconName {\n if (provider === 'github') return 'github' as IconName;\n return 'componentLine' as IconName;\n}\n\nfunction SyncBadge({\n sync,\n isPending,\n}: {\n sync: DefinitionSyncSummaryDto | null | undefined;\n isPending: boolean;\n}) {\n if (isPending) return <StatusBadge variant=\"neutral\">Loading</StatusBadge>;\n if (sync === undefined) return <StatusBadge variant=\"neutral\">Unavailable</StatusBadge>;\n if (sync === null) return <StatusBadge variant=\"neutral\">No sync</StatusBadge>;\n\n const variantByStatus = {\n pending: 'neutral',\n syncing: 'info',\n succeeded: 'success',\n failed: 'error',\n } as const;\n\n return (\n <StatusBadge variant={variantByStatus[sync.status as keyof typeof variantByStatus]}>\n {sync.status}\n </StatusBadge>\n );\n}\n"],"names":["useActiveWorkspace","useSourceConnectionsQuery","StatusBadge","Icon","Skeleton","Tooltip","TooltipContent","TooltipTrigger","Code","Text","SourceStrip","connectionId","externalRepositoryId","sync","isPending","workspace","connectionsQuery","id","connection","data","connections","find","c","section","className","aria-label","div","name","providerIconName","provider","size","bold","display_name","asChild","variant","SyncBadge","undefined","variantByStatus","pending","syncing","succeeded","failed","status"],"mappings":";AACA,SAAQA,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,yBAAyB,QAAO,+BAA+B;AACvE,SAAQC,WAAW,QAAO,0BAA0B;AACpD,SAAQC,IAAI,QAAsB,yBAAyB;AAC3D,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,EAAEC,IAAI,QAAO,+BAA+B;AAExD;;;;;CAKC,GACD,OAAO,SAASC,YAAY,EAC1BC,YAAY,EACZC,oBAAoB,EACpBC,IAAI,EACJC,SAAS,EAMV;IACC,MAAMC,YAAYf;IAClB,MAAMgB,mBAAmBf,0BAA0Bc,UAAUE,EAAE;IAC/D,MAAMC,aAAaF,iBAAiBG,IAAI,EAAEC,YAAYC,KAAK,CAACC,IAAMA,EAAEL,EAAE,KAAKN;IAE3E,qBACE,MAACY;QACCC,WAAU;QACVC,cAAW;;0BAEX,MAACC;gBAAIF,WAAU;;kCACb,KAACrB;wBAAKwB,MAAMC,iBAAiBV,YAAYW;wBAAWL,WAAU;;kCAC9D,MAACE;wBAAIF,WAAU;;4BACZR,iBAAiBF,SAAS,iBACzB,KAACV;gCAASoB,WAAU;+CAEpB,KAACf;gCAAKqB,MAAK;gCAAKC,IAAI;gCAACP,WAAU;0CAC5BN,YAAYc,gBAAgB;;0CAGjC,MAAC3B;;kDACC,KAACE;wCAAe0B,OAAO;kDACrB,cAAA,KAACzB;4CAAK0B,SAAQ;4CAAQV,WAAU;sDAC7BZ;;;kDAGL,KAACN;kDAAgBM;;;;;;;;0BAIvB,KAACc;gBAAIF,WAAU;0BACb,cAAA,KAACW;oBAAUtB,MAAMA;oBAAMC,WAAWA;;;;;AAI1C;AAEA,SAASc,iBAAiBC,QAA4B;IACpD,IAAIA,aAAa,UAAU,OAAO;IAClC,OAAO;AACT;AAEA,SAASM,UAAU,EACjBtB,IAAI,EACJC,SAAS,EAIV;IACC,IAAIA,WAAW,qBAAO,KAACZ;QAAYgC,SAAQ;kBAAU;;IACrD,IAAIrB,SAASuB,WAAW,qBAAO,KAAClC;QAAYgC,SAAQ;kBAAU;;IAC9D,IAAIrB,SAAS,MAAM,qBAAO,KAACX;QAAYgC,SAAQ;kBAAU;;IAEzD,MAAMG,kBAAkB;QACtBC,SAAS;QACTC,SAAS;QACTC,WAAW;QACXC,QAAQ;IACV;IAEA,qBACE,KAACvC;QAAYgC,SAASG,eAAe,CAACxB,KAAK6B,MAAM,CAAiC;kBAC/E7B,KAAK6B,MAAM;;AAGlB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type WorkspaceSetupState } from '@shipfox/client-shell/runtime';
|
|
2
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
3
|
+
export interface WorkspaceSetupRouteOptions {
|
|
4
|
+
queryClient: QueryClient;
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
pathname: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function loadWorkspaceSetupRoute({ queryClient, workspaceId, pathname, }: WorkspaceSetupRouteOptions): Promise<WorkspaceSetupState>;
|
|
9
|
+
//# sourceMappingURL=workspace-setup-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-setup-guard.d.ts","sourceRoot":"","sources":["../../src/components/workspace-setup-guard.tsx"],"names":[],"mappings":"AAQA,OAAO,EAA0B,KAAK,mBAAmB,EAAC,MAAM,+BAA+B,CAAC;AAChG,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAQvD,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,uBAAuB,CAAC,EAC5C,WAAW,EACX,WAAW,EACX,QAAQ,GACT,EAAE,0BAA0B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA0D3D"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { isModelProviderOnboardingDismissed, listModelProviderConfigs, modelProviderQueryKeys } from '@shipfox/client-agent';
|
|
2
|
+
import { integrationsQueryKeys, listSourceConnections } from '@shipfox/client-integrations';
|
|
3
|
+
import { WorkspaceSetupLoadError } from '@shipfox/client-shell/runtime';
|
|
4
|
+
import { redirect } from '@tanstack/react-router';
|
|
5
|
+
import { listProjects, projectsQueryKeys } from '#hooks/api/projects.js';
|
|
6
|
+
const TRAILING_SLASHES_RE = /\/+$/u;
|
|
7
|
+
const PROJECT_EXISTENCE_STALE_TIME_MS = 30_000;
|
|
8
|
+
export async function loadWorkspaceSetupRoute({ queryClient, workspaceId, pathname }) {
|
|
9
|
+
const projects = await fetchWorkspaceProjectExistence(queryClient, workspaceId);
|
|
10
|
+
const normalizedPathname = normalizePath(pathname);
|
|
11
|
+
const hasProject = projects.projects.length > 0;
|
|
12
|
+
if (hasProject) {
|
|
13
|
+
if (isIntegrationsIndexPath(normalizedPathname, workspaceId)) {
|
|
14
|
+
throw redirect({
|
|
15
|
+
to: '/workspaces/$wid/settings/integrations',
|
|
16
|
+
params: {
|
|
17
|
+
wid: workspaceId
|
|
18
|
+
},
|
|
19
|
+
replace: true
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
hideProjectNavigation: false
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const sourceConnections = await fetchWorkspaceSourceConnections(queryClient, workspaceId);
|
|
27
|
+
const hasSourceConnection = sourceConnections.connections.length > 0;
|
|
28
|
+
if (!hasSourceConnection) {
|
|
29
|
+
if (isIntegrationSetupPath(normalizedPathname, workspaceId)) {
|
|
30
|
+
return {
|
|
31
|
+
hideProjectNavigation: true
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
throw redirect({
|
|
35
|
+
to: '/workspaces/$wid/integrations',
|
|
36
|
+
params: {
|
|
37
|
+
wid: workspaceId
|
|
38
|
+
},
|
|
39
|
+
replace: true
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (isAgentSettingsPath(normalizedPathname, workspaceId)) {
|
|
43
|
+
return {
|
|
44
|
+
hideProjectNavigation: true
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const providerHandled = await hasHandledModelProviderOnboarding(queryClient, workspaceId);
|
|
48
|
+
if (!providerHandled) {
|
|
49
|
+
if (isModelProviderOnboardingPath(normalizedPathname, workspaceId)) {
|
|
50
|
+
return {
|
|
51
|
+
hideProjectNavigation: true
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
throw redirect({
|
|
55
|
+
to: '/workspaces/$wid/model-provider',
|
|
56
|
+
params: {
|
|
57
|
+
wid: workspaceId
|
|
58
|
+
},
|
|
59
|
+
replace: true
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (isProjectCreationPath(normalizedPathname, workspaceId)) {
|
|
63
|
+
return {
|
|
64
|
+
hideProjectNavigation: true
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
throw redirect({
|
|
68
|
+
to: '/workspaces/$wid/projects/new',
|
|
69
|
+
params: {
|
|
70
|
+
wid: workspaceId
|
|
71
|
+
},
|
|
72
|
+
replace: true
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async function fetchWorkspaceProjectExistence(queryClient, workspaceId) {
|
|
76
|
+
const queryKey = projectsQueryKeys.exists(workspaceId);
|
|
77
|
+
try {
|
|
78
|
+
return await queryClient.fetchQuery({
|
|
79
|
+
queryKey,
|
|
80
|
+
queryFn: ({ signal })=>listProjects({
|
|
81
|
+
workspaceId,
|
|
82
|
+
limit: 1,
|
|
83
|
+
signal
|
|
84
|
+
}),
|
|
85
|
+
// beforeLoad runs for every in-workspace navigation. Use a short
|
|
86
|
+
// freshness window to avoid hot-path refetches while still detecting
|
|
87
|
+
// project creation from another tab or actor.
|
|
88
|
+
staleTime: PROJECT_EXISTENCE_STALE_TIME_MS
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const cached = queryClient.getQueryData(queryKey);
|
|
92
|
+
if (cached !== undefined) return cached;
|
|
93
|
+
throw new WorkspaceSetupLoadError(error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function fetchWorkspaceSourceConnections(queryClient, workspaceId) {
|
|
97
|
+
const queryKey = integrationsQueryKeys.sourceConnections(workspaceId);
|
|
98
|
+
try {
|
|
99
|
+
// Onboarding-only path: kept fresh (no staleTime) because some install
|
|
100
|
+
// flows connect a source without invalidating this key, and a stale
|
|
101
|
+
// "no connection" read would trap the user in the onboarding redirect.
|
|
102
|
+
return await queryClient.fetchQuery({
|
|
103
|
+
queryKey,
|
|
104
|
+
queryFn: ({ signal })=>listSourceConnections({
|
|
105
|
+
workspaceId,
|
|
106
|
+
signal
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const cached = queryClient.getQueryData(queryKey);
|
|
111
|
+
if (cached !== undefined) return cached;
|
|
112
|
+
throw new WorkspaceSetupLoadError(error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function hasHandledModelProviderOnboarding(queryClient, workspaceId) {
|
|
116
|
+
if (isModelProviderOnboardingDismissed(workspaceId)) return true;
|
|
117
|
+
const configs = await fetchWorkspaceModelProviderConfigs(queryClient, workspaceId);
|
|
118
|
+
return configs === null || configs.configs.length > 0;
|
|
119
|
+
}
|
|
120
|
+
async function fetchWorkspaceModelProviderConfigs(queryClient, workspaceId) {
|
|
121
|
+
const queryKey = modelProviderQueryKeys.configs(workspaceId);
|
|
122
|
+
try {
|
|
123
|
+
return await queryClient.fetchQuery({
|
|
124
|
+
queryKey,
|
|
125
|
+
queryFn: ({ signal })=>listModelProviderConfigs({
|
|
126
|
+
workspaceId,
|
|
127
|
+
signal
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
} catch {
|
|
131
|
+
const cached = queryClient.getQueryData(queryKey);
|
|
132
|
+
if (cached !== undefined) return cached;
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function normalizePath(pathname) {
|
|
137
|
+
if (pathname === '/') return pathname;
|
|
138
|
+
return pathname.replace(TRAILING_SLASHES_RE, '');
|
|
139
|
+
}
|
|
140
|
+
function workspacePath(workspaceId, suffix) {
|
|
141
|
+
return `/workspaces/${workspaceId}${suffix}`;
|
|
142
|
+
}
|
|
143
|
+
function isIntegrationsIndexPath(pathname, workspaceId) {
|
|
144
|
+
return pathname === workspacePath(workspaceId, '/integrations');
|
|
145
|
+
}
|
|
146
|
+
function isIntegrationSetupPath(pathname, workspaceId) {
|
|
147
|
+
const basePath = workspacePath(workspaceId, '/integrations');
|
|
148
|
+
return pathname === basePath || pathname.startsWith(`${basePath}/`) || pathname === workspacePath(workspaceId, '/settings/integrations');
|
|
149
|
+
}
|
|
150
|
+
function isProjectCreationPath(pathname, workspaceId) {
|
|
151
|
+
return pathname === workspacePath(workspaceId, '/projects/new');
|
|
152
|
+
}
|
|
153
|
+
function isModelProviderOnboardingPath(pathname, workspaceId) {
|
|
154
|
+
return pathname === workspacePath(workspaceId, '/model-provider');
|
|
155
|
+
}
|
|
156
|
+
function isAgentSettingsPath(pathname, workspaceId) {
|
|
157
|
+
return pathname === workspacePath(workspaceId, '/settings/agents');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//# sourceMappingURL=workspace-setup-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/workspace-setup-guard.tsx"],"sourcesContent":["import type {ListIntegrationConnectionsResponseDto} from '@shipfox/api-integration-core-dto';\nimport type {ListProjectsResponseDto} from '@shipfox/api-projects-dto';\nimport {\n isModelProviderOnboardingDismissed,\n listModelProviderConfigs,\n modelProviderQueryKeys,\n} from '@shipfox/client-agent';\nimport {integrationsQueryKeys, listSourceConnections} from '@shipfox/client-integrations';\nimport {WorkspaceSetupLoadError, type WorkspaceSetupState} from '@shipfox/client-shell/runtime';\nimport type {QueryClient} from '@tanstack/react-query';\nimport {redirect} from '@tanstack/react-router';\nimport {listProjects, projectsQueryKeys} from '#hooks/api/projects.js';\n\nconst TRAILING_SLASHES_RE = /\\/+$/u;\nconst PROJECT_EXISTENCE_STALE_TIME_MS = 30_000;\ntype ListModelProviderConfigsResponseDto = Awaited<ReturnType<typeof listModelProviderConfigs>>;\n\nexport interface WorkspaceSetupRouteOptions {\n queryClient: QueryClient;\n workspaceId: string;\n pathname: string;\n}\n\nexport async function loadWorkspaceSetupRoute({\n queryClient,\n workspaceId,\n pathname,\n}: WorkspaceSetupRouteOptions): Promise<WorkspaceSetupState> {\n const projects = await fetchWorkspaceProjectExistence(queryClient, workspaceId);\n const normalizedPathname = normalizePath(pathname);\n const hasProject = projects.projects.length > 0;\n\n if (hasProject) {\n if (isIntegrationsIndexPath(normalizedPathname, workspaceId)) {\n throw redirect({\n to: '/workspaces/$wid/settings/integrations',\n params: {wid: workspaceId},\n replace: true,\n });\n }\n\n return {hideProjectNavigation: false};\n }\n\n const sourceConnections = await fetchWorkspaceSourceConnections(queryClient, workspaceId);\n const hasSourceConnection = sourceConnections.connections.length > 0;\n\n if (!hasSourceConnection) {\n if (isIntegrationSetupPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n throw redirect({\n to: '/workspaces/$wid/integrations',\n params: {wid: workspaceId},\n replace: true,\n });\n }\n\n if (isAgentSettingsPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n const providerHandled = await hasHandledModelProviderOnboarding(queryClient, workspaceId);\n if (!providerHandled) {\n if (isModelProviderOnboardingPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n throw redirect({\n to: '/workspaces/$wid/model-provider',\n params: {wid: workspaceId},\n replace: true,\n });\n }\n\n if (isProjectCreationPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n throw redirect({\n to: '/workspaces/$wid/projects/new',\n params: {wid: workspaceId},\n replace: true,\n });\n}\n\nasync function fetchWorkspaceProjectExistence(queryClient: QueryClient, workspaceId: string) {\n const queryKey = projectsQueryKeys.exists(workspaceId);\n\n try {\n return await queryClient.fetchQuery({\n queryKey,\n queryFn: ({signal}) => listProjects({workspaceId, limit: 1, signal}),\n // beforeLoad runs for every in-workspace navigation. Use a short\n // freshness window to avoid hot-path refetches while still detecting\n // project creation from another tab or actor.\n staleTime: PROJECT_EXISTENCE_STALE_TIME_MS,\n });\n } catch (error) {\n const cached = queryClient.getQueryData<ListProjectsResponseDto>(queryKey);\n if (cached !== undefined) return cached;\n throw new WorkspaceSetupLoadError(error);\n }\n}\n\nasync function fetchWorkspaceSourceConnections(queryClient: QueryClient, workspaceId: string) {\n const queryKey = integrationsQueryKeys.sourceConnections(workspaceId);\n\n try {\n // Onboarding-only path: kept fresh (no staleTime) because some install\n // flows connect a source without invalidating this key, and a stale\n // \"no connection\" read would trap the user in the onboarding redirect.\n return await queryClient.fetchQuery({\n queryKey,\n queryFn: ({signal}) => listSourceConnections({workspaceId, signal}),\n });\n } catch (error) {\n const cached = queryClient.getQueryData<ListIntegrationConnectionsResponseDto>(queryKey);\n if (cached !== undefined) return cached;\n throw new WorkspaceSetupLoadError(error);\n }\n}\n\nasync function hasHandledModelProviderOnboarding(\n queryClient: QueryClient,\n workspaceId: string,\n): Promise<boolean> {\n if (isModelProviderOnboardingDismissed(workspaceId)) return true;\n\n const configs = await fetchWorkspaceModelProviderConfigs(queryClient, workspaceId);\n return configs === null || configs.configs.length > 0;\n}\n\nasync function fetchWorkspaceModelProviderConfigs(\n queryClient: QueryClient,\n workspaceId: string,\n): Promise<ListModelProviderConfigsResponseDto | null> {\n const queryKey = modelProviderQueryKeys.configs(workspaceId);\n\n try {\n return await queryClient.fetchQuery({\n queryKey,\n queryFn: ({signal}) => listModelProviderConfigs({workspaceId, signal}),\n });\n } catch {\n const cached = queryClient.getQueryData<ListModelProviderConfigsResponseDto>(queryKey);\n if (cached !== undefined) return cached;\n return null;\n }\n}\n\nfunction normalizePath(pathname: string) {\n if (pathname === '/') return pathname;\n return pathname.replace(TRAILING_SLASHES_RE, '');\n}\n\nfunction workspacePath(workspaceId: string, suffix: string) {\n return `/workspaces/${workspaceId}${suffix}`;\n}\n\nfunction isIntegrationsIndexPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/integrations');\n}\n\nfunction isIntegrationSetupPath(pathname: string, workspaceId: string) {\n const basePath = workspacePath(workspaceId, '/integrations');\n return (\n pathname === basePath ||\n pathname.startsWith(`${basePath}/`) ||\n pathname === workspacePath(workspaceId, '/settings/integrations')\n );\n}\n\nfunction isProjectCreationPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/projects/new');\n}\n\nfunction isModelProviderOnboardingPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/model-provider');\n}\n\nfunction isAgentSettingsPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/settings/agents');\n}\n"],"names":["isModelProviderOnboardingDismissed","listModelProviderConfigs","modelProviderQueryKeys","integrationsQueryKeys","listSourceConnections","WorkspaceSetupLoadError","redirect","listProjects","projectsQueryKeys","TRAILING_SLASHES_RE","PROJECT_EXISTENCE_STALE_TIME_MS","loadWorkspaceSetupRoute","queryClient","workspaceId","pathname","projects","fetchWorkspaceProjectExistence","normalizedPathname","normalizePath","hasProject","length","isIntegrationsIndexPath","to","params","wid","replace","hideProjectNavigation","sourceConnections","fetchWorkspaceSourceConnections","hasSourceConnection","connections","isIntegrationSetupPath","isAgentSettingsPath","providerHandled","hasHandledModelProviderOnboarding","isModelProviderOnboardingPath","isProjectCreationPath","queryKey","exists","fetchQuery","queryFn","signal","limit","staleTime","error","cached","getQueryData","undefined","configs","fetchWorkspaceModelProviderConfigs","workspacePath","suffix","basePath","startsWith"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const projectsFeature: {
|
|
2
|
+
readonly id: "shipfox.projects";
|
|
3
|
+
readonly routes: readonly [{
|
|
4
|
+
readonly path: "/workspaces/$wid";
|
|
5
|
+
readonly parent: "workspaceLayout";
|
|
6
|
+
readonly impl: "@shipfox/client-projects/routes/home";
|
|
7
|
+
}, {
|
|
8
|
+
readonly path: "/workspaces/$wid/projects/new";
|
|
9
|
+
readonly parent: "workspaceLayout";
|
|
10
|
+
readonly impl: "@shipfox/client-projects/routes/create-project";
|
|
11
|
+
}, {
|
|
12
|
+
readonly path: "/workspaces/$wid/projects/$pid";
|
|
13
|
+
readonly parent: "projectLayout";
|
|
14
|
+
readonly impl: "@shipfox/client-projects/routes/project-index";
|
|
15
|
+
}, {
|
|
16
|
+
readonly path: "/workspaces/$wid/projects/$pid/workflows";
|
|
17
|
+
readonly parent: "projectLayout";
|
|
18
|
+
readonly impl: "@shipfox/client-projects/routes/workflows";
|
|
19
|
+
}];
|
|
20
|
+
readonly navigation: readonly [{
|
|
21
|
+
readonly id: "nav.projects";
|
|
22
|
+
readonly scope: "workspace";
|
|
23
|
+
readonly label: "Projects";
|
|
24
|
+
readonly to: "/workspaces/$wid";
|
|
25
|
+
readonly exact: true;
|
|
26
|
+
readonly order: 100;
|
|
27
|
+
}, {
|
|
28
|
+
readonly id: "nav.workflows";
|
|
29
|
+
readonly scope: "project";
|
|
30
|
+
readonly label: "Workflows";
|
|
31
|
+
readonly to: "/workspaces/$wid/projects/$pid/workflows";
|
|
32
|
+
readonly order: 200;
|
|
33
|
+
}];
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=feature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../src/feature.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyC1B,CAAC"}
|
package/dist/feature.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defineClientFeature } from '@shipfox/client-shell';
|
|
2
|
+
export const projectsFeature = defineClientFeature({
|
|
3
|
+
id: 'shipfox.projects',
|
|
4
|
+
routes: [
|
|
5
|
+
{
|
|
6
|
+
path: '/workspaces/$wid',
|
|
7
|
+
parent: 'workspaceLayout',
|
|
8
|
+
impl: '@shipfox/client-projects/routes/home'
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
path: '/workspaces/$wid/projects/new',
|
|
12
|
+
parent: 'workspaceLayout',
|
|
13
|
+
impl: '@shipfox/client-projects/routes/create-project'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
path: '/workspaces/$wid/projects/$pid',
|
|
17
|
+
parent: 'projectLayout',
|
|
18
|
+
impl: '@shipfox/client-projects/routes/project-index'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
path: '/workspaces/$wid/projects/$pid/workflows',
|
|
22
|
+
parent: 'projectLayout',
|
|
23
|
+
impl: '@shipfox/client-projects/routes/workflows'
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
navigation: [
|
|
27
|
+
{
|
|
28
|
+
id: 'nav.projects',
|
|
29
|
+
scope: 'workspace',
|
|
30
|
+
label: 'Projects',
|
|
31
|
+
to: '/workspaces/$wid',
|
|
32
|
+
exact: true,
|
|
33
|
+
order: 100
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'nav.workflows',
|
|
37
|
+
scope: 'project',
|
|
38
|
+
label: 'Workflows',
|
|
39
|
+
to: '/workspaces/$wid/projects/$pid/workflows',
|
|
40
|
+
order: 200
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/feature.ts"],"sourcesContent":["import {defineClientFeature} from '@shipfox/client-shell';\n\nexport const projectsFeature = defineClientFeature({\n id: 'shipfox.projects',\n routes: [\n {\n path: '/workspaces/$wid',\n parent: 'workspaceLayout',\n impl: '@shipfox/client-projects/routes/home',\n },\n {\n path: '/workspaces/$wid/projects/new',\n parent: 'workspaceLayout',\n impl: '@shipfox/client-projects/routes/create-project',\n },\n {\n path: '/workspaces/$wid/projects/$pid',\n parent: 'projectLayout',\n impl: '@shipfox/client-projects/routes/project-index',\n },\n {\n path: '/workspaces/$wid/projects/$pid/workflows',\n parent: 'projectLayout',\n impl: '@shipfox/client-projects/routes/workflows',\n },\n ],\n navigation: [\n {\n id: 'nav.projects',\n scope: 'workspace',\n label: 'Projects',\n to: '/workspaces/$wid',\n exact: true,\n order: 100,\n },\n {\n id: 'nav.workflows',\n scope: 'project',\n label: 'Workflows',\n to: '/workspaces/$wid/projects/$pid/workflows',\n order: 200,\n },\n ],\n});\n"],"names":["defineClientFeature","projectsFeature","id","routes","path","parent","impl","navigation","scope","label","to","exact","order"],"mappings":"AAAA,SAAQA,mBAAmB,QAAO,wBAAwB;AAE1D,OAAO,MAAMC,kBAAkBD,oBAAoB;IACjDE,IAAI;IACJC,QAAQ;QACN;YACEC,MAAM;YACNC,QAAQ;YACRC,MAAM;QACR;QACA;YACEF,MAAM;YACNC,QAAQ;YACRC,MAAM;QACR;QACA;YACEF,MAAM;YACNC,QAAQ;YACRC,MAAM;QACR;QACA;YACEF,MAAM;YACNC,QAAQ;YACRC,MAAM;QACR;KACD;IACDC,YAAY;QACV;YACEL,IAAI;YACJM,OAAO;YACPC,OAAO;YACPC,IAAI;YACJC,OAAO;YACPC,OAAO;QACT;QACA;YACEV,IAAI;YACJM,OAAO;YACPC,OAAO;YACPC,IAAI;YACJE,OAAO;QACT;KACD;AACH,GAAG"}
|