@shipfox/client-integrations 5.0.0 → 6.0.2

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 (187) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +84 -0
  3. package/dist/application/complete-integration-callback.d.ts +13 -0
  4. package/dist/application/complete-integration-callback.d.ts.map +1 -0
  5. package/dist/application/complete-integration-callback.js +26 -0
  6. package/dist/application/complete-integration-callback.js.map +1 -0
  7. package/dist/components/connection-picker.d.ts +2 -2
  8. package/dist/components/connection-picker.d.ts.map +1 -1
  9. package/dist/components/connection-picker.js +4 -4
  10. package/dist/components/connection-picker.js.map +1 -1
  11. package/dist/components/installed-integrations-section.d.ts +8 -6
  12. package/dist/components/installed-integrations-section.d.ts.map +1 -1
  13. package/dist/components/installed-integrations-section.js +22 -15
  14. package/dist/components/installed-integrations-section.js.map +1 -1
  15. package/dist/components/integration-gallery-for-workspace.d.ts +2 -2
  16. package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -1
  17. package/dist/components/integration-gallery-for-workspace.js +14 -7
  18. package/dist/components/integration-gallery-for-workspace.js.map +1 -1
  19. package/dist/components/integration-gallery.d.ts +2 -2
  20. package/dist/components/integration-gallery.d.ts.map +1 -1
  21. package/dist/components/integration-gallery.js.map +1 -1
  22. package/dist/components/integration-usage-events.d.ts +1 -3
  23. package/dist/components/integration-usage-events.d.ts.map +1 -1
  24. package/dist/components/integration-usage-events.js +1 -125
  25. package/dist/components/integration-usage-events.js.map +1 -1
  26. package/dist/components/integration-usage-modal.d.ts +2 -2
  27. package/dist/components/integration-usage-modal.d.ts.map +1 -1
  28. package/dist/components/integration-usage-modal.js +2 -2
  29. package/dist/components/integration-usage-modal.js.map +1 -1
  30. package/dist/components/provider-grid.d.ts +7 -4
  31. package/dist/components/provider-grid.d.ts.map +1 -1
  32. package/dist/components/provider-grid.js +15 -10
  33. package/dist/components/provider-grid.js.map +1 -1
  34. package/dist/components/redirect-install-page.d.ts +2 -3
  35. package/dist/components/redirect-install-page.d.ts.map +1 -1
  36. package/dist/components/redirect-install-page.js +1 -1
  37. package/dist/components/redirect-install-page.js.map +1 -1
  38. package/dist/components/repository-picker.d.ts +2 -2
  39. package/dist/components/repository-picker.d.ts.map +1 -1
  40. package/dist/components/repository-picker.js +4 -4
  41. package/dist/components/repository-picker.js.map +1 -1
  42. package/dist/components/webhook/webhook-create-modal.d.ts +2 -2
  43. package/dist/components/webhook/webhook-create-modal.d.ts.map +1 -1
  44. package/dist/components/webhook/webhook-create-modal.js +8 -8
  45. package/dist/components/webhook/webhook-create-modal.js.map +1 -1
  46. package/dist/components/webhook/webhook-modals.stories.d.ts.map +1 -1
  47. package/dist/components/webhook/webhook-modals.stories.js +7 -7
  48. package/dist/components/webhook/webhook-modals.stories.js.map +1 -1
  49. package/dist/components/webhook/webhook-usage-details.d.ts.map +1 -1
  50. package/dist/components/webhook/webhook-usage-details.js +2 -2
  51. package/dist/components/webhook/webhook-usage-details.js.map +1 -1
  52. package/dist/connection-status-badge.d.ts +2 -2
  53. package/dist/connection-status-badge.d.ts.map +1 -1
  54. package/dist/connection-status-badge.js +11 -15
  55. package/dist/connection-status-badge.js.map +1 -1
  56. package/dist/core/models.d.ts +66 -0
  57. package/dist/core/models.d.ts.map +1 -0
  58. package/dist/core/models.js +156 -0
  59. package/dist/core/models.js.map +1 -0
  60. package/dist/feature.d.ts +7 -0
  61. package/dist/feature.d.ts.map +1 -1
  62. package/dist/feature.js +10 -9
  63. package/dist/feature.js.map +1 -1
  64. package/dist/hooks/api/integration-mapper.d.ts +11 -0
  65. package/dist/hooks/api/integration-mapper.d.ts.map +1 -0
  66. package/dist/hooks/api/integration-mapper.js +54 -0
  67. package/dist/hooks/api/integration-mapper.js.map +1 -0
  68. package/dist/hooks/api/integrations.d.ts +45 -240
  69. package/dist/hooks/api/integrations.d.ts.map +1 -1
  70. package/dist/hooks/api/integrations.js +78 -31
  71. package/dist/hooks/api/integrations.js.map +1 -1
  72. package/dist/hooks/api/webhook-connections.d.ts +12 -64
  73. package/dist/hooks/api/webhook-connections.d.ts.map +1 -1
  74. package/dist/hooks/api/webhook-connections.js +17 -9
  75. package/dist/hooks/api/webhook-connections.js.map +1 -1
  76. package/dist/index.d.ts +2 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +2 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/linear-callback.d.ts +2 -1
  81. package/dist/linear-callback.d.ts.map +1 -1
  82. package/dist/linear-callback.js +17 -15
  83. package/dist/linear-callback.js.map +1 -1
  84. package/dist/pages/gitea-install-page.d.ts.map +1 -1
  85. package/dist/pages/gitea-install-page.js +1 -10
  86. package/dist/pages/gitea-install-page.js.map +1 -1
  87. package/dist/pages/linear-callback-page.d.ts.map +1 -1
  88. package/dist/pages/linear-callback-page.js +19 -32
  89. package/dist/pages/linear-callback-page.js.map +1 -1
  90. package/dist/pages/linear-install-page.d.ts.map +1 -1
  91. package/dist/pages/linear-install-page.js +2 -1
  92. package/dist/pages/linear-install-page.js.map +1 -1
  93. package/dist/pages/sentry-callback-page.d.ts.map +1 -1
  94. package/dist/pages/sentry-callback-page.js +22 -25
  95. package/dist/pages/sentry-callback-page.js.map +1 -1
  96. package/dist/pages/sentry-install-page.d.ts.map +1 -1
  97. package/dist/pages/sentry-install-page.js +2 -1
  98. package/dist/pages/sentry-install-page.js.map +1 -1
  99. package/dist/pages/slack-callback-page.d.ts.map +1 -1
  100. package/dist/pages/slack-callback-page.js +21 -28
  101. package/dist/pages/slack-callback-page.js.map +1 -1
  102. package/dist/pages/slack-install-page.d.ts.map +1 -1
  103. package/dist/pages/slack-install-page.js +2 -1
  104. package/dist/pages/slack-install-page.js.map +1 -1
  105. package/dist/pages/source-control-onboarding-page.d.ts.map +1 -1
  106. package/dist/pages/source-control-onboarding-page.js +5 -1
  107. package/dist/pages/source-control-onboarding-page.js.map +1 -1
  108. package/dist/provider-catalog.d.ts.map +1 -1
  109. package/dist/provider-catalog.js +7 -6
  110. package/dist/provider-catalog.js.map +1 -1
  111. package/dist/routes/github-callback.d.ts +16 -0
  112. package/dist/routes/github-callback.d.ts.map +1 -1
  113. package/dist/routes/github-callback.js +43 -21
  114. package/dist/routes/github-callback.js.map +1 -1
  115. package/dist/routes/linear-callback.d.ts +2 -0
  116. package/dist/routes/linear-callback.d.ts.map +1 -1
  117. package/dist/routes/linear-callback.js +2 -0
  118. package/dist/routes/linear-callback.js.map +1 -1
  119. package/dist/routes/sentry-callback.d.ts +2 -0
  120. package/dist/routes/sentry-callback.d.ts.map +1 -1
  121. package/dist/routes/sentry-callback.js +2 -0
  122. package/dist/routes/sentry-callback.js.map +1 -1
  123. package/dist/routes/slack-callback.d.ts +2 -0
  124. package/dist/routes/slack-callback.d.ts.map +1 -1
  125. package/dist/routes/slack-callback.js +2 -0
  126. package/dist/routes/slack-callback.js.map +1 -1
  127. package/dist/sentry-callback.d.ts +2 -1
  128. package/dist/sentry-callback.d.ts.map +1 -1
  129. package/dist/sentry-callback.js +18 -19
  130. package/dist/sentry-callback.js.map +1 -1
  131. package/dist/slack-callback.d.ts +2 -1
  132. package/dist/slack-callback.d.ts.map +1 -1
  133. package/dist/slack-callback.js +17 -15
  134. package/dist/slack-callback.js.map +1 -1
  135. package/dist/tsconfig.test.tsbuildinfo +1 -1
  136. package/package.json +17 -17
  137. package/src/application/complete-integration-callback.test.ts +41 -0
  138. package/src/application/complete-integration-callback.ts +39 -0
  139. package/src/components/connection-picker.tsx +7 -7
  140. package/src/components/installed-integrations-section.tsx +29 -24
  141. package/src/components/integration-gallery-for-workspace.tsx +23 -16
  142. package/src/components/integration-gallery.tsx +3 -3
  143. package/src/components/integration-usage-events.ts +1 -116
  144. package/src/components/integration-usage-modal.tsx +4 -4
  145. package/src/components/provider-grid.tsx +30 -18
  146. package/src/components/redirect-install-page.test.tsx +2 -6
  147. package/src/components/redirect-install-page.tsx +3 -2
  148. package/src/components/repository-picker.tsx +6 -6
  149. package/src/components/webhook/webhook-create-modal.tsx +12 -11
  150. package/src/components/webhook/webhook-modals.stories.tsx +10 -10
  151. package/src/components/webhook/webhook-usage-details.tsx +2 -4
  152. package/src/connection-status-badge.tsx +8 -14
  153. package/src/core/models.ts +200 -0
  154. package/src/feature.ts +12 -10
  155. package/src/hooks/api/integration-mapper.ts +64 -0
  156. package/src/hooks/api/integrations.test.ts +44 -22
  157. package/src/hooks/api/integrations.ts +229 -84
  158. package/src/hooks/api/webhook-connections.ts +58 -17
  159. package/src/index.ts +2 -1
  160. package/src/linear-callback.ts +24 -16
  161. package/src/pages/gitea-install-page.tsx +1 -10
  162. package/src/pages/linear-callback-page.tsx +18 -30
  163. package/src/pages/linear-install-page.tsx +2 -1
  164. package/src/pages/sentry-callback-page.test.tsx +5 -1
  165. package/src/pages/sentry-callback-page.tsx +28 -25
  166. package/src/pages/sentry-install-page.tsx +2 -1
  167. package/src/pages/slack-callback-page.tsx +20 -26
  168. package/src/pages/slack-install-page.tsx +2 -1
  169. package/src/pages/source-control-onboarding-page.tsx +5 -1
  170. package/src/provider-catalog.ts +7 -6
  171. package/src/routes/github-callback.test.ts +35 -1
  172. package/src/routes/github-callback.tsx +47 -21
  173. package/src/routes/linear-callback.tsx +5 -1
  174. package/src/routes/sentry-callback.tsx +5 -1
  175. package/src/routes/slack-callback.tsx +6 -1
  176. package/src/sentry-callback.test.ts +2 -1
  177. package/src/sentry-callback.ts +30 -20
  178. package/src/slack-callback.ts +24 -16
  179. package/tsconfig.build.tsbuildinfo +1 -1
  180. package/vercel.json +5 -0
  181. package/dist/integration-icon.d.ts +0 -17
  182. package/dist/integration-icon.d.ts.map +0 -1
  183. package/dist/integration-icon.js +0 -22
  184. package/dist/integration-icon.js.map +0 -1
  185. package/src/integration-icon.react.test.tsx +0 -40
  186. package/src/integration-icon.test.ts +0 -19
  187. package/src/integration-icon.tsx +0 -26
