diff --git a/.github/workflows/react-native-cicd.yml b/.github/workflows/react-native-cicd.yml index 5ec6ddf..ae6d0bb 100644 --- a/.github/workflows/react-native-cicd.yml +++ b/.github/workflows/react-native-cicd.yml @@ -66,6 +66,8 @@ env: EXPO_APPLE_TEAM_TYPE: ${{ secrets.EXPO_APPLE_TEAM_TYPE }} UNIT_APTABASE_APP_KEY: ${{ secrets.UNIT_APTABASE_APP_KEY }} UNIT_APTABASE_URL: ${{ secrets.UNIT_APTABASE_URL }} + UNIT_APP_KEY: ${{ secrets.UNIT_APP_KEY }} + APP_KEY: ${{ secrets.APP_KEY }} NODE_OPTIONS: --openssl-legacy-provider jobs: diff --git a/src/app/(app)/_layout.tsx b/src/app/(app)/_layout.tsx index adfb570..caa611c 100644 --- a/src/app/(app)/_layout.tsx +++ b/src/app/(app)/_layout.tsx @@ -39,6 +39,7 @@ export default function TabLayout() { const [isFirstTime, _setIsFirstTime] = useIsFirstTime(); const [isOpen, setIsOpen] = React.useState(false); const [isNotificationsOpen, setIsNotificationsOpen] = React.useState(false); + const [isNotificationSystemReady, setIsNotificationSystemReady] = React.useState(false); const { width, height } = useWindowDimensions(); const isLandscape = width > height; const { isActive, appState } = useAppLifecycle(); @@ -215,6 +216,20 @@ export default function TabLayout() { const activeUnitId = useCoreStore((state) => state.activeUnitId); const rights = securityStore((state) => state.rights); + // Manage notification system readiness + useEffect(() => { + const isReady = Boolean(activeUnitId && config && config.NovuApplicationId && config.NovuBackendApiUrl && config.NovuSocketUrl && rights?.DepartmentCode); + + if (isReady && !isNotificationSystemReady) { + // Add a small delay to ensure the main UI is rendered first + setTimeout(() => { + setIsNotificationSystemReady(true); + }, 1000); + } else if (!isReady && isNotificationSystemReady) { + setIsNotificationSystemReady(false); + } + }, [activeUnitId, config, rights?.DepartmentCode, isNotificationSystemReady]); + if (isFirstTime) { //setIsOnboarding(); return ; @@ -338,7 +353,7 @@ export default function TabLayout() { {/* NotificationInbox positioned within the tab content area */} - {activeUnitId && config && rights?.DepartmentCode && setIsNotificationsOpen(false)} />} + {isNotificationSystemReady && setIsNotificationsOpen(false)} />} @@ -346,8 +361,8 @@ export default function TabLayout() { return ( <> - {activeUnitId && config && rights?.DepartmentCode ? ( - + {isNotificationSystemReady ? ( + {content} ) : ( @@ -394,11 +409,8 @@ const CreateNotificationButton = ({ return null; } - return ( - - setIsNotificationsOpen(true)} /> - - ); + // Only render after notification system is ready to prevent timing issues + return setIsNotificationsOpen(true)} />; }; const styles = StyleSheet.create({ diff --git a/src/components/notifications/NotificationInbox.tsx b/src/components/notifications/NotificationInbox.tsx index 615f532..3e35ce1 100644 --- a/src/components/notifications/NotificationInbox.tsx +++ b/src/components/notifications/NotificationInbox.tsx @@ -235,6 +235,11 @@ export const NotificationInbox = ({ isOpen, onClose }: NotificationInboxProps) = return null; } + // Additional safety check to prevent rendering overlay without proper config + if (!activeUnitId || !config || !config.NovuApplicationId || !config.NovuBackendApiUrl || !config.NovuSocketUrl) { + return null; + } + return ( {/* Backdrop for tapping outside to close */} diff --git a/src/components/notifications/__tests__/NotificationInbox.test.tsx b/src/components/notifications/__tests__/NotificationInbox.test.tsx index b28e06d..60a1ddf 100644 --- a/src/components/notifications/__tests__/NotificationInbox.test.tsx +++ b/src/components/notifications/__tests__/NotificationInbox.test.tsx @@ -89,7 +89,12 @@ describe('NotificationInbox', () => { mockUseCoreStore.mockImplementation((selector: any) => { const state = { activeUnitId: 'unit-1', - config: { apiUrl: 'test-url' }, + config: { + apiUrl: 'test-url', + NovuApplicationId: 'test-app-id', + NovuBackendApiUrl: 'test-backend-url', + NovuSocketUrl: 'test-socket-url' + }, }; return selector(state); }); @@ -257,16 +262,18 @@ describe('NotificationInbox', () => { mockUseCoreStore.mockImplementation((selector: any) => { const state = { activeUnitId: null, - config: null, + config: { apiUrl: 'test-url' }, // Missing Novu config properties }; return selector(state); }); - const { getByText } = render( + const { queryByText } = render( ); - expect(getByText('Unable to load notifications')).toBeTruthy(); + // Component should return null when required config is missing + expect(queryByText('Notifications')).toBeNull(); + expect(queryByText('Unable to load notifications')).toBeNull(); }); it('opens notification detail on tap in normal mode', async () => {