@shipfox/client-onboarding 5.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.
@@ -0,0 +1,157 @@
1
+ import {
2
+ isModelProviderOnboardingDismissed,
3
+ modelProviderConfigsQueryOptions,
4
+ } from '@shipfox/client-agent';
5
+ import {
6
+ type IntegrationConnection,
7
+ sourceConnectionsQueryOptions,
8
+ } from '@shipfox/client-integrations';
9
+ import {projectExistenceQueryOptions} from '@shipfox/client-projects';
10
+ import {WorkspaceSetupLoadError, type WorkspaceSetupState} from '@shipfox/client-shell/runtime';
11
+ import type {QueryClient} from '@tanstack/react-query';
12
+ import {redirect} from '@tanstack/react-router';
13
+
14
+ const TRAILING_SLASHES_RE = /\/+$/u;
15
+
16
+ export interface WorkspaceSetupRouteOptions {
17
+ queryClient: QueryClient;
18
+ workspaceId: string;
19
+ pathname: string;
20
+ }
21
+
22
+ export async function loadWorkspaceSetupRoute({
23
+ queryClient,
24
+ workspaceId,
25
+ pathname,
26
+ }: WorkspaceSetupRouteOptions): Promise<WorkspaceSetupState> {
27
+ const projects = await fetchWorkspaceProjectExistence(queryClient, workspaceId);
28
+ const normalizedPathname = normalizePath(pathname);
29
+
30
+ if (projects.projects.length > 0) {
31
+ if (isIntegrationsIndexPath(normalizedPathname, workspaceId)) {
32
+ throw redirect({
33
+ to: '/workspaces/$wid/settings/integrations',
34
+ params: {wid: workspaceId},
35
+ replace: true,
36
+ });
37
+ }
38
+
39
+ return {hideProjectNavigation: false};
40
+ }
41
+
42
+ const sourceConnections = await fetchWorkspaceSourceConnections(queryClient, workspaceId);
43
+ const hasSourceConnection = sourceConnections.length > 0;
44
+
45
+ if (!hasSourceConnection) {
46
+ if (isIntegrationSetupPath(normalizedPathname, workspaceId)) {
47
+ return {hideProjectNavigation: true};
48
+ }
49
+
50
+ throw redirect({
51
+ to: '/workspaces/$wid/integrations',
52
+ params: {wid: workspaceId},
53
+ replace: true,
54
+ });
55
+ }
56
+
57
+ if (isAgentSettingsPath(normalizedPathname, workspaceId)) {
58
+ return {hideProjectNavigation: true};
59
+ }
60
+
61
+ const providerHandled = await hasHandledModelProviderOnboarding(queryClient, workspaceId);
62
+ if (!providerHandled) {
63
+ if (isModelProviderOnboardingPath(normalizedPathname, workspaceId)) {
64
+ return {hideProjectNavigation: true};
65
+ }
66
+
67
+ throw redirect({
68
+ to: '/workspaces/$wid/model-provider',
69
+ params: {wid: workspaceId},
70
+ replace: true,
71
+ });
72
+ }
73
+
74
+ if (isProjectCreationPath(normalizedPathname, workspaceId)) {
75
+ return {hideProjectNavigation: true};
76
+ }
77
+
78
+ throw redirect({
79
+ to: '/workspaces/$wid/projects/new',
80
+ params: {wid: workspaceId},
81
+ replace: true,
82
+ });
83
+ }
84
+
85
+ async function fetchWorkspaceProjectExistence(queryClient: QueryClient, workspaceId: string) {
86
+ const options = projectExistenceQueryOptions(workspaceId);
87
+
88
+ try {
89
+ return await queryClient.fetchQuery(options);
90
+ } catch (error) {
91
+ const cached = queryClient.getQueryData<{projects: unknown[]}>(options.queryKey);
92
+ if (cached !== undefined) return cached;
93
+ throw new WorkspaceSetupLoadError(error);
94
+ }
95
+ }
96
+
97
+ async function fetchWorkspaceSourceConnections(queryClient: QueryClient, workspaceId: string) {
98
+ const options = sourceConnectionsQueryOptions(workspaceId);
99
+
100
+ try {
101
+ return await queryClient.fetchQuery(options);
102
+ } catch (error) {
103
+ const cached = queryClient.getQueryData<IntegrationConnection[]>(options.queryKey);
104
+ if (cached !== undefined) return cached;
105
+ throw new WorkspaceSetupLoadError(error);
106
+ }
107
+ }
108
+
109
+ async function hasHandledModelProviderOnboarding(
110
+ queryClient: QueryClient,
111
+ workspaceId: string,
112
+ ): Promise<boolean> {
113
+ if (isModelProviderOnboardingDismissed(workspaceId)) return true;
114
+
115
+ const options = modelProviderConfigsQueryOptions(workspaceId);
116
+ try {
117
+ const configs = await queryClient.fetchQuery(options);
118
+ return configs.configs.length > 0;
119
+ } catch {
120
+ const cached = queryClient.getQueryData<{configs: unknown[]}>(options.queryKey);
121
+ return cached?.configs.length !== 0;
122
+ }
123
+ }
124
+
125
+ function normalizePath(pathname: string) {
126
+ if (pathname === '/') return pathname;
127
+ return pathname.replace(TRAILING_SLASHES_RE, '');
128
+ }
129
+
130
+ function workspacePath(workspaceId: string, suffix: string) {
131
+ return `/workspaces/${workspaceId}${suffix}`;
132
+ }
133
+
134
+ function isIntegrationsIndexPath(pathname: string, workspaceId: string) {
135
+ return pathname === workspacePath(workspaceId, '/integrations');
136
+ }
137
+
138
+ function isIntegrationSetupPath(pathname: string, workspaceId: string) {
139
+ const basePath = workspacePath(workspaceId, '/integrations');
140
+ return (
141
+ pathname === basePath ||
142
+ pathname.startsWith(`${basePath}/`) ||
143
+ pathname === workspacePath(workspaceId, '/settings/integrations')
144
+ );
145
+ }
146
+
147
+ function isProjectCreationPath(pathname: string, workspaceId: string) {
148
+ return pathname === workspacePath(workspaceId, '/projects/new');
149
+ }
150
+
151
+ function isModelProviderOnboardingPath(pathname: string, workspaceId: string) {
152
+ return pathname === workspacePath(workspaceId, '/model-provider');
153
+ }
154
+
155
+ function isAgentSettingsPath(pathname: string, workspaceId: string) {
156
+ return pathname === workspacePath(workspaceId, '/settings/agents');
157
+ }
package/test/setup.ts ADDED
@@ -0,0 +1,3 @@
1
+ import {installClientDomTestEnv} from '@shipfox/client-test-setup';
2
+
3
+ installClientDomTestEnv();
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@shipfox/ts-config/react",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "customConditions": ["workspace-source"]
7
+ },
8
+ "include": ["src"],
9
+ "exclude": ["**/*.test.tsx", "**/*.test.ts"]
10
+ }