@shipfox/client-integrations 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.
Files changed (243) hide show
  1. package/.storybook/main.ts +1 -0
  2. package/.storybook/preview.css +4 -0
  3. package/.storybook/preview.tsx +56 -0
  4. package/.turbo/turbo-build.log +2 -0
  5. package/.turbo/turbo-type$colon$emit.log +1 -0
  6. package/.turbo/turbo-type.log +1 -0
  7. package/CHANGELOG.md +207 -0
  8. package/LICENSE +21 -0
  9. package/dist/components/connection-picker.d.ts +7 -0
  10. package/dist/components/connection-picker.d.ts.map +1 -0
  11. package/dist/components/connection-picker.js +98 -0
  12. package/dist/components/connection-picker.js.map +1 -0
  13. package/dist/components/installed-integrations-section.d.ts +14 -0
  14. package/dist/components/installed-integrations-section.d.ts.map +1 -0
  15. package/dist/components/installed-integrations-section.js +203 -0
  16. package/dist/components/installed-integrations-section.js.map +1 -0
  17. package/dist/components/integration-delete-confirm-modal.d.ts +10 -0
  18. package/dist/components/integration-delete-confirm-modal.d.ts.map +1 -0
  19. package/dist/components/integration-delete-confirm-modal.js +61 -0
  20. package/dist/components/integration-delete-confirm-modal.js.map +1 -0
  21. package/dist/components/integration-gallery-for-workspace.d.ts +9 -0
  22. package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -0
  23. package/dist/components/integration-gallery-for-workspace.js +169 -0
  24. package/dist/components/integration-gallery-for-workspace.js.map +1 -0
  25. package/dist/components/integration-gallery.d.ts +8 -0
  26. package/dist/components/integration-gallery.d.ts.map +1 -0
  27. package/dist/components/integration-gallery.js +26 -0
  28. package/dist/components/integration-gallery.js.map +1 -0
  29. package/dist/components/integration-gallery.stories.d.ts +26 -0
  30. package/dist/components/integration-gallery.stories.d.ts.map +1 -0
  31. package/dist/components/integration-gallery.stories.js +335 -0
  32. package/dist/components/integration-gallery.stories.js.map +1 -0
  33. package/dist/components/integration-usage-events.d.ts +4 -0
  34. package/dist/components/integration-usage-events.d.ts.map +1 -0
  35. package/dist/components/integration-usage-events.js +127 -0
  36. package/dist/components/integration-usage-events.js.map +1 -0
  37. package/dist/components/integration-usage-modal.d.ts +16 -0
  38. package/dist/components/integration-usage-modal.d.ts.map +1 -0
  39. package/dist/components/integration-usage-modal.js +184 -0
  40. package/dist/components/integration-usage-modal.js.map +1 -0
  41. package/dist/components/provider-grid.d.ts +14 -0
  42. package/dist/components/provider-grid.d.ts.map +1 -0
  43. package/dist/components/provider-grid.js +152 -0
  44. package/dist/components/provider-grid.js.map +1 -0
  45. package/dist/components/redirect-install-page.d.ts +19 -0
  46. package/dist/components/redirect-install-page.d.ts.map +1 -0
  47. package/dist/components/redirect-install-page.js +68 -0
  48. package/dist/components/redirect-install-page.js.map +1 -0
  49. package/dist/components/repository-picker.d.ts +15 -0
  50. package/dist/components/repository-picker.d.ts.map +1 -0
  51. package/dist/components/repository-picker.js +78 -0
  52. package/dist/components/repository-picker.js.map +1 -0
  53. package/dist/components/webhook/copyable-value.d.ts +10 -0
  54. package/dist/components/webhook/copyable-value.d.ts.map +1 -0
  55. package/dist/components/webhook/copyable-value.js +65 -0
  56. package/dist/components/webhook/copyable-value.js.map +1 -0
  57. package/dist/components/webhook/webhook-create-modal.d.ts +10 -0
  58. package/dist/components/webhook/webhook-create-modal.d.ts.map +1 -0
  59. package/dist/components/webhook/webhook-create-modal.js +233 -0
  60. package/dist/components/webhook/webhook-create-modal.js.map +1 -0
  61. package/dist/components/webhook/webhook-form-errors.d.ts +11 -0
  62. package/dist/components/webhook/webhook-form-errors.d.ts.map +1 -0
  63. package/dist/components/webhook/webhook-form-errors.js +27 -0
  64. package/dist/components/webhook/webhook-form-errors.js.map +1 -0
  65. package/dist/components/webhook/webhook-modals.stories.d.ts +25 -0
  66. package/dist/components/webhook/webhook-modals.stories.d.ts.map +1 -0
  67. package/dist/components/webhook/webhook-modals.stories.js +222 -0
  68. package/dist/components/webhook/webhook-modals.stories.js.map +1 -0
  69. package/dist/components/webhook/webhook-public-endpoint-alert.d.ts +2 -0
  70. package/dist/components/webhook/webhook-public-endpoint-alert.d.ts.map +1 -0
  71. package/dist/components/webhook/webhook-public-endpoint-alert.js +20 -0
  72. package/dist/components/webhook/webhook-public-endpoint-alert.js.map +1 -0
  73. package/dist/components/webhook/webhook-usage-details.d.ts +7 -0
  74. package/dist/components/webhook/webhook-usage-details.d.ts.map +1 -0
  75. package/dist/components/webhook/webhook-usage-details.js +49 -0
  76. package/dist/components/webhook/webhook-usage-details.js.map +1 -0
  77. package/dist/connection-status-badge.d.ts +12 -0
  78. package/dist/connection-status-badge.d.ts.map +1 -0
  79. package/dist/connection-status-badge.js +34 -0
  80. package/dist/connection-status-badge.js.map +1 -0
  81. package/dist/feature.d.ts +37 -0
  82. package/dist/feature.d.ts.map +1 -0
  83. package/dist/feature.js +48 -0
  84. package/dist/feature.js.map +1 -0
  85. package/dist/hooks/api/integrations.d.ts +265 -0
  86. package/dist/hooks/api/integrations.d.ts.map +1 -0
  87. package/dist/hooks/api/integrations.js +229 -0
  88. package/dist/hooks/api/integrations.js.map +1 -0
  89. package/dist/hooks/api/webhook-connections.d.ts +91 -0
  90. package/dist/hooks/api/webhook-connections.d.ts.map +1 -0
  91. package/dist/hooks/api/webhook-connections.js +97 -0
  92. package/dist/hooks/api/webhook-connections.js.map +1 -0
  93. package/dist/index.d.ts +17 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +18 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/integration-icon.d.ts +17 -0
  98. package/dist/integration-icon.d.ts.map +1 -0
  99. package/dist/integration-icon.js +22 -0
  100. package/dist/integration-icon.js.map +1 -0
  101. package/dist/linear-callback.d.ts +17 -0
  102. package/dist/linear-callback.d.ts.map +1 -0
  103. package/dist/linear-callback.js +113 -0
  104. package/dist/linear-callback.js.map +1 -0
  105. package/dist/pages/gitea-form-errors.d.ts +11 -0
  106. package/dist/pages/gitea-form-errors.d.ts.map +1 -0
  107. package/dist/pages/gitea-form-errors.js +35 -0
  108. package/dist/pages/gitea-form-errors.js.map +1 -0
  109. package/dist/pages/gitea-install-page.d.ts +2 -0
  110. package/dist/pages/gitea-install-page.d.ts.map +1 -0
  111. package/dist/pages/gitea-install-page.js +139 -0
  112. package/dist/pages/gitea-install-page.js.map +1 -0
  113. package/dist/pages/github-install-page.d.ts +2 -0
  114. package/dist/pages/github-install-page.d.ts.map +1 -0
  115. package/dist/pages/github-install-page.js +11 -0
  116. package/dist/pages/github-install-page.js.map +1 -0
  117. package/dist/pages/linear-callback-page.d.ts +2 -0
  118. package/dist/pages/linear-callback-page.d.ts.map +1 -0
  119. package/dist/pages/linear-callback-page.js +192 -0
  120. package/dist/pages/linear-callback-page.js.map +1 -0
  121. package/dist/pages/linear-install-page.d.ts +2 -0
  122. package/dist/pages/linear-install-page.d.ts.map +1 -0
  123. package/dist/pages/linear-install-page.js +24 -0
  124. package/dist/pages/linear-install-page.js.map +1 -0
  125. package/dist/pages/sentry-callback-page.d.ts +2 -0
  126. package/dist/pages/sentry-callback-page.d.ts.map +1 -0
  127. package/dist/pages/sentry-callback-page.js +256 -0
  128. package/dist/pages/sentry-callback-page.js.map +1 -0
  129. package/dist/pages/sentry-install-page.d.ts +2 -0
  130. package/dist/pages/sentry-install-page.d.ts.map +1 -0
  131. package/dist/pages/sentry-install-page.js +17 -0
  132. package/dist/pages/sentry-install-page.js.map +1 -0
  133. package/dist/pages/source-control-onboarding-page.d.ts +2 -0
  134. package/dist/pages/source-control-onboarding-page.d.ts.map +1 -0
  135. package/dist/pages/source-control-onboarding-page.js +37 -0
  136. package/dist/pages/source-control-onboarding-page.js.map +1 -0
  137. package/dist/provider-catalog.d.ts +15 -0
  138. package/dist/provider-catalog.d.ts.map +1 -0
  139. package/dist/provider-catalog.js +28 -0
  140. package/dist/provider-catalog.js.map +1 -0
  141. package/dist/routes/gitea.d.ts +6 -0
  142. package/dist/routes/gitea.d.ts.map +1 -0
  143. package/dist/routes/gitea.js +7 -0
  144. package/dist/routes/gitea.js.map +1 -0
  145. package/dist/routes/github-callback.d.ts +6 -0
  146. package/dist/routes/github-callback.d.ts.map +1 -0
  147. package/dist/routes/github-callback.js +129 -0
  148. package/dist/routes/github-callback.js.map +1 -0
  149. package/dist/routes/github.d.ts +6 -0
  150. package/dist/routes/github.d.ts.map +1 -0
  151. package/dist/routes/github.js +7 -0
  152. package/dist/routes/github.js.map +1 -0
  153. package/dist/routes/integrations.d.ts +6 -0
  154. package/dist/routes/integrations.d.ts.map +1 -0
  155. package/dist/routes/integrations.js +7 -0
  156. package/dist/routes/integrations.js.map +1 -0
  157. package/dist/routes/linear-callback.d.ts +6 -0
  158. package/dist/routes/linear-callback.d.ts.map +1 -0
  159. package/dist/routes/linear-callback.js +7 -0
  160. package/dist/routes/linear-callback.js.map +1 -0
  161. package/dist/routes/linear.d.ts +6 -0
  162. package/dist/routes/linear.d.ts.map +1 -0
  163. package/dist/routes/linear.js +7 -0
  164. package/dist/routes/linear.js.map +1 -0
  165. package/dist/routes/sentry-callback.d.ts +6 -0
  166. package/dist/routes/sentry-callback.d.ts.map +1 -0
  167. package/dist/routes/sentry-callback.js +7 -0
  168. package/dist/routes/sentry-callback.js.map +1 -0
  169. package/dist/routes/sentry.d.ts +6 -0
  170. package/dist/routes/sentry.d.ts.map +1 -0
  171. package/dist/routes/sentry.js +7 -0
  172. package/dist/routes/sentry.js.map +1 -0
  173. package/dist/sentry-callback.d.ts +35 -0
  174. package/dist/sentry-callback.d.ts.map +1 -0
  175. package/dist/sentry-callback.js +132 -0
  176. package/dist/sentry-callback.js.map +1 -0
  177. package/dist/tsconfig.test.tsbuildinfo +1 -0
  178. package/package.json +120 -0
  179. package/src/components/connection-picker.tsx +89 -0
  180. package/src/components/installed-integrations-section.tsx +198 -0
  181. package/src/components/integration-delete-confirm-modal.tsx +62 -0
  182. package/src/components/integration-gallery-for-workspace.tsx +193 -0
  183. package/src/components/integration-gallery.stories.tsx +302 -0
  184. package/src/components/integration-gallery.test.tsx +692 -0
  185. package/src/components/integration-gallery.tsx +49 -0
  186. package/src/components/integration-usage-events.test.ts +80 -0
  187. package/src/components/integration-usage-events.ts +116 -0
  188. package/src/components/integration-usage-modal.tsx +178 -0
  189. package/src/components/provider-grid.tsx +163 -0
  190. package/src/components/redirect-install-page.test.tsx +98 -0
  191. package/src/components/redirect-install-page.tsx +68 -0
  192. package/src/components/repository-picker.tsx +102 -0
  193. package/src/components/webhook/copyable-value.test.tsx +41 -0
  194. package/src/components/webhook/copyable-value.tsx +72 -0
  195. package/src/components/webhook/webhook-create-modal.tsx +247 -0
  196. package/src/components/webhook/webhook-form-errors.test.ts +45 -0
  197. package/src/components/webhook/webhook-form-errors.ts +32 -0
  198. package/src/components/webhook/webhook-modals.stories.tsx +225 -0
  199. package/src/components/webhook/webhook-public-endpoint-alert.tsx +15 -0
  200. package/src/components/webhook/webhook-usage-details.tsx +44 -0
  201. package/src/connection-status-badge.tsx +38 -0
  202. package/src/feature.ts +47 -0
  203. package/src/hooks/api/integrations.test.ts +105 -0
  204. package/src/hooks/api/integrations.ts +287 -0
  205. package/src/hooks/api/webhook-connections.ts +115 -0
  206. package/src/index.ts +16 -0
  207. package/src/integration-icon.react.test.tsx +40 -0
  208. package/src/integration-icon.test.ts +19 -0
  209. package/src/integration-icon.tsx +26 -0
  210. package/src/linear-callback.test.ts +227 -0
  211. package/src/linear-callback.ts +136 -0
  212. package/src/pages/gitea-form-errors.test.ts +55 -0
  213. package/src/pages/gitea-form-errors.ts +36 -0
  214. package/src/pages/gitea-install-page.tsx +108 -0
  215. package/src/pages/github-install-page.tsx +11 -0
  216. package/src/pages/linear-callback-page.test.tsx +157 -0
  217. package/src/pages/linear-callback-page.tsx +175 -0
  218. package/src/pages/linear-install-page.tsx +26 -0
  219. package/src/pages/sentry-callback-page.test.tsx +205 -0
  220. package/src/pages/sentry-callback-page.tsx +238 -0
  221. package/src/pages/sentry-install-page.tsx +17 -0
  222. package/src/pages/source-control-onboarding-page.test.tsx +46 -0
  223. package/src/pages/source-control-onboarding-page.tsx +26 -0
  224. package/src/provider-catalog.test.ts +42 -0
  225. package/src/provider-catalog.ts +49 -0
  226. package/src/routes/gitea.tsx +4 -0
  227. package/src/routes/github-callback.tsx +114 -0
  228. package/src/routes/github.tsx +4 -0
  229. package/src/routes/integrations.tsx +4 -0
  230. package/src/routes/linear-callback.tsx +4 -0
  231. package/src/routes/linear.tsx +4 -0
  232. package/src/routes/sentry-callback.tsx +4 -0
  233. package/src/routes/sentry.tsx +4 -0
  234. package/src/sentry-callback.test.ts +239 -0
  235. package/src/sentry-callback.ts +142 -0
  236. package/test/render.tsx +111 -0
  237. package/test/setup.ts +3 -0
  238. package/tsconfig.build.json +9 -0
  239. package/tsconfig.build.tsbuildinfo +1 -0
  240. package/tsconfig.json +3 -0
  241. package/tsconfig.test.json +8 -0
  242. package/vercel.json +8 -0
  243. package/vitest.config.ts +75 -0
