@shipfox/client-projects 21.0.0 → 22.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 +31 -0
- package/dist/components/source-strip.d.ts.map +1 -1
- package/dist/components/source-strip.js +49 -45
- package/dist/components/source-strip.js.map +1 -1
- package/dist/pages/create-project-page.d.ts.map +1 -1
- package/dist/pages/create-project-page.js +161 -175
- package/dist/pages/create-project-page.js.map +1 -1
- package/dist/pages/project-settings-page.d.ts.map +1 -1
- package/dist/pages/project-settings-page.js +82 -84
- package/dist/pages/project-settings-page.js.map +1 -1
- package/dist/pages/projects-hub-page.d.ts.map +1 -1
- package/dist/pages/projects-hub-page.js +160 -159
- package/dist/pages/projects-hub-page.js.map +1 -1
- package/dist/pages/projects-hub-page.stories.d.ts +22 -0
- package/dist/pages/projects-hub-page.stories.d.ts.map +1 -0
- package/dist/pages/projects-hub-page.stories.js +214 -0
- package/dist/pages/projects-hub-page.stories.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/components/source-strip.tsx +29 -26
- package/src/pages/create-project-page.test.tsx +85 -1
- package/src/pages/create-project-page.tsx +165 -153
- package/src/pages/home-router.test.tsx +2 -1
- package/src/pages/project-settings-page.test.tsx +10 -1
- package/src/pages/project-settings-page.tsx +88 -76
- package/src/pages/projects-hub-page.stories.tsx +203 -0
- package/src/pages/projects-hub-page.test.tsx +90 -10
- package/src/pages/projects-hub-page.tsx +136 -125
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -11,9 +11,17 @@ import {Callout} from '@shipfox/react-ui/callout';
|
|
|
11
11
|
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
12
12
|
import {Icon} from '@shipfox/react-ui/icon';
|
|
13
13
|
import {Input} from '@shipfox/react-ui/input';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
Panel,
|
|
16
|
+
PanelActions,
|
|
17
|
+
PanelBody,
|
|
18
|
+
PanelCell,
|
|
19
|
+
PanelCellAction,
|
|
20
|
+
PanelGrid,
|
|
21
|
+
PanelHeader,
|
|
22
|
+
} from '@shipfox/react-ui/panel';
|
|
15
23
|
import {Skeleton} from '@shipfox/react-ui/skeleton';
|
|
16
|
-
import {
|
|
24
|
+
import {Text} from '@shipfox/react-ui/typography';
|
|
17
25
|
import {Link, useNavigate} from '@tanstack/react-router';
|
|
18
26
|
import {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';
|
|
19
27
|
import type {Project} from '#core/project.js';
|
|
@@ -41,114 +49,118 @@ export function ProjectsHubPage({search = ''}: {search?: string}) {
|
|
|
41
49
|
|
|
42
50
|
return (
|
|
43
51
|
<div className="flex w-full flex-col gap-section">
|
|
44
|
-
<header className="flex flex-col gap-group">
|
|
45
|
-
<div className="flex items-start justify-between gap-section max-[640px]:flex-col">
|
|
46
|
-
<Header variant="h2">Projects</Header>
|
|
47
|
-
</div>
|
|
48
|
-
<div className="flex items-center gap-cluster max-[640px]:flex-col max-[640px]:items-stretch">
|
|
49
|
-
<Input
|
|
50
|
-
type="search"
|
|
51
|
-
value={search}
|
|
52
|
-
onChange={(event) => {
|
|
53
|
-
const next = event.target.value.trim();
|
|
54
|
-
navigate({search: (next ? {search: next} : {}) as never, replace: true});
|
|
55
|
-
}}
|
|
56
|
-
placeholder="Search projects…"
|
|
57
|
-
aria-label="Search projects"
|
|
58
|
-
className="flex-1"
|
|
59
|
-
iconLeft={<Icon name="searchLine" className="size-16" />}
|
|
60
|
-
iconRight={
|
|
61
|
-
isSearching ? (
|
|
62
|
-
<Icon
|
|
63
|
-
name="spinner"
|
|
64
|
-
size={16}
|
|
65
|
-
className="text-foreground-neutral-muted"
|
|
66
|
-
aria-hidden="true"
|
|
67
|
-
/>
|
|
68
|
-
) : undefined
|
|
69
|
-
}
|
|
70
|
-
/>
|
|
71
|
-
<Button asChild iconLeft="addLine" className="shrink-0 max-[640px]:w-full">
|
|
72
|
-
<Link to="/w/$workspaceSlug/projects/new" params={{workspaceSlug: workspace.slug}}>
|
|
73
|
-
New project
|
|
74
|
-
</Link>
|
|
75
|
-
</Button>
|
|
76
|
-
</div>
|
|
77
|
-
</header>
|
|
78
|
-
|
|
79
52
|
<ModelProviderReminderBanner workspaceId={workspace.id} />
|
|
80
53
|
|
|
81
|
-
|
|
54
|
+
<section aria-label="Projects">
|
|
55
|
+
<Panel>
|
|
56
|
+
<PanelHeader className="flex-wrap max-[640px]:items-stretch">
|
|
57
|
+
<div className="min-w-0 flex-1 max-[640px]:basis-full">
|
|
58
|
+
<Input
|
|
59
|
+
type="search"
|
|
60
|
+
value={search}
|
|
61
|
+
onChange={(event) => {
|
|
62
|
+
const next = event.target.value.trim();
|
|
63
|
+
navigate({search: (next ? {search: next} : {}) as never, replace: true});
|
|
64
|
+
}}
|
|
65
|
+
placeholder="Search projects…"
|
|
66
|
+
aria-label="Search projects"
|
|
67
|
+
iconLeft={<Icon name="searchLine" className="size-16" />}
|
|
68
|
+
iconRight={
|
|
69
|
+
isSearching ? (
|
|
70
|
+
<Icon
|
|
71
|
+
name="spinner"
|
|
72
|
+
size={16}
|
|
73
|
+
className="text-foreground-neutral-muted"
|
|
74
|
+
aria-hidden="true"
|
|
75
|
+
/>
|
|
76
|
+
) : undefined
|
|
77
|
+
}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<PanelActions className="max-[640px]:ml-0 max-[640px]:w-full">
|
|
81
|
+
<Button asChild iconLeft="addLine" className="max-[640px]:w-full">
|
|
82
|
+
<Link to="/w/$workspaceSlug/projects/new" params={{workspaceSlug: workspace.slug}}>
|
|
83
|
+
New project
|
|
84
|
+
</Link>
|
|
85
|
+
</Button>
|
|
86
|
+
</PanelActions>
|
|
87
|
+
</PanelHeader>
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
<PanelBody>
|
|
90
|
+
{isInitialLoading || (search && hasNoData && query.isFetching) ? (
|
|
91
|
+
<ProjectsSkeleton />
|
|
92
|
+
) : null}
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
{query.isError && hasNoData ? (
|
|
95
|
+
<QueryLoadError query={query} subject="projects" variant="panel" />
|
|
96
|
+
) : null}
|
|
88
97
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
onClear={() => navigate({search: {} as never, replace: true})}
|
|
93
|
-
/>
|
|
94
|
-
) : null}
|
|
98
|
+
{!isInitialLoading && !query.isError && projects.length === 0 && !search ? (
|
|
99
|
+
<EmptyProjects workspaceSlug={workspace.slug} />
|
|
100
|
+
) : null}
|
|
95
101
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<ProjectCard
|
|
101
|
-
project={project}
|
|
102
|
-
connection={connectionsById.get(project.source.connectionId)}
|
|
103
|
-
connectionsResolved={connectionsQuery.isSuccess}
|
|
104
|
-
connectionsSettled={connectionsQuery.isSuccess || connectionsQuery.isError}
|
|
105
|
-
key={project.id}
|
|
106
|
-
workspaceSlug={workspace.slug}
|
|
102
|
+
{!query.isFetching && !query.isError && projects.length === 0 && search ? (
|
|
103
|
+
<NoSearchResults
|
|
104
|
+
search={search}
|
|
105
|
+
onClear={() => navigate({search: {} as never, replace: true})}
|
|
107
106
|
/>
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
107
|
+
) : null}
|
|
108
|
+
|
|
109
|
+
{projects.length > 0 ? (
|
|
110
|
+
<>
|
|
111
|
+
<PanelGrid aria-label="Projects list">
|
|
112
|
+
{projects.map((project) => (
|
|
113
|
+
<ProjectCell
|
|
114
|
+
project={project}
|
|
115
|
+
connection={connectionsById.get(project.source.connectionId)}
|
|
116
|
+
connectionsResolved={connectionsQuery.isSuccess}
|
|
117
|
+
connectionsSettled={connectionsQuery.isSuccess || connectionsQuery.isError}
|
|
118
|
+
key={project.id}
|
|
119
|
+
workspaceSlug={workspace.slug}
|
|
120
|
+
/>
|
|
121
|
+
))}
|
|
122
|
+
</PanelGrid>
|
|
123
|
+
{query.error && query.data ? (
|
|
124
|
+
<div className="border-t border-border-neutral-base p-panel-compact">
|
|
125
|
+
<Callout role="alert" type="error">
|
|
126
|
+
<Text size="sm">
|
|
127
|
+
Could not load the next page. Existing projects are still shown.
|
|
128
|
+
</Text>
|
|
129
|
+
</Callout>
|
|
130
|
+
</div>
|
|
131
|
+
) : null}
|
|
132
|
+
{query.hasNextPage ? (
|
|
133
|
+
<div className="flex justify-center border-t border-border-neutral-base p-panel-compact">
|
|
134
|
+
<Button
|
|
135
|
+
variant="secondary"
|
|
136
|
+
isLoading={query.isFetchingNextPage}
|
|
137
|
+
onClick={() => query.fetchNextPage()}
|
|
138
|
+
>
|
|
139
|
+
Load more
|
|
140
|
+
</Button>
|
|
141
|
+
</div>
|
|
142
|
+
) : null}
|
|
143
|
+
</>
|
|
144
|
+
) : null}
|
|
145
|
+
</PanelBody>
|
|
146
|
+
</Panel>
|
|
147
|
+
</section>
|
|
130
148
|
</div>
|
|
131
149
|
);
|
|
132
150
|
}
|
|
133
151
|
|
|
134
152
|
function ProjectsSkeleton() {
|
|
135
153
|
return (
|
|
136
|
-
<
|
|
137
|
-
role="status"
|
|
138
|
-
aria-label="Loading projects"
|
|
139
|
-
className="grid grid-cols-2 gap-cluster max-[760px]:grid-cols-1"
|
|
140
|
-
>
|
|
154
|
+
<PanelGrid role="status" aria-label="Loading projects">
|
|
141
155
|
{[0, 1, 2, 3, 4, 5].map((row) => (
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
</Panel>
|
|
149
|
-
</li>
|
|
156
|
+
<PanelCell key={row}>
|
|
157
|
+
<div className="flex items-center gap-cluster px-row py-row">
|
|
158
|
+
<Skeleton className="size-24 shrink-0" />
|
|
159
|
+
<Skeleton className="h-16 w-1/2" />
|
|
160
|
+
</div>
|
|
161
|
+
</PanelCell>
|
|
150
162
|
))}
|
|
151
|
-
</
|
|
163
|
+
</PanelGrid>
|
|
152
164
|
);
|
|
153
165
|
}
|
|
154
166
|
|
|
@@ -158,6 +170,7 @@ function EmptyProjects({workspaceSlug}: {workspaceSlug: string}) {
|
|
|
158
170
|
icon="folderLine"
|
|
159
171
|
title="Create your first project"
|
|
160
172
|
description="Connect a repository-backed project to start building workflows."
|
|
173
|
+
variant="panel"
|
|
161
174
|
action={
|
|
162
175
|
<Button asChild iconRight="chevronRight">
|
|
163
176
|
<Link to="/w/$workspaceSlug/projects/new" params={{workspaceSlug}}>
|
|
@@ -175,6 +188,7 @@ function NoSearchResults({search, onClear}: {search: string; onClear: () => void
|
|
|
175
188
|
icon="searchLine"
|
|
176
189
|
title={`No projects match “${search}”`}
|
|
177
190
|
description="Try a different search, or clear it to see all projects."
|
|
191
|
+
variant="panel"
|
|
178
192
|
action={
|
|
179
193
|
<Button size="sm" variant="secondary" onClick={onClear}>
|
|
180
194
|
Clear search
|
|
@@ -184,7 +198,7 @@ function NoSearchResults({search, onClear}: {search: string; onClear: () => void
|
|
|
184
198
|
);
|
|
185
199
|
}
|
|
186
200
|
|
|
187
|
-
function
|
|
201
|
+
function ProjectCell({
|
|
188
202
|
project,
|
|
189
203
|
connection,
|
|
190
204
|
connectionsResolved,
|
|
@@ -203,34 +217,31 @@ function ProjectCard({
|
|
|
203
217
|
const status = connectionsResolved ? (connection?.lifecycleStatus ?? 'error') : undefined;
|
|
204
218
|
|
|
205
219
|
return (
|
|
206
|
-
<
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
</Panel>
|
|
233
|
-
</Link>
|
|
234
|
-
</li>
|
|
220
|
+
<PanelCell>
|
|
221
|
+
<PanelCellAction asChild>
|
|
222
|
+
<Link
|
|
223
|
+
to="/w/$workspaceSlug/p/$projectSlug"
|
|
224
|
+
params={{workspaceSlug, projectSlug: project.slug}}
|
|
225
|
+
>
|
|
226
|
+
{/* Settle on success or error: a failed fetch falls back to the
|
|
227
|
+
neutral provider icon rather than spinning forever. */}
|
|
228
|
+
{connectionsSettled ? (
|
|
229
|
+
<IntegrationIcon
|
|
230
|
+
source={connection?.provider}
|
|
231
|
+
aria-hidden
|
|
232
|
+
className="size-24 shrink-0 text-foreground-neutral-base"
|
|
233
|
+
/>
|
|
234
|
+
) : (
|
|
235
|
+
<Skeleton className="size-24 shrink-0" />
|
|
236
|
+
)}
|
|
237
|
+
<span className="flex min-w-0 flex-1 items-center gap-inline">
|
|
238
|
+
<Text as="span" size="md" bold className="truncate">
|
|
239
|
+
{project.name}
|
|
240
|
+
</Text>
|
|
241
|
+
{status ? <ConnectionStatusBadge status={status} className="shrink-0" /> : null}
|
|
242
|
+
</span>
|
|
243
|
+
</Link>
|
|
244
|
+
</PanelCellAction>
|
|
245
|
+
</PanelCell>
|
|
235
246
|
);
|
|
236
247
|
}
|