@shipfox/client-integrations 13.0.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/dist/application/complete-integration-callback.d.ts +11 -0
  4. package/dist/application/complete-integration-callback.d.ts.map +1 -1
  5. package/dist/application/complete-integration-callback.js +38 -8
  6. package/dist/application/complete-integration-callback.js.map +1 -1
  7. package/dist/components/callback-status-shell.d.ts +1 -1
  8. package/dist/components/callback-status-shell.d.ts.map +1 -1
  9. package/dist/components/callback-status-shell.js.map +1 -1
  10. package/dist/core/models.d.ts +6 -0
  11. package/dist/core/models.d.ts.map +1 -1
  12. package/dist/core/models.js +12 -0
  13. package/dist/core/models.js.map +1 -1
  14. package/dist/feature.d.ts +8 -0
  15. package/dist/feature.d.ts.map +1 -1
  16. package/dist/feature.js +10 -0
  17. package/dist/feature.js.map +1 -1
  18. package/dist/hooks/api/integration-mapper.d.ts +3 -1
  19. package/dist/hooks/api/integration-mapper.d.ts.map +1 -1
  20. package/dist/hooks/api/integration-mapper.js +8 -0
  21. package/dist/hooks/api/integration-mapper.js.map +1 -1
  22. package/dist/hooks/api/integrations.d.ts +25 -1
  23. package/dist/hooks/api/integrations.d.ts.map +1 -1
  24. package/dist/hooks/api/integrations.js +44 -1
  25. package/dist/hooks/api/integrations.js.map +1 -1
  26. package/dist/index.d.ts +4 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/jira-callback-state.d.ts +9 -0
  31. package/dist/jira-callback-state.d.ts.map +1 -0
  32. package/dist/jira-callback-state.js +16 -0
  33. package/dist/jira-callback-state.js.map +1 -0
  34. package/dist/jira-callback.d.ts +11 -0
  35. package/dist/jira-callback.d.ts.map +1 -0
  36. package/dist/jira-callback.js +59 -0
  37. package/dist/jira-callback.js.map +1 -0
  38. package/dist/jira-form-errors.d.ts +8 -0
  39. package/dist/jira-form-errors.d.ts.map +1 -0
  40. package/dist/jira-form-errors.js +55 -0
  41. package/dist/jira-form-errors.js.map +1 -0
  42. package/dist/pages/jira-callback-page.d.ts +2 -0
  43. package/dist/pages/jira-callback-page.d.ts.map +1 -0
  44. package/dist/pages/jira-callback-page.js +341 -0
  45. package/dist/pages/jira-callback-page.js.map +1 -0
  46. package/dist/pages/jira-install-page.d.ts +2 -0
  47. package/dist/pages/jira-install-page.d.ts.map +1 -0
  48. package/dist/pages/jira-install-page.js +26 -0
  49. package/dist/pages/jira-install-page.js.map +1 -0
  50. package/dist/provider-catalog.d.ts +1 -1
  51. package/dist/provider-catalog.d.ts.map +1 -1
  52. package/dist/provider-catalog.js +5 -0
  53. package/dist/provider-catalog.js.map +1 -1
  54. package/dist/routes/jira-callback.d.ts +8 -0
  55. package/dist/routes/jira-callback.d.ts.map +1 -0
  56. package/dist/routes/jira-callback.js +9 -0
  57. package/dist/routes/jira-callback.js.map +1 -0
  58. package/dist/routes/jira.d.ts +6 -0
  59. package/dist/routes/jira.d.ts.map +1 -0
  60. package/dist/routes/jira.js +7 -0
  61. package/dist/routes/jira.js.map +1 -0
  62. package/dist/tsconfig.test.tsbuildinfo +1 -1
  63. package/package.json +9 -8
  64. package/src/application/complete-integration-callback.test.ts +44 -1
  65. package/src/application/complete-integration-callback.ts +61 -8
  66. package/src/components/callback-status-shell.tsx +4 -1
  67. package/src/components/integration-usage-events.test.ts +13 -0
  68. package/src/core/models.ts +18 -0
  69. package/src/feature.ts +10 -0
  70. package/src/hooks/api/integration-mapper.ts +6 -0
  71. package/src/hooks/api/integrations.test.ts +92 -0
  72. package/src/hooks/api/integrations.ts +71 -0
  73. package/src/index.ts +4 -0
  74. package/src/jira-callback-state.ts +19 -0
  75. package/src/jira-callback.test.ts +76 -0
  76. package/src/jira-callback.ts +67 -0
  77. package/src/jira-form-errors.test.ts +57 -0
  78. package/src/jira-form-errors.ts +94 -0
  79. package/src/pages/jira-callback-page.test.tsx +250 -0
  80. package/src/pages/jira-callback-page.tsx +366 -0
  81. package/src/pages/jira-install-page.tsx +28 -0
  82. package/src/provider-catalog.test.ts +6 -0
  83. package/src/provider-catalog.ts +7 -1
  84. package/src/routes/jira-callback.tsx +8 -0
  85. package/src/routes/jira.tsx +4 -0
  86. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,341 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useAuthState, useRefreshAuth } from '@shipfox/client-auth';
