From 4cfc9518d0b9d4203ee88b28dcd8323d2d3547ee Mon Sep 17 00:00:00 2001 From: Dylan Nienberg Date: Wed, 6 Nov 2024 11:59:10 -0600 Subject: [PATCH] migrated back to redux store for necessary variables --- VAMobile/src/App.tsx | 25 ++++---- VAMobile/src/api/auth/getAuthSettings.tsx | 4 -- .../src/api/auth/getBiometricsSettings.tsx | 1 - .../src/api/auth/handleTokenCallbackUrl.tsx | 8 ++- VAMobile/src/api/auth/logout.tsx | 11 ++-- VAMobile/src/api/types/auth.ts | 6 -- .../BiometricsPreferenceScreen.tsx | 11 ++-- .../src/screens/SyncScreen/SyncScreen.tsx | 20 +++++-- VAMobile/src/store/index.ts | 2 + VAMobile/src/store/slices/authSlice.ts | 51 ++++++++++++++++ VAMobile/src/store/slices/index.ts | 4 ++ VAMobile/src/testUtils.tsx | 7 +-- VAMobile/src/utils/auth.ts | 60 ++++++++----------- 13 files changed, 128 insertions(+), 82 deletions(-) create mode 100644 VAMobile/src/store/slices/authSlice.ts diff --git a/VAMobile/src/App.tsx b/VAMobile/src/App.tsx index 014ce6750d1..178af2f85b1 100644 --- a/VAMobile/src/App.tsx +++ b/VAMobile/src/App.tsx @@ -57,7 +57,7 @@ import EditAddressScreen from 'screens/HomeScreen/ProfileScreen/ContactInformati import RequestNotificationsScreen from 'screens/auth/RequestNotifications/RequestNotificationsScreen' import store, { RootState } from 'store' import { getAccessToken, setRefreshAccessToken, setlogout } from 'store/api' -import { AnalyticsState } from 'store/slices' +import { AnalyticsState, AuthState } from 'store/slices' import { SettingsState } from 'store/slices' import { AccessibilityState, @@ -211,6 +211,9 @@ export function AuthGuard() { const { data: userBiometricSettings, isLoading: initializingBiometrics } = useBiometricsSettings() const initializing = initializingAuth || initializingBiometrics const { tappedForegroundNotification, setTappedForegroundNotification } = useNotificationContext() + const { loggedIn, syncing, displayBiometricsPreferenceScreen } = useSelector( + (state) => state.auth, + ) const { loadingRemoteConfig, remoteConfigActivated } = useSelector( (state) => state.settings, ) @@ -308,10 +311,10 @@ export function AuthGuard() { }, } console.debug('AuthGuard: initializing') - if (userAuthSettings?.loggedIn && tappedForegroundNotification) { + if (loggedIn && tappedForegroundNotification) { console.debug('User tapped foreground notification. Skipping initializeAuth.') setTappedForegroundNotification(false) - } else if (!userAuthSettings?.loggedIn) { + } else if (loggedIn) { initializeAuth(() => { refreshAccessToken(getAccessToken() || '', mutateOptions) }) @@ -331,7 +334,7 @@ export function AuthGuard() { } } }, [ - userAuthSettings?.loggedIn, + loggedIn, tappedForegroundNotification, setTappedForegroundNotification, refreshAccessToken, @@ -384,10 +387,10 @@ export function AuthGuard() { ) } else if ( - userAuthSettings?.syncing && + syncing && userAuthSettings?.firstTimeLogin && userBiometricSettings?.canStoreWithBiometric && - userBiometricSettings?.displayBiometricsPreferenceScreen + displayBiometricsPreferenceScreen ) { content = ( @@ -398,17 +401,17 @@ export function AuthGuard() { /> ) - } else if (userAuthSettings?.firstTimeLogin && userAuthSettings?.loggedIn) { + } else if (userAuthSettings?.firstTimeLogin && loggedIn) { content = - } else if (userAuthSettings?.syncing) { + } else if (syncing) { content = ( ) - } else if (userAuthSettings?.firstTimeLogin && userAuthSettings?.loggedIn) { + } else if (userAuthSettings?.firstTimeLogin && loggedIn) { content = - } else if (!userAuthSettings?.firstTimeLogin && userAuthSettings?.loggedIn && requestNotificationPreferenceScreen) { + } else if (!userAuthSettings?.firstTimeLogin && loggedIn && requestNotificationPreferenceScreen) { content = ( ) - } else if (userAuthSettings?.loggedIn) { + } else if (loggedIn) { content = ( <> diff --git a/VAMobile/src/api/auth/getAuthSettings.tsx b/VAMobile/src/api/auth/getAuthSettings.tsx index 72217935ebc..082b4af173b 100644 --- a/VAMobile/src/api/auth/getAuthSettings.tsx +++ b/VAMobile/src/api/auth/getAuthSettings.tsx @@ -12,10 +12,6 @@ const getAuthSettings = async (): Promise => { const firstTimeLogin = await checkFirstTimeLogin() return { firstTimeLogin: firstTimeLogin, - loading: false, - loggedIn: false, - loggingOut: false, - syncing: false, } } diff --git a/VAMobile/src/api/auth/getBiometricsSettings.tsx b/VAMobile/src/api/auth/getBiometricsSettings.tsx index eff5a8471e5..ecb01dc8ada 100644 --- a/VAMobile/src/api/auth/getBiometricsSettings.tsx +++ b/VAMobile/src/api/auth/getBiometricsSettings.tsx @@ -13,7 +13,6 @@ const getBiometricsSettings = async (): Promise => { const biometricsPreferred = await isBiometricsPreferred() return { canStoreWithBiometric: !!supportedBiometric, - displayBiometricsPreferenceScreen: true, shouldStoreWithBiometric: biometricsPreferred, supportedBiometric: supportedBiometric, } diff --git a/VAMobile/src/api/auth/handleTokenCallbackUrl.tsx b/VAMobile/src/api/auth/handleTokenCallbackUrl.tsx index 84ae68d0df4..0644d915879 100644 --- a/VAMobile/src/api/auth/handleTokenCallbackUrl.tsx +++ b/VAMobile/src/api/auth/handleTokenCallbackUrl.tsx @@ -6,6 +6,7 @@ import { logAnalyticsEvent, logNonFatalErrorToFirebase } from 'utils/analytics' import { getCodeVerifier, loginFinish, loginStart, parseCallbackUrlParams, processAuthResponse } from 'utils/auth' import { isErrorObject } from 'utils/common' import getEnv from 'utils/env' +import { useAppDispatch } from 'utils/hooks' import { clearCookies } from 'utils/rnAuthSesson' import { usePostLoggedIn } from './postLoggedIn' @@ -36,16 +37,17 @@ const handleTokenCallbackUrl = async (handleTokenCallbackParams: handleTokenCall */ export const useHandleTokenCallbackUrl = () => { const { mutate: postLoggedIn } = usePostLoggedIn() + const dispatch = useAppDispatch() return useMutation({ mutationFn: handleTokenCallbackUrl, onSettled: () => { logAnalyticsEvent(Events.vama_auth_completed()) - loginStart(true) + loginStart(dispatch, true) clearCookies() }, onSuccess: async (data) => { const authCredentials = await processAuthResponse(data) - await loginFinish(false, authCredentials) + await loginFinish(dispatch, false, authCredentials) postLoggedIn() }, onError: (error) => { @@ -54,7 +56,7 @@ export const useHandleTokenCallbackUrl = () => { if (error.status) { logAnalyticsEvent(Events.vama_login_token_fetch(error)) } - loginFinish(true) + loginFinish(dispatch, true) } }, }) diff --git a/VAMobile/src/api/auth/logout.tsx b/VAMobile/src/api/auth/logout.tsx index 8423c75a68c..a22ea6bf58e 100644 --- a/VAMobile/src/api/auth/logout.tsx +++ b/VAMobile/src/api/auth/logout.tsx @@ -5,6 +5,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { UserAuthSettings } from 'api/types' import { RootState } from 'store' import * as api from 'store/api' +import { dispatchUpdateLoggingOut, dispatchUpdateSyncing } from 'store/slices' import { DemoState, updateDemoMode } from 'store/slices/demoSlice' import { logNonFatalErrorToFirebase } from 'utils/analytics' import { clearStoredAuthCreds, finishInitialize, retrieveRefreshToken } from 'utils/auth' @@ -47,12 +48,8 @@ export const useLogout = () => { return useMutation({ mutationFn: logout, onMutate: async () => { - const userSettings = queryClient.getQueryData(authKeys.settings) as UserAuthSettings - queryClient.setQueryData(authKeys.settings, { - ...userSettings, - loggingOut: true, - syncing: true, - }) + dispatch(dispatchUpdateLoggingOut(true)) + dispatch(dispatchUpdateSyncing(true)) await clearCookies() if (demoMode) { dispatch(updateDemoMode(false, true)) @@ -62,7 +59,7 @@ export const useLogout = () => { await clearStoredAuthCreds() api.setAccessToken(undefined) api.setRefreshToken(undefined) - await finishInitialize(false) + await finishInitialize(dispatch, false) queryClient.clear() }, onError: (error) => { diff --git a/VAMobile/src/api/types/auth.ts b/VAMobile/src/api/types/auth.ts index d6b0c08c4df..4795bce0929 100644 --- a/VAMobile/src/api/types/auth.ts +++ b/VAMobile/src/api/types/auth.ts @@ -44,17 +44,11 @@ export enum LOGIN_PROMPT_TYPE { export type UserAuthSettings = { firstTimeLogin: boolean - loading: boolean - loggedIn: boolean - loggingOut: boolean - syncing: boolean - authCredentials?: AuthCredentialData } export type UserBiometricsSettings = { canStoreWithBiometric: boolean - displayBiometricsPreferenceScreen: boolean shouldStoreWithBiometric: boolean supportedBiometric?: string } diff --git a/VAMobile/src/screens/BiometricsPreferenceScreen/BiometricsPreferenceScreen.tsx b/VAMobile/src/screens/BiometricsPreferenceScreen/BiometricsPreferenceScreen.tsx index 2b6dbf9a383..7f8a313b0c2 100644 --- a/VAMobile/src/screens/BiometricsPreferenceScreen/BiometricsPreferenceScreen.tsx +++ b/VAMobile/src/screens/BiometricsPreferenceScreen/BiometricsPreferenceScreen.tsx @@ -6,21 +6,22 @@ import { Button, ButtonVariants } from '@department-of-veterans-affairs/mobile-c import { useBiometricsSettings } from 'api/auth' import { Box, TextView, VAScrollView } from 'components' import { NAMESPACE } from 'constants/namespaces' -import { setBiometricsPreference, setDisplayBiometricsPreferenceScreen } from 'utils/auth' +import { dispatchUpdateDisplayBiometricsPreferenceScreen } from 'store/slices/authSlice' +import { setBiometricsPreference } from 'utils/auth' import { getSupportedBiometricA11yLabel, getSupportedBiometricText, getSupportedBiometricTranslationTag, getTranslation, } from 'utils/formattingUtils' -import { useTheme } from 'utils/hooks' +import { useAppDispatch, useTheme } from 'utils/hooks' export type SyncScreenProps = Record function BiometricsPreferenceScreen({}: SyncScreenProps) { const theme = useTheme() const { t } = useTranslation(NAMESPACE.COMMON) - + const dispatch = useAppDispatch() const { data: userBiometricSettings } = useBiometricsSettings() const supportedBiometric = userBiometricSettings?.supportedBiometric const biometricsText = getSupportedBiometricText(supportedBiometric || '', t) @@ -31,12 +32,12 @@ function BiometricsPreferenceScreen({}: SyncScreenProps) { ) const onSkip = (): void => { - setDisplayBiometricsPreferenceScreen(false) + dispatch(dispatchUpdateDisplayBiometricsPreferenceScreen(false)) } const onUseBiometrics = (): void => { setBiometricsPreference(true) - setDisplayBiometricsPreferenceScreen(false) + dispatch(dispatchUpdateDisplayBiometricsPreferenceScreen(false)) } return ( diff --git a/VAMobile/src/screens/SyncScreen/SyncScreen.tsx b/VAMobile/src/screens/SyncScreen/SyncScreen.tsx index 5f674e1a278..f113629316d 100644 --- a/VAMobile/src/screens/SyncScreen/SyncScreen.tsx +++ b/VAMobile/src/screens/SyncScreen/SyncScreen.tsx @@ -20,12 +20,12 @@ import { UserAnalytics } from 'constants/analytics' import { TimeFrameTypeConstants } from 'constants/appointments' import { NAMESPACE } from 'constants/namespaces' import { RootState } from 'store' -import { ErrorsState, checkForDowntimeErrors } from 'store/slices' +import { ErrorsState, checkForDowntimeErrors, dispatchUpdateSyncing } from 'store/slices' import { DemoState } from 'store/slices/demoSlice' import colors from 'styles/themes/VAColors' import { setAnalyticsUserProperty } from 'utils/analytics' import { getUpcomingAppointmentDateRange } from 'utils/appointments' -import { completeSync, loginFinish } from 'utils/auth' +import { loginFinish } from 'utils/auth' import getEnv from 'utils/env' import { useAppDispatch, useOrientation, useTheme } from 'utils/hooks' @@ -80,7 +80,7 @@ function SyncScreen({}: SyncScreenProps) { useEffect(() => { if (demoMode && !loggedIn) { - loginFinish(false) + loginFinish(dispatch, false) } }, [dispatch, demoMode, loggedIn, queryClient]) @@ -96,10 +96,20 @@ function SyncScreen({}: SyncScreenProps) { } if (!loggingOut && loggedIn && downtimeWindowsFetched && authorizedServicesFetched) { - completeSync() + dispatch(dispatchUpdateSyncing(false)) setAnalyticsUserProperty(UserAnalytics.vama_environment(ENVIRONMENT)) } - }, [loggedIn, loggingOut, downtimeWindowsFetched, authorizedServicesFetched, t, syncing, queryClient, ENVIRONMENT]) + }, [ + loggedIn, + loggingOut, + downtimeWindowsFetched, + authorizedServicesFetched, + t, + syncing, + queryClient, + ENVIRONMENT, + dispatch, + ]) return ( diff --git a/VAMobile/src/store/index.ts b/VAMobile/src/store/index.ts index 9ba80513f46..b3ea76466d8 100644 --- a/VAMobile/src/store/index.ts +++ b/VAMobile/src/store/index.ts @@ -2,6 +2,7 @@ import { Action, ThunkAction, configureStore } from '@reduxjs/toolkit' import accessabilityReducer from 'store/slices/accessibilitySlice' import analyticsReducer from 'store/slices/analyticsSlice' +import authReducer from 'store/slices/authSlice' import demoReducer from 'store/slices/demoSlice' import errorReducer from 'store/slices/errorSlice' import settingsReducer from 'store/slices/settingsSlice' @@ -11,6 +12,7 @@ import snackbarReducer from 'store/slices/snackBarSlice' const store = configureStore({ reducer: { accessibility: accessabilityReducer, + auth: authReducer, demo: demoReducer, errors: errorReducer, analytics: analyticsReducer, diff --git a/VAMobile/src/store/slices/authSlice.ts b/VAMobile/src/store/slices/authSlice.ts new file mode 100644 index 00000000000..09466858653 --- /dev/null +++ b/VAMobile/src/store/slices/authSlice.ts @@ -0,0 +1,51 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit' + +export type AuthState = { + loading: boolean + loggedIn: boolean + loggingOut: boolean + syncing: boolean + displayBiometricsPreferenceScreen: boolean +} + +export const initialAuthState: AuthState = { + loading: false, + loggedIn: false, + loggingOut: false, + syncing: false, + displayBiometricsPreferenceScreen: true, +} + +/** + * Redux slice that will create the actions and reducers + */ +const authSlice = createSlice({ + name: 'auth', + initialState: initialAuthState, + reducers: { + dispatchUpdateLoading: (state, action: PayloadAction) => { + state.loading = action.payload + }, + dispatchUpdateLoggedIn: (state, action: PayloadAction) => { + state.loggedIn = action.payload + }, + dispatchUpdateLoggingOut: (state, action: PayloadAction) => { + state.loggingOut = action.payload + }, + dispatchUpdateSyncing: (state, action: PayloadAction) => { + state.syncing = action.payload + }, + dispatchUpdateDisplayBiometricsPreferenceScreen: (state, action: PayloadAction) => { + state.displayBiometricsPreferenceScreen = action.payload + }, + }, +}) + +export const { + dispatchUpdateLoading, + dispatchUpdateLoggedIn, + dispatchUpdateLoggingOut, + dispatchUpdateSyncing, + dispatchUpdateDisplayBiometricsPreferenceScreen, +} = authSlice.actions +export default authSlice.reducer diff --git a/VAMobile/src/store/slices/index.ts b/VAMobile/src/store/slices/index.ts index b9eb4841903..896d9dd03be 100644 --- a/VAMobile/src/store/slices/index.ts +++ b/VAMobile/src/store/slices/index.ts @@ -6,8 +6,11 @@ import { initialErrorsState } from 'store/slices/errorSlice' import { initialSettingsState } from 'store/slices/settingsSlice' import { initialSnackBarState } from 'store/slices/snackBarSlice' +import { initialAuthState } from './authSlice' + export * from './accessibilitySlice' export * from './analyticsSlice' +export * from './authSlice' export * from './errorSlice' export * from './snackBarSlice' export * from './settingsSlice' @@ -15,6 +18,7 @@ export * from './settingsSlice' export const InitialState: RootState = { errors: initialErrorsState, accessibility: initialAccessibilityState, + auth: initialAuthState, demo: initialDemoState, analytics: initialAnalyticsState, snackBar: initialSnackBarState, diff --git a/VAMobile/src/testUtils.tsx b/VAMobile/src/testUtils.tsx index e06c0fc3dd5..3418472f6d0 100644 --- a/VAMobile/src/testUtils.tsx +++ b/VAMobile/src/testUtils.tsx @@ -19,6 +19,7 @@ import { RootState } from 'store' import { InitialState } from 'store/slices' import accessabilityReducer from 'store/slices/accessibilitySlice' import analyticsReducer from 'store/slices/analyticsSlice' +import authReducer from 'store/slices/authSlice' import demoReducer from 'store/slices/demoSlice' import errorReducer from 'store/slices/errorSlice' import settingsReducer from 'store/slices/settingsSlice' @@ -77,6 +78,7 @@ const getConfiguredStore = (state?: Partial) => { return configureStore({ reducer: { accessibility: accessabilityReducer as any, + auth: authReducer as any, demo: demoReducer as any, errors: errorReducer as any, analytics: analyticsReducer as any, @@ -206,14 +208,9 @@ function render(ui, { preloadedState, navigationProvided = false, queriesData, . }) queryClient.setQueryData(authKeys.settings, { firstTimeLogin: false, - loading: false, - loggedIn: false, - loggingOut: false, - syncing: false, } as UserAuthSettings) queryClient.setQueryData(authKeys.biometrics, { canStoreWithBiometric: true, - displayBiometricsPreferenceScreen: true, shouldStoreWithBiometric: true, supportedBiometric: '', } as UserBiometricsSettings) diff --git a/VAMobile/src/utils/auth.ts b/VAMobile/src/utils/auth.ts index 44567765ef2..ef3b05a9f1d 100644 --- a/VAMobile/src/utils/auth.ts +++ b/VAMobile/src/utils/auth.ts @@ -21,7 +21,9 @@ import { } from 'api/types' import { Events, UserAnalytics } from 'constants/analytics' import { EnvironmentTypesConstants } from 'constants/common' +import store, { AppDispatch } from 'store' import * as api from 'store/api' +import { dispatchUpdateLoading, dispatchUpdateLoggedIn, dispatchUpdateSyncing } from 'store/slices' import getEnv from 'utils/env' import { logAnalyticsEvent, logNonFatalErrorToFirebase, setAnalyticsUserProperty } from './analytics' @@ -270,23 +272,11 @@ export const debugResetFirstTimeLogin = async (logout: UseMutateFunction { - const userSettings = queryClient.getQueryData(authKeys.biometrics) as UserBiometricsSettings - queryClient.setQueryData(authKeys.biometrics, { ...userSettings, displayBiometricsPreferenceScreen: value }) -} - -/** - * Signal the sync process is completed - */ -export const completeSync = () => { - const userSettings = queryClient.getQueryData(authKeys.settings) as UserAuthSettings - queryClient.setQueryData(authKeys.settings, { ...userSettings, syncing: false }) -} - -export const finishInitialize = async (loggedIn: boolean, authCredentials?: AuthCredentialData) => { +export const finishInitialize = async ( + dispatch: AppDispatch, + loggedIn: boolean, + authCredentials?: AuthCredentialData, +) => { // check if staging or Google Pre-Launch test, staging or test and turn off analytics if that is the case if (utils().isRunningInTestLab || ENVIRONMENT === EnvironmentTypesConstants.Staging || __DEV__ || IS_TEST) { await crashlytics().setCrashlyticsCollectionEnabled(false) @@ -296,31 +286,31 @@ export const finishInitialize = async (loggedIn: boolean, authCredentials?: Auth const userSettings = queryClient.getQueryData(authKeys.settings) as UserAuthSettings queryClient.setQueryData(authKeys.settings, { ...userSettings, - loggedIn: loggedIn, - syncing: userSettings?.syncing && loggedIn, authCredentials: authCredentials, }) + dispatch(dispatchUpdateLoggedIn(loggedIn)) + dispatch(dispatchUpdateSyncing(loggedIn && store.getState().auth.syncing)) } -export const loginStart = async (syncing: boolean) => { +export const loginStart = async (dispatch: AppDispatch, syncing: boolean) => { await logAnalyticsEvent(Events.vama_login_start(true, false)) const userSettings = queryClient.getQueryData(authKeys.settings) as UserAuthSettings queryClient.setQueryData(authKeys.settings, { ...userSettings, - loading: true, - syncing: syncing, }) + dispatch(dispatchUpdateLoading(true)) + dispatch(dispatchUpdateSyncing(syncing)) } -export const loginFinish = async (isError: boolean, authCredentials?: AuthCredentialData) => { +export const loginFinish = async (dispatch: AppDispatch, isError: boolean, authCredentials?: AuthCredentialData) => { const userSettings = queryClient.getQueryData(authKeys.settings) as UserAuthSettings queryClient.setQueryData(authKeys.settings, { ...userSettings, authCredentials: authCredentials, - loading: isError, - loggedIn: !isError, - syncing: userSettings?.syncing && !isError, }) + dispatch(dispatchUpdateLoading(isError)) + dispatch(dispatchUpdateLoggedIn(!isError)) + dispatch(dispatchUpdateSyncing(store.getState().auth.syncing && !isError)) } export const processAuthResponse = async (response: Response): Promise => { @@ -346,12 +336,12 @@ export const processAuthResponse = async (response: Response): Promise void) => { +export const initializeAuth = async (dispatch: AppDispatch, refreshAccessToken: () => void) => { const pType = await getAuthLoginPromptType() if (pType === LOGIN_PROMPT_TYPE.UNLOCK) { - await finishInitialize(false) - await startBiometricsLogin(refreshAccessToken) + await finishInitialize(dispatch, false) + await startBiometricsLogin(dispatch, refreshAccessToken) return } else { const refreshToken = await retrieveRefreshToken() @@ -359,14 +349,14 @@ export const initializeAuth = async (refreshAccessToken: () => void) => { await refreshAccessToken() } else { await clearStoredAuthCreds() - await finishInitialize(false) + await finishInitialize(dispatch, false) } } } -const startBiometricsLogin = async (refreshAccessToken: () => void) => { - const userSettings = queryClient.getQueryData(authKeys.settings) as UserAuthSettings - if (userSettings.loading) { +const startBiometricsLogin = async (dispatch: AppDispatch, refreshAccessToken: () => void) => { + const loading = store.getState().auth.loading + if (loading) { return } await logAnalyticsEvent(Events.vama_login_start(true, true)) @@ -374,10 +364,10 @@ const startBiometricsLogin = async (refreshAccessToken: () => void) => { try { const refreshToken = await retrieveRefreshToken() if (refreshToken) { - loginStart(true) + loginStart(dispatch, true) await refreshAccessToken() } else { - await finishInitialize(false) + await finishInitialize(dispatch, false) } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) {