@shipfox/client-projects 12.0.1 → 13.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.
Files changed (111) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +59 -0
  3. package/dist/chrome.d.ts +5 -1
  4. package/dist/chrome.d.ts.map +1 -1
  5. package/dist/chrome.js +18 -26
  6. package/dist/chrome.js.map +1 -1
  7. package/dist/components/model-provider-reminder-banner.d.ts.map +1 -1
  8. package/dist/components/model-provider-reminder-banner.js +4 -2
  9. package/dist/components/model-provider-reminder-banner.js.map +1 -1
  10. package/dist/components/project-crumb.d.ts +3 -1
  11. package/dist/components/project-crumb.d.ts.map +1 -1
  12. package/dist/components/project-crumb.js +7 -5
  13. package/dist/components/project-crumb.js.map +1 -1
  14. package/dist/components/project-switcher.d.ts +2 -1
  15. package/dist/components/project-switcher.d.ts.map +1 -1
  16. package/dist/components/project-switcher.js +10 -8
  17. package/dist/components/project-switcher.js.map +1 -1
  18. package/dist/core/definition.d.ts +6 -0
  19. package/dist/core/definition.d.ts.map +1 -1
  20. package/dist/core/definition.js.map +1 -1
  21. package/dist/core/project.d.ts +7 -0
  22. package/dist/core/project.d.ts.map +1 -1
  23. package/dist/core/project.js.map +1 -1
  24. package/dist/feature.d.ts +29 -5
  25. package/dist/feature.d.ts.map +1 -1
  26. package/dist/feature.js +26 -5
  27. package/dist/feature.js.map +1 -1
  28. package/dist/hooks/api/mappers.d.ts.map +1 -1
  29. package/dist/hooks/api/mappers.js +9 -1
  30. package/dist/hooks/api/mappers.js.map +1 -1
  31. package/dist/hooks/api/projects.d.ts +21 -2
  32. package/dist/hooks/api/projects.d.ts.map +1 -1
  33. package/dist/hooks/api/projects.js +175 -2
  34. package/dist/hooks/api/projects.js.map +1 -1
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/pages/create-project-page.d.ts.map +1 -1
  40. package/dist/pages/create-project-page.js +97 -21
  41. package/dist/pages/create-project-page.js.map +1 -1
  42. package/dist/pages/create-project-page.stories.js +5 -3
  43. package/dist/pages/create-project-page.stories.js.map +1 -1
  44. package/dist/pages/project-settings-form-errors.d.ts +11 -0
  45. package/dist/pages/project-settings-form-errors.d.ts.map +1 -0
  46. package/dist/pages/project-settings-form-errors.js +16 -0
  47. package/dist/pages/project-settings-form-errors.js.map +1 -0
  48. package/dist/pages/project-settings-page.d.ts +2 -0
  49. package/dist/pages/project-settings-page.d.ts.map +1 -0
  50. package/dist/pages/project-settings-page.js +225 -0
  51. package/dist/pages/project-settings-page.js.map +1 -0
  52. package/dist/pages/project-settings-page.stories.d.ts +23 -0
  53. package/dist/pages/project-settings-page.stories.d.ts.map +1 -0
  54. package/dist/pages/project-settings-page.stories.js +206 -0
  55. package/dist/pages/project-settings-page.stories.js.map +1 -0
  56. package/dist/pages/projects-hub-page.js +11 -11
  57. package/dist/pages/projects-hub-page.js.map +1 -1
  58. package/dist/project-error.d.ts +1 -0
  59. package/dist/project-error.d.ts.map +1 -1
  60. package/dist/project-error.js +7 -0
  61. package/dist/project-error.js.map +1 -1
  62. package/dist/routes/inputs.d.ts +2 -2
  63. package/dist/routes/inputs.d.ts.map +1 -1
  64. package/dist/routes/inputs.js +5 -5
  65. package/dist/routes/inputs.js.map +1 -1
  66. package/dist/routes/project-index.d.ts +2 -2
  67. package/dist/routes/project-index.d.ts.map +1 -1
  68. package/dist/routes/project-index.js +3 -3
  69. package/dist/routes/project-index.js.map +1 -1
  70. package/dist/routes/project-settings-index.d.ts +10 -0
  71. package/dist/routes/project-settings-index.d.ts.map +1 -0
  72. package/dist/routes/project-settings-index.js +12 -0
  73. package/dist/routes/project-settings-index.js.map +1 -0
  74. package/dist/routes/project-settings.d.ts +6 -0
  75. package/dist/routes/project-settings.d.ts.map +1 -0
  76. package/dist/routes/project-settings.js +7 -0
  77. package/dist/routes/project-settings.js.map +1 -0
  78. package/dist/tsconfig.test.tsbuildinfo +1 -1
  79. package/package.json +11 -10
  80. package/src/chrome.tsx +27 -19
  81. package/src/components/model-provider-reminder-banner.tsx +3 -1
  82. package/src/components/project-crumb.tsx +18 -5
  83. package/src/components/project-switcher.test.tsx +7 -4
  84. package/src/components/project-switcher.tsx +16 -5
  85. package/src/core/definition.ts +7 -0
  86. package/src/core/project.ts +8 -0
  87. package/src/feature.ts +26 -4
  88. package/src/hooks/api/mappers.test.ts +19 -0
  89. package/src/hooks/api/mappers.ts +6 -0
  90. package/src/hooks/api/projects.test.ts +247 -1
  91. package/src/hooks/api/projects.ts +227 -2
  92. package/src/index.ts +9 -1
  93. package/src/pages/create-project-page.stories.tsx +5 -4
  94. package/src/pages/create-project-page.test.tsx +191 -12
  95. package/src/pages/create-project-page.tsx +102 -15
  96. package/src/pages/home-router.test.tsx +2 -2
  97. package/src/pages/project-settings-form-errors.ts +17 -0
  98. package/src/pages/project-settings-page.stories.tsx +216 -0
  99. package/src/pages/project-settings-page.test.tsx +74 -0
  100. package/src/pages/project-settings-page.tsx +220 -0
  101. package/src/pages/projects-hub-page.test.tsx +17 -11
  102. package/src/pages/projects-hub-page.tsx +9 -9
  103. package/src/project-error.test.ts +1 -0
  104. package/src/project-error.ts +8 -0
  105. package/src/routes/inputs.test.ts +8 -6
  106. package/src/routes/inputs.ts +7 -6
  107. package/src/routes/project-index.tsx +3 -3
  108. package/src/routes/project-settings-index.tsx +11 -0
  109. package/src/routes/project-settings.tsx +4 -0
  110. package/test/pages.tsx +26 -22
  111. package/tsconfig.build.tsbuildinfo +1 -1
