@shipfox/client-integrations 12.0.2 → 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 (145) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +52 -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 +4 -3
  8. package/dist/components/callback-status-shell.d.ts.map +1 -1
  9. package/dist/components/callback-status-shell.js +11 -11
  10. package/dist/components/callback-status-shell.js.map +1 -1
  11. package/dist/components/installed-integrations-section.d.ts +2 -1
  12. package/dist/components/installed-integrations-section.d.ts.map +1 -1
  13. package/dist/components/installed-integrations-section.js +7 -6
  14. package/dist/components/installed-integrations-section.js.map +1 -1
  15. package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -1
  16. package/dist/components/integration-gallery-for-workspace.js +11 -2
  17. package/dist/components/integration-gallery-for-workspace.js.map +1 -1
  18. package/dist/components/integration-gallery.stories.js +10 -9
  19. package/dist/components/integration-gallery.stories.js.map +1 -1
  20. package/dist/components/provider-grid.d.ts +2 -2
  21. package/dist/components/provider-grid.d.ts.map +1 -1
  22. package/dist/components/provider-grid.js +4 -4
  23. package/dist/components/provider-grid.js.map +1 -1
  24. package/dist/components/redirect-install-page.d.ts +1 -1
  25. package/dist/components/redirect-install-page.js +2 -2
  26. package/dist/components/redirect-install-page.js.map +1 -1
  27. package/dist/components/webhook/webhook-modals.stories.js +2 -2
  28. package/dist/components/webhook/webhook-modals.stories.js.map +1 -1
  29. package/dist/core/models.d.ts +6 -0
  30. package/dist/core/models.d.ts.map +1 -1
  31. package/dist/core/models.js +12 -0
  32. package/dist/core/models.js.map +1 -1
  33. package/dist/feature.d.ts +15 -7
  34. package/dist/feature.d.ts.map +1 -1
  35. package/dist/feature.js +17 -7
  36. package/dist/feature.js.map +1 -1
  37. package/dist/hooks/api/integration-mapper.d.ts +3 -1
  38. package/dist/hooks/api/integration-mapper.d.ts.map +1 -1
  39. package/dist/hooks/api/integration-mapper.js +8 -0
  40. package/dist/hooks/api/integration-mapper.js.map +1 -1
  41. package/dist/hooks/api/integrations.d.ts +25 -1
  42. package/dist/hooks/api/integrations.d.ts.map +1 -1
  43. package/dist/hooks/api/integrations.js +45 -2
  44. package/dist/hooks/api/integrations.js.map +1 -1
  45. package/dist/index.d.ts +4 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +4 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/jira-callback-state.d.ts +9 -0
  50. package/dist/jira-callback-state.d.ts.map +1 -0
  51. package/dist/jira-callback-state.js +16 -0
  52. package/dist/jira-callback-state.js.map +1 -0
  53. package/dist/jira-callback.d.ts +11 -0
  54. package/dist/jira-callback.d.ts.map +1 -0
  55. package/dist/jira-callback.js +59 -0
  56. package/dist/jira-callback.js.map +1 -0
  57. package/dist/jira-form-errors.d.ts +8 -0
  58. package/dist/jira-form-errors.d.ts.map +1 -0
  59. package/dist/jira-form-errors.js +55 -0
  60. package/dist/jira-form-errors.js.map +1 -0
  61. package/dist/pages/gitea-install-page.js +4 -4
  62. package/dist/pages/gitea-install-page.js.map +1 -1
  63. package/dist/pages/jira-callback-page.d.ts +2 -0
  64. package/dist/pages/jira-callback-page.d.ts.map +1 -0
  65. package/dist/pages/jira-callback-page.js +341 -0
  66. package/dist/pages/jira-callback-page.js.map +1 -0
  67. package/dist/pages/jira-install-page.d.ts +2 -0
  68. package/dist/pages/jira-install-page.d.ts.map +1 -0
  69. package/dist/pages/jira-install-page.js +26 -0
  70. package/dist/pages/jira-install-page.js.map +1 -0
  71. package/dist/pages/linear-callback-page.d.ts.map +1 -1
  72. package/dist/pages/linear-callback-page.js +58 -12
  73. package/dist/pages/linear-callback-page.js.map +1 -1
  74. package/dist/pages/sentry-callback-page.d.ts.map +1 -1
  75. package/dist/pages/sentry-callback-page.js +22 -9
  76. package/dist/pages/sentry-callback-page.js.map +1 -1
  77. package/dist/pages/slack-callback-page.d.ts.map +1 -1
  78. package/dist/pages/slack-callback-page.js +50 -18
  79. package/dist/pages/slack-callback-page.js.map +1 -1
  80. package/dist/pages/source-control-onboarding-page.js +1 -1
  81. package/dist/pages/source-control-onboarding-page.js.map +1 -1
  82. package/dist/provider-catalog.d.ts +1 -1
  83. package/dist/provider-catalog.d.ts.map +1 -1
  84. package/dist/provider-catalog.js +10 -5
  85. package/dist/provider-catalog.js.map +1 -1
  86. package/dist/routes/jira-callback.d.ts +8 -0
  87. package/dist/routes/jira-callback.d.ts.map +1 -0
  88. package/dist/routes/jira-callback.js +9 -0
  89. package/dist/routes/jira-callback.js.map +1 -0
  90. package/dist/routes/jira.d.ts +6 -0
  91. package/dist/routes/jira.d.ts.map +1 -0
  92. package/dist/routes/jira.js +7 -0
  93. package/dist/routes/jira.js.map +1 -0
  94. package/dist/sentry-callback.js +2 -2
  95. package/dist/sentry-callback.js.map +1 -1
  96. package/dist/tsconfig.test.tsbuildinfo +1 -1
  97. package/dist/workspace-navigation.d.ts +13 -0
  98. package/dist/workspace-navigation.d.ts.map +1 -0
  99. package/dist/workspace-navigation.js +17 -0
  100. package/dist/workspace-navigation.js.map +1 -0
  101. package/package.json +14 -13
  102. package/src/application/complete-integration-callback.test.ts +44 -1
  103. package/src/application/complete-integration-callback.ts +61 -8
  104. package/src/components/callback-status-shell.tsx +17 -10
  105. package/src/components/installed-integrations-section.tsx +16 -9
  106. package/src/components/integration-gallery-for-workspace.tsx +20 -10
  107. package/src/components/integration-gallery.stories.tsx +10 -10
  108. package/src/components/integration-gallery.test.tsx +30 -15
  109. package/src/components/integration-usage-events.test.ts +13 -0
  110. package/src/components/provider-grid.tsx +6 -6
  111. package/src/components/redirect-install-page.test.tsx +3 -3
  112. package/src/components/redirect-install-page.tsx +2 -2
  113. package/src/components/webhook/webhook-modals.stories.tsx +2 -2
  114. package/src/core/models.ts +18 -0
  115. package/src/feature.ts +17 -7
  116. package/src/hooks/api/integration-mapper.ts +6 -0
  117. package/src/hooks/api/integrations.test.ts +92 -0
  118. package/src/hooks/api/integrations.ts +72 -1
  119. package/src/index.ts +4 -0
  120. package/src/jira-callback-state.ts +19 -0
  121. package/src/jira-callback.test.ts +76 -0
  122. package/src/jira-callback.ts +67 -0
  123. package/src/jira-form-errors.test.ts +57 -0
  124. package/src/jira-form-errors.ts +94 -0
  125. package/src/pages/gitea-install-page.tsx +2 -2
  126. package/src/pages/jira-callback-page.test.tsx +250 -0
  127. package/src/pages/jira-callback-page.tsx +366 -0
  128. package/src/pages/jira-install-page.tsx +28 -0
  129. package/src/pages/linear-callback-page.test.tsx +25 -12
  130. package/src/pages/linear-callback-page.tsx +73 -12
  131. package/src/pages/sentry-callback-page.test.tsx +8 -5
  132. package/src/pages/sentry-callback-page.tsx +32 -8
  133. package/src/pages/slack-callback-page.test.tsx +78 -0
  134. package/src/pages/slack-callback-page.tsx +56 -18
  135. package/src/pages/source-control-onboarding-page.test.tsx +4 -4
  136. package/src/pages/source-control-onboarding-page.tsx +1 -1
  137. package/src/provider-catalog.test.ts +10 -4
  138. package/src/provider-catalog.ts +16 -10
  139. package/src/routes/jira-callback.tsx +8 -0
  140. package/src/routes/jira.tsx +4 -0
  141. package/src/sentry-callback.ts +2 -2
  142. package/src/workspace-navigation.test.ts +43 -0
  143. package/src/workspace-navigation.ts +38 -0
  144. package/test/render.tsx +2 -1
  145. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,33 +1,39 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useRefreshAuth } from '@shipfox/client-auth';
2
+ import { useAuthState, useRefreshAuth } from '@shipfox/client-auth';
3
3
  import { useRouteSearch } from '@shipfox/client-shell/runtime';
4
4
  import { createSingleFlight, sessionStorageOrUndefined } from '@shipfox/client-ui';
5
5
  import { FullPageLoader } from '@shipfox/react-ui/loader';
6
6
  import { toast } from '@shipfox/react-ui/toast';
7
+ import { useQueryClient } from '@tanstack/react-query';
7
8
  import { useNavigate } from '@tanstack/react-router';
8
9
  import { useEffect, useMemo, useState } from 'react';
9
10
  import { useCompleteIntegrationCallback } from '#application/complete-integration-callback.js';
10
11
  import { CallbackStatusShell } from '#components/callback-status-shell.js';
11
12
  import { useCompleteLinearCallbackMutation } from '#hooks/api/integrations.js';
12
13
  import { classifyLinearCallbackError, clearLinearInstallWorkspace, parseLinearCallbackQuery, readLinearInstallWorkspace, serializeLinearCallbackQuery } from '#linear-callback.js';
14
+ import { rememberCallbackKey, resolveWorkspaceSlug } from '#workspace-navigation.js';
13
15
  // Retain only recent completions: this bounds long-lived callback pages while
14
16
  // still covering StrictMode and immediate Back/Forward remounts.
15
17
  const callbackRequests = createSingleFlight({
16
18
  maxTerminalResults: 32
17
19
  });
20
+ const completedCallbacks = new Set();
18
21
  // Keeps the success toast firing once per distinct callback even though the
19
22
  // effect re-runs against the cached request as the mutation identity churns.
20
23
  const toastedCallbacks = new Set();
