FeaturePreviewContext.tsx172 lines · main
1import { FeatureFlagContext, LOCAL_STORAGE_KEYS, useFlag } from 'common'
2import { noop } from 'lodash'
3import { useQueryState } from 'nuqs'
4import {
5 createContext,
6 useCallback,
7 useContext,
8 useEffect,
9 useMemo,
10 useState,
11 type PropsWithChildren,
12} from 'react'
13
14import { useFeaturePreviews } from './useFeaturePreviews'
15import { useStaticEffectEvent } from '@/hooks/useStaticEffectEvent'
16import { EMPTY_OBJ } from '@/lib/void'
17
18type FeaturePreviewContextType = {
19 flags: { [key: string]: boolean }
20 onUpdateFlag: (key: string, value: boolean) => void
21}
22
23const FeaturePreviewContext = createContext<FeaturePreviewContextType>({
24 flags: EMPTY_OBJ,
25 onUpdateFlag: noop,
26})
27
28export const useFeaturePreviewContext = () => useContext(FeaturePreviewContext)
29
30export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren) => {
31 const { hasLoaded } = useContext(FeatureFlagContext)
32 const featurePreviews = useFeaturePreviews()
33
34 const [flags, setFlags] = useState(() =>
35 featurePreviews.reduce((a, b) => ({ ...a, [b.key]: false }), {})
36 )
37
38 const initializeFlags = useStaticEffectEvent(() => {
39 setFlags(
40 featurePreviews.reduce((a, b) => {
41 const defaultOptIn = b.isDefaultOptIn
42 try {
43 const localStorageValue = window.localStorage.getItem(b.key)
44 return {
45 ...a,
46 [b.key]: !localStorageValue ? defaultOptIn : localStorageValue === 'true',
47 }
48 } catch {
49 return { ...a, [b.key]: defaultOptIn }
50 }
51 }, {})
52 )
53 })
54
55 useEffect(() => {
56 if (typeof window !== 'undefined') {
57 initializeFlags()
58 }
59 }, [hasLoaded, initializeFlags])
60
61 const value = {
62 flags,
63 onUpdateFlag: (key: string, value: boolean) => {
64 try {
65 if (typeof window !== 'undefined' && window.localStorage) {
66 window.localStorage.setItem(key, value ? 'true' : 'false')
67 }
68 } catch {
69 // Silently fail in restricted storage modes (e.g. Safari private browsing)
70 }
71 const updatedFlags = { ...flags, [key]: value }
72 setFlags(updatedFlags)
73 },
74 }
75
76 return <FeaturePreviewContext.Provider value={value}>{children}</FeaturePreviewContext.Provider>
77}
78
79// Helpers
80
81export const useIsColumnLevelPrivilegesEnabled = () => {
82 const { flags } = useFeaturePreviewContext()
83 return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_CLS]
84}
85
86export const useUnifiedLogsPreview = () => {
87 const { flags, onUpdateFlag } = useFeaturePreviewContext()
88 const { hasLoaded: flagsHaveLoaded } = useContext(FeatureFlagContext)
89 const unifiedLogsEnabled = useFlag('unifiedLogs')
90
91 const isLoading = !flagsHaveLoaded
92 const isEnabled = unifiedLogsEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS]
93
94 const enable = () => onUpdateFlag(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, true)
95 const disable = () => onUpdateFlag(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, false)
96
97 return { isEnabled, isEligible: unifiedLogsEnabled, isLoading, enable, disable }
98}
99
100export const useIsPgDeltaDiffEnabled = () => {
101 const { flags } = useFeaturePreviewContext()
102 const pgDeltaDiffEnabled = useFlag('pgdeltaDiff')
103 return pgDeltaDiffEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_PG_DELTA_DIFF]
104}
105
106export const useIsAdvisorRulesEnabled = () => {
107 const { flags } = useFeaturePreviewContext()
108 return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_ADVISOR_RULES]
109}
110
111export const useIsPlatformWebhooksEnabled = () => {
112 const { flags } = useFeaturePreviewContext()
113 const platformWebhooksEnabled = useFlag('platformWebhooks')
114 return platformWebhooksEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_PLATFORM_WEBHOOKS]
115}
116
117export const useIsJitDbAccessEnabled = () => {
118 const { flags } = useFeaturePreviewContext()
119 const jitDbAccessEnabled = useFlag('jitDbAccess')
120 return jitDbAccessEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_JIT_DB_ACCESS]
121}
122
123export const useIsRLSTesterEnabled = () => {
124 const { flags } = useFeaturePreviewContext()
125 return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_RLS_TESTER]
126}
127
128export const useIsMarketplaceEnabled = () => {
129 const { flags } = useFeaturePreviewContext()
130 const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
131 return isMarketplaceEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_MARKETPLACE]
132}
133
134export const useFeaturePreviewModal = () => {
135 const featurePreviews = useFeaturePreviews()
136 const [featurePreviewModal, setFeaturePreviewModal] = useQueryState('featurePreviewModal')
137
138 const selectedFeatureKeyFromQuery = featurePreviewModal?.trim() ?? null
139 const showFeaturePreviewModal = selectedFeatureKeyFromQuery !== null
140
141 const selectedFeatureKey = (
142 !selectedFeatureKeyFromQuery ? featurePreviews[0].key : selectedFeatureKeyFromQuery
143 ) as (typeof featurePreviews)[number]['key']
144
145 const selectFeaturePreview = useCallback(
146 (featureKey: (typeof featurePreviews)[number]['key']) => {
147 setFeaturePreviewModal(featureKey)
148 },
149 [setFeaturePreviewModal]
150 )
151
152 const toggleFeaturePreviewModal = useCallback(
153 (value: boolean) => {
154 if (!value) {
155 setFeaturePreviewModal(null)
156 } else {
157 selectFeaturePreview(selectedFeatureKey)
158 }
159 },
160 [selectFeaturePreview, setFeaturePreviewModal, selectedFeatureKey]
161 )
162
163 return useMemo(
164 () => ({
165 showFeaturePreviewModal,
166 selectedFeatureKey,
167 selectFeaturePreview,
168 toggleFeaturePreviewModal,
169 }),
170 [showFeaturePreviewModal, selectedFeatureKey, selectFeaturePreview, toggleFeaturePreviewModal]
171 )
172}