@shipfox/client-integrations 5.0.0 → 6.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +84 -0
  3. package/dist/application/complete-integration-callback.d.ts +13 -0
  4. package/dist/application/complete-integration-callback.d.ts.map +1 -0
  5. package/dist/application/complete-integration-callback.js +26 -0
  6. package/dist/application/complete-integration-callback.js.map +1 -0
  7. package/dist/components/connection-picker.d.ts +2 -2
  8. package/dist/components/connection-picker.d.ts.map +1 -1
  9. package/dist/components/connection-picker.js +4 -4
  10. package/dist/components/connection-picker.js.map +1 -1
  11. package/dist/components/installed-integrations-section.d.ts +8 -6
  12. package/dist/components/installed-integrations-section.d.ts.map +1 -1
  13. package/dist/components/installed-integrations-section.js +22 -15
  14. package/dist/components/installed-integrations-section.js.map +1 -1
  15. package/dist/components/integration-gallery-for-workspace.d.ts +2 -2
  16. package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -1
  17. package/dist/components/integration-gallery-for-workspace.js +14 -7
  18. package/dist/components/integration-gallery-for-workspace.js.map +1 -1
  19. package/dist/components/integration-gallery.d.ts +2 -2
  20. package/dist/components/integration-gallery.d.ts.map +1 -1
  21. package/dist/components/integration-gallery.js.map +1 -1
  22. package/dist/components/integration-usage-events.d.ts +1 -3
  23. package/dist/components/integration-usage-events.d.ts.map +1 -1
  24. package/dist/components/integration-usage-events.js +1 -125
  25. package/dist/components/integration-usage-events.js.map +1 -1
  26. package/dist/components/integration-usage-modal.d.ts +2 -2
  27. package/dist/components/integration-usage-modal.d.ts.map +1 -1
  28. package/dist/components/integration-usage-modal.js +2 -2
  29. package/dist/components/integration-usage-modal.js.map +1 -1
  30. package/dist/components/provider-grid.d.ts +7 -4
  31. package/dist/components/provider-grid.d.ts.map +1 -1
  32. package/dist/components/provider-grid.js +15 -10
  33. package/dist/components/provider-grid.js.map +1 -1
  34. package/dist/components/redirect-install-page.d.ts +2 -3
  35. package/dist/components/redirect-install-page.d.ts.map +1 -1
  36. package/dist/components/redirect-install-page.js +1 -1
  37. package/dist/components/redirect-install-page.js.map +1 -1
  38. package/dist/components/repository-picker.d.ts +2 -2
  39. package/dist/components/repository-picker.d.ts.map +1 -1
  40. package/dist/components/repository-picker.js +4 -4
  41. package/dist/components/repository-picker.js.map +1 -1
  42. package/dist/components/webhook/webhook-create-modal.d.ts +2 -2
  43. package/dist/components/webhook/webhook-create-modal.d.ts.map +1 -1
  44. package/dist/components/webhook/webhook-create-modal.js +8 -8
  45. package/dist/components/webhook/webhook-create-modal.js.map +1 -1
  46. package/dist/components/webhook/webhook-modals.stories.d.ts.map +1 -1
  47. package/dist/components/webhook/webhook-modals.stories.js +7 -7
  48. package/dist/components/webhook/webhook-modals.stories.js.map +1 -1
  49. package/dist/components/webhook/webhook-usage-details.d.ts.map +1 -1
  50. package/dist/components/webhook/webhook-usage-details.js +2 -2
  51. package/dist/components/webhook/webhook-usage-details.js.map +1 -1
  52. package/dist/connection-status-badge.d.ts +2 -2
  53. package/dist/connection-status-badge.d.ts.map +1 -1
  54. package/dist/connection-status-badge.js +11 -15
  55. package/dist/connection-status-badge.js.map +1 -1
  56. package/dist/core/models.d.ts +66 -0
  57. package/dist/core/models.d.ts.map +1 -0
  58. package/dist/core/models.js +156 -0
  59. package/dist/core/models.js.map +1 -0
  60. package/dist/feature.d.ts +7 -0
  61. package/dist/feature.d.ts.map +1 -1
  62. package/dist/feature.js +10 -9
  63. package/dist/feature.js.map +1 -1
  64. package/dist/hooks/api/integration-mapper.d.ts +11 -0
  65. package/dist/hooks/api/integration-mapper.d.ts.map +1 -0
  66. package/dist/hooks/api/integration-mapper.js +54 -0
  67. package/dist/hooks/api/integration-mapper.js.map +1 -0
  68. package/dist/hooks/api/integrations.d.ts +45 -240
  69. package/dist/hooks/api/integrations.d.ts.map +1 -1
  70. package/dist/hooks/api/integrations.js +78 -31
  71. package/dist/hooks/api/integrations.js.map +1 -1
  72. package/dist/hooks/api/webhook-connections.d.ts +12 -64
  73. package/dist/hooks/api/webhook-connections.d.ts.map +1 -1
  74. package/dist/hooks/api/webhook-connections.js +17 -9
  75. package/dist/hooks/api/webhook-connections.js.map +1 -1
  76. package/dist/index.d.ts +2 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +2 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/linear-callback.d.ts +2 -1
  81. package/dist/linear-callback.d.ts.map +1 -1
  82. package/dist/linear-callback.js +17 -15
  83. package/dist/linear-callback.js.map +1 -1
  84. package/dist/pages/gitea-install-page.d.ts.map +1 -1
  85. package/dist/pages/gitea-install-page.js +1 -10
  86. package/dist/pages/gitea-install-page.js.map +1 -1
  87. package/dist/pages/linear-callback-page.d.ts.map +1 -1
  88. package/dist/pages/linear-callback-page.js +19 -32
  89. package/dist/pages/linear-callback-page.js.map +1 -1
  90. package/dist/pages/linear-install-page.d.ts.map +1 -1
  91. package/dist/pages/linear-install-page.js +2 -1
  92. package/dist/pages/linear-install-page.js.map +1 -1
  93. package/dist/pages/sentry-callback-page.d.ts.map +1 -1
  94. package/dist/pages/sentry-callback-page.js +22 -25
  95. package/dist/pages/sentry-callback-page.js.map +1 -1
  96. package/dist/pages/sentry-install-page.d.ts.map +1 -1
  97. package/dist/pages/sentry-install-page.js +2 -1
  98. package/dist/pages/sentry-install-page.js.map +1 -1
  99. package/dist/pages/slack-callback-page.d.ts.map +1 -1
  100. package/dist/pages/slack-callback-page.js +21 -28
  101. package/dist/pages/slack-callback-page.js.map +1 -1
  102. package/dist/pages/slack-install-page.d.ts.map +1 -1
  103. package/dist/pages/slack-install-page.js +2 -1
  104. package/dist/pages/slack-install-page.js.map +1 -1
  105. package/dist/pages/source-control-onboarding-page.d.ts.map +1 -1
  106. package/dist/pages/source-control-onboarding-page.js +5 -1
  107. package/dist/pages/source-control-onboarding-page.js.map +1 -1
  108. package/dist/provider-catalog.d.ts.map +1 -1
  109. package/dist/provider-catalog.js +7 -6
  110. package/dist/provider-catalog.js.map +1 -1
  111. package/dist/routes/github-callback.d.ts +16 -0
  112. package/dist/routes/github-callback.d.ts.map +1 -1
  113. package/dist/routes/github-callback.js +43 -21
  114. package/dist/routes/github-callback.js.map +1 -1
  115. package/dist/routes/linear-callback.d.ts +2 -0
  116. package/dist/routes/linear-callback.d.ts.map +1 -1
  117. package/dist/routes/linear-callback.js +2 -0
  118. package/dist/routes/linear-callback.js.map +1 -1
  119. package/dist/routes/sentry-callback.d.ts +2 -0
  120. package/dist/routes/sentry-callback.d.ts.map +1 -1
  121. package/dist/routes/sentry-callback.js +2 -0
  122. package/dist/routes/sentry-callback.js.map +1 -1
  123. package/dist/routes/slack-callback.d.ts +2 -0
  124. package/dist/routes/slack-callback.d.ts.map +1 -1
  125. package/dist/routes/slack-callback.js +2 -0
  126. package/dist/routes/slack-callback.js.map +1 -1
  127. package/dist/sentry-callback.d.ts +2 -1
  128. package/dist/sentry-callback.d.ts.map +1 -1
  129. package/dist/sentry-callback.js +18 -19
  130. package/dist/sentry-callback.js.map +1 -1
  131. package/dist/slack-callback.d.ts +2 -1
  132. package/dist/slack-callback.d.ts.map +1 -1
  133. package/dist/slack-callback.js +17 -15
  134. package/dist/slack-callback.js.map +1 -1
  135. package/dist/tsconfig.test.tsbuildinfo +1 -1
  136. package/package.json +17 -17
  137. package/src/application/complete-integration-callback.test.ts +41 -0
  138. package/src/application/complete-integration-callback.ts +39 -0
  139. package/src/components/connection-picker.tsx +7 -7
  140. package/src/components/installed-integrations-section.tsx +29 -24
  141. package/src/components/integration-gallery-for-workspace.tsx +23 -16
  142. package/src/components/integration-gallery.tsx +3 -3
  143. package/src/components/integration-usage-events.ts +1 -116
  144. package/src/components/integration-usage-modal.tsx +4 -4
  145. package/src/components/provider-grid.tsx +30 -18
  146. package/src/components/redirect-install-page.test.tsx +2 -6
  147. package/src/components/redirect-install-page.tsx +3 -2
  148. package/src/components/repository-picker.tsx +6 -6
  149. package/src/components/webhook/webhook-create-modal.tsx +12 -11
  150. package/src/components/webhook/webhook-modals.stories.tsx +10 -10
  151. package/src/components/webhook/webhook-usage-details.tsx +2 -4
  152. package/src/connection-status-badge.tsx +8 -14
  153. package/src/core/models.ts +200 -0
  154. package/src/feature.ts +12 -10
  155. package/src/hooks/api/integration-mapper.ts +64 -0
  156. package/src/hooks/api/integrations.test.ts +44 -22
  157. package/src/hooks/api/integrations.ts +229 -84
  158. package/src/hooks/api/webhook-connections.ts +58 -17
  159. package/src/index.ts +2 -1
  160. package/src/linear-callback.ts +24 -16
  161. package/src/pages/gitea-install-page.tsx +1 -10
  162. package/src/pages/linear-callback-page.tsx +18 -30
  163. package/src/pages/linear-install-page.tsx +2 -1
  164. package/src/pages/sentry-callback-page.test.tsx +5 -1
  165. package/src/pages/sentry-callback-page.tsx +28 -25
  166. package/src/pages/sentry-install-page.tsx +2 -1
  167. package/src/pages/slack-callback-page.tsx +20 -26
  168. package/src/pages/slack-install-page.tsx +2 -1
  169. package/src/pages/source-control-onboarding-page.tsx +5 -1
  170. package/src/provider-catalog.ts +7 -6
  171. package/src/routes/github-callback.test.ts +35 -1
  172. package/src/routes/github-callback.tsx +47 -21
  173. package/src/routes/linear-callback.tsx +5 -1
  174. package/src/routes/sentry-callback.tsx +5 -1
  175. package/src/routes/slack-callback.tsx +6 -1
  176. package/src/sentry-callback.test.ts +2 -1
  177. package/src/sentry-callback.ts +30 -20
  178. package/src/slack-callback.ts +24 -16
  179. package/tsconfig.build.tsbuildinfo +1 -1
  180. package/vercel.json +5 -0
  181. package/dist/integration-icon.d.ts +0 -17
  182. package/dist/integration-icon.d.ts.map +0 -1
  183. package/dist/integration-icon.js +0 -22
  184. package/dist/integration-icon.js.map +0 -1
  185. package/src/integration-icon.react.test.tsx +0 -40
  186. package/src/integration-icon.test.ts +0 -19
  187. package/src/integration-icon.tsx +0 -26
