Bluesky app fork with some witchin' additions 💫

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' 1 + import { 2 + createContext, 3 + type ReactNode, 4 + useContext, 5 + useMemo, 6 + useState, 7 + } from 'react' 2 8 9 + import {useSession} from '#/state/session' 3 10 import {Provider as PortalProvider} from '#/components/PolicyUpdateOverlay/Portal' 4 11 import { 5 12 type PolicyUpdateState, 6 13 usePolicyUpdateState, 7 14 } from '#/components/PolicyUpdateOverlay/usePolicyUpdateState' 8 15 9 - const Context = createContext<PolicyUpdateState>({ 10 - completed: true, 11 - complete: () => {}, 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: () => {}, 12 30 }) 13 31 14 - export function usePolicyUpdateStateContext() { 32 + export function usePolicyUpdateContext() { 15 33 const context = useContext(Context) 16 34 if (!context) { 17 35 throw new Error( 18 - 'usePolicyUpdateStateContext must be used within a PolicyUpdateProvider', 36 + 'usePolicyUpdateContext must be used within a PolicyUpdateProvider', 19 37 ) 20 38 } 21 39 return context 22 40 } 23 41 24 42 export function Provider({children}: {children?: ReactNode}) { 25 - const state = usePolicyUpdateState() 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 + ) 26 61 27 62 return ( 28 63 <PortalProvider> 29 - <Context.Provider value={state}>{children}</Context.Provider> 64 + <Context.Provider value={ctx}>{children}</Context.Provider> 30 65 </PortalProvider> 31 66 ) 32 67 }
+11 -3
src/components/PolicyUpdateOverlay/index.tsx
··· 1 + import {useEffect} from 'react' 1 2 import {View} from 'react-native' 2 3 3 4 import {isIOS} from '#/platform/detection' 4 5 import {atoms as a} from '#/alf' 5 6 import {FullWindowOverlay} from '#/components/FullWindowOverlay' 6 - import {usePolicyUpdateStateContext} from '#/components/PolicyUpdateOverlay/context' 7 + import {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' 7 8 import {Portal} from '#/components/PolicyUpdateOverlay/Portal' 8 9 import {Content} from '#/components/PolicyUpdateOverlay/updates/202508' 9 10 10 11 export {Provider} from '#/components/PolicyUpdateOverlay/context' 11 - export {usePolicyUpdateStateContext} from '#/components/PolicyUpdateOverlay/context' 12 + export {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' 12 13 export {Outlet} from '#/components/PolicyUpdateOverlay/Portal' 13 14 14 15 export function PolicyUpdateOverlay() { 15 - const state = usePolicyUpdateStateContext() 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]) 16 24 17 25 /* 18 26 * See `window.clearNux` example in `/state/queries/nuxs` for a way to clear
+22 -2
src/components/PolicyUpdateOverlay/usePolicyUpdateState.ts
··· 11 11 complete: () => void 12 12 } 13 13 14 - export function usePolicyUpdateState() { 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 + }) { 15 23 const nux = useNux(ACTIVE_UPDATE_ID) 16 24 const {mutate: save, variables} = useSaveNux() 17 25 const deviceStorage = useStorage(device, [ACTIVE_UPDATE_ID]) 18 26 const debugOverride = 19 27 !!useStorage(device, ['policyUpdateDebugOverride'])[0] && IS_DEV 28 + 20 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 + 21 41 const nuxIsReady = nux.status === 'ready' 22 42 const nuxIsCompleted = nux.nux?.completed === true 23 43 const nuxIsOptimisticallyCompleted = !!variables?.completed ··· 59 79 setCompletedForDevice(true) 60 80 }, 61 81 } 62 - }, [nux, save, variables, deviceStorage, debugOverride]) 82 + }, [enabled, nux, save, variables, deviceStorage, debugOverride]) 63 83 } 64 84 65 85 export function computeCompletedState({
+2 -2
src/view/shell/index.tsx
··· 33 33 import {SigninDialog} from '#/components/dialogs/Signin' 34 34 import { 35 35 Outlet as PolicyUpdateOverlayPortalOutlet, 36 - usePolicyUpdateStateContext, 36 + usePolicyUpdateContext, 37 37 } from '#/components/PolicyUpdateOverlay' 38 38 import {Outlet as PortalOutlet} from '#/components/Portal' 39 39 import {RoutesContainer, TabsNavigator} from '#/Navigation' ··· 49 49 const setIsDrawerOpen = useSetDrawerOpen() 50 50 const winDim = useWindowDimensions() 51 51 const insets = useSafeAreaInsets() 52 - const policyUpdateState = usePolicyUpdateStateContext() 52 + const {state: policyUpdateState} = usePolicyUpdateContext() 53 53 54 54 const renderDrawerContent = useCallback(() => <DrawerContent />, []) 55 55 const onOpenDrawer = useCallback(
+2 -2
src/view/shell/index.web.tsx
··· 24 24 import {SigninDialog} from '#/components/dialogs/Signin' 25 25 import { 26 26 Outlet as PolicyUpdateOverlayPortalOutlet, 27 - usePolicyUpdateStateContext, 27 + usePolicyUpdateContext, 28 28 } from '#/components/PolicyUpdateOverlay' 29 29 import {Outlet as PortalOutlet} from '#/components/Portal' 30 30 import {FlatNavigator, RoutesContainer} from '#/Navigation' ··· 41 41 const {_} = useLingui() 42 42 const showDrawer = !isDesktop && isDrawerOpen 43 43 const [showDrawerDelayedExit, setShowDrawerDelayedExit] = useState(showDrawer) 44 - const policyUpdateState = usePolicyUpdateStateContext() 44 + const {state: policyUpdateState} = usePolicyUpdateContext() 45 45 46 46 useLayoutEffect(() => { 47 47 if (showDrawer !== showDrawerDelayedExit) {