FeaturePreviewContext.tsx 5.4 KB

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