diff --git a/ts/boot/__tests__/__snapshots__/persistedStore.test.ts.snap b/ts/boot/__tests__/__snapshots__/persistedStore.test.ts.snap index 0e0dec8563e..1cdfedcdba0 100644 --- a/ts/boot/__tests__/__snapshots__/persistedStore.test.ts.snap +++ b/ts/boot/__tests__/__snapshots__/persistedStore.test.ts.snap @@ -34,6 +34,7 @@ exports[`Check the addition for new fields to the persisted store. If one of thi exports[`Check the addition for new fields to the persisted store. If one of this test fails, check that exists the migration before updating the snapshot! Freeze 'debug' state 1`] = ` { + "debugData": {}, "isDebugModeEnabled": false, } `; diff --git a/ts/boot/configureStoreAndPersistor.ts b/ts/boot/configureStoreAndPersistor.ts index 092be3a236d..0013dcc706b 100644 --- a/ts/boot/configureStoreAndPersistor.ts +++ b/ts/boot/configureStoreAndPersistor.ts @@ -438,13 +438,12 @@ const rootPersistConfig: PersistConfig = { migrate: createMigrate(migrations, { debug: isDevEnv }), // Entities and features implement a persisted reduce that avoids persisting messages. // Other entities section will be persisted - blacklist: ["entities", "features", "lollipop"], + blacklist: ["debug", "entities", "features", "lollipop"], // Sections of the store that must be persisted and rehydrated with this storage. whitelist: [ "onboarding", "notifications", "profile", - "debug", "persistedPreferences", "installation", "payments", diff --git a/ts/components/DebugInfoOverlay.tsx b/ts/components/DebugInfoOverlay.tsx index e5b903d4e5e..dd3eb7dd1d4 100644 --- a/ts/components/DebugInfoOverlay.tsx +++ b/ts/components/DebugInfoOverlay.tsx @@ -1,30 +1,31 @@ +import { + HStack, + IOColors, + VStack, + hexToRgba, + makeFontStyleObject +} from "@pagopa/io-app-design-system"; import * as React from "react"; +import { useState } from "react"; import { - StyleSheet, + Platform, Pressable, SafeAreaView, - View, - Text, - Platform + StyleSheet, + Text } from "react-native"; -import { connect } from "react-redux"; -import { useState } from "react"; import { widthPercentageToDP } from "react-native-responsive-screen"; -import { - HSpacer, - IOColors, - IOStyles, - hexToRgba, - makeFontStyleObject -} from "@pagopa/io-app-design-system"; +import { connect } from "react-redux"; import { ReduxProps } from "../store/actions/types"; +import { useIOSelector } from "../store/hooks"; import { currentRouteSelector } from "../store/reducers/navigation"; +import { isPagoPATestEnabledSelector } from "../store/reducers/persistedPreferences"; import { GlobalState } from "../store/reducers/types"; import { getAppVersion } from "../utils/appVersion"; import { clipboardSetStringWithFeedback } from "../utils/clipboard"; -import { useIOSelector } from "../store/hooks"; -import { isPagoPATestEnabledSelector } from "../store/reducers/persistedPreferences"; import PagoPATestIndicator from "./PagoPATestIndicator"; +import { DebugDataIndicator } from "./debug/DebugDataIndicator"; +import { DebugDataOverlay } from "./debug/DebugDataOverlay"; type Props = ReturnType & ReduxProps; @@ -66,48 +67,57 @@ const styles = StyleSheet.create({ borderRadius: 8, maxWidth: widthPercentageToDP(80), paddingHorizontal: 8, - backgroundColor: debugItemBgColor, - marginTop: 4 + backgroundColor: debugItemBgColor } }); const DebugInfoOverlay: React.FunctionComponent = (props: Props) => { const appVersion = getAppVersion(); const [showRootName, setShowRootName] = useState(true); + const [isDebugDataVisibile, showDebugData] = useState(false); const isPagoPATestEnabled = useIOSelector(isPagoPATestEnabledSelector); const appVersionText = `v. ${appVersion}`; return ( - - - setShowRootName(prevState => !prevState)} - accessibilityRole="button" - accessibilityLabel={appVersionText} - accessibilityHint={"Tap here to show/hide the root name"} - > - {appVersionText} - - {isPagoPATestEnabled && ( - <> - - - - )} - - {showRootName && ( - clipboardSetStringWithFeedback(props.screenNameDebug)} - > - {props.screenNameDebug} - + <> + + + + setShowRootName(prevState => !prevState)} + accessibilityRole="button" + accessibilityLabel={appVersionText} + accessibilityHint={"Tap here to show/hide the root name"} + > + {appVersionText} + + {isPagoPATestEnabled && } + + {showRootName && ( + + clipboardSetStringWithFeedback(props.screenNameDebug) + } + > + + {props.screenNameDebug} + + + )} + showDebugData(prevState => !prevState)} + /> + + + {isDebugDataVisibile && ( + showDebugData(false)} /> )} - + ); }; diff --git a/ts/components/DebugPrettyPrint.tsx b/ts/components/DebugPrettyPrint.tsx deleted file mode 100644 index 79e7c3f7590..00000000000 --- a/ts/components/DebugPrettyPrint.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* -WARNING: This component is not referenced anywhere, but is used -for development purposes. for development purposes. Don't REMOVE it! -*/ -import { - IOColors, - Icon, - LabelSmall, - useTypographyFactory -} from "@pagopa/io-app-design-system"; -import React from "react"; -import { Pressable, View } from "react-native"; - -type Props = { - title?: string; - data?: any; - startCollapsed?: boolean; -}; - -const CustomBodyMonospace = (props: { children?: React.ReactNode }) => - useTypographyFactory({ - ...props, - defaultWeight: "Medium", - defaultColor: "bluegrey", - font: "DMMono", - fontStyle: { fontSize: 12, lineHeight: 18 } - }); - -/** - * This simple component allows to print the content of an object in an elegant and readable way. - */ -export const DebugPrettyPrint = ({ - title, - data, - startCollapsed = false -}: Props) => { - const [expanded, setExpanded] = React.useState(!startCollapsed); - - return ( - <> - {title && ( - setExpanded(_ => !_)} - > - - {title} - - - - )} - {expanded && ( - - - {JSON.stringify(data, null, 2)} - - - )} - - ); -}; diff --git a/ts/components/debug/DebugDataIndicator.tsx b/ts/components/debug/DebugDataIndicator.tsx new file mode 100644 index 00000000000..7a44ddaab72 --- /dev/null +++ b/ts/components/debug/DebugDataIndicator.tsx @@ -0,0 +1,63 @@ +import { + HStack, + IOColors, + Icon, + hexToRgba, + makeFontStyleObject +} from "@pagopa/io-app-design-system"; +import _ from "lodash"; +import * as React from "react"; +import { Pressable, StyleSheet, Text } from "react-native"; +import { useIOSelector } from "../../store/hooks"; +import { debugDataSelector } from "../../store/reducers/debug"; + +type DebugDataIndicatorProps = { + onPress: () => void; +}; + +export const DebugDataIndicator = (props: DebugDataIndicatorProps) => { + const data = useIOSelector(debugDataSelector); + const dataSize = _.size(data); + + if (dataSize === 0) { + return null; + } + + return ( + + + + {dataSize} + + + ); +}; + +const debugItemBgColor = hexToRgba(IOColors["warning-500"], 0.4); +const debugItemBorderColor = hexToRgba(IOColors["warning-850"], 0.1); + +const styles = StyleSheet.create({ + wrapper: { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + borderColor: debugItemBorderColor, + borderWidth: 1, + paddingHorizontal: 6, + borderRadius: 8, + backgroundColor: debugItemBgColor + }, + text: { + letterSpacing: 0.2, + fontSize: 14, + textTransform: "uppercase", + color: IOColors["warning-850"], + ...makeFontStyleObject("Semibold") + } +}); diff --git a/ts/components/debug/DebugDataOverlay.tsx b/ts/components/debug/DebugDataOverlay.tsx new file mode 100644 index 00000000000..3b01112e977 --- /dev/null +++ b/ts/components/debug/DebugDataOverlay.tsx @@ -0,0 +1,68 @@ +import React from "react"; +import { + ScrollView, + StyleSheet, + TouchableWithoutFeedback, + View +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { useIOSelector } from "../../store/hooks"; +import { debugDataSelector } from "../../store/reducers/debug"; +import { DebugPrettyPrint } from "./DebugPrettyPrint"; + +type DebugDataOverlayProps = { + onDismissed?: () => void; +}; + +export const DebugDataOverlay = ({ onDismissed }: DebugDataOverlayProps) => { + const debugData = useIOSelector(debugDataSelector); + return ( + + + + + + {Object.entries(debugData).map(([key, value]) => ( + + ))} + + + ); +}; + +const overlayColor = "#000000B0"; + +const styles = StyleSheet.create({ + container: { + position: "absolute", + top: 0, + bottom: 0, + left: 0, + right: 0, + zIndex: 999, + paddingTop: 60 + }, + overlay: { + position: "absolute", + top: 0, + bottom: 0, + left: 0, + right: 0, + backgroundColor: overlayColor + }, + scroll: { + flexGrow: 0 + }, + scrollContainer: { + paddingHorizontal: 16 + } +}); diff --git a/ts/components/debug/DebugPrettyPrint.tsx b/ts/components/debug/DebugPrettyPrint.tsx new file mode 100644 index 00000000000..65bc1991c0b --- /dev/null +++ b/ts/components/debug/DebugPrettyPrint.tsx @@ -0,0 +1,114 @@ +/* +WARNING: This component is not referenced anywhere, but is used +for development purposes. for development purposes. Don't REMOVE it! +*/ +import { + HStack, + IOColors, + IconButton, + LabelSmall, + useTypographyFactory +} from "@pagopa/io-app-design-system"; +import React from "react"; +import { StyleSheet, View } from "react-native"; +import { Prettify } from "../../types/helpers"; +import { clipboardSetStringWithFeedback } from "../../utils/clipboard"; +import { withDebugEnabled } from "./withDebugEnabled"; + +type ExpandableProps = + | { + expandable: true; + isExpanded?: boolean; + } + | { + expandable?: false; + isExpanded?: undefined; + }; + +type Props = Prettify< + { + title: string; + data: any; + } & ExpandableProps +>; + +/** + * This component allows to print the content of an object in an elegant and readable way. + * and to copy its content to the clipboard by pressing on the title. + * The component it is rendered only if debug mode is enabled + */ +export const DebugPrettyPrint = withDebugEnabled( + ({ title, data, expandable = true, isExpanded = false }: Props) => { + const [expanded, setExpanded] = React.useState(isExpanded); + const prettyData = React.useMemo( + () => JSON.stringify(data, null, 2), + [data] + ); + + const content = React.useMemo(() => { + if ((expandable && !expanded) || !expandable) { + return null; + } + return ( + + {prettyData} + + ); + }, [prettyData, expandable, expanded]); + + return ( + + + + {title} + + + clipboardSetStringWithFeedback(prettyData)} + color="contrast" + /> + {expandable && ( + setExpanded(_ => !_)} + color="contrast" + /> + )} + + + {content} + + ); + } +); + +const CustomBodyMonospace = (props: { children?: React.ReactNode }) => + useTypographyFactory({ + ...props, + defaultWeight: "Medium", + defaultColor: "bluegrey", + font: "DMMono", + fontStyle: { fontSize: 12, lineHeight: 18 } + }); + +const styles = StyleSheet.create({ + container: { + borderRadius: 4, + overflow: "hidden", + marginVertical: 4 + }, + header: { + backgroundColor: IOColors["error-600"], + padding: 12, + flex: 1, + flexDirection: "row", + justifyContent: "space-between" + }, + content: { + backgroundColor: IOColors["grey-50"], + padding: 8 + } +}); diff --git a/ts/components/debug/DebugView.tsx b/ts/components/debug/DebugView.tsx new file mode 100644 index 00000000000..1ef44f14007 --- /dev/null +++ b/ts/components/debug/DebugView.tsx @@ -0,0 +1,39 @@ +import { + IOColors, + IOVisualCostants, + ListItemHeader +} from "@pagopa/io-app-design-system"; +import React from "react"; +import { View } from "react-native"; +import { withDebugEnabled } from "./withDebugEnabled"; + +export type DebugViewProps = { + title?: string; + ignoreHorizontalMargins?: boolean; +}; + +/** + * This component renders its content only if the debug mode is enabled, otherwise return null (nothing) + */ +export const DebugView = withDebugEnabled( + ({ + children, + title = "Debug", + ignoreHorizontalMargins = false + }: React.PropsWithChildren) => ( + + + {children} + + ) +); diff --git a/ts/components/debug/__tests__/DebugPrettyPrint.test.tsx b/ts/components/debug/__tests__/DebugPrettyPrint.test.tsx new file mode 100644 index 00000000000..c71cac7dd3f --- /dev/null +++ b/ts/components/debug/__tests__/DebugPrettyPrint.test.tsx @@ -0,0 +1,39 @@ +import { render } from "@testing-library/react-native"; +import _ from "lodash"; +import React from "react"; +import { Provider } from "react-redux"; +import configureMockStore from "redux-mock-store"; +import { applicationChangeState } from "../../../store/actions/application"; +import { appReducer } from "../../../store/reducers"; +import { GlobalState } from "../../../store/reducers/types"; +import { DebugPrettyPrint } from "../DebugPrettyPrint"; + +describe("DebugPrettyPrint", () => { + it("should render its content if debug mode is enabled", () => { + const { queryByTestId } = renderComponent(true); + expect(queryByTestId("DebugPrettyPrintTestID")).not.toBeNull(); + }); + it("should not render its content if debug mode is disabled", () => { + const { queryByTestId } = renderComponent(false); + expect(queryByTestId("DebugPrettyPrintTestID")).toBeNull(); + }); +}); + +const renderComponent = (isDebugModeEnabled: boolean) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + + const mockStore = configureMockStore(); + const store: ReturnType = mockStore( + _.merge(undefined, globalState, { + debug: { + isDebugModeEnabled + } + } as GlobalState) + ); + + return render( + + + + ); +}; diff --git a/ts/components/debug/__tests__/DebugView.test.tsx b/ts/components/debug/__tests__/DebugView.test.tsx new file mode 100644 index 00000000000..d386be1313c --- /dev/null +++ b/ts/components/debug/__tests__/DebugView.test.tsx @@ -0,0 +1,42 @@ +import { H2 } from "@pagopa/io-app-design-system"; +import { render } from "@testing-library/react-native"; +import _ from "lodash"; +import React from "react"; +import { Provider } from "react-redux"; +import configureMockStore from "redux-mock-store"; +import { applicationChangeState } from "../../../store/actions/application"; +import { appReducer } from "../../../store/reducers"; +import { GlobalState } from "../../../store/reducers/types"; +import { DebugView } from "../DebugView"; + +describe("DebugView", () => { + it("should render its content if debug mode is enabled", () => { + const { queryByTestId } = renderComponent(true); + expect(queryByTestId("DebugViewTestID")).not.toBeNull(); + }); + it("should not render its content if debug mode is disabled", () => { + const { queryByTestId } = renderComponent(false); + expect(queryByTestId("DebugViewTestID")).toBeNull(); + }); +}); + +const renderComponent = (isDebugModeEnabled: boolean) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + + const mockStore = configureMockStore(); + const store: ReturnType = mockStore( + _.merge(undefined, globalState, { + debug: { + isDebugModeEnabled + } + } as GlobalState) + ); + + return render( + + +

Hello!

+
+
+ ); +}; diff --git a/ts/components/debug/withDebugEnabled.tsx b/ts/components/debug/withDebugEnabled.tsx new file mode 100644 index 00000000000..72eb5eccea5 --- /dev/null +++ b/ts/components/debug/withDebugEnabled.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import { useIOSelector } from "../../store/hooks"; +import { isDebugModeEnabledSelector } from "../../store/reducers/debug"; + +/** + * This HOC allows to render the wrapped component only if the debug mode is enabled, otherwise returns null (nothing) + */ +export const withDebugEnabled = + (WrappedComponent: React.ComponentType

) => + (props: P) => { + const isDebug = useIOSelector(isDebugModeEnabledSelector); + if (!isDebug) { + return null; + } + return ; + }; diff --git a/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialFailureScreen.tsx b/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialFailureScreen.tsx index 1f9c34efb95..3fc1e040c3d 100644 --- a/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialFailureScreen.tsx +++ b/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialFailureScreen.tsx @@ -1,16 +1,12 @@ -import { Alert } from "@pagopa/io-app-design-system"; import { constNull, pipe } from "fp-ts/lib/function"; import * as O from "fp-ts/lib/Option"; -import * as E from "fp-ts/lib/Either"; -import * as t from "io-ts"; import React from "react"; import { OperationResultScreenContent, OperationResultScreenContentProps } from "../../../../components/screens/OperationResultScreenContent"; +import { useDebugInfo } from "../../../../hooks/useDebugInfo"; import I18n from "../../../../i18n"; -import { useIOSelector } from "../../../../store/hooks"; -import { isDebugModeEnabledSelector } from "../../../../store/reducers/debug"; import { CredentialIssuanceFailure, CredentialIssuanceFailureType, @@ -41,6 +37,10 @@ const ContentView = ({ failure }: ContentViewProps) => { const closeIssuance = () => machineRef.send({ type: "close" }); const retryIssuance = () => machineRef.send({ type: "retry" }); + useDebugInfo({ + failure + }); + const resultScreensMap: Record< CredentialIssuanceFailureType, OperationResultScreenContentProps @@ -63,25 +63,5 @@ const ContentView = ({ failure }: ContentViewProps) => { }; const resultScreenProps = resultScreensMap[failure.type]; - return ( - - - - ); -}; - -const ErrorAlertDebugOnly = ({ failure }: ContentViewProps) => { - const isDebug = useIOSelector(isDebugModeEnabledSelector); - - if (!isDebug) { - return null; - } - - const errorText = pipe( - failure.reason instanceof Error ? failure.reason.message : failure.reason, - t.string.decode, - E.getOrElse(() => "Unknown error") - ); - - return ; + return ; }; diff --git a/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialPreviewScreen.tsx b/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialPreviewScreen.tsx index 3b57fdc6a58..48f857d2cc2 100644 --- a/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialPreviewScreen.tsx +++ b/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialPreviewScreen.tsx @@ -13,11 +13,13 @@ import React from "react"; import { SafeAreaView, View } from "react-native"; import { FooterActions } from "../../../../components/ui/FooterActions"; import { LoadingIndicator } from "../../../../components/ui/LoadingIndicator"; +import { useDebugInfo } from "../../../../hooks/useDebugInfo"; import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel"; import I18n from "../../../../i18n"; import { useIONavigation } from "../../../../navigation/params/AppParamsList"; import { identificationRequest } from "../../../../store/actions/identification"; import { useIODispatch } from "../../../../store/hooks"; +import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton"; import { ItwCredentialClaimsList } from "../../common/components/ItwCredentialClaimList"; import { useItwDisbleGestureNavigation } from "../../common/hooks/useItwDisbleGestureNavigation"; import { useItwDismissalDialog } from "../../common/hooks/useItwDismissalDialog"; @@ -30,7 +32,6 @@ import { selectIsLoading } from "../../machine/credential/selectors"; import { ItwCredentialIssuanceMachineContext } from "../../machine/provider"; -import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton"; export const ItwIssuanceCredentialPreviewScreen = () => { const credentialTypeOption = ItwCredentialIssuanceMachineContext.useSelector( @@ -96,6 +97,10 @@ const ContentView = ({ credentialType, credential }: ContentViewProps) => { goBack: dismissDialog.show }); + useDebugInfo({ + parsedCredential: credential.parsedCredential + }); + return ( diff --git a/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialTrustIssuerScreen.tsx b/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialTrustIssuerScreen.tsx index c62099db45c..c7491ec9eed 100644 --- a/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialTrustIssuerScreen.tsx +++ b/ts/features/itwallet/issuance/screens/ItwIssuanceCredentialTrustIssuerScreen.tsx @@ -15,9 +15,11 @@ import * as O from "fp-ts/lib/Option"; import React from "react"; import { ImageURISource, StyleSheet, View } from "react-native"; import { FooterActions } from "../../../../components/ui/FooterActions"; +import { useDebugInfo } from "../../../../hooks/useDebugInfo"; import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel"; import I18n from "../../../../i18n"; import { useIOSelector } from "../../../../store/hooks"; +import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton"; import ItwMarkdown from "../../common/components/ItwMarkdown"; import { useItwDisbleGestureNavigation } from "../../common/hooks/useItwDisbleGestureNavigation"; import { useItwDismissalDialog } from "../../common/hooks/useItwDismissalDialog"; @@ -40,7 +42,6 @@ import { ItwRequestedClaimsList, RequiredClaim } from "../components/ItwRequiredClaimsList"; -import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton"; const ItwIssuanceCredentialTrustIssuerScreen = () => { const eidOption = useIOSelector(itwCredentialsEidSelector); @@ -92,6 +93,11 @@ const ContentView = ({ credentialType, eid }: ContentViewProps) => { useHeaderSecondLevel({ title: "", goBack: dismissDialog.show }); + useDebugInfo({ + issuerConfOption, + parsedCredential: eid.parsedCredential + }); + const claims = parseClaims(eid.parsedCredential, { exclude: ["unique_id"] }); const requiredClaims = claims.map( claim => diff --git a/ts/features/itwallet/issuance/screens/ItwIssuanceEidFailureScreen.tsx b/ts/features/itwallet/issuance/screens/ItwIssuanceEidFailureScreen.tsx index 96f6c44bb96..abbb200e3d2 100644 --- a/ts/features/itwallet/issuance/screens/ItwIssuanceEidFailureScreen.tsx +++ b/ts/features/itwallet/issuance/screens/ItwIssuanceEidFailureScreen.tsx @@ -1,21 +1,18 @@ -import React from "react"; -import * as t from "io-ts"; import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; -import { Alert } from "@pagopa/io-app-design-system"; +import React from "react"; import { OperationResultScreenContent, OperationResultScreenContentProps } from "../../../../components/screens/OperationResultScreenContent"; +import { useDebugInfo } from "../../../../hooks/useDebugInfo"; import I18n from "../../../../i18n"; import { IssuanceFailure, IssuanceFailureType } from "../../machine/eid/failure"; -import { ItwEidIssuanceMachineContext } from "../../machine/provider"; import { selectFailureOption } from "../../machine/eid/selectors"; -import { useIOSelector } from "../../../../store/hooks"; -import { isDebugModeEnabledSelector } from "../../../../store/reducers/debug"; +import { ItwEidIssuanceMachineContext } from "../../machine/provider"; export const ItwIssuanceEidFailureScreen = () => { const machineRef = ItwEidIssuanceMachineContext.useActorRef(); @@ -25,6 +22,10 @@ export const ItwIssuanceEidFailureScreen = () => { const closeIssuance = () => machineRef.send({ type: "close" }); const ContentView = ({ failure }: { failure: IssuanceFailure }) => { + useDebugInfo({ + failure + }); + const resultScreensMap: Record< IssuanceFailureType, OperationResultScreenContentProps @@ -98,11 +99,7 @@ export const ItwIssuanceEidFailureScreen = () => { const resultScreenProps = resultScreensMap[failure.type] ?? resultScreensMap.GENERIC; - return ( - - - - ); + return ; }; return pipe( @@ -113,21 +110,3 @@ export const ItwIssuanceEidFailureScreen = () => { ) ); }; - -const ErrorAlertDebugOnly = ({ failure }: { failure: IssuanceFailure }) => { - const isDebug = useIOSelector(isDebugModeEnabledSelector); - - if (!isDebug) { - return null; - } - - const renderErrorText = () => - pipe( - failure.reason instanceof Error ? failure.reason.message : failure.reason, - t.string.decode, - O.fromEither, - O.getOrElse(() => "Unknown error") - ); - - return ; -}; diff --git a/ts/features/itwallet/issuance/screens/ItwIssuanceEidPreviewScreen.tsx b/ts/features/itwallet/issuance/screens/ItwIssuanceEidPreviewScreen.tsx index 087f0613b1f..969241736cd 100644 --- a/ts/features/itwallet/issuance/screens/ItwIssuanceEidPreviewScreen.tsx +++ b/ts/features/itwallet/issuance/screens/ItwIssuanceEidPreviewScreen.tsx @@ -4,10 +4,12 @@ import { pipe } from "fp-ts/lib/function"; import React from "react"; import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent"; import { IOScrollViewWithLargeHeader } from "../../../../components/ui/IOScrollViewWithLargeHeader"; +import { useDebugInfo } from "../../../../hooks/useDebugInfo"; import I18n from "../../../../i18n"; import { useIONavigation } from "../../../../navigation/params/AppParamsList"; import { identificationRequest } from "../../../../store/actions/identification"; import { useIODispatch } from "../../../../store/hooks"; +import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton"; import { ItwCredentialClaimsList } from "../../common/components/ItwCredentialClaimList"; import { useItwDismissalDialog } from "../../common/hooks/useItwDismissalDialog"; import { @@ -17,7 +19,6 @@ import { import { StoredCredential } from "../../common/utils/itwTypesUtils"; import { selectEidOption } from "../../machine/eid/selectors"; import { ItwEidIssuanceMachineContext } from "../../machine/provider"; -import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton"; export const ItwIssuanceEidPreviewScreen = () => { const machineRef = ItwEidIssuanceMachineContext.useActorRef(); @@ -62,9 +63,12 @@ export const ItwIssuanceEidPreviewScreen = () => { }); }, []); + useDebugInfo({ + parsedCredential: eid.parsedCredential + }); + return ( { backgroundColor: themeColor }); + useDebugInfo({ + parsedCredential: credential.parsedCredential + }); + const credentialStatus = getCredentialExpireStatus( credential.parsedCredential ); diff --git a/ts/hooks/useDebugInfo.ts b/ts/hooks/useDebugInfo.ts new file mode 100644 index 00000000000..b524e6b63fb --- /dev/null +++ b/ts/hooks/useDebugInfo.ts @@ -0,0 +1,29 @@ +import { useFocusEffect } from "@react-navigation/native"; +import React from "react"; +import { resetDebugData, setDebugData } from "../store/actions/debug"; +import { useIODispatch, useIOSelector } from "../store/hooks"; +import { isDebugModeEnabledSelector } from "../store/reducers/debug"; + +/** + * Sets debug data for the mounted component. Removes it when the component is unmounted + * @param data Data to be displayes in debug mode + */ +export const useDebugInfo = (data: Record) => { + const dispatch = useIODispatch(); + const isDebug = useIOSelector(isDebugModeEnabledSelector); + + useFocusEffect( + React.useCallback(() => { + // Avoids storing debug data if debug is disabled + if (!isDebug) { + return undefined; + } + + dispatch(setDebugData(data)); + + return () => { + dispatch(resetDebugData(Object.keys(data))); + }; + }, [dispatch, isDebug, data]) + ); +}; diff --git a/ts/store/actions/debug.ts b/ts/store/actions/debug.ts index 39538f381ad..6df521d9d20 100644 --- a/ts/store/actions/debug.ts +++ b/ts/store/actions/debug.ts @@ -16,6 +16,20 @@ export const setDebugCurrentRouteName = createStandardAction( "DEBUG_SET_CURRENT_ROUTE" )(); +/** + * Adds debug data to be displayed in the DebugInfoOverlay + */ +export const setDebugData = + createStandardAction("DEBUG_SET_DATA")>(); + +/** + * Removes all the debug data stored + */ +export const resetDebugData = + createStandardAction("DEBUG_RESET_DATA")>(); + export type DebugActions = | ActionType - | ActionType; + | ActionType + | ActionType + | ActionType; diff --git a/ts/store/reducers/debug.ts b/ts/store/reducers/debug.ts index 9f60917b410..79b127ba1ac 100644 --- a/ts/store/reducers/debug.ts +++ b/ts/store/reducers/debug.ts @@ -1,17 +1,25 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { PersistConfig, PersistPartial, persistReducer } from "redux-persist"; import { getType } from "typesafe-actions"; -import { setDebugModeEnabled } from "../actions/debug"; +import { + resetDebugData, + setDebugData, + setDebugModeEnabled +} from "../actions/debug"; import { Action } from "../actions/types"; import { GlobalState } from "./types"; -export type DebugState = Readonly<{ +type DebugState = Readonly<{ isDebugModeEnabled: boolean; + debugData: Record; }>; const INITIAL_STATE: DebugState = { - isDebugModeEnabled: false + isDebugModeEnabled: false, + debugData: {} }; -export function debugReducer( +function debugReducer( state: DebugState = INITIAL_STATE, action: Action ): DebugState { @@ -19,13 +27,50 @@ export function debugReducer( case getType(setDebugModeEnabled): return { ...state, - isDebugModeEnabled: action.payload + isDebugModeEnabled: action.payload, + debugData: {} + }; + + /** + * Debug data to be displayed in DebugInfoOverlay + */ + case getType(setDebugData): + return { + ...state, + debugData: action.payload + }; + case getType(resetDebugData): + return { + ...state, + debugData: Object.fromEntries( + Object.entries(state.debugData).filter( + ([key]) => !action.payload.includes(key) + ) + ) }; } return state; } +// Persistor +const CURRENT_REDUX_DEBUG_STORE_VERSION = -1; + +const persistConfig: PersistConfig = { + key: "debug", + storage: AsyncStorage, + version: CURRENT_REDUX_DEBUG_STORE_VERSION, + whitelist: ["isDebugModeEnabled"] +}; + +export type PersistedDebugState = DebugState & PersistPartial; + +export const debugPersistor = persistReducer( + persistConfig, + debugReducer +); + // Selector export const isDebugModeEnabledSelector = (state: GlobalState) => state.debug.isDebugModeEnabled; +export const debugDataSelector = (state: GlobalState) => state.debug.debugData; diff --git a/ts/store/reducers/index.ts b/ts/store/reducers/index.ts index 5ef368bcac6..6b3b90e464d 100644 --- a/ts/store/reducers/index.ts +++ b/ts/store/reducers/index.ts @@ -45,7 +45,7 @@ import contentReducer, { initialContentState as contentInitialContentState } from "./content"; import crossSessionsReducer from "./crossSessions"; -import { debugReducer } from "./debug"; +import { debugPersistor } from "./debug"; import emailValidationReducer from "./emailValidation"; import entitiesReducer, { entitiesPersistConfig, @@ -158,7 +158,7 @@ export const appReducer: Reducer = combineReducers< entitiesPersistConfig, entitiesReducer ), - debug: debugReducer, + debug: debugPersistor, persistedPreferences: persistedPreferencesReducer, installation: installationReducer, payments: paymentsReducer, diff --git a/ts/store/reducers/types.ts b/ts/store/reducers/types.ts index 6b74e34715c..1d37f9a1049 100644 --- a/ts/store/reducers/types.ts +++ b/ts/store/reducers/types.ts @@ -14,7 +14,7 @@ import { BackoffErrorState } from "./backoffError"; import { CieState } from "./cie"; import { ContentState } from "./content"; import { CrossSessionsState } from "./crossSessions"; -import { DebugState } from "./debug"; +import { PersistedDebugState } from "./debug"; import { EmailValidationState } from "./emailValidation"; import { PersistedEntitiesState } from "./entities"; import { PersistedIdentificationState } from "./identification"; @@ -48,7 +48,7 @@ export type GlobalState = Readonly<{ content: ContentState; identification: PersistedIdentificationState; installation: InstallationState; - debug: DebugState; + debug: PersistedDebugState; search: SearchState; payments: PaymentsState; emailValidation: EmailValidationState;