@@ -4,6 +4,7 @@ export interface ProjectErrorCopy {
4
4
  title: string;
5
5
  message: string;
6
6
  existingProjectId?: string | undefined;
7
+ slugConflict?: boolean | undefined;
7
8
  }
8
9
 
9
10
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -98,6 +99,13 @@ export function projectErrorCopy(error: unknown): ProjectErrorCopy {
98
99
  existingProjectId: stringDetail(error, 'existing_project_id'),
99
100
  };
100
101
  }
102
+ if (error.code === 'slug-conflict') {
103
+ return {
104
+ title: 'Project slug already exists',
105
+ message: 'Choose another slug and try again.',
106
+ slugConflict: true,
107
+ };
108
+ }
101
109
 
102
110
  return {
103
111
  title: 'Project request failed',
@@ -1,14 +1,16 @@
1
1
  import {projectRouteParams} from './inputs.js';
2
2
 
3
3
  describe('project route inputs', () => {
4
- it('requires both workspace and project ids', () => {
5
- expect(projectRouteParams({wid: 'workspace-1', pid: 'project-1'})).toEqual({
6
- wid: 'workspace-1',
7
- pid: 'project-1',
4
+ it('requires both workspace and project slugs', () => {
5
+ expect(projectRouteParams({workspaceSlug: 'workspace-1', projectSlug: 'project-1'})).toEqual({
6
+ workspaceSlug: 'workspace-1',
7
+ projectSlug: 'project-1',
8
8
  });
9
- expect(() => projectRouteParams({wid: 'workspace-1'})).toThrow(
9
+ expect(() => projectRouteParams({workspaceSlug: 'workspace-1'})).toThrow(
10
10
  'Project route is missing required path parameters.',
11
11
  );
12
- expect(() => projectRouteParams({wid: ['workspace-1'], pid: 'project-1'})).toThrow();
12
+ expect(() =>
13
+ projectRouteParams({workspaceSlug: ['workspace-1'], projectSlug: 'project-1'}),
14
+ ).toThrow();
13
15
  });
14
16
  });
@@ -1,13 +1,14 @@
1
1
  export interface ProjectRouteParams {
2
- wid: string;
3
- pid: string;
2
+ workspaceSlug: string;
3
+ projectSlug: string;
4
4
  }
5
5
 
6
6
  export function projectRouteParams(input: Record<string, unknown>): ProjectRouteParams {
7
- const wid = stringParam(input.wid);
8
- const pid = stringParam(input.pid);
9
- if (!wid || !pid) throw new Error('Project route is missing required path parameters.');
10
- return {wid, pid};
7
+ const workspaceSlug = stringParam(input.workspaceSlug);
8
+ const projectSlug = stringParam(input.projectSlug);
9
+ if (!workspaceSlug || !projectSlug)
10
+ throw new Error('Project route is missing required path parameters.');
11
+ return {workspaceSlug, projectSlug};
11
12
  }
12
13
 
13
14
  function stringParam(value: unknown): string | undefined {
@@ -2,10 +2,10 @@ import {defineRoute} from '@shipfox/client-shell/runtime';
2
2
  import {redirect} from '@tanstack/react-router';
3
3
 
4
4
  export default defineRoute({
5
- beforeLoad: ({params}: {params: {wid: string; pid: string}}) => {
5
+ beforeLoad: ({params}: {params: {workspaceSlug: string; projectSlug: string}}) => {
6
6
  throw redirect({
7
- to: '/workspaces/$wid/projects/$pid/runs',
8
- params: {wid: params.wid, pid: params.pid},
7
+ to: '/w/$workspaceSlug/p/$projectSlug/runs',
8
+ params: {workspaceSlug: params.workspaceSlug, projectSlug: params.projectSlug},
9
9
  });
10
10
  },
11
11
  });
@@ -0,0 +1,11 @@
1
+ import {defineRoute} from '@shipfox/client-shell/runtime';
2
+ import {redirect} from '@tanstack/react-router';
3
+
4
+ export default defineRoute({
5
+ beforeLoad: ({params}: {params: {workspaceSlug: string; projectSlug: string}}) => {
6
+ throw redirect({
7
+ to: '/w/$workspaceSlug/p/$projectSlug/settings/general',
8
+ params,
9
+ });
10
+ },
11
+ });
@@ -0,0 +1,4 @@
1
+ import {defineRoute} from '@shipfox/client-shell/runtime';
2
+ import {ProjectSettingsPage} from '#pages/project-settings-page.js';
3
+
4
+ export default defineRoute({component: ProjectSettingsPage});
package/test/pages.tsx CHANGED
@@ -18,9 +18,9 @@ import {CreateProjectPage} from '#pages/create-project-page.js';
18
18
  import {ProjectsHubPage} from '#pages/projects-hub-page.js';
19
19
 
20
20
  // All test renders that exercise pages requiring `useActiveWorkspace()` mount
21
- // under `/workspaces/$wid`. The seeded workspace id (see authState) is the wid
22
- // used in routes.
21
+ // under `/w/$workspaceSlug`. APIs still use the UUID below.
23
22
  export const PROJECT_TEST_WID = '11111111-1111-4111-8111-111111111111';
23
+ export const PROJECT_TEST_WSLUG = 'acme';
24
24
 
25
25
  const authState: AuthState = {
26
26
  status: 'authenticated',
@@ -30,7 +30,7 @@ const authState: AuthState = {
30
30
  email: 'user@example.com',
31
31
  emailVerifiedAt: new Date().toISOString(),
32
32
  },
33
- workspaces: [{id: PROJECT_TEST_WID, name: 'Acme', membershipId: 'm-1'}],
33
+ workspaces: [{id: PROJECT_TEST_WID, name: 'Acme', slug: PROJECT_TEST_WSLUG, membershipId: 'm-1'}],
34
34
  };
35
35
 
36
36
  export function jsonResponse(body: unknown, init: ResponseInit = {}) {
@@ -51,36 +51,36 @@ function createTestRouter(path: string, element: ReactElement) {
51
51
  });
52
52
  const workspaceRoute = createRoute({
53
53
  getParentRoute: () => rootRoute,
54
- path: '/workspaces/$wid',
55
- component: () =>
56
- initialPath === `/workspaces/${PROJECT_TEST_WID}` ? element : <ProjectsHubPage />,
54
+ path: '/w/$workspaceSlug',
55
+ component: () => (initialPath === `/w/${PROJECT_TEST_WSLUG}` ? element : <ProjectsHubPage />),
57
56
  });
58
57
  const workspaceNewProjectRoute = createRoute({
59
58
  getParentRoute: () => rootRoute,
60
- path: '/workspaces/$wid/projects/new',
59
+ path: '/w/$workspaceSlug/projects/new',
61
60
  component: () =>
62
- initialPath === `/workspaces/${PROJECT_TEST_WID}/projects/new` ? (
63
- element
64
- ) : (
65
- <CreateProjectPage />
66
- ),
61
+ initialPath === `/w/${PROJECT_TEST_WSLUG}/projects/new` ? element : <CreateProjectPage />,
67
62
  });
68
63
  const integrationsRoute = createRoute({
69
64
  getParentRoute: () => rootRoute,
70
- path: '/workspaces/$wid/integrations',
65
+ path: '/w/$workspaceSlug/integrations',
71
66
  component: () => <div>Integrations gallery placeholder</div>,
72
67
  });
73
68
  const modelProviderSettingsRoute = createRoute({
74
69
  getParentRoute: () => rootRoute,
75
- path: '/workspaces/$wid/settings/agents',
70
+ path: '/w/$workspaceSlug/settings/agents',
76
71
  component: () => <div>Agent settings placeholder</div>,
77
72
  });
73
+ const projectSettingsGeneralRoute = createRoute({
74
+ getParentRoute: () => rootRoute,
75
+ path: '/w/$workspaceSlug/p/$projectSlug/settings/general',
76
+ component: () => element,
77
+ });
78
78
  const projectDetailRoute = createRoute({
79
79
  getParentRoute: () => rootRoute,
80
- path: '/workspaces/$wid/projects/$pid',
80
+ path: '/w/$workspaceSlug/p/$projectSlug',
81
81
  component: () => {
82
- const params = useParams({strict: false}) as {pid?: string};
83
- if (initialPath === `/workspaces/${PROJECT_TEST_WID}/projects/${params.pid}`) {
82
+ const params = useParams({strict: false}) as {projectSlug?: string};
83
+ if (initialPath === `/w/${PROJECT_TEST_WSLUG}/p/${params.projectSlug}`) {
84
84
  return element;
85
85
  }
86
86
 
@@ -92,10 +92,10 @@ function createTestRouter(path: string, element: ReactElement) {
92
92
  });
93
93
  const projectWorkflowsRoute = createRoute({
94
94
  getParentRoute: () => rootRoute,
95
- path: '/workspaces/$wid/projects/$pid/workflows',
95
+ path: '/w/$workspaceSlug/p/$projectSlug/workflows',
96
96
  component: () => {
97
- const params = useParams({strict: false}) as {pid?: string};
98
- if (initialPath === `/workspaces/${PROJECT_TEST_WID}/projects/${params.pid}/workflows`) {
97
+ const params = useParams({strict: false}) as {projectSlug?: string};
98
+ if (initialPath === `/w/${PROJECT_TEST_WSLUG}/p/${params.projectSlug}/workflows`) {
99
99
  return element;
100
100
  }
101
101
 
@@ -111,14 +111,18 @@ function createTestRouter(path: string, element: ReactElement) {
111
111
  workspaceNewProjectRoute,
112
112
  integrationsRoute,
113
113
  modelProviderSettingsRoute,
114
+ projectSettingsGeneralRoute,
114
115
  projectDetailRoute,
115
116
  projectWorkflowsRoute,
116
117
  ]),
117
118
  });
118
119
  }
119
120
 
120
- export function renderProjectPage(path: string, element: ReactElement): RenderResult {
121
- const queryClient = new QueryClient({defaultOptions: {queries: {retry: false}}});
121
+ export function renderProjectPage(
122
+ path: string,
123
+ element: ReactElement,
124
+ queryClient = new QueryClient({defaultOptions: {queries: {retry: false}}}),
125
+ ): RenderResult {
122
126
  const router = createTestRouter(path, element);
123
127
  const store = createStore();
124
128
  store.set(authStateAtom, authState);