@@ -1,21 +1,21 @@
1
- import type {
2
- IntegrationProviderDto,
3
- ListIntegrationProvidersResponseDto,
4
- } from '@shipfox/api-integration-core-dto';
5
1
  import {QueryLoadError} from '@shipfox/client-ui';
2
+ import {IntegrationIcon} from '@shipfox/integration-icons';
6
3
  import {Card} from '@shipfox/react-ui/card';
7
4
  import {EmptyState} from '@shipfox/react-ui/empty-state';
8
5
  import {Icon} from '@shipfox/react-ui/icon';
9
6
  import {Skeleton} from '@shipfox/react-ui/skeleton';
10
7
  import {Text} from '@shipfox/react-ui/typography';
11
8
  import {cn} from '@shipfox/react-ui/utils';
12
- import type {UseQueryResult} from '@tanstack/react-query';
13
9
  import {Link} from '@tanstack/react-router';
14
- import {IntegrationIcon} from '#integration-icon.js';
10
+ import type {IntegrationProvider} from '#core/models.js';
15
11
  import {PROVIDER_CATALOG} from '#provider-catalog.js';
16
12
 
17
13
  export interface ProviderGridProps {
18
- providersQuery: UseQueryResult<ListIntegrationProvidersResponseDto, Error>;
14
+ providers: IntegrationProvider[];
15
+ isPending: boolean;
16
+ isFetching?: boolean;
17
+ error?: Error | null | undefined;
18
+ onRetry?: () => void;
19
19
  workspaceId: string;
20
20
  emptyMessage: string;
21
21
  loadingLabel?: string;
@@ -29,27 +29,39 @@ export const PROVIDER_SURFACE_CLASS =
29
29
  'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
30
30
 
31
31
  export function ProviderGrid({
32
- providersQuery,
32
+ providers,
33
+ isPending,
34
+ isFetching = false,
35
+ error,
36
+ onRetry,
33
37
  workspaceId,
34
38
  emptyMessage,
35
39
  loadingLabel = 'Loading providers',
36
40
  errorSubject = 'available integrations',
37
41
  onOpenProvider,
38
42
  }: ProviderGridProps) {
39
- const providers = providersQuery.data?.providers ?? [];
40
43
  const installableProviders = providers.filter((provider) => PROVIDER_CATALOG[provider.provider]);
41
44
 
42
- if (providersQuery.isPending) return <ProviderGridSkeleton label={loadingLabel} />;
45
+ if (isPending) return <ProviderGridSkeleton label={loadingLabel} />;
43
46
 
44
- if (providersQuery.isError && providersQuery.data === undefined) {
47
+ if (error) {
45
48
  return (
46
49
  <div className={cn(PROVIDER_SURFACE_CLASS, 'px-16')}>
47
- <QueryLoadError query={providersQuery} subject={errorSubject} />
50
+ <QueryLoadError
51
+ query={{
52
+ isError: true,
53
+ isFetching,
54
+ data: undefined,
55
+ error,
56
+ refetch: onRetry ?? (() => undefined),
57
+ }}
58
+ subject={errorSubject}
59
+ />
48
60
  </div>
49
61
  );
50
62
  }
51
63
 
52
- if (providersQuery.data !== undefined && installableProviders.length === 0) {
64
+ if (installableProviders.length === 0) {
53
65
  return (
54
66
  <div className={cn(PROVIDER_SURFACE_CLASS, 'px-16')}>
55
67
  <EmptyState
@@ -81,7 +93,7 @@ function ProviderCard({
81
93
  workspaceId,
82
94
  onOpenProvider,
83
95
  }: {
84
- provider: IntegrationProviderDto;
96
+ provider: IntegrationProvider;
85
97
  workspaceId: string;
86
98
  onOpenProvider?: ((provider: string) => void) | undefined;
87
99
  }) {
@@ -92,7 +104,7 @@ function ProviderCard({
92
104
  return (
93
105
  <button
94
106
  type="button"
95
- aria-label={`Add ${provider.display_name}`}
107
+ aria-label={`Add ${provider.displayName}`}
96
108
  onClick={() => onOpenProvider?.(provider.provider)}
97
109
  className="group flex h-full w-full min-w-0 items-center justify-between gap-12 rounded-8 border border-border-neutral-base bg-background-neutral-base p-16 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none"
98
110
  >
@@ -105,7 +117,7 @@ function ProviderCard({
105
117
  <Link
106
118
  to={catalog.setupPath}
107
119
  params={{wid: workspaceId}}
108
- aria-label={`Install ${provider.display_name}`}
120
+ aria-label={`Install ${provider.displayName}`}
109
121
  className="group flex h-full min-w-0 items-center justify-between gap-12 rounded-8 border border-border-neutral-base bg-background-neutral-base p-16 transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none"
110
122
  >
111
123
  <ProviderCardContent provider={provider} action="Install" />
@@ -117,7 +129,7 @@ function ProviderCardContent({
117
129
  provider,
118
130
  action,
119
131
  }: {
120
- provider: IntegrationProviderDto;
132
+ provider: IntegrationProvider;
121
133
  action: 'Add' | 'Install';
122
134
  }) {
123
135
  return (
@@ -129,7 +141,7 @@ function ProviderCardContent({
129
141
  className="size-24 shrink-0 text-foreground-neutral-base"
130
142
  />
131
143
  <Text as="span" size="md" bold className="truncate">
132
- {provider.display_name}
144
+ {provider.displayName}
133
145
  </Text>
134
146
  </span>
135
147
  <span className="flex shrink-0 items-center gap-4 text-foreground-neutral-muted transition-colors group-hover:text-foreground-highlight-interactive">
@@ -26,9 +26,7 @@ function renderInstallPage(
26
26
 
27
27
  describe('RedirectInstallPage', () => {
28
28
  test('requests the install URL and leaves the app', async () => {
29
- const installRequest = vi
30
- .fn()
31
- .mockResolvedValue({install_url: 'https://provider.test/install'});
29
+ const installRequest = vi.fn().mockResolvedValue({installUrl: 'https://provider.test/install'});
32
30
  const assignLocation = vi.fn();
33
31
  const beforeRedirect = vi.fn();
34
32
 
@@ -68,9 +66,7 @@ describe('RedirectInstallPage', () => {
68
66
  });
69
67
 
70
68
  test('requests the install URL exactly once in Strict Mode', async () => {
71
- const installRequest = vi
72
- .fn()
73
- .mockResolvedValue({install_url: 'https://provider.test/install'});
69
+ const installRequest = vi.fn().mockResolvedValue({installUrl: 'https://provider.test/install'});
74
70
 
75
71
  renderInstallPage(
76
72
  {
@@ -6,9 +6,10 @@ import {FullPageLoader} from '@shipfox/react-ui/loader';
6
6
  import {Text} from '@shipfox/react-ui/typography';
7
7
  import {Link} from '@tanstack/react-router';
8
8
  import {useEffect, useRef, useState} from 'react';
9
+ import type {InstallRedirect} from '#core/models.js';
9
10
 
10
11
  interface RedirectInstallPageProps {
11
- installRequest: (body: {workspace_id: string}) => Promise<{install_url: string}>;
12
+ installRequest: (body: {workspace_id: string}) => Promise<InstallRedirect>;
12
13
  errorFallbackMessage: string;
13
14
  loadingLabel?: string;
14
15
  /**
@@ -44,7 +45,7 @@ export function RedirectInstallPage({
44
45
  }
45
46
  installRequest({workspace_id: workspace.id})
46
47
  .then((response) => {
47
- assignLocation(response.install_url);
48
+ assignLocation(response.installUrl);
48
49
  })
49
50
  .catch((error: unknown) => {
50
51
  setErrorMessage(error instanceof ApiError ? error.message : errorFallbackMessage);
@@ -1,4 +1,3 @@
1
- import type {RepositoryDto} from '@shipfox/api-integration-core-dto';
2
1
  import {Button} from '@shipfox/react-ui/button';
3
2
  import {Input} from '@shipfox/react-ui/input';
4
3
  import {Label} from '@shipfox/react-ui/label';
@@ -6,6 +5,7 @@ import {RadioGroup, RadioGroupItem} from '@shipfox/react-ui/radio-group';
6
5
  import {Skeleton} from '@shipfox/react-ui/skeleton';
7
6
  import {Text} from '@shipfox/react-ui/typography';
8
7
  import {useId} from 'react';
8
+ import type {Repository} from '#core/models.js';
9
9
 
10
10
  export function RepositoryPicker({
11
11
  repositories,
@@ -20,7 +20,7 @@ export function RepositoryPicker({
20
20
  onSearchChange,
21
21
  searchDisabled,
22
22
  }: {
23
- repositories: RepositoryDto[];
23
+ repositories: Repository[];
24
24
  selectedRepositoryId: string | undefined;
25
25
  onSelect: (repositoryId: string) => void;
26
26
  isLoading: boolean;
@@ -69,16 +69,16 @@ export function RepositoryPicker({
69
69
  >
70
70
  {repositories.map((repository) => (
71
71
  <RadioGroupItem
72
- key={repository.external_repository_id}
73
- value={repository.external_repository_id}
72
+ key={repository.externalRepositoryId}
73
+ value={repository.externalRepositoryId}
74
74
  className="p-12"
75
75
  >
76
76
  <span className="flex min-w-0 items-center justify-between gap-10">
77
77
  <Text as="span" size="sm" bold className="truncate">
78
- {repository.full_name}
78
+ {repository.fullName}
79
79
  </Text>
80
80
  <Text as="span" size="xs" className="shrink-0 text-foreground-neutral-muted">
81
- {repository.default_branch}
81
+ {repository.defaultBranch}
82
82
  </Text>
83
83
  </span>
84
84
  </RadioGroupItem>
@@ -1,8 +1,6 @@
1
- import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';
2
1
  import {
3
2
  createWebhookConnectionBodySchema,
4
3
  WEBHOOK_RESERVED_SLUGS,
5
- type WebhookConnectionDto,
6
4
  webhookSlugSchema,
7
5
  } from '@shipfox/api-integration-webhook-dto';
8
6
  import {displayNameFieldError} from '@shipfox/client-ui';
@@ -21,6 +19,7 @@ import {toast} from '@shipfox/react-ui/toast';
21
19
  import {Code, Text} from '@shipfox/react-ui/typography';
22
20
  import {useForm} from '@tanstack/react-form';
23
21
  import {useEffect, useRef, useState} from 'react';
22
+ import type {IntegrationConnection, WebhookConnection} from '#core/models.js';
24
23
  import {useCreateWebhookConnectionMutation} from '#hooks/api/webhook-connections.js';
25
24
  import {webhookCreateErrorToFormError} from './webhook-form-errors.js';
26
25
 
@@ -28,7 +27,7 @@ interface WebhookCreateModalProps {
28
27
  workspaceId: string;
29
28
  open: boolean;
30
29
  onOpenChange: (open: boolean) => void;
31
- onCreated: (connection: IntegrationConnectionDto) => void;
30
+ onCreated: (connection: IntegrationConnection) => void;
32
31
  }
33
32
 
34
33
  export function WebhookCreateModal({
@@ -53,7 +52,7 @@ export function WebhookCreateModal({
53
52
  slug: value.slug.trim(),
54
53
  });
55
54
  const connection = await createWebhook.mutateAsync(body);
56
- onCreated(toIntegrationConnection(connection));
55
+ onCreated(webhookConnectionToIntegrationConnection(connection));
57
56
  toast.success('Webhook created.');
58
57
  onOpenChange(false);
59
58
  } catch (error) {
@@ -231,17 +230,19 @@ function suggestWebhookSlug(name: string): string {
231
230
  return suffix ? `${prefix}-${suffix}` : prefix;
232
231
  }
233
232
 
234
- function toIntegrationConnection(connection: WebhookConnectionDto): IntegrationConnectionDto {
233
+ function webhookConnectionToIntegrationConnection(
234
+ connection: WebhookConnection,
235
+ ): IntegrationConnection {
235
236
  return {
236
237
  id: connection.id,
237
- workspace_id: connection.workspace_id,
238
+ workspaceId: connection.workspaceId,
238
239
  provider: 'webhook',
239
- external_account_id: connection.slug,
240
+ externalAccountId: connection.slug,
240
241
  slug: connection.slug,
241
- display_name: connection.name,
242
- lifecycle_status: connection.lifecycle_status,
242
+ displayName: connection.name,
243
+ lifecycleStatus: connection.lifecycleStatus,
243
244
  capabilities: [],
244
- created_at: connection.created_at,
245
- updated_at: connection.updated_at,
245
+ createdAt: connection.createdAt,
246
+ updatedAt: connection.updatedAt,
246
247
  };
247
248
  }
@@ -1,4 +1,3 @@
1
- import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';
2
1
  import type {WebhookConnectionDto} from '@shipfox/api-integration-webhook-dto';
3
2
  import {WEBHOOK_RECEIVED_EVENT} from '@shipfox/api-integration-webhook-dto';
4
3
  import {configureApiClient} from '@shipfox/client-api';
@@ -14,6 +13,7 @@ import {
14
13
  RouterProvider,
15
14
  } from '@tanstack/react-router';
16
15
  import {useMemo} from 'react';
16
+ import type {IntegrationConnection} from '#core/models.js';
17
17
  import {IntegrationDeleteConfirmModal} from '../integration-delete-confirm-modal.js';
18
18
  import {IntegrationUsageModal} from '../integration-usage-modal.js';
19
19
  import {CopyableValue} from './copyable-value.js';
@@ -52,22 +52,22 @@ const disabledConnection: WebhookConnectionDto = {
52
52
  lifecycle_status: 'disabled',
53
53
  };
54
54
 
55
- const activeIntegrationConnection: IntegrationConnectionDto = {
55
+ const activeIntegrationConnection: IntegrationConnection = {
56
56
  id: CONNECTION_ID,
57
- workspace_id: WORKSPACE_ID,
57
+ workspaceId: WORKSPACE_ID,
58
58
  provider: 'webhook',
59
- external_account_id: activeConnection.slug,
59
+ externalAccountId: activeConnection.slug,
60
60
  slug: activeConnection.slug,
61
- display_name: activeConnection.name,
62
- lifecycle_status: activeConnection.lifecycle_status,
61
+ displayName: activeConnection.name,
62
+ lifecycleStatus: activeConnection.lifecycle_status,
63
63
  capabilities: [],
64
- created_at: activeConnection.created_at,
65
- updated_at: activeConnection.updated_at,
64
+ createdAt: activeConnection.created_at,
65
+ updatedAt: activeConnection.updated_at,
66
66
  };
67
67
 
68
- const disabledIntegrationConnection: IntegrationConnectionDto = {
68
+ const disabledIntegrationConnection: IntegrationConnection = {
69
69
  ...activeIntegrationConnection,
70
- lifecycle_status: 'disabled',
70
+ lifecycleStatus: 'disabled',
71
71
  };
72
72
 
73
73
  function WebhookModalStory({scenario}: WebhookModalStoryProps) {
@@ -12,9 +12,7 @@ interface WebhookUsageDetailsProps {
12
12
 
13
13
  export function WebhookUsageDetails({workspaceId, connectionId}: WebhookUsageDetailsProps) {
14
14
  const connectionsQuery = useWebhookConnectionsQuery(workspaceId);
15
- const connection = connectionsQuery.data?.connections.find(
16
- (candidate) => candidate.id === connectionId,
17
- );
15
+ const connection = connectionsQuery.data?.find((candidate) => candidate.id === connectionId);
18
16
 
19
17
  if (connectionsQuery.isPending) {
20
18
  return (
@@ -36,7 +34,7 @@ export function WebhookUsageDetails({workspaceId, connectionId}: WebhookUsageDet
36
34
  <Text size="sm" bold>
37
35
  Inbound URL
38
36
  </Text>
39
- <CopyableValue label="inbound URL" value={connection.inbound_url} />
37
+ <CopyableValue label="inbound URL" value={connection.inboundUrl} />
40
38
  <WebhookPublicEndpointAlert />
41
39
  </div>
42
40
  </div>
@@ -1,19 +1,9 @@
1
- import type {IntegrationConnectionLifecycleStatusDto} from '@shipfox/api-integration-core-dto';
2
1
  import {Badge} from '@shipfox/react-ui/badge';
3
2
  import type {IconName} from '@shipfox/react-ui/icon';
4
-
5
- const lifecyclePills: Record<
6
- IntegrationConnectionLifecycleStatusDto,
7
- {variant: 'warning' | 'error'; label: string; iconLeft?: IconName} | undefined
8
- > = {
9
- // `active` is the expected state and carries no badge.
10
- active: undefined,
11
- disabled: {variant: 'warning', label: 'Disabled', iconLeft: 'errorWarningLine'},
12
- error: {variant: 'error', label: 'Error'},
13
- };
3
+ import {type ConnectionLifecycleStatus, connectionLifecyclePresentation} from '#core/models.js';
14
4
 
15
5
  export interface ConnectionStatusBadgeProps {
16
- status: IntegrationConnectionLifecycleStatusDto;
6
+ status: ConnectionLifecycleStatus;
17
7
  className?: string;
18
8
  }
19
9
 
@@ -23,8 +13,12 @@ export interface ConnectionStatusBadgeProps {
23
13
  * nothing for the expected `active` state.
24
14
  */
25
15
  export function ConnectionStatusBadge({status, className}: ConnectionStatusBadgeProps) {
26
- const pill = lifecyclePills[status];
27
- if (!pill) return null;
16
+ const presentation = connectionLifecyclePresentation(status);
17
+ if (presentation.kind === 'active') return null;
18
+ const pill: {variant: 'warning' | 'error'; label: string; iconLeft?: IconName} =
19
+ presentation.kind === 'disabled'
20
+ ? {variant: 'warning', label: presentation.label, iconLeft: 'errorWarningLine'}
21
+ : {variant: 'error', label: presentation.label};
28
22
  return (
29
23
  <Badge
30
24
  variant={pill.variant}
@@ -0,0 +1,200 @@
1
+ export type IntegrationCapability = 'source_control' | 'agent_tools';
2
+ export type ConnectionLifecycleStatus = 'active' | 'disabled' | 'error';
3
+
4
+ export interface IntegrationProvider {
5
+ provider: string;
6
+ displayName: string;
7
+ capabilities: IntegrationCapability[];
8
+ }
9
+
10
+ export interface IntegrationConnection {
11
+ id: string;
12
+ workspaceId: string;
13
+ provider: string;
14
+ externalAccountId: string;
15
+ slug: string;
16
+ displayName: string;
17
+ lifecycleStatus: ConnectionLifecycleStatus;
18
+ capabilities: IntegrationCapability[];
19
+ externalUrl?: string | undefined;
20
+ createdAt: string;
21
+ updatedAt: string;
22
+ }
23
+
24
+ export interface Repository {
25
+ connectionId: string;
26
+ externalRepositoryId: string;
27
+ owner: string;
28
+ name: string;
29
+ fullName: string;
30
+ defaultBranch: string;
31
+ visibility: 'public' | 'private' | 'internal' | 'unknown';
32
+ cloneUrl: string;
33
+ htmlUrl: string;
34
+ }
35
+
36
+ export interface RepositoryPage {
37
+ repositories: Repository[];
38
+ nextCursor?: string;
39
+ }
40
+
41
+ export interface WebhookConnection {
42
+ id: string;
43
+ workspaceId: string;
44
+ name: string;
45
+ slug: string;
46
+ lifecycleStatus: ConnectionLifecycleStatus;
47
+ inboundUrl: string;
48
+ createdAt: string;
49
+ updatedAt: string;
50
+ }
51
+
52
+ export interface InstallRedirect {
53
+ installUrl: string;
54
+ }
55
+
56
+ export type IntegrationUsageEvent = {
57
+ value: string;
58
+ label: string;
59
+ };
60
+
61
+ export type ConnectionLifecyclePresentation =
62
+ | {kind: 'active'}
63
+ | {kind: 'disabled'; label: 'Disabled'}
64
+ | {kind: 'error'; label: 'Error'};
65
+
66
+ const githubUsageEvents = [
67
+ 'push',
68
+ 'branch_protection_configuration',
69
+ 'branch_protection_rule',
70
+ 'check_run',
71
+ 'check_suite',
72
+ 'code_scanning_alert',
73
+ 'commit_comment',
74
+ 'create',
75
+ 'custom_property',
76
+ 'custom_property_values',
77
+ 'delete',
78
+ 'dependabot_alert',
79
+ 'deploy_key',
80
+ 'deployment',
81
+ 'deployment_protection_rule',
82
+ 'deployment_review',
83
+ 'deployment_status',
84
+ 'discussion',
85
+ 'discussion_comment',
86
+ 'fork',
87
+ 'github_app_authorization',
88
+ 'gollum',
89
+ 'installation',
90
+ 'installation_repositories',
91
+ 'installation_target',
92
+ 'issue_comment',
93
+ 'issue_dependencies',
94
+ 'issues',
95
+ 'label',
96
+ 'marketplace_purchase',
97
+ 'member',
98
+ 'membership',
99
+ 'merge_group',
100
+ 'meta',
101
+ 'milestone',
102
+ 'org_block',
103
+ 'organization',
104
+ 'package',
105
+ 'page_build',
106
+ 'personal_access_token_request',
107
+ 'ping',
108
+ 'project',
109
+ 'project_card',
110
+ 'project_column',
111
+ 'projects_v2',
112
+ 'projects_v2_item',
113
+ 'projects_v2_status_update',
114
+ 'public',
115
+ 'pull_request',
116
+ 'pull_request_review',
117
+ 'pull_request_review_comment',
118
+ 'pull_request_review_thread',
119
+ 'registry_package',
120
+ 'release',
121
+ 'repository',
122
+ 'repository_advisory',
123
+ 'repository_dispatch',
124
+ 'repository_import',
125
+ 'repository_ruleset',
126
+ 'repository_vulnerability_alert',
127
+ 'secret_scanning_alert',
128
+ 'secret_scanning_alert_location',
129
+ 'secret_scanning_scan',
130
+ 'security_advisory',
131
+ 'security_and_analysis',
132
+ 'sponsorship',
133
+ 'star',
134
+ 'status',
135
+ 'sub_issues',
136
+ 'team',
137
+ 'team_add',
138
+ 'watch',
139
+ 'workflow_dispatch',
140
+ 'workflow_job',
141
+ 'workflow_run',
142
+ ] as const;
143
+
144
+ const linearUsageEvents = [
145
+ 'Issue.create',
146
+ 'Issue.update',
147
+ 'Issue.remove',
148
+ 'Comment.create',
149
+ 'Comment.update',
150
+ 'Comment.remove',
151
+ 'IssueLabel.create',
152
+ 'IssueLabel.update',
153
+ 'IssueLabel.remove',
154
+ 'Project.create',
155
+ 'Project.update',
156
+ 'Project.remove',
157
+ 'Cycle.create',
158
+ 'Cycle.update',
159
+ 'Cycle.remove',
160
+ 'agentSession.created',
161
+ 'agentSession.prompted',
162
+ ] as const;
163
+
164
+ const sentryIssueActions = ['created', 'resolved', 'assigned', 'archived', 'unresolved'] as const;
165
+
166
+ export function usageEventsForConnection(
167
+ connection: Pick<IntegrationConnection, 'provider' | 'capabilities'>,
168
+ ): IntegrationUsageEvent[] {
169
+ if (connection.provider === 'webhook') return [{value: 'received', label: 'received'}];
170
+ if (connection.provider === 'github')
171
+ return githubUsageEvents.map((value) => ({value, label: value}));
172
+ if (connection.provider === 'sentry')
173
+ return sentryIssueActions.map((action) => ({
174
+ value: `issue.${action}`,
175
+ label: `issue.${action}`,
176
+ }));
177
+ if (connection.provider === 'linear')
178
+ return linearUsageEvents.map((value) => ({value, label: value}));
179
+ if (connection.capabilities.includes('source_control')) return [{value: 'push', label: 'push'}];
180
+ return [{value: 'received', label: 'received'}];
181
+ }
182
+
183
+ export function connectionLifecyclePresentation(
184
+ status: ConnectionLifecycleStatus,
185
+ ): ConnectionLifecyclePresentation {
186
+ if (status === 'disabled') return {kind: 'disabled', label: 'Disabled'};
187
+ if (status === 'error') return {kind: 'error', label: 'Error'};
188
+ return {kind: 'active'};
189
+ }
190
+
191
+ export function hasCapability(
192
+ connection: IntegrationConnection,
193
+ capability: IntegrationCapability,
194
+ ) {
195
+ return connection.capabilities.includes(capability);
196
+ }
197
+
198
+ export function isUsableConnection(connection: IntegrationConnection) {
199
+ return connection.lifecycleStatus === 'active';
200
+ }
package/src/feature.ts CHANGED
@@ -1,4 +1,14 @@
1
- import {defineClientFeature} from '@shipfox/client-shell';
1
+ import {defineClientFeature, type SettingsSectionEntry} from '@shipfox/client-shell';
2
+
3
+ export const integrationsSettingsSections = [
4
+ {
5
+ id: 'settings.integrations',
6
+ pathSegment: 'integrations',
7
+ label: 'Integrations',
8
+ icon: 'plugLine',
9
+ order: 700,
10
+ },
11
+ ] as const satisfies readonly SettingsSectionEntry[];
2
12
 
3
13
  export const integrationsFeature = defineClientFeature({
4
14
  id: 'shipfox.integrations',
@@ -59,13 +69,5 @@ export const integrationsFeature = defineClientFeature({
59
69
  impl: '@shipfox/client-integrations/routes/integrations-settings',
60
70
  },
61
71
  ],
62
- settingsSections: [
63
- {
64
- id: 'settings.integrations',
65
- pathSegment: 'integrations',
66
- label: 'Integrations',
67
- icon: 'plugLine',
68
- order: 700,
69
- },
70
- ],
72
+ settingsSections: integrationsSettingsSections,
71
73
  });
@@ -0,0 +1,64 @@
1
+ import type {
2
+ IntegrationConnectionDto,
3
+ IntegrationProviderDto,
4
+ RepositoryDto,
5
+ } from '@shipfox/api-integration-core-dto';
6
+ import type {WebhookConnectionDto} from '@shipfox/api-integration-webhook-dto';
7
+ import type {
8
+ InstallRedirect,
9
+ IntegrationConnection,
10
+ IntegrationProvider,
11
+ Repository,
12
+ WebhookConnection,
13
+ } from '#core/models.js';
14
+
15
+ export function toIntegrationProvider(dto: IntegrationProviderDto): IntegrationProvider {
16
+ return {provider: dto.provider, displayName: dto.display_name, capabilities: dto.capabilities};
17
+ }
18
+
19
+ export function toInstallRedirect(dto: {install_url: string}): InstallRedirect {
20
+ return {installUrl: dto.install_url};
21
+ }
22
+
23
+ export function toIntegrationConnection(dto: IntegrationConnectionDto): IntegrationConnection {
24
+ return {
25
+ id: dto.id,
26
+ workspaceId: dto.workspace_id,
27
+ provider: dto.provider,
28
+ externalAccountId: dto.external_account_id,
29
+ slug: dto.slug,
30
+ displayName: dto.display_name,
31
+ lifecycleStatus: dto.lifecycle_status,
32
+ capabilities: dto.capabilities,
33
+ externalUrl: dto.external_url,
34
+ createdAt: dto.created_at,
35
+ updatedAt: dto.updated_at,
36
+ };
37
+ }
38
+
39
+ export function toRepository(dto: RepositoryDto): Repository {
40
+ return {
41
+ connectionId: dto.connection_id,
42
+ externalRepositoryId: dto.external_repository_id,
43
+ owner: dto.owner,
44
+ name: dto.name,
45
+ fullName: dto.full_name,
46
+ defaultBranch: dto.default_branch,
47
+ visibility: dto.visibility,
48
+ cloneUrl: dto.clone_url,
49
+ htmlUrl: dto.html_url,
50
+ };
51
+ }
52
+
53
+ export function toWebhookConnection(dto: WebhookConnectionDto): WebhookConnection {
54
+ return {
55
+ id: dto.id,
56
+ workspaceId: dto.workspace_id,
57
+ name: dto.name,
58
+ slug: dto.slug,
59
+ lifecycleStatus: dto.lifecycle_status,
60
+ inboundUrl: dto.inbound_url,
61
+ createdAt: dto.created_at,
62
+ updatedAt: dto.updated_at,
63
+ };
64
+ }