3
+ import { useRouteSearch } from '@shipfox/client-shell/runtime';
4
+ import { sessionStorageOrUndefined } from '@shipfox/client-ui';
5
+ import { Button, ButtonLink } from '@shipfox/react-ui/button';
6
+ import { Callout } from '@shipfox/react-ui/callout';
7
+ import { Card } from '@shipfox/react-ui/card';
8
+ import { FullPageLoader } from '@shipfox/react-ui/loader';
9
+ import { toast } from '@shipfox/react-ui/toast';
10
+ import { Header, Text } from '@shipfox/react-ui/typography';
11
+ import { useQueryClient } from '@tanstack/react-query';
12
+ import { Link, useNavigate } from '@tanstack/react-router';
13
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
+ import { useCompleteIntegrationCallback, useCompleteIntegrationCallbackResult } from '#application/complete-integration-callback.js';
15
+ import { CallbackStatusShell } from '#components/callback-status-shell.js';
16
+ import { useCompleteJiraCallbackMutation, useCompleteJiraSiteSelectionMutation } from '#hooks/api/integrations.js';
17
+ import { clearJiraInstallWorkspace, parseJiraCallbackQuery, readJiraInstallWorkspace, serializeJiraCallbackQuery } from '#jira-callback.js';
18
+ import { JIRA_CALLBACK_CACHE_SIZE, jiraCallbackRequests, jiraCompletedConnections, jiraSiteSelectionRequests, jiraToastedCallbacks } from '#jira-callback-state.js';
19
+ import { classifyJiraCallbackError } from '#jira-form-errors.js';
20
+ import { rememberCallbackKey, resolveWorkspaceSlug } from '#workspace-navigation.js';
21
+ export function JiraCallbackPage() {
22
+ const navigate = useNavigate();
23
+ const queryClient = useQueryClient();
24
+ const refreshAuth = useRefreshAuth();
25
+ const { workspaces, isLoading } = useAuthState();
26
+ const completeIntegrationCallback = useCompleteIntegrationCallback();
27
+ const completeIntegrationCallbackResult = useCompleteIntegrationCallbackResult();
28
+ const { mutateAsync: completeJiraCallback } = useCompleteJiraCallbackMutation();
29
+ const { mutateAsync: completeJiraSiteSelection } = useCompleteJiraSiteSelectionMutation();
30
+ const params = useRouteSearch(parseJiraCallbackQuery);
31
+ const callbackKey = params ? serializeJiraCallbackQuery(params) : undefined;
32
+ const workspaceId = useMemo(()=>readJiraInstallWorkspace(sessionStorageOrUndefined()), []);
33
+ const [sites, setSites] = useState();
34
+ const [failure, setFailure] = useState();
35
+ const [selectedCloudId, setSelectedCloudId] = useState();
36
+ const [completedWorkspace, setCompletedWorkspace] = useState();
37
+ const disposedRef = useRef(false);
38
+ const callbackKeyRef = useRef(callbackKey);
39
+ callbackKeyRef.current = callbackKey;
40
+ const [callbackStateKey, setCallbackStateKey] = useState(callbackKey);
41
+ useEffect(()=>{
42
+ disposedRef.current = false;
43
+ return ()=>{
44
+ disposedRef.current = true;
45
+ };
46
+ }, []);
47
+ const completeConnection = useCallback(async (connection, callbackKey, isActive = ()=>true)=>{
48
+ if (disposedRef.current || !isActive()) return;
49
+ const completedConnection = jiraCompletedConnections.get(callbackKey);
50
+ if (completedConnection) {
51
+ const workspaceSlug = await resolveWorkspaceSlug({
52
+ workspaceId: completedConnection.workspaceId,
53
+ fallbackWorkspaces: workspaces,
54
+ queryClient
55
+ });
56
+ if (!disposedRef.current && isActive()) setCompletedWorkspace(workspaceSlug ? {
57
+ slug: workspaceSlug
58
+ } : {});
59
+ return;
60
+ }
61
+ rememberCompletedConnection(callbackKey, connection);
62
+ try {
63
+ clearJiraInstallWorkspace(sessionStorageOrUndefined());
64
+ } catch {
65
+ // The successful API response remains the source of truth for navigation.
66
+ }
67
+ if (disposedRef.current || !isActive()) return;
68
+ if (!jiraToastedCallbacks.has(callbackKey)) {
69
+ rememberCallbackKey(jiraToastedCallbacks, callbackKey);
70
+ toast.success('Jira installed.');
71
+ }
72
+ let workspaceSlug;
73
+ try {
74
+ workspaceSlug = await resolveWorkspaceSlug({
75
+ workspaceId: connection.workspaceId,
76
+ fallbackWorkspaces: workspaces,
77
+ queryClient
78
+ });
79
+ if (disposedRef.current || !isActive()) return;
80
+ if (!workspaceSlug) {
81
+ setCompletedWorkspace({});
82
+ return;
83
+ }
84
+ setCompletedWorkspace({
85
+ slug: workspaceSlug
86
+ });
87
+ await navigate({
88
+ to: '/w/$workspaceSlug/settings/integrations',
89
+ params: {
90
+ workspaceSlug
91
+ },
92
+ replace: true
93
+ });
94
+ } catch {
95
+ if (!disposedRef.current && isActive()) setCompletedWorkspace({
96
+ slug: workspaceSlug
97
+ });
98
+ }
99
+ }, [
100
+ navigate,
101
+ queryClient,
102
+ workspaces
103
+ ]);
104
+ useEffect(()=>{
105
+ if (callbackKey === undefined) return;
106
+ setCallbackStateKey(callbackKey);
107
+ setSites(undefined);
108
+ setSelectedCloudId(undefined);
109
+ setFailure(undefined);
110
+ setCompletedWorkspace(undefined);
111
+ }, [
112
+ callbackKey
113
+ ]);
114
+ useEffect(()=>{
115
+ if (!params || !callbackKey || isLoading) return;
116
+ let active = true;
117
+ const completedConnection = jiraCompletedConnections.get(callbackKey);
118
+ if (completedConnection) {
119
+ void completeConnection(completedConnection, callbackKey, ()=>active);
120
+ return ()=>{
121
+ active = false;
122
+ };
123
+ }
124
+ const request = jiraCallbackRequests.run(callbackKey, async ()=>{
125
+ return await completeIntegrationCallbackResult({
126
+ input: params,
127
+ refreshAuth,
128
+ complete: async (query, token)=>await completeJiraCallback({
129
+ query,
130
+ token
131
+ }),
132
+ getConnection: (result)=>'sites' in result ? undefined : result
133
+ });
134
+ });
135
+ request.then(async (result)=>{
136
+ if (disposedRef.current || !active) return;
137
+ if ('sites' in result) {
138
+ setFailure(undefined);
139
+ setSites(result.sites);
140
+ return;
141
+ }
142
+ await completeConnection(result, callbackKey, ()=>active);
143
+ }, (error)=>{
144
+ if (!disposedRef.current && active) setFailure(classifyJiraCallbackError(error));
145
+ });
146
+ return ()=>{
147
+ active = false;
148
+ };
149
+ }, [
150
+ callbackKey,
151
+ completeIntegrationCallbackResult,
152
+ completeJiraCallback,
153
+ completeConnection,
154
+ isLoading,
155
+ params,
156
+ refreshAuth
157
+ ]);
158
+ function selectSite(site) {
159
+ if (!params || !callbackKey || selectedCloudId) return;
160
+ setSelectedCloudId(site.cloudId);
161
+ setFailure(undefined);
162
+ const selectionKey = callbackKey;
163
+ const request = jiraSiteSelectionRequests.run(params.state, async ()=>{
164
+ return await completeIntegrationCallback({
165
+ input: {
166
+ cloud_id: site.cloudId,
167
+ state: params.state
168
+ },
169
+ refreshAuth,
170
+ complete: async (body, token)=>await completeJiraSiteSelection({
171
+ body,
172
+ token
173
+ })
174
+ });
175
+ });
176
+ request.then(async (connection)=>{
177
+ if (disposedRef.current || callbackKeyRef.current !== selectionKey) return;
178
+ jiraCallbackRequests.clear(selectionKey);
179
+ await completeConnection(connection, selectionKey, ()=>callbackKeyRef.current === selectionKey);
180
+ }, (error)=>{
181
+ if (disposedRef.current || callbackKeyRef.current !== selectionKey) return;
182
+ setSelectedCloudId(undefined);
183
+ setFailure(classifyJiraCallbackError(error));
184
+ });
185
+ }
186
+ if (!params) {
187
+ return /*#__PURE__*/ _jsx(JiraCallbackFailurePage, {
188
+ failure: classifyJiraCallbackError(new Error('invalid callback')),
189
+ workspaceSlug: workspaces.find(({ id })=>id === workspaceId)?.slug
190
+ });
191
+ }
192
+ if (isLoading) return /*#__PURE__*/ _jsx(FullPageLoader, {
193
+ "aria-label": "Connecting Jira"
194
+ });
195
+ if (callbackStateKey === callbackKey && completedWorkspace) return /*#__PURE__*/ _jsx(CallbackStatusShell, {
196
+ title: "Jira connected",
197
+ status: "success",
198
+ message: completedWorkspace.slug ? 'Jira is connected. Continue in integrations settings.' : 'Jira is connected. Return to Shipfox to continue.',
199
+ workspaceSlug: completedWorkspace.slug,
200
+ installPath: "/w/$workspaceSlug/integrations/jira"
201
+ });
202
+ if (callbackStateKey === callbackKey && sites) return /*#__PURE__*/ _jsx(JiraSiteSelectionPage, {
203
+ sites: sites,
204
+ selectedCloudId: selectedCloudId,
205
+ failure: failure,
206
+ workspaceSlug: workspaces.find(({ id })=>id === workspaceId)?.slug,
207
+ onSelect: selectSite
208
+ });
209
+ if (callbackStateKey === callbackKey && failure) return /*#__PURE__*/ _jsx(JiraCallbackFailurePage, {
210
+ failure: failure,
211
+ workspaceSlug: workspaces.find(({ id })=>id === workspaceId)?.slug
212
+ });
213
+ return /*#__PURE__*/ _jsx(FullPageLoader, {
214
+ "aria-label": "Connecting Jira"
215
+ });
216
+ }
217
+ function rememberCompletedConnection(key, connection) {
218
+ jiraCompletedConnections.delete(key);
219
+ jiraCompletedConnections.set(key, connection);
220
+ while(jiraCompletedConnections.size > JIRA_CALLBACK_CACHE_SIZE){
221
+ const oldestKey = jiraCompletedConnections.keys().next().value;
222
+ if (oldestKey === undefined) return;
223
+ jiraCompletedConnections.delete(oldestKey);
224
+ }
225
+ }
226
+ function JiraCallbackFailurePage({ failure, workspaceSlug }) {
227
+ return /*#__PURE__*/ _jsx(CallbackStatusShell, {
228
+ title: failure.title,
229
+ message: failure.message,
230
+ startOver: failure.startOver,
231
+ switchAccount: failure.signIn,
232
+ workspaceSlug: workspaceSlug,
233
+ installPath: "/w/$workspaceSlug/integrations/jira"
234
+ });
235
+ }
236
+ function JiraSiteSelectionPage({ sites, selectedCloudId, failure, workspaceSlug, onSelect }) {
237
+ return /*#__PURE__*/ _jsx("main", {
238
+ className: "flex min-h-screen bg-background-subtle-base px-16 py-32",
239
+ children: /*#__PURE__*/ _jsxs("div", {
240
+ className: "mx-auto flex w-full max-w-[480px] flex-col justify-center gap-20",
241
+ children: [
242
+ /*#__PURE__*/ _jsxs("header", {
243
+ className: "flex flex-col gap-8",
244
+ children: [
245
+ /*#__PURE__*/ _jsx(Header, {
246
+ variant: "h2",
247
+ children: "Choose a Jira site"
248
+ }),
249
+ /*#__PURE__*/ _jsx(Text, {
250
+ size: "sm",
251
+ className: "text-foreground-neutral-muted",
252
+ children: "Select the Jira site to connect to this Shipfox workspace."
253
+ })
254
+ ]
255
+ }),
256
+ failure ? /*#__PURE__*/ _jsx(Callout, {
257
+ role: "alert",
258
+ type: "error",
259
+ children: /*#__PURE__*/ _jsx(Text, {
260
+ size: "sm",
261
+ children: failure.message
262
+ })
263
+ }) : null,
264
+ /*#__PURE__*/ _jsx("section", {
265
+ className: "flex flex-col gap-8",
266
+ "aria-label": "Choose a Jira site",
267
+ children: sites.map((site)=>/*#__PURE__*/ _jsx(Card, {
268
+ className: "p-16",
269
+ children: /*#__PURE__*/ _jsxs("div", {
270
+ className: "flex items-center justify-between gap-12",
271
+ children: [
272
+ /*#__PURE__*/ _jsxs("div", {
273
+ className: "min-w-0",
274
+ children: [
275
+ /*#__PURE__*/ _jsx(Text, {
276
+ size: "md",
277
+ bold: true,
278
+ className: "truncate",
279
+ children: site.name
280
+ }),
281
+ /*#__PURE__*/ _jsx(Text, {
282
+ size: "sm",
283
+ className: "truncate text-foreground-neutral-muted",
284
+ children: site.url
285
+ })
286
+ ]
287
+ }),
288
+ /*#__PURE__*/ _jsx(Button, {
289
+ variant: "secondary",
290
+ disabled: selectedCloudId !== undefined,
291
+ isLoading: selectedCloudId === site.cloudId,
292
+ onClick: ()=>onSelect(site),
293
+ children: "Connect"
294
+ })
295
+ ]
296
+ })
297
+ }, site.cloudId))
298
+ }),
299
+ /*#__PURE__*/ _jsxs("div", {
300
+ className: "flex flex-col gap-8 sm:flex-row sm:items-center",
301
+ children: [
302
+ failure?.startOver && workspaceSlug ? /*#__PURE__*/ _jsx(ButtonLink, {
303
+ asChild: true,
304
+ variant: "muted",
305
+ className: "min-h-44 w-full sm:w-fit",
306
+ children: /*#__PURE__*/ _jsx(Link, {
307
+ to: "/w/$workspaceSlug/integrations/jira",
308
+ params: {
309
+ workspaceSlug
310
+ },
311
+ children: "Start over"
312
+ })
313
+ }) : null,
314
+ workspaceSlug ? /*#__PURE__*/ _jsx(ButtonLink, {
315
+ asChild: true,
316
+ variant: "muted",
317
+ className: "min-h-44 w-full sm:w-fit",
318
+ children: /*#__PURE__*/ _jsx(Link, {
319
+ to: "/w/$workspaceSlug/settings/integrations",
320
+ params: {
321
+ workspaceSlug
322
+ },
323
+ children: "Back to integrations"
324
+ })
325
+ }) : /*#__PURE__*/ _jsx(ButtonLink, {
326
+ asChild: true,
327
+ variant: "muted",
328
+ className: "min-h-44 w-full sm:w-fit",
329
+ children: /*#__PURE__*/ _jsx(Link, {
330
+ to: "/",
331
+ children: "Back to Shipfox"
332
+ })
333
+ })
334
+ ]
335
+ })
336
+ ]
337
+ })
338
+ });
339
+ }
340
+
341
+ //# sourceMappingURL=jira-callback-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pages/jira-callback-page.tsx"],"sourcesContent":["import {useAuthState, useRefreshAuth} from '@shipfox/client-auth';\nimport {useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Card} from '@shipfox/react-ui/card';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {useCallback, useEffect, useMemo, useRef, useState} from 'react';\nimport {\n useCompleteIntegrationCallback,\n useCompleteIntegrationCallbackResult,\n} from '#application/complete-integration-callback.js';\nimport {CallbackStatusShell} from '#components/callback-status-shell.js';\nimport type {IntegrationConnection, JiraSite} from '#core/models.js';\nimport {\n useCompleteJiraCallbackMutation,\n useCompleteJiraSiteSelectionMutation,\n} from '#hooks/api/integrations.js';\nimport {\n clearJiraInstallWorkspace,\n parseJiraCallbackQuery,\n readJiraInstallWorkspace,\n serializeJiraCallbackQuery,\n} from '#jira-callback.js';\nimport {\n JIRA_CALLBACK_CACHE_SIZE,\n jiraCallbackRequests,\n jiraCompletedConnections,\n jiraSiteSelectionRequests,\n jiraToastedCallbacks,\n} from '#jira-callback-state.js';\nimport {classifyJiraCallbackError, type JiraCallbackFailure} from '#jira-form-errors.js';\nimport {rememberCallbackKey, resolveWorkspaceSlug} from '#workspace-navigation.js';\n\nexport function JiraCallbackPage() {\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const refreshAuth = useRefreshAuth();\n const {workspaces, isLoading} = useAuthState();\n const completeIntegrationCallback = useCompleteIntegrationCallback();\n const completeIntegrationCallbackResult = useCompleteIntegrationCallbackResult();\n const {mutateAsync: completeJiraCallback} = useCompleteJiraCallbackMutation();\n const {mutateAsync: completeJiraSiteSelection} = useCompleteJiraSiteSelectionMutation();\n const params = useRouteSearch(parseJiraCallbackQuery);\n const callbackKey = params ? serializeJiraCallbackQuery(params) : undefined;\n const workspaceId = useMemo(() => readJiraInstallWorkspace(sessionStorageOrUndefined()), []);\n const [sites, setSites] = useState<JiraSite[] | undefined>();\n const [failure, setFailure] = useState<JiraCallbackFailure | undefined>();\n const [selectedCloudId, setSelectedCloudId] = useState<string | undefined>();\n const [completedWorkspace, setCompletedWorkspace] = useState<{\n slug?: string | undefined;\n }>();\n const disposedRef = useRef(false);\n const callbackKeyRef = useRef(callbackKey);\n callbackKeyRef.current = callbackKey;\n const [callbackStateKey, setCallbackStateKey] = useState(callbackKey);\n\n useEffect(() => {\n disposedRef.current = false;\n return () => {\n disposedRef.current = true;\n };\n }, []);\n\n const completeConnection = useCallback(\n async (\n connection: IntegrationConnection,\n callbackKey: string,\n isActive: () => boolean = () => true,\n ) => {\n if (disposedRef.current || !isActive()) return;\n const completedConnection = jiraCompletedConnections.get(callbackKey);\n if (completedConnection) {\n const workspaceSlug = await resolveWorkspaceSlug({\n workspaceId: completedConnection.workspaceId,\n fallbackWorkspaces: workspaces,\n queryClient,\n });\n if (!disposedRef.current && isActive())\n setCompletedWorkspace(workspaceSlug ? {slug: workspaceSlug} : {});\n return;\n }\n rememberCompletedConnection(callbackKey, connection);\n try {\n clearJiraInstallWorkspace(sessionStorageOrUndefined());\n } catch {\n // The successful API response remains the source of truth for navigation.\n }\n if (disposedRef.current || !isActive()) return;\n if (!jiraToastedCallbacks.has(callbackKey)) {\n rememberCallbackKey(jiraToastedCallbacks, callbackKey);\n toast.success('Jira installed.');\n }\n let workspaceSlug: string | undefined;\n try {\n workspaceSlug = await resolveWorkspaceSlug({\n workspaceId: connection.workspaceId,\n fallbackWorkspaces: workspaces,\n queryClient,\n });\n if (disposedRef.current || !isActive()) return;\n if (!workspaceSlug) {\n setCompletedWorkspace({});\n return;\n }\n setCompletedWorkspace({slug: workspaceSlug});\n await navigate({\n to: '/w/$workspaceSlug/settings/integrations',\n params: {workspaceSlug},\n replace: true,\n });\n } catch {\n if (!disposedRef.current && isActive()) setCompletedWorkspace({slug: workspaceSlug});\n }\n },\n [navigate, queryClient, workspaces],\n );\n\n useEffect(() => {\n if (callbackKey === undefined) return;\n setCallbackStateKey(callbackKey);\n setSites(undefined);\n setSelectedCloudId(undefined);\n setFailure(undefined);\n setCompletedWorkspace(undefined);\n }, [callbackKey]);\n\n useEffect(() => {\n if (!params || !callbackKey || isLoading) return;\n\n let active = true;\n\n const completedConnection = jiraCompletedConnections.get(callbackKey);\n if (completedConnection) {\n void completeConnection(completedConnection, callbackKey, () => active);\n return () => {\n active = false;\n };\n }\n\n const request = jiraCallbackRequests.run(callbackKey, async () => {\n return await completeIntegrationCallbackResult({\n input: params,\n refreshAuth,\n complete: async (query, token) => await completeJiraCallback({query, token}),\n getConnection: (result) => ('sites' in result ? undefined : result),\n });\n });\n\n request.then(\n async (result) => {\n if (disposedRef.current || !active) return;\n if ('sites' in result) {\n setFailure(undefined);\n setSites(result.sites);\n return;\n }\n await completeConnection(result, callbackKey, () => active);\n },\n (error: unknown) => {\n if (!disposedRef.current && active) setFailure(classifyJiraCallbackError(error));\n },\n );\n\n return () => {\n active = false;\n };\n }, [\n callbackKey,\n completeIntegrationCallbackResult,\n completeJiraCallback,\n completeConnection,\n isLoading,\n params,\n refreshAuth,\n ]);\n\n function selectSite(site: JiraSite) {\n if (!params || !callbackKey || selectedCloudId) return;\n setSelectedCloudId(site.cloudId);\n setFailure(undefined);\n const selectionKey = callbackKey;\n const request = jiraSiteSelectionRequests.run(params.state, async () => {\n return await completeIntegrationCallback({\n input: {cloud_id: site.cloudId, state: params.state},\n refreshAuth,\n complete: async (body, token) => await completeJiraSiteSelection({body, token}),\n });\n });\n request.then(\n async (connection) => {\n if (disposedRef.current || callbackKeyRef.current !== selectionKey) return;\n jiraCallbackRequests.clear(selectionKey);\n await completeConnection(\n connection,\n selectionKey,\n () => callbackKeyRef.current === selectionKey,\n );\n },\n (error: unknown) => {\n if (disposedRef.current || callbackKeyRef.current !== selectionKey) return;\n setSelectedCloudId(undefined);\n setFailure(classifyJiraCallbackError(error));\n },\n );\n }\n\n if (!params) {\n return (\n <JiraCallbackFailurePage\n failure={classifyJiraCallbackError(new Error('invalid callback'))}\n workspaceSlug={workspaces.find(({id}) => id === workspaceId)?.slug}\n />\n );\n }\n\n if (isLoading) return <FullPageLoader aria-label=\"Connecting Jira\" />;\n\n if (callbackStateKey === callbackKey && completedWorkspace)\n return (\n <CallbackStatusShell\n title=\"Jira connected\"\n status=\"success\"\n message={\n completedWorkspace.slug\n ? 'Jira is connected. Continue in integrations settings.'\n : 'Jira is connected. Return to Shipfox to continue.'\n }\n workspaceSlug={completedWorkspace.slug}\n installPath=\"/w/$workspaceSlug/integrations/jira\"\n />\n );\n\n if (callbackStateKey === callbackKey && sites)\n return (\n <JiraSiteSelectionPage\n sites={sites}\n selectedCloudId={selectedCloudId}\n failure={failure}\n workspaceSlug={workspaces.find(({id}) => id === workspaceId)?.slug}\n onSelect={selectSite}\n />\n );\n\n if (callbackStateKey === callbackKey && failure)\n return (\n <JiraCallbackFailurePage\n failure={failure}\n workspaceSlug={workspaces.find(({id}) => id === workspaceId)?.slug}\n />\n );\n\n return <FullPageLoader aria-label=\"Connecting Jira\" />;\n}\n\nfunction rememberCompletedConnection(key: string, connection: IntegrationConnection): void {\n jiraCompletedConnections.delete(key);\n jiraCompletedConnections.set(key, connection);\n while (jiraCompletedConnections.size > JIRA_CALLBACK_CACHE_SIZE) {\n const oldestKey = jiraCompletedConnections.keys().next().value;\n if (oldestKey === undefined) return;\n jiraCompletedConnections.delete(oldestKey);\n }\n}\n\nfunction JiraCallbackFailurePage({\n failure,\n workspaceSlug,\n}: {\n failure: JiraCallbackFailure;\n workspaceSlug: string | undefined;\n}) {\n return (\n <CallbackStatusShell\n title={failure.title}\n message={failure.message}\n startOver={failure.startOver}\n switchAccount={failure.signIn}\n workspaceSlug={workspaceSlug}\n installPath=\"/w/$workspaceSlug/integrations/jira\"\n />\n );\n}\n\nfunction JiraSiteSelectionPage({\n sites,\n selectedCloudId,\n failure,\n workspaceSlug,\n onSelect,\n}: {\n sites: JiraSite[];\n selectedCloudId: string | undefined;\n failure: JiraCallbackFailure | undefined;\n workspaceSlug: string | undefined;\n onSelect: (site: JiraSite) => void;\n}) {\n return (\n <main className=\"flex min-h-screen bg-background-subtle-base px-16 py-32\">\n <div className=\"mx-auto flex w-full max-w-[480px] flex-col justify-center gap-20\">\n <header className=\"flex flex-col gap-8\">\n <Header variant=\"h2\">Choose a Jira site</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Select the Jira site to connect to this Shipfox workspace.\n </Text>\n </header>\n\n {failure ? (\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">{failure.message}</Text>\n </Callout>\n ) : null}\n\n <section className=\"flex flex-col gap-8\" aria-label=\"Choose a Jira site\">\n {sites.map((site) => (\n <Card key={site.cloudId} className=\"p-16\">\n <div className=\"flex items-center justify-between gap-12\">\n <div className=\"min-w-0\">\n <Text size=\"md\" bold className=\"truncate\">\n {site.name}\n </Text>\n <Text size=\"sm\" className=\"truncate text-foreground-neutral-muted\">\n {site.url}\n </Text>\n </div>\n <Button\n variant=\"secondary\"\n disabled={selectedCloudId !== undefined}\n isLoading={selectedCloudId === site.cloudId}\n onClick={() => onSelect(site)}\n >\n Connect\n </Button>\n </div>\n </Card>\n ))}\n </section>\n\n <div className=\"flex flex-col gap-8 sm:flex-row sm:items-center\">\n {failure?.startOver && workspaceSlug ? (\n <ButtonLink asChild variant=\"muted\" className=\"min-h-44 w-full sm:w-fit\">\n <Link to=\"/w/$workspaceSlug/integrations/jira\" params={{workspaceSlug}}>\n Start over\n </Link>\n </ButtonLink>\n ) : null}\n {workspaceSlug ? (\n <ButtonLink asChild variant=\"muted\" className=\"min-h-44 w-full sm:w-fit\">\n <Link to=\"/w/$workspaceSlug/settings/integrations\" params={{workspaceSlug}}>\n Back to integrations\n </Link>\n </ButtonLink>\n ) : (\n <ButtonLink asChild variant=\"muted\" className=\"min-h-44 w-full sm:w-fit\">\n <Link to=\"/\">Back to Shipfox</Link>\n </ButtonLink>\n )}\n </div>\n </div>\n </main>\n );\n}\n"],"names":["useAuthState","useRefreshAuth","useRouteSearch","sessionStorageOrUndefined","Button","ButtonLink","Callout","Card","FullPageLoader","toast","Header","Text","useQueryClient","Link","useNavigate","useCallback","useEffect","useMemo","useRef","useState","useCompleteIntegrationCallback","useCompleteIntegrationCallbackResult","CallbackStatusShell","useCompleteJiraCallbackMutation","useCompleteJiraSiteSelectionMutation","clearJiraInstallWorkspace","parseJiraCallbackQuery","readJiraInstallWorkspace","serializeJiraCallbackQuery","JIRA_CALLBACK_CACHE_SIZE","jiraCallbackRequests","jiraCompletedConnections","jiraSiteSelectionRequests","jiraToastedCallbacks","classifyJiraCallbackError","rememberCallbackKey","resolveWorkspaceSlug","JiraCallbackPage","navigate","queryClient","refreshAuth","workspaces","isLoading","completeIntegrationCallback","completeIntegrationCallbackResult","mutateAsync","completeJiraCallback","completeJiraSiteSelection","params","callbackKey","undefined","workspaceId","sites","setSites","failure","setFailure","selectedCloudId","setSelectedCloudId","completedWorkspace","setCompletedWorkspace","disposedRef","callbackKeyRef","current","callbackStateKey","setCallbackStateKey","completeConnection","connection","isActive","completedConnection","get","workspaceSlug","fallbackWorkspaces","slug","rememberCompletedConnection","has","success","to","replace","active","request","run","input","complete","query","token","getConnection","result","then","error","selectSite","site","cloudId","selectionKey","state","cloud_id","body","clear","JiraCallbackFailurePage","Error","find","id","aria-label","title","status","message","installPath","JiraSiteSelectionPage","onSelect","key","delete","set","size","oldestKey","keys","next","value","startOver","switchAccount","signIn","main","className","div","header","variant","role","type","section","map","bold","name","url","disabled","onClick","asChild"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function JiraInstallPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=jira-install-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jira-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/jira-install-page.tsx"],"names":[],"mappings":"AAMA,wBAAgB,eAAe,gCAqB9B"}
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { sessionStorageOrUndefined } from '@shipfox/client-ui';
3
+ import { useCallback } from 'react';
4
+ import { RedirectInstallPage } from '#components/redirect-install-page.js';
5
+ import { useCreateJiraInstallMutation } from '#hooks/api/integrations.js';
6
+ import { saveJiraInstallWorkspace } from '#jira-callback.js';
7
+ export function JiraInstallPage() {
8
+ const createInstall = useCreateJiraInstallMutation();
9
+ const installRequest = useCallback(async (body)=>await createInstall.mutateAsync(body), [
10
+ createInstall
11
+ ]);
12
+ return /*#__PURE__*/ _jsx(RedirectInstallPage, {
13
+ installRequest: installRequest,
14
+ errorFallbackMessage: "Could not start Jira install.",
15
+ loadingLabel: "Connecting Jira",
16
+ beforeRedirect: (workspaceId)=>{
17
+ try {
18
+ saveJiraInstallWorkspace(sessionStorageOrUndefined(), workspaceId);
19
+ } catch {
20
+ // Storage is only a navigation hint; the signed state is authoritative.
21
+ }
22
+ }
23
+ });
24
+ }
25
+
26
+ //# sourceMappingURL=jira-install-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pages/jira-install-page.tsx"],"sourcesContent":["import {sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {useCallback} from 'react';\nimport {RedirectInstallPage} from '#components/redirect-install-page.js';\nimport {useCreateJiraInstallMutation} from '#hooks/api/integrations.js';\nimport {saveJiraInstallWorkspace} from '#jira-callback.js';\n\nexport function JiraInstallPage() {\n const createInstall = useCreateJiraInstallMutation();\n const installRequest = useCallback(\n async (body: {workspace_id: string}) => await createInstall.mutateAsync(body),\n [createInstall],\n );\n\n return (\n <RedirectInstallPage\n installRequest={installRequest}\n errorFallbackMessage=\"Could not start Jira install.\"\n loadingLabel=\"Connecting Jira\"\n beforeRedirect={(workspaceId) => {\n try {\n saveJiraInstallWorkspace(sessionStorageOrUndefined(), workspaceId);\n } catch {\n // Storage is only a navigation hint; the signed state is authoritative.\n }\n }}\n />\n );\n}\n"],"names":["sessionStorageOrUndefined","useCallback","RedirectInstallPage","useCreateJiraInstallMutation","saveJiraInstallWorkspace","JiraInstallPage","createInstall","installRequest","body","mutateAsync","errorFallbackMessage","loadingLabel","beforeRedirect","workspaceId"],"mappings":";AAAA,SAAQA,yBAAyB,QAAO,qBAAqB;AAC7D,SAAQC,WAAW,QAAO,QAAQ;AAClC,SAAQC,mBAAmB,QAAO,uCAAuC;AACzE,SAAQC,4BAA4B,QAAO,6BAA6B;AACxE,SAAQC,wBAAwB,QAAO,oBAAoB;AAE3D,OAAO,SAASC;IACd,MAAMC,gBAAgBH;IACtB,MAAMI,iBAAiBN,YACrB,OAAOO,OAAiC,MAAMF,cAAcG,WAAW,CAACD,OACxE;QAACF;KAAc;IAGjB,qBACE,KAACJ;QACCK,gBAAgBA;QAChBG,sBAAqB;QACrBC,cAAa;QACbC,gBAAgB,CAACC;YACf,IAAI;gBACFT,yBAAyBJ,6BAA6Ba;YACxD,EAAE,OAAM;YACN,wEAAwE;YAC1E;QACF;;AAGN"}
@@ -1,5 +1,5 @@
1
1
  import type { IconName } from '@shipfox/react-ui/icon';
