diff --git a/.changeset/curly-coats-draw.md b/.changeset/curly-coats-draw.md new file mode 100644 index 000000000000..1f1eef01e78e --- /dev/null +++ b/.changeset/curly-coats-draw.md @@ -0,0 +1,7 @@ +--- +"@ledgerhq/types-live": patch +"ledger-live-desktop": patch +"@ledgerhq/live-common": patch +--- + +Add specific deeplinks for useCase tutorial onboarding diff --git a/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.tsx b/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.tsx index 52e3e2324100..9ad30fc6252c 100644 --- a/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.tsx @@ -34,6 +34,7 @@ import { CARD_APP_ID } from "~/renderer/screens/card"; import TopGradient from "./TopGradient"; import Hide from "./Hide"; import { track } from "~/renderer/analytics/segment"; +import { useAccountPath } from "@ledgerhq/live-common/hooks/recoverFeatureFlag"; const MAIN_SIDEBAR_WIDTH = 230; const TagText = styled.div.attrs<{ collapsed?: boolean }>(p => ({ @@ -229,6 +230,8 @@ const MainSideBar = () => { const referralProgramConfig = useFeature("referralProgramDesktopSidebar"); const ptxEarnConfig = useFeature("ptxEarn"); const recoverFeature = useFeature("protectServicesDesktop"); + const recoverHomePath = useAccountPath(recoverFeature); + const handleCollapse = useCallback(() => { dispatch(setSidebarCollapsed(!collapsed)); }, [dispatch, collapsed]); @@ -316,8 +319,8 @@ const MainSideBar = () => { const openRecoverFromSidebar = recoverFeature?.params?.openRecoverFromSidebar; const liveAppId = recoverFeature?.params?.protectId; - if (enabled && openRecoverFromSidebar && liveAppId) { - push(`/recover/${liveAppId}`); + if (enabled && openRecoverFromSidebar && liveAppId && recoverHomePath) { + history.push(recoverHomePath); } else if (enabled) { dispatch(openModal("MODAL_PROTECT_DISCOVER", undefined)); } @@ -328,7 +331,8 @@ const MainSideBar = () => { recoverFeature?.enabled, recoverFeature?.params?.openRecoverFromSidebar, recoverFeature?.params?.protectId, - push, + recoverHomePath, + history, dispatch, ]); diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/index.tsx b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/index.tsx index f301c272f1fa..77bc623e60d6 100644 --- a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/index.tsx @@ -1,7 +1,9 @@ import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import { - usePostOnboardingPath, + useAlreadySeededDevicePath, + useRestore24Path, useUpsellPath, + useCustomPath, } from "@ledgerhq/live-common/hooks/recoverFeatureFlag"; import { useStartPostOnboardingCallback } from "@ledgerhq/live-common/postOnboarding/hooks/index"; import { @@ -230,7 +232,9 @@ export default function Tutorial({ useCase }: Props) { const { pathname } = useLocation(); const recoverFF = useFeature("protectServicesDesktop"); const upsellPath = useUpsellPath(recoverFF); - const postOnboardingPath = usePostOnboardingPath(recoverFF); + const restore24Path = useRestore24Path(recoverFF); + const devicePairingPath = useAlreadySeededDevicePath(recoverFF); + const recoverRestorePath = useCustomPath(recoverFF, "restore", "lld-restore-with-recover"); const recoverDiscoverPath = useMemo(() => { return `/recover/${recoverFF?.params?.protectId}?redirectTo=disclaimerRestore`; }, [recoverFF?.params?.protectId]); @@ -567,15 +571,29 @@ export default function Tutorial({ useCase }: Props) { deviceModelId: connectedDevice.modelId, fallbackIfNoAction: () => history.push("/"), }); - if (upsellPath) { + + if (useCase === UseCase.setupDevice && upsellPath) { history.push(upsellPath); + } else if (useCase === UseCase.recoveryPhrase && restore24Path) { + history.push(restore24Path); + } else if (useCase === UseCase.connectDevice && devicePairingPath) { + history.push(devicePairingPath); } }, 0); return () => { clearTimeout(timeout); }; } - }, [connectedDevice, handleStartPostOnboarding, history, onboardingDone, upsellPath]); + }, [ + connectedDevice, + devicePairingPath, + handleStartPostOnboarding, + history, + onboardingDone, + restore24Path, + upsellPath, + useCase, + ]); const steps = useMemo(() => { const stepList = [ @@ -681,8 +699,8 @@ export default function Tutorial({ useCase }: Props) { const handleNextPin = useCallback(() => { let targetPath: string | object = `${path}/${ScreenId.existingRecoveryPhrase}`; - if (useCase === UseCase.recover && postOnboardingPath) { - const [pathname, search] = postOnboardingPath.split("?"); + if (useCase === UseCase.recover && recoverRestorePath) { + const [pathname, search] = recoverRestorePath.split("?"); targetPath = { pathname, search: search ? `?${search}` : undefined, @@ -696,7 +714,7 @@ export default function Tutorial({ useCase }: Props) { } handleNextInDrawer(setHelpPinCode, targetPath); - }, [connectedDevice?.deviceId, dispatch, handleNextInDrawer, path, postOnboardingPath, useCase]); + }, [connectedDevice?.deviceId, dispatch, handleNextInDrawer, path, recoverRestorePath, useCase]); return ( <> diff --git a/apps/ledger-live-desktop/src/renderer/components/SyncOnboarding/Manual/SyncOnboardingCompanion.tsx b/apps/ledger-live-desktop/src/renderer/components/SyncOnboarding/Manual/SyncOnboardingCompanion.tsx index 93fdfdf28522..cc9b6989a7e8 100644 --- a/apps/ledger-live-desktop/src/renderer/components/SyncOnboarding/Manual/SyncOnboardingCompanion.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/SyncOnboarding/Manual/SyncOnboardingCompanion.tsx @@ -10,7 +10,7 @@ import React, { import { useHistory } from "react-router-dom"; import { Box, Flex, Text, VerticalTimeline } from "@ledgerhq/react-ui"; import { useTranslation } from "react-i18next"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { useOnboardingStatePolling } from "@ledgerhq/live-common/onboarding/hooks/useOnboardingStatePolling"; import { getDeviceModel } from "@ledgerhq/devices"; import { DeviceModelInfo, SeedPhraseType } from "@ledgerhq/types-live"; @@ -19,7 +19,7 @@ import { fromSeedPhraseTypeToNbOfSeedWords, } from "@ledgerhq/live-common/hw/extractOnboardingState"; import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; -import { usePostOnboardingPath } from "@ledgerhq/live-common/hooks/recoverFeatureFlag"; +import { useCustomPath } from "@ledgerhq/live-common/hooks/recoverFeatureFlag"; import { lastSeenDeviceSelector } from "~/renderer/reducers/settings"; import { DesyncOverlay } from "./DesyncOverlay"; import SeedStep, { SeedPathStatus } from "./SeedStep"; @@ -34,6 +34,7 @@ import { Device } from "@ledgerhq/live-common/hw/actions/types"; import { setDrawer } from "~/renderer/drawers/Provider"; import LockedDeviceDrawer, { Props as LockedDeviceDrawerProps } from "./LockedDeviceDrawer"; import { LockedDeviceError } from "@ledgerhq/errors"; +import { saveSettings } from "~/renderer/actions/settings"; const READY_REDIRECT_DELAY_MS = 2500; const POLLING_PERIOD_MS = 1000; @@ -101,6 +102,7 @@ const SyncOnboardingCompanion: React.FC = ({ }) => { const { t } = useTranslation(); const history = useHistory(); + const dispatch = useDispatch(); const [stepKey, setStepKey] = useState(StepKey.Paired); const [shouldRestoreApps, setShouldRestoreApps] = useState(false); const deviceToRestore = useSelector(lastSeenDeviceSelector) as DeviceModelInfo | null | undefined; @@ -108,7 +110,7 @@ const SyncOnboardingCompanion: React.FC = ({ const [seedPathStatus, setSeedPathStatus] = useState("choice_new_or_restore"); const servicesConfig = useFeature("protectServicesDesktop"); - const postOnboardingPath = usePostOnboardingPath(servicesConfig); + const recoverRestoreStaxPath = useCustomPath(servicesConfig, "restore", "lld-stax-onboarding"); const productName = device ? getDeviceModel(device.modelId).productName || device.modelId @@ -466,15 +468,20 @@ const SyncOnboardingCompanion: React.FC = ({ }, [device, allowedError, handleDesyncTimerRunsOut, desyncTimeout]); useEffect(() => { - if (seedPathStatus === "recover_seed" && postOnboardingPath) { - const [pathname, search] = postOnboardingPath.split("?"); + if (seedPathStatus === "recover_seed" && recoverRestoreStaxPath) { + const [pathname, search] = recoverRestoreStaxPath.split("?"); + dispatch( + saveSettings({ + hasCompletedOnboarding: true, + }), + ); history.push({ pathname, search: search ? `?${search}` : undefined, state: { fromOnboarding: true }, }); } - }, [history, postOnboardingPath, seedPathStatus]); + }, [dispatch, history, recoverRestoreStaxPath, seedPathStatus]); return ( diff --git a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts index fc9b25c93a62..374061cabc61 100644 --- a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts +++ b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts @@ -150,16 +150,24 @@ export const DEFAULT_FEATURES: Features = { params: { availableOnDesktop: false, account: { - homeURI: "ledgerlive://recover/protect-simu?redirectTo=account", - loginURI: "ledgerlive://recover/protect-simu?redirectTo=login", + homeURI: + "ledgerlive://recover/protect-simu?redirectTo=account&source=lld-sidebar-navigation&ajs_recover_source=lld-sidebar-navigation&ajs_recover_campaign=recover-launch", + loginURI: + "ledgerlive://recover/protect-simu?redirectTo=login&source=lld-welcome-login&ajs_recover_source=lld-welcome-login&ajs_recover_campaign=recover-launch", }, discoverTheBenefitsLink: "https://www.ledger.com/recover", onboardingCompleted: { alreadySubscribedURI: "ledgerlive://recover/protect-simu?redirectTo=login", - upsellURI: "ledgerlive://recover/protect-simu?redirectTo=upsell", + alreadyDeviceSeededURI: + "ledgerlive://recover/protect-simu?redirectTo=upsell&source=lld-pairing&ajs_recover_source=lld-pairing&ajs_recover_campaign=recover-launch", + upsellURI: + "ledgerlive://recover/protect-simu?redirectTo=upsell&source=lld-onboarding-24&ajs_recover_source=lld-onboarding-24&ajs_recover_campaign=recover-launch", + restore24URI: + "ledgerlive://recover/protect-simu?redirectTo=upsell&source=lld-restore-24&ajs_recover_source=lld-restore-24&ajs_recover_campaign=recover-launch", }, onboardingRestore: { - postOnboardingURI: "ledgerlive://recover/protect-simu?redirectTo=restore", + postOnboardingURI: + "ledgerlive://recover/protect-simu?redirectTo=restore&source=lld-restore", restoreInfoDrawer: { enabled: true, manualStepsURI: diff --git a/libs/ledger-live-common/src/hooks/recoverFeatureFlag.ts b/libs/ledger-live-common/src/hooks/recoverFeatureFlag.ts index ae274022589a..426fed93dec6 100644 --- a/libs/ledger-live-common/src/hooks/recoverFeatureFlag.ts +++ b/libs/ledger-live-common/src/hooks/recoverFeatureFlag.ts @@ -94,3 +94,77 @@ export function useLoginPath( return usePath(servicesConfig, uri); } + +export function useRestore24URI( + servicesConfig: Feature_ProtectServicesDesktop | null, +): string | undefined { + const uri = servicesConfig?.params?.onboardingCompleted?.restore24URI; + const id = servicesConfig?.params?.protectId; + + return useReplacedURI(uri, id); +} + +export function useRestore24Path( + servicesConfig: Feature_ProtectServicesDesktop | null, +): string | undefined { + const uri = useRestore24URI(servicesConfig); + + return usePath(servicesConfig, uri); +} + +export function useAccountURI( + servicesConfig: Feature_ProtectServicesDesktop | null, +): string | undefined { + const uri = servicesConfig?.params?.account?.homeURI; + const id = servicesConfig?.params?.protectId; + + return useReplacedURI(uri, id); +} + +export function useAccountPath( + servicesConfig: Feature_ProtectServicesDesktop | null, +): string | undefined { + const uri = useAccountURI(servicesConfig); + + return usePath(servicesConfig, uri); +} + +export function useAlreadySeededDeviceURI( + servicesConfig: Feature_ProtectServicesDesktop | null, +): string | undefined { + const uri = servicesConfig?.params?.onboardingCompleted?.alreadyDeviceSeededURI; + const id = servicesConfig?.params?.protectId; + + return useReplacedURI(uri, id); +} + +export function useAlreadySeededDevicePath( + servicesConfig: Feature_ProtectServicesDesktop | null, +): string | undefined { + const uri = useAlreadySeededDeviceURI(servicesConfig); + + return usePath(servicesConfig, uri); +} + +export function useCustomPath( + servicesConfig: Feature_ProtectServicesDesktop | Feature_ProtectServicesMobile | null, + page?: string, + source?: string, + deeplinkCampaign?: string, +): string | undefined { + const modelUri = usePostOnboardingURI(servicesConfig); + const customUri = useMemo(() => { + const [basicUri] = modelUri ? modelUri.split("?") : []; + const uri = new URL(basicUri); + + if (page) uri.searchParams.append("redirectTo", page); + if (source) uri.searchParams.append("source", source); + if (source && deeplinkCampaign) { + uri.searchParams.append("ajs_recover_source", source); + uri.searchParams.append("ajs_recover_campaign", deeplinkCampaign); + } + return uri; + }, [deeplinkCampaign, modelUri, page, source]); + + return usePath(servicesConfig, customUri.toString()) ?? undefined; +} diff --git a/libs/ledgerjs/packages/types-live/src/feature.ts b/libs/ledgerjs/packages/types-live/src/feature.ts index 19aa7b9492dd..e8e0baf4720d 100644 --- a/libs/ledgerjs/packages/types-live/src/feature.ts +++ b/libs/ledgerjs/packages/types-live/src/feature.ts @@ -333,7 +333,9 @@ export type Feature_ProtectServicesDesktop = Feature<{ }; onboardingCompleted: { upsellURI: string; + restore24URI: string; alreadySubscribedURI: string; + alreadyDeviceSeededURI: string; }; account: { homeURI: string;