@@ -1 +1 @@
1
- {"version":3,"file":"linear-callback.d.ts","sourceRoot":"","sources":["../src/linear-callback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,qCAAqC,CAAC;AAGhF,eAAO,MAAM,4BAA4B,wCAAwC,CAAC;AAElF,KAAK,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC,CAAC;AAE5E,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAM/F;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAMxF;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAM3E;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,sBAAsB,GAAG,SAAS,CAWpC;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CASlF;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,qBAAqB,CAmEjF"}
1
+ {"version":3,"file":"linear-callback.d.ts","sourceRoot":"","sources":["../src/linear-callback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,qCAAqC,CAAC;AAEhF,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,oBAAoB,CAAC;AAE5B,eAAO,MAAM,4BAA4B,wCAAwC,CAAC;AAElF,KAAK,gBAAgB,GAAG,cAAc,GAAG,SAAS,CAAC;AAanD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAE/F;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAExF;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAE3E;AAMD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,sBAAsB,GAAG,SAAS,CAWpC;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CASlF;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,qBAAqB,CAmEjF"}
@@ -1,25 +1,27 @@
1
1
  import { ApiError } from '@shipfox/client-api';
2
+ import { createTypedBrowserStorage } from '@shipfox/client-ui';
2
3
  export const LINEAR_INSTALL_WORKSPACE_KEY = 'shipfox.linear-install.workspace-id';
4
+ const linearInstallWorkspaceStorageKey = {
5
+ key: LINEAR_INSTALL_WORKSPACE_KEY,
6
+ lifetime: 'session',
7
+ // This is a one-shot navigation hint, not private workspace state. It must
8
+ // remain readable on an OAuth callback before workspace hydration completes;
9
+ // the callback state and returned connection are the authoritative checks.
10
+ principalScope: 'global',
11
+ serialize: (workspaceId)=>workspaceId,
12
+ parse: (value)=>value || undefined
13
+ };
3
14
  export function saveLinearInstallWorkspace(storage, workspaceId) {
4
- try {
5
- storage.setItem(LINEAR_INSTALL_WORKSPACE_KEY, workspaceId);
6
- } catch {
7
- // Storage only improves recovery navigation; callback input always comes from the URL.
8
- }
15
+ installWorkspaceStorage(storage).write(workspaceId);
9
16
  }
10
17
  export function readLinearInstallWorkspace(storage) {
11
- try {
12
- return storage.getItem(LINEAR_INSTALL_WORKSPACE_KEY) ?? undefined;
13
- } catch {
14
- return undefined;
15
- }
18
+ return installWorkspaceStorage(storage).read();
16
19
  }
17
20
  export function clearLinearInstallWorkspace(storage) {
18
- try {
19
- storage.removeItem(LINEAR_INSTALL_WORKSPACE_KEY);
20
- } catch {
21
- // A stale navigation hint cannot alter the callback request or its response.
22
- }
21
+ installWorkspaceStorage(storage).remove();
22
+ }
23
+ function installWorkspaceStorage(storage) {
24
+ return createTypedBrowserStorage(()=>storage, linearInstallWorkspaceStorageKey);
23
25
  }
24
26
  export function parseLinearCallbackQuery(search) {
25
27
  const state = stringParam(search.state);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/linear-callback.ts"],"sourcesContent":["import type {LinearCallbackQueryDto} from '@shipfox/api-integration-linear-dto';\nimport {ApiError} from '@shipfox/client-api';\n\nexport const LINEAR_INSTALL_WORKSPACE_KEY = 'shipfox.linear-install.workspace-id';\n\ntype WorkspaceStorage = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;\n\nexport function saveLinearInstallWorkspace(storage: WorkspaceStorage, workspaceId: string): void {\n try {\n storage.setItem(LINEAR_INSTALL_WORKSPACE_KEY, workspaceId);\n } catch {\n // Storage only improves recovery navigation; callback input always comes from the URL.\n }\n}\n\nexport function readLinearInstallWorkspace(storage: WorkspaceStorage): string | undefined {\n try {\n return storage.getItem(LINEAR_INSTALL_WORKSPACE_KEY) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function clearLinearInstallWorkspace(storage: WorkspaceStorage): void {\n try {\n storage.removeItem(LINEAR_INSTALL_WORKSPACE_KEY);\n } catch {\n // A stale navigation hint cannot alter the callback request or its response.\n }\n}\n\nexport function parseLinearCallbackQuery(\n search: Record<string, unknown>,\n): LinearCallbackQueryDto | undefined {\n const state = stringParam(search.state);\n if (!state) return undefined;\n\n const code = stringParam(search.code);\n if (code) return {code, state};\n\n const error = stringParam(search.error);\n if (!error) return undefined;\n const errorDescription = stringParam(search.error_description);\n return errorDescription ? {error, error_description: errorDescription, state} : {error, state};\n}\n\nexport function serializeLinearCallbackQuery(query: LinearCallbackQueryDto): string {\n const params = new URLSearchParams();\n if ('code' in query) params.set('code', query.code);\n else {\n params.set('error', query.error);\n if (query.error_description) params.set('error_description', query.error_description);\n }\n params.set('state', query.state);\n return params.toString();\n}\n\nexport type LinearCallbackFailure = {\n title?: string;\n message: string;\n startOver: boolean;\n signIn: boolean;\n};\n\nexport function classifyLinearCallbackError(error: unknown): LinearCallbackFailure {\n if (error instanceof ApiError) {\n if (error.code === 'invalid-linear-install-state') {\n return {\n title: 'Linear install link expired',\n message: 'Linear install link expired. Start again from workspace settings.',\n startOver: true,\n signIn: false,\n };\n }\n if (error.code === 'linear-install-state-actor-mismatch' || error.code === 'unauthorized') {\n return {\n title: 'Different Shipfox account',\n message: 'Different Shipfox account. Sign in with the account that started this install.',\n startOver: true,\n signIn: true,\n };\n }\n if (\n error.code === 'linear-installation-already-linked' ||\n error.code === 'linear-connection-already-linked'\n ) {\n return {\n title: 'Linear already linked',\n message: 'This Linear organization is already linked to another workspace.',\n startOver: false,\n signIn: false,\n };\n }\n if (\n error.code === 'linear-authorization-scope-mismatch' ||\n error.code === 'linear-oauth-callback-error'\n ) {\n return {\n title: 'Linear permissions needed',\n message:\n 'Linear did not authorize the permissions Shipfox needs. Review the Linear consent and start again.',\n startOver: true,\n signIn: false,\n };\n }\n if (error.status === 0 || error.code === 'network-error') {\n return {\n message: 'Could not reach Shipfox. Check your connection and start again.',\n startOver: true,\n signIn: false,\n };\n }\n if (\n error.status >= 500 ||\n error.status === 429 ||\n error.code === 'provider-unavailable' ||\n error.code === 'timeout' ||\n error.code === 'rate-limited'\n ) {\n return {\n message: 'Linear is temporarily unavailable. Start a new install when it is available.',\n startOver: true,\n signIn: false,\n };\n }\n }\n return {\n message: 'Could not complete the Linear install. Start again from workspace settings.',\n startOver: true,\n signIn: false,\n };\n}\n\nfunction stringParam(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n"],"names":["ApiError","LINEAR_INSTALL_WORKSPACE_KEY","saveLinearInstallWorkspace","storage","workspaceId","setItem","readLinearInstallWorkspace","getItem","undefined","clearLinearInstallWorkspace","removeItem","parseLinearCallbackQuery","search","state","stringParam","code","error","errorDescription","error_description","serializeLinearCallbackQuery","query","params","URLSearchParams","set","toString","classifyLinearCallbackError","title","message","startOver","signIn","status","value","length"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/linear-callback.ts"],"sourcesContent":["import type {LinearCallbackQueryDto} from '@shipfox/api-integration-linear-dto';\nimport {ApiError} from '@shipfox/client-api';\nimport {\n type BrowserStorage,\n type BrowserStorageKey,\n createTypedBrowserStorage,\n} from '@shipfox/client-ui';\n\nexport const LINEAR_INSTALL_WORKSPACE_KEY = 'shipfox.linear-install.workspace-id';\n\ntype WorkspaceStorage = BrowserStorage | undefined;\n\nconst linearInstallWorkspaceStorageKey = {\n key: LINEAR_INSTALL_WORKSPACE_KEY,\n lifetime: 'session',\n // This is a one-shot navigation hint, not private workspace state. It must\n // remain readable on an OAuth callback before workspace hydration completes;\n // the callback state and returned connection are the authoritative checks.\n principalScope: 'global',\n serialize: (workspaceId: string) => workspaceId,\n parse: (value: string) => value || undefined,\n} satisfies BrowserStorageKey<string>;\n\nexport function saveLinearInstallWorkspace(storage: WorkspaceStorage, workspaceId: string): void {\n installWorkspaceStorage(storage).write(workspaceId);\n}\n\nexport function readLinearInstallWorkspace(storage: WorkspaceStorage): string | undefined {\n return installWorkspaceStorage(storage).read();\n}\n\nexport function clearLinearInstallWorkspace(storage: WorkspaceStorage): void {\n installWorkspaceStorage(storage).remove();\n}\n\nfunction installWorkspaceStorage(storage: WorkspaceStorage) {\n return createTypedBrowserStorage(() => storage, linearInstallWorkspaceStorageKey);\n}\n\nexport function parseLinearCallbackQuery(\n search: Record<string, unknown>,\n): LinearCallbackQueryDto | undefined {\n const state = stringParam(search.state);\n if (!state) return undefined;\n\n const code = stringParam(search.code);\n if (code) return {code, state};\n\n const error = stringParam(search.error);\n if (!error) return undefined;\n const errorDescription = stringParam(search.error_description);\n return errorDescription ? {error, error_description: errorDescription, state} : {error, state};\n}\n\nexport function serializeLinearCallbackQuery(query: LinearCallbackQueryDto): string {\n const params = new URLSearchParams();\n if ('code' in query) params.set('code', query.code);\n else {\n params.set('error', query.error);\n if (query.error_description) params.set('error_description', query.error_description);\n }\n params.set('state', query.state);\n return params.toString();\n}\n\nexport type LinearCallbackFailure = {\n title?: string;\n message: string;\n startOver: boolean;\n signIn: boolean;\n};\n\nexport function classifyLinearCallbackError(error: unknown): LinearCallbackFailure {\n if (error instanceof ApiError) {\n if (error.code === 'invalid-linear-install-state') {\n return {\n title: 'Linear install link expired',\n message: 'Linear install link expired. Start again from workspace settings.',\n startOver: true,\n signIn: false,\n };\n }\n if (error.code === 'linear-install-state-actor-mismatch' || error.code === 'unauthorized') {\n return {\n title: 'Different Shipfox account',\n message: 'Different Shipfox account. Sign in with the account that started this install.',\n startOver: true,\n signIn: true,\n };\n }\n if (\n error.code === 'linear-installation-already-linked' ||\n error.code === 'linear-connection-already-linked'\n ) {\n return {\n title: 'Linear already linked',\n message: 'This Linear organization is already linked to another workspace.',\n startOver: false,\n signIn: false,\n };\n }\n if (\n error.code === 'linear-authorization-scope-mismatch' ||\n error.code === 'linear-oauth-callback-error'\n ) {\n return {\n title: 'Linear permissions needed',\n message:\n 'Linear did not authorize the permissions Shipfox needs. Review the Linear consent and start again.',\n startOver: true,\n signIn: false,\n };\n }\n if (error.status === 0 || error.code === 'network-error') {\n return {\n message: 'Could not reach Shipfox. Check your connection and start again.',\n startOver: true,\n signIn: false,\n };\n }\n if (\n error.status >= 500 ||\n error.status === 429 ||\n error.code === 'provider-unavailable' ||\n error.code === 'timeout' ||\n error.code === 'rate-limited'\n ) {\n return {\n message: 'Linear is temporarily unavailable. Start a new install when it is available.',\n startOver: true,\n signIn: false,\n };\n }\n }\n return {\n message: 'Could not complete the Linear install. Start again from workspace settings.',\n startOver: true,\n signIn: false,\n };\n}\n\nfunction stringParam(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n"],"names":["ApiError","createTypedBrowserStorage","LINEAR_INSTALL_WORKSPACE_KEY","linearInstallWorkspaceStorageKey","key","lifetime","principalScope","serialize","workspaceId","parse","value","undefined","saveLinearInstallWorkspace","storage","installWorkspaceStorage","write","readLinearInstallWorkspace","read","clearLinearInstallWorkspace","remove","parseLinearCallbackQuery","search","state","stringParam","code","error","errorDescription","error_description","serializeLinearCallbackQuery","query","params","URLSearchParams","set","toString","classifyLinearCallbackError","title","message","startOver","signIn","status","length"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gitea-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/gitea-install-page.tsx"],"names":[],"mappings":"AAaA,wBAAgB,gBAAgB,gCA8F/B"}
1
+ {"version":3,"file":"gitea-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/gitea-install-page.tsx"],"names":[],"mappings":"AAYA,wBAAgB,gBAAgB,gCAsF/B"}
@@ -6,15 +6,13 @@ import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-fi
6
6
  import { toast } from '@shipfox/react-ui/toast';
7
7
  import { Header, Text } from '@shipfox/react-ui/typography';
8
8
  import { useForm } from '@tanstack/react-form';
9
- import { useQueryClient } from '@tanstack/react-query';
10
9
  import { Link, useNavigate } from '@tanstack/react-router';
11
10
  import { useState } from 'react';
12
- import { integrationsQueryKeys, useCreateGiteaConnectionMutation } from '#hooks/api/integrations.js';
11
+ import { useCreateGiteaConnectionMutation } from '#hooks/api/integrations.js';
13
12
  import { giteaConnectErrorToFormError } from './gitea-form-errors.js';
14
13
  export function GiteaInstallPage() {
15
14
  const workspace = useActiveWorkspace();
16
15
  const connect = useCreateGiteaConnectionMutation();
17
- const queryClient = useQueryClient();
18
16
  const navigate = useNavigate();
19
17
  const [formError, setFormError] = useState();
20
18
  const form = useForm({
@@ -29,13 +27,6 @@ export function GiteaInstallPage() {
29
27
  workspace_id: workspaceId,
30
28
  org: value.org.trim()
31
29
  });
32
- await queryClient.invalidateQueries({
33
- queryKey: integrationsQueryKeys.sourceConnections(workspaceId),
34
- // The workspace home is the next active observer, not this page, so the
35
- // default 'active' refetch would no-op and leave the cache stale until
36
- // after the home redirects back here.
37
- refetchType: 'all'
38
- });
39
30
  toast.success('Gitea organization installed.');
40
31
  await navigate({
41
32
  to: '/workspaces/$wid',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/gitea-install-page.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {integrationsQueryKeys, useCreateGiteaConnectionMutation} from '#hooks/api/integrations.js';\nimport {giteaConnectErrorToFormError} from './gitea-form-errors.js';\n\nexport function GiteaInstallPage() {\n const workspace = useActiveWorkspace();\n const connect = useCreateGiteaConnectionMutation();\n const queryClient = useQueryClient();\n const navigate = useNavigate();\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {org: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n const workspaceId = workspace.id;\n try {\n await connect.mutateAsync({workspace_id: workspaceId, org: value.org.trim()});\n await queryClient.invalidateQueries({\n queryKey: integrationsQueryKeys.sourceConnections(workspaceId),\n // The workspace home is the next active observer, not this page, so the\n // default 'active' refetch would no-op and leave the cache stale until\n // after the home redirects back here.\n refetchType: 'all',\n });\n toast.success('Gitea organization installed.');\n await navigate({to: '/workspaces/$wid', params: {wid: workspaceId}});\n } catch (error) {\n const mapped = giteaConnectErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n },\n });\n\n return (\n <div className=\"mx-auto flex w-full max-w-[480px] flex-col gap-20\">\n <header className=\"flex flex-col gap-8\">\n <Header variant=\"h1\">Install Gitea</Header>\n <Text size=\"md\" className=\"text-foreground-neutral-muted\">\n Enter the Gitea organization to install in this workspace.\n </Text>\n </header>\n\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"org\"\n validators={{\n onBlur: ({value}) =>\n value.trim().length > 0 ? undefined : 'Enter your Gitea organization.',\n onSubmit: ({value}) =>\n value.trim().length > 0 ? undefined : 'Enter your Gitea organization.',\n }}\n >\n {(field) => (\n <FormField label=\"Organization\" id=\"gitea-org\" error={fieldError(field)}>\n <FormFieldInput\n placeholder=\"my-org\"\n autoComplete=\"off\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n <div className=\"flex items-center gap-12\">\n <Button type=\"submit\" isLoading={connect.isPending}>\n Install\n </Button>\n <ButtonLink asChild variant=\"muted\">\n <Link to=\"/workspaces/$wid/integrations\" params={{wid: workspace.id}}>\n Cancel\n </Link>\n </ButtonLink>\n </div>\n </form>\n </div>\n );\n}\n"],"names":["useActiveWorkspace","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","toast","Header","Text","useForm","useQueryClient","Link","useNavigate","useState","integrationsQueryKeys","useCreateGiteaConnectionMutation","giteaConnectErrorToFormError","GiteaInstallPage","workspace","connect","queryClient","navigate","formError","setFormError","form","defaultValues","org","onSubmit","value","undefined","workspaceId","id","mutateAsync","workspace_id","trim","invalidateQueries","queryKey","sourceConnections","refetchType","success","to","params","wid","error","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","message","div","className","header","variant","size","noValidate","event","preventDefault","stopPropagation","handleSubmit","role","type","Field","name","validators","onBlur","length","label","placeholder","autoComplete","state","onChange","handleChange","target","handleBlur","isLoading","isPending","asChild"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/gitea-install-page.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {useCreateGiteaConnectionMutation} from '#hooks/api/integrations.js';\nimport {giteaConnectErrorToFormError} from './gitea-form-errors.js';\n\nexport function GiteaInstallPage() {\n const workspace = useActiveWorkspace();\n const connect = useCreateGiteaConnectionMutation();\n const navigate = useNavigate();\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {org: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n const workspaceId = workspace.id;\n try {\n await connect.mutateAsync({workspace_id: workspaceId, org: value.org.trim()});\n toast.success('Gitea organization installed.');\n await navigate({to: '/workspaces/$wid', params: {wid: workspaceId}});\n } catch (error) {\n const mapped = giteaConnectErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n },\n });\n\n return (\n <div className=\"mx-auto flex w-full max-w-[480px] flex-col gap-20\">\n <header className=\"flex flex-col gap-8\">\n <Header variant=\"h1\">Install Gitea</Header>\n <Text size=\"md\" className=\"text-foreground-neutral-muted\">\n Enter the Gitea organization to install in this workspace.\n </Text>\n </header>\n\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"org\"\n validators={{\n onBlur: ({value}) =>\n value.trim().length > 0 ? undefined : 'Enter your Gitea organization.',\n onSubmit: ({value}) =>\n value.trim().length > 0 ? undefined : 'Enter your Gitea organization.',\n }}\n >\n {(field) => (\n <FormField label=\"Organization\" id=\"gitea-org\" error={fieldError(field)}>\n <FormFieldInput\n placeholder=\"my-org\"\n autoComplete=\"off\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n <div className=\"flex items-center gap-12\">\n <Button type=\"submit\" isLoading={connect.isPending}>\n Install\n </Button>\n <ButtonLink asChild variant=\"muted\">\n <Link to=\"/workspaces/$wid/integrations\" params={{wid: workspace.id}}>\n Cancel\n </Link>\n </ButtonLink>\n </div>\n </form>\n </div>\n );\n}\n"],"names":["useActiveWorkspace","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","toast","Header","Text","useForm","Link","useNavigate","useState","useCreateGiteaConnectionMutation","giteaConnectErrorToFormError","GiteaInstallPage","workspace","connect","navigate","formError","setFormError","form","defaultValues","org","onSubmit","value","undefined","workspaceId","id","mutateAsync","workspace_id","trim","success","to","params","wid","error","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","message","div","className","header","variant","size","noValidate","event","preventDefault","stopPropagation","handleSubmit","role","type","Field","name","validators","onBlur","length","label","placeholder","autoComplete","state","onChange","handleChange","target","handleBlur","isLoading","isPending","asChild"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"linear-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/linear-callback-page.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,kBAAkB,gCAuFjC"}
1
+ {"version":3,"file":"linear-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/linear-callback-page.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,kBAAkB,gCA0EjC"}
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useRefreshAuth } from '@shipfox/client-auth';
3
- import { createSingleFlight } from '@shipfox/client-ui';
3
+ import { useRouteSearch } from '@shipfox/client-shell/runtime';
4
+ import { createSingleFlight, sessionStorageOrUndefined } from '@shipfox/client-ui';
4
5
  import { FullPageLoader } from '@shipfox/react-ui/loader';
5
6
  import { toast } from '@shipfox/react-ui/toast';
6
- import { useQueryClient } from '@tanstack/react-query';
7
- import { useNavigate, useSearch } from '@tanstack/react-router';
7
+ import { useNavigate } from '@tanstack/react-router';
8
8
  import { useEffect, useMemo, useState } from 'react';
9
+ import { useCompleteIntegrationCallback } from '#application/complete-integration-callback.js';
9
10
  import { CallbackStatusShell } from '#components/callback-status-shell.js';
10
- import { integrationsQueryKeys, useCompleteLinearCallbackMutation } from '#hooks/api/integrations.js';
11
+ import { useCompleteLinearCallbackMutation } from '#hooks/api/integrations.js';
11
12
  import { classifyLinearCallbackError, clearLinearInstallWorkspace, parseLinearCallbackQuery, readLinearInstallWorkspace, serializeLinearCallbackQuery } from '#linear-callback.js';
12
13
  // Retain only recent completions: this bounds long-lived callback pages while
13
14
  // still covering StrictMode and immediate Back/Forward remounts.
@@ -18,46 +19,32 @@ const callbackRequests = createSingleFlight({
18
19
  // effect re-runs against the cached request as the mutation identity churns.
19
20
  const toastedCallbacks = new Set();
20
21
  export function LinearCallbackPage() {
21
- const search = useSearch({
22
- strict: false
23
- });
24
22
  const navigate = useNavigate();
25
23
  const refreshAuth = useRefreshAuth();
26
- const queryClient = useQueryClient();
24
+ const completeIntegrationCallback = useCompleteIntegrationCallback();
27
25
  const { mutateAsync: completeLinearCallback } = useCompleteLinearCallbackMutation();
28
- const params = useMemo(()=>parseLinearCallbackQuery(search), [
29
- search
30
- ]);
31
- const workspaceId = useMemo(()=>{
32
- try {
33
- return readLinearInstallWorkspace(window.sessionStorage);
34
- } catch {
35
- return undefined;
36
- }
37
- }, []);
26
+ const params = useRouteSearch(parseLinearCallbackQuery);
27
+ const workspaceId = useMemo(()=>readLinearInstallWorkspace(sessionStorageOrUndefined()), []);
38
28
  const [failure, setFailure] = useState();
39
29
  useEffect(()=>{
40
30
  if (!params) return;
41
31
  let disposed = false;
42
32
  const key = serializeLinearCallbackQuery(params);
43
- const request = callbackRequests.run(key, async ()=>await refreshAuth().then(async (session)=>await completeLinearCallback({
44
- query: params,
45
- token: session.accessToken
46
- })));
33
+ const request = callbackRequests.run(key, async ()=>await completeIntegrationCallback({
34
+ input: params,
35
+ refreshAuth,
36
+ complete: async (query, token)=>await completeLinearCallback({
37
+ query,
38
+ token
39
+ })
40
+ }));
47
41
  request.then(async (connection)=>{
48
42
  if (disposed) return;
49
43
  try {
50
- clearLinearInstallWorkspace(window.sessionStorage);
44
+ clearLinearInstallWorkspace(sessionStorageOrUndefined());
51
45
  } catch {
52
46
  // The successful API response remains the source of truth for navigation.
53
47
  }
54
- try {
55
- await queryClient.invalidateQueries({
56
- queryKey: integrationsQueryKeys.connectionsByWorkspace(connection.workspace_id)
57
- });
58
- } catch {
59
- // Cache refresh is best effort: the successful callback is already committed server-side.
60
- }
61
48
  if (disposed) return;
62
49
  if (!toastedCallbacks.has(key)) {
63
50
  toastedCallbacks.add(key);
@@ -67,7 +54,7 @@ export function LinearCallbackPage() {
67
54
  await navigate({
68
55
  to: '/workspaces/$wid/settings/integrations',
69
56
  params: {
70
- wid: connection.workspace_id
57
+ wid: connection.workspaceId
71
58
  },
72
59
  replace: true
73
60
  });
@@ -82,10 +69,10 @@ export function LinearCallbackPage() {
82
69
  disposed = true;
83
70
  };
84
71
  }, [
72
+ completeIntegrationCallback,
85
73
  completeLinearCallback,
86
74
  navigate,
87
75
  params,
88
- queryClient,
89
76
  refreshAuth
90
77
  ]);
91
78
  if (!params) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/linear-callback-page.tsx"],"sourcesContent":["import type {LinearCallbackResponseDto} from '@shipfox/api-integration-linear-dto';\nimport {useRefreshAuth} from '@shipfox/client-auth';\nimport {createSingleFlight} 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, useSearch} from '@tanstack/react-router';\nimport {useEffect, useMemo, useState} from 'react';\nimport {CallbackStatusShell} from '#components/callback-status-shell.js';\nimport {integrationsQueryKeys, 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, LinearCallbackResponseDto>({\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 search = useSearch({strict: false});\n const navigate = useNavigate();\n const refreshAuth = useRefreshAuth();\n const queryClient = useQueryClient();\n const {mutateAsync: completeLinearCallback} = useCompleteLinearCallbackMutation();\n const params = useMemo(() => parseLinearCallbackQuery(search), [search]);\n const workspaceId = useMemo(() => {\n try {\n return readLinearInstallWorkspace(window.sessionStorage);\n } catch {\n return undefined;\n }\n }, []);\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 refreshAuth().then(\n async (session) =>\n await completeLinearCallback({query: params, token: session.accessToken}),\n ),\n );\n\n request.then(\n async (connection) => {\n if (disposed) return;\n try {\n clearLinearInstallWorkspace(window.sessionStorage);\n } catch {\n // The successful API response remains the source of truth for navigation.\n }\n try {\n await queryClient.invalidateQueries({\n queryKey: integrationsQueryKeys.connectionsByWorkspace(connection.workspace_id),\n });\n } catch {\n // Cache refresh is best effort: the successful callback is already committed server-side.\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.workspace_id},\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 }, [completeLinearCallback, navigate, params, queryClient, 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","createSingleFlight","FullPageLoader","toast","useQueryClient","useNavigate","useSearch","useEffect","useMemo","useState","CallbackStatusShell","integrationsQueryKeys","useCompleteLinearCallbackMutation","classifyLinearCallbackError","clearLinearInstallWorkspace","parseLinearCallbackQuery","readLinearInstallWorkspace","serializeLinearCallbackQuery","callbackRequests","maxTerminalResults","toastedCallbacks","Set","LinearCallbackPage","search","strict","navigate","refreshAuth","queryClient","mutateAsync","completeLinearCallback","params","workspaceId","window","sessionStorage","undefined","failure","setFailure","disposed","key","request","run","then","session","query","token","accessToken","connection","invalidateQueries","queryKey","connectionsByWorkspace","workspace_id","has","add","success","to","wid","replace","error","LinearCallbackFailurePage","title","message","startOver","signIn","aria-label","switchAccount","installPath"],"mappings":"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"}
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 +1 @@
1
- {"version":3,"file":"linear-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/linear-install-page.tsx"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,gCAqBhC"}
1
+ {"version":3,"file":"linear-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/linear-install-page.tsx"],"names":[],"mappings":"AAMA,wBAAgB,iBAAiB,gCAqBhC"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { sessionStorageOrUndefined } from '@shipfox/client-ui';
2
3
  import { useCallback } from 'react';
3
4
  import { RedirectInstallPage } from '#components/redirect-install-page.js';
4
5
  import { useCreateLinearInstallMutation } from '#hooks/api/integrations.js';
@@ -14,7 +15,7 @@ export function LinearInstallPage() {
14
15
  loadingLabel: "Connecting Linear",
15
16
  beforeRedirect: (workspaceId)=>{
16
17
  try {
17
- saveLinearInstallWorkspace(window.sessionStorage, workspaceId);
18
+ saveLinearInstallWorkspace(sessionStorageOrUndefined(), workspaceId);
18
19
  } catch {
19
20
  // Storage can be disabled before the helper gets a usable Storage object.
20
21
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/linear-install-page.tsx"],"sourcesContent":["import {useCallback} from 'react';\nimport {RedirectInstallPage} from '#components/redirect-install-page.js';\nimport {useCreateLinearInstallMutation} from '#hooks/api/integrations.js';\nimport {saveLinearInstallWorkspace} from '#linear-callback.js';\n\nexport function LinearInstallPage() {\n const createInstall = useCreateLinearInstallMutation();\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 Linear install.\"\n loadingLabel=\"Connecting Linear\"\n beforeRedirect={(workspaceId) => {\n try {\n saveLinearInstallWorkspace(window.sessionStorage, workspaceId);\n } catch {\n // Storage can be disabled before the helper gets a usable Storage object.\n }\n }}\n />\n );\n}\n"],"names":["useCallback","RedirectInstallPage","useCreateLinearInstallMutation","saveLinearInstallWorkspace","LinearInstallPage","createInstall","installRequest","body","mutateAsync","errorFallbackMessage","loadingLabel","beforeRedirect","workspaceId","window","sessionStorage"],"mappings":";AAAA,SAAQA,WAAW,QAAO,QAAQ;AAClC,SAAQC,mBAAmB,QAAO,uCAAuC;AACzE,SAAQC,8BAA8B,QAAO,6BAA6B;AAC1E,SAAQC,0BAA0B,QAAO,sBAAsB;AAE/D,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,2BAA2BU,OAAOC,cAAc,EAAEF;YACpD,EAAE,OAAM;YACN,0EAA0E;YAC5E;QACF;;AAGN"}
1
+ {"version":3,"sources":["../../src/pages/linear-install-page.tsx"],"sourcesContent":["import {sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {useCallback} from 'react';\nimport {RedirectInstallPage} from '#components/redirect-install-page.js';\nimport {useCreateLinearInstallMutation} from '#hooks/api/integrations.js';\nimport {saveLinearInstallWorkspace} from '#linear-callback.js';\n\nexport function LinearInstallPage() {\n const createInstall = useCreateLinearInstallMutation();\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 Linear install.\"\n loadingLabel=\"Connecting Linear\"\n beforeRedirect={(workspaceId) => {\n try {\n saveLinearInstallWorkspace(sessionStorageOrUndefined(), workspaceId);\n } catch {\n // Storage can be disabled before the helper gets a usable Storage object.\n }\n }}\n />\n );\n}\n"],"names":["sessionStorageOrUndefined","useCallback","RedirectInstallPage","useCreateLinearInstallMutation","saveLinearInstallWorkspace","LinearInstallPage","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,8BAA8B,QAAO,6BAA6B;AAC1E,SAAQC,0BAA0B,QAAO,sBAAsB;AAE/D,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,2BAA2BJ,6BAA6Ba;YAC1D,EAAE,OAAM;YACN,0EAA0E;YAC5E;QACF;;AAGN"}
@@ -1 +1 @@
1
- {"version":3,"file":"sentry-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/sentry-callback-page.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,kBAAkB,uCAiMjC"}
1
+ {"version":3,"file":"sentry-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/sentry-callback-page.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,kBAAkB,uCAmMjC"}
@@ -1,30 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useAuthState, useRefreshAuth } from '@shipfox/client-auth';
3
- import { createSingleFlight } from '@shipfox/client-ui';
3
+ import { useRouteSearch } from '@shipfox/client-shell/runtime';
4
+ import { createSingleFlight, sessionStorageOrUndefined } from '@shipfox/client-ui';
4
5
  import { Button, ButtonLink } from '@shipfox/react-ui/button';
5
6
  import { Callout } from '@shipfox/react-ui/callout';
6
7
  import { Card } from '@shipfox/react-ui/card';
7
8
  import { FullPageLoader } from '@shipfox/react-ui/loader';
8
9
  import { toast } from '@shipfox/react-ui/toast';
9
10
  import { Header, Text } from '@shipfox/react-ui/typography';
10
- import { useQueryClient } from '@tanstack/react-query';
11
- import { Link, useNavigate, useSearch } from '@tanstack/react-router';
11
+ import { Link, useNavigate } from '@tanstack/react-router';
12
12
  import { useEffect, useMemo, useRef, useState } from 'react';
13
- import { connectSentry, integrationsQueryKeys } from '#hooks/api/integrations.js';
13
+ import { useCompleteIntegrationCallback } from '#application/complete-integration-callback.js';
14
+ import { connectSentry } from '#hooks/api/integrations.js';
14
15
  import { classifySentryConnectError, clearSentryInstallWorkspace, parseSentryCallbackParams, preselectSentryWorkspace, readSentryInstallWorkspace } from '#sentry-callback.js';
15
16
  const connectRequests = createSingleFlight();
16
17
  export function SentryCallbackPage() {
17
- const search = useSearch({
18
- strict: false
19
- });
20
18
  const navigate = useNavigate();
21
19
  const refreshAuth = useRefreshAuth();
22
20
  const { workspaces, isLoading } = useAuthState();
23
- const queryClient = useQueryClient();
24
- const params = useMemo(()=>parseSentryCallbackParams(search), [
25
- search
26
- ]);
27
- const storedWorkspaceId = useMemo(()=>readSentryInstallWorkspace(window.sessionStorage), []);
21
+ const completeIntegrationCallback = useCompleteIntegrationCallback();
22
+ const params = useRouteSearch(parseSentryCallbackParams);
23
+ const storedWorkspaceId = useMemo(()=>readSentryInstallWorkspace(sessionStorageOrUndefined()), []);
28
24
  const preselection = preselectSentryWorkspace(storedWorkspaceId, workspaces);
29
25
  const preselectedId = preselection.kind === 'pick' ? preselection.preselectedId : undefined;
30
26
  const [connectingId, setConnectingId] = useState();
@@ -97,19 +93,20 @@ export function SentryCallbackPage() {
97
93
  setConnectingId(workspaceId);
98
94
  setFailure(undefined);
99
95
  const key = `${params.installationId}|${workspaceId}|${params.code}`;
100
- const request = connectRequests.run(key, async ()=>await refreshAuth().then((session)=>connectSentry({
101
- body: {
102
- workspace_id: workspaceId,
103
- code: params.code,
104
- installation_id: params.installationId
105
- },
106
- token: session.accessToken
107
- })));
96
+ const request = connectRequests.run(key, async ()=>await completeIntegrationCallback({
97
+ input: workspaceId,
98
+ refreshAuth,
99
+ complete: async (selectedWorkspaceId, token)=>await connectSentry({
100
+ body: {
101
+ workspace_id: selectedWorkspaceId,
102
+ code: params.code,
103
+ installation_id: params.installationId
104
+ },
105
+ token
106
+ })
107
+ }));
108
108
  request.then(async ()=>{
109
- clearSentryInstallWorkspace(window.sessionStorage);
110
- await queryClient.invalidateQueries({
111
- queryKey: integrationsQueryKeys.connectionsByWorkspace(workspaceId)
112
- });
109
+ clearSentryInstallWorkspace(sessionStorageOrUndefined());
113
110
  if (disposedRef.current) return;
114
111
  toast.success('Sentry installed.');
115
112
  await navigate({
@@ -125,7 +122,7 @@ export function SentryCallbackPage() {
125
122
  if (classified.kind === 'terminal') {
126
123
  // Only a fresh install (new grant code) can recover; the stored
127
124
  // handoff has served its purpose either way.
128
- clearSentryInstallWorkspace(window.sessionStorage);
125
+ clearSentryInstallWorkspace(sessionStorageOrUndefined());
129
126
  }
130
127
  setConnectingId(undefined);
131
128
  setFailure({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/sentry-callback-page.tsx"],"sourcesContent":["import type {SentryConnectResponseDto} from '@shipfox/api-integration-sentry-dto';\nimport {useAuthState, useRefreshAuth} from '@shipfox/client-auth';\nimport {createSingleFlight} 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, useSearch} from '@tanstack/react-router';\nimport {useEffect, useMemo, useRef, useState} from 'react';\nimport {connectSentry, integrationsQueryKeys} 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, SentryConnectResponseDto>();\n\nexport function SentryCallbackPage() {\n const search = useSearch({strict: false});\n const navigate = useNavigate();\n const refreshAuth = useRefreshAuth();\n const {workspaces, isLoading} = useAuthState();\n const queryClient = useQueryClient();\n\n const params = useMemo(() => parseSentryCallbackParams(search), [search]);\n const storedWorkspaceId = useMemo(() => readSentryInstallWorkspace(window.sessionStorage), []);\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 refreshAuth().then((session) =>\n connectSentry({\n body: {\n workspace_id: workspaceId,\n code: params.code,\n installation_id: params.installationId,\n },\n token: session.accessToken,\n }),\n ),\n );\n\n request\n .then(async () => {\n clearSentryInstallWorkspace(window.sessionStorage);\n await queryClient.invalidateQueries({\n queryKey: integrationsQueryKeys.connectionsByWorkspace(workspaceId),\n });\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(window.sessionStorage);\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","createSingleFlight","Button","ButtonLink","Callout","Card","FullPageLoader","toast","Header","Text","useQueryClient","Link","useNavigate","useSearch","useEffect","useMemo","useRef","useState","connectSentry","integrationsQueryKeys","classifySentryConnectError","clearSentryInstallWorkspace","parseSentryCallbackParams","preselectSentryWorkspace","readSentryInstallWorkspace","connectRequests","SentryCallbackPage","search","strict","navigate","refreshAuth","workspaces","isLoading","queryClient","params","storedWorkspaceId","window","sessionStorage","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","then","session","body","workspace_id","installation_id","token","accessToken","invalidateQueries","queryKey","connectionsByWorkspace","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 {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":";AAAA,SAAQA,YAAY,EAAEC,cAAc,QAAO,uBAAuB;AAClE,SAAQC,cAAc,QAAO,gCAAgC;AAC7D,SAAQC,kBAAkB,EAAEC,yBAAyB,QAAO,qBAAqB;AACjF,SAAQC,MAAM,EAAEC,UAAU,QAAO,2BAA2B;AAC5D,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,cAAc,QAAO,2BAA2B;AACxD,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAC1D,SAAQC,IAAI,EAAEC,WAAW,QAAO,yBAAyB;AACzD,SAAQC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAC3D,SAAQC,8BAA8B,QAAO,gDAAgD;AAE7F,SAAQC,aAAa,QAAO,6BAA6B;AACzD,SACEC,0BAA0B,EAC1BC,2BAA2B,EAC3BC,yBAAyB,EACzBC,wBAAwB,EACxBC,0BAA0B,QAErB,sBAAsB;AAE7B,MAAMC,kBAAkBvB;AAExB,OAAO,SAASwB;IACd,MAAMC,WAAWd;IACjB,MAAMe,cAAc5B;IACpB,MAAM,EAAC6B,UAAU,EAAEC,SAAS,EAAC,GAAG/B;IAChC,MAAMgC,8BAA8Bb;IAEpC,MAAMc,SAAS/B,eAAeqB;IAC9B,MAAMW,oBAAoBlB,QACxB,IAAMS,2BAA2BrB,8BACjC,EAAE;IAEJ,MAAM+B,eAAeX,yBAAyBU,mBAAmBJ;IACjE,MAAMM,gBAAgBD,aAAaE,IAAI,KAAK,SAASF,aAAaC,aAAa,GAAGE;IAElF,MAAM,CAACC,cAAcC,gBAAgB,GAAGtB;IACxC,MAAM,CAACuB,SAASC,WAAW,GAAGxB;IAG9B,MAAM,CAACyB,aAAaC,eAAe,GAAG1B,SAAS;IAC/C,MAAM2B,cAAc5B,OAAO;IAE3BF,UAAU;QACR8B,YAAYC,OAAO,GAAG;QACtB,OAAO;YACLD,YAAYC,OAAO,GAAG;QACxB;IACF,GAAG,EAAE;IAEL,8EAA8E;IAC9E,0EAA0E;IAC1E,4EAA4E;IAC5E,MAAMC,cAAc,CAAChB,aAAaI,aAAaE,IAAI,KAAK;IACxDtB,UAAU;QACR,IAAI,CAACgC,aAAa;QAClBrC,MAAMsC,KAAK,CAAC;QACZpB,SAAS;YAACqB,IAAI;YAAKC,SAAS;QAAI;IAClC,GAAG;QAACH;QAAanB;KAAS;IAE1B,MAAMuB,oBACJV,SAASA,QAAQJ,SAAS,cAAcI,QAAQA,OAAO,CAACU,iBAAiB,GAAGb;IAC9EvB,UAAU;QACR,0EAA0E;QAC1E,yEAAyE;QACzE,IAAI,CAACoC,mBAAmB;YACtBP,eAAe;YACf;QACF;QACAA,eAAe;QACf,MAAMQ,QAAQC,WAAW,IAAMT,eAAe,QAAQO,oBAAoB;QAC1E,OAAO,IAAMG,aAAaF;IAC5B,GAAG;QAACD;KAAkB;IAEtB,IAAI,CAAClB,QAAQ;QACX,qBACE,MAACsB;;8BACC,KAAChD;oBAAQiD,MAAK;oBAAQC,MAAK;8BACzB,cAAA,KAAC7C;wBAAK8C,MAAK;kCAAK;;;8BAKlB,KAACpD;oBAAWqD,OAAO;oBAACC,SAAQ;oBAAQC,WAAU;8BAC5C,cAAA,KAAChD;wBAAKoC,IAAG;kCAAI;;;;;IAIrB;IAEA,kEAAkE;IAClE,8DAA8D;IAC9D,IAAIlB,WAAW,qBAAO,KAACtB;IAEvB,IAAIsC,aAAa,OAAO;IAExB,MAAMe,UAAU,CAACC;QACfvB,gBAAgBuB;QAChBrB,WAAWJ;QACX,MAAM0B,MAAM,GAAG/B,OAAOgC,cAAc,CAAC,CAAC,EAAEF,YAAY,CAAC,EAAE9B,OAAOiC,IAAI,EAAE;QACpE,MAAMC,UAAUzC,gBAAgB0C,GAAG,CACjCJ,KACA,UACE,MAAMhC,4BAA4B;gBAChCqC,OAAON;gBACPlC;gBACAyC,UAAU,OAAOC,qBAAqBC,QACpC,MAAMpD,cAAc;wBAClBqD,MAAM;4BACJC,cAAcH;4BACdL,MAAMjC,OAAOiC,IAAI;4BACjBS,iBAAiB1C,OAAOgC,cAAc;wBACxC;wBACAO;oBACF;YACJ;QAGJL,QACGS,IAAI,CAAC;YACJtD,4BAA4BlB;YAC5B,IAAIyC,YAAYC,OAAO,EAAE;YACzBpC,MAAMmE,OAAO,CAAC;YACd,MAAMjD,SAAS;gBACbqB,IAAI;gBACJhB,QAAQ;oBAAC6C,KAAKf;gBAAW;gBACzBb,SAAS;YACX;QACF,GACC6B,KAAK,CAAC,CAAC/B;YACN,IAAIH,YAAYC,OAAO,EAAE;YACzB,MAAMkC,aAAa3D,2BAA2B2B;YAC9C,IAAIgC,WAAW3C,IAAI,KAAK,YAAY;gBAClC,gEAAgE;gBAChE,6CAA6C;gBAC7Cf,4BAA4BlB;YAC9B;YACAoC,gBAAgBF;YAChBI,WAAW;gBAACqB;gBAAatB,SAASuC;YAAU;QAC9C;IACJ;IAEA,MAAMC,oBAAoB7C,gBACtB;WACKN,WAAWoD,MAAM,CAAC,CAACC,YAAcA,UAAUC,EAAE,KAAKhD;WAClDN,WAAWoD,MAAM,CAAC,CAACC,YAAcA,UAAUC,EAAE,KAAKhD;KACtD,GACDN;IAEJ,MAAMuD,qBAAqB5C,SAASsB,eAAe3B,iBAAiBN,UAAU,CAAC,EAAE,EAAEsD;IAEnF,qBACE,MAAC7B;;0BACC,MAAC+B;gBAAOzB,WAAU;;kCAChB,KAAClD;wBAAOiD,SAAQ;kCAAK;;kCACrB,KAAChD;wBAAK8C,MAAK;wBAAKG,WAAU;kCACvB5B,OAAOsD,OAAO,GACX,CAAC,wBAAwB,EAAEtD,OAAOsD,OAAO,CAAC,iBAAiB,CAAC,GAC5D;;;;YAIP9C,wBACC,KAAClC;gBAAQiD,MAAK;gBAAQC,MAAK;0BACzB,cAAA,MAAC+B;oBAAI3B,WAAU;;sCACb,KAACjD;4BAAK8C,MAAK;sCAAMjB,QAAQA,OAAO,CAACgD,OAAO;;wBACvChD,QAAQA,OAAO,CAACJ,IAAI,KAAK,4BACxB,KAAChC;4BACCqD,MAAK;4BACLE,SAAQ;4BACRC,WAAU;4BACV6B,UAAU/C,eAAeJ,iBAAiBD;4BAC1CqD,SAAS,IAAM7B,QAAQrB,QAAQsB,WAAW;sCAC3C;6BAGC;wBACHtB,QAAQA,OAAO,CAACJ,IAAI,KAAK,cAAcI,QAAQA,OAAO,CAACmD,SAAS,iBAC/D,KAACvF;4BAAOsD,OAAO;4BAACD,MAAK;4BAAKE,SAAQ;4BAAYC,WAAU;sCACtD,cAAA,KAAChD;gCAAKoC,IAAG;gCAAuChB,QAAQ;oCAAC6C,KAAKrC,QAAQsB,WAAW;gCAAA;0CAAG;;6BAIpF;;;iBAGN;0BAEJ,KAAC8B;gBAAQhC,WAAU;gBAAsBiC,cAAW;0BACjDb,kBAAkBc,GAAG,CAAC,CAACZ,0BACtB,KAAC3E;wBAAwBqD,WAAU;kCACjC,cAAA,MAAC2B;4BAAI3B,WAAU;;8CACb,KAACjD;oCAAK8C,MAAK;oCAAKsC,IAAI;oCAACnC,WAAU;8CAC5BsB,UAAUc,IAAI;;8CAEjB,KAAC5F;oCACCuD,SAAQ;oCACR8B,UAAUnD,iBAAiBD;oCAC3BP,WAAWQ,iBAAiB4C,UAAUC,EAAE;oCACxCO,SAAS,IAAM7B,QAAQqB,UAAUC,EAAE;8CACpC;;;;uBAVMD,UAAUC,EAAE;;YAkB1BC,mCACC,KAAC/E;gBAAWqD,OAAO;gBAACC,SAAQ;gBAAQC,WAAU;0BAC5C,cAAA,KAAChD;oBAAKoC,IAAG;oBAAyChB,QAAQ;wBAAC6C,KAAKO;oBAAkB;8BAAG;;iBAIrF;;;AAGV;AAEA,SAAS9B,eAAe,EAAC2C,QAAQ,EAA8B;IAC7D,qBACE,KAACC;QAAKtC,WAAU;kBACd,cAAA,KAAC2B;YAAI3B,WAAU;sBACZqC;;;AAIT"}
@@ -1 +1 @@
1
- {"version":3,"file":"sentry-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/sentry-install-page.tsx"],"names":[],"mappings":"AAIA,wBAAgB,iBAAiB,gCAahC"}
1
+ {"version":3,"file":"sentry-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/sentry-install-page.tsx"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,gCAahC"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { sessionStorageOrUndefined } from '@shipfox/client-ui';
2
3
  import { RedirectInstallPage } from '#components/redirect-install-page.js';
3
4
  import { createSentryInstall } from '#hooks/api/integrations.js';
4
5
  import { saveSentryInstallWorkspace } from '#sentry-callback.js';
@@ -10,7 +11,7 @@ export function SentryInstallPage() {
10
11
  // Sentry's redirect has no state param; the stored id lets the callback
11
12
  // pre-select this workspace. Saving never throws (see helper).
12
13
  beforeRedirect: (workspaceId)=>{
13
- saveSentryInstallWorkspace(window.sessionStorage, workspaceId);
14
+ saveSentryInstallWorkspace(sessionStorageOrUndefined(), workspaceId);
14
15
  }
15
16
  });
16
17
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/sentry-install-page.tsx"],"sourcesContent":["import {RedirectInstallPage} from '#components/redirect-install-page.js';\nimport {createSentryInstall} from '#hooks/api/integrations.js';\nimport {saveSentryInstallWorkspace} from '#sentry-callback.js';\n\nexport function SentryInstallPage() {\n return (\n <RedirectInstallPage\n installRequest={createSentryInstall}\n errorFallbackMessage=\"Could not start Sentry install.\"\n loadingLabel=\"Connecting Sentry\"\n // Sentry's redirect has no state param; the stored id lets the callback\n // pre-select this workspace. Saving never throws (see helper).\n beforeRedirect={(workspaceId) => {\n saveSentryInstallWorkspace(window.sessionStorage, workspaceId);\n }}\n />\n );\n}\n"],"names":["RedirectInstallPage","createSentryInstall","saveSentryInstallWorkspace","SentryInstallPage","installRequest","errorFallbackMessage","loadingLabel","beforeRedirect","workspaceId","window","sessionStorage"],"mappings":";AAAA,SAAQA,mBAAmB,QAAO,uCAAuC;AACzE,SAAQC,mBAAmB,QAAO,6BAA6B;AAC/D,SAAQC,0BAA0B,QAAO,sBAAsB;AAE/D,OAAO,SAASC;IACd,qBACE,KAACH;QACCI,gBAAgBH;QAChBI,sBAAqB;QACrBC,cAAa;QACb,wEAAwE;QACxE,+DAA+D;QAC/DC,gBAAgB,CAACC;YACfN,2BAA2BO,OAAOC,cAAc,EAAEF;QACpD;;AAGN"}
1
+ {"version":3,"sources":["../../src/pages/sentry-install-page.tsx"],"sourcesContent":["import {sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {RedirectInstallPage} from '#components/redirect-install-page.js';\nimport {createSentryInstall} from '#hooks/api/integrations.js';\nimport {saveSentryInstallWorkspace} from '#sentry-callback.js';\n\nexport function SentryInstallPage() {\n return (\n <RedirectInstallPage\n installRequest={createSentryInstall}\n errorFallbackMessage=\"Could not start Sentry install.\"\n loadingLabel=\"Connecting Sentry\"\n // Sentry's redirect has no state param; the stored id lets the callback\n // pre-select this workspace. Saving never throws (see helper).\n beforeRedirect={(workspaceId) => {\n saveSentryInstallWorkspace(sessionStorageOrUndefined(), workspaceId);\n }}\n />\n );\n}\n"],"names":["sessionStorageOrUndefined","RedirectInstallPage","createSentryInstall","saveSentryInstallWorkspace","SentryInstallPage","installRequest","errorFallbackMessage","loadingLabel","beforeRedirect","workspaceId"],"mappings":";AAAA,SAAQA,yBAAyB,QAAO,qBAAqB;AAC7D,SAAQC,mBAAmB,QAAO,uCAAuC;AACzE,SAAQC,mBAAmB,QAAO,6BAA6B;AAC/D,SAAQC,0BAA0B,QAAO,sBAAsB;AAE/D,OAAO,SAASC;IACd,qBACE,KAACH;QACCI,gBAAgBH;QAChBI,sBAAqB;QACrBC,cAAa;QACb,wEAAwE;QACxE,+DAA+D;QAC/DC,gBAAgB,CAACC;YACfN,2BAA2BH,6BAA6BS;QAC1D;;AAGN"}
@@ -1 +1 @@
1
- {"version":3,"file":"slack-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/slack-callback-page.tsx"],"names":[],"mappings":"AA2BA,wBAAgB,iBAAiB,gCAgGhC"}
1
+ {"version":3,"file":"slack-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/slack-callback-page.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,iBAAiB,gCAyFhC"}