Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: [IOBP-238] IDPAY CODE onboarding flow Xstate and flow basics #4976

Closed
wants to merge 10 commits into from
63 changes: 63 additions & 0 deletions ts/features/idpay/codeOnboarding/navigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ParamListBase, RouteProp } from "@react-navigation/native";
import {
StackNavigationProp,
createStackNavigator
} from "@react-navigation/stack";

import * as React from "react";
import { View } from "react-native";
import { IDPayCodeOnboardingMachineProvider } from "./xstate/provider";

export const IDPayCodeOnboardingRoutes = {
IDPAY_CODE_ONBOARDING_MAIN: "IDPAY_CODE_ONBOARDING_MAIN",
IDPAY_CODE_ONBOARDING_INTRO: "IDPAY_CODE_ONBOARDING_INTRO",
IDPAY_CODE_ONBOARDING_END: "IDPAY_CODE_ONBOARDING_END",
IDPAY_CODE_ONBOARDING_RESULT: "IDPAY_CODE_ONBOARDING_RESULT"
} as const;

export type IDPayCodeOnboardingParamsList = {
[IDPayCodeOnboardingRoutes.IDPAY_CODE_ONBOARDING_INTRO]: undefined;
[IDPayCodeOnboardingRoutes.IDPAY_CODE_ONBOARDING_END]: undefined;
[IDPayCodeOnboardingRoutes.IDPAY_CODE_ONBOARDING_RESULT]: undefined;
};

const Stack = createStackNavigator<IDPayCodeOnboardingParamsList>();

export const IDPayCodeOnboardingNavigator = () => (
<IDPayCodeOnboardingMachineProvider>
<Stack.Navigator
initialRouteName={IDPayCodeOnboardingRoutes.IDPAY_CODE_ONBOARDING_INTRO}
headerMode={"none"}
screenOptions={{ gestureEnabled: false }}
>
<Stack.Screen
name={IDPayCodeOnboardingRoutes.IDPAY_CODE_ONBOARDING_INTRO}
component={MockScreen}
options={{ gestureEnabled: true }}
/>
<Stack.Screen
name={IDPayCodeOnboardingRoutes.IDPAY_CODE_ONBOARDING_END}
component={MockScreen}
/>
<Stack.Screen
name={IDPayCodeOnboardingRoutes.IDPAY_CODE_ONBOARDING_RESULT}
component={MockScreen}
/>
</Stack.Navigator>
</IDPayCodeOnboardingMachineProvider>
);

const MockScreen = () => <View></View>;

export type IDPayCodeOnboardingStackNavigationRouteProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: IDPayCodeOnboardingStackNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};

export type IDPayCodeOnboardingStackNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = StackNavigationProp<ParamList, RouteName>;
40 changes: 40 additions & 0 deletions ts/features/idpay/codeOnboarding/xstate/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable no-console */
import {
AppParamsList,
IOStackNavigationProp
} from "../../../../navigation/params/AppParamsList";

const createActionsImplementation = (
navigation: IOStackNavigationProp<AppParamsList, keyof AppParamsList>
// dispatch: ReturnType<typeof useIODispatch>
) => {
const authorizeUser = () => {
// CALL AUTH HOOK
console.log("authorizeUser");
};

const navigateToPinShowScreen = () => {
// navigation.navigate
console.log("navigateToPinShowScreen");
};
const navigateToErrorScreen = () => {
// navigation.navigate
console.log("navigateToErrorScreen");
};
const navigateToSuccessScreen = () => {
// navigation.navigate
console.log("navigateToSuccessScreen");
};
const quitFlow = () => {
navigation.pop();
};
return {
authorizeUser,
navigateToPinShowScreen,
navigateToErrorScreen,
navigateToSuccessScreen,
quitFlow
};
};

export { createActionsImplementation };
7 changes: 7 additions & 0 deletions ts/features/idpay/codeOnboarding/xstate/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as O from "fp-ts/lib/Option";

export type Context = { failure: O.Option<unknown>; isCodeEnabled: boolean };
export const INITIAL_CONTEXT: Context = {
failure: O.none,
isCodeEnabled: false
};
97 changes: 97 additions & 0 deletions ts/features/idpay/codeOnboarding/xstate/machine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { createMachine } from "xstate";
import { LOADING_TAG, WAITING_USER_INPUT_TAG } from "../../../../xstate/utils";
import { Context, INITIAL_CONTEXT } from "./context";

