Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client

Fix policy overlay logic (#8793)

* Only enable policy update overlay once the actual Overlay mounts (after onboarding and all that)

* Disable policy overlay in e2e

* Add comments

* Add extra insurance

* Rm log

authored by

Eric Bailey and committed by
GitHub
11c9931f c0593e49

+80 -17
+43 -8
src/components/PolicyUpdateOverlay/context.tsx
··· 1 - import {createContext, type ReactNode, useContext} from 'react' 2 3 import {Provider as PortalProvider} from '#/components/PolicyUpdateOverlay/Portal' 4 import { 5 type PolicyUpdateState, 6 usePolicyUpdateState, 7 } from '#/components/PolicyUpdateOverlay/usePolicyUpdateState' 8 9 - const Context = createContext<PolicyUpdateState>({ 10 - completed: true, 11 - complete: () => {}, 12 }) 13 14 - export function usePolicyUpdateStateContext() { 15 const context = useContext(Context) 16 if (!context) { 17 throw new Error( 18 - 'usePolicyUpdateStateContext must be used within a PolicyUpdateProvider', 19 ) 20 } 21 return context 22 } 23 24 export function Provider({children}: {children?: ReactNode}) { 25 - const state = usePolicyUpdateState() 26 27 return ( 28 <PortalProvider> 29 - <Context.Provider value={state}>{children}</Context.Provider> 30 </PortalProvider> 31 ) 32 }
··· 1 + import { 2 + createContext, 3 + type ReactNode, 4 + useContext, 5 + useMemo, 6 + useState, 7 + } from 'react' 8 9 + import {useSession} from '#/state/session' 10 import {Provider as PortalProvider} from '#/components/PolicyUpdateOverlay/Portal' 11 import { 12 type PolicyUpdateState, 13 usePolicyUpdateState, 14 } from '#/components/PolicyUpdateOverlay/usePolicyUpdateState' 15 16 + const Context = createContext<{ 17 + state: PolicyUpdateState 18 + setIsReadyToShowOverlay: () => void 19 + }>({ 20 + state: { 21 + completed: true, 22 + complete: () => {}, 23 + }, 24 + /** 25 + * Although our data will be ready to go when the app shell mounts, we don't 26 + * want to show the overlay until we actually render it, which happens after 27 + * sigin/signup/onboarding in `createNativeStackNavigatorWithAuth`. 28 + */ 29 + setIsReadyToShowOverlay: () => {}, 30 }) 31 32 + export function usePolicyUpdateContext() { 33 const context = useContext(Context) 34 if (!context) { 35 throw new Error( 36 + 'usePolicyUpdateContext must be used within a PolicyUpdateProvider', 37 ) 38 } 39 return context 40 } 41 42 export function Provider({children}: {children?: ReactNode}) { 43 + const {hasSession} = useSession() 44 + const [isReadyToShowOverlay, setIsReadyToShowOverlay] = useState(false) 45 + const state = usePolicyUpdateState({ 46 + // only enable the policy update overlay in non-test environments 47 + enabled: 48 + isReadyToShowOverlay && hasSession && process.env.NODE_ENV !== 'test', 49 + }) 50 + 51 + const ctx = useMemo( 52 + () => ({ 53 + state, 54 + setIsReadyToShowOverlay() { 55 + if (isReadyToShowOverlay) return 56 + setIsReadyToShowOverlay(true) 57 + }, 58 + }), 59 + [state, isReadyToShowOverlay, setIsReadyToShowOverlay], 60 + ) 61 62 return ( 63 <PortalProvider> 64 + <Context.Provider value={ctx}>{children}</Context.Provider> 65 </PortalProvider> 66 ) 67 }
+11 -3
src/components/PolicyUpdateOverlay/index.tsx
··· 1 import {View} from 'react-native' 2 3 import {isIOS} from '#/platform/detection' 4 import {atoms as a} from '#/alf' 5 import {FullWindowOverlay} from '#/components/FullWindowOverlay' 6 - import {usePolicyUpdateStateContext} from '#/components/PolicyUpdateOverlay/context' 7 import {Portal} from '#/components/PolicyUpdateOverlay/Portal' 8 import {Content} from '#/components/PolicyUpdateOverlay/updates/202508' 9 10 export {Provider} from '#/components/PolicyUpdateOverlay/context' 11 - export {usePolicyUpdateStateContext} from '#/components/PolicyUpdateOverlay/context' 12 export {Outlet} from '#/components/PolicyUpdateOverlay/Portal' 13 14 export function PolicyUpdateOverlay() { 15 - const state = usePolicyUpdateStateContext() 16 17 /* 18 * See `window.clearNux` example in `/state/queries/nuxs` for a way to clear
··· 1 + import {useEffect} from 'react' 2 import {View} from 'react-native' 3 4 import {isIOS} from '#/platform/detection' 5 import {atoms as a} from '#/alf' 6 import {FullWindowOverlay} from '#/components/FullWindowOverlay' 7 + import {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' 8 import {Portal} from '#/components/PolicyUpdateOverlay/Portal' 9 import {Content} from '#/components/PolicyUpdateOverlay/updates/202508' 10 11 export {Provider} from '#/components/PolicyUpdateOverlay/context' 12 + export {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' 13 export {Outlet} from '#/components/PolicyUpdateOverlay/Portal' 14 15 export function PolicyUpdateOverlay() { 16 + const {state, setIsReadyToShowOverlay} = usePolicyUpdateContext() 17 + 18 + useEffect(() => { 19 + /** 20 + * Tell the context that we are ready to show the overlay. 21 + */ 22 + setIsReadyToShowOverlay() 23 + }, [setIsReadyToShowOverlay]) 24 25 /* 26 * See `window.clearNux` example in `/state/queries/nuxs` for a way to clear
+22 -2
src/components/PolicyUpdateOverlay/usePolicyUpdateState.ts
··· 11 complete: () => void 12 } 13 14 - export function usePolicyUpdateState() { 15 const nux = useNux(ACTIVE_UPDATE_ID) 16 const {mutate: save, variables} = useSaveNux() 17 const deviceStorage = useStorage(device, [ACTIVE_UPDATE_ID]) 18 const debugOverride = 19 !!useStorage(device, ['policyUpdateDebugOverride'])[0] && IS_DEV 20 return useMemo(() => { 21 const nuxIsReady = nux.status === 'ready' 22 const nuxIsCompleted = nux.nux?.completed === true 23 const nuxIsOptimisticallyCompleted = !!variables?.completed ··· 59 setCompletedForDevice(true) 60 }, 61 } 62 - }, [nux, save, variables, deviceStorage, debugOverride]) 63 } 64 65 export function computeCompletedState({
··· 11 complete: () => void 12 } 13 14 + export function usePolicyUpdateState({ 15 + enabled, 16 + }: { 17 + /** 18 + * Used to skip the policy update overlay until we're actually ready to 19 + * show it. 20 + */ 21 + enabled: boolean 22 + }) { 23 const nux = useNux(ACTIVE_UPDATE_ID) 24 const {mutate: save, variables} = useSaveNux() 25 const deviceStorage = useStorage(device, [ACTIVE_UPDATE_ID]) 26 const debugOverride = 27 !!useStorage(device, ['policyUpdateDebugOverride'])[0] && IS_DEV 28 + 29 return useMemo(() => { 30 + /** 31 + * If not enabled, then just return a completed state so the app functions 32 + * as normal. 33 + */ 34 + if (!enabled) { 35 + return { 36 + completed: true, 37 + complete() {}, 38 + } 39 + } 40 + 41 const nuxIsReady = nux.status === 'ready' 42 const nuxIsCompleted = nux.nux?.completed === true 43 const nuxIsOptimisticallyCompleted = !!variables?.completed ··· 79 setCompletedForDevice(true) 80 }, 81 } 82 + }, [enabled, nux, save, variables, deviceStorage, debugOverride]) 83 } 84 85 export function computeCompletedState({
+2 -2
src/view/shell/index.tsx
··· 33 import {SigninDialog} from '#/components/dialogs/Signin' 34 import { 35 Outlet as PolicyUpdateOverlayPortalOutlet, 36 - usePolicyUpdateStateContext, 37 } from '#/components/PolicyUpdateOverlay' 38 import {Outlet as PortalOutlet} from '#/components/Portal' 39 import {RoutesContainer, TabsNavigator} from '#/Navigation' ··· 49 const setIsDrawerOpen = useSetDrawerOpen() 50 const winDim = useWindowDimensions() 51 const insets = useSafeAreaInsets() 52 - const policyUpdateState = usePolicyUpdateStateContext() 53 54 const renderDrawerContent = useCallback(() => <DrawerContent />, []) 55 const onOpenDrawer = useCallback(
··· 33 import {SigninDialog} from '#/components/dialogs/Signin' 34 import { 35 Outlet as PolicyUpdateOverlayPortalOutlet, 36 + usePolicyUpdateContext, 37 } from '#/components/PolicyUpdateOverlay' 38 import {Outlet as PortalOutlet} from '#/components/Portal' 39 import {RoutesContainer, TabsNavigator} from '#/Navigation' ··· 49 const setIsDrawerOpen = useSetDrawerOpen() 50 const winDim = useWindowDimensions() 51 const insets = useSafeAreaInsets() 52 + const {state: policyUpdateState} = usePolicyUpdateContext() 53 54 const renderDrawerContent = useCallback(() => <DrawerContent />, []) 55 const onOpenDrawer = useCallback(
+2 -2
src/view/shell/index.web.tsx
··· 24 import {SigninDialog} from '#/components/dialogs/Signin' 25 import { 26 Outlet as PolicyUpdateOverlayPortalOutlet, 27 - usePolicyUpdateStateContext, 28 } from '#/components/PolicyUpdateOverlay' 29 import {Outlet as PortalOutlet} from '#/components/Portal' 30 import {FlatNavigator, RoutesContainer} from '#/Navigation' ··· 41 const {_} = useLingui() 42 const showDrawer = !isDesktop && isDrawerOpen 43 const [showDrawerDelayedExit, setShowDrawerDelayedExit] = useState(showDrawer) 44 - const policyUpdateState = usePolicyUpdateStateContext() 45 46 useLayoutEffect(() => { 47 if (showDrawer !== showDrawerDelayedExit) {
··· 24 import {SigninDialog} from '#/components/dialogs/Signin' 25 import { 26 Outlet as PolicyUpdateOverlayPortalOutlet, 27 + usePolicyUpdateContext, 28 } from '#/components/PolicyUpdateOverlay' 29 import {Outlet as PortalOutlet} from '#/components/Portal' 30 import {FlatNavigator, RoutesContainer} from '#/Navigation' ··· 41 const {_} = useLingui() 42 const showDrawer = !isDesktop && isDrawerOpen 43 const [showDrawerDelayedExit, setShowDrawerDelayedExit] = useState(showDrawer) 44 + const {state: policyUpdateState} = usePolicyUpdateContext() 45 46 useLayoutEffect(() => { 47 if (showDrawer !== showDrawerDelayedExit) {