PrivateAppsContext.tsx 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import type { components } from 'api-types'
  2. import { createContext, PropsWithChildren, useContext, useMemo, useState } from 'react'
  3. import type { Installation, PrivateApp } from './PrivateApps.types'
  4. import { usePlatformAppInstallationsQuery } from '@/data/platform-apps/platform-app-installations-query'
  5. import { usePlatformAppsQuery } from '@/data/platform-apps/platform-apps-query'
  6. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  7. export type { Installation, PrivateApp } from './PrivateApps.types'
  8. interface PrivateAppsContextValue {
  9. slug: string | undefined
  10. apps: PrivateApp[]
  11. isLoading: boolean
  12. isLoadingInstallations: boolean
  13. installations: Installation[]
  14. addInstallation: (
  15. data: components['schemas']['InstallPlatformAppResponse'],
  16. projectScope: 'all' | string[]
  17. ) => void
  18. removeInstallation: (id: string) => void
  19. removeInstallationsByAppId: (appId: string) => void
  20. }
  21. const PrivateAppsContext = createContext<PrivateAppsContextValue | null>(null)
  22. export function PrivateAppsProvider({ children }: PropsWithChildren) {
  23. const { data: org } = useSelectedOrganizationQuery()
  24. const slug = org?.slug
  25. const { data: appsData, isLoading } = usePlatformAppsQuery({ slug })
  26. const {
  27. data: installationsData,
  28. isLoading: isLoadingInstallations,
  29. isError: installationsError,
  30. } = usePlatformAppInstallationsQuery({ slug }, { retry: false })
  31. // Local state fallback when GET installations endpoint is unavailable
  32. const [localInstallations, setLocalInstallations] = useState<Installation[]>([])
  33. const installations = useMemo<Installation[]>(() => {
  34. if (!installationsError && installationsData?.installations) {
  35. return installationsData.installations.map((inst) => ({
  36. ...(inst as components['schemas']['InstallPlatformAppResponse']),
  37. projectScope: 'all' as const,
  38. }))
  39. }
  40. return localInstallations
  41. }, [installationsData, installationsError, localInstallations])
  42. function addInstallation(
  43. data: components['schemas']['InstallPlatformAppResponse'],
  44. projectScope: 'all' | string[]
  45. ) {
  46. setLocalInstallations((prev) => [...prev, { ...data, projectScope }])
  47. }
  48. function removeInstallation(id: string) {
  49. setLocalInstallations((prev) => prev.filter((i) => i.id !== id))
  50. }
  51. function removeInstallationsByAppId(appId: string) {
  52. setLocalInstallations((prev) => prev.filter((i) => i.app_id !== appId))
  53. }
  54. return (
  55. <PrivateAppsContext.Provider
  56. value={{
  57. slug,
  58. apps: appsData?.apps ?? [],
  59. isLoading: isLoading || !slug,
  60. isLoadingInstallations: isLoadingInstallations || !slug,
  61. installations,
  62. addInstallation,
  63. removeInstallation,
  64. removeInstallationsByAppId,
  65. }}
  66. >
  67. {children}
  68. </PrivateAppsContext.Provider>
  69. )
  70. }
  71. export function usePrivateApps() {
  72. const ctx = useContext(PrivateAppsContext)
  73. if (!ctx) throw new Error('usePrivateApps must be used within PrivateAppsProvider')
  74. return ctx
  75. }