21
24
  export function LinearCallbackPage() {
22
25
  const navigate = useNavigate();
26
+ const queryClient = useQueryClient();
23
27
  const refreshAuth = useRefreshAuth();
24
28
  const completeIntegrationCallback = useCompleteIntegrationCallback();
25
29
  const { mutateAsync: completeLinearCallback } = useCompleteLinearCallbackMutation();
30
+ const { workspaces, isLoading } = useAuthState();
26
31
  const params = useRouteSearch(parseLinearCallbackQuery);
27
32
  const workspaceId = useMemo(()=>readLinearInstallWorkspace(sessionStorageOrUndefined()), []);
28
33
  const [failure, setFailure] = useState();
34
+ const [completedWorkspace, setCompletedWorkspace] = useState();
29
35
  useEffect(()=>{
30
- if (!params) return;
36
+ if (!params || isLoading) return;
31
37
  let disposed = false;
32
38
  const key = serializeLinearCallbackQuery(params);
33
39
  const request = callbackRequests.run(key, async ()=>await completeIntegrationCallback({
@@ -40,6 +46,18 @@ export function LinearCallbackPage() {
40
46
  }));
41
47
  request.then(async (connection)=>{
42
48
  if (disposed) return;
49
+ if (completedCallbacks.has(key)) {
50
+ const workspaceSlug = await resolveWorkspaceSlug({
51
+ workspaceId: connection.workspaceId,
52
+ fallbackWorkspaces: workspaces,
53
+ queryClient
54
+ });
55
+ if (!disposed) setCompletedWorkspace(workspaceSlug ? {
56
+ slug: workspaceSlug
57
+ } : {});
58
+ return;
59
+ }
60
+ rememberCallbackKey(completedCallbacks, key);
43
61
  try {
44
62
  clearLinearInstallWorkspace(sessionStorageOrUndefined());
45
63
  } catch {
@@ -47,19 +65,37 @@ export function LinearCallbackPage() {
47
65
  }
48
66
  if (disposed) return;
49
67
  if (!toastedCallbacks.has(key)) {
50
- toastedCallbacks.add(key);
68
+ rememberCallbackKey(toastedCallbacks, key);
51
69
  toast.success('Linear installed.');
52
70
  }
71
+ let workspaceSlug;
53
72
  try {
73
+ if (disposed) return;
74
+ workspaceSlug = await resolveWorkspaceSlug({
75
+ workspaceId: connection.workspaceId,
76
+ fallbackWorkspaces: workspaces,
77
+ queryClient
78
+ });
79
+ if (disposed) return;
80
+ if (!workspaceSlug) {
81
+ setCompletedWorkspace({});
82
+ return;
83
+ }
84
+ setCompletedWorkspace({
85
+ slug: workspaceSlug
86
+ });
54
87
  await navigate({
55
- to: '/workspaces/$wid/settings/integrations',
88
+ to: '/w/$workspaceSlug/settings/integrations',
56
89
  params: {
57
- wid: connection.workspaceId
90
+ workspaceSlug
58
91
  },
59
92
  replace: true
60
93
  });
61
94
  } catch {
62
- // Keep the completed callback page visible if client navigation is interrupted.
95
+ // Keep the completed callback page visible if client navigation is interrupted.
96
+ if (!disposed) setCompletedWorkspace({
97
+ slug: workspaceSlug
98
+ });
63
99
  }
64
100
  }, (error)=>{
65
101
  if (disposed) return;
@@ -71,9 +107,12 @@ export function LinearCallbackPage() {
71
107
  }, [
72
108
  completeIntegrationCallback,
73
109
  completeLinearCallback,
110
+ isLoading,
74
111
  navigate,
75
112
  params,
76
- refreshAuth
113
+ queryClient,
114
+ refreshAuth,
115
+ workspaces
77
116
  ]);
78
117
  if (!params) {
79
118
  return /*#__PURE__*/ _jsx(LinearCallbackFailurePage, {
@@ -83,25 +122,32 @@ export function LinearCallbackPage() {
83
122
  startOver: true,
84
123
  signIn: false
85
124
  },
86
- workspaceId: workspaceId
125
+ workspaceSlug: workspaces.find(({ id })=>id === workspaceId)?.slug
87
126
  });
88
127
  }
128
+ if (completedWorkspace) return /*#__PURE__*/ _jsx(CallbackStatusShell, {
129
+ title: "Linear connected",
130
+ status: "success",
131
+ message: completedWorkspace.slug ? 'Linear is connected. Continue in integrations settings.' : 'Linear is connected. Return to Shipfox to continue.',
132
+ workspaceSlug: completedWorkspace.slug,
133
+ installPath: "/w/$workspaceSlug/integrations/linear"
134
+ });
89
135
  if (!failure) return /*#__PURE__*/ _jsx(FullPageLoader, {
90
136
  "aria-label": "Connecting Linear"
91
137
  });
92
138
  return /*#__PURE__*/ _jsx(LinearCallbackFailurePage, {
93
139
  failure: failure,
94
- workspaceId: workspaceId
140
+ workspaceSlug: workspaces.find(({ id })=>id === workspaceId)?.slug
95
141
  });
96
142
  }
97
- function LinearCallbackFailurePage({ failure, workspaceId }) {
143
+ function LinearCallbackFailurePage({ failure, workspaceSlug }) {
98
144
  return /*#__PURE__*/ _jsx(CallbackStatusShell, {
99
145
  title: failure.title ?? 'Linear install could not be completed',
100
146
  message: failure.message,
101
147
  startOver: failure.startOver,
102
148
  switchAccount: failure.signIn,
103
- workspaceId: workspaceId,
104
- installPath: "/workspaces/$wid/integrations/linear"
149
+ workspaceSlug: workspaceSlug,
150
+ installPath: "/w/$workspaceSlug/integrations/linear"
105
151
  });
106
152
  }
107
153
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/linear-callback-page.tsx"],"sourcesContent":["import {useRefreshAuth} from '@shipfox/client-auth';\nimport {useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {createSingleFlight, sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useEffect, useMemo, useState} from 'react';\nimport {useCompleteIntegrationCallback} from '#application/complete-integration-callback.js';\nimport {CallbackStatusShell} from '#components/callback-status-shell.js';\nimport type {IntegrationConnection} from '#core/models.js';\nimport {useCompleteLinearCallbackMutation} from '#hooks/api/integrations.js';\nimport {\n classifyLinearCallbackError,\n clearLinearInstallWorkspace,\n type LinearCallbackFailure,\n parseLinearCallbackQuery,\n readLinearInstallWorkspace,\n serializeLinearCallbackQuery,\n} from '#linear-callback.js';\n\n// Retain only recent completions: this bounds long-lived callback pages while\n// still covering StrictMode and immediate Back/Forward remounts.\nconst callbackRequests = createSingleFlight<string, IntegrationConnection>({\n maxTerminalResults: 32,\n});\n// Keeps the success toast firing once per distinct callback even though the\n// effect re-runs against the cached request as the mutation identity churns.\nconst toastedCallbacks = new Set<string>();\n\nexport function LinearCallbackPage() {\n const navigate = useNavigate();\n const refreshAuth = useRefreshAuth();\n const completeIntegrationCallback = useCompleteIntegrationCallback();\n const {mutateAsync: completeLinearCallback} = useCompleteLinearCallbackMutation();\n const params = useRouteSearch(parseLinearCallbackQuery);\n const workspaceId = useMemo(() => readLinearInstallWorkspace(sessionStorageOrUndefined()), []);\n const [failure, setFailure] = useState<LinearCallbackFailure | undefined>();\n useEffect(() => {\n if (!params) return;\n\n let disposed = false;\n const key = serializeLinearCallbackQuery(params);\n const request = callbackRequests.run(\n key,\n async () =>\n await completeIntegrationCallback({\n input: params,\n refreshAuth,\n complete: async (query, token) => await completeLinearCallback({query, token}),\n }),\n );\n\n request.then(\n async (connection) => {\n if (disposed) return;\n try {\n clearLinearInstallWorkspace(sessionStorageOrUndefined());\n } catch {\n // The successful API response remains the source of truth for navigation.\n }\n if (disposed) return;\n if (!toastedCallbacks.has(key)) {\n toastedCallbacks.add(key);\n toast.success('Linear installed.');\n }\n try {\n await navigate({\n to: '/workspaces/$wid/settings/integrations',\n params: {wid: connection.workspaceId},\n replace: true,\n });\n } catch {\n // Keep the completed callback page visible if client navigation is interrupted.\n }\n },\n (error: unknown) => {\n if (disposed) return;\n setFailure(classifyLinearCallbackError(error));\n },\n );\n\n return () => {\n disposed = true;\n };\n }, [completeIntegrationCallback, completeLinearCallback, navigate, params, refreshAuth]);\n\n if (!params) {\n return (\n <LinearCallbackFailurePage\n failure={{\n title: 'Invalid Linear callback',\n message: 'Invalid Linear callback. Start the install again from workspace settings.',\n startOver: true,\n signIn: false,\n }}\n workspaceId={workspaceId}\n />\n );\n }\n\n if (!failure) return <FullPageLoader aria-label=\"Connecting Linear\" />;\n\n return <LinearCallbackFailurePage failure={failure} workspaceId={workspaceId} />;\n}\n\nfunction LinearCallbackFailurePage({\n failure,\n workspaceId,\n}: {\n failure: LinearCallbackFailure;\n workspaceId: string | undefined;\n}) {\n return (\n <CallbackStatusShell\n title={failure.title ?? 'Linear install could not be completed'}\n message={failure.message}\n startOver={failure.startOver}\n switchAccount={failure.signIn}\n workspaceId={workspaceId}\n installPath=\"/workspaces/$wid/integrations/linear\"\n />\n );\n}\n"],"names":["useRefreshAuth","useRouteSearch","createSingleFlight","sessionStorageOrUndefined","FullPageLoader","toast","useNavigate","useEffect","useMemo","useState","useCompleteIntegrationCallback","CallbackStatusShell","useCompleteLinearCallbackMutation","classifyLinearCallbackError","clearLinearInstallWorkspace","parseLinearCallbackQuery","readLinearInstallWorkspace","serializeLinearCallbackQuery","callbackRequests","maxTerminalResults","toastedCallbacks","Set","LinearCallbackPage","navigate","refreshAuth","completeIntegrationCallback","mutateAsync","completeLinearCallback","params","workspaceId","failure","setFailure","disposed","key","request","run","input","complete","query","token","then","connection","has","add","success","to","wid","replace","error","LinearCallbackFailurePage","title","message","startOver","signIn","aria-label","switchAccount","installPath"],"mappings":";AAAA,SAAQA,cAAc,QAAO,uBAAuB;AACpD,SAAQC,cAAc,QAAO,gCAAgC;AAC7D,SAAQC,kBAAkB,EAAEC,yBAAyB,QAAO,qBAAqB;AACjF,SAAQC,cAAc,QAAO,2BAA2B;AACxD,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,WAAW,QAAO,yBAAyB;AACnD,SAAQC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAO,QAAQ;AACnD,SAAQC,8BAA8B,QAAO,gDAAgD;AAC7F,SAAQC,mBAAmB,QAAO,uCAAuC;AAEzE,SAAQC,iCAAiC,QAAO,6BAA6B;AAC7E,SACEC,2BAA2B,EAC3BC,2BAA2B,EAE3BC,wBAAwB,EACxBC,0BAA0B,EAC1BC,4BAA4B,QACvB,sBAAsB;AAE7B,8EAA8E;AAC9E,iEAAiE;AACjE,MAAMC,mBAAmBhB,mBAAkD;IACzEiB,oBAAoB;AACtB;AACA,4EAA4E;AAC5E,6EAA6E;AAC7E,MAAMC,mBAAmB,IAAIC;AAE7B,OAAO,SAASC;IACd,MAAMC,WAAWjB;IACjB,MAAMkB,cAAcxB;IACpB,MAAMyB,8BAA8Bf;IACpC,MAAM,EAACgB,aAAaC,sBAAsB,EAAC,GAAGf;IAC9C,MAAMgB,SAAS3B,eAAec;IAC9B,MAAMc,cAAcrB,QAAQ,IAAMQ,2BAA2Bb,8BAA8B,EAAE;IAC7F,MAAM,CAAC2B,SAASC,WAAW,GAAGtB;IAC9BF,UAAU;QACR,IAAI,CAACqB,QAAQ;QAEb,IAAII,WAAW;QACf,MAAMC,MAAMhB,6BAA6BW;QACzC,MAAMM,UAAUhB,iBAAiBiB,GAAG,CAClCF,KACA,UACE,MAAMR,4BAA4B;gBAChCW,OAAOR;gBACPJ;gBACAa,UAAU,OAAOC,OAAOC,QAAU,MAAMZ,uBAAuB;wBAACW;wBAAOC;oBAAK;YAC9E;QAGJL,QAAQM,IAAI,CACV,OAAOC;YACL,IAAIT,UAAU;YACd,IAAI;gBACFlB,4BAA4BX;YAC9B,EAAE,OAAM;YACN,0EAA0E;YAC5E;YACA,IAAI6B,UAAU;YACd,IAAI,CAACZ,iBAAiBsB,GAAG,CAACT,MAAM;gBAC9Bb,iBAAiBuB,GAAG,CAACV;gBACrB5B,MAAMuC,OAAO,CAAC;YAChB;YACA,IAAI;gBACF,MAAMrB,SAAS;oBACbsB,IAAI;oBACJjB,QAAQ;wBAACkB,KAAKL,WAAWZ,WAAW;oBAAA;oBACpCkB,SAAS;gBACX;YACF,EAAE,OAAM;YACN,gFAAgF;YAClF;QACF,GACA,CAACC;YACC,IAAIhB,UAAU;YACdD,WAAWlB,4BAA4BmC;QACzC;QAGF,OAAO;YACLhB,WAAW;QACb;IACF,GAAG;QAACP;QAA6BE;QAAwBJ;QAAUK;QAAQJ;KAAY;IAEvF,IAAI,CAACI,QAAQ;QACX,qBACE,KAACqB;YACCnB,SAAS;gBACPoB,OAAO;gBACPC,SAAS;gBACTC,WAAW;gBACXC,QAAQ;YACV;YACAxB,aAAaA;;IAGnB;IAEA,IAAI,CAACC,SAAS,qBAAO,KAAC1B;QAAekD,cAAW;;IAEhD,qBAAO,KAACL;QAA0BnB,SAASA;QAASD,aAAaA;;AACnE;AAEA,SAASoB,0BAA0B,EACjCnB,OAAO,EACPD,WAAW,EAIZ;IACC,qBACE,KAAClB;QACCuC,OAAOpB,QAAQoB,KAAK,IAAI;QACxBC,SAASrB,QAAQqB,OAAO;QACxBC,WAAWtB,QAAQsB,SAAS;QAC5BG,eAAezB,QAAQuB,MAAM;QAC7BxB,aAAaA;QACb2B,aAAY;;AAGlB"}
1
+ {"version":3,"sources":["../../src/pages/linear-callback-page.tsx"],"sourcesContent":["import {useAuthState, useRefreshAuth} from '@shipfox/client-auth';\nimport {useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {createSingleFlight, sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useEffect, useMemo, useState} from 'react';\nimport {useCompleteIntegrationCallback} from '#application/complete-integration-callback.js';\nimport {CallbackStatusShell} from '#components/callback-status-shell.js';\nimport type {IntegrationConnection} from '#core/models.js';\nimport {useCompleteLinearCallbackMutation} from '#hooks/api/integrations.js';\nimport {\n classifyLinearCallbackError,\n clearLinearInstallWorkspace,\n type LinearCallbackFailure,\n parseLinearCallbackQuery,\n readLinearInstallWorkspace,\n serializeLinearCallbackQuery,\n} from '#linear-callback.js';\nimport {rememberCallbackKey, resolveWorkspaceSlug} from '#workspace-navigation.js';\n\n// Retain only recent completions: this bounds long-lived callback pages while\n// still covering StrictMode and immediate Back/Forward remounts.\nconst callbackRequests = createSingleFlight<string, IntegrationConnection>({\n maxTerminalResults: 32,\n});\nconst completedCallbacks = new Set<string>();\n// Keeps the success toast firing once per distinct callback even though the\n// effect re-runs against the cached request as the mutation identity churns.\nconst toastedCallbacks = new Set<string>();\n\nexport function LinearCallbackPage() {\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const refreshAuth = useRefreshAuth();\n const completeIntegrationCallback = useCompleteIntegrationCallback();\n const {mutateAsync: completeLinearCallback} = useCompleteLinearCallbackMutation();\n const {workspaces, isLoading} = useAuthState();\n const params = useRouteSearch(parseLinearCallbackQuery);\n const workspaceId = useMemo(() => readLinearInstallWorkspace(sessionStorageOrUndefined()), []);\n const [failure, setFailure] = useState<LinearCallbackFailure | undefined>();\n const [completedWorkspace, setCompletedWorkspace] = useState<{\n slug?: string | undefined;\n }>();\n useEffect(() => {\n if (!params || isLoading) return;\n\n let disposed = false;\n const key = serializeLinearCallbackQuery(params);\n const request = callbackRequests.run(\n key,\n async () =>\n await completeIntegrationCallback({\n input: params,\n refreshAuth,\n complete: async (query, token) => await completeLinearCallback({query, token}),\n }),\n );\n\n request.then(\n async (connection) => {\n if (disposed) return;\n if (completedCallbacks.has(key)) {\n const workspaceSlug = await resolveWorkspaceSlug({\n workspaceId: connection.workspaceId,\n fallbackWorkspaces: workspaces,\n queryClient,\n });\n if (!disposed) setCompletedWorkspace(workspaceSlug ? {slug: workspaceSlug} : {});\n return;\n }\n rememberCallbackKey(completedCallbacks, key);\n try {\n clearLinearInstallWorkspace(sessionStorageOrUndefined());\n } catch {\n // The successful API response remains the source of truth for navigation.\n }\n if (disposed) return;\n if (!toastedCallbacks.has(key)) {\n rememberCallbackKey(toastedCallbacks, key);\n toast.success('Linear installed.');\n }\n let workspaceSlug: string | undefined;\n try {\n if (disposed) return;\n workspaceSlug = await resolveWorkspaceSlug({\n workspaceId: connection.workspaceId,\n fallbackWorkspaces: workspaces,\n queryClient,\n });\n if (disposed) 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 // Keep the completed callback page visible if client navigation is interrupted.\n if (!disposed) setCompletedWorkspace({slug: workspaceSlug});\n }\n },\n (error: unknown) => {\n if (disposed) return;\n setFailure(classifyLinearCallbackError(error));\n },\n );\n\n return () => {\n disposed = true;\n };\n }, [\n completeIntegrationCallback,\n completeLinearCallback,\n isLoading,\n navigate,\n params,\n queryClient,\n refreshAuth,\n workspaces,\n ]);\n\n if (!params) {\n return (\n <LinearCallbackFailurePage\n failure={{\n title: 'Invalid Linear callback',\n message: 'Invalid Linear callback. Start the install again from workspace settings.',\n startOver: true,\n signIn: false,\n }}\n workspaceSlug={workspaces.find(({id}) => id === workspaceId)?.slug}\n />\n );\n }\n\n if (completedWorkspace)\n return (\n <CallbackStatusShell\n title=\"Linear connected\"\n status=\"success\"\n message={\n completedWorkspace.slug\n ? 'Linear is connected. Continue in integrations settings.'\n : 'Linear is connected. Return to Shipfox to continue.'\n }\n workspaceSlug={completedWorkspace.slug}\n installPath=\"/w/$workspaceSlug/integrations/linear\"\n />\n );\n\n if (!failure) return <FullPageLoader aria-label=\"Connecting Linear\" />;\n\n return (\n <LinearCallbackFailurePage\n failure={failure}\n workspaceSlug={workspaces.find(({id}) => id === workspaceId)?.slug}\n />\n );\n}\n\nfunction LinearCallbackFailurePage({\n failure,\n workspaceSlug,\n}: {\n failure: LinearCallbackFailure;\n workspaceSlug: string | undefined;\n}) {\n return (\n <CallbackStatusShell\n title={failure.title ?? 'Linear install could not be completed'}\n message={failure.message}\n startOver={failure.startOver}\n switchAccount={failure.signIn}\n workspaceSlug={workspaceSlug}\n installPath=\"/w/$workspaceSlug/integrations/linear\"\n />\n );\n}\n"],"names":["useAuthState","useRefreshAuth","useRouteSearch","createSingleFlight","sessionStorageOrUndefined","FullPageLoader","toast","useQueryClient","useNavigate","useEffect","useMemo","useState","useCompleteIntegrationCallback","CallbackStatusShell","useCompleteLinearCallbackMutation","classifyLinearCallbackError","clearLinearInstallWorkspace","parseLinearCallbackQuery","readLinearInstallWorkspace","serializeLinearCallbackQuery","rememberCallbackKey","resolveWorkspaceSlug","callbackRequests","maxTerminalResults","completedCallbacks","Set","toastedCallbacks","LinearCallbackPage","navigate","queryClient","refreshAuth","completeIntegrationCallback","mutateAsync","completeLinearCallback","workspaces","isLoading","params","workspaceId","failure","setFailure","completedWorkspace","setCompletedWorkspace","disposed","key","request","run","input","complete","query","token","then","connection","has","workspaceSlug","fallbackWorkspaces","slug","success","to","replace","error","LinearCallbackFailurePage","title","message","startOver","signIn","find","id","status","installPath","aria-label","switchAccount"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sentry-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/sentry-callback-page.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,kBAAkB,uCAmMjC"}
1
+ {"version":3,"file":"sentry-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/sentry-callback-page.tsx"],"names":[],"mappings":"AA2BA,wBAAgB,kBAAkB,uCAyNjC"}
@@ -8,14 +8,17 @@ import { Card } from '@shipfox/react-ui/card';
8
8
  import { FullPageLoader } from '@shipfox/react-ui/loader';
9
9
  import { toast } from '@shipfox/react-ui/toast';
10
10
  import { Header, Text } from '@shipfox/react-ui/typography';
11
+ import { useQueryClient } from '@tanstack/react-query';
11
12
  import { Link, useNavigate } from '@tanstack/react-router';
12
13
  import { useEffect, useMemo, useRef, useState } from 'react';
13
14
  import { useCompleteIntegrationCallback } from '#application/complete-integration-callback.js';
14
15
  import { connectSentry } from '#hooks/api/integrations.js';
15
16
  import { classifySentryConnectError, clearSentryInstallWorkspace, parseSentryCallbackParams, preselectSentryWorkspace, readSentryInstallWorkspace } from '#sentry-callback.js';
17
+ import { resolveWorkspaceSlug } from '#workspace-navigation.js';
16
18
  const connectRequests = createSingleFlight();
17
19
  export function SentryCallbackPage() {
18
20
  const navigate = useNavigate();
21
+ const queryClient = useQueryClient();
19
22
  const refreshAuth = useRefreshAuth();
20
23
  const { workspaces, isLoading } = useAuthState();
21
24
  const completeIntegrationCallback = useCompleteIntegrationCallback();
@@ -109,12 +112,21 @@ export function SentryCallbackPage() {
109
112
  clearSentryInstallWorkspace(sessionStorageOrUndefined());
110
113
  if (disposedRef.current) return;
111
114
  toast.success('Sentry installed.');
112
- await navigate({
113
- to: '/workspaces/$wid/settings/integrations',
115
+ const workspaceSlug = await resolveWorkspaceSlug({
116
+ workspaceId,
117
+ fallbackWorkspaces: workspaces,
118
+ queryClient
119
+ });
120
+ if (disposedRef.current) return;
121
+ await navigate(workspaceSlug ? {
122
+ to: '/w/$workspaceSlug/settings/integrations',
114
123
  params: {
115
- wid: workspaceId
124
+ workspaceSlug
116
125
  },
117
126
  replace: true
127
+ } : {
128
+ to: '/',
129
+ replace: true
118
130
  });
119
131
  }).catch((error)=>{
120
132
  if (disposedRef.current) return;
@@ -136,6 +148,7 @@ export function SentryCallbackPage() {
136
148
  ...workspaces.filter((workspace)=>workspace.id !== preselectedId)
137
149
  ] : workspaces;
138
150
  const failureWorkspaceId = failure?.workspaceId ?? preselectedId ?? workspaces[0]?.id;
151
+ const failureWorkspace = failureWorkspaceId ? workspaces.find(({ id })=>id === failureWorkspaceId) : undefined;
139
152
  return /*#__PURE__*/ _jsxs(CallbackColumn, {
140
153
  children: [
141
154
  /*#__PURE__*/ _jsxs("header", {
@@ -170,15 +183,15 @@ export function SentryCallbackPage() {
170
183
  onClick: ()=>connect(failure.workspaceId),
171
184
  children: "Retry"
172
185
  }) : null,
173
- failure.failure.kind === 'terminal' && failure.failure.startOver ? /*#__PURE__*/ _jsx(Button, {
186
+ failure.failure.kind === 'terminal' && failure.failure.startOver && failureWorkspace ? /*#__PURE__*/ _jsx(Button, {
174
187
  asChild: true,
175
188
  size: "sm",
176
189
  variant: "secondary",
177
190
  className: "w-fit",
178
191
  children: /*#__PURE__*/ _jsx(Link, {
179
- to: "/workspaces/$wid/integrations/sentry",
192
+ to: "/w/$workspaceSlug/integrations/sentry",
180
193
  params: {
181
- wid: failure.workspaceId
194
+ workspaceSlug: failureWorkspace.slug
182
195
  },
183
196
  children: "Start over"
184
197
  })
@@ -211,14 +224,14 @@ export function SentryCallbackPage() {
211
224
  })
212
225
  }, workspace.id))
213
226
  }),
214
- failureWorkspaceId ? /*#__PURE__*/ _jsx(ButtonLink, {
227
+ failureWorkspace ? /*#__PURE__*/ _jsx(ButtonLink, {
215
228
  asChild: true,
216
229
  variant: "muted",
217
230
  className: "w-fit",
218
231
  children: /*#__PURE__*/ _jsx(Link, {
219
- to: "/workspaces/$wid/settings/integrations",
232
+ to: "/w/$workspaceSlug/settings/integrations",
220
233
  params: {
221
- wid: failureWorkspaceId
234
+ workspaceSlug: failureWorkspace.slug
222
235
  },
223
236
  children: "Back to settings"
224
237
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/sentry-callback-page.tsx"],"sourcesContent":["import {useAuthState, useRefreshAuth} from '@shipfox/client-auth';\nimport {useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {createSingleFlight, 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 {Link, useNavigate} from '@tanstack/react-router';\nimport {useEffect, useMemo, useRef, useState} from 'react';\nimport {useCompleteIntegrationCallback} from '#application/complete-integration-callback.js';\nimport type {IntegrationConnection} from '#core/models.js';\nimport {connectSentry} from '#hooks/api/integrations.js';\nimport {\n classifySentryConnectError,\n clearSentryInstallWorkspace,\n parseSentryCallbackParams,\n preselectSentryWorkspace,\n readSentryInstallWorkspace,\n type SentryConnectFailure,\n} from '#sentry-callback.js';\n\nconst connectRequests = createSingleFlight<string, IntegrationConnection>();\n\nexport function SentryCallbackPage() {\n const navigate = useNavigate();\n const refreshAuth = useRefreshAuth();\n const {workspaces, isLoading} = useAuthState();\n const completeIntegrationCallback = useCompleteIntegrationCallback();\n\n const params = useRouteSearch(parseSentryCallbackParams);\n const storedWorkspaceId = useMemo(\n () => readSentryInstallWorkspace(sessionStorageOrUndefined()),\n [],\n );\n const preselection = preselectSentryWorkspace(storedWorkspaceId, workspaces);\n const preselectedId = preselection.kind === 'pick' ? preselection.preselectedId : undefined;\n\n const [connectingId, setConnectingId] = useState<string | undefined>();\n const [failure, setFailure] = useState<\n {workspaceId: string; failure: SentryConnectFailure} | undefined\n >();\n const [retryLocked, setRetryLocked] = useState(false);\n const disposedRef = useRef(false);\n\n useEffect(() => {\n disposedRef.current = false;\n return () => {\n disposedRef.current = true;\n };\n }, []);\n\n // Wait for auth before treating an empty workspace list as \"no workspace\": on\n // a cold return from Sentry, `workspaces` is [] until the session refresh\n // resolves, and bouncing to `/` then would discard the one-shot grant code.\n const noWorkspace = !isLoading && preselection.kind === 'none';\n useEffect(() => {\n if (!noWorkspace) return;\n toast.error('You need a workspace before installing Sentry.');\n navigate({to: '/', replace: true});\n }, [noWorkspace, navigate]);\n\n const retryAfterSeconds =\n failure?.failure.kind === 'retryable' ? failure.failure.retryAfterSeconds : undefined;\n useEffect(() => {\n // No backoff window (any non-rate-limited failure) re-enables Retry, so a\n // prior rate-limit lock can never strand the button in a disabled state.\n if (!retryAfterSeconds) {\n setRetryLocked(false);\n return;\n }\n setRetryLocked(true);\n const timer = setTimeout(() => setRetryLocked(false), retryAfterSeconds * 1000);\n return () => clearTimeout(timer);\n }, [retryAfterSeconds]);\n\n if (!params) {\n return (\n <CallbackColumn>\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">\n This Sentry link is missing required parameters. Start the install again from your\n workspace settings.\n </Text>\n </Callout>\n <ButtonLink asChild variant=\"muted\" className=\"w-fit\">\n <Link to=\"/\">Back to Shipfox</Link>\n </ButtonLink>\n </CallbackColumn>\n );\n }\n\n // Hold the loader until auth resolves; the workspace list and the\n // noWorkspace redirect both depend on a settled `workspaces`.\n if (isLoading) return <FullPageLoader />;\n\n if (noWorkspace) return null;\n\n const connect = (workspaceId: string) => {\n setConnectingId(workspaceId);\n setFailure(undefined);\n const key = `${params.installationId}|${workspaceId}|${params.code}`;\n const request = connectRequests.run(\n key,\n async () =>\n await completeIntegrationCallback({\n input: workspaceId,\n refreshAuth,\n complete: async (selectedWorkspaceId, token) =>\n await connectSentry({\n body: {\n workspace_id: selectedWorkspaceId,\n code: params.code,\n installation_id: params.installationId,\n },\n token,\n }),\n }),\n );\n\n request\n .then(async () => {\n clearSentryInstallWorkspace(sessionStorageOrUndefined());\n if (disposedRef.current) return;\n toast.success('Sentry installed.');\n await navigate({\n to: '/workspaces/$wid/settings/integrations',\n params: {wid: workspaceId},\n replace: true,\n });\n })\n .catch((error: unknown) => {\n if (disposedRef.current) return;\n const classified = classifySentryConnectError(error);\n if (classified.kind === 'terminal') {\n // Only a fresh install (new grant code) can recover; the stored\n // handoff has served its purpose either way.\n clearSentryInstallWorkspace(sessionStorageOrUndefined());\n }\n setConnectingId(undefined);\n setFailure({workspaceId, failure: classified});\n });\n };\n\n const orderedWorkspaces = preselectedId\n ? [\n ...workspaces.filter((workspace) => workspace.id === preselectedId),\n ...workspaces.filter((workspace) => workspace.id !== preselectedId),\n ]\n : workspaces;\n\n const failureWorkspaceId = failure?.workspaceId ?? preselectedId ?? workspaces[0]?.id;\n\n return (\n <CallbackColumn>\n <header className=\"flex flex-col gap-8\">\n <Header variant=\"h2\">Install Sentry</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {params.orgSlug\n ? `Install the Sentry org \"${params.orgSlug}\" in a workspace.`\n : 'Install this Sentry integration in a workspace.'}\n </Text>\n </header>\n\n {failure ? (\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex flex-col gap-8\">\n <Text size=\"sm\">{failure.failure.message}</Text>\n {failure.failure.kind === 'retryable' ? (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n className=\"w-fit\"\n disabled={retryLocked || connectingId !== undefined}\n onClick={() => connect(failure.workspaceId)}\n >\n Retry\n </Button>\n ) : null}\n {failure.failure.kind === 'terminal' && failure.failure.startOver ? (\n <Button asChild size=\"sm\" variant=\"secondary\" className=\"w-fit\">\n <Link to=\"/workspaces/$wid/integrations/sentry\" params={{wid: failure.workspaceId}}>\n Start over\n </Link>\n </Button>\n ) : null}\n </div>\n </Callout>\n ) : null}\n\n <section className=\"flex flex-col gap-8\" aria-label=\"Choose a workspace\">\n {orderedWorkspaces.map((workspace) => (\n <Card key={workspace.id} className=\"p-16\">\n <div className=\"flex items-center justify-between gap-12\">\n <Text size=\"md\" bold className=\"truncate\">\n {workspace.name}\n </Text>\n <Button\n variant=\"secondary\"\n disabled={connectingId !== undefined}\n isLoading={connectingId === workspace.id}\n onClick={() => connect(workspace.id)}\n >\n Install\n </Button>\n </div>\n </Card>\n ))}\n </section>\n\n {failureWorkspaceId ? (\n <ButtonLink asChild variant=\"muted\" className=\"w-fit\">\n <Link to=\"/workspaces/$wid/settings/integrations\" params={{wid: failureWorkspaceId}}>\n Back to settings\n </Link>\n </ButtonLink>\n ) : null}\n </CallbackColumn>\n );\n}\n\nfunction CallbackColumn({children}: {children: React.ReactNode}) {\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 {children}\n </div>\n </main>\n );\n}\n"],"names":["useAuthState","useRefreshAuth","useRouteSearch","createSingleFlight","sessionStorageOrUndefined","Button","ButtonLink","Callout","Card","FullPageLoader","toast","Header","Text","Link","useNavigate","useEffect","useMemo","useRef","useState","useCompleteIntegrationCallback","connectSentry","classifySentryConnectError","clearSentryInstallWorkspace","parseSentryCallbackParams","preselectSentryWorkspace","readSentryInstallWorkspace","connectRequests","SentryCallbackPage","navigate","refreshAuth","workspaces","isLoading","completeIntegrationCallback","params","storedWorkspaceId","preselection","preselectedId","kind","undefined","connectingId","setConnectingId","failure","setFailure","retryLocked","setRetryLocked","disposedRef","current","noWorkspace","error","to","replace","retryAfterSeconds","timer","setTimeout","clearTimeout","CallbackColumn","role","type","size","asChild","variant","className","connect","workspaceId","key","installationId","code","request","run","input","complete","selectedWorkspaceId","token","body","workspace_id","installation_id","then","success","wid","catch","classified","orderedWorkspaces","filter","workspace","id","failureWorkspaceId","header","orgSlug","div","message","disabled","onClick","startOver","section","aria-label","map","bold","name","children","main"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/sentry-callback-page.tsx"],"sourcesContent":["import {useAuthState, useRefreshAuth} from '@shipfox/client-auth';\nimport {useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {createSingleFlight, 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 {useEffect, useMemo, useRef, useState} from 'react';\nimport {useCompleteIntegrationCallback} from '#application/complete-integration-callback.js';\nimport type {IntegrationConnection} from '#core/models.js';\nimport {connectSentry} from '#hooks/api/integrations.js';\nimport {\n classifySentryConnectError,\n clearSentryInstallWorkspace,\n parseSentryCallbackParams,\n preselectSentryWorkspace,\n readSentryInstallWorkspace,\n type SentryConnectFailure,\n} from '#sentry-callback.js';\nimport {resolveWorkspaceSlug} from '#workspace-navigation.js';\n\nconst connectRequests = createSingleFlight<string, IntegrationConnection>();\n\nexport function SentryCallbackPage() {\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const refreshAuth = useRefreshAuth();\n const {workspaces, isLoading} = useAuthState();\n const completeIntegrationCallback = useCompleteIntegrationCallback();\n\n const params = useRouteSearch(parseSentryCallbackParams);\n const storedWorkspaceId = useMemo(\n () => readSentryInstallWorkspace(sessionStorageOrUndefined()),\n [],\n );\n const preselection = preselectSentryWorkspace(storedWorkspaceId, workspaces);\n const preselectedId = preselection.kind === 'pick' ? preselection.preselectedId : undefined;\n\n const [connectingId, setConnectingId] = useState<string | undefined>();\n const [failure, setFailure] = useState<\n {workspaceId: string; failure: SentryConnectFailure} | undefined\n >();\n const [retryLocked, setRetryLocked] = useState(false);\n const disposedRef = useRef(false);\n\n useEffect(() => {\n disposedRef.current = false;\n return () => {\n disposedRef.current = true;\n };\n }, []);\n\n // Wait for auth before treating an empty workspace list as \"no workspace\": on\n // a cold return from Sentry, `workspaces` is [] until the session refresh\n // resolves, and bouncing to `/` then would discard the one-shot grant code.\n const noWorkspace = !isLoading && preselection.kind === 'none';\n useEffect(() => {\n if (!noWorkspace) return;\n toast.error('You need a workspace before installing Sentry.');\n navigate({to: '/', replace: true});\n }, [noWorkspace, navigate]);\n\n const retryAfterSeconds =\n failure?.failure.kind === 'retryable' ? failure.failure.retryAfterSeconds : undefined;\n useEffect(() => {\n // No backoff window (any non-rate-limited failure) re-enables Retry, so a\n // prior rate-limit lock can never strand the button in a disabled state.\n if (!retryAfterSeconds) {\n setRetryLocked(false);\n return;\n }\n setRetryLocked(true);\n const timer = setTimeout(() => setRetryLocked(false), retryAfterSeconds * 1000);\n return () => clearTimeout(timer);\n }, [retryAfterSeconds]);\n\n if (!params) {\n return (\n <CallbackColumn>\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">\n This Sentry link is missing required parameters. Start the install again from your\n workspace settings.\n </Text>\n </Callout>\n <ButtonLink asChild variant=\"muted\" className=\"w-fit\">\n <Link to=\"/\">Back to Shipfox</Link>\n </ButtonLink>\n </CallbackColumn>\n );\n }\n\n // Hold the loader until auth resolves; the workspace list and the\n // noWorkspace redirect both depend on a settled `workspaces`.\n if (isLoading) return <FullPageLoader />;\n\n if (noWorkspace) return null;\n\n const connect = (workspaceId: string) => {\n setConnectingId(workspaceId);\n setFailure(undefined);\n const key = `${params.installationId}|${workspaceId}|${params.code}`;\n const request = connectRequests.run(\n key,\n async () =>\n await completeIntegrationCallback({\n input: workspaceId,\n refreshAuth,\n complete: async (selectedWorkspaceId, token) =>\n await connectSentry({\n body: {\n workspace_id: selectedWorkspaceId,\n code: params.code,\n installation_id: params.installationId,\n },\n token,\n }),\n }),\n );\n\n request\n .then(async () => {\n clearSentryInstallWorkspace(sessionStorageOrUndefined());\n if (disposedRef.current) return;\n toast.success('Sentry installed.');\n const workspaceSlug = await resolveWorkspaceSlug({\n workspaceId,\n fallbackWorkspaces: workspaces,\n queryClient,\n });\n if (disposedRef.current) return;\n await navigate(\n workspaceSlug\n ? {\n to: '/w/$workspaceSlug/settings/integrations',\n params: {workspaceSlug},\n replace: true,\n }\n : {to: '/', replace: true},\n );\n })\n .catch((error: unknown) => {\n if (disposedRef.current) return;\n const classified = classifySentryConnectError(error);\n if (classified.kind === 'terminal') {\n // Only a fresh install (new grant code) can recover; the stored\n // handoff has served its purpose either way.\n clearSentryInstallWorkspace(sessionStorageOrUndefined());\n }\n setConnectingId(undefined);\n setFailure({workspaceId, failure: classified});\n });\n };\n\n const orderedWorkspaces = preselectedId\n ? [\n ...workspaces.filter((workspace) => workspace.id === preselectedId),\n ...workspaces.filter((workspace) => workspace.id !== preselectedId),\n ]\n : workspaces;\n\n const failureWorkspaceId = failure?.workspaceId ?? preselectedId ?? workspaces[0]?.id;\n const failureWorkspace = failureWorkspaceId\n ? workspaces.find(({id}) => id === failureWorkspaceId)\n : undefined;\n\n return (\n <CallbackColumn>\n <header className=\"flex flex-col gap-8\">\n <Header variant=\"h2\">Install Sentry</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {params.orgSlug\n ? `Install the Sentry org \"${params.orgSlug}\" in a workspace.`\n : 'Install this Sentry integration in a workspace.'}\n </Text>\n </header>\n\n {failure ? (\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex flex-col gap-8\">\n <Text size=\"sm\">{failure.failure.message}</Text>\n {failure.failure.kind === 'retryable' ? (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n className=\"w-fit\"\n disabled={retryLocked || connectingId !== undefined}\n onClick={() => connect(failure.workspaceId)}\n >\n Retry\n </Button>\n ) : null}\n {failure.failure.kind === 'terminal' &&\n failure.failure.startOver &&\n failureWorkspace ? (\n <Button asChild size=\"sm\" variant=\"secondary\" className=\"w-fit\">\n <Link\n to=\"/w/$workspaceSlug/integrations/sentry\"\n params={{workspaceSlug: failureWorkspace.slug}}\n >\n Start over\n </Link>\n </Button>\n ) : null}\n </div>\n </Callout>\n ) : null}\n\n <section className=\"flex flex-col gap-8\" aria-label=\"Choose a workspace\">\n {orderedWorkspaces.map((workspace) => (\n <Card key={workspace.id} className=\"p-16\">\n <div className=\"flex items-center justify-between gap-12\">\n <Text size=\"md\" bold className=\"truncate\">\n {workspace.name}\n </Text>\n <Button\n variant=\"secondary\"\n disabled={connectingId !== undefined}\n isLoading={connectingId === workspace.id}\n onClick={() => connect(workspace.id)}\n >\n Install\n </Button>\n </div>\n </Card>\n ))}\n </section>\n\n {failureWorkspace ? (\n <ButtonLink asChild variant=\"muted\" className=\"w-fit\">\n <Link\n to=\"/w/$workspaceSlug/settings/integrations\"\n params={{workspaceSlug: failureWorkspace.slug}}\n >\n Back to settings\n </Link>\n </ButtonLink>\n ) : null}\n </CallbackColumn>\n );\n}\n\nfunction CallbackColumn({children}: {children: React.ReactNode}) {\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 {children}\n </div>\n </main>\n );\n}\n"],"names":["useAuthState","useRefreshAuth","useRouteSearch","createSingleFlight","sessionStorageOrUndefined","Button","ButtonLink","Callout","Card","FullPageLoader","toast","Header","Text","useQueryClient","Link","useNavigate","useEffect","useMemo","useRef","useState","useCompleteIntegrationCallback","connectSentry","classifySentryConnectError","clearSentryInstallWorkspace","parseSentryCallbackParams","preselectSentryWorkspace","readSentryInstallWorkspace","resolveWorkspaceSlug","connectRequests","SentryCallbackPage","navigate","queryClient","refreshAuth","workspaces","isLoading","completeIntegrationCallback","params","storedWorkspaceId","preselection","preselectedId","kind","undefined","connectingId","setConnectingId","failure","setFailure","retryLocked","setRetryLocked","disposedRef","current","noWorkspace","error","to","replace","retryAfterSeconds","timer","setTimeout","clearTimeout","CallbackColumn","role","type","size","asChild","variant","className","connect","workspaceId","key","installationId","code","request","run","input","complete","selectedWorkspaceId","token","body","workspace_id","installation_id","then","success","workspaceSlug","fallbackWorkspaces","catch","classified","orderedWorkspaces","filter","workspace","id","failureWorkspaceId","failureWorkspace","find","header","orgSlug","div","message","disabled","onClick","startOver","slug","section","aria-label","map","bold","name","children","main"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"slack-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/slack-callback-page.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,iBAAiB,gCAyFhC"}
1
+ {"version":3,"file":"slack-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/slack-callback-page.tsx"],"names":[],"mappings":"AA8BA,wBAAgB,iBAAiB,gCA6HhC"}
@@ -1,15 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useRefreshAuth } from '@shipfox/client-auth';
2
+ import { useAuthState, useRefreshAuth } from '@shipfox/client-auth';
3
3
  import { useRouteSearch } from '@shipfox/client-shell/runtime';
4
4
  import { createSingleFlight, sessionStorageOrUndefined } from '@shipfox/client-ui';
5
5
  import { FullPageLoader } from '@shipfox/react-ui/loader';
6
6
  import { toast } from '@shipfox/react-ui/toast';
7
+ import { useQueryClient } from '@tanstack/react-query';
7
8
  import { useNavigate } from '@tanstack/react-router';
8
9
  import { useEffect, useMemo, useState } from 'react';
9
10
  import { useCompleteIntegrationCallback } from '#application/complete-integration-callback.js';
10
11
  import { CallbackStatusShell } from '#components/callback-status-shell.js';
11
12
  import { useCompleteSlackCallbackMutation } from '#hooks/api/integrations.js';
12
13
  import { classifySlackCallbackError, clearSlackInstallWorkspace, parseSlackCallbackQuery, readSlackInstallWorkspace, serializeSlackCallbackQuery } from '#slack-callback.js';
14
+ import { rememberCallbackKey, resolveWorkspaceSlug } from '#workspace-navigation.js';
13
15
  // Retain only recent completions: this bounds long-lived callback pages while
14
16
  // still covering StrictMode and immediate Back/Forward remounts.
15
17
  const callbackRequests = createSingleFlight({
@@ -19,15 +21,17 @@ const completedCallbacks = new Set();
19
21
  const toastedCallbacks = new Set();
20
22
  export function SlackCallbackPage() {
21
23
  const navigate = useNavigate();
24
+ const queryClient = useQueryClient();
22
25
  const refreshAuth = useRefreshAuth();
23
26
  const completeIntegrationCallback = useCompleteIntegrationCallback();
24
27
  const { mutateAsync: completeSlackCallback } = useCompleteSlackCallbackMutation();
28
+ const { workspaces, isLoading } = useAuthState();
25
29
  const params = useRouteSearch(parseSlackCallbackQuery);
26
30
  const workspaceId = useMemo(()=>readSlackInstallWorkspace(sessionStorageOrUndefined()), []);
27
31
  const [failure, setFailure] = useState();
28
- const [completedWorkspaceId, setCompletedWorkspaceId] = useState();
32
+ const [completedWorkspace, setCompletedWorkspace] = useState();
29
33
  useEffect(()=>{
30
- if (!params) return;
34
+ if (!params || isLoading) return;
31
35
  let disposed = false;
32
36
  const key = serializeSlackCallbackQuery(params);
33
37
  const request = callbackRequests.run(key, async ()=>await completeIntegrationCallback({
@@ -41,10 +45,17 @@ export function SlackCallbackPage() {
41
45
  request.then(async (connection)=>{
42
46
  if (disposed) return;
43
47
  if (completedCallbacks.has(key)) {
44
- setCompletedWorkspaceId(connection.workspaceId);
48
+ const workspaceSlug = await resolveWorkspaceSlug({
49
+ workspaceId: connection.workspaceId,
50
+ fallbackWorkspaces: workspaces,
51
+ queryClient
52
+ });
53
+ if (!disposed) setCompletedWorkspace(workspaceSlug ? {
54
+ slug: workspaceSlug
55
+ } : {});
45
56
  return;
46
57
  }
47
- completedCallbacks.add(key);
58
+ rememberCallbackKey(completedCallbacks, key);
48
59
  try {
49
60
  clearSlackInstallWorkspace(sessionStorageOrUndefined());
50
61
  } catch {
@@ -52,19 +63,36 @@ export function SlackCallbackPage() {
52
63
  }
53
64
  if (disposed) return;
54
65
  if (!toastedCallbacks.has(key)) {
55
- toastedCallbacks.add(key);
66
+ rememberCallbackKey(toastedCallbacks, key);
56
67
  toast.success('Slack installed.');
57
68
  }
69
+ let workspaceSlug;
58
70
  try {
71
+ if (disposed) return;
72
+ workspaceSlug = await resolveWorkspaceSlug({
73
+ workspaceId: connection.workspaceId,
74
+ fallbackWorkspaces: workspaces,
75
+ queryClient
76
+ });
77
+ if (disposed) return;
78
+ if (!workspaceSlug) {
79
+ setCompletedWorkspace({});
80
+ return;
81
+ }
82
+ setCompletedWorkspace({
83
+ slug: workspaceSlug
84
+ });
59
85
  await navigate({
60
- to: '/workspaces/$wid/settings/integrations',
86
+ to: '/w/$workspaceSlug/settings/integrations',
61
87
  params: {
62
- wid: connection.workspaceId
88
+ workspaceSlug
63
89
  },
64
90
  replace: true
65
91
  });
66
92
  } catch {
67
- if (!disposed) setCompletedWorkspaceId(connection.workspaceId);
93
+ if (!disposed) setCompletedWorkspace({
94
+ slug: workspaceSlug
95
+ });
68
96
  }
69
97
  }, (error)=>{
70
98
  if (!disposed) setFailure(classifySlackCallbackError(error));
@@ -75,28 +103,32 @@ export function SlackCallbackPage() {
75
103
  }, [
76
104
  completeIntegrationCallback,
77
105
  completeSlackCallback,
106
+ isLoading,
78
107
  navigate,
79
108
  params,
80
- refreshAuth
109
+ queryClient,
110
+ refreshAuth,
111
+ workspaces
81
112
  ]);
82
113
  if (!params) return /*#__PURE__*/ _jsx(CallbackStatusShell, {
83
114
  title: "Invalid Slack callback",
84
115
  message: "This Slack link is missing required parameters. Start the install again from workspace settings.",
85
116
  startOver: true,
86
- workspaceId: workspaceId,
87
- installPath: "/workspaces/$wid/integrations/slack"
117
+ workspaceSlug: workspaces.find(({ id })=>id === workspaceId)?.slug,
118
+ installPath: "/w/$workspaceSlug/integrations/slack"
88
119
  });
89
- if (completedWorkspaceId) return /*#__PURE__*/ _jsx(CallbackStatusShell, {
120
+ if (completedWorkspace) return /*#__PURE__*/ _jsx(CallbackStatusShell, {
90
121
  title: "Slack connected",
91
- message: "Slack is connected. Continue in integrations settings.",
92
- workspaceId: completedWorkspaceId,
93
- installPath: "/workspaces/$wid/integrations/slack"
122
+ status: "success",
123
+ message: completedWorkspace.slug ? 'Slack is connected. Continue in integrations settings.' : 'Slack is connected. Return to Shipfox to continue.',
124
+ workspaceSlug: completedWorkspace.slug,
125
+ installPath: "/w/$workspaceSlug/integrations/slack"
94
126
  });
95
127
  if (failure) return /*#__PURE__*/ _jsx(CallbackStatusShell, {
96
128
  ...failure,
97
- workspaceId: workspaceId,
129
+ workspaceSlug: workspaces.find(({ id })=>id === workspaceId)?.slug,
98
130
  switchAccount: failure.signIn,
99
- installPath: "/workspaces/$wid/integrations/slack"
131
+ installPath: "/w/$workspaceSlug/integrations/slack"
100
132
  });
101
133
  return /*#__PURE__*/ _jsx(FullPageLoader, {
102
134
  "aria-label": "Connecting Slack"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/slack-callback-page.tsx"],"sourcesContent":["import {useRefreshAuth} from '@shipfox/client-auth';\nimport {useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {createSingleFlight, sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useEffect, useMemo, useState} from 'react';\nimport {useCompleteIntegrationCallback} from '#application/complete-integration-callback.js';\nimport {CallbackStatusShell} from '#components/callback-status-shell.js';\nimport type {IntegrationConnection} from '#core/models.js';\nimport {useCompleteSlackCallbackMutation} from '#hooks/api/integrations.js';\nimport {\n classifySlackCallbackError,\n clearSlackInstallWorkspace,\n parseSlackCallbackQuery,\n readSlackInstallWorkspace,\n type SlackCallbackFailure,\n serializeSlackCallbackQuery,\n} from '#slack-callback.js';\n\n// Retain only recent completions: this bounds long-lived callback pages while\n// still covering StrictMode and immediate Back/Forward remounts.\nconst callbackRequests = createSingleFlight<string, IntegrationConnection>({\n maxTerminalResults: 32,\n});\nconst completedCallbacks = new Set<string>();\nconst toastedCallbacks = new Set<string>();\n\nexport function SlackCallbackPage() {\n const navigate = useNavigate();\n const refreshAuth = useRefreshAuth();\n const completeIntegrationCallback = useCompleteIntegrationCallback();\n const {mutateAsync: completeSlackCallback} = useCompleteSlackCallbackMutation();\n const params = useRouteSearch(parseSlackCallbackQuery);\n const workspaceId = useMemo(() => readSlackInstallWorkspace(sessionStorageOrUndefined()), []);\n const [failure, setFailure] = useState<SlackCallbackFailure>();\n const [completedWorkspaceId, setCompletedWorkspaceId] = useState<string>();\n\n useEffect(() => {\n if (!params) return;\n let disposed = false;\n const key = serializeSlackCallbackQuery(params);\n const request = callbackRequests.run(\n key,\n async () =>\n await completeIntegrationCallback({\n input: params,\n refreshAuth,\n complete: async (query, token) => await completeSlackCallback({query, token}),\n }),\n );\n request.then(\n async (connection) => {\n if (disposed) return;\n if (completedCallbacks.has(key)) {\n setCompletedWorkspaceId(connection.workspaceId);\n return;\n }\n completedCallbacks.add(key);\n try {\n clearSlackInstallWorkspace(sessionStorageOrUndefined());\n } catch {\n // The successful API response remains the source of truth.\n }\n if (disposed) return;\n if (!toastedCallbacks.has(key)) {\n toastedCallbacks.add(key);\n toast.success('Slack installed.');\n }\n try {\n await navigate({\n to: '/workspaces/$wid/settings/integrations',\n params: {wid: connection.workspaceId},\n replace: true,\n });\n } catch {\n if (!disposed) setCompletedWorkspaceId(connection.workspaceId);\n }\n },\n (error: unknown) => {\n if (!disposed) setFailure(classifySlackCallbackError(error));\n },\n );\n return () => {\n disposed = true;\n };\n }, [completeIntegrationCallback, completeSlackCallback, navigate, params, refreshAuth]);\n\n if (!params)\n return (\n <CallbackStatusShell\n title=\"Invalid Slack callback\"\n message=\"This Slack link is missing required parameters. Start the install again from workspace settings.\"\n startOver\n workspaceId={workspaceId}\n installPath=\"/workspaces/$wid/integrations/slack\"\n />\n );\n if (completedWorkspaceId)\n return (\n <CallbackStatusShell\n title=\"Slack connected\"\n message=\"Slack is connected. Continue in integrations settings.\"\n workspaceId={completedWorkspaceId}\n installPath=\"/workspaces/$wid/integrations/slack\"\n />\n );\n if (failure)\n return (\n <CallbackStatusShell\n {...failure}\n workspaceId={workspaceId}\n switchAccount={failure.signIn}\n installPath=\"/workspaces/$wid/integrations/slack\"\n />\n );\n return <FullPageLoader aria-label=\"Connecting Slack\" />;\n}\n"],"names":["useRefreshAuth","useRouteSearch","createSingleFlight","sessionStorageOrUndefined","FullPageLoader","toast","useNavigate","useEffect","useMemo","useState","useCompleteIntegrationCallback","CallbackStatusShell","useCompleteSlackCallbackMutation","classifySlackCallbackError","clearSlackInstallWorkspace","parseSlackCallbackQuery","readSlackInstallWorkspace","serializeSlackCallbackQuery","callbackRequests","maxTerminalResults","completedCallbacks","Set","toastedCallbacks","SlackCallbackPage","navigate","refreshAuth","completeIntegrationCallback","mutateAsync","completeSlackCallback","params","workspaceId","failure","setFailure","completedWorkspaceId","setCompletedWorkspaceId","disposed","key","request","run","input","complete","query","token","then","connection","has","add","success","to","wid","replace","error","title","message","startOver","installPath","switchAccount","signIn","aria-label"],"mappings":";AAAA,SAAQA,cAAc,QAAO,uBAAuB;AACpD,SAAQC,cAAc,QAAO,gCAAgC;AAC7D,SAAQC,kBAAkB,EAAEC,yBAAyB,QAAO,qBAAqB;AACjF,SAAQC,cAAc,QAAO,2BAA2B;AACxD,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,WAAW,QAAO,yBAAyB;AACnD,SAAQC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAO,QAAQ;AACnD,SAAQC,8BAA8B,QAAO,gDAAgD;AAC7F,SAAQC,mBAAmB,QAAO,uCAAuC;AAEzE,SAAQC,gCAAgC,QAAO,6BAA6B;AAC5E,SACEC,0BAA0B,EAC1BC,0BAA0B,EAC1BC,uBAAuB,EACvBC,yBAAyB,EAEzBC,2BAA2B,QACtB,qBAAqB;AAE5B,8EAA8E;AAC9E,iEAAiE;AACjE,MAAMC,mBAAmBhB,mBAAkD;IACzEiB,oBAAoB;AACtB;AACA,MAAMC,qBAAqB,IAAIC;AAC/B,MAAMC,mBAAmB,IAAID;AAE7B,OAAO,SAASE;IACd,MAAMC,WAAWlB;IACjB,MAAMmB,cAAczB;IACpB,MAAM0B,8BAA8BhB;IACpC,MAAM,EAACiB,aAAaC,qBAAqB,EAAC,GAAGhB;IAC7C,MAAMiB,SAAS5B,eAAec;IAC9B,MAAMe,cAActB,QAAQ,IAAMQ,0BAA0Bb,8BAA8B,EAAE;IAC5F,MAAM,CAAC4B,SAASC,WAAW,GAAGvB;IAC9B,MAAM,CAACwB,sBAAsBC,wBAAwB,GAAGzB;IAExDF,UAAU;QACR,IAAI,CAACsB,QAAQ;QACb,IAAIM,WAAW;QACf,MAAMC,MAAMnB,4BAA4BY;QACxC,MAAMQ,UAAUnB,iBAAiBoB,GAAG,CAClCF,KACA,UACE,MAAMV,4BAA4B;gBAChCa,OAAOV;gBACPJ;gBACAe,UAAU,OAAOC,OAAOC,QAAU,MAAMd,sBAAsB;wBAACa;wBAAOC;oBAAK;YAC7E;QAEJL,QAAQM,IAAI,CACV,OAAOC;YACL,IAAIT,UAAU;YACd,IAAIf,mBAAmByB,GAAG,CAACT,MAAM;gBAC/BF,wBAAwBU,WAAWd,WAAW;gBAC9C;YACF;YACAV,mBAAmB0B,GAAG,CAACV;YACvB,IAAI;gBACFtB,2BAA2BX;YAC7B,EAAE,OAAM;YACN,2DAA2D;YAC7D;YACA,IAAIgC,UAAU;YACd,IAAI,CAACb,iBAAiBuB,GAAG,CAACT,MAAM;gBAC9Bd,iBAAiBwB,GAAG,CAACV;gBACrB/B,MAAM0C,OAAO,CAAC;YAChB;YACA,IAAI;gBACF,MAAMvB,SAAS;oBACbwB,IAAI;oBACJnB,QAAQ;wBAACoB,KAAKL,WAAWd,WAAW;oBAAA;oBACpCoB,SAAS;gBACX;YACF,EAAE,OAAM;gBACN,IAAI,CAACf,UAAUD,wBAAwBU,WAAWd,WAAW;YAC/D;QACF,GACA,CAACqB;YACC,IAAI,CAAChB,UAAUH,WAAWnB,2BAA2BsC;QACvD;QAEF,OAAO;YACLhB,WAAW;QACb;IACF,GAAG;QAACT;QAA6BE;QAAuBJ;QAAUK;QAAQJ;KAAY;IAEtF,IAAI,CAACI,QACH,qBACE,KAAClB;QACCyC,OAAM;QACNC,SAAQ;QACRC,SAAS;QACTxB,aAAaA;QACbyB,aAAY;;IAGlB,IAAItB,sBACF,qBACE,KAACtB;QACCyC,OAAM;QACNC,SAAQ;QACRvB,aAAaG;QACbsB,aAAY;;IAGlB,IAAIxB,SACF,qBACE,KAACpB;QACE,GAAGoB,OAAO;QACXD,aAAaA;QACb0B,eAAezB,QAAQ0B,MAAM;QAC7BF,aAAY;;IAGlB,qBAAO,KAACnD;QAAesD,cAAW;;AACpC"}
1
+ {"version":3,"sources":["../../src/pages/slack-callback-page.tsx"],"sourcesContent":["import {useAuthState, useRefreshAuth} from '@shipfox/client-auth';\nimport {useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {createSingleFlight, sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useEffect, useMemo, useState} from 'react';\nimport {useCompleteIntegrationCallback} from '#application/complete-integration-callback.js';\nimport {CallbackStatusShell} from '#components/callback-status-shell.js';\nimport type {IntegrationConnection} from '#core/models.js';\nimport {useCompleteSlackCallbackMutation} from '#hooks/api/integrations.js';\nimport {\n classifySlackCallbackError,\n clearSlackInstallWorkspace,\n parseSlackCallbackQuery,\n readSlackInstallWorkspace,\n type SlackCallbackFailure,\n serializeSlackCallbackQuery,\n} from '#slack-callback.js';\nimport {rememberCallbackKey, resolveWorkspaceSlug} from '#workspace-navigation.js';\n\n// Retain only recent completions: this bounds long-lived callback pages while\n// still covering StrictMode and immediate Back/Forward remounts.\nconst callbackRequests = createSingleFlight<string, IntegrationConnection>({\n maxTerminalResults: 32,\n});\nconst completedCallbacks = new Set<string>();\nconst toastedCallbacks = new Set<string>();\n\nexport function SlackCallbackPage() {\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const refreshAuth = useRefreshAuth();\n const completeIntegrationCallback = useCompleteIntegrationCallback();\n const {mutateAsync: completeSlackCallback} = useCompleteSlackCallbackMutation();\n const {workspaces, isLoading} = useAuthState();\n const params = useRouteSearch(parseSlackCallbackQuery);\n const workspaceId = useMemo(() => readSlackInstallWorkspace(sessionStorageOrUndefined()), []);\n const [failure, setFailure] = useState<SlackCallbackFailure>();\n const [completedWorkspace, setCompletedWorkspace] = useState<{\n slug?: string | undefined;\n }>();\n\n useEffect(() => {\n if (!params || isLoading) return;\n let disposed = false;\n const key = serializeSlackCallbackQuery(params);\n const request = callbackRequests.run(\n key,\n async () =>\n await completeIntegrationCallback({\n input: params,\n refreshAuth,\n complete: async (query, token) => await completeSlackCallback({query, token}),\n }),\n );\n request.then(\n async (connection) => {\n if (disposed) return;\n if (completedCallbacks.has(key)) {\n const workspaceSlug = await resolveWorkspaceSlug({\n workspaceId: connection.workspaceId,\n fallbackWorkspaces: workspaces,\n queryClient,\n });\n if (!disposed) setCompletedWorkspace(workspaceSlug ? {slug: workspaceSlug} : {});\n return;\n }\n rememberCallbackKey(completedCallbacks, key);\n try {\n clearSlackInstallWorkspace(sessionStorageOrUndefined());\n } catch {\n // The successful API response remains the source of truth.\n }\n if (disposed) return;\n if (!toastedCallbacks.has(key)) {\n rememberCallbackKey(toastedCallbacks, key);\n toast.success('Slack installed.');\n }\n let workspaceSlug: string | undefined;\n try {\n if (disposed) return;\n workspaceSlug = await resolveWorkspaceSlug({\n workspaceId: connection.workspaceId,\n fallbackWorkspaces: workspaces,\n queryClient,\n });\n if (disposed) 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 (!disposed) setCompletedWorkspace({slug: workspaceSlug});\n }\n },\n (error: unknown) => {\n if (!disposed) setFailure(classifySlackCallbackError(error));\n },\n );\n return () => {\n disposed = true;\n };\n }, [\n completeIntegrationCallback,\n completeSlackCallback,\n isLoading,\n navigate,\n params,\n queryClient,\n refreshAuth,\n workspaces,\n ]);\n\n if (!params)\n return (\n <CallbackStatusShell\n title=\"Invalid Slack callback\"\n message=\"This Slack link is missing required parameters. Start the install again from workspace settings.\"\n startOver\n workspaceSlug={workspaces.find(({id}) => id === workspaceId)?.slug}\n installPath=\"/w/$workspaceSlug/integrations/slack\"\n />\n );\n if (completedWorkspace)\n return (\n <CallbackStatusShell\n title=\"Slack connected\"\n status=\"success\"\n message={\n completedWorkspace.slug\n ? 'Slack is connected. Continue in integrations settings.'\n : 'Slack is connected. Return to Shipfox to continue.'\n }\n workspaceSlug={completedWorkspace.slug}\n installPath=\"/w/$workspaceSlug/integrations/slack\"\n />\n );\n if (failure)\n return (\n <CallbackStatusShell\n {...failure}\n workspaceSlug={workspaces.find(({id}) => id === workspaceId)?.slug}\n switchAccount={failure.signIn}\n installPath=\"/w/$workspaceSlug/integrations/slack\"\n />\n );\n return <FullPageLoader aria-label=\"Connecting Slack\" />;\n}\n"],"names":["useAuthState","useRefreshAuth","useRouteSearch","createSingleFlight","sessionStorageOrUndefined","FullPageLoader","toast","useQueryClient","useNavigate","useEffect","useMemo","useState","useCompleteIntegrationCallback","CallbackStatusShell","useCompleteSlackCallbackMutation","classifySlackCallbackError","clearSlackInstallWorkspace","parseSlackCallbackQuery","readSlackInstallWorkspace","serializeSlackCallbackQuery","rememberCallbackKey","resolveWorkspaceSlug","callbackRequests","maxTerminalResults","completedCallbacks","Set","toastedCallbacks","SlackCallbackPage","navigate","queryClient","refreshAuth","completeIntegrationCallback","mutateAsync","completeSlackCallback","workspaces","isLoading","params","workspaceId","failure","setFailure","completedWorkspace","setCompletedWorkspace","disposed","key","request","run","input","complete","query","token","then","connection","has","workspaceSlug","fallbackWorkspaces","slug","success","to","replace","error","title","message","startOver","find","id","installPath","status","switchAccount","signIn","aria-label"],"mappings":"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"}
@@ -26,12 +26,12 @@ export function SourceControlOnboardingPage() {
26
26
  ]
27
27
  }),
28
28
  /*#__PURE__*/ _jsx(ProviderGrid, {
29
+ workspaceSlug: workspace.slug,
29
30
  providers: providersQuery.data ?? [],
30
31
  isPending: providersQuery.isPending,
31
32
  isFetching: providersQuery.isFetching,
32
33
  error: providersQuery.isError ? providersQuery.error : undefined,
33
34
  onRetry: ()=>void providersQuery.refetch(),
34
- workspaceId: workspace.id,
35
35
  emptyMessage: "Enable at least one source-control provider in the application settings."
36
36
  })
37
37
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/source-control-onboarding-page.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {ProviderGrid} from '#components/provider-grid.js';\nimport {useIntegrationProvidersQuery} from '#hooks/api/integrations.js';\n\nexport function SourceControlOnboardingPage() {\n const workspace = useActiveWorkspace();\n const providersQuery = useIntegrationProvidersQuery({capability: 'source_control'});\n\n return (\n <div className=\"mx-auto flex w-full max-w-[640px] flex-col gap-20\">\n <header className=\"flex flex-col gap-8\">\n <Header variant=\"h1\">Install source control</Header>\n <Text size=\"md\" className=\"text-foreground-neutral-muted\">\n Shipfox needs a source control integration to import your repositories.\n </Text>\n </header>\n\n <ProviderGrid\n providers={providersQuery.data ?? []}\n isPending={providersQuery.isPending}\n isFetching={providersQuery.isFetching}\n error={providersQuery.isError ? providersQuery.error : undefined}\n onRetry={() => void providersQuery.refetch()}\n workspaceId={workspace.id}\n emptyMessage=\"Enable at least one source-control provider in the application settings.\"\n />\n </div>\n );\n}\n"],"names":["useActiveWorkspace","Header","Text","ProviderGrid","useIntegrationProvidersQuery","SourceControlOnboardingPage","workspace","providersQuery","capability","div","className","header","variant","size","providers","data","isPending","isFetching","error","isError","undefined","onRetry","refetch","workspaceId","id","emptyMessage"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAC1D,SAAQC,YAAY,QAAO,+BAA+B;AAC1D,SAAQC,4BAA4B,QAAO,6BAA6B;AAExE,OAAO,SAASC;IACd,MAAMC,YAAYN;IAClB,MAAMO,iBAAiBH,6BAA6B;QAACI,YAAY;IAAgB;IAEjF,qBACE,MAACC;QAAIC,WAAU;;0BACb,MAACC;gBAAOD,WAAU;;kCAChB,KAACT;wBAAOW,SAAQ;kCAAK;;kCACrB,KAACV;wBAAKW,MAAK;wBAAKH,WAAU;kCAAgC;;;;0BAK5D,KAACP;gBACCW,WAAWP,eAAeQ,IAAI,IAAI,EAAE;gBACpCC,WAAWT,eAAeS,SAAS;gBACnCC,YAAYV,eAAeU,UAAU;gBACrCC,OAAOX,eAAeY,OAAO,GAAGZ,eAAeW,KAAK,GAAGE;gBACvDC,SAAS,IAAM,KAAKd,eAAee,OAAO;gBAC1CC,aAAajB,UAAUkB,EAAE;gBACzBC,cAAa;;;;AAIrB"}
1
+ {"version":3,"sources":["../../src/pages/source-control-onboarding-page.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {ProviderGrid} from '#components/provider-grid.js';\nimport {useIntegrationProvidersQuery} from '#hooks/api/integrations.js';\n\nexport function SourceControlOnboardingPage() {\n const workspace = useActiveWorkspace();\n const providersQuery = useIntegrationProvidersQuery({capability: 'source_control'});\n\n return (\n <div className=\"mx-auto flex w-full max-w-[640px] flex-col gap-20\">\n <header className=\"flex flex-col gap-8\">\n <Header variant=\"h1\">Install source control</Header>\n <Text size=\"md\" className=\"text-foreground-neutral-muted\">\n Shipfox needs a source control integration to import your repositories.\n </Text>\n </header>\n\n <ProviderGrid\n workspaceSlug={workspace.slug}\n providers={providersQuery.data ?? []}\n isPending={providersQuery.isPending}\n isFetching={providersQuery.isFetching}\n error={providersQuery.isError ? providersQuery.error : undefined}\n onRetry={() => void providersQuery.refetch()}\n emptyMessage=\"Enable at least one source-control provider in the application settings.\"\n />\n </div>\n );\n}\n"],"names":["useActiveWorkspace","Header","Text","ProviderGrid","useIntegrationProvidersQuery","SourceControlOnboardingPage","workspace","providersQuery","capability","div","className","header","variant","size","workspaceSlug","slug","providers","data","isPending","isFetching","error","isError","undefined","onRetry","refetch","emptyMessage"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAC1D,SAAQC,YAAY,QAAO,+BAA+B;AAC1D,SAAQC,4BAA4B,QAAO,6BAA6B;AAExE,OAAO,SAASC;IACd,MAAMC,YAAYN;IAClB,MAAMO,iBAAiBH,6BAA6B;QAACI,YAAY;IAAgB;IAEjF,qBACE,MAACC;QAAIC,WAAU;;0BACb,MAACC;gBAAOD,WAAU;;kCAChB,KAACT;wBAAOW,SAAQ;kCAAK;;kCACrB,KAACV;wBAAKW,MAAK;wBAAKH,WAAU;kCAAgC;;;;0BAK5D,KAACP;gBACCW,eAAeR,UAAUS,IAAI;gBAC7BC,WAAWT,eAAeU,IAAI,IAAI,EAAE;gBACpCC,WAAWX,eAAeW,SAAS;gBACnCC,YAAYZ,eAAeY,UAAU;gBACrCC,OAAOb,eAAec,OAAO,GAAGd,eAAea,KAAK,GAAGE;gBACvDC,SAAS,IAAM,KAAKhB,eAAeiB,OAAO;gBAC1CC,cAAa;;;;AAIrB"}
@@ -1,5 +1,5 @@
1
1
  import type { IconName } from '@shipfox/react-ui/icon';
2
- export type ProviderSetupPath = '/workspaces/$wid/integrations/github' | '/workspaces/$wid/integrations/gitea' | '/workspaces/$wid/integrations/sentry' | '/workspaces/$wid/integrations/linear' | '/workspaces/$wid/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,sCAAsC,GACtC,qCAAqC,GACrC,sCAAsC,GACtC,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,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"}