const createIDPayCodeOnboardingMachine = (isCodeEnabled: boolean) =>
createMachine(
{
/** @xstate-layout N4IgpgJg5mDOIC5QEkAiAFAggTQPoGFkBRXAeQDkAhUzAJVWXIHEBiJ03SzfAaQG0ADAF1EoAA4B7WAEsALtIkA7USAAeiAMwBGACwA6DToBMRrRoCcRgOwBWDTYBsAGhABPRAIC+nl2ix5CEgpqOgZmFgAxRmQAZQAJQREkEEkZeSUVdQQBF3ds718MHAJiMioaekYmPQYY9AAZHCrcRgAVWlIWGNa6VtwI+tIAdUSVVLkFZWSsk30rKy07XS0BLSNzHVzEGxsjPQcjAA4NBy0HQ6PHApA-YsCykMrmPUwAVVa40lpkAC1m15iRFoLDeH1wMVe+HwRBiMVGyXG6SmoCyNnMAj06J0h0OZg0AmOWi0WwQO3MehsOhsAgE2LJxgc11uAVKwQqYWqoM+3z+zFwAKBIPecXBkOhsL4WiS4ikEwy00Qlgceh0VksGg0RhsZiJmzc2zRFJ0OnM5gc5w0VgEGiZRRZQXKoSqL2FX1+-0BwK5-UwyHqr1oRHhMrSk0yiGNWkxtlW5nxZotVhJxisekONNM8x0DhNq1t-hKDseHL0TCI5CBmFazXQjBYtfIuDLFdoVaIqGDKVlSPDCDOJj0qxxVhMlNsNhJZKN1PxZzRJkO+burMdT2qzcr1b5DfrjCb5c3yAoPr9AaDwjG3bDCr7WgWKpHaw1hjW5hJhhsekWhwcNPmAg2O8l3tB52WdeJhhrOt8AoLdXnPaUu1DeUUUQLRjk-Q5dgcbUdl0bUNBJKx8QpVZaWzcxHBMIxvB8EBFAkCA4BUZlC1Ap1mEvZDkTURAAFojAxONv1-XRtEcEktVTHCsMo7UiQEBwrGAti2Q46pagaJo+TaDouLlHiZlTRTDh0MxzCw85jUnOwvyOE4zguSyVPuNS1xdD43V5Jh+U9fSexvDQf0HC1zF0JTTDMJN9VJWy1mOU5zkuRk6NY1zVxLDdWy3HyG3869UL7ewowAtFfyMDUcNpGy9i0dFCXMK0zAEGwXJXYtwM+IYoPIfKUN4vsLn0bUlKsY1BNMCcYqnSlqVpLC0RTNqizA55NMabBmjcjlRShGEYj6wzEGI5UdEUqwcPNU04ymvIR0-VZ1nOBYLAcONlvY9z1u0nztuaCJfX9QNDt7BwSJ0fF7AuzULLjd9tUHDUVkm+SP1ozwgA */
context: { ...INITIAL_CONTEXT, isCodeEnabled },
tsTypes: {} as import("./machine.typegen").Typegen0,
schema: {
context: {} as Context,
events: undefined,
services: undefined
},
predictableActionArguments: true,
id: "IDPAY_CODE_ONBOARDING",
initial: "DISPLAYING_INTRO",
on: {
GO_BACK: {
actions: "quitFlow"
},
FINISH: {
actions: "quitFlow"
}
},
states: {
DISPLAYING_INTRO: {
tags: [WAITING_USER_INPUT_TAG],
on: {
START_FLOW: {
target: "AUTHORIZING_USER"
}
}
},
AUTHORIZING_USER: {
tags: [WAITING_USER_INPUT_TAG],
on: {
AUTH_SUCCESS: [
{
cond: "pin_exists",
target: "DISPLAYING_ONBOARDING_SUCCESS"
},
{
target: "GENERATING_PIN"
}
],
AUTH_FAILURE: {
target: "DISPLAYING_ONBOARDING_FAILURE"
}
}
},
GENERATING_PIN: {
tags: [LOADING_TAG],
invoke: {
id: "generatePin",
src: "generatePin",
onDone: {
target: "SHOWING_PIN"
},
onError: {
target: "DISPLAYING_ONBOARDING_FAILURE"
}
}
},
SHOWING_PIN: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SHOWING_PIN: {
DISPLAYING_PIN: {

entry: "navigateToPinShowScreen",
tags: [WAITING_USER_INPUT_TAG],
on: {
CONTINUE: {
target: "DISPLAYING_ONBOARDING_SUCCESS"
}
}
},
DISPLAYING_ONBOARDING_SUCCESS: {
entry: "navigateToSuccessScreen",
tags: [WAITING_USER_INPUT_TAG]
},
DISPLAYING_ONBOARDING_FAILURE: {
entry: "navigateToErrorScreen",
tags: [WAITING_USER_INPUT_TAG]
}
}
},
{
guards: {
// this comes from the global state, context will contain the original value
pin_exists: (context: Context) => context.isCodeEnabled
}
}
);

type IDPayCodeOnboardingMachineType = ReturnType<
typeof createIDPayCodeOnboardingMachine
>;
export type { IDPayCodeOnboardingMachineType };
export { createIDPayCodeOnboardingMachine };
60 changes: 60 additions & 0 deletions ts/features/idpay/codeOnboarding/xstate/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useNavigation } from "@react-navigation/native";
import { useInterpret } from "@xstate/react";
import * as React from "react";
import { InterpreterFrom } from "xstate";
import {
AppParamsList,
IOStackNavigationProp
} from "../../../../navigation/params/AppParamsList";
import { useXStateMachine } from "../../../../xstate/hooks/useXStateMachine";
import { createActionsImplementation } from "./actions";
import {
IDPayCodeOnboardingMachineType,
createIDPayCodeOnboardingMachine
} from "./machine";
import { createServicesImplementation } from "./services";