2
- export type ProviderSetupPath = '/w/$workspaceSlug/integrations/github' | '/w/$workspaceSlug/integrations/gitea' | '/w/$workspaceSlug/integrations/sentry' | '/w/$workspaceSlug/integrations/linear' | '/w/$workspaceSlug/integrations/slack';
2
+ export type ProviderSetupPath = '/w/$workspaceSlug/integrations/github' | '/w/$workspaceSlug/integrations/gitea' | '/w/$workspaceSlug/integrations/sentry' | '/w/$workspaceSlug/integrations/linear' | '/w/$workspaceSlug/integrations/slack' | '/w/$workspaceSlug/integrations/jira';
3
3
  interface RouteProviderCatalogEntry {
4
4
  kind: 'redirect-install' | 'direct-connect';
5
5
  iconName: IconName;
@@ -1 +1 @@
1
- {"version":3,"file":"provider-catalog.d.ts","sourceRoot":"","sources":["../src/provider-catalog.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAIrD,MAAM,MAAM,iBAAiB,GACzB,uCAAuC,GACvC,sCAAsC,GACtC,uCAAuC,GACvC,uCAAuC,GACvC,sCAAsC,CAAC;AAE3C,UAAU,yBAAyB;IACjC,IAAI,EAAE,kBAAkB,GAAG,gBAAgB,CAAC;IAC5C,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,iBAAiB,CAAC;CAC9B;AAED,UAAU,yBAAyB;IACjC,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,MAAM,MAAM,oBAAoB,GAAG,yBAAyB,GAAG,yBAAyB,CAAC;AAEzF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CA8BjE,CAAC"}
1
+ {"version":3,"file":"provider-catalog.d.ts","sourceRoot":"","sources":["../src/provider-catalog.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAIrD,MAAM,MAAM,iBAAiB,GACzB,uCAAuC,GACvC,sCAAsC,GACtC,uCAAuC,GACvC,uCAAuC,GACvC,sCAAsC,GACtC,qCAAqC,CAAC;AAE1C,UAAU,yBAAyB;IACjC,IAAI,EAAE,kBAAkB,GAAG,gBAAgB,CAAC;IAC5C,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,iBAAiB,CAAC;CAC9B;AAED,UAAU,yBAAyB;IACjC,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,MAAM,MAAM,oBAAoB,GAAG,yBAAyB,GAAG,yBAAyB,CAAC;AAEzF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAmCjE,CAAC"}
@@ -20,6 +20,11 @@ export const PROVIDER_CATALOG = {
20
20
  iconName: PROVIDER_ICONS.slack,
21
21
  setupPath: '/w/$workspaceSlug/integrations/slack'
22
22
  },
23
+ jira: {
24
+ kind: 'redirect-install',
25
+ iconName: PROVIDER_ICONS.jira,
26
+ setupPath: '/w/$workspaceSlug/integrations/jira'
27
+ },
23
28
  gitea: {
24
29
  kind: 'direct-connect',
25
30
  iconName: PROVIDER_ICONS.gitea,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider-catalog.ts"],"sourcesContent":["import {PROVIDER_ICONS} from '@shipfox/integration-icons';\nimport type {IconName} from '@shipfox/react-ui/icon';\n\n// Literal union (not string) so `<Link to={catalog.setupPath}>` stays typed\n// against TanStack Router's route tree.\nexport type ProviderSetupPath =\n | '/w/$workspaceSlug/integrations/github'\n | '/w/$workspaceSlug/integrations/gitea'\n | '/w/$workspaceSlug/integrations/sentry'\n | '/w/$workspaceSlug/integrations/linear'\n | '/w/$workspaceSlug/integrations/slack';\n\ninterface RouteProviderCatalogEntry {\n kind: 'redirect-install' | 'direct-connect';\n iconName: IconName;\n setupPath: ProviderSetupPath;\n}\n\ninterface ModalProviderCatalogEntry {\n kind: 'modal-connect';\n iconName: IconName;\n}\n\nexport type ProviderCatalogEntry = RouteProviderCatalogEntry | ModalProviderCatalogEntry;\n\nexport const PROVIDER_CATALOG: Record<string, ProviderCatalogEntry> = {\n github: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.github,\n setupPath: '/w/$workspaceSlug/integrations/github',\n },\n sentry: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.sentry,\n setupPath: '/w/$workspaceSlug/integrations/sentry',\n },\n linear: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.linear,\n setupPath: '/w/$workspaceSlug/integrations/linear',\n },\n slack: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.slack,\n setupPath: '/w/$workspaceSlug/integrations/slack',\n },\n gitea: {\n kind: 'direct-connect',\n iconName: PROVIDER_ICONS.gitea,\n setupPath: '/w/$workspaceSlug/integrations/gitea',\n },\n webhook: {\n kind: 'modal-connect',\n iconName: PROVIDER_ICONS.webhook,\n },\n};\n"],"names":["PROVIDER_ICONS","PROVIDER_CATALOG","github","kind","iconName","setupPath","sentry","linear","slack","gitea","webhook"],"mappings":"AAAA,SAAQA,cAAc,QAAO,6BAA6B;AAyB1D,OAAO,MAAMC,mBAAyD;IACpEC,QAAQ;QACNC,MAAM;QACNC,UAAUJ,eAAeE,MAAM;QAC/BG,WAAW;IACb;IACAC,QAAQ;QACNH,MAAM;QACNC,UAAUJ,eAAeM,MAAM;QAC/BD,WAAW;IACb;IACAE,QAAQ;QACNJ,MAAM;QACNC,UAAUJ,eAAeO,MAAM;QAC/BF,WAAW;IACb;IACAG,OAAO;QACLL,MAAM;QACNC,UAAUJ,eAAeQ,KAAK;QAC9BH,WAAW;IACb;IACAI,OAAO;QACLN,MAAM;QACNC,UAAUJ,eAAeS,KAAK;QAC9BJ,WAAW;IACb;IACAK,SAAS;QACPP,MAAM;QACNC,UAAUJ,eAAeU,OAAO;IAClC;AACF,EAAE"}
1
+ {"version":3,"sources":["../src/provider-catalog.ts"],"sourcesContent":["import {PROVIDER_ICONS} from '@shipfox/integration-icons';\nimport type {IconName} from '@shipfox/react-ui/icon';\n\n// Literal union (not string) so `<Link to={catalog.setupPath}>` stays typed\n// against TanStack Router's route tree.\nexport type ProviderSetupPath =\n | '/w/$workspaceSlug/integrations/github'\n | '/w/$workspaceSlug/integrations/gitea'\n | '/w/$workspaceSlug/integrations/sentry'\n | '/w/$workspaceSlug/integrations/linear'\n | '/w/$workspaceSlug/integrations/slack'\n | '/w/$workspaceSlug/integrations/jira';\n\ninterface RouteProviderCatalogEntry {\n kind: 'redirect-install' | 'direct-connect';\n iconName: IconName;\n setupPath: ProviderSetupPath;\n}\n\ninterface ModalProviderCatalogEntry {\n kind: 'modal-connect';\n iconName: IconName;\n}\n\nexport type ProviderCatalogEntry = RouteProviderCatalogEntry | ModalProviderCatalogEntry;\n\nexport const PROVIDER_CATALOG: Record<string, ProviderCatalogEntry> = {\n github: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.github,\n setupPath: '/w/$workspaceSlug/integrations/github',\n },\n sentry: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.sentry,\n setupPath: '/w/$workspaceSlug/integrations/sentry',\n },\n linear: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.linear,\n setupPath: '/w/$workspaceSlug/integrations/linear',\n },\n slack: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.slack,\n setupPath: '/w/$workspaceSlug/integrations/slack',\n },\n jira: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.jira,\n setupPath: '/w/$workspaceSlug/integrations/jira',\n },\n gitea: {\n kind: 'direct-connect',\n iconName: PROVIDER_ICONS.gitea,\n setupPath: '/w/$workspaceSlug/integrations/gitea',\n },\n webhook: {\n kind: 'modal-connect',\n iconName: PROVIDER_ICONS.webhook,\n },\n};\n"],"names":["PROVIDER_ICONS","PROVIDER_CATALOG","github","kind","iconName","setupPath","sentry","linear","slack","jira","gitea","webhook"],"mappings":"AAAA,SAAQA,cAAc,QAAO,6BAA6B;AA0B1D,OAAO,MAAMC,mBAAyD;IACpEC,QAAQ;QACNC,MAAM;QACNC,UAAUJ,eAAeE,MAAM;QAC/BG,WAAW;IACb;IACAC,QAAQ;QACNH,MAAM;QACNC,UAAUJ,eAAeM,MAAM;QAC/BD,WAAW;IACb;IACAE,QAAQ;QACNJ,MAAM;QACNC,UAAUJ,eAAeO,MAAM;QAC/BF,WAAW;IACb;IACAG,OAAO;QACLL,MAAM;QACNC,UAAUJ,eAAeQ,KAAK;QAC9BH,WAAW;IACb;IACAI,MAAM;QACJN,MAAM;QACNC,UAAUJ,eAAeS,IAAI;QAC7BJ,WAAW;IACb;IACAK,OAAO;QACLP,MAAM;QACNC,UAAUJ,eAAeU,KAAK;QAC9BL,WAAW;IACb;IACAM,SAAS;QACPR,MAAM;QACNC,UAAUJ,eAAeW,OAAO;IAClC;AACF,EAAE"}
@@ -0,0 +1,8 @@
1
+ import { JiraCallbackPage } from '#pages/jira-callback-page.js';
2
+ import { parseJiraCallbackQuery } from '../jira-callback.js';
3
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
4
+ readonly validateSearch: typeof parseJiraCallbackQuery;
5
+ readonly component: typeof JiraCallbackPage;
6
+ }>;
7
+ export default _default;
8
+ //# sourceMappingURL=jira-callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jira-callback.d.ts","sourceRoot":"","sources":["../../src/routes/jira-callback.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,gBAAgB,EAAC,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAC,sBAAsB,EAAC,MAAM,qBAAqB,CAAC;;;;;AAE3D,wBAGG"}
@@ -0,0 +1,9 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { JiraCallbackPage } from '#pages/jira-callback-page.js';
3
+ import { parseJiraCallbackQuery } from '../jira-callback.js';
4
+ export default defineRoute({
5
+ validateSearch: parseJiraCallbackQuery,
6
+ component: JiraCallbackPage
7
+ });
8
+
9
+ //# sourceMappingURL=jira-callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/jira-callback.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {JiraCallbackPage} from '#pages/jira-callback-page.js';\nimport {parseJiraCallbackQuery} from '../jira-callback.js';\n\nexport default defineRoute({\n validateSearch: parseJiraCallbackQuery,\n component: JiraCallbackPage,\n});\n"],"names":["defineRoute","JiraCallbackPage","parseJiraCallbackQuery","validateSearch","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,gBAAgB,QAAO,+BAA+B;AAC9D,SAAQC,sBAAsB,QAAO,sBAAsB;AAE3D,eAAeF,YAAY;IACzBG,gBAAgBD;IAChBE,WAAWH;AACb,GAAG"}
@@ -0,0 +1,6 @@
1
+ import { JiraInstallPage } from '#pages/jira-install-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof JiraInstallPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=jira.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jira.d.ts","sourceRoot":"","sources":["../../src/routes/jira.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,eAAe,EAAC,MAAM,6BAA6B,CAAC;;;;AAE5D,wBAAyD"}
@@ -0,0 +1,7 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { JiraInstallPage } from '#pages/jira-install-page.js';
3
+ export default defineRoute({
4
+ component: JiraInstallPage
5
+ });
6
+
7
+ //# sourceMappingURL=jira.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/jira.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {JiraInstallPage} from '#pages/jira-install-page.js';\n\nexport default defineRoute({component: JiraInstallPage});\n"],"names":["defineRoute","JiraInstallPage","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,eAAe,QAAO,8BAA8B;AAE5D,eAAeD,YAAY;IAACE,WAAWD;AAAe,GAAG"}