@shipfox/client-integrations 1.0.0 → 2.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 (78) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +27 -0
  3. package/dist/components/callback-status-shell.d.ts +9 -0
  4. package/dist/components/callback-status-shell.d.ts.map +1 -0
  5. package/dist/components/callback-status-shell.js +81 -0
  6. package/dist/components/callback-status-shell.js.map +1 -0
  7. package/dist/components/integration-gallery.stories.d.ts.map +1 -1
  8. package/dist/components/integration-gallery.stories.js +28 -0
  9. package/dist/components/integration-gallery.stories.js.map +1 -1
  10. package/dist/components/redirect-install-page.d.ts +2 -1
  11. package/dist/components/redirect-install-page.d.ts.map +1 -1
  12. package/dist/components/redirect-install-page.js +4 -2
  13. package/dist/components/redirect-install-page.js.map +1 -1
  14. package/dist/feature.d.ts +8 -0
  15. package/dist/feature.d.ts.map +1 -1
  16. package/dist/feature.js +10 -0
  17. package/dist/feature.js.map +1 -1
  18. package/dist/hooks/api/integrations.d.ts +41 -0
  19. package/dist/hooks/api/integrations.d.ts.map +1 -1
  20. package/dist/hooks/api/integrations.js +24 -0
  21. package/dist/hooks/api/integrations.js.map +1 -1
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/pages/linear-callback-page.d.ts.map +1 -1
  27. package/dist/pages/linear-callback-page.js +11 -78
  28. package/dist/pages/linear-callback-page.js.map +1 -1
  29. package/dist/pages/linear-install-page.d.ts.map +1 -1
  30. package/dist/pages/linear-install-page.js +1 -0
  31. package/dist/pages/linear-install-page.js.map +1 -1
  32. package/dist/pages/sentry-install-page.d.ts.map +1 -1
  33. package/dist/pages/sentry-install-page.js +1 -0
  34. package/dist/pages/sentry-install-page.js.map +1 -1
  35. package/dist/pages/slack-callback-page.d.ts +2 -0
  36. package/dist/pages/slack-callback-page.d.ts.map +1 -0
  37. package/dist/pages/slack-callback-page.js +114 -0
  38. package/dist/pages/slack-callback-page.js.map +1 -0
  39. package/dist/pages/slack-install-page.d.ts +2 -0
  40. package/dist/pages/slack-install-page.d.ts.map +1 -0
  41. package/dist/pages/slack-install-page.js +19 -0
  42. package/dist/pages/slack-install-page.js.map +1 -0
  43. package/dist/provider-catalog.d.ts +1 -1
  44. package/dist/provider-catalog.d.ts.map +1 -1
  45. package/dist/provider-catalog.js +5 -0
  46. package/dist/provider-catalog.js.map +1 -1
  47. package/dist/routes/slack-callback.d.ts +6 -0
  48. package/dist/routes/slack-callback.d.ts.map +1 -0
  49. package/dist/routes/slack-callback.js +7 -0
  50. package/dist/routes/slack-callback.js.map +1 -0
  51. package/dist/routes/slack.d.ts +6 -0
  52. package/dist/routes/slack.d.ts.map +1 -0
  53. package/dist/routes/slack.js +7 -0
  54. package/dist/routes/slack.js.map +1 -0
  55. package/dist/slack-callback.d.ts +17 -0
  56. package/dist/slack-callback.d.ts.map +1 -0
  57. package/dist/slack-callback.js +99 -0
  58. package/dist/slack-callback.js.map +1 -0
  59. package/dist/tsconfig.test.tsbuildinfo +1 -1
  60. package/package.json +13 -12
  61. package/src/components/callback-status-shell.tsx +73 -0
  62. package/src/components/integration-gallery.stories.tsx +18 -0
  63. package/src/components/redirect-install-page.tsx +3 -1
  64. package/src/feature.ts +10 -0
  65. package/src/hooks/api/integrations.test.ts +30 -0
  66. package/src/hooks/api/integrations.ts +35 -0
  67. package/src/index.ts +3 -0
  68. package/src/pages/linear-callback-page.tsx +11 -52
  69. package/src/pages/linear-install-page.tsx +1 -0
  70. package/src/pages/sentry-install-page.tsx +1 -0
  71. package/src/pages/slack-callback-page.tsx +120 -0
  72. package/src/pages/slack-install-page.tsx +22 -0
  73. package/src/provider-catalog.ts +7 -1
  74. package/src/routes/slack-callback.tsx +3 -0
  75. package/src/routes/slack.tsx +3 -0
  76. package/src/slack-callback.test.ts +32 -0
  77. package/src/slack-callback.ts +141 -0
  78. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/integrations.ts"],"sourcesContent":["import type {\n IntegrationCapabilityDto,\n IntegrationConnectionDto,\n ListIntegrationConnectionsResponseDto,\n ListIntegrationProvidersResponseDto,\n ListRepositoriesResponseDto,\n UpdateIntegrationConnectionBodyDto,\n} from '@shipfox/api-integration-core-dto';\nimport type {\n CreateGiteaConnectionBodyDto,\n CreateGiteaConnectionResponseDto,\n} from '@shipfox/api-integration-gitea-dto';\nimport type {\n CreateGithubInstallBodyDto,\n CreateGithubInstallResponseDto,\n} from '@shipfox/api-integration-github-dto';\nimport type {\n CreateLinearInstallBodyDto,\n CreateLinearInstallResponseDto,\n LinearCallbackQueryDto,\n LinearCallbackResponseDto,\n} from '@shipfox/api-integration-linear-dto';\nimport type {\n CreateSentryInstallBodyDto,\n CreateSentryInstallResponseDto,\n SentryConnectBodyDto,\n SentryConnectResponseDto,\n} from '@shipfox/api-integration-sentry-dto';\nimport {apiRequest} from '@shipfox/client-api';\nimport {useInfiniteQuery, useMutation, useQuery, useQueryClient} from '@tanstack/react-query';\nimport {serializeLinearCallbackQuery} from '#linear-callback.js';\n\nexport const integrationsQueryKeys = {\n all: ['integrations'] as const,\n providers: (capability: IntegrationCapabilityDto | 'all') =>\n [...integrationsQueryKeys.all, 'providers', capability] as const,\n connections: (workspaceId: string, capability: IntegrationCapabilityDto | 'all') =>\n [...integrationsQueryKeys.all, 'connections', workspaceId, capability] as const,\n // Prefix matching every per-capability connections query for a workspace, so\n // a mutation can refresh all connection views without touching providers or\n // repositories.\n connectionsByWorkspace: (workspaceId: string) =>\n [...integrationsQueryKeys.all, 'connections', workspaceId] as const,\n sourceConnections: (workspaceId: string) =>\n integrationsQueryKeys.connections(workspaceId, 'source_control'),\n repositories: (connectionId: string, search: string) =>\n [...integrationsQueryKeys.all, 'repositories', connectionId, search] as const,\n};\n\nexport async function listIntegrationProviders({\n capability,\n signal,\n}: {\n capability?: IntegrationCapabilityDto;\n signal?: AbortSignal;\n}) {\n const search = new URLSearchParams();\n if (capability) search.set('capability', capability);\n const query = search.toString();\n const path = query ? `/integration-providers?${query}` : '/integration-providers';\n return await apiRequest<ListIntegrationProvidersResponseDto>(path, {signal});\n}\n\nexport async function listIntegrationConnections({\n workspaceId,\n capability,\n signal,\n}: {\n workspaceId: string;\n capability?: IntegrationCapabilityDto | undefined;\n signal?: AbortSignal | undefined;\n}) {\n const search = new URLSearchParams({workspace_id: workspaceId});\n if (capability) search.set('capability', capability);\n return await apiRequest<ListIntegrationConnectionsResponseDto>(\n `/integration-connections?${search.toString()}`,\n {signal},\n );\n}\n\nexport async function listSourceConnections({\n workspaceId,\n signal,\n}: {\n workspaceId: string;\n signal?: AbortSignal;\n}) {\n const result = await listIntegrationConnections({\n workspaceId,\n capability: 'source_control',\n signal,\n });\n // The endpoint returns every lifecycle status (the settings hub needs that),\n // but source-control consumers (onboarding redirect, project creation) only\n // act on usable connections — a disabled/error one must read as \"not there\".\n return {\n ...result,\n connections: result.connections.filter(\n (connection) => connection.lifecycle_status === 'active',\n ),\n };\n}\n\nexport async function createGiteaConnection(body: CreateGiteaConnectionBodyDto) {\n return await apiRequest<CreateGiteaConnectionResponseDto>('/integrations/gitea/connections', {\n method: 'POST',\n body,\n });\n}\n\nexport async function createGithubInstall(body: CreateGithubInstallBodyDto) {\n return await apiRequest<CreateGithubInstallResponseDto>('/integrations/github/install', {\n method: 'POST',\n body,\n });\n}\n\nexport async function createSentryInstall(body: CreateSentryInstallBodyDto) {\n return await apiRequest<CreateSentryInstallResponseDto>('/integrations/sentry/install', {\n method: 'POST',\n body,\n });\n}\n\nexport async function createLinearInstall(body: CreateLinearInstallBodyDto) {\n return await apiRequest<CreateLinearInstallResponseDto>('/integrations/linear/install', {\n method: 'POST',\n body,\n });\n}\n\nexport async function completeLinearCallback({\n query,\n token,\n}: {\n query: LinearCallbackQueryDto;\n token: string;\n}) {\n return await apiRequest<LinearCallbackResponseDto>(\n `/integrations/linear/callback/api?${serializeLinearCallbackQuery(query)}`,\n {headers: {authorization: `Bearer ${token}`}},\n );\n}\n\n// Called from the callback route with an explicit bearer (same as the GitHub\n// callback): the route refreshes auth itself before forwarding the grant code.\nexport async function connectSentry({body, token}: {body: SentryConnectBodyDto; token: string}) {\n return await apiRequest<SentryConnectResponseDto>('/integrations/sentry/connect', {\n method: 'POST',\n body,\n headers: {authorization: `Bearer ${token}`},\n });\n}\n\nexport async function listRepositories({\n connectionId,\n cursor,\n search,\n signal,\n}: {\n connectionId: string;\n cursor?: string;\n search?: string;\n signal?: AbortSignal;\n}) {\n const params = new URLSearchParams();\n if (cursor) params.set('cursor', cursor);\n if (search) params.set('search', search);\n const query = params.toString();\n const path = query\n ? `/integration-connections/${connectionId}/repositories?${query}`\n : `/integration-connections/${connectionId}/repositories`;\n return await apiRequest<ListRepositoriesResponseDto>(path, {signal});\n}\n\nexport async function updateIntegrationConnection({\n connectionId,\n body,\n}: {\n connectionId: string;\n body: UpdateIntegrationConnectionBodyDto;\n}) {\n return await apiRequest<IntegrationConnectionDto>(\n `/integration-connections/${encodeURIComponent(connectionId)}`,\n {method: 'PATCH', body},\n );\n}\n\nexport async function deleteIntegrationConnection({connectionId}: {connectionId: string}) {\n await apiRequest<void>(`/integration-connections/${encodeURIComponent(connectionId)}`, {\n method: 'DELETE',\n });\n}\n\nexport function useIntegrationProvidersQuery(params?: {capability?: IntegrationCapabilityDto}) {\n const capability = params?.capability;\n return useQuery({\n queryKey: integrationsQueryKeys.providers(capability ?? 'all'),\n queryFn: ({signal}) => listIntegrationProviders(capability ? {capability, signal} : {signal}),\n });\n}\n\nexport function useSourceConnectionsQuery(workspaceId: string | undefined) {\n return useQuery({\n queryKey: workspaceId\n ? integrationsQueryKeys.sourceConnections(workspaceId)\n : [...integrationsQueryKeys.all, 'source-connections'],\n enabled: Boolean(workspaceId),\n queryFn: ({signal}) => listSourceConnections({workspaceId: workspaceId ?? '', signal}),\n });\n}\n\nexport function useIntegrationConnectionsQuery(workspaceId: string | undefined) {\n return useQuery({\n queryKey: workspaceId\n ? integrationsQueryKeys.connections(workspaceId, 'all')\n : [...integrationsQueryKeys.all, 'connections'],\n enabled: Boolean(workspaceId),\n queryFn: ({signal}) => listIntegrationConnections({workspaceId: workspaceId ?? '', signal}),\n });\n}\n\nexport function useRepositoriesInfiniteQuery(\n connectionId: string | undefined,\n options?: {search?: string},\n) {\n const trimmedSearch = options?.search?.trim() ?? '';\n return useInfiniteQuery({\n queryKey: connectionId\n ? integrationsQueryKeys.repositories(connectionId, trimmedSearch)\n : [...integrationsQueryKeys.all, 'repositories'],\n enabled: Boolean(connectionId),\n initialPageParam: undefined as string | undefined,\n queryFn: ({pageParam, signal}) => {\n const args: {connectionId: string; cursor?: string; search?: string; signal: AbortSignal} = {\n connectionId: connectionId ?? '',\n signal,\n };\n if (pageParam) args.cursor = pageParam;\n if (trimmedSearch) args.search = trimmedSearch;\n return listRepositories(args);\n },\n getNextPageParam: (lastPage) => lastPage.next_cursor ?? undefined,\n });\n}\n\nexport function useCreateGiteaConnectionMutation() {\n return useMutation({mutationFn: createGiteaConnection});\n}\n\nexport function useCreateLinearInstallMutation() {\n return useMutation({mutationFn: createLinearInstall});\n}\n\nexport function useCompleteLinearCallbackMutation() {\n return useMutation({mutationFn: completeLinearCallback});\n}\n\nexport function useUpdateIntegrationConnectionMutation() {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: updateIntegrationConnection,\n onSuccess: async (connection) => {\n await queryClient.invalidateQueries({\n queryKey: integrationsQueryKeys.connectionsByWorkspace(connection.workspace_id),\n });\n },\n });\n}\n\nexport function useDeleteIntegrationConnectionMutation() {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: ({\n workspaceId: _workspaceId,\n connectionId,\n }: {\n workspaceId: string;\n connectionId: string;\n }) => deleteIntegrationConnection({connectionId}),\n onSuccess: async (_result, variables) => {\n await queryClient.invalidateQueries({\n queryKey: integrationsQueryKeys.connectionsByWorkspace(variables.workspaceId),\n });\n },\n });\n}\n"],"names":["apiRequest","useInfiniteQuery","useMutation","useQuery","useQueryClient","serializeLinearCallbackQuery","integrationsQueryKeys","all","providers","capability","connections","workspaceId","connectionsByWorkspace","sourceConnections","repositories","connectionId","search","listIntegrationProviders","signal","URLSearchParams","set","query","toString","path","listIntegrationConnections","workspace_id","listSourceConnections","result","filter","connection","lifecycle_status","createGiteaConnection","body","method","createGithubInstall","createSentryInstall","createLinearInstall","completeLinearCallback","token","headers","authorization","connectSentry","listRepositories","cursor","params","updateIntegrationConnection","encodeURIComponent","deleteIntegrationConnection","useIntegrationProvidersQuery","queryKey","queryFn","useSourceConnectionsQuery","enabled","Boolean","useIntegrationConnectionsQuery","useRepositoriesInfiniteQuery","options","trimmedSearch","trim","initialPageParam","undefined","pageParam","args","getNextPageParam","lastPage","next_cursor","useCreateGiteaConnectionMutation","mutationFn","useCreateLinearInstallMutation","useCompleteLinearCallbackMutation","useUpdateIntegrationConnectionMutation","queryClient","onSuccess","invalidateQueries","useDeleteIntegrationConnectionMutation","_workspaceId","_result","variables"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/hooks/api/integrations.ts"],"sourcesContent":["import type {\n IntegrationCapabilityDto,\n IntegrationConnectionDto,\n ListIntegrationConnectionsResponseDto,\n ListIntegrationProvidersResponseDto,\n ListRepositoriesResponseDto,\n UpdateIntegrationConnectionBodyDto,\n} from '@shipfox/api-integration-core-dto';\nimport type {\n CreateGiteaConnectionBodyDto,\n CreateGiteaConnectionResponseDto,\n} from '@shipfox/api-integration-gitea-dto';\nimport type {\n CreateGithubInstallBodyDto,\n CreateGithubInstallResponseDto,\n} from '@shipfox/api-integration-github-dto';\nimport type {\n CreateLinearInstallBodyDto,\n CreateLinearInstallResponseDto,\n LinearCallbackQueryDto,\n LinearCallbackResponseDto,\n} from '@shipfox/api-integration-linear-dto';\nimport type {\n CreateSentryInstallBodyDto,\n CreateSentryInstallResponseDto,\n SentryConnectBodyDto,\n SentryConnectResponseDto,\n} from '@shipfox/api-integration-sentry-dto';\nimport type {\n CreateSlackInstallBodyDto,\n CreateSlackInstallResponseDto,\n SlackCallbackQueryDto,\n SlackCallbackResponseDto,\n} from '@shipfox/api-integration-slack-dto';\nimport {apiRequest} from '@shipfox/client-api';\nimport {useInfiniteQuery, useMutation, useQuery, useQueryClient} from '@tanstack/react-query';\nimport {serializeLinearCallbackQuery} from '#linear-callback.js';\nimport {serializeSlackCallbackQuery} from '#slack-callback.js';\n\nexport const integrationsQueryKeys = {\n all: ['integrations'] as const,\n providers: (capability: IntegrationCapabilityDto | 'all') =>\n [...integrationsQueryKeys.all, 'providers', capability] as const,\n connections: (workspaceId: string, capability: IntegrationCapabilityDto | 'all') =>\n [...integrationsQueryKeys.all, 'connections', workspaceId, capability] as const,\n // Prefix matching every per-capability connections query for a workspace, so\n // a mutation can refresh all connection views without touching providers or\n // repositories.\n connectionsByWorkspace: (workspaceId: string) =>\n [...integrationsQueryKeys.all, 'connections', workspaceId] as const,\n sourceConnections: (workspaceId: string) =>\n integrationsQueryKeys.connections(workspaceId, 'source_control'),\n repositories: (connectionId: string, search: string) =>\n [...integrationsQueryKeys.all, 'repositories', connectionId, search] as const,\n};\n\nexport async function listIntegrationProviders({\n capability,\n signal,\n}: {\n capability?: IntegrationCapabilityDto;\n signal?: AbortSignal;\n}) {\n const search = new URLSearchParams();\n if (capability) search.set('capability', capability);\n const query = search.toString();\n const path = query ? `/integration-providers?${query}` : '/integration-providers';\n return await apiRequest<ListIntegrationProvidersResponseDto>(path, {signal});\n}\n\nexport async function listIntegrationConnections({\n workspaceId,\n capability,\n signal,\n}: {\n workspaceId: string;\n capability?: IntegrationCapabilityDto | undefined;\n signal?: AbortSignal | undefined;\n}) {\n const search = new URLSearchParams({workspace_id: workspaceId});\n if (capability) search.set('capability', capability);\n return await apiRequest<ListIntegrationConnectionsResponseDto>(\n `/integration-connections?${search.toString()}`,\n {signal},\n );\n}\n\nexport async function listSourceConnections({\n workspaceId,\n signal,\n}: {\n workspaceId: string;\n signal?: AbortSignal;\n}) {\n const result = await listIntegrationConnections({\n workspaceId,\n capability: 'source_control',\n signal,\n });\n // The endpoint returns every lifecycle status (the settings hub needs that),\n // but source-control consumers (onboarding redirect, project creation) only\n // act on usable connections — a disabled/error one must read as \"not there\".\n return {\n ...result,\n connections: result.connections.filter(\n (connection) => connection.lifecycle_status === 'active',\n ),\n };\n}\n\nexport async function createGiteaConnection(body: CreateGiteaConnectionBodyDto) {\n return await apiRequest<CreateGiteaConnectionResponseDto>('/integrations/gitea/connections', {\n method: 'POST',\n body,\n });\n}\n\nexport async function createGithubInstall(body: CreateGithubInstallBodyDto) {\n return await apiRequest<CreateGithubInstallResponseDto>('/integrations/github/install', {\n method: 'POST',\n body,\n });\n}\n\nexport async function createSentryInstall(body: CreateSentryInstallBodyDto) {\n return await apiRequest<CreateSentryInstallResponseDto>('/integrations/sentry/install', {\n method: 'POST',\n body,\n });\n}\n\nexport async function createLinearInstall(body: CreateLinearInstallBodyDto) {\n return await apiRequest<CreateLinearInstallResponseDto>('/integrations/linear/install', {\n method: 'POST',\n body,\n });\n}\n\nexport async function createSlackInstall(body: CreateSlackInstallBodyDto) {\n return await apiRequest<CreateSlackInstallResponseDto>('/integrations/slack/install', {\n method: 'POST',\n body,\n });\n}\n\nexport async function completeLinearCallback({\n query,\n token,\n}: {\n query: LinearCallbackQueryDto;\n token: string;\n}) {\n return await apiRequest<LinearCallbackResponseDto>(\n `/integrations/linear/callback/api?${serializeLinearCallbackQuery(query)}`,\n {headers: {authorization: `Bearer ${token}`}},\n );\n}\n\nexport async function completeSlackCallback({\n query,\n token,\n}: {\n query: SlackCallbackQueryDto;\n token: string;\n}) {\n return await apiRequest<SlackCallbackResponseDto>(\n `/integrations/slack/callback/api?${serializeSlackCallbackQuery(query)}`,\n {headers: {authorization: `Bearer ${token}`}},\n );\n}\n\n// Called from the callback route with an explicit bearer (same as the GitHub\n// callback): the route refreshes auth itself before forwarding the grant code.\nexport async function connectSentry({body, token}: {body: SentryConnectBodyDto; token: string}) {\n return await apiRequest<SentryConnectResponseDto>('/integrations/sentry/connect', {\n method: 'POST',\n body,\n headers: {authorization: `Bearer ${token}`},\n });\n}\n\nexport async function listRepositories({\n connectionId,\n cursor,\n search,\n signal,\n}: {\n connectionId: string;\n cursor?: string;\n search?: string;\n signal?: AbortSignal;\n}) {\n const params = new URLSearchParams();\n if (cursor) params.set('cursor', cursor);\n if (search) params.set('search', search);\n const query = params.toString();\n const path = query\n ? `/integration-connections/${connectionId}/repositories?${query}`\n : `/integration-connections/${connectionId}/repositories`;\n return await apiRequest<ListRepositoriesResponseDto>(path, {signal});\n}\n\nexport async function updateIntegrationConnection({\n connectionId,\n body,\n}: {\n connectionId: string;\n body: UpdateIntegrationConnectionBodyDto;\n}) {\n return await apiRequest<IntegrationConnectionDto>(\n `/integration-connections/${encodeURIComponent(connectionId)}`,\n {method: 'PATCH', body},\n );\n}\n\nexport async function deleteIntegrationConnection({connectionId}: {connectionId: string}) {\n await apiRequest<void>(`/integration-connections/${encodeURIComponent(connectionId)}`, {\n method: 'DELETE',\n });\n}\n\nexport function useIntegrationProvidersQuery(params?: {capability?: IntegrationCapabilityDto}) {\n const capability = params?.capability;\n return useQuery({\n queryKey: integrationsQueryKeys.providers(capability ?? 'all'),\n queryFn: ({signal}) => listIntegrationProviders(capability ? {capability, signal} : {signal}),\n });\n}\n\nexport function useSourceConnectionsQuery(workspaceId: string | undefined) {\n return useQuery({\n queryKey: workspaceId\n ? integrationsQueryKeys.sourceConnections(workspaceId)\n : [...integrationsQueryKeys.all, 'source-connections'],\n enabled: Boolean(workspaceId),\n queryFn: ({signal}) => listSourceConnections({workspaceId: workspaceId ?? '', signal}),\n });\n}\n\nexport function useIntegrationConnectionsQuery(workspaceId: string | undefined) {\n return useQuery({\n queryKey: workspaceId\n ? integrationsQueryKeys.connections(workspaceId, 'all')\n : [...integrationsQueryKeys.all, 'connections'],\n enabled: Boolean(workspaceId),\n queryFn: ({signal}) => listIntegrationConnections({workspaceId: workspaceId ?? '', signal}),\n });\n}\n\nexport function useRepositoriesInfiniteQuery(\n connectionId: string | undefined,\n options?: {search?: string},\n) {\n const trimmedSearch = options?.search?.trim() ?? '';\n return useInfiniteQuery({\n queryKey: connectionId\n ? integrationsQueryKeys.repositories(connectionId, trimmedSearch)\n : [...integrationsQueryKeys.all, 'repositories'],\n enabled: Boolean(connectionId),\n initialPageParam: undefined as string | undefined,\n queryFn: ({pageParam, signal}) => {\n const args: {connectionId: string; cursor?: string; search?: string; signal: AbortSignal} = {\n connectionId: connectionId ?? '',\n signal,\n };\n if (pageParam) args.cursor = pageParam;\n if (trimmedSearch) args.search = trimmedSearch;\n return listRepositories(args);\n },\n getNextPageParam: (lastPage) => lastPage.next_cursor ?? undefined,\n });\n}\n\nexport function useCreateGiteaConnectionMutation() {\n return useMutation({mutationFn: createGiteaConnection});\n}\n\nexport function useCreateLinearInstallMutation() {\n return useMutation({mutationFn: createLinearInstall});\n}\n\nexport function useCreateSlackInstallMutation() {\n return useMutation({mutationFn: createSlackInstall});\n}\n\nexport function useCompleteLinearCallbackMutation() {\n return useMutation({mutationFn: completeLinearCallback});\n}\n\nexport function useCompleteSlackCallbackMutation() {\n return useMutation({mutationFn: completeSlackCallback});\n}\n\nexport function useUpdateIntegrationConnectionMutation() {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: updateIntegrationConnection,\n onSuccess: async (connection) => {\n await queryClient.invalidateQueries({\n queryKey: integrationsQueryKeys.connectionsByWorkspace(connection.workspace_id),\n });\n },\n });\n}\n\nexport function useDeleteIntegrationConnectionMutation() {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: ({\n workspaceId: _workspaceId,\n connectionId,\n }: {\n workspaceId: string;\n connectionId: string;\n }) => deleteIntegrationConnection({connectionId}),\n onSuccess: async (_result, variables) => {\n await queryClient.invalidateQueries({\n queryKey: integrationsQueryKeys.connectionsByWorkspace(variables.workspaceId),\n });\n },\n });\n}\n"],"names":["apiRequest","useInfiniteQuery","useMutation","useQuery","useQueryClient","serializeLinearCallbackQuery","serializeSlackCallbackQuery","integrationsQueryKeys","all","providers","capability","connections","workspaceId","connectionsByWorkspace","sourceConnections","repositories","connectionId","search","listIntegrationProviders","signal","URLSearchParams","set","query","toString","path","listIntegrationConnections","workspace_id","listSourceConnections","result","filter","connection","lifecycle_status","createGiteaConnection","body","method","createGithubInstall","createSentryInstall","createLinearInstall","createSlackInstall","completeLinearCallback","token","headers","authorization","completeSlackCallback","connectSentry","listRepositories","cursor","params","updateIntegrationConnection","encodeURIComponent","deleteIntegrationConnection","useIntegrationProvidersQuery","queryKey","queryFn","useSourceConnectionsQuery","enabled","Boolean","useIntegrationConnectionsQuery","useRepositoriesInfiniteQuery","options","trimmedSearch","trim","initialPageParam","undefined","pageParam","args","getNextPageParam","lastPage","next_cursor","useCreateGiteaConnectionMutation","mutationFn","useCreateLinearInstallMutation","useCreateSlackInstallMutation","useCompleteLinearCallbackMutation","useCompleteSlackCallbackMutation","useUpdateIntegrationConnectionMutation","queryClient","onSuccess","invalidateQueries","useDeleteIntegrationConnectionMutation","_workspaceId","_result","variables"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -11,7 +11,10 @@ export * from './pages/linear-callback-page.js';
11
11
  export * from './pages/linear-install-page.js';
12
12
  export * from './pages/sentry-callback-page.js';
13
13
  export * from './pages/sentry-install-page.js';
14
+ export * from './pages/slack-callback-page.js';
15
+ export * from './pages/slack-install-page.js';
14
16
  export * from './pages/source-control-onboarding-page.js';
15
17
  export * from './provider-catalog.js';
16
18
  export * from './sentry-callback.js';
19
+ export * from './slack-callback.js';
17
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -11,8 +11,11 @@ export * from './pages/linear-callback-page.js';
11
11
  export * from './pages/linear-install-page.js';
12
12
  export * from './pages/sentry-callback-page.js';
13
13
  export * from './pages/sentry-install-page.js';
14
+ export * from './pages/slack-callback-page.js';
15
+ export * from './pages/slack-install-page.js';
14
16
  export * from './pages/source-control-onboarding-page.js';
15
17
  export * from './provider-catalog.js';
16
18
  export * from './sentry-callback.js';
19
+ export * from './slack-callback.js';
17
20
 
18
21
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './components/connection-picker.js';\nexport * from './components/integration-gallery.js';\nexport * from './components/redirect-install-page.js';\nexport * from './components/repository-picker.js';\nexport * from './connection-status-badge.js';\nexport * from './hooks/api/integrations.js';\nexport * from './integration-icon.js';\nexport * from './pages/gitea-install-page.js';\nexport * from './pages/github-install-page.js';\nexport * from './pages/linear-callback-page.js';\nexport * from './pages/linear-install-page.js';\nexport * from './pages/sentry-callback-page.js';\nexport * from './pages/sentry-install-page.js';\nexport * from './pages/source-control-onboarding-page.js';\nexport * from './provider-catalog.js';\nexport * from './sentry-callback.js';\n"],"names":[],"mappings":"AAAA,cAAc,oCAAoC;AAClD,cAAc,sCAAsC;AACpD,cAAc,wCAAwC;AACtD,cAAc,oCAAoC;AAClD,cAAc,+BAA+B;AAC7C,cAAc,8BAA8B;AAC5C,cAAc,wBAAwB;AACtC,cAAc,gCAAgC;AAC9C,cAAc,iCAAiC;AAC/C,cAAc,kCAAkC;AAChD,cAAc,iCAAiC;AAC/C,cAAc,kCAAkC;AAChD,cAAc,iCAAiC;AAC/C,cAAc,4CAA4C;AAC1D,cAAc,wBAAwB;AACtC,cAAc,uBAAuB"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './components/connection-picker.js';\nexport * from './components/integration-gallery.js';\nexport * from './components/redirect-install-page.js';\nexport * from './components/repository-picker.js';\nexport * from './connection-status-badge.js';\nexport * from './hooks/api/integrations.js';\nexport * from './integration-icon.js';\nexport * from './pages/gitea-install-page.js';\nexport * from './pages/github-install-page.js';\nexport * from './pages/linear-callback-page.js';\nexport * from './pages/linear-install-page.js';\nexport * from './pages/sentry-callback-page.js';\nexport * from './pages/sentry-install-page.js';\nexport * from './pages/slack-callback-page.js';\nexport * from './pages/slack-install-page.js';\nexport * from './pages/source-control-onboarding-page.js';\nexport * from './provider-catalog.js';\nexport * from './sentry-callback.js';\nexport * from './slack-callback.js';\n"],"names":[],"mappings":"AAAA,cAAc,oCAAoC;AAClD,cAAc,sCAAsC;AACpD,cAAc,wCAAwC;AACtD,cAAc,oCAAoC;AAClD,cAAc,+BAA+B;AAC7C,cAAc,8BAA8B;AAC5C,cAAc,wBAAwB;AACtC,cAAc,gCAAgC;AAC9C,cAAc,iCAAiC;AAC/C,cAAc,kCAAkC;AAChD,cAAc,iCAAiC;AAC/C,cAAc,kCAAkC;AAChD,cAAc,iCAAiC;AAC/C,cAAc,iCAAiC;AAC/C,cAAc,gCAAgC;AAC9C,cAAc,4CAA4C;AAC1D,cAAc,wBAAwB;AACtC,cAAc,uBAAuB;AACrC,cAAc,sBAAsB"}
@@ -1 +1 @@
1
- {"version":3,"file":"linear-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/linear-callback-page.tsx"],"names":[],"mappings":"AA8BA,wBAAgB,kBAAkB,gCAsFjC"}
1
+ {"version":3,"file":"linear-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/linear-callback-page.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,kBAAkB,gCAsFjC"}
@@ -1,13 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useRefreshAuth } from '@shipfox/client-auth';
3
- import { ButtonLink } from '@shipfox/react-ui/button';
4
- import { Callout } from '@shipfox/react-ui/callout';
5
3
  import { FullPageLoader } from '@shipfox/react-ui/loader';