type CodeOnboardingMachineContext =
InterpreterFrom<IDPayCodeOnboardingMachineType>;
const CodeOnboardingMachineContext =
React.createContext<CodeOnboardingMachineContext>(
{} as CodeOnboardingMachineContext
);

type Props = {
children: React.ReactNode;
};

const IDPayCodeOnboardingMachineProvider = (props: Props) => {
// const dispatch = useIODispatch();
const isIDPayCodeEnabled = true; // TODO: get from store
const machineGenerator = () =>
createIDPayCodeOnboardingMachine(isIDPayCodeEnabled);
const [machine] = useXStateMachine(machineGenerator);
const navigation = useNavigation<IOStackNavigationProp<AppParamsList>>();
const token = "token"; // get from wherever
const client = null; // ''

const actions = createActionsImplementation(
navigation
// dispatch
);
const services = createServicesImplementation(client, token);

const machineService = useInterpret(machine, { services, actions });

return (
<CodeOnboardingMachineContext.Provider value={machineService}>
{props.children}
</CodeOnboardingMachineContext.Provider>
);
};

const useCodeOnboardingMachineService = () =>
React.useContext(CodeOnboardingMachineContext);

export {
CodeOnboardingMachineContext,
IDPayCodeOnboardingMachineProvider,
useCodeOnboardingMachineService
};
29 changes: 29 additions & 0 deletions ts/features/idpay/codeOnboarding/xstate/services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Context } from "./context";

export type Services = {
generatePin: {
data: unknown;
};
};

const mapFetchError = (error: unknown) => {
if (error === "max-retries") {
return null;
}
return undefined;
};

const createServicesImplementation = (client: unknown, token: string) => {
const generatePin = async (context: Context) => {
// required to avoid errors while implementation is so barebone
// eslint-disable-next-line no-console
console.log(mapFetchError("max-retries"));
// eslint-disable-next-line no-console
console.log(client, token, context);

return new Promise((resolve, _reject) => resolve(true));
};
return { generatePin };
};