@@ -0,0 +1,184 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Button } from '@shipfox/react-ui/button';
3
+ import { CodeBlock, CodeBlockBody, CodeBlockContent, CodeBlockCopyButton, CodeBlockFilename, CodeBlockFiles, CodeBlockHeader, CodeBlockItem } from '@shipfox/react-ui/code-block';
4
+ import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ModalTitle } from '@shipfox/react-ui/modal';
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@shipfox/react-ui/select';
6
+ import { Text } from '@shipfox/react-ui/typography';
7
+ import { useEffect, useMemo, useState } from 'react';
8
+ import { ConnectionStatusBadge } from '#connection-status-badge.js';
9
+ const WORKFLOW_FILENAME = '.shipfox/workflows/integration.yml';
10
+ export function IntegrationUsageModal({ connection, events, open, onOpenChange, children }) {
11
+ const [selectedEvent, setSelectedEvent] = useState('');
12
+ const eventValuesKey = events.map((event)=>event.value).join('\u0000');
13
+ useEffect(()=>{
14
+ const eventValues = eventValuesKey ? eventValuesKey.split('\u0000') : [];
15
+ setSelectedEvent((current)=>eventValues.includes(current) ? current : eventValues[0] ?? '');
16
+ }, [
17
+ eventValuesKey
18
+ ]);
19
+ const workflowExample = connection ? buildWorkflowExample({
20
+ source: connection.slug,
21
+ event: selectedEvent
22
+ }) : '';
23
+ const data = useMemo(()=>[
24
+ {
25
+ language: 'yaml',
26
+ filename: WORKFLOW_FILENAME,
27
+ code: workflowExample
28
+ }
29
+ ], [
30
+ workflowExample
31
+ ]);
32
+ const title = connection ? `Use ${connection.display_name}` : 'Use integration';
33
+ return /*#__PURE__*/ _jsx(Modal, {
34
+ open: open && connection !== null,
35
+ onOpenChange: onOpenChange,
36
+ children: /*#__PURE__*/ _jsxs(ModalContent, {
37
+ "aria-describedby": undefined,
38
+ className: "max-h-[calc(100vh-32px)] max-w-[640px]",
39
+ children: [
40
+ /*#__PURE__*/ _jsx(ModalTitle, {
41
+ className: "sr-only",
42
+ children: title
43
+ }),
44
+ /*#__PURE__*/ _jsx(ModalHeader, {
45
+ children: /*#__PURE__*/ _jsxs("div", {
46
+ className: "flex min-w-0 flex-col gap-2",
47
+ children: [
48
+ /*#__PURE__*/ _jsxs("div", {
49
+ className: "flex min-w-0 items-center gap-8",
50
+ children: [
51
+ /*#__PURE__*/ _jsx(Text, {
52
+ size: "lg",
53
+ "aria-hidden": "true",
54
+ className: "truncate",
55
+ children: title
56
+ }),
57
+ connection ? /*#__PURE__*/ _jsx(ConnectionStatusBadge, {
58
+ status: connection.lifecycle_status,
59
+ className: "shrink-0"
60
+ }) : null
61
+ ]
62
+ }),
63
+ /*#__PURE__*/ _jsx(Text, {
64
+ size: "sm",
65
+ className: "text-foreground-neutral-muted",
66
+ children: "Reference this connection from a workflow trigger."
67
+ })
68
+ ]
69
+ })
70
+ }),
71
+ connection ? /*#__PURE__*/ _jsxs(_Fragment, {
72
+ children: [
73
+ /*#__PURE__*/ _jsxs(ModalBody, {
74
+ className: "min-h-0 flex-1 gap-24 overflow-y-auto overflow-x-clip scrollbar",
75
+ children: [
76
+ /*#__PURE__*/ _jsxs("section", {
77
+ className: "flex w-full flex-col gap-12",
78
+ children: [
79
+ /*#__PURE__*/ _jsx(Text, {
80
+ size: "md",
81
+ bold: true,
82
+ children: "Usage"
83
+ }),
84
+ events.length > 1 ? /*#__PURE__*/ _jsxs("div", {
85
+ className: "flex flex-col gap-6",
86
+ children: [
87
+ /*#__PURE__*/ _jsx("label", {
88
+ htmlFor: "integration-usage-event",
89
+ className: "font-display text-sm font-medium text-foreground-neutral-base",
90
+ children: "Event"
91
+ }),
92
+ /*#__PURE__*/ _jsxs(Select, {
93
+ value: selectedEvent,
94
+ onValueChange: setSelectedEvent,
95
+ children: [
96
+ /*#__PURE__*/ _jsx(SelectTrigger, {
97
+ id: "integration-usage-event",
98
+ children: /*#__PURE__*/ _jsx(SelectValue, {
99
+ placeholder: "Select event"
100
+ })
101
+ }),
102
+ /*#__PURE__*/ _jsx(SelectContent, {
103
+ children: events.map((event)=>/*#__PURE__*/ _jsx(SelectItem, {
104
+ value: event.value,
105
+ children: event.label
106
+ }, event.value))
107
+ })
108
+ ]
109
+ })
110
+ ]
111
+ }) : null,
112
+ /*#__PURE__*/ _jsxs(CodeBlock, {
113
+ data: data,
114
+ className: "h-auto min-h-0 rounded-8",
115
+ children: [
116
+ /*#__PURE__*/ _jsxs(CodeBlockHeader, {
117
+ children: [
118
+ /*#__PURE__*/ _jsx(CodeBlockFiles, {
119
+ children: (item)=>/*#__PURE__*/ _jsx(CodeBlockFilename, {
120
+ value: item.filename,
121
+ children: item.filename
122
+ })
123
+ }),
124
+ /*#__PURE__*/ _jsx(CodeBlockCopyButton, {})
125
+ ]
126
+ }),
127
+ /*#__PURE__*/ _jsx(CodeBlockBody, {
128
+ children: (item)=>/*#__PURE__*/ _jsx(CodeBlockItem, {
129
+ value: item.filename,
130
+ children: /*#__PURE__*/ _jsx(CodeBlockContent, {
131
+ language: "yaml",
132
+ syntaxHighlighting: true,
133
+ highlightedLineRange: {
134
+ startLine: 3,
135
+ endLine: 4
136
+ },
137
+ children: item.code
138
+ })
139
+ })
140
+ })
141
+ ]
142
+ })
143
+ ]
144
+ }),
145
+ children ? /*#__PURE__*/ _jsxs("section", {
146
+ className: "flex w-full flex-col gap-12",
147
+ children: [
148
+ /*#__PURE__*/ _jsx(Text, {
149
+ size: "md",
150
+ bold: true,
151
+ children: "Details"
152
+ }),
153
+ children
154
+ ]
155
+ }) : null
156
+ ]
157
+ }),
158
+ /*#__PURE__*/ _jsx(ModalFooter, {
159
+ children: /*#__PURE__*/ _jsx(Button, {
160
+ type: "button",
161
+ onClick: ()=>onOpenChange(false),
162
+ children: "Done"
163
+ })
164
+ })
165
+ ]
166
+ }) : null
167
+ ]
168
+ })
169
+ });
170
+ }
171
+ function buildWorkflowExample({ source, event }) {
172
+ return [
173
+ 'triggers:',
174
+ ' integration:',
175
+ ` source: ${source}`,
176
+ ` event: ${event}`,
177
+ 'jobs:',
178
+ ' handle-event:',
179
+ ' steps:',
180
+ ' - run: echo "Received event"'
181
+ ].join('\n');
182
+ }
183
+
184
+ //# sourceMappingURL=integration-usage-modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/integration-usage-modal.tsx"],"sourcesContent":["import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';\nimport {Button} from '@shipfox/react-ui/button';\nimport {\n CodeBlock,\n CodeBlockBody,\n CodeBlockContent,\n CodeBlockCopyButton,\n CodeBlockFilename,\n CodeBlockFiles,\n CodeBlockHeader,\n CodeBlockItem,\n} from '@shipfox/react-ui/code-block';\nimport {\n Modal,\n ModalBody,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalTitle,\n} from '@shipfox/react-ui/modal';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@shipfox/react-ui/select';\nimport {Text} from '@shipfox/react-ui/typography';\nimport type {ReactNode} from 'react';\nimport {useEffect, useMemo, useState} from 'react';\nimport {ConnectionStatusBadge} from '#connection-status-badge.js';\n\nexport interface IntegrationUsageEvent {\n value: string;\n label: string;\n}\n\ninterface IntegrationUsageModalProps {\n connection: IntegrationConnectionDto | null;\n events: IntegrationUsageEvent[];\n open: boolean;\n onOpenChange: (open: boolean) => void;\n children?: ReactNode;\n}\n\nconst WORKFLOW_FILENAME = '.shipfox/workflows/integration.yml';\n\nexport function IntegrationUsageModal({\n connection,\n events,\n open,\n onOpenChange,\n children,\n}: IntegrationUsageModalProps) {\n const [selectedEvent, setSelectedEvent] = useState('');\n const eventValuesKey = events.map((event) => event.value).join('\\u0000');\n\n useEffect(() => {\n const eventValues = eventValuesKey ? eventValuesKey.split('\\u0000') : [];\n setSelectedEvent((current) =>\n eventValues.includes(current) ? current : (eventValues[0] ?? ''),\n );\n }, [eventValuesKey]);\n\n const workflowExample = connection\n ? buildWorkflowExample({source: connection.slug, event: selectedEvent})\n : '';\n const data = useMemo(\n () => [{language: 'yaml', filename: WORKFLOW_FILENAME, code: workflowExample}],\n [workflowExample],\n );\n const title = connection ? `Use ${connection.display_name}` : 'Use integration';\n\n return (\n <Modal open={open && connection !== null} onOpenChange={onOpenChange}>\n <ModalContent aria-describedby={undefined} className=\"max-h-[calc(100vh-32px)] max-w-[640px]\">\n <ModalTitle className=\"sr-only\">{title}</ModalTitle>\n <ModalHeader>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <Text size=\"lg\" aria-hidden=\"true\" className=\"truncate\">\n {title}\n </Text>\n {connection ? (\n <ConnectionStatusBadge status={connection.lifecycle_status} className=\"shrink-0\" />\n ) : null}\n </div>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Reference this connection from a workflow trigger.\n </Text>\n </div>\n </ModalHeader>\n {connection ? (\n <>\n <ModalBody className=\"min-h-0 flex-1 gap-24 overflow-y-auto overflow-x-clip scrollbar\">\n <section className=\"flex w-full flex-col gap-12\">\n <Text size=\"md\" bold>\n Usage\n </Text>\n {events.length > 1 ? (\n <div className=\"flex flex-col gap-6\">\n <label\n htmlFor=\"integration-usage-event\"\n className=\"font-display text-sm font-medium text-foreground-neutral-base\"\n >\n Event\n </label>\n <Select value={selectedEvent} onValueChange={setSelectedEvent}>\n <SelectTrigger id=\"integration-usage-event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((event) => (\n <SelectItem key={event.value} value={event.value}>\n {event.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : null}\n <CodeBlock data={data} className=\"h-auto min-h-0 rounded-8\">\n <CodeBlockHeader>\n <CodeBlockFiles>\n {(item) => (\n <CodeBlockFilename value={item.filename}>{item.filename}</CodeBlockFilename>\n )}\n </CodeBlockFiles>\n <CodeBlockCopyButton />\n </CodeBlockHeader>\n <CodeBlockBody>\n {(item) => (\n <CodeBlockItem value={item.filename}>\n <CodeBlockContent\n language=\"yaml\"\n syntaxHighlighting\n highlightedLineRange={{startLine: 3, endLine: 4}}\n >\n {item.code}\n </CodeBlockContent>\n </CodeBlockItem>\n )}\n </CodeBlockBody>\n </CodeBlock>\n </section>\n {children ? (\n <section className=\"flex w-full flex-col gap-12\">\n <Text size=\"md\" bold>\n Details\n </Text>\n {children}\n </section>\n ) : null}\n </ModalBody>\n <ModalFooter>\n <Button type=\"button\" onClick={() => onOpenChange(false)}>\n Done\n </Button>\n </ModalFooter>\n </>\n ) : null}\n </ModalContent>\n </Modal>\n );\n}\n\nfunction buildWorkflowExample({source, event}: {source: string; event: string}) {\n return [\n 'triggers:',\n ' integration:',\n ` source: ${source}`,\n ` event: ${event}`,\n 'jobs:',\n ' handle-event:',\n ' steps:',\n ' - run: echo \"Received event\"',\n ].join('\\n');\n}\n"],"names":["Button","CodeBlock","CodeBlockBody","CodeBlockContent","CodeBlockCopyButton","CodeBlockFilename","CodeBlockFiles","CodeBlockHeader","CodeBlockItem","Modal","ModalBody","ModalContent","ModalFooter","ModalHeader","ModalTitle","Select","SelectContent","SelectItem","SelectTrigger","SelectValue","Text","useEffect","useMemo","useState","ConnectionStatusBadge","WORKFLOW_FILENAME","IntegrationUsageModal","connection","events","open","onOpenChange","children","selectedEvent","setSelectedEvent","eventValuesKey","map","event","value","join","eventValues","split","current","includes","workflowExample","buildWorkflowExample","source","slug","data","language","filename","code","title","display_name","aria-describedby","undefined","className","div","size","aria-hidden","status","lifecycle_status","section","bold","length","label","htmlFor","onValueChange","id","placeholder","item","syntaxHighlighting","highlightedLineRange","startLine","endLine","type","onClick"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ListIntegrationProvidersResponseDto } from '@shipfox/api-integration-core-dto';
2
+ import type { UseQueryResult } from '@tanstack/react-query';
3
+ export interface ProviderGridProps {
4
+ providersQuery: UseQueryResult<ListIntegrationProvidersResponseDto, Error>;
5
+ workspaceId: string;
6
+ emptyMessage: string;
7
+ loadingLabel?: string;
8
+ errorSubject?: string;
9
+ onOpenProvider?: ((provider: string) => void) | undefined;
10
+ }
11
+ export declare const PROVIDER_GRID_CLASS = "grid grid-cols-2 gap-12 max-[760px]:grid-cols-1";
12
+ export declare const PROVIDER_SURFACE_CLASS = "overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base";
13
+ export declare function ProviderGrid({ providersQuery, workspaceId, emptyMessage, loadingLabel, errorSubject, onOpenProvider, }: ProviderGridProps): import("react").JSX.Element;
14
+ //# sourceMappingURL=provider-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-grid.d.ts","sourceRoot":"","sources":["../../src/components/provider-grid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,mCAAmC,EACpC,MAAM,mCAAmC,CAAC;AAQ3C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAK1D,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,cAAc,CAAC,mCAAmC,EAAE,KAAK,CAAC,CAAC;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC3D;AAED,eAAO,MAAM,mBAAmB,oDAAoD,CAAC;AAErF,eAAO,MAAM,sBAAsB,2FACuD,CAAC;AAE3F,wBAAgB,YAAY,CAAC,EAC3B,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAkC,EAClC,YAAuC,EACvC,cAAc,GACf,EAAE,iBAAiB,+BAuCnB"}
@@ -0,0 +1,152 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { QueryLoadError } from '@shipfox/client-ui';
3
+ import { Card } from '@shipfox/react-ui/card';
4
+ import { EmptyState } from '@shipfox/react-ui/empty-state';
5
+ import { Icon } from '@shipfox/react-ui/icon';
6
+ import { Skeleton } from '@shipfox/react-ui/skeleton';
7
+ import { Text } from '@shipfox/react-ui/typography';
8
+ import { cn } from '@shipfox/react-ui/utils';
9
+ import { Link } from '@tanstack/react-router';
10
+ import { IntegrationIcon } from '#integration-icon.js';
11
+ import { PROVIDER_CATALOG } from '#provider-catalog.js';
12
+ export const PROVIDER_GRID_CLASS = 'grid grid-cols-2 gap-12 max-[760px]:grid-cols-1';
13
+ export const PROVIDER_SURFACE_CLASS = 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
14
+ export function ProviderGrid({ providersQuery, workspaceId, emptyMessage, loadingLabel = 'Loading providers', errorSubject = 'available integrations', onOpenProvider }) {
15
+ const providers = providersQuery.data?.providers ?? [];
16
+ const installableProviders = providers.filter((provider)=>PROVIDER_CATALOG[provider.provider]);
17
+ if (providersQuery.isPending) return /*#__PURE__*/ _jsx(ProviderGridSkeleton, {
18
+ label: loadingLabel
19
+ });
20
+ if (providersQuery.isError && providersQuery.data === undefined) {
21
+ return /*#__PURE__*/ _jsx("div", {
22
+ className: cn(PROVIDER_SURFACE_CLASS, 'px-16'),
23
+ children: /*#__PURE__*/ _jsx(QueryLoadError, {
24
+ query: providersQuery,
25
+ subject: errorSubject
26
+ })
27
+ });
28
+ }
29
+ if (providersQuery.data !== undefined && installableProviders.length === 0) {
30
+ return /*#__PURE__*/ _jsx("div", {
31
+ className: cn(PROVIDER_SURFACE_CLASS, 'px-16'),
32
+ children: /*#__PURE__*/ _jsx(EmptyState, {
33
+ icon: "componentLine",
34
+ title: "No integrations available",
35
+ description: emptyMessage
36
+ })
37
+ });
38
+ }
39
+ return /*#__PURE__*/ _jsx("ul", {
40
+ className: PROVIDER_GRID_CLASS,
41
+ children: installableProviders.map((provider)=>/*#__PURE__*/ _jsx("li", {
42
+ children: /*#__PURE__*/ _jsx(ProviderCard, {
43
+ provider: provider,
44
+ workspaceId: workspaceId,
45
+ onOpenProvider: onOpenProvider
46
+ })
47
+ }, provider.provider))
48
+ });
49
+ }
50
+ function ProviderCard({ provider, workspaceId, onOpenProvider }) {
51
+ const catalog = PROVIDER_CATALOG[provider.provider];
52
+ if (!catalog) return null;
53
+ if (catalog.kind === 'modal-connect') {
54
+ return /*#__PURE__*/ _jsx("button", {
55
+ type: "button",
56
+ "aria-label": `Add ${provider.display_name}`,
57
+ onClick: ()=>onOpenProvider?.(provider.provider),
58
+ 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",
59
+ children: /*#__PURE__*/ _jsx(ProviderCardContent, {
60
+ provider: provider,
61
+ action: "Add"
62
+ })
63
+ });
64
+ }
65
+ return /*#__PURE__*/ _jsx(Link, {
66
+ to: catalog.setupPath,
67
+ params: {
68
+ wid: workspaceId
69
+ },
70
+ "aria-label": `Install ${provider.display_name}`,
71
+ 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",
72
+ children: /*#__PURE__*/ _jsx(ProviderCardContent, {
73
+ provider: provider,
74
+ action: "Install"
75
+ })
76
+ });
77
+ }
78
+ function ProviderCardContent({ provider, action }) {
79
+ return /*#__PURE__*/ _jsxs(_Fragment, {
80
+ children: [
81
+ /*#__PURE__*/ _jsxs("span", {
82
+ className: "flex min-w-0 items-center gap-12",
83
+ children: [
84
+ /*#__PURE__*/ _jsx(IntegrationIcon, {
85
+ source: provider.provider,
86
+ "aria-hidden": true,
87
+ className: "size-24 shrink-0 text-foreground-neutral-base"
88
+ }),
89
+ /*#__PURE__*/ _jsx(Text, {
90
+ as: "span",
91
+ size: "md",
92
+ bold: true,
93
+ className: "truncate",
94
+ children: provider.display_name
95
+ })
96
+ ]
97
+ }),
98
+ /*#__PURE__*/ _jsxs("span", {
99
+ className: "flex shrink-0 items-center gap-4 text-foreground-neutral-muted transition-colors group-hover:text-foreground-highlight-interactive",
100
+ children: [
101
+ /*#__PURE__*/ _jsx(Text, {
102
+ as: "span",
103
+ size: "sm",
104
+ children: action
105
+ }),
106
+ /*#__PURE__*/ _jsx(Icon, {
107
+ name: "chevronRight",
108
+ className: "size-16"
109
+ })
110
+ ]
111
+ })
112
+ ]
113
+ });
114
+ }
115
+ function ProviderGridSkeleton({ label }) {
116
+ return /*#__PURE__*/ _jsx("ul", {
117
+ role: "status",
118
+ "aria-label": label,
119
+ className: PROVIDER_GRID_CLASS,
120
+ children: [
121
+ 0,
122
+ 1,
123
+ 2,
124
+ 3
125
+ ].map((tile)=>/*#__PURE__*/ _jsx("li", {
126
+ children: /*#__PURE__*/ _jsx(Card, {
127
+ className: "h-full p-16",
128
+ children: /*#__PURE__*/ _jsxs("div", {
129
+ className: "flex items-center justify-between gap-12",
130
+ children: [
131
+ /*#__PURE__*/ _jsxs("div", {
132
+ className: "flex min-w-0 items-center gap-12",
133
+ children: [
134
+ /*#__PURE__*/ _jsx(Skeleton, {
135
+ className: "size-24 shrink-0"
136
+ }),
137
+ /*#__PURE__*/ _jsx(Skeleton, {
138
+ className: "h-16 w-100"
139
+ })
140
+ ]
141
+ }),
142
+ /*#__PURE__*/ _jsx(Skeleton, {
143
+ className: "h-16 w-64 shrink-0"
144
+ })
145
+ ]
146
+ })
147
+ })
148
+ }, tile))
149
+ });
150
+ }
151
+
152
+ //# sourceMappingURL=provider-grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/provider-grid.tsx"],"sourcesContent":["import type {\n IntegrationProviderDto,\n ListIntegrationProvidersResponseDto,\n} from '@shipfox/api-integration-core-dto';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Card} from '@shipfox/react-ui/card';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport type {UseQueryResult} from '@tanstack/react-query';\nimport {Link} from '@tanstack/react-router';\nimport {IntegrationIcon} from '#integration-icon.js';\nimport {PROVIDER_CATALOG} from '#provider-catalog.js';\n\nexport interface ProviderGridProps {\n providersQuery: UseQueryResult<ListIntegrationProvidersResponseDto, Error>;\n workspaceId: string;\n emptyMessage: string;\n loadingLabel?: string;\n errorSubject?: string;\n onOpenProvider?: ((provider: string) => void) | undefined;\n}\n\nexport const PROVIDER_GRID_CLASS = 'grid grid-cols-2 gap-12 max-[760px]:grid-cols-1';\n\nexport const PROVIDER_SURFACE_CLASS =\n 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';\n\nexport function ProviderGrid({\n providersQuery,\n workspaceId,\n emptyMessage,\n loadingLabel = 'Loading providers',\n errorSubject = 'available integrations',\n onOpenProvider,\n}: ProviderGridProps) {\n const providers = providersQuery.data?.providers ?? [];\n const installableProviders = providers.filter((provider) => PROVIDER_CATALOG[provider.provider]);\n\n if (providersQuery.isPending) return <ProviderGridSkeleton label={loadingLabel} />;\n\n if (providersQuery.isError && providersQuery.data === undefined) {\n return (\n <div className={cn(PROVIDER_SURFACE_CLASS, 'px-16')}>\n <QueryLoadError query={providersQuery} subject={errorSubject} />\n </div>\n );\n }\n\n if (providersQuery.data !== undefined && installableProviders.length === 0) {\n return (\n <div className={cn(PROVIDER_SURFACE_CLASS, 'px-16')}>\n <EmptyState\n icon=\"componentLine\"\n title=\"No integrations available\"\n description={emptyMessage}\n />\n </div>\n );\n }\n\n return (\n <ul className={PROVIDER_GRID_CLASS}>\n {installableProviders.map((provider) => (\n <li key={provider.provider}>\n <ProviderCard\n provider={provider}\n workspaceId={workspaceId}\n onOpenProvider={onOpenProvider}\n />\n </li>\n ))}\n </ul>\n );\n}\n\nfunction ProviderCard({\n provider,\n workspaceId,\n onOpenProvider,\n}: {\n provider: IntegrationProviderDto;\n workspaceId: string;\n onOpenProvider?: ((provider: string) => void) | undefined;\n}) {\n const catalog = PROVIDER_CATALOG[provider.provider];\n if (!catalog) return null;\n\n if (catalog.kind === 'modal-connect') {\n return (\n <button\n type=\"button\"\n aria-label={`Add ${provider.display_name}`}\n onClick={() => onOpenProvider?.(provider.provider)}\n 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\"\n >\n <ProviderCardContent provider={provider} action=\"Add\" />\n </button>\n );\n }\n\n return (\n <Link\n to={catalog.setupPath}\n params={{wid: workspaceId}}\n aria-label={`Install ${provider.display_name}`}\n 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\"\n >\n <ProviderCardContent provider={provider} action=\"Install\" />\n </Link>\n );\n}\n\nfunction ProviderCardContent({\n provider,\n action,\n}: {\n provider: IntegrationProviderDto;\n action: 'Add' | 'Install';\n}) {\n return (\n <>\n <span className=\"flex min-w-0 items-center gap-12\">\n <IntegrationIcon\n source={provider.provider}\n aria-hidden\n className=\"size-24 shrink-0 text-foreground-neutral-base\"\n />\n <Text as=\"span\" size=\"md\" bold className=\"truncate\">\n {provider.display_name}\n </Text>\n </span>\n <span className=\"flex shrink-0 items-center gap-4 text-foreground-neutral-muted transition-colors group-hover:text-foreground-highlight-interactive\">\n <Text as=\"span\" size=\"sm\">\n {action}\n </Text>\n <Icon name=\"chevronRight\" className=\"size-16\" />\n </span>\n </>\n );\n}\n\nfunction ProviderGridSkeleton({label}: {label: string}) {\n return (\n <ul role=\"status\" aria-label={label} className={PROVIDER_GRID_CLASS}>\n {[0, 1, 2, 3].map((tile) => (\n <li key={tile}>\n <Card className=\"h-full p-16\">\n <div className=\"flex items-center justify-between gap-12\">\n <div className=\"flex min-w-0 items-center gap-12\">\n <Skeleton className=\"size-24 shrink-0\" />\n <Skeleton className=\"h-16 w-100\" />\n </div>\n <Skeleton className=\"h-16 w-64 shrink-0\" />\n </div>\n </Card>\n </li>\n ))}\n </ul>\n );\n}\n"],"names":["QueryLoadError","Card","EmptyState","Icon","Skeleton","Text","cn","Link","IntegrationIcon","PROVIDER_CATALOG","PROVIDER_GRID_CLASS","PROVIDER_SURFACE_CLASS","ProviderGrid","providersQuery","workspaceId","emptyMessage","loadingLabel","errorSubject","onOpenProvider","providers","data","installableProviders","filter","provider","isPending","ProviderGridSkeleton","label","isError","undefined","div","className","query","subject","length","icon","title","description","ul","map","li","ProviderCard","catalog","kind","button","type","aria-label","display_name","onClick","ProviderCardContent","action","to","setupPath","params","wid","span","source","aria-hidden","as","size","bold","name","role","tile"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ interface RedirectInstallPageProps {
2
+ installRequest: (body: {
3
+ workspace_id: string;
4
+ }) => Promise<{
5
+ install_url: string;
6
+ }>;
7
+ errorFallbackMessage: string;
8
+ /**
9
+ * Runs before leaving the app (e.g. to persist the workspace id for a
10
+ * state-less provider callback). Must not throw — a failed side effect
11
+ * should never block the redirect.
12
+ */
13
+ beforeRedirect?: (workspaceId: string) => void;
14
+ /** Injectable for tests: jsdom's window.location cannot be stubbed. */
15
+ assignLocation?: (url: string) => void;
16
+ }
17
+ export declare function RedirectInstallPage({ installRequest, errorFallbackMessage, beforeRedirect, assignLocation, }: RedirectInstallPageProps): import("react").JSX.Element;
18
+ export {};
19
+ //# sourceMappingURL=redirect-install-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-install-page.d.ts","sourceRoot":"","sources":["../../src/components/redirect-install-page.tsx"],"names":[],"mappings":"AASA,UAAU,wBAAwB;IAChC,cAAc,EAAE,CAAC,IAAI,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IACjF,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,uEAAuE;IACvE,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,wBAAgB,mBAAmB,CAAC,EAClC,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,cAAqD,GACtD,EAAE,wBAAwB,+BAwC1B"}
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ApiError } from '@shipfox/client-api';
3
+ import { useActiveWorkspace } from '@shipfox/client-auth';
4
+ import { ButtonLink } from '@shipfox/react-ui/button';
5
+ import { Callout } from '@shipfox/react-ui/callout';
6
+ import { FullPageLoader } from '@shipfox/react-ui/loader';
7
+ import { Text } from '@shipfox/react-ui/typography';
8
+ import { Link } from '@tanstack/react-router';
9
+ import { useEffect, useRef, useState } from 'react';
10
+ export function RedirectInstallPage({ installRequest, errorFallbackMessage, beforeRedirect, assignLocation = (url)=>window.location.assign(url) }) {
11
+ const workspace = useActiveWorkspace();
12
+ const startedRef = useRef(false);
13
+ const [errorMessage, setErrorMessage] = useState();
14
+ useEffect(()=>{
15
+ if (startedRef.current) return;
16
+ startedRef.current = true;
17
+ // The side effect is best-effort persistence; a throw here must never
18
+ // block the install redirect, so swallow it and continue.
19
+ try {
20
+ beforeRedirect?.(workspace.id);
21
+ } catch {
22
+ // ignore
23
+ }
24
+ installRequest({
25
+ workspace_id: workspace.id
26
+ }).then((response)=>{
27
+ assignLocation(response.install_url);
28
+ }).catch((error)=>{
29
+ setErrorMessage(error instanceof ApiError ? error.message : errorFallbackMessage);
30
+ });
31
+ }, [
32
+ workspace,
33
+ installRequest,
34
+ beforeRedirect,
35
+ errorFallbackMessage,
36
+ assignLocation
37
+ ]);
38
+ if (errorMessage) {
39
+ return /*#__PURE__*/ _jsxs("div", {
40
+ className: "mx-auto flex w-full max-w-[480px] flex-col gap-16",
41
+ children: [
42
+ /*#__PURE__*/ _jsx(Callout, {
43
+ role: "alert",
44
+ type: "error",
45
+ children: /*#__PURE__*/ _jsx(Text, {
46
+ size: "sm",
47
+ children: errorMessage
48
+ })
49
+ }),
50
+ /*#__PURE__*/ _jsx(ButtonLink, {
51
+ asChild: true,
52
+ variant: "muted",
53
+ className: "w-fit",
54
+ children: /*#__PURE__*/ _jsx(Link, {
55
+ to: "/workspaces/$wid/integrations",
56
+ params: {
57
+ wid: workspace.id
58
+ },
59
+ children: "Back to integrations"
60
+ })
61
+ })
62
+ ]
63
+ });
64
+ }
65
+ return /*#__PURE__*/ _jsx(FullPageLoader, {});
66
+ }
67
+
68
+ //# sourceMappingURL=redirect-install-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/redirect-install-page.tsx"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {Link} from '@tanstack/react-router';\nimport {useEffect, useRef, useState} from 'react';\n\ninterface RedirectInstallPageProps {\n installRequest: (body: {workspace_id: string}) => Promise<{install_url: string}>;\n errorFallbackMessage: string;\n /**\n * Runs before leaving the app (e.g. to persist the workspace id for a\n * state-less provider callback). Must not throw — a failed side effect\n * should never block the redirect.\n */\n beforeRedirect?: (workspaceId: string) => void;\n /** Injectable for tests: jsdom's window.location cannot be stubbed. */\n assignLocation?: (url: string) => void;\n}\n\nexport function RedirectInstallPage({\n installRequest,\n errorFallbackMessage,\n beforeRedirect,\n assignLocation = (url) => window.location.assign(url),\n}: RedirectInstallPageProps) {\n const workspace = useActiveWorkspace();\n const startedRef = useRef(false);\n const [errorMessage, setErrorMessage] = useState<string | undefined>();\n\n useEffect(() => {\n if (startedRef.current) return;\n startedRef.current = true;\n // The side effect is best-effort persistence; a throw here must never\n // block the install redirect, so swallow it and continue.\n try {\n beforeRedirect?.(workspace.id);\n } catch {\n // ignore\n }\n installRequest({workspace_id: workspace.id})\n .then((response) => {\n assignLocation(response.install_url);\n })\n .catch((error: unknown) => {\n setErrorMessage(error instanceof ApiError ? error.message : errorFallbackMessage);\n });\n }, [workspace, installRequest, beforeRedirect, errorFallbackMessage, assignLocation]);\n\n if (errorMessage) {\n return (\n <div className=\"mx-auto flex w-full max-w-[480px] flex-col gap-16\">\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">{errorMessage}</Text>\n </Callout>\n <ButtonLink asChild variant=\"muted\" className=\"w-fit\">\n <Link to=\"/workspaces/$wid/integrations\" params={{wid: workspace.id}}>\n Back to integrations\n </Link>\n </ButtonLink>\n </div>\n );\n }\n\n return <FullPageLoader />;\n}\n"],"names":["ApiError","useActiveWorkspace","ButtonLink","Callout","FullPageLoader","Text","Link","useEffect","useRef","useState","RedirectInstallPage","installRequest","errorFallbackMessage","beforeRedirect","assignLocation","url","window","location","assign","workspace","startedRef","errorMessage","setErrorMessage","current","id","workspace_id","then","response","install_url","catch","error","message","div","className","role","type","size","asChild","variant","to","params","wid"],"mappings":";AAAA,SAAQA,QAAQ,QAAO,sBAAsB;AAC7C,SAAQC,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,UAAU,QAAO,2BAA2B;AACpD,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,cAAc,QAAO,2BAA2B;AACxD,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAelD,OAAO,SAASC,oBAAoB,EAClCC,cAAc,EACdC,oBAAoB,EACpBC,cAAc,EACdC,iBAAiB,CAACC,MAAQC,OAAOC,QAAQ,CAACC,MAAM,CAACH,IAAI,EAC5B;IACzB,MAAMI,YAAYlB;IAClB,MAAMmB,aAAaZ,OAAO;IAC1B,MAAM,CAACa,cAAcC,gBAAgB,GAAGb;IAExCF,UAAU;QACR,IAAIa,WAAWG,OAAO,EAAE;QACxBH,WAAWG,OAAO,GAAG;QACrB,sEAAsE;QACtE,0DAA0D;QAC1D,IAAI;YACFV,iBAAiBM,UAAUK,EAAE;QAC/B,EAAE,OAAM;QACN,SAAS;QACX;QACAb,eAAe;YAACc,cAAcN,UAAUK,EAAE;QAAA,GACvCE,IAAI,CAAC,CAACC;YACLb,eAAea,SAASC,WAAW;QACrC,GACCC,KAAK,CAAC,CAACC;YACNR,gBAAgBQ,iBAAiB9B,WAAW8B,MAAMC,OAAO,GAAGnB;QAC9D;IACJ,GAAG;QAACO;QAAWR;QAAgBE;QAAgBD;QAAsBE;KAAe;IAEpF,IAAIO,cAAc;QAChB,qBACE,MAACW;YAAIC,WAAU;;8BACb,KAAC9B;oBAAQ+B,MAAK;oBAAQC,MAAK;8BACzB,cAAA,KAAC9B;wBAAK+B,MAAK;kCAAMf;;;8BAEnB,KAACnB;oBAAWmC,OAAO;oBAACC,SAAQ;oBAAQL,WAAU;8BAC5C,cAAA,KAAC3B;wBAAKiC,IAAG;wBAAgCC,QAAQ;4BAACC,KAAKtB,UAAUK,EAAE;wBAAA;kCAAG;;;;;IAM9E;IAEA,qBAAO,KAACpB;AACV"}
@@ -0,0 +1,15 @@
1
+ import type { RepositoryDto } from '@shipfox/api-integration-core-dto';
2
+ export declare function RepositoryPicker({ repositories, selectedRepositoryId, onSelect, isLoading, isFetchingNextPage, hasNextPage, onLoadMore, emptyMessage, searchValue, onSearchChange, searchDisabled, }: {
3
+ repositories: RepositoryDto[];
4
+ selectedRepositoryId: string | undefined;
5
+ onSelect: (repositoryId: string) => void;
6
+ isLoading: boolean;
7
+ isFetchingNextPage?: boolean;
8
+ hasNextPage?: boolean;
9
+ onLoadMore?: () => void;
10
+ emptyMessage?: string;
11
+ searchValue?: string;
12
+ onSearchChange?: (value: string) => void;
13
+ searchDisabled?: boolean;
14
+ }): import("react").JSX.Element;
15
+ //# sourceMappingURL=repository-picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository-picker.d.ts","sourceRoot":"","sources":["../../src/components/repository-picker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,mCAAmC,CAAC;AASrE,wBAAgB,gBAAgB,CAAC,EAC/B,YAAY,EACZ,oBAAoB,EACpB,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,YAAuC,EACvC,WAAW,EACX,cAAc,EACd,cAAc,GACf,EAAE;IACD,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,+BAoEA"}
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from '@shipfox/react-ui/button';
3
+ import { Input } from '@shipfox/react-ui/input';
4
+ import { Label } from '@shipfox/react-ui/label';
5
+ import { RadioGroup, RadioGroupItem } from '@shipfox/react-ui/radio-group';
6
+ import { Skeleton } from '@shipfox/react-ui/skeleton';
7
+ import { Text } from '@shipfox/react-ui/typography';
8
+ import { useId } from 'react';
9
+ export function RepositoryPicker({ repositories, selectedRepositoryId, onSelect, isLoading, isFetchingNextPage, hasNextPage, onLoadMore, emptyMessage = 'No repositories found.', searchValue, onSearchChange, searchDisabled }) {
10
+ const labelId = useId();
11
+ const showSearch = onSearchChange !== undefined;
12
+ return /*#__PURE__*/ _jsxs("div", {
13
+ className: "flex flex-col gap-10",
14
+ children: [
15
+ /*#__PURE__*/ _jsx(Label, {
16
+ id: labelId,
17
+ className: "sr-only",
18
+ children: "Repository"
19
+ }),
20
+ showSearch ? /*#__PURE__*/ _jsx(Input, {
21
+ type: "search",
22
+ placeholder: "Search repositories…",
23
+ "aria-label": "Search repositories",
24
+ value: searchValue ?? '',
25
+ onChange: (event)=>onSearchChange?.(event.target.value),
26
+ disabled: searchDisabled
27
+ }) : null,
28
+ isLoading ? /*#__PURE__*/ _jsx(Skeleton, {
29
+ className: "h-58 w-full"
30
+ }) : null,
31
+ !isLoading && repositories.length === 0 ? /*#__PURE__*/ _jsx("div", {
32
+ className: "rounded-8 border border-border-neutral-base bg-background-subtle-base p-14",
33
+ children: /*#__PURE__*/ _jsx(Text, {
34
+ size: "sm",
35
+ children: emptyMessage
36
+ })
37
+ }) : null,
38
+ repositories.length > 0 ? /*#__PURE__*/ _jsx(RadioGroup, {
39
+ "aria-labelledby": labelId,
40
+ value: selectedRepositoryId ?? '',
41
+ onValueChange: onSelect,
42
+ className: "grid grid-cols-2 gap-8 min-[1200px]:grid-cols-3 max-[760px]:grid-cols-1",
43
+ children: repositories.map((repository)=>/*#__PURE__*/ _jsx(RadioGroupItem, {
44
+ value: repository.external_repository_id,
45
+ className: "p-12",
46
+ children: /*#__PURE__*/ _jsxs("span", {
47
+ className: "flex min-w-0 items-center justify-between gap-10",
48
+ children: [
49
+ /*#__PURE__*/ _jsx(Text, {
50
+ as: "span",
51
+ size: "sm",
52
+ bold: true,
53
+ className: "truncate",
54
+ children: repository.full_name
55
+ }),
56
+ /*#__PURE__*/ _jsx(Text, {
57
+ as: "span",
58
+ size: "xs",
59
+ className: "shrink-0 text-foreground-neutral-muted",
60
+ children: repository.default_branch
61
+ })
62
+ ]
63
+ })
64
+ }, repository.external_repository_id))
65
+ }) : null,
66
+ hasNextPage && onLoadMore ? /*#__PURE__*/ _jsx(Button, {
67
+ type: "button",
68
+ variant: "secondary",
69
+ size: "sm",
70
+ isLoading: isFetchingNextPage ?? false,
71
+ onClick: onLoadMore,
72
+ children: "Load more"
73
+ }) : null
74
+ ]
75
+ });
76
+ }
77
+
78
+ //# sourceMappingURL=repository-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/repository-picker.tsx"],"sourcesContent":["import type {RepositoryDto} from '@shipfox/api-integration-core-dto';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Input} from '@shipfox/react-ui/input';\nimport {Label} from '@shipfox/react-ui/label';\nimport {RadioGroup, RadioGroupItem} from '@shipfox/react-ui/radio-group';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useId} from 'react';\n\nexport function RepositoryPicker({\n repositories,\n selectedRepositoryId,\n onSelect,\n isLoading,\n isFetchingNextPage,\n hasNextPage,\n onLoadMore,\n emptyMessage = 'No repositories found.',\n searchValue,\n onSearchChange,\n searchDisabled,\n}: {\n repositories: RepositoryDto[];\n selectedRepositoryId: string | undefined;\n onSelect: (repositoryId: string) => void;\n isLoading: boolean;\n isFetchingNextPage?: boolean;\n hasNextPage?: boolean;\n onLoadMore?: () => void;\n emptyMessage?: string;\n searchValue?: string;\n onSearchChange?: (value: string) => void;\n searchDisabled?: boolean;\n}) {\n const labelId = useId();\n const showSearch = onSearchChange !== undefined;\n\n return (\n <div className=\"flex flex-col gap-10\">\n <Label id={labelId} className=\"sr-only\">\n Repository\n </Label>\n\n {showSearch ? (\n <Input\n type=\"search\"\n placeholder=\"Search repositories…\"\n aria-label=\"Search repositories\"\n value={searchValue ?? ''}\n onChange={(event) => onSearchChange?.(event.target.value)}\n disabled={searchDisabled}\n />\n ) : null}\n\n {isLoading ? <Skeleton className=\"h-58 w-full\" /> : null}\n\n {!isLoading && repositories.length === 0 ? (\n <div className=\"rounded-8 border border-border-neutral-base bg-background-subtle-base p-14\">\n <Text size=\"sm\">{emptyMessage}</Text>\n </div>\n ) : null}\n\n {repositories.length > 0 ? (\n <RadioGroup\n aria-labelledby={labelId}\n value={selectedRepositoryId ?? ''}\n onValueChange={onSelect}\n className=\"grid grid-cols-2 gap-8 min-[1200px]:grid-cols-3 max-[760px]:grid-cols-1\"\n >\n {repositories.map((repository) => (\n <RadioGroupItem\n key={repository.external_repository_id}\n value={repository.external_repository_id}\n className=\"p-12\"\n >\n <span className=\"flex min-w-0 items-center justify-between gap-10\">\n <Text as=\"span\" size=\"sm\" bold className=\"truncate\">\n {repository.full_name}\n </Text>\n <Text as=\"span\" size=\"xs\" className=\"shrink-0 text-foreground-neutral-muted\">\n {repository.default_branch}\n </Text>\n </span>\n </RadioGroupItem>\n ))}\n </RadioGroup>\n ) : null}\n\n {hasNextPage && onLoadMore ? (\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n isLoading={isFetchingNextPage ?? false}\n onClick={onLoadMore}\n >\n Load more\n </Button>\n ) : null}\n </div>\n );\n}\n"],"names":["Button","Input","Label","RadioGroup","RadioGroupItem","Skeleton","Text","useId","RepositoryPicker","repositories","selectedRepositoryId","onSelect","isLoading","isFetchingNextPage","hasNextPage","onLoadMore","emptyMessage","searchValue","onSearchChange","searchDisabled","labelId","showSearch","undefined","div","className","id","type","placeholder","aria-label","value","onChange","event","target","disabled","length","size","aria-labelledby","onValueChange","map","repository","external_repository_id","span","as","bold","full_name","default_branch","variant","onClick"],"mappings":";AACA,SAAQA,MAAM,QAAO,2BAA2B;AAChD,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,UAAU,EAAEC,cAAc,QAAO,gCAAgC;AACzE,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,KAAK,QAAO,QAAQ;AAE5B,OAAO,SAASC,iBAAiB,EAC/BC,YAAY,EACZC,oBAAoB,EACpBC,QAAQ,EACRC,SAAS,EACTC,kBAAkB,EAClBC,WAAW,EACXC,UAAU,EACVC,eAAe,wBAAwB,EACvCC,WAAW,EACXC,cAAc,EACdC,cAAc,EAaf;IACC,MAAMC,UAAUb;IAChB,MAAMc,aAAaH,mBAAmBI;IAEtC,qBACE,MAACC;QAAIC,WAAU;;0BACb,KAACtB;gBAAMuB,IAAIL;gBAASI,WAAU;0BAAU;;YAIvCH,2BACC,KAACpB;gBACCyB,MAAK;gBACLC,aAAY;gBACZC,cAAW;gBACXC,OAAOZ,eAAe;gBACtBa,UAAU,CAACC,QAAUb,iBAAiBa,MAAMC,MAAM,CAACH,KAAK;gBACxDI,UAAUd;iBAEV;YAEHP,0BAAY,KAACP;gBAASmB,WAAU;iBAAmB;YAEnD,CAACZ,aAAaH,aAAayB,MAAM,KAAK,kBACrC,KAACX;gBAAIC,WAAU;0BACb,cAAA,KAAClB;oBAAK6B,MAAK;8BAAMnB;;iBAEjB;YAEHP,aAAayB,MAAM,GAAG,kBACrB,KAAC/B;gBACCiC,mBAAiBhB;gBACjBS,OAAOnB,wBAAwB;gBAC/B2B,eAAe1B;gBACfa,WAAU;0BAETf,aAAa6B,GAAG,CAAC,CAACC,2BACjB,KAACnC;wBAECyB,OAAOU,WAAWC,sBAAsB;wBACxChB,WAAU;kCAEV,cAAA,MAACiB;4BAAKjB,WAAU;;8CACd,KAAClB;oCAAKoC,IAAG;oCAAOP,MAAK;oCAAKQ,IAAI;oCAACnB,WAAU;8CACtCe,WAAWK,SAAS;;8CAEvB,KAACtC;oCAAKoC,IAAG;oCAAOP,MAAK;oCAAKX,WAAU;8CACjCe,WAAWM,cAAc;;;;uBATzBN,WAAWC,sBAAsB;iBAe1C;YAEH1B,eAAeC,2BACd,KAACf;gBACC0B,MAAK;gBACLoB,SAAQ;gBACRX,MAAK;gBACLvB,WAAWC,sBAAsB;gBACjCkC,SAAShC;0BACV;iBAGC;;;AAGV"}
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ interface CopyableValueProps {
3
+ label: string;
4
+ value: string;
5
+ note?: ReactNode;
6
+ className?: string;
7
+ }
8
+ export declare function CopyableValue({ label, value, note, className }: CopyableValueProps): import("react").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=copyable-value.d.ts.map