6
4
  import { toast } from '@shipfox/react-ui/toast';
7
- import { Text } from '@shipfox/react-ui/typography';
8
5
  import { useQueryClient } from '@tanstack/react-query';
9
- import { Link, useNavigate, useSearch } from '@tanstack/react-router';
10
- import { useEffect, useMemo, useRef, useState } from 'react';
6
+ import { useNavigate, useSearch } from '@tanstack/react-router';
7
+ import { useEffect, useMemo, useState } from 'react';
8
+ import { CallbackStatusShell } from '#components/callback-status-shell.js';
11
9
  import { integrationsQueryKeys, useCompleteLinearCallbackMutation } from '#hooks/api/integrations.js';
12
10
  import { classifyLinearCallbackError, clearLinearInstallWorkspace, parseLinearCallbackQuery, readLinearInstallWorkspace, serializeLinearCallbackQuery } from '#linear-callback.js';
13
11
  // Keyed by the callback's code+state. This page has no in-place Retry (failures
@@ -114,78 +112,13 @@ export function LinearCallbackPage() {
114
112
  });
115
113
  }
116
114
  function LinearCallbackFailurePage({ failure, workspaceId }) {
117
- const headingRef = useRef(null);
118
- useEffect(()=>{
119
- headingRef.current?.focus();
120
- }, []);
121
- const recoveryVariant = failure.startOver || failure.signIn ? 'muted' : 'base';
122
- const switchAccountHref = workspaceId ? `/auth/logout?redirect=${encodeURIComponent(`/workspaces/${workspaceId}/integrations/linear`)}` : '/auth/logout';
123
- const settingsLink = workspaceId ? /*#__PURE__*/ _jsx(ButtonLink, {
124
- asChild: true,
125
- variant: recoveryVariant,
126
- className: "min-h-44 w-full sm:w-fit",
127
- children: /*#__PURE__*/ _jsx(Link, {
128
- to: "/workspaces/$wid/settings/integrations",
129
- params: {
130
- wid: workspaceId
131
- },
132
- children: "Back to integrations"
133
- })
134
- }) : /*#__PURE__*/ _jsx(ButtonLink, {
135
- asChild: true,
136
- variant: recoveryVariant,
137
- className: "min-h-44 w-full sm:w-fit",
138
- children: /*#__PURE__*/ _jsx(Link, {
139
- to: "/",
140
- children: "Back to Shipfox"
141
- })
142
- });
143
- return /*#__PURE__*/ _jsx("main", {
144
- className: "flex min-h-screen bg-background-subtle-base px-16 py-32",
145
- children: /*#__PURE__*/ _jsxs("div", {
146
- className: "mx-auto flex w-full max-w-[480px] flex-col justify-center gap-20",
147
- children: [
148
- /*#__PURE__*/ _jsx("h2", {
149
- ref: headingRef,
150
- tabIndex: -1,
151
- className: "text-24 font-semibold outline-none",
152
- children: failure.title ?? 'Linear install could not be completed'
153
- }),
154
- /*#__PURE__*/ _jsx(Callout, {
155
- role: "alert",
156
- type: "error",
157
- children: /*#__PURE__*/ _jsx(Text, {
158
- size: "sm",
159
- children: failure.message
160
- })
161
- }),
162
- /*#__PURE__*/ _jsxs("div", {
163
- className: "flex flex-col gap-8 sm:flex-row sm:items-center",
164
- children: [
165
- failure.signIn ? /*#__PURE__*/ _jsx(ButtonLink, {
166
- asChild: true,
167
- className: "min-h-44 w-full sm:w-fit",
168
- children: /*#__PURE__*/ _jsx(Link, {
169
- to: switchAccountHref,
170
- children: "Switch account"
171
- })
172
- }) : null,
173
- failure.startOver && workspaceId ? /*#__PURE__*/ _jsx(ButtonLink, {
174
- asChild: true,
175
- className: "min-h-44 w-full sm:w-fit",
176
- children: /*#__PURE__*/ _jsx(Link, {
177
- to: "/workspaces/$wid/integrations/linear",
178
- params: {
179
- wid: workspaceId
180
- },
181
- children: "Start over"
182
- })
183
- }) : null,
184
- settingsLink
185
- ]
186
- })
187
- ]
188
- })
115
+ return /*#__PURE__*/ _jsx(CallbackStatusShell, {
116
+ title: failure.title ?? 'Linear install could not be completed',
117
+ message: failure.message,
118
+ startOver: failure.startOver,
119
+ switchAccount: failure.signIn,
120
+ workspaceId: workspaceId,
121
+ installPath: "/workspaces/$wid/integrations/linear"
189
122
  });
190
123
  }
191
124
 
@@ -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 {ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {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 {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// Keyed by the callback's code+state. This page has no in-place Retry (failures\n// route to \"Start over\", which begins a fresh install with a new state+code), so\n// entries are intentionally never evicted: retaining them dedupes StrictMode and\n// remount re-runs so the single-use grant code is exchanged at most once. The\n// sibling Sentry page evicts on settle only because its Retry replays the code.\nconst callbackRequests = new Map<string, Promise<LinearCallbackResponseDto>>();\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 let request = callbackRequests.get(key);\n if (!request) {\n request = refreshAuth().then(\n async (session) => await completeLinearCallback({query: params, token: session.token}),\n );\n callbackRequests.set(key, request);\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 const headingRef = useRef<HTMLHeadingElement>(null);\n\n useEffect(() => {\n headingRef.current?.focus();\n }, []);\n\n const recoveryVariant = failure.startOver || failure.signIn ? 'muted' : 'base';\n const switchAccountHref = workspaceId\n ? `/auth/logout?redirect=${encodeURIComponent(`/workspaces/${workspaceId}/integrations/linear`)}`\n : '/auth/logout';\n const settingsLink = workspaceId ? (\n <ButtonLink asChild variant={recoveryVariant} className=\"min-h-44 w-full sm:w-fit\">\n <Link to=\"/workspaces/$wid/settings/integrations\" params={{wid: workspaceId}}>\n Back to integrations\n </Link>\n </ButtonLink>\n ) : (\n <ButtonLink asChild variant={recoveryVariant} className=\"min-h-44 w-full sm:w-fit\">\n <Link to=\"/\">Back to Shipfox</Link>\n </ButtonLink>\n );\n\n return (\n <main className=\"flex min-h-screen bg-background-subtle-base px-16 py-32\">\n <div className=\"mx-auto flex w-full max-w-[480px] flex-col justify-center gap-20\">\n <h2 ref={headingRef} tabIndex={-1} className=\"text-24 font-semibold outline-none\">\n {failure.title ?? 'Linear install could not be completed'}\n </h2>\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">{failure.message}</Text>\n </Callout>\n <div className=\"flex flex-col gap-8 sm:flex-row sm:items-center\">\n {failure.signIn ? (\n <ButtonLink asChild className=\"min-h-44 w-full sm:w-fit\">\n <Link to={switchAccountHref}>Switch account</Link>\n </ButtonLink>\n ) : null}\n {failure.startOver && workspaceId ? (\n <ButtonLink asChild className=\"min-h-44 w-full sm:w-fit\">\n <Link to=\"/workspaces/$wid/integrations/linear\" params={{wid: workspaceId}}>\n Start over\n </Link>\n </ButtonLink>\n ) : null}\n {settingsLink}\n </div>\n </div>\n </main>\n );\n}\n"],"names":["useRefreshAuth","ButtonLink","Callout","FullPageLoader","toast","Text","useQueryClient","Link","useNavigate","useSearch","useEffect","useMemo","useRef","useState","integrationsQueryKeys","useCompleteLinearCallbackMutation","classifyLinearCallbackError","clearLinearInstallWorkspace","parseLinearCallbackQuery","readLinearInstallWorkspace","serializeLinearCallbackQuery","callbackRequests","Map","toastedCallbacks","Set","LinearCallbackPage","search","strict","navigate","refreshAuth","queryClient","mutateAsync","completeLinearCallback","params","workspaceId","window","sessionStorage","undefined","failure","setFailure","disposed","key","request","get","then","session","query","token","set","connection","invalidateQueries","queryKey","connectionsByWorkspace","workspace_id","has","add","success","to","wid","replace","error","LinearCallbackFailurePage","title","message","startOver","signIn","aria-label","headingRef","current","focus","recoveryVariant","switchAccountHref","encodeURIComponent","settingsLink","asChild","variant","className","main","div","h2","ref","tabIndex","role","type","size"],"mappings":"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"}
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 {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// Keyed by the callback's code+state. This page has no in-place Retry (failures\n// route to \"Start over\", which begins a fresh install with a new state+code), so\n// entries are intentionally never evicted: retaining them dedupes StrictMode and\n// remount re-runs so the single-use grant code is exchanged at most once. The\n// sibling Sentry page evicts on settle only because its Retry replays the code.\nconst callbackRequests = new Map<string, Promise<LinearCallbackResponseDto>>();\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 let request = callbackRequests.get(key);\n if (!request) {\n request = refreshAuth().then(\n async (session) => await completeLinearCallback({query: params, token: session.token}),\n );\n callbackRequests.set(key, request);\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","FullPageLoader","toast","useQueryClient","useNavigate","useSearch","useEffect","useMemo","useState","CallbackStatusShell","integrationsQueryKeys","useCompleteLinearCallbackMutation","classifyLinearCallbackError","clearLinearInstallWorkspace","parseLinearCallbackQuery","readLinearInstallWorkspace","serializeLinearCallbackQuery","callbackRequests","Map","toastedCallbacks","Set","LinearCallbackPage","search","strict","navigate","refreshAuth","queryClient","mutateAsync","completeLinearCallback","params","workspaceId","window","sessionStorage","undefined","failure","setFailure","disposed","key","request","get","then","session","query","token","set","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 +1 @@
1
- {"version":3,"file":"linear-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/linear-install-page.tsx"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,gCAoBhC"}
1
+ {"version":3,"file":"linear-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/linear-install-page.tsx"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,gCAqBhC"}
@@ -11,6 +11,7 @@ export function LinearInstallPage() {
11
11
  return /*#__PURE__*/ _jsx(RedirectInstallPage, {
12
12
  installRequest: installRequest,
13
13
  errorFallbackMessage: "Could not start Linear install.",
14
+ loadingLabel: "Connecting Linear",
14
15
  beforeRedirect: (workspaceId)=>{
15
16
  try {
16
17
  saveLinearInstallWorkspace(window.sessionStorage, workspaceId);
@@ -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 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","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,gBAAgB,CAACC;YACf,IAAI;gBACFR,2BAA2BS,OAAOC,cAAc,EAAEF;YACpD,EAAE,OAAM;YACN,0EAA0E;YAC5E;QACF;;AAGN"}
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 +1 @@
1
- {"version":3,"file":"sentry-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/sentry-install-page.tsx"],"names":[],"mappings":"AAIA,wBAAgB,iBAAiB,gCAYhC"}
1
+ {"version":3,"file":"sentry-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/sentry-install-page.tsx"],"names":[],"mappings":"AAIA,wBAAgB,iBAAiB,gCAahC"}
@@ -6,6 +6,7 @@ export function SentryInstallPage() {
6
6
  return /*#__PURE__*/ _jsx(RedirectInstallPage, {
7
7
  installRequest: createSentryInstall,
8
8
  errorFallbackMessage: "Could not start Sentry install.",
9
+ loadingLabel: "Connecting Sentry",
9
10
  // Sentry's redirect has no state param; the stored id lets the callback
10
11
  // pre-select this workspace. Saving never throws (see helper).
11
12
  beforeRedirect: (workspaceId)=>{
@@ -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 // 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","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;QACrB,wEAAwE;QACxE,+DAA+D;QAC/DC,gBAAgB,CAACC;YACfL,2BAA2BM,OAAOC,cAAc,EAAEF;QACpD;;AAGN"}
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"}
@@ -0,0 +1,2 @@
1
+ export declare function SlackCallbackPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=slack-callback-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-callback-page.d.ts","sourceRoot":"","sources":["../../src/pages/slack-callback-page.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,iBAAiB,gCA+FhC"}
@@ -0,0 +1,114 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRefreshAuth } from '@shipfox/client-auth';
3
+ import { FullPageLoader } from '@shipfox/react-ui/loader';
4
+ import { toast } from '@shipfox/react-ui/toast';
5
+ import { useQueryClient } from '@tanstack/react-query';
6
+ import { useNavigate, useSearch } from '@tanstack/react-router';
7
+ import { useEffect, useMemo, useState } from 'react';
8
+ import { CallbackStatusShell } from '#components/callback-status-shell.js';
9
+ import { integrationsQueryKeys, useCompleteSlackCallbackMutation } from '#hooks/api/integrations.js';
10
+ import { classifySlackCallbackError, clearSlackInstallWorkspace, parseSlackCallbackQuery, readSlackInstallWorkspace, serializeSlackCallbackQuery } from '#slack-callback.js';
11
+ // Requests and committed keys remain for the document lifetime so Strict Mode,
12
+ // browser Back, and remounts never replay Slack's single-use grant code.
13
+ const callbackRequests = new Map();
14
+ const completedCallbacks = new Set();
15
+ const toastedCallbacks = new Set();
16
+ export function SlackCallbackPage() {
17
+ const search = useSearch({
18
+ strict: false
19
+ });
20
+ const navigate = useNavigate();
21
+ const refreshAuth = useRefreshAuth();
22
+ const queryClient = useQueryClient();
23
+ const { mutateAsync: completeSlackCallback } = useCompleteSlackCallbackMutation();
24
+ const params = useMemo(()=>parseSlackCallbackQuery(search), [
25
+ search
26
+ ]);
27
+ const workspaceId = useMemo(()=>readSlackInstallWorkspace(window.sessionStorage), []);
28
+ const [failure, setFailure] = useState();
29
+ const [completedWorkspaceId, setCompletedWorkspaceId] = useState();
30
+ useEffect(()=>{
31
+ if (!params) return;
32
+ let disposed = false;
33
+ const key = serializeSlackCallbackQuery(params);
34
+ let request = callbackRequests.get(key);
35
+ if (!request) {
36
+ request = refreshAuth().then(async (session)=>await completeSlackCallback({
37
+ query: params,
38
+ token: session.token
39
+ }));
40
+ callbackRequests.set(key, request);
41
+ }
42
+ request.then(async (connection)=>{
43
+ if (disposed) return;
44
+ if (completedCallbacks.has(key)) {
45
+ setCompletedWorkspaceId(connection.workspace_id);
46
+ return;
47
+ }
48
+ completedCallbacks.add(key);
49
+ try {
50
+ clearSlackInstallWorkspace(window.sessionStorage);
51
+ } catch {
52
+ // The successful API response remains the source of truth.
53
+ }
54
+ try {
55
+ await queryClient.invalidateQueries({
56
+ queryKey: integrationsQueryKeys.connectionsByWorkspace(connection.workspace_id)
57
+ });
58
+ } catch {
59
+ // Navigation can continue when cache refresh is unavailable.
60
+ }
61
+ if (disposed) return;
62
+ if (!toastedCallbacks.has(key)) {
63
+ toastedCallbacks.add(key);
64
+ toast.success('Slack installed.');
65
+ }
66
+ try {
67
+ await navigate({
68
+ to: '/workspaces/$wid/settings/integrations',
69
+ params: {
70
+ wid: connection.workspace_id
71
+ },
72
+ replace: true
73
+ });
74
+ } catch {
75
+ if (!disposed) setCompletedWorkspaceId(connection.workspace_id);
76
+ }
77
+ }, (error)=>{
78
+ if (!disposed) setFailure(classifySlackCallbackError(error));
79
+ });
80
+ return ()=>{
81
+ disposed = true;
82
+ };
83
+ }, [
84
+ completeSlackCallback,
85
+ navigate,
86
+ params,
87
+ queryClient,
88
+ refreshAuth
89
+ ]);
90
+ if (!params) return /*#__PURE__*/ _jsx(CallbackStatusShell, {
91
+ title: "Invalid Slack callback",
92
+ message: "This Slack link is missing required parameters. Start the install again from workspace settings.",
93
+ startOver: true,
94
+ workspaceId: workspaceId,
95
+ installPath: "/workspaces/$wid/integrations/slack"
96
+ });
97
+ if (completedWorkspaceId) return /*#__PURE__*/ _jsx(CallbackStatusShell, {
98
+ title: "Slack connected",
99
+ message: "Slack is connected. Continue in integrations settings.",
100
+ workspaceId: completedWorkspaceId,
101
+ installPath: "/workspaces/$wid/integrations/slack"
102
+ });
103
+ if (failure) return /*#__PURE__*/ _jsx(CallbackStatusShell, {
104
+ ...failure,
105
+ workspaceId: workspaceId,
106
+ switchAccount: failure.signIn,
107
+ installPath: "/workspaces/$wid/integrations/slack"
108
+ });
109
+ return /*#__PURE__*/ _jsx(FullPageLoader, {
110
+ "aria-label": "Connecting Slack"
111
+ });
112
+ }
113
+
114
+ //# sourceMappingURL=slack-callback-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pages/slack-callback-page.tsx"],"sourcesContent":["import type {SlackCallbackResponseDto} from '@shipfox/api-integration-slack-dto';\nimport {useRefreshAuth} from '@shipfox/client-auth';\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, 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// Requests and committed keys remain for the document lifetime so Strict Mode,\n// browser Back, and remounts never replay Slack's single-use grant code.\nconst callbackRequests = new Map<string, Promise<SlackCallbackResponseDto>>();\nconst completedCallbacks = new Set<string>();\nconst toastedCallbacks = new Set<string>();\n\nexport function SlackCallbackPage() {\n const search = useSearch({strict: false});\n const navigate = useNavigate();\n const refreshAuth = useRefreshAuth();\n const queryClient = useQueryClient();\n const {mutateAsync: completeSlackCallback} = useCompleteSlackCallbackMutation();\n const params = useMemo(() => parseSlackCallbackQuery(search), [search]);\n const workspaceId = useMemo(() => readSlackInstallWorkspace(window.sessionStorage), []);\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 let request = callbackRequests.get(key);\n if (!request) {\n request = refreshAuth().then(\n async (session) => await completeSlackCallback({query: params, token: session.token}),\n );\n callbackRequests.set(key, request);\n }\n request.then(\n async (connection) => {\n if (disposed) return;\n if (completedCallbacks.has(key)) {\n setCompletedWorkspaceId(connection.workspace_id);\n return;\n }\n completedCallbacks.add(key);\n try {\n clearSlackInstallWorkspace(window.sessionStorage);\n } catch {\n // The successful API response remains the source of truth.\n }\n try {\n await queryClient.invalidateQueries({\n queryKey: integrationsQueryKeys.connectionsByWorkspace(connection.workspace_id),\n });\n } catch {\n // Navigation can continue when cache refresh is unavailable.\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.workspace_id},\n replace: true,\n });\n } catch {\n if (!disposed) setCompletedWorkspaceId(connection.workspace_id);\n }\n },\n (error: unknown) => {\n if (!disposed) setFailure(classifySlackCallbackError(error));\n },\n );\n return () => {\n disposed = true;\n };\n }, [completeSlackCallback, navigate, params, queryClient, 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","FullPageLoader","toast","useQueryClient","useNavigate","useSearch","useEffect","useMemo","useState","CallbackStatusShell","integrationsQueryKeys","useCompleteSlackCallbackMutation","classifySlackCallbackError","clearSlackInstallWorkspace","parseSlackCallbackQuery","readSlackInstallWorkspace","serializeSlackCallbackQuery","callbackRequests","Map","completedCallbacks","Set","toastedCallbacks","SlackCallbackPage","search","strict","navigate","refreshAuth","queryClient","mutateAsync","completeSlackCallback","params","workspaceId","window","sessionStorage","failure","setFailure","completedWorkspaceId","setCompletedWorkspaceId","disposed","key","request","get","then","session","query","token","set","connection","has","workspace_id","add","invalidateQueries","queryKey","connectionsByWorkspace","success","to","wid","replace","error","title","message","startOver","installPath","switchAccount","signIn","aria-label"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function SlackInstallPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=slack-install-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-install-page.d.ts","sourceRoot":"","sources":["../../src/pages/slack-install-page.tsx"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,gCAgB/B"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback } from 'react';
3
+ import { RedirectInstallPage } from '#components/redirect-install-page.js';
4
+ import { useCreateSlackInstallMutation } from '#hooks/api/integrations.js';
5
+ import { saveSlackInstallWorkspace } from '#slack-callback.js';
6
+ export function SlackInstallPage() {
7
+ const createInstall = useCreateSlackInstallMutation();
8
+ const installRequest = useCallback(async (body)=>await createInstall.mutateAsync(body), [
9
+ createInstall
10
+ ]);
11
+ return /*#__PURE__*/ _jsx(RedirectInstallPage, {
12
+ installRequest: installRequest,
13
+ errorFallbackMessage: "Could not start Slack install.",
14
+ loadingLabel: "Connecting Slack",
15
+ beforeRedirect: (workspaceId)=>saveSlackInstallWorkspace(window.sessionStorage, workspaceId)
16
+ });
17
+ }
18
+
19
+ //# sourceMappingURL=slack-install-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pages/slack-install-page.tsx"],"sourcesContent":["import {useCallback} from 'react';\nimport {RedirectInstallPage} from '#components/redirect-install-page.js';\nimport {useCreateSlackInstallMutation} from '#hooks/api/integrations.js';\nimport {saveSlackInstallWorkspace} from '#slack-callback.js';\n\nexport function SlackInstallPage() {\n const createInstall = useCreateSlackInstallMutation();\n const installRequest = useCallback(\n async (body: {workspace_id: string}) => await createInstall.mutateAsync(body),\n [createInstall],\n );\n return (\n <RedirectInstallPage\n installRequest={installRequest}\n errorFallbackMessage=\"Could not start Slack install.\"\n loadingLabel=\"Connecting Slack\"\n beforeRedirect={(workspaceId) =>\n saveSlackInstallWorkspace(window.sessionStorage, workspaceId)\n }\n />\n );\n}\n"],"names":["useCallback","RedirectInstallPage","useCreateSlackInstallMutation","saveSlackInstallWorkspace","SlackInstallPage","createInstall","installRequest","body","mutateAsync","errorFallbackMessage","loadingLabel","beforeRedirect","workspaceId","window","sessionStorage"],"mappings":";AAAA,SAAQA,WAAW,QAAO,QAAQ;AAClC,SAAQC,mBAAmB,QAAO,uCAAuC;AACzE,SAAQC,6BAA6B,QAAO,6BAA6B;AACzE,SAAQC,yBAAyB,QAAO,qBAAqB;AAE7D,OAAO,SAASC;IACd,MAAMC,gBAAgBH;IACtB,MAAMI,iBAAiBN,YACrB,OAAOO,OAAiC,MAAMF,cAAcG,WAAW,CAACD,OACxE;QAACF;KAAc;IAEjB,qBACE,KAACJ;QACCK,gBAAgBA;QAChBG,sBAAqB;QACrBC,cAAa;QACbC,gBAAgB,CAACC,cACfT,0BAA0BU,OAAOC,cAAc,EAAEF;;AAIzD"}
@@ -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';
2
+ export type ProviderSetupPath = '/workspaces/$wid/integrations/github' | '/workspaces/$wid/integrations/gitea' | '/workspaces/$wid/integrations/sentry' | '/workspaces/$wid/integrations/linear' | '/workspaces/$wid/integrations/slack';
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":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAIrD,MAAM,MAAM,iBAAiB,GACzB,sCAAsC,GACtC,qCAAqC,GACrC,sCAAsC,GACtC,sCAAsC,CAAC;AAE3C,UAAU,yBAAyB;IACjC,IAAI,EAAE,kBAAkB,GAAG,gBAAgB,CAAC;IAC5C,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,iBAAiB,CAAC;CAC9B;AAED,UAAU,yBAAyB;IACjC,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,MAAM,MAAM,oBAAoB,GAAG,yBAAyB,GAAG,yBAAyB,CAAC;AAEzF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAyBjE,CAAC"}
1
+ {"version":3,"file":"provider-catalog.d.ts","sourceRoot":"","sources":["../src/provider-catalog.ts"],"names":[],"mappings":"AAAA,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"}
@@ -14,6 +14,11 @@ export const PROVIDER_CATALOG = {
14
14
  iconName: 'linear',
15
15
  setupPath: '/workspaces/$wid/integrations/linear'
16
16
  },
17
+ slack: {
18
+ kind: 'redirect-install',
19
+ iconName: 'slack',
20
+ setupPath: '/workspaces/$wid/integrations/slack'
21
+ },
17
22
  gitea: {
18
23
  kind: 'direct-connect',
19
24
  iconName: 'gitea',
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider-catalog.ts"],"sourcesContent":["import type {IconName} from '@shipfox/react-ui/icon';\n\n// Literal union (not string) so `<Link to={catalog.setupPath}>` stays typed\n// against TanStack Router's route tree.\nexport type ProviderSetupPath =\n | '/workspaces/$wid/integrations/github'\n | '/workspaces/$wid/integrations/gitea'\n | '/workspaces/$wid/integrations/sentry'\n | '/workspaces/$wid/integrations/linear';\n\ninterface RouteProviderCatalogEntry {\n kind: 'redirect-install' | 'direct-connect';\n iconName: IconName;\n setupPath: ProviderSetupPath;\n}\n\ninterface ModalProviderCatalogEntry {\n kind: 'modal-connect';\n iconName: IconName;\n}\n\nexport type ProviderCatalogEntry = RouteProviderCatalogEntry | ModalProviderCatalogEntry;\n\nexport const PROVIDER_CATALOG: Record<string, ProviderCatalogEntry> = {\n github: {\n kind: 'redirect-install',\n iconName: 'github',\n setupPath: '/workspaces/$wid/integrations/github',\n },\n sentry: {\n kind: 'redirect-install',\n iconName: 'sentry',\n setupPath: '/workspaces/$wid/integrations/sentry',\n },\n linear: {\n kind: 'redirect-install',\n iconName: 'linear',\n setupPath: '/workspaces/$wid/integrations/linear',\n },\n gitea: {\n kind: 'direct-connect',\n iconName: 'gitea',\n setupPath: '/workspaces/$wid/integrations/gitea',\n },\n webhook: {\n kind: 'modal-connect',\n iconName: 'webhookLine',\n },\n};\n"],"names":["PROVIDER_CATALOG","github","kind","iconName","setupPath","sentry","linear","gitea","webhook"],"mappings":"AAuBA,OAAO,MAAMA,mBAAyD;IACpEC,QAAQ;QACNC,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAC,QAAQ;QACNH,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAE,QAAQ;QACNJ,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAG,OAAO;QACLL,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAI,SAAS;QACPN,MAAM;QACNC,UAAU;IACZ;AACF,EAAE"}
1
+ {"version":3,"sources":["../src/provider-catalog.ts"],"sourcesContent":["import type {IconName} from '@shipfox/react-ui/icon';\n\n// Literal union (not string) so `<Link to={catalog.setupPath}>` stays typed\n// against TanStack Router's route tree.\nexport type ProviderSetupPath =\n | '/workspaces/$wid/integrations/github'\n | '/workspaces/$wid/integrations/gitea'\n | '/workspaces/$wid/integrations/sentry'\n | '/workspaces/$wid/integrations/linear'\n | '/workspaces/$wid/integrations/slack';\n\ninterface RouteProviderCatalogEntry {\n kind: 'redirect-install' | 'direct-connect';\n iconName: IconName;\n setupPath: ProviderSetupPath;\n}\n\ninterface ModalProviderCatalogEntry {\n kind: 'modal-connect';\n iconName: IconName;\n}\n\nexport type ProviderCatalogEntry = RouteProviderCatalogEntry | ModalProviderCatalogEntry;\n\nexport const PROVIDER_CATALOG: Record<string, ProviderCatalogEntry> = {\n github: {\n kind: 'redirect-install',\n iconName: 'github',\n setupPath: '/workspaces/$wid/integrations/github',\n },\n sentry: {\n kind: 'redirect-install',\n iconName: 'sentry',\n setupPath: '/workspaces/$wid/integrations/sentry',\n },\n linear: {\n kind: 'redirect-install',\n iconName: 'linear',\n setupPath: '/workspaces/$wid/integrations/linear',\n },\n slack: {\n kind: 'redirect-install',\n iconName: 'slack',\n setupPath: '/workspaces/$wid/integrations/slack',\n },\n gitea: {\n kind: 'direct-connect',\n iconName: 'gitea',\n setupPath: '/workspaces/$wid/integrations/gitea',\n },\n webhook: {\n kind: 'modal-connect',\n iconName: 'webhookLine',\n },\n};\n"],"names":["PROVIDER_CATALOG","github","kind","iconName","setupPath","sentry","linear","slack","gitea","webhook"],"mappings":"AAwBA,OAAO,MAAMA,mBAAyD;IACpEC,QAAQ;QACNC,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAC,QAAQ;QACNH,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAE,QAAQ;QACNJ,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAG,OAAO;QACLL,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAI,OAAO;QACLN,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAK,SAAS;QACPP,MAAM;QACNC,UAAU;IACZ;AACF,EAAE"}
@@ -0,0 +1,6 @@
1
+ import { SlackCallbackPage } from '#pages/slack-callback-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof SlackCallbackPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=slack-callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-callback.d.ts","sourceRoot":"","sources":["../../src/routes/slack-callback.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,iBAAiB,EAAC,MAAM,+BAA+B,CAAC;;;;AAChE,wBAA2D"}
@@ -0,0 +1,7 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { SlackCallbackPage } from '#pages/slack-callback-page.js';
3
+ export default defineRoute({
4
+ component: SlackCallbackPage
5
+ });
6
+
7
+ //# sourceMappingURL=slack-callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/slack-callback.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {SlackCallbackPage} from '#pages/slack-callback-page.js';\nexport default defineRoute({component: SlackCallbackPage});\n"],"names":["defineRoute","SlackCallbackPage","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,iBAAiB,QAAO,gCAAgC;AAChE,eAAeD,YAAY;IAACE,WAAWD;AAAiB,GAAG"}
@@ -0,0 +1,6 @@
1
+ import { SlackInstallPage } from '#pages/slack-install-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof SlackInstallPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=slack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/routes/slack.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,gBAAgB,EAAC,MAAM,8BAA8B,CAAC;;;;AAC9D,wBAA0D"}