Update CBS Mobile App codebase
This commit is contained in:
@@ -1,19 +1,108 @@
|
||||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
|
||||
import { useAppSelector } from '../app/hooks';
|
||||
import {
|
||||
selectLastProfile,
|
||||
selectSignInContextLoaded,
|
||||
} from '../modules/auth/store/authSelectors';
|
||||
import { FeatureKey } from '../constants/featureKeys';
|
||||
import { useFeature } from '../features';
|
||||
import type { AuthStackParamList } from './types';
|
||||
import LoginScreen from '../modules/auth/screens/LoginScreen';
|
||||
import RegisterScreen from '../modules/auth/screens/RegisterScreen';
|
||||
import VerifyOtpScreen from '../modules/auth/screens/VerifyOtpScreen';
|
||||
import CreateMpinScreen from '../modules/auth/screens/CreateMpinScreen';
|
||||
import MpinLockedScreen from '../modules/auth/screens/MpinLockedScreen';
|
||||
|
||||
const Stack = createNativeStackNavigator<AuthStackParamList>();
|
||||
|
||||
export const AuthNavigator = () => (
|
||||
<Stack.Navigator
|
||||
initialRouteName="Login"
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="Login" component={LoginScreen} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
export const AuthNavigator = () => {
|
||||
const lastProfile = useAppSelector(selectLastProfile);
|
||||
const contextLoaded = useAppSelector(selectSignInContextLoaded);
|
||||
|
||||
/**
|
||||
* Which screen a customer owes us depends on whether this device has ever
|
||||
* completed registration: a stored profile means they only owe an MPIN,
|
||||
* nothing stored means registration.
|
||||
*
|
||||
* Decided here rather than by navigating out of the sign-in screen. An
|
||||
* effect-driven `replace()` on a screen's first render races the navigation
|
||||
* container's readiness and the action is silently dropped — and the warning
|
||||
* that would tell you so is stripped from release builds, so it fails only
|
||||
* where it is hardest to see. This is also flash-free.
|
||||
*
|
||||
* `contextLoaded` is what makes it safe: before the Keystore has been read, a
|
||||
* null profile means "unknown", not "never registered".
|
||||
*/
|
||||
/**
|
||||
* Registration is switchable, and turning it off unregisters the three screens
|
||||
* that make it up — so the initial route has to fall back to sign-in, or it
|
||||
* would name a route that no longer exists and the navigator would throw.
|
||||
*
|
||||
* A device with nothing stored then lands on a sign-in screen it cannot
|
||||
* complete, which is the correct outcome: the bank has stopped onboarding, and
|
||||
* the app should say nothing rather than offer a flow that will be refused.
|
||||
*/
|
||||
const registrationEnabled = useFeature(FeatureKey.AUTH_REGISTER);
|
||||
|
||||
/**
|
||||
* Read once, on the navigator's first render, and frozen from then on.
|
||||
*
|
||||
* `initialRouteName` is not a live prop — React Navigation rebuilds its route
|
||||
* list when it changes, and the inputs here are anything but stable:
|
||||
* verify-otp records a `lastProfile` and flips this from 'Register' to
|
||||
* 'Login' in the very same tick that the OTP screen calls `reset()`. The
|
||||
* rebuild and the reset then describe two different stacks to Fabric in one
|
||||
* commit, and mounting the result throws "The specified child already has a
|
||||
* parent" — a red box on the way in, and a blank white screen once it is
|
||||
* dismissed, with no way to sign in.
|
||||
*
|
||||
* Freezing it costs nothing, because nothing here needs it to be live:
|
||||
* `lastProfile` arriving later is what `LoginScreen` already watches, and it
|
||||
* routes to registration itself.
|
||||
*/
|
||||
const frozenInitialRoute = useRef<keyof AuthStackParamList | null>(null);
|
||||
|
||||
if (frozenInitialRoute.current === null) {
|
||||
frozenInitialRoute.current =
|
||||
registrationEnabled && contextLoaded && !lastProfile ? 'Register' : 'Login';
|
||||
}
|
||||
|
||||
const initialRouteName = frozenInitialRoute.current;
|
||||
|
||||
return (
|
||||
<Stack.Navigator
|
||||
initialRouteName={initialRouteName}
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="Login" component={LoginScreen} />
|
||||
{registrationEnabled && (
|
||||
<>
|
||||
<Stack.Screen name="Register" component={RegisterScreen} />
|
||||
<Stack.Screen name="VerifyOtp" component={VerifyOtpScreen} />
|
||||
</>
|
||||
)}
|
||||
{/* Deliberately still registered when registration is switched off. A
|
||||
customer who has already passed OTP holds a single-use setup token —
|
||||
unregistering this screen under them would leave them unable to finish
|
||||
and unable to start again, because the token is spent. Switching the
|
||||
feature off closes the entrance; it does not trap anyone inside.
|
||||
|
||||
No swipe back out of MPIN creation, for that same single-use reason. */}
|
||||
<Stack.Screen
|
||||
name="CreateMpin"
|
||||
component={CreateMpinScreen}
|
||||
options={{ gestureEnabled: false }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="MpinLocked"
|
||||
component={MpinLockedScreen}
|
||||
options={{ gestureEnabled: false, animation: 'fade' }}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user