export { createServicesImplementation };
16 changes: 12 additions & 4 deletions ts/navigation/AuthenticatedStackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import { FciStackNavigator } from "../features/fci/navigation/FciStackNavigator"
import { FCI_ROUTES } from "../features/fci/navigation/routes";
import { FimsNavigator } from "../features/fims/navigation/navigator";
import FIMS_ROUTES from "../features/fims/navigation/routes";
import {
IDPayCodeOnboardingNavigator,
IDPayCodeOnboardingRoutes
} from "../features/idpay/codeOnboarding/navigation";
import {
IDPayConfigurationNavigator,
IDPayConfigurationRoutes
Expand All @@ -42,6 +46,10 @@ import {
import UnsupportedDeviceScreen from "../features/lollipop/screens/UnsupportedDeviceScreen";
import UADONATION_ROUTES from "../features/uaDonations/navigation/routes";
import { UAWebViewScreen } from "../features/uaDonations/screens/UAWebViewScreen";
import {
WalletOnboardingNavigator,
WalletOnboardingRoutes
} from "../features/walletV3/onboarding/navigation/navigator";
import { ZendeskStackNavigator } from "../features/zendesk/navigation/navigator";
import ZENDESK_ROUTES from "../features/zendesk/navigation/routes";
import { useIOSelector } from "../store/hooks";
Expand All @@ -52,10 +60,6 @@ import {
isFIMSEnabledSelector,
isIdPayEnabledSelector
} from "../store/reducers/backendStatus";
import {
WalletOnboardingNavigator,
WalletOnboardingRoutes
} from "../features/walletV3/onboarding/navigation/navigator";
import { isGestureEnabled } from "../utils/navigation";
import { MessagesStackNavigator } from "./MessagesNavigator";
import OnboardingNavigator from "./OnboardingNavigator";
Expand Down Expand Up @@ -175,6 +179,10 @@ const AuthenticatedStackNavigator = () => {
component={IDpayDetailsNavigator}
options={{ gestureEnabled: isGestureEnabled }}
/>
<Stack.Screen
name={IDPayCodeOnboardingRoutes.IDPAY_CODE_ONBOARDING_MAIN}
component={IDPayCodeOnboardingNavigator}
/>
<Stack.Screen
name={IDPayConfigurationRoutes.IDPAY_CONFIGURATION_MAIN}
component={IDPayConfigurationNavigator}
Expand Down
15 changes: 10 additions & 5 deletions ts/navigation/params/AppParamsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import { FCI_ROUTES } from "../../features/fci/navigation/routes";
import { FimsParamsList } from "../../features/fims/navigation/params";
import FIMS_ROUTES from "../../features/fims/navigation/routes";
import {
IDPayPaymentParamsList,
IDPayPaymentRoutes
} from "../../features/idpay/payment/navigation/navigator";
IDPayCodeOnboardingParamsList,
IDPayCodeOnboardingRoutes
} from "../../features/idpay/codeOnboarding/navigation";
import {
IDPayConfigurationParamsList,
IDPayConfigurationRoutes
Expand All @@ -32,17 +32,21 @@ import {
IDPayOnboardingParamsList,
IDPayOnboardingRoutes
} from "../../features/idpay/onboarding/navigation/navigator";
import {
IDPayPaymentParamsList,
IDPayPaymentRoutes
} from "../../features/idpay/payment/navigation/navigator";
import {
IDPayUnsubscriptionNavigatorParams,
IDPayUnsubscriptionParamsList,
IDPayUnsubscriptionRoutes
} from "../../features/idpay/unsubscription/navigation/navigator";
import UADONATION_ROUTES from "../../features/uaDonations/navigation/routes";
import { UAWebviewScreenNavigationParams } from "../../features/uaDonations/screens/UAWebViewScreen";
import {
WalletOnboardingParamsList,
WalletOnboardingRoutes
} from "../../features/walletV3/onboarding/navigation/navigator";
import UADONATION_ROUTES from "../../features/uaDonations/navigation/routes";
import { UAWebviewScreenNavigationParams } from "../../features/uaDonations/screens/UAWebViewScreen";
import { ZendeskParamsList } from "../../features/zendesk/navigation/params";
import ZENDESK_ROUTES from "../../features/zendesk/navigation/routes";
import ROUTES from "../routes";
Expand Down Expand Up @@ -84,6 +88,7 @@ export type AppParamsList = {
[IDPayOnboardingRoutes.IDPAY_ONBOARDING_MAIN]: NavigatorScreenParams<IDPayOnboardingParamsList>;
[IDPayConfigurationRoutes.IDPAY_CONFIGURATION_MAIN]: NavigatorScreenParams<IDPayConfigurationParamsList>;
[IDPayDetailsRoutes.IDPAY_DETAILS_MAIN]: NavigatorScreenParams<IDPayDetailsParamsList>;
[IDPayCodeOnboardingRoutes.IDPAY_CODE_ONBOARDING_MAIN]: NavigatorScreenParams<IDPayCodeOnboardingParamsList>;
[IDPayUnsubscriptionRoutes.IDPAY_UNSUBSCRIPTION_MAIN]:
| NavigatorScreenParams<IDPayUnsubscriptionParamsList>
| IDPayUnsubscriptionNavigatorParams;
Expand Down
Loading