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