@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.
- package/.storybook/main.ts +1 -0
- package/.storybook/preview.css +4 -0
- package/.storybook/preview.tsx +56 -0
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +207 -0
- package/LICENSE +21 -0
- package/dist/components/connection-picker.d.ts +7 -0
- package/dist/components/connection-picker.d.ts.map +1 -0
- package/dist/components/connection-picker.js +98 -0
- package/dist/components/connection-picker.js.map +1 -0
- package/dist/components/installed-integrations-section.d.ts +14 -0
- package/dist/components/installed-integrations-section.d.ts.map +1 -0
- package/dist/components/installed-integrations-section.js +203 -0
- package/dist/components/installed-integrations-section.js.map +1 -0
- package/dist/components/integration-delete-confirm-modal.d.ts +10 -0
- package/dist/components/integration-delete-confirm-modal.d.ts.map +1 -0
- package/dist/components/integration-delete-confirm-modal.js +61 -0
- package/dist/components/integration-delete-confirm-modal.js.map +1 -0
- package/dist/components/integration-gallery-for-workspace.d.ts +9 -0
- package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -0
- package/dist/components/integration-gallery-for-workspace.js +169 -0
- package/dist/components/integration-gallery-for-workspace.js.map +1 -0
- package/dist/components/integration-gallery.d.ts +8 -0
- package/dist/components/integration-gallery.d.ts.map +1 -0
- package/dist/components/integration-gallery.js +26 -0
- package/dist/components/integration-gallery.js.map +1 -0
- package/dist/components/integration-gallery.stories.d.ts +26 -0
- package/dist/components/integration-gallery.stories.d.ts.map +1 -0
- package/dist/components/integration-gallery.stories.js +335 -0
- package/dist/components/integration-gallery.stories.js.map +1 -0
- package/dist/components/integration-usage-events.d.ts +4 -0
- package/dist/components/integration-usage-events.d.ts.map +1 -0
- package/dist/components/integration-usage-events.js +127 -0
- package/dist/components/integration-usage-events.js.map +1 -0
- package/dist/components/integration-usage-modal.d.ts +16 -0
- package/dist/components/integration-usage-modal.d.ts.map +1 -0
- package/dist/components/integration-usage-modal.js +184 -0
- package/dist/components/integration-usage-modal.js.map +1 -0
- package/dist/components/provider-grid.d.ts +14 -0
- package/dist/components/provider-grid.d.ts.map +1 -0
- package/dist/components/provider-grid.js +152 -0
- package/dist/components/provider-grid.js.map +1 -0
- package/dist/components/redirect-install-page.d.ts +19 -0
- package/dist/components/redirect-install-page.d.ts.map +1 -0
- package/dist/components/redirect-install-page.js +68 -0
- package/dist/components/redirect-install-page.js.map +1 -0
- package/dist/components/repository-picker.d.ts +15 -0
- package/dist/components/repository-picker.d.ts.map +1 -0
- package/dist/components/repository-picker.js +78 -0
- package/dist/components/repository-picker.js.map +1 -0
- package/dist/components/webhook/copyable-value.d.ts +10 -0
- package/dist/components/webhook/copyable-value.d.ts.map +1 -0
- package/dist/components/webhook/copyable-value.js +65 -0
- package/dist/components/webhook/copyable-value.js.map +1 -0
- package/dist/components/webhook/webhook-create-modal.d.ts +10 -0
- package/dist/components/webhook/webhook-create-modal.d.ts.map +1 -0
- package/dist/components/webhook/webhook-create-modal.js +233 -0
- package/dist/components/webhook/webhook-create-modal.js.map +1 -0
- package/dist/components/webhook/webhook-form-errors.d.ts +11 -0
- package/dist/components/webhook/webhook-form-errors.d.ts.map +1 -0
- package/dist/components/webhook/webhook-form-errors.js +27 -0
- package/dist/components/webhook/webhook-form-errors.js.map +1 -0
- package/dist/components/webhook/webhook-modals.stories.d.ts +25 -0
- package/dist/components/webhook/webhook-modals.stories.d.ts.map +1 -0
- package/dist/components/webhook/webhook-modals.stories.js +222 -0
- package/dist/components/webhook/webhook-modals.stories.js.map +1 -0
- package/dist/components/webhook/webhook-public-endpoint-alert.d.ts +2 -0
- package/dist/components/webhook/webhook-public-endpoint-alert.d.ts.map +1 -0
- package/dist/components/webhook/webhook-public-endpoint-alert.js +20 -0
- package/dist/components/webhook/webhook-public-endpoint-alert.js.map +1 -0
- package/dist/components/webhook/webhook-usage-details.d.ts +7 -0
- package/dist/components/webhook/webhook-usage-details.d.ts.map +1 -0
- package/dist/components/webhook/webhook-usage-details.js +49 -0
- package/dist/components/webhook/webhook-usage-details.js.map +1 -0
- package/dist/connection-status-badge.d.ts +12 -0
- package/dist/connection-status-badge.d.ts.map +1 -0
- package/dist/connection-status-badge.js +34 -0
- package/dist/connection-status-badge.js.map +1 -0
- package/dist/feature.d.ts +37 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +48 -0
- package/dist/feature.js.map +1 -0
- package/dist/hooks/api/integrations.d.ts +265 -0
- package/dist/hooks/api/integrations.d.ts.map +1 -0
- package/dist/hooks/api/integrations.js +229 -0
- package/dist/hooks/api/integrations.js.map +1 -0
- package/dist/hooks/api/webhook-connections.d.ts +91 -0
- package/dist/hooks/api/webhook-connections.d.ts.map +1 -0
- package/dist/hooks/api/webhook-connections.js +97 -0
- package/dist/hooks/api/webhook-connections.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/integration-icon.d.ts +17 -0
- package/dist/integration-icon.d.ts.map +1 -0
- package/dist/integration-icon.js +22 -0
- package/dist/integration-icon.js.map +1 -0
- package/dist/linear-callback.d.ts +17 -0
- package/dist/linear-callback.d.ts.map +1 -0
- package/dist/linear-callback.js +113 -0
- package/dist/linear-callback.js.map +1 -0
- package/dist/pages/gitea-form-errors.d.ts +11 -0
- package/dist/pages/gitea-form-errors.d.ts.map +1 -0
- package/dist/pages/gitea-form-errors.js +35 -0
- package/dist/pages/gitea-form-errors.js.map +1 -0
- package/dist/pages/gitea-install-page.d.ts +2 -0
- package/dist/pages/gitea-install-page.d.ts.map +1 -0
- package/dist/pages/gitea-install-page.js +139 -0
- package/dist/pages/gitea-install-page.js.map +1 -0
- package/dist/pages/github-install-page.d.ts +2 -0
- package/dist/pages/github-install-page.d.ts.map +1 -0
- package/dist/pages/github-install-page.js +11 -0
- package/dist/pages/github-install-page.js.map +1 -0
- package/dist/pages/linear-callback-page.d.ts +2 -0
- package/dist/pages/linear-callback-page.d.ts.map +1 -0
- package/dist/pages/linear-callback-page.js +192 -0
- package/dist/pages/linear-callback-page.js.map +1 -0
- package/dist/pages/linear-install-page.d.ts +2 -0
- package/dist/pages/linear-install-page.d.ts.map +1 -0
- package/dist/pages/linear-install-page.js +24 -0
- package/dist/pages/linear-install-page.js.map +1 -0
- package/dist/pages/sentry-callback-page.d.ts +2 -0
- package/dist/pages/sentry-callback-page.d.ts.map +1 -0
- package/dist/pages/sentry-callback-page.js +256 -0
- package/dist/pages/sentry-callback-page.js.map +1 -0
- package/dist/pages/sentry-install-page.d.ts +2 -0
- package/dist/pages/sentry-install-page.d.ts.map +1 -0
- package/dist/pages/sentry-install-page.js +17 -0
- package/dist/pages/sentry-install-page.js.map +1 -0
- package/dist/pages/source-control-onboarding-page.d.ts +2 -0
- package/dist/pages/source-control-onboarding-page.d.ts.map +1 -0
- package/dist/pages/source-control-onboarding-page.js +37 -0
- package/dist/pages/source-control-onboarding-page.js.map +1 -0
- package/dist/provider-catalog.d.ts +15 -0
- package/dist/provider-catalog.d.ts.map +1 -0
- package/dist/provider-catalog.js +28 -0
- package/dist/provider-catalog.js.map +1 -0
- package/dist/routes/gitea.d.ts +6 -0
- package/dist/routes/gitea.d.ts.map +1 -0
- package/dist/routes/gitea.js +7 -0
- package/dist/routes/gitea.js.map +1 -0
- package/dist/routes/github-callback.d.ts +6 -0
- package/dist/routes/github-callback.d.ts.map +1 -0
- package/dist/routes/github-callback.js +129 -0
- package/dist/routes/github-callback.js.map +1 -0
- package/dist/routes/github.d.ts +6 -0
- package/dist/routes/github.d.ts.map +1 -0
- package/dist/routes/github.js +7 -0
- package/dist/routes/github.js.map +1 -0
- package/dist/routes/integrations.d.ts +6 -0
- package/dist/routes/integrations.d.ts.map +1 -0
- package/dist/routes/integrations.js +7 -0
- package/dist/routes/integrations.js.map +1 -0
- package/dist/routes/linear-callback.d.ts +6 -0
- package/dist/routes/linear-callback.d.ts.map +1 -0
- package/dist/routes/linear-callback.js +7 -0
- package/dist/routes/linear-callback.js.map +1 -0
- package/dist/routes/linear.d.ts +6 -0
- package/dist/routes/linear.d.ts.map +1 -0
- package/dist/routes/linear.js +7 -0
- package/dist/routes/linear.js.map +1 -0
- package/dist/routes/sentry-callback.d.ts +6 -0
- package/dist/routes/sentry-callback.d.ts.map +1 -0
- package/dist/routes/sentry-callback.js +7 -0
- package/dist/routes/sentry-callback.js.map +1 -0
- package/dist/routes/sentry.d.ts +6 -0
- package/dist/routes/sentry.d.ts.map +1 -0
- package/dist/routes/sentry.js +7 -0
- package/dist/routes/sentry.js.map +1 -0
- package/dist/sentry-callback.d.ts +35 -0
- package/dist/sentry-callback.d.ts.map +1 -0
- package/dist/sentry-callback.js +132 -0
- package/dist/sentry-callback.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +120 -0
- package/src/components/connection-picker.tsx +89 -0
- package/src/components/installed-integrations-section.tsx +198 -0
- package/src/components/integration-delete-confirm-modal.tsx +62 -0
- package/src/components/integration-gallery-for-workspace.tsx +193 -0
- package/src/components/integration-gallery.stories.tsx +302 -0
- package/src/components/integration-gallery.test.tsx +692 -0
- package/src/components/integration-gallery.tsx +49 -0
- package/src/components/integration-usage-events.test.ts +80 -0
- package/src/components/integration-usage-events.ts +116 -0
- package/src/components/integration-usage-modal.tsx +178 -0
- package/src/components/provider-grid.tsx +163 -0
- package/src/components/redirect-install-page.test.tsx +98 -0
- package/src/components/redirect-install-page.tsx +68 -0
- package/src/components/repository-picker.tsx +102 -0
- package/src/components/webhook/copyable-value.test.tsx +41 -0
- package/src/components/webhook/copyable-value.tsx +72 -0
- package/src/components/webhook/webhook-create-modal.tsx +247 -0
- package/src/components/webhook/webhook-form-errors.test.ts +45 -0
- package/src/components/webhook/webhook-form-errors.ts +32 -0
- package/src/components/webhook/webhook-modals.stories.tsx +225 -0
- package/src/components/webhook/webhook-public-endpoint-alert.tsx +15 -0
- package/src/components/webhook/webhook-usage-details.tsx +44 -0
- package/src/connection-status-badge.tsx +38 -0
- package/src/feature.ts +47 -0
- package/src/hooks/api/integrations.test.ts +105 -0
- package/src/hooks/api/integrations.ts +287 -0
- package/src/hooks/api/webhook-connections.ts +115 -0
- package/src/index.ts +16 -0
- package/src/integration-icon.react.test.tsx +40 -0
- package/src/integration-icon.test.ts +19 -0
- package/src/integration-icon.tsx +26 -0
- package/src/linear-callback.test.ts +227 -0
- package/src/linear-callback.ts +136 -0
- package/src/pages/gitea-form-errors.test.ts +55 -0
- package/src/pages/gitea-form-errors.ts +36 -0
- package/src/pages/gitea-install-page.tsx +108 -0
- package/src/pages/github-install-page.tsx +11 -0
- package/src/pages/linear-callback-page.test.tsx +157 -0
- package/src/pages/linear-callback-page.tsx +175 -0
- package/src/pages/linear-install-page.tsx +26 -0
- package/src/pages/sentry-callback-page.test.tsx +205 -0
- package/src/pages/sentry-callback-page.tsx +238 -0
- package/src/pages/sentry-install-page.tsx +17 -0
- package/src/pages/source-control-onboarding-page.test.tsx +46 -0
- package/src/pages/source-control-onboarding-page.tsx +26 -0
- package/src/provider-catalog.test.ts +42 -0
- package/src/provider-catalog.ts +49 -0
- package/src/routes/gitea.tsx +4 -0
- package/src/routes/github-callback.tsx +114 -0
- package/src/routes/github.tsx +4 -0
- package/src/routes/integrations.tsx +4 -0
- package/src/routes/linear-callback.tsx +4 -0
- package/src/routes/linear.tsx +4 -0
- package/src/routes/sentry-callback.tsx +4 -0
- package/src/routes/sentry.tsx +4 -0
- package/src/sentry-callback.test.ts +239 -0
- package/src/sentry-callback.ts +142 -0
- package/test/render.tsx +111 -0
- package/test/setup.ts +3 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vercel.json +8 -0
- package/vitest.config.ts +75 -0
package/package.json
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shipfox/client-integrations",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
8
|
+
"directory": "libs/client/integrations"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "dist/index.js",
|
|
12
|
+
"types": "dist/index.d.ts",
|
|
13
|
+
"imports": {
|
|
14
|
+
"#*": {
|
|
15
|
+
"types": "./src/*",
|
|
16
|
+
"workspace-source": "./src/*",
|
|
17
|
+
"development": "./src/*",
|
|
18
|
+
"default": "./dist/*"
|
|
19
|
+
},
|
|
20
|
+
"#test/*": "./test/*"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"development": {
|
|
25
|
+
"types": "./src/index.ts",
|
|
26
|
+
"default": "./src/index.ts"
|
|
27
|
+
},
|
|
28
|
+
"default": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"./feature": {
|
|
34
|
+
"development": {
|
|
35
|
+
"types": "./src/feature.ts",
|
|
36
|
+
"default": "./src/feature.ts"
|
|
37
|
+
},
|
|
38
|
+
"default": {
|
|
39
|
+
"types": "./dist/feature.d.ts",
|
|
40
|
+
"default": "./dist/feature.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"./routes/*": {
|
|
44
|
+
"development": {
|
|
45
|
+
"types": "./src/routes/*.tsx",
|
|
46
|
+
"default": "./src/routes/*.tsx"
|
|
47
|
+
},
|
|
48
|
+
"default": {
|
|
49
|
+
"types": "./dist/routes/*.d.ts",
|
|
50
|
+
"default": "./dist/routes/*.js"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@swc/helpers": "^0.5.17",
|
|
56
|
+
"@tanstack/react-form": "^1.32.0",
|
|
57
|
+
"@shipfox/api-integration-core-dto": "3.0.0",
|
|
58
|
+
"@shipfox/api-integration-gitea-dto": "3.0.0",
|
|
59
|
+
"@shipfox/api-integration-github-dto": "3.0.0",
|
|
60
|
+
"@shipfox/api-integration-sentry-dto": "3.0.0",
|
|
61
|
+
"@shipfox/api-integration-linear-dto": "3.0.0",
|
|
62
|
+
"@shipfox/client-api": "0.2.0",
|
|
63
|
+
"@shipfox/api-integration-webhook-dto": "3.0.0",
|
|
64
|
+
"@shipfox/client-auth": "0.2.0",
|
|
65
|
+
"@shipfox/client-shell": "0.2.0",
|
|
66
|
+
"@shipfox/react-ui": "0.3.1",
|
|
67
|
+
"@shipfox/client-ui": "0.2.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@tanstack/react-query": "^5.101.0",
|
|
71
|
+
"@tanstack/react-router": "^1.170.16",
|
|
72
|
+
"jotai": "^2.19.1",
|
|
73
|
+
"react": "^19.0.0",
|
|
74
|
+
"react-dom": "^19.0.0"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@argos-ci/cli": "^5.0.0",
|
|
78
|
+
"@argos-ci/storybook": "^6.0.0",
|
|
79
|
+
"@storybook/addon-vitest": "^10.3.6",
|
|
80
|
+
"@storybook/react": "^10.0.0",
|
|
81
|
+
"@storybook/react-vite": "^10.0.0",
|
|
82
|
+
"@tailwindcss/vite": "^4.1.13",
|
|
83
|
+
"@tanstack/react-query": "^5.101.0",
|
|
84
|
+
"@tanstack/react-router": "^1.170.16",
|
|
85
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
86
|
+
"@testing-library/react": "^16.3.2",
|
|
87
|
+
"@testing-library/user-event": "^14.6.1",
|
|
88
|
+
"@types/react": "^19.1.11",
|
|
89
|
+
"@types/react-dom": "^19.1.7",
|
|
90
|
+
"@vitejs/plugin-react": "^6.0.0",
|
|
91
|
+
"@vitest/browser-playwright": "^4.1.5",
|
|
92
|
+
"jotai": "^2.19.1",
|
|
93
|
+
"jsdom": "^29.0.0",
|
|
94
|
+
"react": "^19.1.1",
|
|
95
|
+
"react-dom": "^19.1.1",
|
|
96
|
+
"storybook": "^10.0.0",
|
|
97
|
+
"storybook-addon-pseudo-states": "^10.0.0",
|
|
98
|
+
"tailwindcss": "^4.1.13",
|
|
99
|
+
"vite": "^8.0.3",
|
|
100
|
+
"vitest": "^4.1.5",
|
|
101
|
+
"@shipfox/biome": "1.8.1",
|
|
102
|
+
"@shipfox/swc": "1.2.5",
|
|
103
|
+
"@shipfox/client-test-setup": "0.0.2",
|
|
104
|
+
"@shipfox/typescript": "1.1.6",
|
|
105
|
+
"@shipfox/ts-config": "1.3.8",
|
|
106
|
+
"@shipfox/vite": "1.2.4",
|
|
107
|
+
"@shipfox/vitest": "1.2.2"
|
|
108
|
+
},
|
|
109
|
+
"scripts": {
|
|
110
|
+
"build": "shipfox-swc",
|
|
111
|
+
"check": "shipfox-biome-check",
|
|
112
|
+
"check:fix": "shipfox-biome-check --write",
|
|
113
|
+
"storybook": "storybook dev -p 6011",
|
|
114
|
+
"storybook:build": "storybook build -o storybook-static",
|
|
115
|
+
"test": "shipfox-vitest-run",
|
|
116
|
+
"test:watch": "shipfox-vitest-watch",
|
|
117
|
+
"type": "shipfox-tsc-check",
|
|
118
|
+
"type:emit": "shipfox-tsc-emit"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import {useIsTextTruncated} from '@shipfox/react-ui/hooks';
|
|
3
|
+
import {Label} from '@shipfox/react-ui/label';
|
|
4
|
+
import {RadioGroup, RadioGroupItem} from '@shipfox/react-ui/radio-group';
|
|
5
|
+
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
6
|
+
import {Text} from '@shipfox/react-ui/typography';
|
|
7
|
+
import {useId} from 'react';
|
|
8
|
+
import {IntegrationIcon} from '#integration-icon.js';
|
|
9
|
+
|
|
10
|
+
export function ConnectionPicker({
|
|
11
|
+
connections,
|
|
12
|
+
selectedConnectionId,
|
|
13
|
+
onSelect,
|
|
14
|
+
}: {
|
|
15
|
+
connections: IntegrationConnectionDto[];
|
|
16
|
+
selectedConnectionId: string | undefined;
|
|
17
|
+
onSelect: (connectionId: string) => void;
|
|
18
|
+
}) {
|
|
19
|
+
const labelId = useId();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex flex-col gap-10">
|
|
23
|
+
<Label id={labelId} className="sr-only">
|
|
24
|
+
Source integration
|
|
25
|
+
</Label>
|
|
26
|
+
<RadioGroup
|
|
27
|
+
aria-labelledby={labelId}
|
|
28
|
+
value={selectedConnectionId ?? ''}
|
|
29
|
+
onValueChange={onSelect}
|
|
30
|
+
className="grid grid-cols-2 gap-8 min-[1200px]:grid-cols-3 max-[760px]:grid-cols-1"
|
|
31
|
+
>
|
|
32
|
+
{connections.map((connection) => (
|
|
33
|
+
<RadioGroupItem key={connection.id} value={connection.id} className="p-12">
|
|
34
|
+
<ConnectionOption connection={connection} />
|
|
35
|
+
</RadioGroupItem>
|
|
36
|
+
))}
|
|
37
|
+
</RadioGroup>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ConnectionOption({connection}: {connection: IntegrationConnectionDto}) {
|
|
43
|
+
const {ref: nameRef, isTruncated} = useIsTextTruncated<HTMLSpanElement>(connection.display_name);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<span className="flex min-w-0 items-center gap-10">
|
|
47
|
+
<Tooltip>
|
|
48
|
+
<TooltipTrigger asChild>
|
|
49
|
+
<span className="shrink-0">
|
|
50
|
+
<IntegrationIcon
|
|
51
|
+
source={connection.provider}
|
|
52
|
+
aria-hidden
|
|
53
|
+
className="size-20 text-foreground-neutral-base"
|
|
54
|
+
/>
|
|
55
|
+
</span>
|
|
56
|
+
</TooltipTrigger>
|
|
57
|
+
<TooltipContent>{integrationName(connection.provider)}</TooltipContent>
|
|
58
|
+
</Tooltip>
|
|
59
|
+
|
|
60
|
+
<Tooltip>
|
|
61
|
+
<TooltipTrigger asChild>
|
|
62
|
+
<span ref={nameRef} className="min-w-0 truncate">
|
|
63
|
+
<Text as="span" size="sm" bold>
|
|
64
|
+
{connection.display_name}
|
|
65
|
+
</Text>
|
|
66
|
+
</span>
|
|
67
|
+
</TooltipTrigger>
|
|
68
|
+
{isTruncated ? <TooltipContent>{connection.display_name}</TooltipContent> : null}
|
|
69
|
+
</Tooltip>
|
|
70
|
+
</span>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function integrationName(provider: string): string {
|
|
75
|
+
switch (provider) {
|
|
76
|
+
case 'github':
|
|
77
|
+
return 'GitHub';
|
|
78
|
+
case 'gitea':
|
|
79
|
+
return 'Gitea';
|
|
80
|
+
case 'sentry':
|
|
81
|
+
return 'Sentry';
|
|
82
|
+
case 'webhook':
|
|
83
|
+
return 'Webhook';
|
|
84
|
+
case 'debug':
|
|
85
|
+
return 'Debug';
|
|
86
|
+
default:
|
|
87
|
+
return provider;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IntegrationConnectionDto,
|
|
3
|
+
ListIntegrationConnectionsResponseDto,
|
|
4
|
+
} from '@shipfox/api-integration-core-dto';
|
|
5
|
+
import {QueryLoadError} from '@shipfox/client-ui';
|
|
6
|
+
import {IconButton} from '@shipfox/react-ui/button';
|
|
7
|
+
import {
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuSeparator,
|
|
12
|
+
DropdownMenuTrigger,
|
|
13
|
+
} from '@shipfox/react-ui/dropdown-menu';
|
|
14
|
+
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
15
|
+
import {Skeleton} from '@shipfox/react-ui/skeleton';
|
|
16
|
+
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
17
|
+
import {cn, formatDate} from '@shipfox/react-ui/utils';
|
|
18
|
+
import type {UseQueryResult} from '@tanstack/react-query';
|
|
19
|
+
import {Link} from '@tanstack/react-router';
|
|
20
|
+
import {ConnectionStatusBadge} from '#connection-status-badge.js';
|
|
21
|
+
import {IntegrationIcon} from '#integration-icon.js';
|
|
22
|
+
import {usageEventsForConnection} from './integration-usage-events.js';
|
|
23
|
+
|
|
24
|
+
interface InstalledIntegrationsSectionProps {
|
|
25
|
+
connectionsQuery: UseQueryResult<ListIntegrationConnectionsResponseDto, Error>;
|
|
26
|
+
connections: IntegrationConnectionDto[];
|
|
27
|
+
isMutating: boolean;
|
|
28
|
+
onUse: (connectionId: string) => void;
|
|
29
|
+
onSetActive: (connection: IntegrationConnectionDto, active: boolean) => void;
|
|
30
|
+
onDelete: (connectionId: string) => void;
|
|
31
|
+
providerDisplayName: (provider: string) => string | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const INSTALLED_SURFACE_CLASS =
|
|
35
|
+
'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
|
|
36
|
+
|
|
37
|
+
export function InstalledIntegrationsSection({
|
|
38
|
+
connectionsQuery,
|
|
39
|
+
connections,
|
|
40
|
+
isMutating,
|
|
41
|
+
onUse,
|
|
42
|
+
onSetActive,
|
|
43
|
+
onDelete,
|
|
44
|
+
providerDisplayName,
|
|
45
|
+
}: InstalledIntegrationsSectionProps) {
|
|
46
|
+
return (
|
|
47
|
+
<section className="flex flex-col gap-16" aria-label="Installed integrations">
|
|
48
|
+
<div className="flex flex-col gap-4">
|
|
49
|
+
<Header variant="h3">Installed integrations</Header>
|
|
50
|
+
<Text size="sm" className="text-foreground-neutral-muted">
|
|
51
|
+
Provider accounts installed in this workspace.
|
|
52
|
+
</Text>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{connectionsQuery.isPending ? <InstalledSkeleton label="Loading integrations" /> : null}
|
|
56
|
+
|
|
57
|
+
{connectionsQuery.isError && connectionsQuery.data === undefined ? (
|
|
58
|
+
<div className={cn(INSTALLED_SURFACE_CLASS, 'px-16')}>
|
|
59
|
+
<QueryLoadError query={connectionsQuery} subject="integrations" />
|
|
60
|
+
</div>
|
|
61
|
+
) : null}
|
|
62
|
+
|
|
63
|
+
{connectionsQuery.data !== undefined && connections.length === 0 ? (
|
|
64
|
+
<div className={cn(INSTALLED_SURFACE_CLASS, 'px-16')}>
|
|
65
|
+
<EmptyState
|
|
66
|
+
icon="componentLine"
|
|
67
|
+
title="No integrations installed yet"
|
|
68
|
+
description="Install a provider below to get started."
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
) : null}
|
|
72
|
+
|
|
73
|
+
{connections.length > 0 ? (
|
|
74
|
+
<ul className={cn('divide-y divide-border-neutral-base', INSTALLED_SURFACE_CLASS)}>
|
|
75
|
+
{connections.map((connection) => (
|
|
76
|
+
<InstalledRow
|
|
77
|
+
key={connection.id}
|
|
78
|
+
connection={connection}
|
|
79
|
+
isMutating={isMutating}
|
|
80
|
+
onUse={onUse}
|
|
81
|
+
onSetActive={(nextActive) => onSetActive(connection, nextActive)}
|
|
82
|
+
onDelete={onDelete}
|
|
83
|
+
providerDisplayName={providerDisplayName}
|
|
84
|
+
/>
|
|
85
|
+
))}
|
|
86
|
+
</ul>
|
|
87
|
+
) : null}
|
|
88
|
+
</section>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function InstalledRow({
|
|
93
|
+
connection,
|
|
94
|
+
isMutating,
|
|
95
|
+
onUse,
|
|
96
|
+
onSetActive,
|
|
97
|
+
onDelete,
|
|
98
|
+
providerDisplayName,
|
|
99
|
+
}: {
|
|
100
|
+
connection: IntegrationConnectionDto;
|
|
101
|
+
isMutating: boolean;
|
|
102
|
+
onUse: (connectionId: string) => void;
|
|
103
|
+
onSetActive: (active: boolean) => void;
|
|
104
|
+
onDelete: (connectionId: string) => void;
|
|
105
|
+
providerDisplayName: (provider: string) => string | undefined;
|
|
106
|
+
}) {
|
|
107
|
+
const muted = connection.lifecycle_status === 'disabled';
|
|
108
|
+
const active = connection.lifecycle_status === 'active';
|
|
109
|
+
const recentEventsEvent = usageEventsForConnection(connection)[0]?.value ?? 'received';
|
|
110
|
+
const providerName = providerDisplayName(connection.provider);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<li className="flex items-center gap-12 px-16 py-12 transition-colors hover:bg-background-components-hover">
|
|
114
|
+
<IntegrationIcon
|
|
115
|
+
source={connection.provider}
|
|
116
|
+
aria-hidden
|
|
117
|
+
className={cn(
|
|
118
|
+
'size-24 shrink-0',
|
|
119
|
+
muted ? 'text-foreground-neutral-disabled' : 'text-foreground-neutral-base',
|
|
120
|
+
)}
|
|
121
|
+
/>
|
|
122
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
|
123
|
+
<div className="flex min-w-0 items-center gap-8">
|
|
124
|
+
<Text
|
|
125
|
+
size="md"
|
|
126
|
+
bold
|
|
127
|
+
className={cn('truncate', muted ? 'text-foreground-neutral-disabled' : undefined)}
|
|
128
|
+
>
|
|
129
|
+
{connection.display_name}
|
|
130
|
+
</Text>
|
|
131
|
+
<ConnectionStatusBadge status={connection.lifecycle_status} className="shrink-0" />
|
|
132
|
+
</div>
|
|
133
|
+
<Text size="sm" className="truncate text-foreground-neutral-muted">
|
|
134
|
+
Added {formatDate(connection.created_at)}
|
|
135
|
+
</Text>
|
|
136
|
+
</div>
|
|
137
|
+
<DropdownMenu>
|
|
138
|
+
<DropdownMenuTrigger asChild>
|
|
139
|
+
<IconButton
|
|
140
|
+
size="sm"
|
|
141
|
+
variant="transparent"
|
|
142
|
+
icon="more2Line"
|
|
143
|
+
aria-label={`Open ${connection.display_name} integration actions`}
|
|
144
|
+
/>
|
|
145
|
+
</DropdownMenuTrigger>
|
|
146
|
+
<DropdownMenuContent align="end">
|
|
147
|
+
{connection.external_url ? (
|
|
148
|
+
<DropdownMenuItem asChild>
|
|
149
|
+
<a href={connection.external_url} target="_blank" rel="noreferrer noopener">
|
|
150
|
+
{providerName ? `Open in ${providerName}` : 'Open provider settings'}
|
|
151
|
+
</a>
|
|
152
|
+
</DropdownMenuItem>
|
|
153
|
+
) : null}
|
|
154
|
+
<DropdownMenuItem onSelect={() => onUse(connection.id)}>
|
|
155
|
+
Use this integration
|
|
156
|
+
</DropdownMenuItem>
|
|
157
|
+
<DropdownMenuItem asChild>
|
|
158
|
+
<Link
|
|
159
|
+
to="/workspaces/$wid/settings/events"
|
|
160
|
+
params={{wid: connection.workspace_id}}
|
|
161
|
+
search={{source: [connection.slug], event: [recentEventsEvent]}}
|
|
162
|
+
>
|
|
163
|
+
View recent events
|
|
164
|
+
</Link>
|
|
165
|
+
</DropdownMenuItem>
|
|
166
|
+
<DropdownMenuSeparator />
|
|
167
|
+
<DropdownMenuItem disabled={isMutating} onSelect={() => onSetActive(!active)}>
|
|
168
|
+
{active ? 'Disable integration' : 'Enable integration'}
|
|
169
|
+
</DropdownMenuItem>
|
|
170
|
+
<DropdownMenuItem disabled={isMutating} onSelect={() => onDelete(connection.id)}>
|
|
171
|
+
Delete integration
|
|
172
|
+
</DropdownMenuItem>
|
|
173
|
+
</DropdownMenuContent>
|
|
174
|
+
</DropdownMenu>
|
|
175
|
+
</li>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function InstalledSkeleton({label}: {label: string}) {
|
|
180
|
+
return (
|
|
181
|
+
<ul
|
|
182
|
+
role="status"
|
|
183
|
+
aria-label={label}
|
|
184
|
+
className={cn('divide-y divide-border-neutral-base', INSTALLED_SURFACE_CLASS)}
|
|
185
|
+
>
|
|
186
|
+
{[0, 1, 2].map((row) => (
|
|
187
|
+
<li key={row} className="flex items-center gap-12 px-16 py-12">
|
|
188
|
+
<Skeleton className="size-24 shrink-0" />
|
|
189
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
|
190
|
+
<Skeleton className="h-16 w-120" />
|
|
191
|
+
<Skeleton className="h-12 w-80" />
|
|
192
|
+
</div>
|
|
193
|
+
<Skeleton className="h-20 w-72 shrink-0" />
|
|
194
|
+
</li>
|
|
195
|
+
))}
|
|
196
|
+
</ul>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {Button} from '@shipfox/react-ui/button';
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
ModalBody,
|
|
5
|
+
ModalContent,
|
|
6
|
+
ModalFooter,
|
|
7
|
+
ModalHeader,
|
|
8
|
+
ModalTitle,
|
|
9
|
+
} from '@shipfox/react-ui/modal';
|
|
10
|
+
import {Text} from '@shipfox/react-ui/typography';
|
|
11
|
+
|
|
12
|
+
interface IntegrationDeleteConfirmModalProps {
|
|
13
|
+
connectionName: string | undefined;
|
|
14
|
+
open: boolean;
|
|
15
|
+
isPending: boolean;
|
|
16
|
+
onOpenChange: (open: boolean) => void;
|
|
17
|
+
onConfirm: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function IntegrationDeleteConfirmModal({
|
|
21
|
+
connectionName,
|
|
22
|
+
open,
|
|
23
|
+
isPending,
|
|
24
|
+
onOpenChange,
|
|
25
|
+
onConfirm,
|
|
26
|
+
}: IntegrationDeleteConfirmModalProps) {
|
|
27
|
+
const name = connectionName ?? 'this integration';
|
|
28
|
+
return (
|
|
29
|
+
<Modal
|
|
30
|
+
open={open}
|
|
31
|
+
onOpenChange={(nextOpen) => {
|
|
32
|
+
if (isPending && !nextOpen) return;
|
|
33
|
+
onOpenChange(nextOpen);
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<ModalContent aria-describedby={undefined}>
|
|
37
|
+
<ModalTitle className="sr-only">Delete integration</ModalTitle>
|
|
38
|
+
<ModalHeader title="Delete integration" showClose={!isPending} />
|
|
39
|
+
<ModalBody className="gap-16">
|
|
40
|
+
<Text size="sm">
|
|
41
|
+
Are you sure you want to delete <strong className="font-medium">{name}</strong>? Once
|
|
42
|
+
deleted, Shipfox will immediately stop processing events from this integration. This
|
|
43
|
+
cannot be undone.
|
|
44
|
+
</Text>
|
|
45
|
+
</ModalBody>
|
|
46
|
+
<ModalFooter>
|
|
47
|
+
<Button
|
|
48
|
+
type="button"
|
|
49
|
+
variant="secondary"
|
|
50
|
+
disabled={isPending}
|
|
51
|
+
onClick={() => onOpenChange(false)}
|
|
52
|
+
>
|
|
53
|
+
Cancel
|
|
54
|
+
</Button>
|
|
55
|
+
<Button type="button" variant="danger" isLoading={isPending} onClick={onConfirm}>
|
|
56
|
+
Delete integration
|
|
57
|
+
</Button>
|
|
58
|
+
</ModalFooter>
|
|
59
|
+
</ModalContent>
|
|
60
|
+
</Modal>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IntegrationCapabilityDto,
|
|
3
|
+
IntegrationConnectionDto,
|
|
4
|
+
IntegrationProviderDto,
|
|
5
|
+
} from '@shipfox/api-integration-core-dto';
|
|
6
|
+
import {toast} from '@shipfox/react-ui/toast';
|
|
7
|
+
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
8
|
+
import {useState} from 'react';
|
|
9
|
+
import {
|
|
10
|
+
useDeleteIntegrationConnectionMutation,
|
|
11
|
+
useIntegrationConnectionsQuery,
|
|
12
|
+
useIntegrationProvidersQuery,
|
|
13
|
+
useUpdateIntegrationConnectionMutation,
|
|
14
|
+
} from '#hooks/api/integrations.js';
|
|
15
|
+
import {
|
|
16
|
+
useDeleteWebhookConnectionMutation,
|
|
17
|
+
useUpdateWebhookConnectionMutation,
|
|
18
|
+
} from '#hooks/api/webhook-connections.js';
|
|
19
|
+
import {InstalledIntegrationsSection} from './installed-integrations-section.js';
|
|
20
|
+
import {IntegrationDeleteConfirmModal} from './integration-delete-confirm-modal.js';
|
|
21
|
+
import {usageEventsForConnection} from './integration-usage-events.js';
|
|
22
|
+
import {IntegrationUsageModal} from './integration-usage-modal.js';
|
|
23
|
+
import {ProviderGrid} from './provider-grid.js';
|
|
24
|
+
import {WebhookCreateModal} from './webhook/webhook-create-modal.js';
|
|
25
|
+
import {WebhookUsageDetails} from './webhook/webhook-usage-details.js';
|
|
26
|
+
|
|
27
|
+
interface IntegrationGalleryForWorkspaceProps {
|
|
28
|
+
capability: IntegrationCapabilityDto | undefined;
|
|
29
|
+
emptyProvidersMessage: string;
|
|
30
|
+
workspaceId: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function IntegrationGalleryForWorkspace({
|
|
34
|
+
capability,
|
|
35
|
+
emptyProvidersMessage,
|
|
36
|
+
workspaceId,
|
|
37
|
+
}: IntegrationGalleryForWorkspaceProps) {
|
|
38
|
+
const [createProvider, setCreateProvider] = useState<string | undefined>();
|
|
39
|
+
const [usageConnectionId, setUsageConnectionId] = useState<string | undefined>();
|
|
40
|
+
const [createdUsageConnection, setCreatedUsageConnection] = useState<
|
|
41
|
+
IntegrationConnectionDto | undefined
|
|
42
|
+
>();
|
|
43
|
+
const [deleteConnectionId, setDeleteConnectionId] = useState<string | undefined>();
|
|
44
|
+
const providersQuery = useIntegrationProvidersQuery(capability ? {capability} : undefined);
|
|
45
|
+
const connectionsQuery = useIntegrationConnectionsQuery(workspaceId);
|
|
46
|
+
const updateConnection = useUpdateIntegrationConnectionMutation();
|
|
47
|
+
const deleteConnection = useDeleteIntegrationConnectionMutation();
|
|
48
|
+
const updateWebhookConnection = useUpdateWebhookConnectionMutation();
|
|
49
|
+
const deleteWebhookConnection = useDeleteWebhookConnectionMutation();
|
|
50
|
+
|
|
51
|
+
const providers = providersQuery.data?.providers ?? [];
|
|
52
|
+
const providersMap = new Map<string, IntegrationProviderDto>(
|
|
53
|
+
providers.map((provider) => [provider.provider, provider]),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const allConnections = connectionsQuery.data?.connections ?? [];
|
|
57
|
+
// Filter in memory so the all-status cache key stays shared; passing capability
|
|
58
|
+
// into the hook would collide with the active-only `source_control` key used
|
|
59
|
+
// elsewhere.
|
|
60
|
+
const connections = capability
|
|
61
|
+
? allConnections.filter((connection) => connection.capabilities.includes(capability))
|
|
62
|
+
: allConnections;
|
|
63
|
+
|
|
64
|
+
const providerDisplayName = (provider: string) => providersMap.get(provider)?.display_name;
|
|
65
|
+
const providerLabel = (provider: string) => providerDisplayName(provider) ?? provider;
|
|
66
|
+
|
|
67
|
+
const sortedConnections = [...connections].sort((a, b) => {
|
|
68
|
+
const byProvider = providerLabel(a.provider).localeCompare(providerLabel(b.provider));
|
|
69
|
+
if (byProvider !== 0) return byProvider;
|
|
70
|
+
return a.created_at.localeCompare(b.created_at);
|
|
71
|
+
});
|
|
72
|
+
const usageConnection =
|
|
73
|
+
sortedConnections.find((connection) => connection.id === usageConnectionId) ??
|
|
74
|
+
(createdUsageConnection?.id === usageConnectionId ? createdUsageConnection : null) ??
|
|
75
|
+
null;
|
|
76
|
+
const deleteConnectionTarget = sortedConnections.find(
|
|
77
|
+
(connection) => connection.id === deleteConnectionId,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
async function setConnectionActive(connection: IntegrationConnectionDto, active: boolean) {
|
|
81
|
+
try {
|
|
82
|
+
const body = {lifecycle_status: active ? 'active' : 'disabled'} as const;
|
|
83
|
+
if (connection.provider === 'webhook') {
|
|
84
|
+
await updateWebhookConnection.mutateAsync({
|
|
85
|
+
workspaceId,
|
|
86
|
+
connectionId: connection.id,
|
|
87
|
+
body,
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
await updateConnection.mutateAsync({
|
|
91
|
+
connectionId: connection.id,
|
|
92
|
+
body,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
toast.success(active ? 'Integration enabled.' : 'Integration disabled.');
|
|
96
|
+
} catch (error) {
|
|
97
|
+
toast.error(error instanceof Error ? error.message : 'Could not update integration.');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function confirmDeleteConnection() {
|
|
102
|
+
if (!deleteConnectionTarget) return;
|
|
103
|
+
try {
|
|
104
|
+
if (deleteConnectionTarget.provider === 'webhook') {
|
|
105
|
+
await deleteWebhookConnection.mutateAsync({
|
|
106
|
+
workspaceId,
|
|
107
|
+
connectionId: deleteConnectionTarget.id,
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
await deleteConnection.mutateAsync({
|
|
111
|
+
workspaceId,
|
|
112
|
+
connectionId: deleteConnectionTarget.id,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
toast.success('Integration deleted.');
|
|
116
|
+
setDeleteConnectionId(undefined);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
toast.error(error instanceof Error ? error.message : 'Could not delete integration.');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div className="flex flex-col gap-24">
|
|
124
|
+
<InstalledIntegrationsSection
|
|
125
|
+
connectionsQuery={connectionsQuery}
|
|
126
|
+
connections={sortedConnections}
|
|
127
|
+
isMutating={
|
|
128
|
+
updateConnection.isPending ||
|
|
129
|
+
deleteConnection.isPending ||
|
|
130
|
+
updateWebhookConnection.isPending ||
|
|
131
|
+
deleteWebhookConnection.isPending
|
|
132
|
+
}
|
|
133
|
+
onUse={setUsageConnectionId}
|
|
134
|
+
onSetActive={(connection, active) => {
|
|
135
|
+
void setConnectionActive(connection, active);
|
|
136
|
+
}}
|
|
137
|
+
onDelete={setDeleteConnectionId}
|
|
138
|
+
providerDisplayName={providerDisplayName}
|
|
139
|
+
/>
|
|
140
|
+
|
|
141
|
+
<section className="flex flex-col gap-16" aria-label="Available integrations">
|
|
142
|
+
<div className="flex flex-col gap-4">
|
|
143
|
+
<Header variant="h3">Available integrations</Header>
|
|
144
|
+
<Text size="sm" className="text-foreground-neutral-muted">
|
|
145
|
+
Providers available to install in this workspace.
|
|
146
|
+
</Text>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<ProviderGrid
|
|
150
|
+
providersQuery={providersQuery}
|
|
151
|
+
workspaceId={workspaceId}
|
|
152
|
+
emptyMessage={emptyProvidersMessage}
|
|
153
|
+
onOpenProvider={setCreateProvider}
|
|
154
|
+
/>
|
|
155
|
+
</section>
|
|
156
|
+
<WebhookCreateModal
|
|
157
|
+
workspaceId={workspaceId}
|
|
158
|
+
open={createProvider === 'webhook'}
|
|
159
|
+
onOpenChange={(open) => setCreateProvider(open ? 'webhook' : undefined)}
|
|
160
|
+
onCreated={(connection) => {
|
|
161
|
+
setCreatedUsageConnection(connection);
|
|
162
|
+
setUsageConnectionId(connection.id);
|
|
163
|
+
}}
|
|
164
|
+
/>
|
|
165
|
+
<IntegrationUsageModal
|
|
166
|
+
connection={usageConnection}
|
|
167
|
+
events={usageConnection ? usageEventsForConnection(usageConnection) : []}
|
|
168
|
+
open={usageConnection !== null}
|
|
169
|
+
onOpenChange={(open) => {
|
|
170
|
+
if (!open) {
|
|
171
|
+
setUsageConnectionId(undefined);
|
|
172
|
+
setCreatedUsageConnection(undefined);
|
|
173
|
+
}
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
{usageConnection?.provider === 'webhook' ? (
|
|
177
|
+
<WebhookUsageDetails workspaceId={workspaceId} connectionId={usageConnection.id} />
|
|
178
|
+
) : null}
|
|
179
|
+
</IntegrationUsageModal>
|
|
180
|
+
<IntegrationDeleteConfirmModal
|
|
181
|
+
connectionName={deleteConnectionTarget?.display_name}
|
|
182
|
+
open={deleteConnectionId !== undefined}
|
|
183
|
+
isPending={deleteConnection.isPending || deleteWebhookConnection.isPending}
|
|
184
|
+
onOpenChange={(open) => {
|
|
185
|
+
if (!open) setDeleteConnectionId(undefined);
|
|
186
|
+
}}
|
|
187
|
+
onConfirm={() => {
|
|
188
|
+
void confirmDeleteConnection();
|
|
189
|
+
}}
|
|
190
|
+
/>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|