diff --git a/.changeset/clean-olives-obey.md b/.changeset/clean-olives-obey.md new file mode 100644 index 000000000000..0a2d3b56fa77 --- /dev/null +++ b/.changeset/clean-olives-obey.md @@ -0,0 +1,5 @@ +--- +"live-mobile": minor +--- + +Update entry points to reborn LP on read only mode diff --git a/apps/ledger-live-mobile/src/components/FabActions/index.tsx b/apps/ledger-live-mobile/src/components/FabActions/index.tsx index 7320f08870c4..36c319fdce70 100644 --- a/apps/ledger-live-mobile/src/components/FabActions/index.tsx +++ b/apps/ledger-live-mobile/src/components/FabActions/index.tsx @@ -12,6 +12,9 @@ import { useAnalytics } from "~/analytics"; import { WrappedButtonProps } from "../wrappedUi/Button"; import { NavigatorName } from "~/const"; import { useRoute } from "@react-navigation/native"; +import { useRebornFlow } from "LLM/features/Reborn/hooks/useRebornFlow"; +import { useSelector } from "react-redux"; +import { hasOrderedNanoSelector, readOnlyModeEnabledSelector } from "~/reducers/settings"; export type ModalOnDisabledClickComponentProps = { account?: AccountLike; @@ -96,6 +99,10 @@ export const FabButtonBarProvider = ({ const navigation = useNavigation>(); + const readOnlyModeEnabled = useSelector(readOnlyModeEnabledSelector); + const hasOrderedNano = useSelector(hasOrderedNanoSelector); + const { navigateToRebornFlow } = useRebornFlow(); + const router = useRoute(); const onNavigate = useCallback( @@ -134,6 +141,11 @@ export const FabButtonBarProvider = ({ (data: Omit) => { const { navigationParams, confirmModalProps, linkUrl, event, eventProperties, id } = data; + if (readOnlyModeEnabled && !hasOrderedNano) { + navigateToRebornFlow(); + return; + } + if (!confirmModalProps) { if (event) { track(event, { page: router.name, ...globalEventProperties, ...eventProperties }); @@ -157,7 +169,15 @@ export const FabButtonBarProvider = ({ setIsModalInfoOpened(true); } }, - [globalEventProperties, onNavigate, track, router.name], + [ + readOnlyModeEnabled, + hasOrderedNano, + navigateToRebornFlow, + track, + router.name, + globalEventProperties, + onNavigate, + ], ); const onContinue = useCallback(() => { diff --git a/apps/ledger-live-mobile/src/components/FabActions/modals/ZeroBalanceDisabledModalContent.tsx b/apps/ledger-live-mobile/src/components/FabActions/modals/ZeroBalanceDisabledModalContent.tsx index be4634e5f5cd..f704c380fc1b 100644 --- a/apps/ledger-live-mobile/src/components/FabActions/modals/ZeroBalanceDisabledModalContent.tsx +++ b/apps/ledger-live-mobile/src/components/FabActions/modals/ZeroBalanceDisabledModalContent.tsx @@ -14,6 +14,9 @@ import { } from "../../RootNavigator/types/helpers"; import { BaseNavigatorStackParamList } from "../../RootNavigator/types/BaseNavigator"; import QueuedDrawer from "../../QueuedDrawer"; +import { useRebornFlow } from "LLM/features/Reborn/hooks/useRebornFlow"; +import { useSelector } from "react-redux"; +import { readOnlyModeEnabledSelector, hasOrderedNanoSelector } from "~/reducers/settings"; function ZeroBalanceDisabledModalContent({ account, @@ -26,10 +29,17 @@ function ZeroBalanceDisabledModalContent({ const { t } = useTranslation(); const navigation = useNavigation>>(); + const { navigateToRebornFlow } = useRebornFlow(); + const readOnlyModeEnabled = useSelector(readOnlyModeEnabledSelector); + const hasOrderedNano = useSelector(hasOrderedNanoSelector); const actionCurrency = account ? getAccountCurrency(account) : currency; const goToBuy = useCallback(() => { + if (readOnlyModeEnabled && !hasOrderedNano) { + navigateToRebornFlow(); + return; + } navigation.navigate(NavigatorName.Exchange, { screen: ScreenName.ExchangeBuy, params: { @@ -38,9 +48,21 @@ function ZeroBalanceDisabledModalContent({ }, }); onClose(); - }, [account?.id, actionCurrency?.id, navigation, onClose]); + }, [ + account?.id, + actionCurrency?.id, + hasOrderedNano, + navigateToRebornFlow, + navigation, + onClose, + readOnlyModeEnabled, + ]); const goToReceive = useCallback(() => { + if (readOnlyModeEnabled && !hasOrderedNano) { + navigateToRebornFlow(); + return; + } if (account) { navigation.navigate(NavigatorName.ReceiveFunds, { screen: ScreenName.ReceiveConfirmation, @@ -60,7 +82,16 @@ function ZeroBalanceDisabledModalContent({ }); } onClose(); - }, [account, parentAccount?.id, actionCurrency, navigation, onClose]); + }, [ + readOnlyModeEnabled, + hasOrderedNano, + account, + onClose, + navigateToRebornFlow, + navigation, + actionCurrency, + parentAccount?.id, + ]); return ( (); @@ -37,6 +38,8 @@ export default function MainNavigator() { const managerNavLockCallback = useManagerNavLockCallback(); const web3hub = useFeature("web3hub"); const earnYiedlLabel = getStakeLabelLocaleBased(); + const { navigateToRebornFlow } = useRebornFlow(); + const insets = useSafeAreaInsets(); const tabBar = useMemo( () => @@ -119,9 +122,14 @@ export default function MainNavigator() { tabPress: e => { e.preventDefault(); managerLockAwareCallback(() => { - navigation.navigate(NavigatorName.Earn, { - screen: ScreenName.Earn, - }); + if (readOnlyModeEnabled && hasOrderedNano) { + navigation.navigate(ScreenName.PostBuyDeviceSetupNanoWallScreen); + } else if (readOnlyModeEnabled) { + navigateToRebornFlow(); + } else + navigation.navigate(NavigatorName.Earn, { + screen: ScreenName.Earn, + }); }); }, })} @@ -190,7 +198,7 @@ export default function MainNavigator() { if (readOnlyModeEnabled && hasOrderedNano) { navigation.navigate(ScreenName.PostBuyDeviceSetupNanoWallScreen); } else if (readOnlyModeEnabled) { - navigation.navigate(NavigatorName.BuyDevice); + navigateToRebornFlow(); } else { navigation.navigate(NavigatorName.MyLedger, { screen: ScreenName.MyLedgerChooseDevice, diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/OnboardingNavigator.tsx b/apps/ledger-live-mobile/src/components/RootNavigator/OnboardingNavigator.tsx index 06230c1056e0..d333920605ac 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/OnboardingNavigator.tsx +++ b/apps/ledger-live-mobile/src/components/RootNavigator/OnboardingNavigator.tsx @@ -49,6 +49,7 @@ import ProtectConnectionInformationModal from "~/screens/Onboarding/steps/setupD import { NavigationHeaderBackButton } from "../NavigationHeaderBackButton"; import AccessExistingWallet from "~/screens/Onboarding/steps/accessExistingWallet"; import AnalyticsOptInPromptNavigator from "./AnalyticsOptInPromptNavigator"; +import LandingPagesNavigator from "./LandingPagesNavigator"; const Stack = createStackNavigator(); const OnboardingPreQuizModalStack = @@ -240,6 +241,7 @@ export default function OnboardingNavigator() { options={{ headerShown: false }} component={AnalyticsOptInPromptNavigator} /> + ); } diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/types/OnboardingNavigator.ts b/apps/ledger-live-mobile/src/components/RootNavigator/types/OnboardingNavigator.ts index 962dc241a089..7fb06121991e 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/types/OnboardingNavigator.ts +++ b/apps/ledger-live-mobile/src/components/RootNavigator/types/OnboardingNavigator.ts @@ -3,6 +3,7 @@ import { NavigatorScreenParams } from "@react-navigation/native"; import { NavigatorName, ScreenName } from "~/const"; import { AnalyticsOptInPromptNavigatorParamList } from "./AnalyticsOptInPromptNavigator"; +import { LandingPagesNavigatorParamList } from "./LandingPagesNavigator"; export type OnboardingPreQuizModalNavigatorParamList = { [ScreenName.OnboardingPreQuizModal]: { onNext?: () => void }; @@ -60,4 +61,5 @@ export type OnboardingNavigatorParamList = { filterByDeviceModelId: DeviceModelId; }; [NavigatorName.AnalyticsOptInPrompt]: NavigatorScreenParams; + [NavigatorName.LandingPages]: NavigatorScreenParams; }; diff --git a/apps/ledger-live-mobile/src/dynamicContent/useDynamicContentLogic.ts b/apps/ledger-live-mobile/src/dynamicContent/useDynamicContentLogic.ts index 9c86ce516fb0..72d204bb0d8d 100644 --- a/apps/ledger-live-mobile/src/dynamicContent/useDynamicContentLogic.ts +++ b/apps/ledger-live-mobile/src/dynamicContent/useDynamicContentLogic.ts @@ -88,8 +88,8 @@ export const useDynamicContentLogic = () => { dispatch(setDynamicContentAssetsCards(assetCards)); dispatch(setDynamicContentNotificationCards(notificationCards)); dispatch(setDynamicContentLearnCards(learnCards)); - dispatch(setIsDynamicContentLoading(false)); dispatch(setDynamicContentLandingPageStickyCtaCards(landingPageStickyCtaCards)); + dispatch(setIsDynamicContentLoading(false)); }, [Braze, dismissedContentCardsIds, dispatch]); const clearOldDismissedContentCards = () => { diff --git a/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/useBuyDeviceBannerModel.ts b/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/useBuyDeviceBannerModel.ts index 1399be55faf9..371c05d4f579 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/useBuyDeviceBannerModel.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/useBuyDeviceBannerModel.ts @@ -11,6 +11,7 @@ import { NavigatorName, ScreenName } from "~/const"; import { track } from "~/analytics"; import { WrappedButtonProps } from "~/components/wrappedUi/Button"; import { Props as ThemeProps } from "~/components/theme/ForceTheme"; +import { useRebornFlow } from "../../hooks/useRebornFlow"; import buyFlexSource from "~/images/illustration/Shared/_FlexTop.png"; import buyDoubleFlexSource from "~/images/illustration/Shared/_FlexTwoSides.png"; @@ -48,6 +49,7 @@ const useBuyDeviceBannerModel = ({ useNavigation>>(); const revertTheme: ThemeProps["selectedPalette"] = theme === "light" ? "dark" : "light"; + const { navigateToRebornFlow } = useRebornFlow(); const imageSource: ImageSourcePropType = (() => { switch (image) { @@ -63,8 +65,8 @@ const useBuyDeviceBannerModel = ({ })(); const handleOnPress = useCallback(() => { - navigate(NavigatorName.BuyDevice); - }, [navigate]); + navigateToRebornFlow(); + }, [navigateToRebornFlow]); const handleSetupCtaOnPress = useCallback(() => { navigate(NavigatorName.BaseOnboarding, { diff --git a/apps/ledger-live-mobile/src/newArch/features/Reborn/constants/analytics.ts b/apps/ledger-live-mobile/src/newArch/features/Reborn/constants/analytics.ts new file mode 100644 index 000000000000..e6b10cff6cd8 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Reborn/constants/analytics.ts @@ -0,0 +1,6 @@ +const RebornAnalytics = { + FALLBACK_REBORN: "Fallback_Reborn", + REBORN_LP: "reborn_LP", +} as const; + +export default RebornAnalytics; diff --git a/apps/ledger-live-mobile/src/newArch/features/Reborn/hooks/useRebornFlow.ts b/apps/ledger-live-mobile/src/newArch/features/Reborn/hooks/useRebornFlow.ts new file mode 100644 index 000000000000..efe0915019df --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Reborn/hooks/useRebornFlow.ts @@ -0,0 +1,110 @@ +import { useRef } from "react"; +import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; +import { ABTestingVariants } from "@ledgerhq/types-live"; +import { useNavigation } from "@react-navigation/native"; +import { + RootNavigationComposite, + StackNavigatorNavigation, +} from "~/components/RootNavigator/types/helpers"; +import { BaseNavigatorStackParamList } from "~/components/RootNavigator/types/BaseNavigator"; +import { NavigatorName, ScreenName } from "~/const"; +import { CategoryContentCard, LandingPageUseCase } from "~/dynamicContent/types"; +import { filterCategoriesByLocation, formatCategories } from "~/dynamicContent/utils"; +import useDynamicContent from "~/dynamicContent/useDynamicContent"; +import { ContentCard } from "@braze/react-native-sdk"; +import { track } from "~/analytics"; +import { useDynamicContentLogic } from "~/dynamicContent/useDynamicContentLogic"; +import useFetchWithTimeout from "LLM/hooks/useFetchWithTimeout"; +import RebornAnalytics from "../constants/analytics"; + +type NavigationProps = RootNavigationComposite< + StackNavigatorNavigation +>; + +const FETCH_TIMEOUT = 3000; + +export function useRebornFlow(isFromOnboarding = false) { + const { navigate } = useNavigation(); + const rebornFeatureFlag = useFeature("llmRebornLP"); + const featureFlagEnabled = rebornFeatureFlag?.enabled; + const variant = getVariant(rebornFeatureFlag?.params?.variant); + const { categoriesCards, mobileCards } = useDynamicContent(); + const { fetchData, refreshDynamicContent } = useDynamicContentLogic(); + const canDisplayLP = useRef(false); + + const fetchWithTimeout = useFetchWithTimeout(FETCH_TIMEOUT); + + const fetchAllData = async () => { + refreshDynamicContent(); + try { + await fetchWithTimeout(fetchData); + } catch (error) { + canDisplayLP.current = false; + } + }; + + const checkIfCanDisplayLP = async (LP: LandingPageUseCase) => { + const result = await hasContentCardToDisplay(LP, categoriesCards, mobileCards); + canDisplayLP.current = result; + }; + + const navigateToLandingPage = async (LP: LandingPageUseCase) => { + await checkIfCanDisplayLP(LP); + if (!canDisplayLP.current) { + await fetchAllData(); + await checkIfCanDisplayLP(LP); + } + + if (canDisplayLP.current && !isFromOnboarding) { + track(RebornAnalytics.REBORN_LP); + navigate(NavigatorName.LandingPages, { + screen: ScreenName.GenericLandingPage, + params: { + useCase: LP, + }, + }); + } else { + track(RebornAnalytics.FALLBACK_REBORN); + navigate(NavigatorName.BuyDevice); + } + }; + + const navigateToRebornFlow = () => { + if (!featureFlagEnabled) { + navigate(NavigatorName.BuyDevice); + return; + } + + switch (variant) { + case ABTestingVariants.variantA: + navigateToLandingPage(LandingPageUseCase.LP_Reborn1); + break; + case ABTestingVariants.variantB: + navigateToLandingPage(LandingPageUseCase.LP_Reborn2); + break; + default: + navigate(NavigatorName.BuyDevice); + break; + } + }; + + return { + navigateToRebornFlow, + rebornFeatureFlagEnabled: featureFlagEnabled, + rebornVariant: variant, + }; +} + +const getVariant = (variant?: ABTestingVariants): ABTestingVariants => + variant === ABTestingVariants.variantB ? ABTestingVariants.variantB : ABTestingVariants.variantA; + +const hasContentCardToDisplay = async ( + lpLocation: LandingPageUseCase, + categoriesCards: CategoryContentCard[], + mobileCards: ContentCard[], +) => { + const categoriesToDisplay = filterCategoriesByLocation(categoriesCards, lpLocation); + const categoriesFormatted = formatCategories(categoriesToDisplay, mobileCards); + + return categoriesFormatted.length > 0; +}; diff --git a/apps/ledger-live-mobile/src/newArch/hooks/__tests__/useFetchWithTimeout.test.ts b/apps/ledger-live-mobile/src/newArch/hooks/__tests__/useFetchWithTimeout.test.ts new file mode 100644 index 000000000000..c35f17205d7b --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/hooks/__tests__/useFetchWithTimeout.test.ts @@ -0,0 +1,44 @@ +import { renderHook, act } from "@tests/test-renderer"; +import useFetchWithTimeout from "../useFetchWithTimeout"; + +describe("useFetchWithTimeout", () => { + it("should resolve the fetch function result within the timeout", async () => { + const fetchFunction = jest.fn().mockResolvedValue("data"); + const { result } = renderHook(() => useFetchWithTimeout(300)); + + await act(async () => { + const data = await result.current(fetchFunction); + expect(data).toBe("data"); + }); + + expect(fetchFunction).toHaveBeenCalledTimes(1); + }); + + it("should reject if the fetch function takes longer than the timeout", async () => { + jest.useFakeTimers(); + const fetchFunction = jest + .fn() + .mockImplementation(() => new Promise(resolve => setTimeout(() => resolve("data"), 600))); + const { result } = renderHook(() => useFetchWithTimeout(300)); + + act(() => { + const fetchPromise = result.current(fetchFunction); + jest.advanceTimersByTime(400); + return expect(fetchPromise).rejects.toThrow("Fetch timed out"); + }); + + jest.runAllTimers(); + expect(fetchFunction).toHaveBeenCalledTimes(1); + }); + + it("should reject if the fetch function throws an error", async () => { + const fetchFunction = jest.fn().mockRejectedValue(new Error("Fetch error")); + const { result } = renderHook(() => useFetchWithTimeout(300)); + + await act(async () => { + await expect(result.current(fetchFunction)).rejects.toThrow("Fetch error"); + }); + + expect(fetchFunction).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/ledger-live-mobile/src/newArch/hooks/useFetchWithTimeout.ts b/apps/ledger-live-mobile/src/newArch/hooks/useFetchWithTimeout.ts new file mode 100644 index 000000000000..9c3e864f2e11 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/hooks/useFetchWithTimeout.ts @@ -0,0 +1,28 @@ +import { useCallback } from "react"; + +const useFetchWithTimeout = (timeout: number = 3000) => { + const fetchWithTimeout = useCallback( + async (fetchFunction: () => Promise): Promise => { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error("Fetch timed out")); + }, timeout); + + fetchFunction() + .then(result => { + clearTimeout(timer); + resolve(result); + }) + .catch(error => { + clearTimeout(timer); + reject(error); + }); + }); + }, + [timeout], + ); + + return fetchWithTimeout; +}; + +export default useFetchWithTimeout; diff --git a/apps/ledger-live-mobile/src/screens/Assets/AssetsNavigationHeader.tsx b/apps/ledger-live-mobile/src/screens/Assets/AssetsNavigationHeader.tsx index a29114ca6bec..29c479219443 100644 --- a/apps/ledger-live-mobile/src/screens/Assets/AssetsNavigationHeader.tsx +++ b/apps/ledger-live-mobile/src/screens/Assets/AssetsNavigationHeader.tsx @@ -4,8 +4,8 @@ import { Flex, IconsLegacy } from "@ledgerhq/native-ui"; import { useNavigation } from "@react-navigation/native"; import AddAccount from "../Accounts/AddAccount"; import Touchable from "~/components/Touchable"; -import { ScreenName } from "~/const"; import { track } from "~/analytics"; +import { useRebornFlow } from "LLM/features/Reborn/hooks/useRebornFlow"; type Props = { readOnly?: boolean; @@ -13,14 +13,15 @@ type Props = { function AssetsNavigationHeader({ readOnly }: Props) { const navigation = useNavigation(); + const { navigateToRebornFlow } = useRebornFlow(); const handleOnReadOnlyAddAccountPress = useCallback(() => { track("button_clicked", { button: "Add Account '+'", page: "Assets", }); - navigation.navigate(ScreenName.NoDeviceWallScreen); - }, [navigation]); + navigateToRebornFlow(); + }, [navigateToRebornFlow]); const goBack = useCallback(() => { track("button_clicked", { diff --git a/apps/ledger-live-mobile/src/screens/Onboarding/steps/NoLedgerYetModal.tsx b/apps/ledger-live-mobile/src/screens/Onboarding/steps/NoLedgerYetModal.tsx index b2376cd7b3f8..55084694e602 100644 --- a/apps/ledger-live-mobile/src/screens/Onboarding/steps/NoLedgerYetModal.tsx +++ b/apps/ledger-live-mobile/src/screens/Onboarding/steps/NoLedgerYetModal.tsx @@ -5,17 +5,13 @@ import { useNavigation } from "@react-navigation/core"; import { useDispatch } from "react-redux"; import styled from "styled-components/native"; import { setHasOrderedNano, setOnboardingHasDevice } from "~/actions/settings"; -import { NavigatorName, ScreenName } from "~/const"; +import { ScreenName } from "~/const"; import QueuedDrawer from "~/components/QueuedDrawer"; -import { - StackNavigatorNavigation, - StackNavigatorProps, -} from "~/components/RootNavigator/types/helpers"; +import { StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; import { OnboardingNavigatorParamList } from "~/components/RootNavigator/types/OnboardingNavigator"; -import { BaseNavigatorStackParamList } from "~/components/RootNavigator/types/BaseNavigator"; import { TrackScreen, track, updateIdentify } from "~/analytics"; import Illustration from "~/images/illustration/Illustration"; - +import { useRebornFlow } from "LLM/features/Reborn/hooks/useRebornFlow"; import ImageLedger from "~/images/double-ledger.png"; type Props = { @@ -28,10 +24,12 @@ type NavigationProps = StackNavigatorProps< ScreenName.OnboardingPostWelcomeSelection >; export function NoLedgerYetModal({ onClose, isOpen }: Props) { - const [isFromBuy, setFromBuy] = useState(false); - const navigation = useNavigation(); const { t } = useTranslation(); const dispatch = useDispatch(); + const navigation = useNavigation(); + + const [isFromBuy, setFromBuy] = useState(false); + const { navigateToRebornFlow, rebornFeatureFlagEnabled } = useRebornFlow(true); const identifyUser = useCallback( (hasDevice: boolean) => { @@ -69,10 +67,8 @@ export function NoLedgerYetModal({ onClose, isOpen }: Props) { page: "Onboarding Get Started", drawer: "Get Started Upsell", }); - (navigation as unknown as StackNavigatorNavigation).navigate( - NavigatorName.BuyDevice, - ); - }, [navigation]); + navigateToRebornFlow(); + }, [navigateToRebornFlow]); return ( - - - + + {!rebornFeatureFlagEnabled && ( + + + + )} diff --git a/apps/ledger-live-mobile/src/screens/Onboarding/steps/discoverLiveInfo.tsx b/apps/ledger-live-mobile/src/screens/Onboarding/steps/discoverLiveInfo.tsx index b39b5dfa96c9..8986c14ed4b2 100644 --- a/apps/ledger-live-mobile/src/screens/Onboarding/steps/discoverLiveInfo.tsx +++ b/apps/ledger-live-mobile/src/screens/Onboarding/steps/discoverLiveInfo.tsx @@ -8,7 +8,7 @@ import { useDispatch } from "react-redux"; import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg"; import { Image, ImageProps } from "react-native"; import { completeOnboarding, setOnboardingHasDevice, setReadOnlyMode } from "~/actions/settings"; - +import { useRebornFlow } from "LLM/features/Reborn/hooks/useRebornFlow"; import { NavigatorName, ScreenName } from "~/const"; import { screen, track } from "~/analytics"; @@ -55,6 +55,7 @@ const Item = ({ const navigation = useNavigation(); const { colors } = useTheme(); const { t } = useTranslation(); + const { navigateToRebornFlow, rebornFeatureFlagEnabled } = useRebornFlow(true); const screenName = useMemo(() => `Reborn Story Step ${currentIndex}`, [currentIndex]); @@ -70,10 +71,8 @@ const Item = ({ const buyLedger = useCallback(() => { onClick("Buy a Ledger"); - navigation.navigate(NavigatorName.BuyDevice, { - screen: undefined, - } as never); - }, [navigation, onClick]); + navigateToRebornFlow(); + }, [navigateToRebornFlow, onClick]); const exploreLedger = useCallback(() => { dispatch(completeOnboarding()); @@ -95,8 +94,7 @@ const Item = ({ const pressBuy = useCallback(() => { buyLedger(); - onClick("Buy a Ledger"); - }, [buyLedger, onClick]); + }, [buyLedger]); return ( @@ -139,9 +137,11 @@ const Item = ({ > {t("onboarding.discoverLive.exploreWithoutADevice")} - + {!rebornFeatureFlagEnabled && ( + + )} )} diff --git a/apps/ledger-live-mobile/src/screens/Platform/v2/hooks.ts b/apps/ledger-live-mobile/src/screens/Platform/v2/hooks.ts index 0505860e2bd8..903934f1c686 100644 --- a/apps/ledger-live-mobile/src/screens/Platform/v2/hooks.ts +++ b/apps/ledger-live-mobile/src/screens/Platform/v2/hooks.ts @@ -23,9 +23,10 @@ import { useSearch } from "@ledgerhq/live-common/hooks/useSearch"; import { useDB } from "../../../db"; import { NavigatorName, ScreenName } from "~/const"; import { useBanner } from "~/components/banners/hooks"; -import { readOnlyModeEnabledSelector } from "../../../reducers/settings"; +import { hasOrderedNanoSelector, readOnlyModeEnabledSelector } from "../../../reducers/settings"; import { NavigationProps } from "./types"; import { useManifests } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index"; +import { useRebornFlow } from "LLM/features/Reborn/hooks/useRebornFlow"; export function useCatalog(initialCategory?: Categories["selected"] | null) { const recentlyUsedDB = useRecentlyUsedDB(); @@ -103,11 +104,14 @@ export type Disclaimer = DisclaimerRaw & { function useDisclaimer(appendRecentlyUsed: (manifest: AppManifest) => void): Disclaimer { const isReadOnly = useSelector(readOnlyModeEnabledSelector); + const hasOrderedNano = useSelector(hasOrderedNanoSelector); + const [isDismissed, dismiss] = useBanner(DAPP_DISCLAIMER_ID); const navigation = useNavigation(); const route = useRoute(); const { platform, ...params } = route.params ?? {}; + const { navigateToRebornFlow } = useRebornFlow(); const [manifest, setManifest] = useState(); const [isChecked, setIsChecked] = useState(false); @@ -123,13 +127,19 @@ function useDisclaimer(appendRecentlyUsed: (manifest: AppManifest) => void): Dis }); return; } + + if (isReadOnly && !hasOrderedNano) { + navigateToRebornFlow(); + return; + } + navigation.navigate(ScreenName.PlatformApp, { ...params, platform: manifest.id, name: manifest.name, }); }, - [navigation, params], + [hasOrderedNano, isReadOnly, navigateToRebornFlow, navigation, params], ); const toggleCheck = useCallback(() => {