diff --git a/.changeset/cuddly-tomatoes-jam.md b/.changeset/cuddly-tomatoes-jam.md new file mode 100644 index 000000000000..f37579b5a55e --- /dev/null +++ b/.changeset/cuddly-tomatoes-jam.md @@ -0,0 +1,21 @@ +--- +"@ledgerhq/hw-transport-node-speculos-http": patch +"@ledgerhq/hw-transport-http": patch +"@ledgerhq/context-module": patch +"@ledgerhq/hw-app-helium": patch +"@ledgerhq/hw-app-solana": patch +"@ledgerhq/cryptoassets": patch +"@ledgerhq/hw-app-btc": patch +"@ledgerhq/hw-app-eth": patch +"@ledgerhq/coin-bitcoin": patch +"@ledgerhq/coin-evm": patch +"ledger-live-desktop": patch +"@ledgerhq/live-common": patch +"@ledgerhq/domain-service": patch +"@ledgerhq/live-network": patch +"@ledgerhq/coin-tester": patch +"@ledgerhq/evm-tools": patch +"@ledgerhq/ledger-libs": patch +--- + +Update `axios` to fixed version `1.7.3` diff --git a/.changeset/eighty-bikes-press.md b/.changeset/eighty-bikes-press.md new file mode 100644 index 000000000000..4f81c8a47416 --- /dev/null +++ b/.changeset/eighty-bikes-press.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Rework LedgerSync Analytics diff --git a/.changeset/eleven-taxis-dance.md b/.changeset/eleven-taxis-dance.md new file mode 100644 index 000000000000..85ea59a43c9b --- /dev/null +++ b/.changeset/eleven-taxis-dance.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/live-network": major +--- + +Migrate to axios version 1.7.3 diff --git a/.changeset/empty-pumas-argue.md b/.changeset/empty-pumas-argue.md new file mode 100644 index 000000000000..e6305e4373bc --- /dev/null +++ b/.changeset/empty-pumas-argue.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"live-mobile": patch +--- + +Update errors in trad diff --git a/.changeset/fair-berries-drum.md b/.changeset/fair-berries-drum.md new file mode 100644 index 000000000000..bafff622a79b --- /dev/null +++ b/.changeset/fair-berries-drum.md @@ -0,0 +1,23 @@ +--- +"@actions/live-common-affected": patch +"@ledgerhq/types-live": patch +"@ledgerhq/errors": patch +"@ledgerhq/live-countervalues-react": patch +"@ledgerhq/crypto-icons-ui": patch +"@actions/turbo-affected": patch +"@ledgerhq/coin-icon": patch +"@ledgerhq/webpack.js-example": patch +"@ledgerhq/coin-ton": patch +"@actions/build-checks": patch +"ledger-live-desktop": patch +"@ledgerhq/next.js-example": patch +"live-mobile": patch +"@ledgerhq/live-common": patch +"@ledgerhq/live-countervalues": patch +"@ledgerhq/speculos-transport": patch +"@ledgerhq/native-ui": patch +"@ledgerhq/icons-ui": patch +"@ledgerhq/react-ui": patch +--- + +Add support for jettons diff --git a/.changeset/fifty-eels-breathe.md b/.changeset/fifty-eels-breathe.md new file mode 100644 index 000000000000..14f7a6cdb002 --- /dev/null +++ b/.changeset/fifty-eels-breathe.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +fix: uses correct status for fee drawers and disable button if any error is present diff --git a/.changeset/fluffy-peaches-dream.md b/.changeset/fluffy-peaches-dream.md new file mode 100644 index 000000000000..ba04049e7149 --- /dev/null +++ b/.changeset/fluffy-peaches-dream.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +User connects backup to instance with same backup or different backup diff --git a/.changeset/four-planes-allow.md b/.changeset/four-planes-allow.md new file mode 100644 index 000000000000..f0e081edb611 --- /dev/null +++ b/.changeset/four-planes-allow.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Fix Wording on FollowInstructions diff --git a/.changeset/gentle-lions-appear.md b/.changeset/gentle-lions-appear.md new file mode 100644 index 000000000000..a0b29fbb92a4 --- /dev/null +++ b/.changeset/gentle-lions-appear.md @@ -0,0 +1,7 @@ +--- +"ledger-live-desktop": minor +"live-mobile": minor +"@ledgerhq/live-wallet": minor +--- + +Ledger Sync - Improve account names module to avoid erasing custom names with the default ones when two instances are pushing around the same time diff --git a/.changeset/giant-beans-wait.md b/.changeset/giant-beans-wait.md new file mode 100644 index 000000000000..ccb8793e1d5c --- /dev/null +++ b/.changeset/giant-beans-wait.md @@ -0,0 +1,7 @@ +--- +"ledger-live-desktop": patch +"live-mobile": patch +"@ledgerhq/trustchain": patch +--- + +Handle TrustchainAlreadyInitialized & TrustchainAlreadyInitializedWithOtherSeed on Scan QR diff --git a/.changeset/good-boats-breathe.md b/.changeset/good-boats-breathe.md new file mode 100644 index 000000000000..190b1a234f17 --- /dev/null +++ b/.changeset/good-boats-breathe.md @@ -0,0 +1,9 @@ +--- +"ledger-live-desktop": minor +"live-mobile": minor +"@ledgerhq/live-wallet": minor +"@ledgerhq/trustchain": minor +"@ledgerhq/web-tools": minor +--- + +Ledger Sync - Added a Loading screen on LLM and LLD when initializing ledger sync while accounts are synchronizing diff --git a/.changeset/hip-plants-impress.md b/.changeset/hip-plants-impress.md new file mode 100644 index 000000000000..7ee6778318c0 --- /dev/null +++ b/.changeset/hip-plants-impress.md @@ -0,0 +1,8 @@ +--- +"live-mobile": minor +--- + +feat(web3hub): new bottom modal to select network and account with dapp browser v3 +Small perf improvement for dapp browser v3 dapps initial load +Lots of small visual improvements, polish and bugfixes +Update app screen header url diff --git a/.changeset/lazy-pillows-bake.md b/.changeset/lazy-pillows-bake.md new file mode 100644 index 000000000000..2dbd6b157a03 --- /dev/null +++ b/.changeset/lazy-pillows-bake.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"@ledgerhq/trustchain": patch +--- + +Handling alredy created key with new or same Ledger device diff --git a/.changeset/lemon-jeans-tie.md b/.changeset/lemon-jeans-tie.md new file mode 100644 index 000000000000..e147e4e1b663 --- /dev/null +++ b/.changeset/lemon-jeans-tie.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/coin-polkadot": patch +--- + +get rid of sidecar fork api and fix validator list for polkadot diff --git a/.changeset/lucky-pandas-exist.md b/.changeset/lucky-pandas-exist.md new file mode 100644 index 000000000000..c808633ce3f4 --- /dev/null +++ b/.changeset/lucky-pandas-exist.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"live-mobile": patch +--- + +Fix minor bugs on both LL diff --git a/.changeset/mighty-mangos-begin.md b/.changeset/mighty-mangos-begin.md new file mode 100644 index 000000000000..4bd2de700b50 --- /dev/null +++ b/.changeset/mighty-mangos-begin.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +perf: improve perf by using the networks instead of listing currencies to match diff --git a/.changeset/odd-masks-sleep.md b/.changeset/odd-masks-sleep.md new file mode 100644 index 000000000000..a5f29bca5298 --- /dev/null +++ b/.changeset/odd-masks-sleep.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/coin-polkadot": patch +--- + +Rename network model types diff --git a/.changeset/pink-rings-deny.md b/.changeset/pink-rings-deny.md new file mode 100644 index 000000000000..0c3a55d27b2c --- /dev/null +++ b/.changeset/pink-rings-deny.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +fix: persistant gasOptions between evm chains creating crashes diff --git a/.changeset/pink-walls-tickle.md b/.changeset/pink-walls-tickle.md new file mode 100644 index 000000000000..95cec4074f24 --- /dev/null +++ b/.changeset/pink-walls-tickle.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Setup analytics on Web3Hub diff --git a/.changeset/pink-wolves-own.md b/.changeset/pink-wolves-own.md new file mode 100644 index 000000000000..b0aa15c9aa70 --- /dev/null +++ b/.changeset/pink-wolves-own.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/coin-tezos": patch +--- + +Update some test for types regarding the upgrade to axios version 1.7.3 diff --git a/.changeset/polite-donuts-peel.md b/.changeset/polite-donuts-peel.md new file mode 100644 index 000000000000..cc633a8742fa --- /dev/null +++ b/.changeset/polite-donuts-peel.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +Add vertical scroll inside optin prompt and correct variable name. Fix export account row from settings that was hidden when LS enabled diff --git a/.changeset/popular-chairs-melt.md b/.changeset/popular-chairs-melt.md new file mode 100644 index 000000000000..e73f48439742 --- /dev/null +++ b/.changeset/popular-chairs-melt.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +btc and evm fee drawer display amount errors when editing fees too high diff --git a/.changeset/quick-mice-love.md b/.changeset/quick-mice-love.md new file mode 100644 index 000000000000..90c2968c48ba --- /dev/null +++ b/.changeset/quick-mice-love.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +fix setTransaction fee drawer call to check error status diff --git a/.changeset/seven-parrots-listen.md b/.changeset/seven-parrots-listen.md new file mode 100644 index 000000000000..46148b6454d4 --- /dev/null +++ b/.changeset/seven-parrots-listen.md @@ -0,0 +1,14 @@ +--- +"@ledgerhq/types-cryptoassets": patch +"@ledgerhq/cryptoassets": patch +"@ledgerhq/coin-bitcoin": patch +"ledger-live-desktop": patch +"live-mobile": patch +"@ledgerhq/live-common": patch +"@ledgerhq/live-countervalues": patch +"@ledgerhq/coin-framework": patch +"@ledgerhq/web-tools": patch +"@ledgerhq/live-cli": patch +--- + +remove pivx code diff --git a/.changeset/shaggy-crabs-approve.md b/.changeset/shaggy-crabs-approve.md new file mode 100644 index 000000000000..53c9e253fcb3 --- /dev/null +++ b/.changeset/shaggy-crabs-approve.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/hw-trustchain": patch +--- + +Fix the "Expected signature to be an Uint8Array with length 64" error diff --git a/.changeset/stupid-dodos-bow.md b/.changeset/stupid-dodos-bow.md new file mode 100644 index 000000000000..5b625c832ac8 --- /dev/null +++ b/.changeset/stupid-dodos-bow.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +Update analytics diff --git a/.changeset/sweet-spies-sell.md b/.changeset/sweet-spies-sell.md new file mode 100644 index 000000000000..1a87608dccdb --- /dev/null +++ b/.changeset/sweet-spies-sell.md @@ -0,0 +1,8 @@ +--- +"@ledgerhq/errors": patch +"ledger-live-desktop": patch +"live-mobile": patch +"@ledgerhq/trustchain": patch +--- + +Refresh ledger sync QR code on expiration diff --git a/.changeset/tall-mayflies-study.md b/.changeset/tall-mayflies-study.md new file mode 100644 index 000000000000..bcd298c1057a --- /dev/null +++ b/.changeset/tall-mayflies-study.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/live-common": minor +--- + +Add new use case to delete locally stored app data (llm/lld) diff --git a/.changeset/ten-files-live.md b/.changeset/ten-files-live.md new file mode 100644 index 000000000000..dd0f532d322b --- /dev/null +++ b/.changeset/ten-files-live.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/live-common": minor +"@ledgerhq/live-env": minor +--- + +feat: added new env to bypass app requirements to init swaps diff --git a/.changeset/tender-apples-know.md b/.changeset/tender-apples-know.md new file mode 100644 index 000000000000..c32faf9f1389 --- /dev/null +++ b/.changeset/tender-apples-know.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/cryptoassets": patch +--- + +edit zcash explorer links diff --git a/.changeset/thick-fans-give.md b/.changeset/thick-fans-give.md new file mode 100644 index 000000000000..1998923e39f1 --- /dev/null +++ b/.changeset/thick-fans-give.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/live-common": patch +--- + +Fix listApps: fallback to HSM script runner if listApps APDU is not available diff --git a/.changeset/tidy-trains-double.md b/.changeset/tidy-trains-double.md new file mode 100644 index 000000000000..69026b6f55ba --- /dev/null +++ b/.changeset/tidy-trains-double.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/native-ui": patch +--- + +refactor: merge BottomDrawer containerStyle prop diff --git a/.changeset/twenty-bottles-leave.md b/.changeset/twenty-bottles-leave.md new file mode 100644 index 000000000000..832b60c515fb --- /dev/null +++ b/.changeset/twenty-bottles-leave.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"@ledgerhq/live-common": patch +--- + +Fix removal of custom lock screen on LLD diff --git a/.changeset/wise-brooms-enjoy.md b/.changeset/wise-brooms-enjoy.md new file mode 100644 index 000000000000..edcfcb92c926 --- /dev/null +++ b/.changeset/wise-brooms-enjoy.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": minor +"live-mobile": minor +--- + +Create the `/ledgersync` deeplink diff --git a/.changeset/young-birds-taste.md b/.changeset/young-birds-taste.md new file mode 100644 index 000000000000..de758435a382 --- /dev/null +++ b/.changeset/young-birds-taste.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/live-common": patch +--- + +fix: missing deps in useEffect array +And cleanup unused code and picomatch dependency diff --git a/apps/cli/src/live-common-setup-base.ts b/apps/cli/src/live-common-setup-base.ts index ff2370140a86..b143093b8cbc 100644 --- a/apps/cli/src/live-common-setup-base.ts +++ b/apps/cli/src/live-common-setup-base.ts @@ -34,7 +34,6 @@ setSupportedCurrencies([ "qtum", "bitcoin_gold", "komodo", - "pivx", "zencash", "bitcoin_testnet", "ethereum_sepolia", diff --git a/apps/ledger-live-desktop/package.json b/apps/ledger-live-desktop/package.json index e0493b48c968..8050465700a7 100644 --- a/apps/ledger-live-desktop/package.json +++ b/apps/ledger-live-desktop/package.json @@ -201,7 +201,7 @@ "@types/write-file-atomic": "^4.0.0", "@vitejs/plugin-react": "^3.1.0", "allure-playwright": "2.15.1", - "axios": "1.3.4", + "axios": "1.7.3", "chalk": "^4.1.2", "cross-env": "^7.0.3", "debug": "^4.3.4", @@ -218,6 +218,7 @@ "jest-environment-jsdom": "^29.7.0", "listr": "^0.14.3", "listr-verbose-renderer": "^0.6.0", + "nock": "^13.0.5", "prebuild-install": "^7.1.1", "react-refresh": "^0.14.0", "react-test-renderer": "^18.2.0", @@ -229,4 +230,4 @@ "vite-plugin-electron": "0.4.9", "yargs": "^17.0.0" } -} +} \ No newline at end of file diff --git a/apps/ledger-live-desktop/src/live-common-set-supported-currencies.ts b/apps/ledger-live-desktop/src/live-common-set-supported-currencies.ts index 418f4e80f3d1..39a0d9048e20 100644 --- a/apps/ledger-live-desktop/src/live-common-set-supported-currencies.ts +++ b/apps/ledger-live-desktop/src/live-common-set-supported-currencies.ts @@ -42,7 +42,6 @@ setSupportedCurrencies([ "qtum", "bitcoin_gold", "komodo", - "pivx", "zencash", "bitcoin_testnet", "ethereum_sepolia", diff --git a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/hooks/useCommonLogic.tsx b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/hooks/useCommonLogic.tsx index c388a185bccc..c2e2e237a8c7 100644 --- a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/hooks/useCommonLogic.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/hooks/useCommonLogic.tsx @@ -30,7 +30,7 @@ export const useAnalyticsOptInPrompt = ({ entryPoint }: Props) => { const dispatch = useDispatch(); - const [isAnalitycsOptInPromptOpened, setIsAnalitycsOptInPromptOpened] = useState(false); + const [isAnalyticsOptInPromptOpened, setIsAnalyticsOptInPromptOpened] = useState(false); const [nextStep, setNextStep] = useState<(() => void) | null>(null); const flow = trackingKeysByFlow?.[entryPoint]; @@ -45,12 +45,12 @@ export const useAnalyticsOptInPrompt = ({ entryPoint }: Props) => { [ABTestingVariants.variantB]: privacyPolicyUrl, }; - const openAnalitycsOptInPrompt = useCallback( + const openAnalyticsOptInPrompt = useCallback( (routePath: string, callBack: () => void) => { - setIsAnalitycsOptInPromptOpened(true); + setIsAnalyticsOptInPromptOpened(true); setNextStep(() => callBack); }, - [setIsAnalitycsOptInPromptOpened], + [setIsAnalyticsOptInPromptOpened], ); const isEntryPointIncludedInFlagParams = lldAnalyticsOptInPromptFlag?.params?.entryPoints @@ -71,7 +71,7 @@ export const useAnalyticsOptInPrompt = ({ entryPoint }: Props) => { ); const onSubmit = () => { - setIsAnalitycsOptInPromptOpened(false); + setIsAnalyticsOptInPromptOpened(false); dispatch(setHasSeenAnalyticsOptInPrompt(true)); if (entryPoint === EntryPoint.onboarding) { nextStep?.(); @@ -80,8 +80,8 @@ export const useAnalyticsOptInPrompt = ({ entryPoint }: Props) => { }; const analyticsOptInPromptProps = { - onClose: () => setIsAnalitycsOptInPromptOpened(false), - isOpened: isAnalitycsOptInPromptOpened, + onClose: () => setIsAnalyticsOptInPromptOpened(false), + isOpened: isAnalyticsOptInPromptOpened, entryPoint: entryPoint, variant, }; @@ -101,8 +101,8 @@ export const useAnalyticsOptInPrompt = ({ entryPoint }: Props) => { }; return { - openAnalitycsOptInPrompt, - setIsAnalitycsOptInPromptOpened, + openAnalyticsOptInPrompt, + setIsAnalyticsOptInPromptOpened, onSubmit, analyticsOptInPromptProps, isFeatureFlagsAnalyticsPrefDisplayed: isFlagEnabled, diff --git a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/hooks/useDisplayOnPortfolio.tsx b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/hooks/useDisplayOnPortfolio.tsx index 5e16695df3b6..194ce8bba4ed 100644 --- a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/hooks/useDisplayOnPortfolio.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/hooks/useDisplayOnPortfolio.tsx @@ -5,7 +5,7 @@ import { EntryPoint } from "../types/AnalyticsOptInPromptNavigator"; export const useDisplayOnPortfolioAnalytics = () => { const { analyticsOptInPromptProps, - setIsAnalitycsOptInPromptOpened, + setIsAnalyticsOptInPromptOpened, isFeatureFlagsAnalyticsPrefDisplayed, onSubmit, } = useAnalyticsOptInPrompt({ entryPoint: EntryPoint.portfolio }); @@ -16,8 +16,8 @@ export const useDisplayOnPortfolioAnalytics = () => { }; useEffect(() => { - if (isFeatureFlagsAnalyticsPrefDisplayed) setIsAnalitycsOptInPromptOpened(true); - }, [isFeatureFlagsAnalyticsPrefDisplayed, setIsAnalitycsOptInPromptOpened]); + if (isFeatureFlagsAnalyticsPrefDisplayed) setIsAnalyticsOptInPromptOpened(true); + }, [isFeatureFlagsAnalyticsPrefDisplayed, setIsAnalyticsOptInPromptOpened]); return { analyticsOptInPromptProps: extendedAnalyticsOptInPromptProps, diff --git a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantA/Main/index.tsx b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantA/Main/index.tsx index 30e68c892a98..2bb02fa82510 100644 --- a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantA/Main/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantA/Main/index.tsx @@ -19,7 +19,14 @@ const Main = ({ shouldWeTrack, handleOpenPrivacyPolicy }: MainProps) => { return ( <> - + handleOpenPrivacyPolicy(page)} /> diff --git a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/Analytics/index.tsx b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/Analytics/index.tsx index 86274d20b998..7349e6f958a5 100644 --- a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/Analytics/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/Analytics/index.tsx @@ -39,7 +39,13 @@ const AnalyticsScreen = ({ return ( <> - +
handleOpenPrivacyPolicy(page)} /> diff --git a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/PersonalRecommendations/index.tsx b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/PersonalRecommendations/index.tsx index c8defc6e009b..7a4cdf46a773 100644 --- a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/PersonalRecommendations/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/PersonalRecommendations/index.tsx @@ -33,7 +33,7 @@ const RecommandationsScreen = ({ return ( <> - +
handleOpenPrivacyPolicy(page)} /> diff --git a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/index.tsx b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/index.tsx index 7ea8f9b8324f..dedde1a751ce 100644 --- a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/index.tsx @@ -22,7 +22,7 @@ const VariantB = ({ entryPoint, onSubmit, step, setStep }: VariantBProps) => { }); return ( <> - + {step === 0 ? ( void; analyticsPage?: AnalyticsPage; ctaVariant?: "shade" | "main"; + onClose?: () => void; }; const Container = styled(Box)` @@ -31,8 +33,10 @@ export const Error = ({ onClick, analyticsPage, ctaVariant = "shade", + onClose, }: Props) => { const { colors } = useTheme(); + const { t } = useTranslation(); return ( @@ -50,6 +54,14 @@ export const Error = ({ {cta} )} + + {onClose && ( + + + {t("walletSync.close")} + + + )} ); }; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/GenericStatusDisplay.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/GenericStatusDisplay.tsx new file mode 100644 index 000000000000..649510043f5a --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/GenericStatusDisplay.tsx @@ -0,0 +1,87 @@ +import { Box, Flex, Icons, Text } from "@ledgerhq/react-ui"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import styled, { useTheme } from "styled-components"; +import { AnalyticsPage } from "../hooks/useLedgerSyncAnalytics"; +import TrackPage from "~/renderer/analytics/TrackPage"; +import ButtonV3 from "~/renderer/components/ButtonV3"; + +export type GenericProps = { + title?: string; + description?: string; + withClose?: boolean; + withCta?: boolean; + onClick?: () => void; + onClose?: () => void; + analyticsPage?: AnalyticsPage; + type?: "success" | "info"; + specificCta?: string; +}; + +const Container = styled(Box)` + background-color: ${p => p.theme.colors.opacityDefault.c05}; + border-radius: 100%; + height: 72px; + width: 72px; + display: flex; + align-items: center; + justify-content: center; +`; + +export const GenericStatusDisplay = ({ + title, + description, + withClose = false, + withCta = false, + onClick, + onClose, + analyticsPage, + type, + specificCta, +}: GenericProps) => { + const { t } = useTranslation(); + const { colors } = useTheme(); + + return ( + + + + {type === "info" ? ( + + ) : ( + + )} + + + {title} + + + {description} + + + {withClose || withCta ? ( + + {withCta && onClick && ( + + {specificCta ?? t("walletSync.success.synchAnother")} + + )} + {withClose && ( + + {t("walletSync.success.close")} + + )} + + ) : null} + + ); +}; + +const BottomContainer = styled(Flex)``; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/Info.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/Info.tsx new file mode 100644 index 000000000000..b0217cf9179b --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/Info.tsx @@ -0,0 +1,4 @@ +import React from "react"; +import { GenericProps, GenericStatusDisplay } from "./GenericStatusDisplay"; + +export const Info = (props: GenericProps) => ; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/Success.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/Success.tsx index f409e01401c8..288fc1b2beac 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/Success.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/components/Success.tsx @@ -1,79 +1,4 @@ -import { Box, Flex, Icons, Text } from "@ledgerhq/react-ui"; import React from "react"; -import { useTranslation } from "react-i18next"; -import styled, { useTheme } from "styled-components"; -import TrackPage from "~/renderer/analytics/TrackPage"; -import ButtonV3 from "~/renderer/components/ButtonV3"; -import { AnalyticsPage } from "../hooks/useWalletSyncAnalytics"; +import { GenericProps, GenericStatusDisplay } from "./GenericStatusDisplay"; -type Props = { - title?: string; - description?: string; - withClose?: boolean; - withCta?: boolean; - onClick?: () => void; - onClose?: () => void; - analyticsPage?: AnalyticsPage; -}; - -const Container = styled(Box)` - background-color: ${p => p.theme.colors.opacityDefault.c05}; - border-radius: 100%; - height: 72px; - width: 72px; - display: flex; - align-items: center; - justify-content: center; -`; - -export const Success = ({ - title, - description, - withClose = false, - withCta = false, - onClick, - onClose, - analyticsPage, -}: Props) => { - const { t } = useTranslation(); - const { colors } = useTheme(); - - return ( - - - - - - - {title} - - - {description} - - - {withClose || withCta ? ( - - {withCta && onClick && ( - - {t("walletSync.success.synchAnother")} - - )} - {withClose && ( - - {t("walletSync.success.close")} - - )} - - ) : null} - - ); -}; - -const BottomContainer = styled(Flex)``; +export const Success = (props: GenericProps) => ; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useAddMember.ts b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useAddMember.ts index aa1428d2be10..2bff58d10c50 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useAddMember.ts +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useAddMember.ts @@ -1,21 +1,31 @@ -import { memberCredentialsSelector, setTrustchain } from "@ledgerhq/trustchain/store"; +import { + memberCredentialsSelector, + setTrustchain, + trustchainSelector, +} from "@ledgerhq/trustchain/store"; import { useDispatch, useSelector } from "react-redux"; import { setFlow } from "~/renderer/actions/walletSync"; import { Flow, Step } from "~/renderer/reducers/walletSync"; import { useTrustchainSdk } from "./useTrustchainSdk"; import { TrustchainResult, TrustchainResultType } from "@ledgerhq/trustchain/types"; import { useCallback, useEffect, useRef, useState } from "react"; +import { + TrustchainAlreadyInitialized, + TrustchainAlreadyInitializedWithOtherSeed, +} from "@ledgerhq/trustchain/errors"; export function useAddMember({ device }: { device: Device | null }) { const dispatch = useDispatch(); const sdk = useTrustchainSdk(); const memberCredentials = useSelector(memberCredentialsSelector); + const trustchain = useSelector(trustchainSelector); const [error, setError] = useState(null); const [userDeviceInteraction, setUserDeviceInteraction] = useState(false); const sdkRef = useRef(sdk); const deviceRef = useRef(device); + const trustchainRef = useRef(trustchain); const memberCredentialsRef = useRef(memberCredentials); const transitionToNextScreen = useCallback( @@ -24,10 +34,12 @@ export function useAddMember({ device }: { device: Device | null }) { dispatch( setFlow({ flow: Flow.Activation, - step: + step: Step.ActivationLoading, + nextStep: trustchainResult.type === TrustchainResultType.created ? Step.ActivationFinal : Step.SynchronizationFinal, + hasTrustchainBeenCreated: trustchainResult.type === TrustchainResultType.created, }), ); }, @@ -63,11 +75,19 @@ export function useAddMember({ device }: { device: Device | null }) { onStartRequestUserInteraction: () => setUserDeviceInteraction(true), onEndRequestUserInteraction: () => setUserDeviceInteraction(false), }, + undefined, + trustchainRef?.current?.rootId, ); transitionToNextScreen(trustchainResult); } catch (error) { - setError(error as Error); + if (error instanceof TrustchainAlreadyInitialized) { + dispatch(setFlow({ flow: Flow.Synchronize, step: Step.AlreadySecuredSameSeed })); + } else if (error instanceof TrustchainAlreadyInitializedWithOtherSeed) { + dispatch(setFlow({ flow: Flow.Synchronize, step: Step.AlreadySecuredOtherSeed })); + } else { + setError(error as Error); + } } }; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useFlows.ts b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useFlows.ts index 5f4966f8ddb2..187b41c72902 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useFlows.ts +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useFlows.ts @@ -23,9 +23,10 @@ export const FlowOptions: Record< 1: Step.CreateOrSynchronize, 2: Step.DeviceAction, 3: Step.CreateOrSynchronizeTrustChain, - 4: Step.ActivationFinal, - 5: Step.SynchronizationFinal, - 6: Step.SynchronizationError, + 4: Step.ActivationLoading, + 5: Step.ActivationFinal, + 6: Step.SynchronizationFinal, + 7: Step.SynchronizationError, }, }, [Flow.Synchronize]: { @@ -33,7 +34,8 @@ export const FlowOptions: Record< 1: Step.SynchronizeMode, 2: Step.SynchronizeWithQRCode, 3: Step.PinCode, - 4: Step.Synchronized, + 4: Step.SynchronizeLoading, + 5: Step.Synchronized, }, }, [Flow.ManageBackup]: { @@ -54,9 +56,9 @@ export const FlowOptions: Record< 7: Step.AutoRemoveInstance, }, }, - [Flow.WalletSyncActivated]: { + [Flow.LedgerSyncActivated]: { steps: { - 1: Step.WalletSyncActivated, + 1: Step.LedgerSyncActivated, }, }, }; @@ -99,7 +101,7 @@ export const useFlows = () => { const goToWelcomeScreenWalletSync = () => { if (trustchain?.rootId) { - dispatch(setFlow({ flow: Flow.WalletSyncActivated, step: Step.WalletSyncActivated })); + dispatch(setFlow({ flow: Flow.LedgerSyncActivated, step: Step.LedgerSyncActivated })); } else { dispatch(setFlow({ flow: Flow.Activation, step: Step.CreateOrSynchronize })); } diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useGetMembers.ts b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useGetMembers.ts index e1e6bf0befc9..f87b2d9e14fb 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useGetMembers.ts +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useGetMembers.ts @@ -11,7 +11,7 @@ export function useGetMembers() { const sdk = useTrustchainSdk(); const trustchain = useSelector(trustchainSelector); const memberCredentials = useSelector(memberCredentialsSelector); - const errorHandler = useLifeCycle(); + const { handleError } = useLifeCycle(); function fetchMembers() { if (!memberCredentials) { @@ -41,9 +41,9 @@ export function useGetMembers() { useEffect(() => { if (isErrorGetMembers) { - errorHandler.handleError(getMembersError); + handleError(getMembersError); } - }, [errorHandler, getMembersError, isErrorGetMembers]); + }, [handleError, getMembersError, isErrorGetMembers]); return { isMembersLoading: isMembersLoading, diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useWalletSyncAnalytics.ts b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useLedgerSyncAnalytics.ts similarity index 71% rename from apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useWalletSyncAnalytics.ts rename to apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useLedgerSyncAnalytics.ts index 7316d49e27bd..a7f8c5a2d09e 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useWalletSyncAnalytics.ts +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useLedgerSyncAnalytics.ts @@ -4,7 +4,7 @@ import { Step } from "~/renderer/reducers/walletSync"; export enum AnalyticsPage { ManageBackup = "Manage Backup", ConfirmDeleteBackup = "Confirm Delete Backup", - BackupDeleted = "Backup Deleted", + BackupDeleted = "delete backup success", BackupDeletionError = "Backup Deletion Error", ManageInstances = "Manage synchronized instances", @@ -13,16 +13,19 @@ export enum AnalyticsPage { InstanceRemovalSuccess = "Instance removal success", Unsecured = "Remove instance wrong device connected", AutoRemove = "Remove current instance", + AlreadySecuredSameSeed = "App already secured with this Ledger", + AlreadySecuredOtherSeed = "You can’t use this Ledger to Sync", - Activation = "Activate Wallet Sync", - DeviceActionActivation = "Device Action Activate Wallet Sync", + Activation = "Activate Ledger Sync", + DeviceActionActivation = "Device Action Activate Ledger Sync", CreateOrSynchronizeTrustChain = "Create or synchronize with trustchain", SynchronizationError = "Synchronization error", SyncMethod = "Choose sync method", - MobileSync = "Mobile sync", - DesktopSync = "Desktop sync", + MobileSync = "Sync from a mobile", + DesktopSync = "Sync from a desktop", KeyCreated = "Backup creation success", - KeyUpdated = "Backup update success", + KeyUpdated = "Sync apps success", + Loading = "Loading", SyncWithQR = "Sync with QR code", PinCode = "Pin code", @@ -30,21 +33,22 @@ export enum AnalyticsPage { UnbackedError = "No trustchain initialized error", SettingsGeneral = "Settings General", - WalletSyncSettings = "Wallet Sync Settings", + LedgerSyncSettings = "Ledger Sync Settings", } -type Flow = "Wallet Sync"; +export type AnalyticsFlow = "Ledger Sync"; +export const AnalyticsFlow = "Ledger Sync"; type OnClickTrack = { button: string; page: string; - flow?: Flow; + flow?: AnalyticsFlow; }; type onActionTrack = { button: string; step: Step; - flow: Flow; + flow?: AnalyticsFlow; }; export const StepMappedToAnalytics: Record = { @@ -57,13 +61,17 @@ export const StepMappedToAnalytics: Record = { [Step.CreateOrSynchronize]: AnalyticsPage.Activation, [Step.DeviceAction]: AnalyticsPage.DeviceActionActivation, [Step.CreateOrSynchronizeTrustChain]: AnalyticsPage.CreateOrSynchronizeTrustChain, + [Step.ActivationLoading]: AnalyticsPage.Loading, [Step.ActivationFinal]: AnalyticsPage.KeyCreated, [Step.SynchronizationFinal]: AnalyticsPage.KeyUpdated, [Step.SynchronizationError]: AnalyticsPage.SynchronizationError, + [Step.AlreadySecuredSameSeed]: AnalyticsPage.AlreadySecuredSameSeed, + [Step.AlreadySecuredOtherSeed]: AnalyticsPage.AlreadySecuredOtherSeed, //Synchronize [Step.SynchronizeMode]: AnalyticsPage.SyncMethod, [Step.SynchronizeWithQRCode]: AnalyticsPage.SyncWithQR, + [Step.SynchronizeLoading]: AnalyticsPage.Loading, [Step.PinCode]: AnalyticsPage.PinCode, [Step.PinCodeError]: AnalyticsPage.PinCodeError, [Step.Synchronized]: AnalyticsPage.KeyUpdated, @@ -78,11 +86,19 @@ export const StepMappedToAnalytics: Record = { [Step.InstanceSuccesfullyDeleted]: AnalyticsPage.InstanceRemovalSuccess, [Step.InstanceErrorDeletion]: AnalyticsPage.ManageInstances, - //walletSyncActivated - [Step.WalletSyncActivated]: AnalyticsPage.WalletSyncSettings, + //LedgerSyncActivated + [Step.LedgerSyncActivated]: AnalyticsPage.LedgerSyncSettings, }; -export function useWalletSyncAnalytics() { +export const StepsOutsideFlow: Step[] = [ + Step.LedgerSyncActivated, + Step.ManageBackup, + Step.AutoRemoveInstance, + Step.UnsecuredLedger, + Step.BackupDeletionError, +]; + +export function useLedgerSyncAnalytics() { const onClickTrack = ({ button, page, flow }: OnClickTrack) => { track("button_clicked2", { button, page, flow }); }; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useLoadingStep.ts b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useLoadingStep.ts new file mode 100644 index 000000000000..75b9c1a1b592 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useLoadingStep.ts @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { setFlow } from "~/renderer/actions/walletSync"; +import { walletSyncFlowSelector, walletSyncNextStepSelector } from "~/renderer/reducers/walletSync"; +import { useWalletSyncUserState } from "../components/WalletSyncContext"; +import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; + +export function useLoadingStep() { + const dispatch = useDispatch(); + const [waitedWatchLoop, setWaitedWatchLoop] = useState(false); + const { visualPending } = useWalletSyncUserState(); + const nextStep = useSelector(walletSyncNextStepSelector); + const flow = useSelector(walletSyncFlowSelector); + const featureWalletSync = useFeature("lldWalletSync"); + const initialTimeout = featureWalletSync?.params?.watchConfig?.initialTimeout || 1000; + const visualPendingTimeout = 1000; + + useEffect(() => { + const timeout = setTimeout( + () => { + setWaitedWatchLoop(true); + }, + initialTimeout + visualPendingTimeout + 500, + ); + + return () => { + clearTimeout(timeout); + }; + }, [initialTimeout]); + + useEffect(() => { + if (waitedWatchLoop && !visualPending && nextStep) { + dispatch( + setFlow({ + flow, + step: nextStep, + nextStep: null, + hasTrustchainBeenCreated: null, + }), + ); + } + }, [waitedWatchLoop, visualPending, dispatch, flow, nextStep]); +} diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useQRCode.ts b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useQRCode.ts index 255190e7ed8f..54bf875c2282 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useQRCode.ts +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useQRCode.ts @@ -1,6 +1,12 @@ import { useCallback, useState } from "react"; import { createQRCodeHostInstance } from "@ledgerhq/trustchain/qrcode/index"; -import { InvalidDigitsError, NoTrustchainInitialized } from "@ledgerhq/trustchain/errors"; +import { + InvalidDigitsError, + NoTrustchainInitialized, + QRCodeWSClosed, + TrustchainAlreadyInitialized, +} from "@ledgerhq/trustchain/errors"; +import { MemberCredentials } from "@ledgerhq/trustchain/types"; import { useDispatch, useSelector } from "react-redux"; import { setFlow, setQrCodePinCode } from "~/renderer/actions/walletSync"; import { Flow, Step } from "~/renderer/reducers/walletSync"; @@ -12,10 +18,12 @@ import { import { useTrustchainSdk } from "./useTrustchainSdk"; import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import getWalletSyncEnvironmentParams from "@ledgerhq/live-common/walletSync/getEnvironmentParams"; -import { useQueryClient } from "@tanstack/react-query"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { QueryKey } from "./type.hooks"; import { useInstanceName } from "./useInstanceName"; +const MIN_TIME_TO_REFRESH = 30_000; + export function useQRCode() { const queryClient = useQueryClient(); const dispatch = useDispatch(); @@ -26,66 +34,79 @@ export function useQRCode() { const { trustchainApiBaseUrl } = getWalletSyncEnvironmentParams( featureWalletSync?.params?.environment, ); - const [isLoading, setIsLoading] = useState(false); const [url, setUrl] = useState(null); - const [error, setError] = useState(null); const memberName = useInstanceName(); const goToActivation = useCallback(() => { dispatch(setFlow({ flow: Flow.Activation, step: Step.DeviceAction })); }, [dispatch]); - const startQRCodeProcessing = useCallback(() => { - if (!memberCredentials) return; + const { mutate, isPending, error } = useMutation({ + mutationFn: (memberCredentials: MemberCredentials) => + createQRCodeHostInstance({ + trustchainApiBaseUrl, + onDisplayQRCode: url => { + setUrl(url); + }, + onDisplayDigits: digits => { + dispatch(setQrCodePinCode(digits)); + dispatch(setFlow({ flow: Flow.Synchronize, step: Step.PinCode })); + }, + addMember: async member => { + if (trustchain) { + await sdk.addMember(trustchain, memberCredentials, member); + return trustchain; + } + throw new NoTrustchainInitialized(); + }, + memberCredentials, + memberName, + initialTrustchainId: trustchain?.rootId, + }), - setError(null); - setIsLoading(true); - createQRCodeHostInstance({ - trustchainApiBaseUrl, - onDisplayQRCode: url => { - setUrl(url); - }, - onDisplayDigits: digits => { - dispatch(setQrCodePinCode(digits)); - dispatch(setFlow({ flow: Flow.Synchronize, step: Step.PinCode })); - }, - addMember: async member => { - if (trustchain) { - await sdk.addMember(trustchain, memberCredentials, member); - return trustchain; - } - throw new NoTrustchainInitialized(); - }, - memberCredentials, - memberName, - alreadyHasATrustchain: !!trustchain, - }) - .catch(e => { - if (e instanceof InvalidDigitsError) { - dispatch(setFlow({ flow: Flow.Synchronize, step: Step.PinCodeError })); - } else if (e instanceof NoTrustchainInitialized) { - dispatch(setFlow({ flow: Flow.Synchronize, step: Step.UnbackedError })); - } - setError(e); - throw e; - }) - .then(newTrustchain => { - if (newTrustchain) { - dispatch(setTrustchain(newTrustchain)); - } - dispatch(setFlow({ flow: Flow.Synchronize, step: Step.Synchronized })); - queryClient.invalidateQueries({ queryKey: [QueryKey.getMembers] }); - setUrl(null); - dispatch(setQrCodePinCode(null)); - setIsLoading(false); - setError(null); - }); - }, [memberCredentials, trustchainApiBaseUrl, memberName, dispatch, trustchain, sdk, queryClient]); + // Don't use retry here because it always uses a delay despite setting it to 0 + onError(e) { + if (e instanceof QRCodeWSClosed) { + const { time } = e as unknown as { time: number }; + if (time >= MIN_TIME_TO_REFRESH) startQRCodeProcessing(); + } + if (e instanceof InvalidDigitsError) { + dispatch(setFlow({ flow: Flow.Synchronize, step: Step.PinCodeError })); + } + if (e instanceof NoTrustchainInitialized) { + dispatch(setFlow({ flow: Flow.Synchronize, step: Step.UnbackedError })); + } + if (e instanceof TrustchainAlreadyInitialized) { + dispatch(setFlow({ flow: Flow.Synchronize, step: Step.SynchronizeWithQRCode })); + } + }, + + onSuccess(newTrustchain) { + if (newTrustchain) { + dispatch(setTrustchain(newTrustchain)); + } + dispatch( + setFlow({ + flow: Flow.Synchronize, + step: Step.SynchronizeLoading, + nextStep: Step.Synchronized, + hasTrustchainBeenCreated: false, + }), + ); + queryClient.invalidateQueries({ queryKey: [QueryKey.getMembers] }); + setUrl(null); + dispatch(setQrCodePinCode(null)); + }, + }); + + const startQRCodeProcessing = useCallback(() => { + if (memberCredentials) mutate(memberCredentials); + }, [mutate, memberCredentials]); return { url, error, - isLoading, + isLoading: isPending, startQRCodeProcessing, goToActivation, }; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useWatchWalletSync.ts b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useWatchWalletSync.ts index 18dd0a23192d..48260ce2bca6 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useWatchWalletSync.ts +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useWatchWalletSync.ts @@ -129,7 +129,6 @@ export function useWatchWalletSync(): WalletSyncUserState { // pull and push wallet sync loop useEffect(() => { const canNotRunWatchLoop = !featureWalletSync?.enabled || !trustchain || !memberCredentials; - if (canNotRunWatchLoop) { onUserRefreshRef.current = noop; setVisualPending(false); diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/01-CreateOrSynchronizeStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/01-CreateOrSynchronizeStep.tsx index e68169e6f0ca..49330d159aba 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/01-CreateOrSynchronizeStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/01-CreateOrSynchronizeStep.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import { useTheme } from "styled-components"; import ButtonV3 from "~/renderer/components/ButtonV3"; import TrackPage from "~/renderer/analytics/TrackPage"; -import { AnalyticsPage } from "../../hooks/useWalletSyncAnalytics"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; import { LogoWrapper } from "../../components/LogoWrapper"; type Props = { diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/04-LoadingStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/04-LoadingStep.tsx new file mode 100644 index 000000000000..95d6b36eca72 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/04-LoadingStep.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import Loading from "../../components/LoadingStep"; +import { useTranslation } from "react-i18next"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; +import TrackPage from "~/renderer/analytics/TrackPage"; +import { useLoadingStep } from "../../hooks/useLoadingStep"; +import { walletSyncHasTrustchainBeenCreatedSelector } from "~/renderer/reducers/walletSync"; +import { useSelector } from "react-redux"; + +export default function ActivationLoadingStep() { + useLoadingStep(); + const hasTrustchainBeenCreated = useSelector(walletSyncHasTrustchainBeenCreatedSelector); + const { t } = useTranslation(); + const title = "walletSync.loading.title"; + const subtitle = hasTrustchainBeenCreated + ? "walletSync.loading.activation" + : "walletSync.loading.synch"; + + return ( + <> + + + + ); +} diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/04-ActivationFinalStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/05-ActivationFinalStep.tsx similarity index 86% rename from apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/04-ActivationFinalStep.tsx rename to apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/05-ActivationFinalStep.tsx index 30d092a749d8..169aba5f15ff 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/04-ActivationFinalStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/05-ActivationFinalStep.tsx @@ -4,7 +4,11 @@ import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { setDrawerVisibility, setFlow } from "~/renderer/actions/walletSync"; import { Flow, Step } from "~/renderer/reducers/walletSync"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { + AnalyticsPage, + useLedgerSyncAnalytics, + AnalyticsFlow, +} from "../../hooks/useLedgerSyncAnalytics"; type Props = { isNewBackup: boolean; @@ -16,14 +20,14 @@ export default function ActivationFinalStep({ isNewBackup }: Props) { const title = isNewBackup ? "walletSync.success.backup.title" : "walletSync.success.synch.title"; const desc = isNewBackup ? "walletSync.success.backup.desc" : "walletSync.success.synch.desc"; - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const goToSync = () => { dispatch(setFlow({ flow: Flow.Synchronize, step: Step.SynchronizeWithQRCode })); onClickTrack({ button: "Sync with another Ledger Live", page: isNewBackup ? AnalyticsPage.KeyCreated : AnalyticsPage.KeyUpdated, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; @@ -32,7 +36,7 @@ export default function ActivationFinalStep({ isNewBackup }: Props) { onClickTrack({ button: "Close", page: isNewBackup ? AnalyticsPage.KeyCreated : AnalyticsPage.KeyUpdated, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; return ( diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/05-ActivationOrSyncError.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/06-ActivationOrSyncError.tsx similarity index 100% rename from apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/05-ActivationOrSyncError.tsx rename to apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/06-ActivationOrSyncError.tsx diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/index.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/index.tsx index e60502d15229..ecf2fa2a1792 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Activation/index.tsx @@ -8,10 +8,15 @@ import { FlowOptions, useFlows } from "../../hooks/useFlows"; import CreateOrSynchronizeStep from "./01-CreateOrSynchronizeStep"; import DeviceActionStep from "./02-DeviceActionStep"; import ActivationOrSynchroWithTrustchain from "./03-ActivationOrSynchroWithTrustchain"; -import ActivationFinalStep from "./04-ActivationFinalStep"; +import LoadingStep from "./04-LoadingStep"; +import ActivationFinalStep from "./05-ActivationFinalStep"; import { Device } from "@ledgerhq/live-common/hw/actions/types"; -import ErrorStep from "./05-ActivationOrSyncError"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import ErrorStep from "./06-ActivationOrSyncError"; +import { + AnalyticsPage, + useLedgerSyncAnalytics, + AnalyticsFlow, +} from "../../hooks/useLedgerSyncAnalytics"; import { BackRef, BackProps } from "../router"; const WalletSyncActivation = forwardRef((_props, ref) => { @@ -20,7 +25,7 @@ const WalletSyncActivation = forwardRef((_props, ref) => { const { currentStep, goToNextScene, goToPreviousScene, goToWelcomeScreenWalletSync } = useFlows(); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); useImperativeHandle(ref, () => ({ goBack, @@ -39,16 +44,16 @@ const WalletSyncActivation = forwardRef((_props, ref) => { onClickTrack({ button: "Already synced a Ledger Live app?", page: AnalyticsPage.Activation, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; const goToCreateBackup = () => { goToNextScene(); onClickTrack({ - button: "create your backup", + button: "Sync your accounts", page: AnalyticsPage.Activation, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; @@ -66,10 +71,13 @@ const WalletSyncActivation = forwardRef((_props, ref) => { return ; case Step.CreateOrSynchronizeTrustChain: return ; + case Step.ActivationLoading: + return ; case Step.ActivationFinal: return ; case Step.SynchronizationFinal: return ; + case Step.SynchronizationError: return ; } diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Manage/index.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Manage/index.tsx index 88f7800af214..119438bde296 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Manage/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Manage/index.tsx @@ -10,7 +10,7 @@ import styled from "styled-components"; import { useInstances } from "../ManageInstances/useInstances"; import { useLifeCycle } from "../../hooks/walletSync.hooks"; import TrackPage from "~/renderer/analytics/TrackPage"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { AnalyticsPage, useLedgerSyncAnalytics } from "../../hooks/useLedgerSyncAnalytics"; import { useLedgerSyncInfo } from "../../hooks/useLedgerSyncInfo"; import { AlertError } from "../../components/AlertError"; import { AlertLedgerSyncDown } from "../../components/AlertLedgerSyncDown"; @@ -31,22 +31,22 @@ const WalletSyncManage = () => { const dispatch = useDispatch(); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const goToSync = () => { dispatch(setFlow({ flow: Flow.Synchronize, step: Step.SynchronizeMode })); - onClickTrack({ button: "Synchronize", page: AnalyticsPage.WalletSyncSettings }); + onClickTrack({ button: "Synchronize", page: AnalyticsPage.LedgerSyncSettings }); }; const goToManageBackup = () => { dispatch(setFlow({ flow: Flow.ManageBackup, step: Step.ManageBackup })); - onClickTrack({ button: "Manage Backup", page: AnalyticsPage.WalletSyncSettings }); + onClickTrack({ button: "Manage Key", page: AnalyticsPage.LedgerSyncSettings }); }; const goToManageInstances = () => { dispatch(setFlow({ flow: Flow.ManageInstances, step: Step.SynchronizedInstances })); - onClickTrack({ button: "Manage Instances", page: AnalyticsPage.WalletSyncSettings }); + onClickTrack({ button: "Manage Instances", page: AnalyticsPage.LedgerSyncSettings }); }; const Options: OptionProps[] = [ @@ -80,7 +80,7 @@ const WalletSyncManage = () => { return ( - + {t("walletSync.title")} diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/01-ManageBackupStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/01-ManageBackupStep.tsx index 71bd59a2b5f5..9cdfdf24e6dd 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/01-ManageBackupStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/01-ManageBackupStep.tsx @@ -5,17 +5,17 @@ import { Card } from "../../components/Card"; import styled, { useTheme } from "styled-components"; import { rgba } from "~/renderer/styles/helpers"; import { ManageBackupStepProps } from "./types"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { AnalyticsPage, useLedgerSyncAnalytics } from "../../hooks/useLedgerSyncAnalytics"; import TrackPage from "~/renderer/analytics/TrackPage"; export default function ManageBackupStep({ goToDeleteBackup }: ManageBackupStepProps) { const { t } = useTranslation(); const { colors } = useTheme(); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const handleGoDeleteBackup = () => { - onClickTrack({ button: "delete data", page: AnalyticsPage.ManageBackup }); + onClickTrack({ button: "Delete backup", page: AnalyticsPage.ManageBackup }); goToDeleteBackup(); }; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/02-DeleteBackupStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/02-DeleteBackupStep.tsx index 53ea92b9507b..b4b6bc46a5f7 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/02-DeleteBackupStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/02-DeleteBackupStep.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import { DeleteBackupStepProps } from "./types"; import { Flex, Text } from "@ledgerhq/react-ui"; import ButtonV3 from "~/renderer/components/ButtonV3"; -import { useWalletSyncAnalytics, AnalyticsPage } from "../../hooks/useWalletSyncAnalytics"; +import { useLedgerSyncAnalytics, AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; import TrackPage from "~/renderer/analytics/TrackPage"; import { useDestroyTrustchain } from "../../hooks/useDestroyTrustchain"; @@ -12,7 +12,7 @@ export default function DeleteBackupStep({ cancel }: DeleteBackupStepProps) { const { deleteMutation } = useDestroyTrustchain(); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const handleDeleteBackup = async () => { onClickTrack({ button: "delete", page: AnalyticsPage.ConfirmDeleteBackup }); diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/03-FinalStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/03-FinalStep.tsx index 0c2da9a3147f..d229fb44fc9c 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/03-FinalStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageBackup/03-FinalStep.tsx @@ -4,14 +4,18 @@ import { useTranslation } from "react-i18next"; import { Flex } from "@ledgerhq/react-ui"; import { Error } from "../../components/Error"; import { BackupDeletedProps } from "./types"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { + AnalyticsFlow, + AnalyticsPage, + useLedgerSyncAnalytics, +} from "../../hooks/useLedgerSyncAnalytics"; import { useDispatch } from "react-redux"; import { setDrawerVisibility } from "~/renderer/actions/walletSync"; export default function BackupDeleted({ isSuccessful }: BackupDeletedProps) { const { t } = useTranslation(); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const dispatch = useDispatch(); @@ -20,7 +24,7 @@ export default function BackupDeleted({ isSuccessful }: BackupDeletedProps) { onClickTrack({ button: "Close", page: AnalyticsPage.BackupDeleted, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/01-ManageInstancesStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/01-ManageInstancesStep.tsx index b14eb0fa35bb..8784d72bbf1d 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/01-ManageInstancesStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/01-ManageInstancesStep.tsx @@ -1,7 +1,7 @@ import { Flex, Text } from "@ledgerhq/react-ui"; import React from "react"; import { useTranslation } from "react-i18next"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { AnalyticsPage, useLedgerSyncAnalytics } from "../../hooks/useLedgerSyncAnalytics"; import TrackPage from "~/renderer/analytics/TrackPage"; import { TinyCard } from "../../components/TinyCard"; import { useInstances } from "./useInstances"; @@ -23,7 +23,7 @@ export default function ManageInstancesStep({ goToDeleteInstance }: Props) { const dispatch = useDispatch(); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const handleGoDeleteInstance = (instance: TrustchainMember) => { onClickTrack({ button: "remove instance", page: AnalyticsPage.ManageInstances }); diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/04-DeletionError.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/04-DeletionError.tsx index 049cebe7e48f..dcf7d843598c 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/04-DeletionError.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/04-DeletionError.tsx @@ -6,7 +6,7 @@ import styled, { useTheme } from "styled-components"; import { setFlow } from "~/renderer/actions/walletSync"; import ButtonV3 from "~/renderer/components/ButtonV3"; import { Flow, Step } from "~/renderer/reducers/walletSync"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { AnalyticsPage, useLedgerSyncAnalytics } from "../../hooks/useLedgerSyncAnalytics"; import TrackPage from "~/renderer/analytics/TrackPage"; const Container = styled(Box)` @@ -31,7 +31,7 @@ type Props = { export const DeletionError = ({ error }: Props) => { const dispatch = useDispatch(); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const tryAgain = () => { dispatch(setFlow({ flow: Flow.ManageInstances, step: Step.DeviceActionInstance })); @@ -39,7 +39,7 @@ export const DeletionError = ({ error }: Props) => { }; const goToDelete = () => { dispatch(setFlow({ flow: Flow.ManageBackup, step: Step.ManageBackup })); - onClickTrack({ button: "delete backup", page: errorConfig[error].analyticsPage }); + onClickTrack({ button: "delete key", page: errorConfig[error].analyticsPage }); }; const understood = () => { diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/04-DeletionFinalStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/04-DeletionFinalStep.tsx index de6426803ac8..7b19e4b3d01f 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/04-DeletionFinalStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/04-DeletionFinalStep.tsx @@ -2,14 +2,18 @@ import React from "react"; import { Success } from "../../components/Success"; import { useTranslation } from "react-i18next"; import { FinalStepProps } from "./04-DeletionFinalErrorStep"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { + AnalyticsFlow, + AnalyticsPage, + useLedgerSyncAnalytics, +} from "../../hooks/useLedgerSyncAnalytics"; import { setDrawerVisibility } from "~/renderer/actions/walletSync"; import { useDispatch } from "react-redux"; export default function DeletionFinalStep({ instance }: FinalStepProps) { const { t } = useTranslation(); const title = "walletSync.manageInstances.deleteInstanceSuccess"; - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const dispatch = useDispatch(); @@ -18,7 +22,7 @@ export default function DeletionFinalStep({ instance }: FinalStepProps) { onClickTrack({ button: "Close", page: AnalyticsPage.InstanceRemovalSuccess, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; return ( diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/01-SyncModeStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/01-SyncModeStep.tsx index e5e49f5b2429..d6867586be2e 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/01-SyncModeStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/01-SyncModeStep.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import { Card } from "../../components/Card"; import styled, { useTheme } from "styled-components"; import TrackPage from "~/renderer/analytics/TrackPage"; -import { AnalyticsPage } from "../../hooks/useWalletSyncAnalytics"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; type Props = { goToQRCode: () => void; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/02-QRCodeStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/02-QRCodeStep.tsx index 5aa4c55ab567..d9fb3573f96a 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/02-QRCodeStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/02-QRCodeStep.tsx @@ -7,7 +7,11 @@ import QRCode from "~/renderer/components/QRCode"; import { useQRCode } from "../../hooks/useQRCode"; import ErrorDisplay from "~/renderer/components/ErrorDisplay"; import TrackPage from "~/renderer/analytics/TrackPage"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { + AnalyticsFlow, + AnalyticsPage, + useLedgerSyncAnalytics, +} from "../../hooks/useLedgerSyncAnalytics"; import { AnimatePresence, motion, useAnimation } from "framer-motion"; const animation = { @@ -37,7 +41,7 @@ export default function SynchWithQRCodeStep() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const handleSelectOption = (option: Options) => { controls.start({ @@ -49,6 +53,7 @@ export default function SynchWithQRCodeStep() { onClickTrack({ button: option, page: AnalyticsPage.SyncWithQR, + flow: AnalyticsFlow, }); }; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/03-PinCodeStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/03-PinCodeStep.tsx index 1cbcfd348a32..20b504f6e707 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/03-PinCodeStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/03-PinCodeStep.tsx @@ -5,7 +5,7 @@ import styled, { useTheme } from "styled-components"; import { useSelector } from "react-redux"; import { walletSyncQrCodePinCodeSelector } from "~/renderer/reducers/walletSync"; import TrackPage from "~/renderer/analytics/TrackPage"; -import { AnalyticsPage } from "../../hooks/useWalletSyncAnalytics"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; export default function PinCodeStep() { const { t } = useTranslation(); diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/04-SyncFinalStep.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/04-SyncFinalStep.tsx index 6cb6a20f5196..ff88877ae101 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/04-SyncFinalStep.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/04-SyncFinalStep.tsx @@ -1,7 +1,11 @@ import React from "react"; import { Success } from "../../components/Success"; import { useTranslation } from "react-i18next"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { + AnalyticsPage, + useLedgerSyncAnalytics, + AnalyticsFlow, +} from "../../hooks/useLedgerSyncAnalytics"; import { useDispatch } from "react-redux"; import { setDrawerVisibility, setFlow } from "~/renderer/actions/walletSync"; import { Flow, Step } from "~/renderer/reducers/walletSync"; @@ -10,7 +14,7 @@ export default function SyncFinalStep() { const { t } = useTranslation(); const dispatch = useDispatch(); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const title = "walletSync.success.synch.title"; const desc = "walletSync.success.synch.desc"; @@ -20,7 +24,7 @@ export default function SyncFinalStep() { onClickTrack({ button: "Close", page: AnalyticsPage.KeyUpdated, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; @@ -29,7 +33,7 @@ export default function SyncFinalStep() { onClickTrack({ button: "Sync with another Ledger Live", page: AnalyticsPage.KeyUpdated, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/05-PinCodeError.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/05-PinCodeError.tsx index 6ce4bcea2934..b95c380b0e8b 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/05-PinCodeError.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/05-PinCodeError.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Error } from "../../components/Error"; import { useTranslation } from "react-i18next"; -import { AnalyticsPage } from "../../hooks/useWalletSyncAnalytics"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; import { useDispatch } from "react-redux"; import { setFlow } from "~/renderer/actions/walletSync"; import { Flow, Step } from "~/renderer/reducers/walletSync"; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/05-UnbackedError.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/05-UnbackedError.tsx index a78f658f2300..1ce8f8622cec 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/05-UnbackedError.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/05-UnbackedError.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Error } from "../../components/Error"; import { useTranslation } from "react-i18next"; -import { AnalyticsPage } from "../../hooks/useWalletSyncAnalytics"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; import { useDispatch } from "react-redux"; import { setFlow } from "~/renderer/actions/walletSync"; import { Flow, Step } from "~/renderer/reducers/walletSync"; diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/06-ActivationAlreadyCreatedSame.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/06-ActivationAlreadyCreatedSame.tsx new file mode 100644 index 000000000000..e1147ee6ab97 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/06-ActivationAlreadyCreatedSame.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { setDrawerVisibility, setFlow } from "~/renderer/actions/walletSync"; +import { Flow, Step } from "~/renderer/reducers/walletSync"; +import { AnalyticsPage, useLedgerSyncAnalytics } from "../../hooks/useLedgerSyncAnalytics"; +import { Info } from "../../components/Info"; + +export default function AlreadyCreatedWithSameSeedStep() { + const { t } = useTranslation(); + const dispatch = useDispatch(); + + const { onClickTrack } = useLedgerSyncAnalytics(); + + const understood = () => { + dispatch(setFlow({ flow: Flow.LedgerSyncActivated, step: Step.LedgerSyncActivated })); + onClickTrack({ + button: "I Understand", + page: AnalyticsPage.AlreadySecuredSameSeed, + }); + }; + + const onClose = () => { + dispatch(setDrawerVisibility(false)); + onClickTrack({ + button: "Close", + page: AnalyticsPage.AlreadySecuredSameSeed, + }); + }; + return ( + + ); +} diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/07-ActivationAlreadyCreatedOther.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/07-ActivationAlreadyCreatedOther.tsx new file mode 100644 index 000000000000..d5b96952a416 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/07-ActivationAlreadyCreatedOther.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { setDrawerVisibility, setFlow } from "~/renderer/actions/walletSync"; +import { Flow, Step } from "~/renderer/reducers/walletSync"; +import { + AnalyticsPage, + useLedgerSyncAnalytics, + AnalyticsFlow, +} from "../../hooks/useLedgerSyncAnalytics"; +import { Error } from "../../components/Error"; + +export default function AlreadyCreatedOtherSeedStep() { + const { t } = useTranslation(); + const dispatch = useDispatch(); + + const { onClickTrack } = useLedgerSyncAnalytics(); + + const deleteKey = () => { + dispatch(setFlow({ flow: Flow.ManageBackup, step: Step.ManageBackup })); + onClickTrack({ + button: "Delete my encryption key", + page: AnalyticsPage.AlreadySecuredOtherSeed, + flow: AnalyticsFlow, + }); + }; + + const onClose = () => { + dispatch(setDrawerVisibility(false)); + onClickTrack({ + button: "close", + page: AnalyticsPage.AlreadySecuredOtherSeed, + }); + }; + + return ( + + ); +} diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/index.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/index.tsx index ecfdb8319442..1540604179e6 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/Synchronize/index.tsx @@ -8,10 +8,17 @@ import SynchronizeModeStep from "./01-SyncModeStep"; import SynchWithQRCodeStep from "./02-QRCodeStep"; import PinCodeStep from "./03-PinCodeStep"; import SyncFinalStep from "./04-SyncFinalStep"; -import { AnalyticsPage, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics"; +import { + AnalyticsPage, + useLedgerSyncAnalytics, + AnalyticsFlow, +} from "../../hooks/useLedgerSyncAnalytics"; import PinCodeErrorStep from "./05-PinCodeError"; import UnbackedErrorStep from "./05-UnbackedError"; import { BackProps, BackRef } from "../router"; +import AlreadyCreatedWithSameSeedStep from "./06-ActivationAlreadyCreatedSame"; +import AlreadyCreatedOtherSeedStep from "./07-ActivationAlreadyCreatedOther"; +import ActivationLoadingStep from "../Activation/04-LoadingStep"; const SynchronizeWallet = forwardRef((_props, ref) => { const dispatch = useDispatch(); @@ -21,7 +28,7 @@ const SynchronizeWallet = forwardRef((_props, ref) => { })); const { currentStep, goToNextScene, goToPreviousScene, goToWelcomeScreenWalletSync } = useFlows(); - const { onClickTrack } = useWalletSyncAnalytics(); + const { onClickTrack } = useLedgerSyncAnalytics(); const goBack = () => { if (currentStep === FlowOptions[Flow.Synchronize].steps[1]) { @@ -34,18 +41,18 @@ const SynchronizeWallet = forwardRef((_props, ref) => { const startSyncWithDevice = () => { dispatch(setFlow({ flow: Flow.Activation, step: Step.DeviceAction })); onClickTrack({ - button: "With your Ledger", + button: "Use your Ledger", page: AnalyticsPage.SyncMethod, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; const startSyncWithQRcode = () => { goToNextScene(); onClickTrack({ - button: "Scan a QR code", + button: "Display QR code", page: AnalyticsPage.SyncMethod, - flow: "Wallet Sync", + flow: AnalyticsFlow, }); }; @@ -70,18 +77,33 @@ const SynchronizeWallet = forwardRef((_props, ref) => { case Step.UnbackedError: return ; + case Step.AlreadySecuredSameSeed: + return ; + case Step.AlreadySecuredOtherSeed: + return ; + case Step.SynchronizeLoading: + return ; + case Step.Synchronized: return ; } }; - const centeredItems = [Step.Synchronized, Step.PinCodeError, Step.UnbackedError]; + const centeredItems = [ + Step.Synchronized, + Step.PinCodeError, + Step.UnbackedError, + Step.AlreadySecuredSameSeed, + Step.AlreadySecuredOtherSeed, + ]; + + const withoutPaddingItems = [Step.SynchronizeLoading]; return ( ((_props, ref) => default: case Flow.Activation: return ; - case Flow.WalletSyncActivated: + case Flow.LedgerSyncActivated: return ; case Flow.Synchronize: return ; diff --git a/apps/ledger-live-desktop/src/renderer/actions/accounts.ts b/apps/ledger-live-desktop/src/renderer/actions/accounts.ts index ca53f82c2e93..cbf7c69a4713 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/accounts.ts +++ b/apps/ledger-live-desktop/src/renderer/actions/accounts.ts @@ -3,6 +3,7 @@ import { Account, AccountUserData } from "@ledgerhq/types-live"; import { AccountComparator } from "@ledgerhq/live-wallet/ordering"; import { getKey } from "~/renderer/storage"; import { PasswordIncorrectError } from "@ledgerhq/errors"; +import { getDefaultAccountName } from "@ledgerhq/live-wallet/accountName"; export const removeAccount = (payload: Account) => ({ type: "DB:REMOVE_ACCOUNT", @@ -10,8 +11,10 @@ export const removeAccount = (payload: Account) => ({ }); export const initAccounts = (data: [Account, AccountUserData][]) => { - const accounts = data.map(data => data[0]); - const accountsUserData = data.map(data => data[1]); + const accounts = data.map(([account]) => account); + const accountsUserData = data + .filter(([account, userData]) => userData.name !== getDefaultAccountName(account)) + .map(([, userData]) => userData); return { type: "INIT_ACCOUNTS", payload: { diff --git a/apps/ledger-live-desktop/src/renderer/actions/walletSync.ts b/apps/ledger-live-desktop/src/renderer/actions/walletSync.ts index 70d274509d8b..6c857dc5e9f6 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/walletSync.ts +++ b/apps/ledger-live-desktop/src/renderer/actions/walletSync.ts @@ -1,7 +1,7 @@ import { TrustchainMember } from "@ledgerhq/trustchain/types"; -import { Flow, Step } from "../reducers/walletSync"; +import { ChangeFlowPayload } from "../reducers/walletSync"; -export const setFlow = (payload: { flow: Flow; step: Step }) => ({ +export const setFlow = (payload: ChangeFlowPayload) => ({ type: "WALLET_SYNC_CHANGE_FLOW", payload, }); diff --git a/apps/ledger-live-desktop/src/renderer/analytics/segment.ts b/apps/ledger-live-desktop/src/renderer/analytics/segment.ts index 4673b22ee42a..2959f2b0942c 100644 --- a/apps/ledger-live-desktop/src/renderer/analytics/segment.ts +++ b/apps/ledger-live-desktop/src/renderer/analytics/segment.ts @@ -67,13 +67,13 @@ const getMarketWidgetAnalytics = () => { return !!marketWidget?.enabled; }; -const getWalletSyncAttributes = (state: State) => { +const getLedgerSyncAttributes = (state: State) => { if (!analyticsFeatureFlagMethod) return false; const walletSync = analyticsFeatureFlagMethod("lldWalletSync"); return { - hasWalletSync: !!walletSync?.enabled, - walletSyncActivated: !!state.trustchain.trustchain, + hasLedgerSync: !!walletSync?.enabled, + ledgerSyncActivated: !!state.trustchain.trustchain?.rootId, }; }; @@ -144,7 +144,7 @@ const extraProperties = (store: ReduxStore) => { const accounts = accountsSelector(state); const ptxAttributes = getPtxAttributes(); - const walletSyncAtributes = getWalletSyncAttributes(state); + const ledgerSyncAtributes = getLedgerSyncAttributes(state); const deviceInfo = device ? { @@ -199,7 +199,7 @@ const extraProperties = (store: ReduxStore) => { modelIdList: devices, ...ptxAttributes, ...deviceInfo, - ...walletSyncAtributes, + ...ledgerSyncAtributes, }; }; diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Welcome/index.tsx b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Welcome/index.tsx index 241f2cf602cb..a0a21a35c5a2 100644 --- a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Welcome/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Welcome/index.tsx @@ -146,7 +146,7 @@ export function Welcome() { const { analyticsOptInPromptProps, isFeatureFlagsAnalyticsPrefDisplayed, - openAnalitycsOptInPrompt, + openAnalyticsOptInPrompt, onSubmit, } = useAnalyticsOptInPrompt({ entryPoint: EntryPoint.onboarding, @@ -182,7 +182,7 @@ export function Welcome() { variant="main" onClick={_ => { isFeatureFlagsAnalyticsPrefDisplayed - ? openAnalitycsOptInPrompt("Onboarding", handleAcceptTermsAndGetStarted) + ? openAnalyticsOptInPrompt("Onboarding", handleAcceptTermsAndGetStarted) : handleAcceptTermsAndGetStarted(); }} mb="5" @@ -194,7 +194,7 @@ export function Welcome() { variant="main" onClick={_ => { isFeatureFlagsAnalyticsPrefDisplayed - ? openAnalitycsOptInPrompt("Onboarding", buyNanoX) + ? openAnalyticsOptInPrompt("Onboarding", buyNanoX) : buyNanoX(); }} outline={true} diff --git a/apps/ledger-live-desktop/src/renderer/components/Web3AppWebview/helpers.ts b/apps/ledger-live-desktop/src/renderer/components/Web3AppWebview/helpers.ts index 7e0f1aab05ce..7afe7d7ab354 100644 --- a/apps/ledger-live-desktop/src/renderer/components/Web3AppWebview/helpers.ts +++ b/apps/ledger-live-desktop/src/renderer/components/Web3AppWebview/helpers.ts @@ -277,7 +277,7 @@ export function useSelectAccount({ currentAccountHistDb?: CurrentAccountHistDB; }) { const currencies = useManifestCurrencies(manifest); - const { setCurrentAccountHist } = useDappCurrentAccount(currentAccountHistDb); + const { setCurrentAccountHist, currentAccount } = useDappCurrentAccount(currentAccountHistDb); const onSelectAccount = useCallback(() => { setDrawer( @@ -297,5 +297,5 @@ export function useSelectAccount({ ); }, [currencies, manifest.id, setCurrentAccountHist]); - return { onSelectAccount }; + return { onSelectAccount, currentAccount }; } diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.tsx b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.tsx index 7ceca9c27183..9d3503011dff 100644 --- a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.tsx @@ -18,7 +18,6 @@ import { WebviewAPI, WebviewState } from "../Web3AppWebview/types"; import Spinner from "../Spinner"; import { getAccountCurrency } from "@ledgerhq/live-common/account/helpers"; import { useDebounce } from "@ledgerhq/live-common/hooks/useDebounce"; -import { useDappCurrentAccount } from "@ledgerhq/live-common/wallet-api/useDappLogic"; import { CurrentAccountHistDB, safeGetRefValue } from "@ledgerhq/live-common/wallet-api/react"; import Wallet from "~/renderer/icons/Wallet"; import { getDefaultAccountName } from "@ledgerhq/live-wallet/accountName"; @@ -142,7 +141,6 @@ export const TopBar = ({ webviewState, }: Props) => { const { name, icon } = manifest; - const { currentAccount } = useDappCurrentAccount(currentAccountHistDb); const { shouldDisplayName = true, @@ -183,7 +181,7 @@ export const TopBar = ({ webview.goForward(); }, [webviewAPIRef]); - const { onSelectAccount } = useSelectAccount({ manifest, currentAccountHistDb }); + const { onSelectAccount, currentAccount } = useSelectAccount({ manifest, currentAccountHistDb }); const isLoading = useDebounce(webviewState.loading, 100); diff --git a/apps/ledger-live-desktop/src/renderer/families/bitcoin/SendAmountFields.tsx b/apps/ledger-live-desktop/src/renderer/families/bitcoin/SendAmountFields.tsx index 6cbba44a515a..6619d921e870 100644 --- a/apps/ledger-live-desktop/src/renderer/families/bitcoin/SendAmountFields.tsx +++ b/apps/ledger-live-desktop/src/renderer/families/bitcoin/SendAmountFields.tsx @@ -68,7 +68,8 @@ const Fields: Props = ({ }, [account.currency.id, account.id, dispatch, history]); const { errors } = status; - const { gasPrice: messageGas } = errors; + const { amount: messageAmount } = errors; + /* TODO: How do we set default RBF to be true ? (@gre) * Meanwhile, using this trick (please don't kill me) */ @@ -180,10 +181,10 @@ const Fields: Props = ({ mapStrategies={mapStrategies} status={status} /> - {messageGas && ( + {messageAmount && ( - + )} diff --git a/apps/ledger-live-desktop/src/renderer/families/evm/SendAmountFields/index.tsx b/apps/ledger-live-desktop/src/renderer/families/evm/SendAmountFields/index.tsx index 54ec9b612531..bfb477d60c0f 100644 --- a/apps/ledger-live-desktop/src/renderer/families/evm/SendAmountFields/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/families/evm/SendAmountFields/index.tsx @@ -25,8 +25,7 @@ const Root: NonNullable["component"] = props => { const bridge: AccountBridge = getAccountBridge(account); const { errors } = props.status; - const { gasPrice: messageGas } = errors; - + const { gasPrice: messageGas, amount: messageAmount } = errors; const dispatch = useDispatch(); const history = useHistory(); @@ -112,10 +111,10 @@ const Root: NonNullable["component"] = props => { )} - {messageGas && ( + {(messageGas || messageAmount) && ( - + )} diff --git a/apps/ledger-live-desktop/src/renderer/families/ton/CommentField.tsx b/apps/ledger-live-desktop/src/renderer/families/ton/CommentField.tsx index 154434ffa97e..09a302497386 100644 --- a/apps/ledger-live-desktop/src/renderer/families/ton/CommentField.tsx +++ b/apps/ledger-live-desktop/src/renderer/families/ton/CommentField.tsx @@ -38,7 +38,7 @@ const CommentField = ({ // on the ledger-live mobile return ( - - + + diff --git a/apps/ledger-live-desktop/src/renderer/hooks/useDeeplinking.ts b/apps/ledger-live-desktop/src/renderer/hooks/useDeeplinking.ts index 5e98962531f6..0834195f8231 100644 --- a/apps/ledger-live-desktop/src/renderer/hooks/useDeeplinking.ts +++ b/apps/ledger-live-desktop/src/renderer/hooks/useDeeplinking.ts @@ -18,6 +18,7 @@ import { Account, SubAccount } from "@ledgerhq/types-live"; import { useStorylyContext } from "~/storyly/StorylyProvider"; import { useNavigateToPostOnboardingHubCallback } from "~/renderer/components/PostOnboardingHub/logic/useNavigateToPostOnboardingHubCallback"; import { usePostOnboardingDeeplinkHandler } from "@ledgerhq/live-common/postOnboarding/hooks/index"; +import { setDrawerVisibility as setLedgerSyncDrawerVisibility } from "../actions/walletSync"; const getAccountsOrSubAccountsByCurrency = ( currency: CryptoOrTokenCurrency, @@ -346,6 +347,11 @@ export function useDeepLinkHandler() { postOnboardingDeeplinkHandler(query.device); break; } + case "ledgersync": { + navigate("/settings/display"); + dispatch(setLedgerSyncDrawerVisibility(true)); + break; + } case "portfolio": default: navigate("/"); diff --git a/apps/ledger-live-desktop/src/renderer/modals/Send/fields/RecipientField.react.test.tsx b/apps/ledger-live-desktop/src/renderer/modals/Send/fields/RecipientField.react.test.tsx index b1dc202f4626..e5da6e3c497f 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/Send/fields/RecipientField.react.test.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/Send/fields/RecipientField.react.test.tsx @@ -1,8 +1,9 @@ +import nock from "nock"; import React from "react"; import axios from "axios"; +import { TFunction } from "i18next"; import BigNumber from "bignumber.js"; import { render, screen, waitFor } from "tests/testUtils"; - import { getCryptoCurrencyById, setSupportedCurrencies, @@ -13,16 +14,13 @@ import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import { DomainServiceProvider } from "@ledgerhq/domain-service/hooks/index"; import { Transaction, TransactionStatus } from "@ledgerhq/live-common/generated/types"; import RecipientField from "./RecipientField"; -import { TFunction } from "i18next"; // Temp mock to prevent error on sentry init jest.mock("../../../../sentry/install", () => ({ init: () => null, })); -jest.mock("axios"); - -const mockedAxios = jest.mocked(axios); +nock.disableNetConnect(); jest.mock("@ledgerhq/live-common/featureFlags/index", () => ({ useFeature: jest.fn(), @@ -340,6 +338,7 @@ describe("RecipientField", () => { }); it("should not change domain because currency not supported", async () => { + const spy = jest.spyOn(axios, "request"); const { user } = setup(null, null, polygonMockAccount); const input = screen.getByRole("textbox"); await user.type(input, "0x16bb635bc5c398b63a0fbb38dac84da709eb3e86"); @@ -350,7 +349,7 @@ describe("RecipientField", () => { recipientDomain: undefined, }), ); - expect(mockedAxios).not.toHaveBeenCalled(); + expect(spy).not.toHaveBeenCalled(); }); }); diff --git a/apps/ledger-live-desktop/src/renderer/reducers/walletSync.ts b/apps/ledger-live-desktop/src/renderer/reducers/walletSync.ts index 1c2143e2d47b..49bca4bb680a 100644 --- a/apps/ledger-live-desktop/src/renderer/reducers/walletSync.ts +++ b/apps/ledger-live-desktop/src/renderer/reducers/walletSync.ts @@ -7,7 +7,7 @@ export enum Flow { Synchronize = "Synchronize", ManageInstances = "ManageInstances", ManageBackup = "ManageBackup", - WalletSyncActivated = "WalletSyncActivated", + LedgerSyncActivated = "LedgerSyncActivated", } export enum Step { @@ -21,15 +21,19 @@ export enum Step { CreateOrSynchronize = "CreateOrSynchronize", DeviceAction = "DeviceAction", CreateOrSynchronizeTrustChain = "CreateOrSynchronizeTrustChain", + ActivationLoading = "ActivationLoading", ActivationFinal = "ActivationFinal", SynchronizationFinal = "SynchronizationFinal", SynchronizationError = "SynchronizationError", + AlreadySecuredSameSeed = "AlreadySecuredSameSeed", + AlreadySecuredOtherSeed = "AlreadySecuredOtherSeed", //Synchronize SynchronizeMode = "SynchronizeMode", SynchronizeWithQRCode = "SynchronizeWithQRCode", PinCode = "PinCode", PinCodeError = "PinCodeError", + SynchronizeLoading = "SynchronizeLoading", UnbackedError = "UnbackedError", Synchronized = "Synchronized", @@ -43,13 +47,15 @@ export enum Step { AutoRemoveInstance = "AutoRemoveInstance", //walletSyncActivated - WalletSyncActivated = "WalletSyncActivated", + LedgerSyncActivated = "LedgerSyncActivated", } export type WalletSyncState = { isDrawerOpen: boolean; flow: Flow; step: Step; + nextStep: Step | null; + hasTrustchainBeenCreated: boolean | null; instances: TrustchainMember[]; hasBeenfaked: boolean; qrCodeUrl: string | null; @@ -60,15 +66,24 @@ export const initialStateWalletSync: WalletSyncState = { isDrawerOpen: false, flow: Flow.Activation, step: Step.CreateOrSynchronize, + nextStep: null, + hasTrustchainBeenCreated: null, instances: [], hasBeenfaked: false, qrCodePinCode: null, qrCodeUrl: null, }; +export type ChangeFlowPayload = { + flow: Flow; + step: Step; + nextStep?: Step | null; + hasTrustchainBeenCreated?: boolean | null; +}; + type HandlersPayloads = { WALLET_SYNC_CHANGE_DRAWER_VISIBILITY: boolean; - WALLET_SYNC_CHANGE_FLOW: { flow: Flow; step: Step }; + WALLET_SYNC_CHANGE_FLOW: ChangeFlowPayload; WALLET_SYNC_CHANGE_ADD_INSTANCE: TrustchainMember; WALLET_SYNC_CHANGE_REMOVE_INSTANCE: TrustchainMember; WALLET_SYNC_CHANGE_CLEAN_INSTANCES: undefined; @@ -94,11 +109,15 @@ const handlers: WalletSyncHandlers = { }), WALLET_SYNC_CHANGE_FLOW: ( state: WalletSyncState, - { payload: { flow, step } }: { payload: { flow: Flow; step: Step } }, + { + payload: { flow, step, nextStep = null, hasTrustchainBeenCreated = null }, + }: { payload: ChangeFlowPayload }, ) => ({ ...state, flow, step, + nextStep, + hasTrustchainBeenCreated, }), WALLET_SYNC_CHANGE_ADD_INSTANCE: ( state: WalletSyncState, @@ -149,6 +168,11 @@ export const walletSyncFlowSelector = (state: { walletSync: WalletSyncState }) = state.walletSync.flow; export const walletSyncStepSelector = (state: { walletSync: WalletSyncState }) => state.walletSync.step; +export const walletSyncNextStepSelector = (state: { walletSync: WalletSyncState }) => + state.walletSync.nextStep; +export const walletSyncHasTrustchainBeenCreatedSelector = (state: { + walletSync: WalletSyncState; +}) => state.walletSync.hasTrustchainBeenCreated; export const walletSyncInstancesSelector = (state: { walletSync: WalletSyncState }) => state.walletSync.instances; diff --git a/apps/ledger-live-desktop/src/renderer/screens/customImage/Step1ChooseImage.tsx b/apps/ledger-live-desktop/src/renderer/screens/customImage/Step1ChooseImage.tsx index 4a9352c75c39..924b1db1c5ef 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/customImage/Step1ChooseImage.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/customImage/Step1ChooseImage.tsx @@ -19,10 +19,6 @@ import useIsMounted from "@ledgerhq/live-common/hooks/useIsMounted"; import TrackPage from "~/renderer/analytics/TrackPage"; import { analyticsPageNames, analyticsFlowName } from "./shared"; import { useTrack } from "~/renderer/analytics/segment"; -import { setDrawer } from "~/renderer/drawers/Provider"; -import RemoveCustomImage from "../manager/DeviceDashboard/DeviceInformationSummary/RemoveCustomImage"; -import { useSelector } from "react-redux"; -import { lastSeenCustomImageSelector } from "~/renderer/reducers/settings"; type Props = StepProps & { onResult: (res: ImageBase64Data) => void; @@ -31,6 +27,7 @@ type Props = StepProps & { setIsShowingNftGallery: (_: boolean) => void; loading?: boolean; hasCustomLockScreen?: boolean; + onClickRemoveCustomImage: () => void; }; const defaultMediaTypes = ["original", "big", "preview"]; @@ -56,6 +53,7 @@ const StepChooseImage: React.FC = props => { isShowingNftGallery, setIsShowingNftGallery, hasCustomLockScreen, + onClickRemoveCustomImage, } = props; const isMounted = useIsMounted(); const { t } = useTranslation(); @@ -111,12 +109,6 @@ const StepChooseImage: React.FC = props => { [isMounted, onError, selectedNftId], ); - const lastSeenCustomImage = useSelector(lastSeenCustomImageSelector); - - const onRemove = useCallback(() => { - setDrawer(RemoveCustomImage, {}); - }, []); - return ( = props => { }); }} /> - {hasCustomLockScreen || lastSeenCustomImage?.size ? ( + {hasCustomLockScreen ? ( {t("removeCurrentPicture.cta")} diff --git a/apps/ledger-live-desktop/src/renderer/screens/customImage/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/customImage/index.tsx index 6525372c7128..f2e798b82732 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/customImage/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/customImage/index.tsx @@ -36,6 +36,7 @@ import TrackPage, { setTrackingSource } from "~/renderer/analytics/TrackPage"; import { useTrack } from "~/renderer/analytics/segment"; import DeviceModelPicker from "~/renderer/components/CustomImage/DeviceModelPicker"; import { useCompleteActionCallback } from "~/renderer/components/PostOnboardingHub/logic/useCompleteAction"; +import RemoveCustomImage from "../manager/DeviceDashboard/DeviceInformationSummary/RemoveCustomImage"; type Props = { imageUri?: string; @@ -44,6 +45,7 @@ type Props = { reopenPreviousDrawer?: () => void; deviceModelId: DeviceModelId | null; hasCustomLockScreen?: boolean; + setHasCustomLockScreen?: (value: boolean) => void; }; const orderedSteps: Step[] = [ @@ -62,6 +64,7 @@ const CustomImage: React.FC = props => { reopenPreviousDrawer, isFromPostOnboardingEntryPoint, hasCustomLockScreen, + setHasCustomLockScreen, } = props; const { t } = useTranslation(); const track = useTrack(); @@ -159,8 +162,9 @@ const CustomImage: React.FC = props => { }, []); const handleStepTransferResult = useCallback(() => { + setHasCustomLockScreen && setHasCustomLockScreen(true); setTransferDone(true); - }, []); + }, [setHasCustomLockScreen]); const handleError = useCallback( (step: Step, error: Error) => { @@ -236,6 +240,20 @@ const CustomImage: React.FC = props => { ) : null; + const [isShowingRemoveCustomImage, setIsShowingRemoveCustomImage] = useState(false); + const onClickRemoveCustomImage = useCallback(() => { + setIsShowingRemoveCustomImage(true); + }, []); + + if (isShowingRemoveCustomImage) { + return ( + setDrawer()} + onRemoved={setHasCustomLockScreen ? () => setHasCustomLockScreen(false) : undefined} + /> + ); + } + return ( = props => { isShowingNftGallery={isShowingNftGallery} setIsShowingNftGallery={setIsShowingNftGallery} hasCustomLockScreen={hasCustomLockScreen} + onClickRemoveCustomImage={onClickRemoveCustomImage} /> { setTransactionState(transaction); setTransaction(transaction); + bridge + .getTransactionStatus( + mainAccount.type === "TokenAccount" ? parentAccount : mainAccount, + transaction, + ) + .then(setTransactionStatus); }, - [setTransaction], + [setTransaction, bridge, mainAccount, parentAccount], ); - const bridge = getAccountBridge(mainAccount, parentAccount); - const handleUpdateTransaction = useCallback( (updater: (arg0: Transaction) => Transaction) => { setTransactionState(prevTransaction => { @@ -102,7 +106,7 @@ export default function FeesDrawerLiveApp({ - {transactionStatus.errors?.amount && ( - - - - )} - + {!ledgerSyncFF?.enabled && ( + + + + )} - {!ledgerSyncFF?.enabled && } + { const hasBeenFaked = useSelector(walletSyncFakedSelector); const hasBack = useMemo(() => STEPS_WITH_BACK.includes(currentStep), [currentStep]); - const { onClickTrack, onActionTrack } = useWalletSyncAnalytics(); + const hasFlowEvent = useMemo(() => !StepsOutsideFlow.includes(currentStep), [currentStep]); + + const { onClickTrack, onActionTrack } = useLedgerSyncAnalytics(); const handleBack = () => { if (childRef.current && hasBack) { childRef.current.goBack(); - onActionTrack({ button: "Back", step: currentStep, flow: "Wallet Sync" }); + onActionTrack({ + button: "Back", + step: currentStep, + flow: hasFlowEvent ? AnalyticsFlow : undefined, + }); } }; @@ -51,7 +59,11 @@ const WalletSyncRow = () => { if (hasBeenFaked) { dispatch(resetWalletSync()); } else { - onActionTrack({ button: "Close", step: currentStep, flow: "Wallet Sync" }); + onActionTrack({ + button: "Close", + step: currentStep, + flow: hasFlowEvent ? AnalyticsFlow : undefined, + }); } dispatch(setDrawerVisibility(false)); }; @@ -59,7 +71,7 @@ const WalletSyncRow = () => { const openDrawer = () => { if (!hasBeenFaked) { goToWelcomeScreenWalletSync(); - onClickTrack({ button: "Wallet Sync", page: AnalyticsPage.SettingsGeneral }); + onClickTrack({ button: "Manage Ledger Sync", page: AnalyticsPage.SettingsGeneral }); } dispatch(setDrawerVisibility(true)); }; diff --git a/apps/ledger-live-desktop/static/i18n/en/app.json b/apps/ledger-live-desktop/static/i18n/en/app.json index fd8ed6989f37..88c6642f74d0 100644 --- a/apps/ledger-live-desktop/static/i18n/en/app.json +++ b/apps/ledger-live-desktop/static/i18n/en/app.json @@ -5701,6 +5701,13 @@ "title": "Something went wrong. Please reconnect your device", "description": "{{message}}" }, + "TrustchainEjected": { + "title": "Not Synced anymore" + }, + "TrustchainNotAllowed": { + "title": "You are not in sync anymore", + "description": "Please try again or contact Ledger Support." + }, "TrustchainNotFound": { "title": "Something went wrong while fetching your data", "description": "Please try again or contact Ledger Support." @@ -6219,6 +6226,24 @@ }, "BitcoinInfrastructureError": { "title": "We are experiencing an issue with our Bitcoin infrastructure. Please try again later." + }, + "TonExcessFee": { + "title": "It constitutes a token transfer. You will pay 0.1 TON in fees, and any extra will be returned to you." + }, + "TonNotEnoughBalanceInParentAccount": { + "title": "Insufficient funds in main account (TON) to calculate fees. The minimum required balance is 0.1 TON." + }, + "TonMinimumRequired": { + "title": "Insufficient funds. The minimum required balance is 0.02 TON." + }, + "BackupAppDataError": { + "title": "Error during app data backup" + }, + "RestoreAppDataError": { + "title": "Error restoring app data" + }, + "DeleteAppDataError": { + "title": "Error deleting app data" } }, "cryptoOrg": { @@ -6414,6 +6439,7 @@ }, "walletSync": { "title": "Ledger Sync", + "close": "Close", "activate": { "title": "Sync your accounts across all platforms", "description": "Your Ledger will generate an encryption key, ensuring your data remains private and retrievable only by you.", @@ -6455,6 +6481,16 @@ "cta": "Connect another Ledger", "ctaDelete": "Delete my encryption key" }, + "alreadySecureError": { + "title": "This app is already secured by this Ledger", + "description": "If you are attempting to create a new backup, please connect the Ledger device you used to create your other encryption key.", + "cta": "I understand" + }, + "alreadySecureOtherSeedError": { + "title": "You can’t use this Ledger to sync", + "description": "If you still wish to synchronize the two apps, please delete your key and try again.", + "cta": "Delete my encryption key" + }, "autoRemoveError": { "title": "You can’t remove the current instance", "description": "For security reasons, you cannot remove the instance you are currently using without deleting your encryption key.", diff --git a/apps/ledger-live-desktop/tests/fixtures/common.ts b/apps/ledger-live-desktop/tests/fixtures/common.ts index b169ea0e5e41..82d6e8e497e3 100644 --- a/apps/ledger-live-desktop/tests/fixtures/common.ts +++ b/apps/ledger-live-desktop/tests/fixtures/common.ts @@ -3,7 +3,7 @@ import fsPromises from "fs/promises"; import * as path from "path"; import { OptionalFeatureMap } from "@ledgerhq/types-live"; import { getEnv, setEnv } from "@ledgerhq/live-env"; -import { startSpeculos, stopSpeculos, specs } from "../utils/speculos"; +import { startSpeculos, stopSpeculos, specs } from "@ledgerhq/live-common/e2e/speculos"; import { Application } from "tests/page"; import { safeAppendFile } from "tests/utils/fileUtils"; diff --git a/apps/ledger-live-desktop/tests/mocks/serviceStatusHelpers.ts b/apps/ledger-live-desktop/tests/mocks/serviceStatusHelpers.ts index d5d39892fb25..680d5ce4cd27 100644 --- a/apps/ledger-live-desktop/tests/mocks/serviceStatusHelpers.ts +++ b/apps/ledger-live-desktop/tests/mocks/serviceStatusHelpers.ts @@ -342,21 +342,6 @@ const statuses = { group: false, only_show_if_degraded: false, }, - { - id: "44xfftm0ztkq", - name: "Pivx (PIVX)", - status: "operational", - created_at: "2020-06-22T17:55:04.800+02:00", - updated_at: "2021-01-14T22:42:40.068+01:00", - position: 14, - description: null, - showcase: true, - start_date: null, - group_id: "rn7ny8423ghs", - page_id: "767c5rcj7z12", - group: false, - only_show_if_degraded: false, - }, { id: "t40n3pwhqqbq", name: "Qtum (QTUM)", diff --git a/apps/ledger-live-desktop/tests/page/drawer/ledger.sync.drawer.ts b/apps/ledger-live-desktop/tests/page/drawer/ledger.sync.drawer.ts index 51f5a5f74a17..92349af2cc30 100644 --- a/apps/ledger-live-desktop/tests/page/drawer/ledger.sync.drawer.ts +++ b/apps/ledger-live-desktop/tests/page/drawer/ledger.sync.drawer.ts @@ -16,26 +16,31 @@ export class LedgerSyncDrawer extends Drawer { @step("Synchronize accounts") async syncAccounts() { + await expect(this.syncAccountsButton).toBeVisible(); await this.syncAccountsButton.click(); } @step("Close the Ledger Sync drawer") async closeLedgerSync() { + await expect(this.closeLedgerSyncButton).toBeVisible(); await this.closeLedgerSyncButton.click(); } @step("Open the Manage Key section") async manageBackup() { + await expect(this.manageBackupButton).toBeVisible(); await this.manageBackupButton.click(); } @step("Click on the 'Delete your data' button") async deleteBackup() { + await expect(this.deleteBackupButton).toBeVisible(); await this.deleteBackupButton.click(); } @step("Confirm the deletion of the data") async confirmBackupDeletion() { + await expect(this.confirmBackupDeletionButton).toBeVisible(); await this.confirmBackupDeletionButton.click(); } diff --git a/apps/ledger-live-desktop/tests/page/speculos.page.ts b/apps/ledger-live-desktop/tests/page/speculos.page.ts index eb07764ecab1..1c097a87ddc7 100644 --- a/apps/ledger-live-desktop/tests/page/speculos.page.ts +++ b/apps/ledger-live-desktop/tests/page/speculos.page.ts @@ -7,7 +7,7 @@ import { verifyAddress as assertAddressesEquality, verifyAmount, waitFor, -} from "tests/utils/speculos"; +} from "@ledgerhq/live-common/e2e/speculos"; import { Account } from "../enum/Account"; import { expect } from "@playwright/test"; import { Transaction } from "tests/models/Transaction"; diff --git a/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts-snapshots/wallet-api-currencies-darwin.json b/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts-snapshots/wallet-api-currencies-darwin.json index 8856c8056cd8..4f16e28d6e9e 100644 --- a/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts-snapshots/wallet-api-currencies-darwin.json +++ b/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts-snapshots/wallet-api-currencies-darwin.json @@ -341,15 +341,6 @@ "color": "#326464", "decimals": 8 }, - { - "type": "CryptoCurrency", - "id": "pivx", - "ticker": "PIVX", - "name": "PivX", - "family": "bitcoin", - "color": "#46385d", - "decimals": 8 - }, { "type": "CryptoCurrency", "id": "zencash", diff --git a/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts-snapshots/wallet-api-currencies-linux.json b/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts-snapshots/wallet-api-currencies-linux.json index 8856c8056cd8..4f16e28d6e9e 100644 --- a/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts-snapshots/wallet-api-currencies-linux.json +++ b/apps/ledger-live-desktop/tests/specs/services/wallet-api.spec.ts-snapshots/wallet-api-currencies-linux.json @@ -341,15 +341,6 @@ "color": "#326464", "decimals": 8 }, - { - "type": "CryptoCurrency", - "id": "pivx", - "ticker": "PIVX", - "name": "PivX", - "family": "bitcoin", - "color": "#46385d", - "decimals": 8 - }, { "type": "CryptoCurrency", "id": "zencash", diff --git a/apps/ledger-live-desktop/tests/utils/allureUtils.ts b/apps/ledger-live-desktop/tests/utils/allureUtils.ts index b345d6b700e5..49a90707c53f 100644 --- a/apps/ledger-live-desktop/tests/utils/allureUtils.ts +++ b/apps/ledger-live-desktop/tests/utils/allureUtils.ts @@ -2,7 +2,7 @@ import { allure } from "allure-playwright"; import { Page, TestInfo } from "@playwright/test"; import { promisify } from "util"; import fs from "fs"; -import { takeScreenshot } from "./speculos"; +import { takeScreenshot } from "@ledgerhq/live-common/e2e/speculos"; const readFileAsync = promisify(fs.readFile); diff --git a/apps/ledger-live-mobile/__mocks__/api/market/coinsList.json b/apps/ledger-live-mobile/__mocks__/api/market/coinsList.json index 3961458afa88..5de7364784c2 100644 --- a/apps/ledger-live-mobile/__mocks__/api/market/coinsList.json +++ b/apps/ledger-live-mobile/__mocks__/api/market/coinsList.json @@ -8553,7 +8553,6 @@ { "id": "pitch-fxs", "symbol": "pitchfxs", "name": "Pitch FXS" }, { "id": "piteas", "symbol": "pts", "name": "Piteas" }, { "id": "pivn", "symbol": "pivn", "name": "PIVN" }, - { "id": "pivx", "symbol": "pivx", "name": "PIVX" }, { "id": "pixel-battle", "symbol": "pwc", "name": "Pixel Battle" }, { "id": "pixelpotus", "symbol": "pxl", "name": "PixelPotus" }, { "id": "pixelverse", "symbol": "pixel", "name": "PixelVerse" }, diff --git a/apps/ledger-live-mobile/android/dapp-store/config.yaml b/apps/ledger-live-mobile/android/dapp-store/config.yaml index 312b825b1804..8b619ae124d3 100644 --- a/apps/ledger-live-mobile/android/dapp-store/config.yaml +++ b/apps/ledger-live-mobile/android/dapp-store/config.yaml @@ -95,7 +95,7 @@ release: Tron (TRX), Polygon (MATIC), Ethereum Classic (ETC), Dash (DASH), Cosmos (ATOM), Elrond (EGLD), Zcash (ZEC), Dogecoin (DOGE), Digibyte (DGB), Bitcoin Gold (BTG), Decred (DCR), Qtum (QTUM), Algorand (ALGO), Komodo - (KMD), Horizen (ZEN), PivX (PIVX), Stakenet (XSN), ERC-20 and BEP-20 tokens. + (KMD), Horizen (ZEN), Stakenet (XSN), ERC-20 and BEP-20 tokens. diff --git a/apps/ledger-live-mobile/src/actions/accounts.ts b/apps/ledger-live-mobile/src/actions/accounts.ts index 0f8182e7a5a6..37da12eabb5e 100644 --- a/apps/ledger-live-mobile/src/actions/accounts.ts +++ b/apps/ledger-live-mobile/src/actions/accounts.ts @@ -11,6 +11,7 @@ import type { import { AccountsActionTypes } from "./types"; import logger from "../logger"; import { initAccounts } from "@ledgerhq/live-wallet/store"; +import { getDefaultAccountName } from "@ledgerhq/live-wallet/accountName"; const version = 0; // FIXME this needs to come from user data @@ -26,7 +27,9 @@ export const importStore = (rawAccounts: { active: { data: AccountRaw }[] }) => } } const accounts = tuples.map(([account]) => account); - const accountsUserData = tuples.map(([, userData]) => userData); + const accountsUserData = tuples + .filter(([account, userData]) => userData.name !== getDefaultAccountName(account)) + .map(([, userData]) => userData); return initAccounts(accounts, accountsUserData); }; export const reorderAccounts = createAction( diff --git a/apps/ledger-live-mobile/src/actions/types.ts b/apps/ledger-live-mobile/src/actions/types.ts index e7ba9100ff4f..bc1686033660 100644 --- a/apps/ledger-live-mobile/src/actions/types.ts +++ b/apps/ledger-live-mobile/src/actions/types.ts @@ -529,10 +529,12 @@ export type MarketPayload = // === WALLETSYNC ACTIONS === export enum WalletSyncActionTypes { WALLET_SYNC_SET_MANAGE_KEY_DRAWER = "WALLET_SYNC_SET_MANAGE_KEY_DRAWER", + LEDGER_SYNC_SET_ACTIVATE_DRAWER = "LEDGER_SYNC_SET_ACTIVATE_DRAWER", } export type WalletSyncSetManageKeyDrawerPayload = boolean; -export type WalletSyncPayload = WalletSyncSetManageKeyDrawerPayload; +export type WalletSyncSetActivateDrawer = boolean; +export type WalletSyncPayload = WalletSyncSetManageKeyDrawerPayload | WalletSyncSetActivateDrawer; // === PAYLOADS === diff --git a/apps/ledger-live-mobile/src/actions/walletSync.ts b/apps/ledger-live-mobile/src/actions/walletSync.ts index 8cde4b6c877e..49563fdbcc15 100644 --- a/apps/ledger-live-mobile/src/actions/walletSync.ts +++ b/apps/ledger-live-mobile/src/actions/walletSync.ts @@ -1,7 +1,11 @@ import { createAction } from "redux-actions"; import { WalletSyncActionTypes } from "./types"; -import type { WalletSyncSetManageKeyDrawerPayload } from "./types"; +import type { WalletSyncSetActivateDrawer, WalletSyncSetManageKeyDrawerPayload } from "./types"; export const setWallectSyncManageKeyDrawer = createAction( WalletSyncActionTypes.WALLET_SYNC_SET_MANAGE_KEY_DRAWER, ); + +export const setLedgerSyncActivateDrawer = createAction( + WalletSyncActionTypes.LEDGER_SYNC_SET_ACTIVATE_DRAWER, +); diff --git a/apps/ledger-live-mobile/src/analytics/segment.ts b/apps/ledger-live-mobile/src/analytics/segment.ts index 1a049425acc7..bf70f149af19 100644 --- a/apps/ledger-live-mobile/src/analytics/segment.ts +++ b/apps/ledger-live-mobile/src/analytics/segment.ts @@ -97,6 +97,16 @@ runOnceWhen(() => !!analyticsFeatureFlagMethod && !!segmentClient, getFeatureFla export const updateSessionId = () => (sessionId = uuid()); +const getLedgerSyncAttributes = (state: State) => { + if (!analyticsFeatureFlagMethod) return false; + const ledgerSync = analyticsFeatureFlagMethod("llmWalletSync"); + + return { + hasLedgerSync: !!ledgerSync?.enabled, + ledgerSyncActivated: !!state.trustchain.trustchain?.rootId, + }; +}; + const getMandatoryProperties = async (store: AppStore) => { const state: State = store.getState(); const { user } = await getOrCreateUser(); @@ -175,6 +185,8 @@ const extraProperties = async (store: AppStore) => { const stakingProvidersCount = stakingProviders?.enabled && stakingProviders?.params?.listProvider.length; + const ledgerSyncAtributes = getLedgerSyncAttributes(state); + return { ...mandatoryProperties, appVersion, @@ -209,6 +221,7 @@ const extraProperties = async (store: AppStore) => { staxLockscreen: customImageType || "none", nps, stakingProvidersEnabled: stakingProvidersCount || "flag not loaded", + ...ledgerSyncAtributes, }; }; diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/types/AddAccountsNavigator.ts b/apps/ledger-live-mobile/src/components/RootNavigator/types/AddAccountsNavigator.ts index bda002c19d75..1e30400175a8 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/types/AddAccountsNavigator.ts +++ b/apps/ledger-live-mobile/src/components/RootNavigator/types/AddAccountsNavigator.ts @@ -15,14 +15,14 @@ export type AddAccountsNavigatorParamList = { inline?: boolean; returnToSwap?: boolean; analyticsPropertyFlow?: string; - onSuccess?: () => void; + onSuccess?: (res: { scannedAccounts: Account[]; selected: Account[] }) => void; }; [ScreenName.AddAccountsAccounts]: { currency: CryptoOrTokenCurrency; device: Device; inline?: boolean; returnToSwap?: boolean; - onSuccess?: (_?: unknown) => void; + onSuccess?: (res: { scannedAccounts: Account[]; selected: Account[] }) => void; }; [ScreenName.AddAccountsSuccess]?: { currency: CryptoOrTokenCurrency; diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/types/WalletSyncNavigator.ts b/apps/ledger-live-mobile/src/components/RootNavigator/types/WalletSyncNavigator.ts index 7e823a4d5672..83fb5ecf2683 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/types/WalletSyncNavigator.ts +++ b/apps/ledger-live-mobile/src/components/RootNavigator/types/WalletSyncNavigator.ts @@ -2,12 +2,18 @@ import { TrustchainMember } from "@ledgerhq/trustchain/types"; import { ScreenName } from "~/const"; export type WalletSyncNavigatorStackParamList = { + [ScreenName.LedgerSyncDeepLinkHandler]: undefined; + [ScreenName.WalletSyncActivationInit]: undefined; [ScreenName.WalletSyncSuccess]: { created: boolean; }; + [ScreenName.WalletSyncLoading]: { + created: boolean; + }; + [ScreenName.WalletSyncActivationProcess]: undefined; [ScreenName.WalletSyncActivated]: undefined; diff --git a/apps/ledger-live-mobile/src/components/Web3AppWebview/helpers.ts b/apps/ledger-live-mobile/src/components/Web3AppWebview/helpers.ts index f215b846313a..3b32d4aa8eab 100644 --- a/apps/ledger-live-mobile/src/components/Web3AppWebview/helpers.ts +++ b/apps/ledger-live-mobile/src/components/Web3AppWebview/helpers.ts @@ -529,7 +529,7 @@ export function useSelectAccount({ currentAccountHistDb?: CurrentAccountHistDB; }) { const currencies = useManifestCurrencies(manifest); - const { setCurrentAccountHist } = useDappCurrentAccount(currentAccountHistDb); + const { setCurrentAccountHist, currentAccount } = useDappCurrentAccount(currentAccountHistDb); const navigation = useNavigation(); const onSelectAccount = useCallback(() => { @@ -558,5 +558,5 @@ export function useSelectAccount({ } }, [manifest.id, currencies, navigation, setCurrentAccountHist]); - return { onSelectAccount }; + return { onSelectAccount, currentAccount }; } diff --git a/apps/ledger-live-mobile/src/components/WebPlatformPlayer/SelectAccountButton.tsx b/apps/ledger-live-mobile/src/components/WebPlatformPlayer/SelectAccountButton.tsx index 9a2568fa4dfc..5244e936605d 100644 --- a/apps/ledger-live-mobile/src/components/WebPlatformPlayer/SelectAccountButton.tsx +++ b/apps/ledger-live-mobile/src/components/WebPlatformPlayer/SelectAccountButton.tsx @@ -1,9 +1,8 @@ import React from "react"; import { Trans } from "react-i18next"; -import { Flex, Text } from "@ledgerhq/native-ui"; +import { Text } from "@ledgerhq/native-ui"; import { AppManifest } from "@ledgerhq/live-common/wallet-api/types"; import { CurrentAccountHistDB } from "@ledgerhq/live-common/wallet-api/react"; -import { useDappCurrentAccount } from "@ledgerhq/live-common/wallet-api/useDappLogic"; import Button from "~/components/Button"; import CircleCurrencyIcon from "~/components/CircleCurrencyIcon"; import { useSelectAccount } from "~/components/Web3AppWebview/helpers"; @@ -18,20 +17,14 @@ export default function SelectAccountButton({ manifest, currentAccountHistDb, }: SelectAccountButtonProps) { - const { currentAccount } = useDappCurrentAccount(currentAccountHistDb); - - const { onSelectAccount } = useSelectAccount({ manifest, currentAccountHistDb }); + const { onSelectAccount, currentAccount } = useSelectAccount({ manifest, currentAccountHistDb }); const currentAccountName = useMaybeAccountName(currentAccount); return ( - ); diff --git a/apps/ledger-live-mobile/src/const/navigation.ts b/apps/ledger-live-mobile/src/const/navigation.ts index 29aa26204fee..a621351af992 100644 --- a/apps/ledger-live-mobile/src/const/navigation.ts +++ b/apps/ledger-live-mobile/src/const/navigation.ts @@ -514,11 +514,13 @@ export enum ScreenName { WalletSyncActivationInit = "WalletSyncActivationInit", WalletSyncActivationProcess = "WalletSyncActivationProcess", WalletSyncSuccess = "WalletSyncSuccess", + WalletSyncLoading = "WalletSyncLoading", WalletSyncActivated = "WalletSyncActivated", WalletSyncManageKeyDeleteSuccess = "WalletSyncManageKeyDeleteSuccess", WalletSyncUnSynchSuccess = "WalletSyncUnSynchSuccess", WalletSyncManageInstancesProcess = "WalletSyncManageInstancesProcess", WalletSyncManageInstancesSuccess = "WalletSyncManageInstancesSuccess", + LedgerSyncDeepLinkHandler = "LedgerSyncDeepLinkHandler", MockedAddAssetButton = "MockedAddAssetButton", GenericLandingPage = "GenericLandingPage", diff --git a/apps/ledger-live-mobile/src/live-common-setup.ts b/apps/ledger-live-mobile/src/live-common-setup.ts index e6e4e721f9ad..fce1cf7b77ca 100644 --- a/apps/ledger-live-mobile/src/live-common-setup.ts +++ b/apps/ledger-live-mobile/src/live-common-setup.ts @@ -74,7 +74,6 @@ setSupportedCurrencies([ "qtum", "bitcoin_gold", "komodo", - "pivx", "zencash", "bitcoin_testnet", "ethereum_sepolia", diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index fecfc7b7c453..915c2710e410 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -946,9 +946,34 @@ "description": "The server could not handle your request. Please try again later or contact Ledger Support.", "errorCode": "Code: {{errorCode}}" }, + "TrustchainEjected": { + "title": "Not Synced anymore" + }, + "TrustchainNotAllowed": { + "title": "You are not in sync anymore", + "description": "Please try again or contact Ledger Support." + }, "TrustchainNotFound": { "title": "Something went wrong while fetching your data", "description": "Please try again or contact Ledger Support." + }, + "TonExcessFee": { + "title": "It constitutes a token transfer. You will pay 0.1 TON in fees, and any extra will be returned to you." + }, + "TonNotEnoughBalanceInParentAccount": { + "title": "Insufficient funds in main account (TON) to calculate fees. The minimum required balance is 0.1 TON." + }, + "TonMinimumRequired": { + "title": "Insufficient funds. The minimum required balance is 0.02 TON." + }, + "BackupAppDataError": { + "title": "Error during app data backup" + }, + "RestoreAppDataError": { + "title": "Error restoring app data" + }, + "DeleteAppDataError": { + "title": "Error deleting app data" } }, "crash": { @@ -6737,6 +6762,11 @@ "title": "Continue on your Ledger {{wording}}", "description": "Follow the instruction which appear on your Ledger’s Trusted Display." }, + "loading": { + "title": "Hang tight...", + "activation": "Your data is being end-to-end encrypted... ", + "synch": "We are updating the synched instances..." + }, "success": { "sync": "Synchronization successful!", "syncDesc": "Changes in your accounts will now automatically appear across all apps and platforms.", @@ -6795,8 +6825,6 @@ }, "errors": { "fetching": "Something went wrong while fetching your synchronized instances.", - "trustchain": "Unexpected falsy trustchain", - "memberCredentials": "Unexpected falsy member credentials", "ledgerSyncUnavailable": "Ledger Sync is currently unavailable. This doesn’t have any impact on your assets." } }, @@ -6846,9 +6874,28 @@ }, "unbacked": { "title": "You need to create your encryption key first", - "desc": "Please make sure you’ve created an encryption key on one of your Ledger Live apps before continuing your synchronization.", + "description": "Please make sure you’ve created an encryption key on one of your Ledger Live apps before continuing your synchronization.", "cta": "Create your encryption key" + }, + "backedWithDifferentSeeds": { + "title": "These apps have different backups", + "description": "Delete your encryption key from one of the apps and try again", + "cta": "I understand" + }, + "alreadyBacked": { + "title": "These apps are already secured by a Ledger", + "cta": "I understand" } + }, + "alreadySecureError": { + "title": "This app is already secured by this Ledger", + "description": "If you are attempting to create a new backup, please connect the Ledger device you used to create your other encryption key.", + "cta": "I understand" + }, + "alreadySecureOtherSeedError": { + "title": "You can’t use this Ledger to sync", + "description": "If you still wish to synchronize the two apps, please delete your key and try again.", + "cta": "Delete my encryption key" } } }, @@ -6877,8 +6924,20 @@ "placeholder": "Search or type a URL" }, "clearSigning": { - "title": "Clear signing", - "description": "Clear signing allows you to sign a message without revealing the content of the message to the app. This is useful for privacy and security reasons." + "title": "Clear signing" + } + }, + "app": { + "selectAccountModal": { + "networkHeader": { + "title": "Choose a network" + }, + "accountHeader": { + "title": "Choose an account" + }, + "addAccountItem": { + "name": "Add an account" + } } } } diff --git a/apps/ledger-live-mobile/src/navigation/DeeplinksProvider.tsx b/apps/ledger-live-mobile/src/navigation/DeeplinksProvider.tsx index 3c8e694c21df..4698bb825333 100644 --- a/apps/ledger-live-mobile/src/navigation/DeeplinksProvider.tsx +++ b/apps/ledger-live-mobile/src/navigation/DeeplinksProvider.tsx @@ -359,6 +359,12 @@ const linkingOptions = () => ({ [ScreenName.GenericLandingPage]: "landing-page", }, }, + + [NavigatorName.WalletSync]: { + screens: { + [ScreenName.LedgerSyncDeepLinkHandler]: "ledgersync", + }, + }, }, }, }, diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/SelectAddAccountMethod/useSelectAddAccountMethodViewModel.ts b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/SelectAddAccountMethod/useSelectAddAccountMethodViewModel.ts index 3c44bf7bf992..b2f865ae99a3 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/SelectAddAccountMethod/useSelectAddAccountMethodViewModel.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/SelectAddAccountMethod/useSelectAddAccountMethodViewModel.ts @@ -37,7 +37,7 @@ const useSelectAddAccountMethodViewModel = ({ currency, onClose }: AddAccountScr }, []); const onClickImport = useCallback(() => { - trackButtonClick("Import from Desktop"); + trackButtonClick("Import via another Ledger Live app"); onClose?.(); navigation.navigate(NavigatorName.ImportAccounts); }, [navigation, trackButtonClick, onClose]); diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/StepFlow.tsx b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/StepFlow.tsx index 5113dff3da12..d0e6b600d21d 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/StepFlow.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/StepFlow.tsx @@ -11,7 +11,8 @@ import PinCodeDisplay from "LLM/features/WalletSync/screens/Synchronize/PinCodeD import PinCodeInput from "LLM/features/WalletSync/screens/Synchronize/PinCodeInput"; import { useInitMemberCredentials } from "LLM/features/WalletSync/hooks/useInitMemberCredentials"; import { useSyncWithQrCode } from "LLM/features/WalletSync/hooks/useSyncWithQrCode"; -import UnbackedError from "~/newArch/features/WalletSync/screens/Synchronize/UnbackedError"; +import { SpecificError } from "~/newArch/features/WalletSync/components/Error/SpecificError"; +import { ErrorReason } from "~/newArch/features/WalletSync/hooks/useSpecificError"; type Props = { currentStep: Steps; @@ -99,8 +100,23 @@ const StepFlow = ({ return ; case Steps.UnbackedError: - return ; + return ; + case Steps.AlreadyBacked: + return ( + setCurrentStep(Steps.QrCodeMethod)} + error={ErrorReason.ALREADY_BACKED_SCAN} + /> + ); + + case Steps.BackedWithDifferentSeeds: + return ( + setCurrentStep(Steps.QrCodeMethod)} + error={ErrorReason.DIFFERENT_BACKUPS} + /> + ); default: return null; } diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/useAddAccountViewModel.ts b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/useAddAccountViewModel.ts index 0afdbb6c0dec..0f2ed84de879 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/useAddAccountViewModel.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/useAddAccountViewModel.ts @@ -3,11 +3,6 @@ import { track } from "~/analytics"; import { useQRCodeHost } from "LLM/features/WalletSync/hooks/useQRCodeHost"; import { Options, Steps } from "LLM/features/WalletSync/types/Activation"; import { NavigatorName, ScreenName } from "~/const"; -import { - AnalyticsButton, - AnalyticsPage, - useLedgerSyncAnalytics, -} from "~/newArch/features/WalletSync/hooks/useLedgerSyncAnalytics"; import { useNavigation } from "@react-navigation/native"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; @@ -26,7 +21,6 @@ const startingStep = Steps.AddAccountMethod; const useAddAccountViewModel = ({ isOpened, onClose }: AddAccountDrawerProps) => { const [currentStep, setCurrentStep] = useState(startingStep); const [currentOption, setCurrentOption] = useState(Options.SCAN); - const { onClickTrack } = useLedgerSyncAnalytics(); const navigateToChooseSyncMethod = () => setCurrentStep(Steps.ChooseSyncMethod); const navigateToQrCodeMethod = () => setCurrentStep(Steps.QrCodeMethod); const navigation = useNavigation(); @@ -68,7 +62,6 @@ const useAddAccountViewModel = ({ isOpened, onClose }: AddAccountDrawerProps) => }); const onCreateKey = () => { - onClickTrack({ button: AnalyticsButton.CreateYourKey, page: AnalyticsPage.Unbacked }); navigation.navigate(NavigatorName.WalletSync, { screen: ScreenName.WalletSyncActivationProcess, }); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/WalletSyncNavigator.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/WalletSyncNavigator.tsx index cc979921e68d..a16759c17175 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/WalletSyncNavigator.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/WalletSyncNavigator.tsx @@ -7,12 +7,15 @@ import { WalletSyncNavigatorStackParamList } from "../../../components/RootNavig import WalletSyncActivation from "LLM/features/WalletSync/screens/Activation"; import { ActivationProcess } from "./screens/Activation/ActivationProcess"; import { ActivationSuccess } from "./screens/Activation/ActivationSuccess"; +import { ActivationLoading } from "./screens/Activation/ActivationLoading"; import { useInitMemberCredentials } from "./hooks/useInitMemberCredentials"; import WalletSyncManage from "./screens/Manage"; import { useTranslation } from "react-i18next"; import { WalletSyncManageKeyDeletionSuccess } from "./screens/ManageKey/DeletionSuccess"; import { ManageInstancesProcess } from "./screens/ManageInstances/ManageInstancesProcess"; import { WalletSyncManageInstanceDeletionSuccess } from "./screens/ManageInstances/DeletionSuccess"; +import { LedgerSyncDeepLinkHandler } from "./screens/LedgerSyncDeepLinkHandler"; +import { NavigationHeaderCloseButton } from "~/components/NavigationHeaderCloseButton"; const Stack = createStackNavigator(); @@ -40,6 +43,15 @@ export default function WalletSyncNavigator() { headerRight: () => null, }} /> + null, + headerRight: () => , + }} + /> null, }} /> + null, + }} + /> ); } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/manageInstances.integration.test.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/manageInstances.integration.test.tsx index 7d8d014ae56f..04ec052ca800 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/manageInstances.integration.test.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/manageInstances.integration.test.tsx @@ -30,6 +30,7 @@ jest.mock("../hooks/useGetMembers", () => ({ data: INSTANCES, isError: false, error: null, + refetch: jest.fn(), }), })); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Activation/ActivationFlow.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Activation/ActivationFlow.tsx index 59cf1eaa9ece..f45c01ec6e11 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Activation/ActivationFlow.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Activation/ActivationFlow.tsx @@ -10,7 +10,8 @@ import PinCodeInput from "../../screens/Synchronize/PinCodeInput"; import SyncError from "../../screens/Synchronize/SyncError"; import { useInitMemberCredentials } from "../../hooks/useInitMemberCredentials"; import { useSyncWithQrCode } from "../../hooks/useSyncWithQrCode"; -import UnbackedError from "../../screens/Synchronize/UnbackedError"; +import { SpecificError } from "../Error/SpecificError"; +import { ErrorReason } from "../../hooks/useSpecificError"; type Props = { currentStep: Steps; @@ -91,7 +92,23 @@ const ActivationFlow = ({ return ; case Steps.UnbackedError: - return ; + return ; + + case Steps.AlreadyBacked: + return ( + setCurrentStep(Steps.QrCodeMethod)} + error={ErrorReason.ALREADY_BACKED_SCAN} + /> + ); + + case Steps.BackedWithDifferentSeeds: + return ( + setCurrentStep(Steps.QrCodeMethod)} + error={ErrorReason.DIFFERENT_BACKUPS} + /> + ); default: return null; } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Detailed.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Detailed.tsx new file mode 100644 index 000000000000..ccca7cb91ac2 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Detailed.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { Flex, Text, Button, Link, Box } from "@ledgerhq/native-ui"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; +import TrackScreen from "~/analytics/TrackScreen"; +import { ButtonProps } from "@ledgerhq/native-ui/components/cta/Button"; +import styled from "styled-components/native"; + +interface Props { + icon: React.ReactNode; + title: string; + description?: string; + info?: string; + cta: string; + ctaSecondary?: string; + primaryAction: () => void; + secondaryAction?: () => void; + analyticsPage: AnalyticsPage; + buttonType: ButtonProps["type"]; + outline?: boolean; +} +export function DetailedError(props: Props) { + const { + icon, + title, + description, + info, + cta, + ctaSecondary, + primaryAction, + secondaryAction, + analyticsPage, + buttonType, + outline, + } = props; + + return ( + + + {icon} + + {title} + + + + {description && ( + + {description} + + )} + {info && ( + + {info} + + )} + + + + + {ctaSecondary && secondaryAction && ( + + + {ctaSecondary} + + + )} + + + ); +} + +const Container = styled(Box)` + background-color: ${p => p.theme.colors.opacityDefault.c05}; + border-radius: 100px; + height: 72px; + width: 72px; + display: flex; + align-items: center; + justify-content: center; +`; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/index.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Simple.tsx similarity index 75% rename from apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/index.tsx rename to apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Simple.tsx index 0e0820a280fb..44088110ed7e 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Error/Simple.tsx @@ -1,20 +1,23 @@ import { Box, Button, Flex, Icons, Text } from "@ledgerhq/native-ui"; import React from "react"; import styled, { useTheme } from "styled-components/native"; +import { TrackScreen } from "~/analytics"; type Props = { title: string; - desc: string; + desc?: string; mainButton: { label: string; onPress: () => void; outline: boolean; }; + analyticsPage: string; }; -export function ErrorComponent({ title, desc, mainButton }: Props) { +export function ErrorComponent({ title, desc, mainButton, analyticsPage }: Props) { const { colors } = useTheme(); return ( + @@ -22,9 +25,11 @@ export function ErrorComponent({ title, desc, mainButton }: Props) { {title} - - {desc} - + {desc && ( + + {desc} + + )} - - - - {ctaSecondary} - - - - - ); +export const DeletionError = (props: Props) => { + return ; }; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/ManageInstances/ListInstances.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/ManageInstances/ListInstances.tsx index a847b7257442..09773db612a9 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/ManageInstances/ListInstances.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/ManageInstances/ListInstances.tsx @@ -6,6 +6,8 @@ import { useTranslation } from "react-i18next"; import { TrustchainMember } from "@ledgerhq/trustchain/types"; import { TinyCard } from "../TinyCard"; import { Scene } from "../../screens/ManageInstances/useManageInstanceDrawer"; +import { TrackScreen } from "~/analytics"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; type Props = { onClickInstance: (member: TrustchainMember) => void; @@ -41,6 +43,7 @@ export function ListInstances({ onClickInstance, changeScene, members, currentIn return ( + {t("walletSync.walletSyncActivated.manageInstances.title")} diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/ManageKey/ManageKey.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/ManageKey/ManageKey.tsx index 258f7b103020..81d100cc365e 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/ManageKey/ManageKey.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/ManageKey/ManageKey.tsx @@ -4,6 +4,8 @@ import { Flex, Icons, rgba, Text } from "@ledgerhq/native-ui"; import { TouchableOpacity } from "react-native"; import styled, { useTheme } from "styled-components/native"; import { useTranslation } from "react-i18next"; +import { TrackScreen } from "~/analytics"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; type Props = { onClickDelete: () => void; @@ -14,6 +16,7 @@ export function ManageKey({ onClickDelete }: Props) { const { colors } = useTheme(); return ( + {t("walletSync.walletSyncActivated.manageKey.drawer.step1.title")} diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Success/index.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Success/index.tsx index 90e68185ee17..2225178ade55 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Success/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Success/index.tsx @@ -1,6 +1,7 @@ import { Box, Button, Flex, Icons, Text } from "@ledgerhq/native-ui"; import React from "react"; import styled, { useTheme } from "styled-components/native"; +import { TrackScreen } from "~/analytics"; import SafeAreaView from "~/components/SafeAreaView"; type Props = { title: string; @@ -14,12 +15,14 @@ type Props = { label: string; onPress: () => void; }; + analyticsPage: string; }; -export function Success({ title, desc, mainButton, secondaryButton }: Props) { +export function Success({ title, desc, mainButton, secondaryButton, analyticsPage }: Props) { const { colors } = useTheme(); return ( + diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/type.hooks.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/type.hooks.ts index eaca56be58ea..50aa8de14098 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/type.hooks.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/type.hooks.ts @@ -3,6 +3,7 @@ export enum QueryKey { destroyTrustchain = "useDestroyTrustchain", fetchTrustchainStatus = "useFetchTrustchainStatus", fetchCloudSyncStatus = "useFetchCloudSyncStatus", + restoreTrustchain = "useRestoreTrustchain", } export enum ErrorType { diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useAddMember.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useAddMember.ts index ec3833947489..c67fd7999c2b 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useAddMember.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useAddMember.ts @@ -1,7 +1,15 @@ -import { memberCredentialsSelector, setTrustchain } from "@ledgerhq/trustchain/store"; +import { + memberCredentialsSelector, + setTrustchain, + trustchainSelector, +} from "@ledgerhq/trustchain/store"; import { useDispatch, useSelector } from "react-redux"; import { useTrustchainSdk } from "./useTrustchainSdk"; -import { TrustchainNotAllowed } from "@ledgerhq/trustchain/errors"; +import { + TrustchainAlreadyInitialized, + TrustchainAlreadyInitializedWithOtherSeed, + TrustchainNotAllowed, +} from "@ledgerhq/trustchain/errors"; import { TrustchainResult, TrustchainResultType } from "@ledgerhq/trustchain/types"; import { useCallback, useEffect, useRef } from "react"; import { Device } from "@ledgerhq/live-common/hw/actions/types"; @@ -13,17 +21,18 @@ import { DrawerProps, SceneKind, useFollowInstructionDrawer } from "./useFollowI export function useAddMember({ device }: { device: Device | null }): DrawerProps { const [DrawerProps, setScene] = useFollowInstructionDrawer(); - + const trustchain = useSelector(trustchainSelector); const dispatch = useDispatch(); const sdk = useTrustchainSdk(); const memberCredentials = useSelector(memberCredentialsSelector); const memberCredentialsRef = useRef(memberCredentials); + const trustchainRef = useRef(trustchain); const navigation = useNavigation>(); const transitionToNextScreen = useCallback( (trustchainResult: TrustchainResult) => { dispatch(setTrustchain(trustchainResult.trustchain)); - navigation.navigate(ScreenName.WalletSyncSuccess, { + navigation.navigate(ScreenName.WalletSyncLoading, { created: trustchainResult.type === TrustchainResultType.created, }); }, @@ -45,6 +54,8 @@ export function useAddMember({ device }: { device: Device | null }): DrawerProps setScene({ kind: SceneKind.DeviceInstructions, device }), onEndRequestUserInteraction: () => setScene({ kind: SceneKind.Loader }), }, + undefined, + trustchainRef?.current?.rootId, ); if (trustchainResult) { transitionToNextScreen(trustchainResult); @@ -52,6 +63,10 @@ export function useAddMember({ device }: { device: Device | null }): DrawerProps } catch (error) { if (error instanceof TrustchainNotAllowed) { setScene({ kind: SceneKind.KeyError }); + } else if (error instanceof TrustchainAlreadyInitialized) { + setScene({ kind: SceneKind.AlreadySecuredSameSeed }); + } else if (error instanceof TrustchainAlreadyInitializedWithOtherSeed) { + setScene({ kind: SceneKind.AlreadySecuredOtherSeed }); } else if (error instanceof Error) { setScene({ kind: SceneKind.GenericError, error }); } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useFollowInstructionDrawer.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useFollowInstructionDrawer.ts index 5fb3ab23ef4f..5332ec662466 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useFollowInstructionDrawer.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useFollowInstructionDrawer.ts @@ -12,12 +12,16 @@ export enum SceneKind { WrongSeedError, KeyError, GenericError, + AlreadySecuredSameSeed, + AlreadySecuredOtherSeed, } type Scene = | { kind: SceneKind.DeviceInstructions; device: Device } | { kind: SceneKind.Loader } | { kind: SceneKind.WrongSeedError } | { kind: SceneKind.KeyError } + | { kind: SceneKind.AlreadySecuredSameSeed } + | { kind: SceneKind.AlreadySecuredOtherSeed } | { kind: SceneKind.GenericError; error: Error }; export type DrawerProps = { diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useGetMembers.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useGetMembers.ts index 2464a8b3694d..290313a421cc 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useGetMembers.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useGetMembers.ts @@ -12,7 +12,7 @@ export function useGetMembers() { const trustchain = useSelector(trustchainSelector); const memberCredentials = useSelector(memberCredentialsSelector); - const errorHandler = useLifeCycle(); + const { handleError } = useLifeCycle(); function getMembers() { if (!memberCredentials) { @@ -42,9 +42,9 @@ export function useGetMembers() { useEffect(() => { if (memberHook.isError) { - errorHandler.handleError(memberHook.error); + handleError(memberHook.error); } - }, [errorHandler, memberHook.error, memberHook.isError]); + }, [handleError, memberHook.error, memberHook.isError]); return memberHook; } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useLedgerSyncAnalytics.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useLedgerSyncAnalytics.ts index 1a1538e9b6cb..8e2801d5f272 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useLedgerSyncAnalytics.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useLedgerSyncAnalytics.ts @@ -4,21 +4,28 @@ export enum AnalyticsPage { ActivateLedgerSync = "Activate Ledger Sync", ChooseSyncMethod = "Choose sync method", BackupCreationSuccess = "Backup creation success", + SyncSuccess = "Sync success", ScanQRCode = "Scan QR code", ShowQRCode = "Show QR code", SyncWithQrCode = "Sync with QR code", PinCode = "Pin code", PinCodesDoNotMatch = "Pin codes don't match", + Loading = "Loading", SettingsGeneral = "Settings General", LedgerSyncSettings = "Ledger Sync Settings", ManageSyncInstances = "Manage synchronized instances", RemoveInstanceWrongDevice = "Remove instance wrong device connected", - ManageBackup = "Manage backup", + RemoveInstanceSuccess = "Instance removal success", + ManageBackup = "Manage key", ConfirmDeleteBackup = "Confirm delete backup", + DeleteBackupSuccess = "Delete key success", SyncWithNoKey = "Sync with no key", LedgerSyncActivated = "Ledger Sync activated", - AutoRemove = "Remove current instance", - Unbacked = "Unbacked", + AutoRemove = "Can’t remove current instance", + OtherSeed = "You can’t use this Ledger to Sync", + SameSeed = "App already secured with this Ledger", + ScanAttemptWithSameBackup = "Scan attempt with same backup", + ScanAttemptWithDifferentBackups = "Scan attempt with different backups", } export enum AnalyticsFlow { @@ -27,7 +34,7 @@ export enum AnalyticsFlow { export enum AnalyticsButton { SyncYourAccounts = "Sync your accounts", - AlreadyCreatedKey = "Already created key", + AlreadyCreatedKey = "Already synced a Ledger Live app", Close = "Close", UseYourLedger = "Use your Ledger", ScanQRCode = "Scan a QR code", @@ -36,7 +43,7 @@ export enum AnalyticsButton { TryAgain = "Try again", Synchronize = "Synchronize", ManageKey = "Manage key", - ManageSynchronizations = "Manage Synchronizations", + ManageInstances = "Manage instances", RemoveInstance = "Remove instance", ConnectAnotherLedger = "Connect another Ledger", DeleteKey = "Delete key", @@ -44,7 +51,7 @@ export enum AnalyticsButton { Cancel = "Cancel", CreateYourKey = "Create your key", LedgerSync = "Ledger Sync", - UseAnother = "Connect new ledger", + UseAnother = "Connect another ledger", Understand = "I understand", } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useLoadingStep.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useLoadingStep.ts new file mode 100644 index 000000000000..db5811dc387a --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useLoadingStep.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { useWalletSyncUserState } from "../components/WalletSyncContext"; +import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; +import { NavigatorName, ScreenName } from "~/const"; +import { useNavigation } from "@react-navigation/core"; + +export function useLoadingStep(created: boolean) { + const [waitedWatchLoop, setWaitedWatchLoop] = useState(false); + const { visualPending } = useWalletSyncUserState(); + const featureWalletSync = useFeature("llmWalletSync"); + const initialTimeout = featureWalletSync?.params?.watchConfig?.initialTimeout || 1000; + const visualPendingTimeout = 1000; + const navigation = useNavigation(); + + useEffect(() => { + const timeout = setTimeout( + () => { + setWaitedWatchLoop(true); + }, + initialTimeout + visualPendingTimeout + 500, + ); + + return () => { + clearTimeout(timeout); + }; + }, [initialTimeout]); + + useEffect(() => { + if (waitedWatchLoop && !visualPending) { + navigation.navigate(NavigatorName.WalletSync, { + screen: ScreenName.WalletSyncSuccess, + params: { + created, + }, + }); + } + }, [waitedWatchLoop, visualPending, navigation, created]); +} diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useQRCodeHost.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useQRCodeHost.ts index fa314a197dad..64475771ac4b 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useQRCodeHost.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useQRCodeHost.ts @@ -1,6 +1,11 @@ import { useCallback, useEffect, useState } from "react"; import { createQRCodeHostInstance } from "@ledgerhq/trustchain/qrcode/index"; -import { InvalidDigitsError, NoTrustchainInitialized } from "@ledgerhq/trustchain/errors"; +import { + InvalidDigitsError, + NoTrustchainInitialized, + QRCodeWSClosed, +} from "@ledgerhq/trustchain/errors"; +import { MemberCredentials } from "@ledgerhq/trustchain/types"; import { useDispatch, useSelector } from "react-redux"; import { trustchainSelector, @@ -13,10 +18,12 @@ import { useNavigation } from "@react-navigation/native"; import { NavigatorName, ScreenName } from "~/const"; import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import getWalletSyncEnvironmentParams from "@ledgerhq/live-common/walletSync/getEnvironmentParams"; -import { useQueryClient } from "@tanstack/react-query"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { QueryKey } from "./type.hooks"; import { useInstanceName } from "./useInstanceName"; +const MIN_TIME_TO_REFRESH = 30_000; + interface Props { setCurrentStep: (step: Steps) => void; currentStep: Steps; @@ -36,77 +43,68 @@ export function useQRCodeHost({ setCurrentStep, currentStep, currentOption }: Pr ); const memberName = useInstanceName(); - const [isLoading, setIsLoading] = useState(false); const [url, setUrl] = useState(null); - const [error, setError] = useState(null); const [pinCode, setPinCode] = useState(null); const navigation = useNavigation(); - const startQRCodeProcessing = useCallback(() => { - if (!memberCredentials || isLoading) return; - - setError(null); - setIsLoading(true); - createQRCodeHostInstance({ - trustchainApiBaseUrl, - onDisplayQRCode: url => { - setUrl(url); - }, - onDisplayDigits: digits => { - setPinCode(digits); - setCurrentStep(Steps.PinDisplay); - }, - addMember: async member => { - if (trustchain) { - await sdk.addMember(trustchain, memberCredentials, member); - return trustchain; - } - throw new NoTrustchainInitialized(); - }, - memberCredentials, - memberName, - alreadyHasATrustchain: !!trustchain, - }) - .then(newTrustchain => { - if (newTrustchain) { - dispatch(setTrustchain(newTrustchain)); - } - queryClient.invalidateQueries({ queryKey: [QueryKey.getMembers] }); - navigation.navigate(NavigatorName.WalletSync, { - screen: ScreenName.WalletSyncSuccess, - params: { - created: false, - }, - }); + const { mutate, isPending, error } = useMutation({ + mutationFn: (memberCredentials: MemberCredentials) => + createQRCodeHostInstance({ + trustchainApiBaseUrl, + onDisplayQRCode: url => { + setUrl(url); + }, + onDisplayDigits: digits => { + setPinCode(digits); + setCurrentStep(Steps.PinDisplay); + }, + addMember: async member => { + if (trustchain) { + await sdk.addMember(trustchain, memberCredentials, member); + return trustchain; + } + throw new NoTrustchainInitialized(); + }, + memberCredentials, + memberName, + initialTrustchainId: trustchain?.rootId, + }), - setUrl(null); - setPinCode(null); - setIsLoading(false); - }) - .catch(e => { - if (e instanceof InvalidDigitsError) { - setCurrentStep(Steps.SyncError); - return; - } else if (e instanceof NoTrustchainInitialized) { - setCurrentStep(Steps.UnbackedError); - return; - } - setError(e); - throw e; + onSuccess: newTrustchain => { + if (newTrustchain) { + dispatch(setTrustchain(newTrustchain)); + } + queryClient.invalidateQueries({ queryKey: [QueryKey.getMembers] }); + navigation.navigate(NavigatorName.WalletSync, { + screen: ScreenName.WalletSyncLoading, + params: { + created: false, + }, }); - }, [ - trustchain, - memberCredentials, - isLoading, - trustchainApiBaseUrl, - memberName, - setCurrentStep, - sdk, - queryClient, - navigation, - dispatch, - ]); + + setUrl(null); + setPinCode(null); + }, + + // Don't use retry here because it always uses a delay despite setting it to 0 + onError: e => { + if (e instanceof QRCodeWSClosed) { + const { time } = e as unknown as { time: number }; + if (time >= MIN_TIME_TO_REFRESH) startQRCodeProcessing(); + } + if (e instanceof InvalidDigitsError) { + setCurrentStep(Steps.SyncError); + } + if (e instanceof NoTrustchainInitialized) { + setCurrentStep(Steps.UnbackedError); + } + }, + }); + + const startQRCodeProcessing = useCallback(() => { + if (memberCredentials) mutate(memberCredentials); + }, [mutate, memberCredentials]); useEffect(() => { if (currentStep === Steps.QrCodeMethod && currentOption === Options.SHOW_QR) { @@ -117,7 +115,7 @@ export function useQRCodeHost({ setCurrentStep, currentStep, currentOption }: Pr return { url, error, - isLoading, + isLoading: isPending, startQRCodeProcessing, pinCode, }; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useRestoreTrustchain.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useRestoreTrustchain.ts new file mode 100644 index 000000000000..bcce8916144f --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useRestoreTrustchain.ts @@ -0,0 +1,37 @@ +import { + memberCredentialsSelector, + trustchainSelector, + setTrustchain, +} from "@ledgerhq/trustchain/store"; +import { Trustchain, MemberCredentials } from "@ledgerhq/trustchain/types"; +import { useQuery } from "@tanstack/react-query"; +import { useDispatch, useSelector } from "react-redux"; +import { QueryKey } from "./type.hooks"; +import { useTrustchainSdk } from "./useTrustchainSdk"; + +export const useRestoreTrustchain = () => { + const dispatch = useDispatch(); + const sdk = useTrustchainSdk(); + const memberCredentials = useSelector(memberCredentialsSelector); + + const trustchain = useSelector(trustchainSelector); + + async function restoreTrustchain() { + const newTrustchain = await sdk.restoreTrustchain( + trustchain as Trustchain, + memberCredentials as MemberCredentials, + ); + + dispatch(setTrustchain(newTrustchain)); + + return newTrustchain; + } + + const restore = useQuery({ + enabled: false, + queryKey: [QueryKey.restoreTrustchain], + queryFn: restoreTrustchain, + }); + + return restore; +}; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSpecificError.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSpecificError.tsx new file mode 100644 index 000000000000..5dc711373be2 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSpecificError.tsx @@ -0,0 +1,184 @@ +import React from "react"; +import { Icons } from "@ledgerhq/native-ui"; +import { useTranslation } from "react-i18next"; +import { useTheme } from "styled-components/native"; +import { AnalyticsButton, AnalyticsPage, useLedgerSyncAnalytics } from "./useLedgerSyncAnalytics"; + +import { ButtonProps } from "@ledgerhq/native-ui/components/cta/Button"; + +export enum ErrorReason { + UNSECURED = "unsecured", + AUTO_REMOVE = "auto-remove", + SAME_SEED = "same", + OTHER_SEED = "other", + ALREADY_BACKED_SCAN = "already-backed", + DIFFERENT_BACKUPS = "different-backups", + NO_BACKUP = "no-backup", +} + +export interface ErrorConfig { + icon: React.ReactNode; + title: string; + description?: string; + info?: string; + cta: string; + ctaSecondary?: string; + analyticsPage: AnalyticsPage; + buttonType: ButtonProps["type"]; + outline?: boolean; + primaryAction: () => void; + secondaryAction?: () => void; +} + +export type SpecificProps = { + primaryAction: () => void; + secondaryAction?: () => void; +}; + +export function useSpecificError({ primaryAction, secondaryAction }: SpecificProps) { + const { onClickTrack } = useLedgerSyncAnalytics(); + const { t } = useTranslation(); + const { colors } = useTheme(); + + const onTryAgain = (page: AnalyticsPage) => { + onClickTrack({ button: AnalyticsButton.UseAnother, page }); + }; + const onGoToDelete = (page: AnalyticsPage) => { + onClickTrack({ button: AnalyticsButton.DeleteKey, page }); + }; + + const onUnderstood = (page: AnalyticsPage) => { + onClickTrack({ button: AnalyticsButton.Understand, page }); + }; + + const onCancel = (page: AnalyticsPage) => { + onClickTrack({ button: AnalyticsButton.Cancel, page }); + }; + + const onCreate = (page: AnalyticsPage) => { + onClickTrack({ button: AnalyticsButton.CreateYourKey, page }); + }; + + const errorConfig: Record = { + [ErrorReason.UNSECURED]: { + icon: , + title: t("walletSync.walletSyncActivated.synchronizedInstances.unsecuredError.title"), + description: t( + "walletSync.walletSyncActivated.synchronizedInstances.unsecuredError.description", + ), + info: t("walletSync.walletSyncActivated.synchronizedInstances.unsecuredError.info"), + cta: t("walletSync.walletSyncActivated.synchronizedInstances.unsecuredError.cta"), + ctaSecondary: t( + "walletSync.walletSyncActivated.synchronizedInstances.unsecuredError.ctaDelete", + ), + analyticsPage: AnalyticsPage.RemoveInstanceWrongDevice, + buttonType: "main" as ButtonProps["type"], + primaryAction: () => { + primaryAction(); + onTryAgain(AnalyticsPage.RemoveInstanceWrongDevice); + }, + secondaryAction: () => { + secondaryAction?.(); + onGoToDelete(AnalyticsPage.RemoveInstanceWrongDevice); + }, + }, + [ErrorReason.AUTO_REMOVE]: { + icon: , + title: t("walletSync.walletSyncActivated.synchronizedInstances.autoRemoveError.title"), + description: t( + "walletSync.walletSyncActivated.synchronizedInstances.autoRemoveError.description", + ), + info: t("walletSync.walletSyncActivated.synchronizedInstances.autoRemoveError.info"), + cta: t("walletSync.walletSyncActivated.synchronizedInstances.autoRemoveError.cta"), + ctaSecondary: t( + "walletSync.walletSyncActivated.synchronizedInstances.autoRemoveError.ctaDelete", + ), + analyticsPage: AnalyticsPage.AutoRemove, + buttonType: "main" as ButtonProps["type"], + primaryAction: () => { + primaryAction(); + onUnderstood(AnalyticsPage.AutoRemove); + }, + secondaryAction: () => { + secondaryAction?.(); + onGoToDelete(AnalyticsPage.AutoRemove); + }, + }, + [ErrorReason.SAME_SEED]: { + icon: , + title: t("walletSync.synchronize.alreadySecureError.title"), + description: t("walletSync.synchronize.alreadySecureError.description"), + cta: t("walletSync.synchronize.alreadySecureError.cta"), + + analyticsPage: AnalyticsPage.SameSeed, + buttonType: "main" as ButtonProps["type"], + primaryAction: () => { + primaryAction(); + onUnderstood(AnalyticsPage.SameSeed); + }, + }, + [ErrorReason.OTHER_SEED]: { + icon: , + title: t("walletSync.synchronize.alreadySecureOtherSeedError.title"), + description: t("walletSync.synchronize.alreadySecureOtherSeedError.description"), + cta: t("walletSync.synchronize.alreadySecureOtherSeedError.cta"), + ctaSecondary: t("common.cancel"), + analyticsPage: AnalyticsPage.OtherSeed, + buttonType: "main" as ButtonProps["type"], + outline: true, + primaryAction: () => { + primaryAction(); + onGoToDelete(AnalyticsPage.OtherSeed); + }, + secondaryAction: () => { + secondaryAction?.(); + onCancel(AnalyticsPage.OtherSeed); + }, + }, + [ErrorReason.ALREADY_BACKED_SCAN]: { + icon: , + title: t("walletSync.synchronize.qrCode.alreadyBacked.title"), + cta: t("walletSync.synchronize.qrCode.alreadyBacked.cta"), + analyticsPage: AnalyticsPage.ScanAttemptWithSameBackup, + buttonType: "main" as ButtonProps["type"], + primaryAction: () => { + primaryAction(); + onUnderstood(AnalyticsPage.ScanAttemptWithSameBackup); + }, + }, + [ErrorReason.DIFFERENT_BACKUPS]: { + icon: , + title: t("walletSync.synchronize.qrCode.backedWithDifferentSeeds.title"), + description: t("walletSync.synchronize.qrCode.backedWithDifferentSeeds.description"), + cta: t("walletSync.synchronize.qrCode.backedWithDifferentSeeds.cta"), + analyticsPage: AnalyticsPage.ScanAttemptWithDifferentBackups, + buttonType: "main" as ButtonProps["type"], + primaryAction: () => { + primaryAction(); + onGoToDelete(AnalyticsPage.ScanAttemptWithDifferentBackups); + }, + }, + [ErrorReason.NO_BACKUP]: { + icon: , + title: t("walletSync.synchronize.qrCode.unbacked.title"), + description: t("walletSync.synchronize.qrCode.unbacked.description"), + cta: t("walletSync.synchronize.qrCode.unbacked.cta"), + analyticsPage: AnalyticsPage.SyncWithNoKey, + buttonType: "main" as ButtonProps["type"], + primaryAction: () => { + primaryAction(); + onCreate(AnalyticsPage.SyncWithNoKey); + }, + }, + }; + + const getErrorConfig = (error: ErrorReason) => errorConfig[error]; + + return { + getErrorConfig, + onCancel, + onGoToDelete, + onTryAgain, + onUnderstood, + }; +} diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSyncWithQrCode.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSyncWithQrCode.ts index 66a613cde89e..b116cb0e6cda 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSyncWithQrCode.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSyncWithQrCode.ts @@ -1,7 +1,12 @@ import { useCallback, useState } from "react"; import { MemberCredentials, TrustchainMember } from "@ledgerhq/trustchain/types"; import { createQRCodeCandidateInstance } from "@ledgerhq/trustchain/qrcode/index"; -import { InvalidDigitsError, NoTrustchainInitialized } from "@ledgerhq/trustchain/errors"; +import { + InvalidDigitsError, + NoTrustchainInitialized, + TrustchainAlreadyInitialized, + TrustchainAlreadyInitializedWithOtherSeed, +} from "@ledgerhq/trustchain/errors"; import { setTrustchain, trustchainSelector } from "@ledgerhq/trustchain/store"; import { useDispatch, useSelector } from "react-redux"; import { useNavigation } from "@react-navigation/native"; @@ -35,7 +40,7 @@ export const useSyncWithQrCode = () => { setInput(null); setInputCallback(null); navigation.navigate(NavigatorName.WalletSync, { - screen: ScreenName.WalletSyncSuccess, + screen: ScreenName.WalletSyncLoading, params: { created: false, }, @@ -61,7 +66,7 @@ export const useSyncWithQrCode = () => { } throw new NoTrustchainInitialized(); }, - alreadyHasATrustchain: !!trustchain, + initialTrustchainId: trustchain?.rootId, }); if (newTrustchain) { dispatch(setTrustchain(newTrustchain)); @@ -75,6 +80,16 @@ export const useSyncWithQrCode = () => { } else if (e instanceof NoTrustchainInitialized) { setCurrentStep(Steps.UnbackedError); return; + } else if (e instanceof TrustchainAlreadyInitialized) { + if (e.message === trustchain?.rootId) { + setCurrentStep(Steps.AlreadyBacked); + } else { + setCurrentStep(Steps.BackedWithDifferentSeeds); + } + return; + } else if (e instanceof TrustchainAlreadyInitializedWithOtherSeed) { + setCurrentStep(Steps.BackedWithDifferentSeeds); + return; } throw e; } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts index df4d0137aa23..080c3b56f145 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts @@ -1,22 +1,27 @@ import { resetTrustchainStore } from "@ledgerhq/trustchain/store"; import { useDispatch } from "react-redux"; -import { TrustchainEjected, TrustchainNotAllowed } from "@ledgerhq/trustchain/errors"; +import { + TrustchainEjected, + TrustchainNotAllowed, + TrustchainOutdated, +} from "@ledgerhq/trustchain/errors"; import { ErrorType } from "./type.hooks"; -import { useNavigation } from "@react-navigation/native"; -import { StackNavigatorNavigation } from "~/components/RootNavigator/types/helpers"; -import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; -import { ScreenName } from "~/const"; +import { StackActions, useNavigation } from "@react-navigation/native"; import { useTrustchainSdk } from "./useTrustchainSdk"; +import { useRestoreTrustchain } from "./useRestoreTrustchain"; +import { NavigatorName, ScreenName } from "~/const"; export const useLifeCycle = () => { const dispatch = useDispatch(); const sdk = useTrustchainSdk(); - - const navigation = useNavigation>(); + const { refetch: restoreTrustchain } = useRestoreTrustchain(); + const navigation = useNavigation(); function reset() { dispatch(resetTrustchainStore()); - navigation.navigate(ScreenName.WalletSyncActivationInit); + const routeName = NavigatorName.WalletSync; + const screen = ScreenName.WalletSyncActivationInit; + navigation.dispatch(StackActions.replace(routeName, { screen })); sdk.invalidateJwt(); } @@ -30,6 +35,8 @@ export const useLifeCycle = () => { if (error instanceof TrustchainEjected) reset(); if (error instanceof TrustchainNotAllowed) reset(); + if (error instanceof TrustchainOutdated) restoreTrustchain(); + const errorToHandle = Object.entries(includesErrorActions).find(([err, _action]) => error.message.includes(err), ); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationLoading.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationLoading.tsx new file mode 100644 index 000000000000..ea78ef6bc97c --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationLoading.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; +import { ScreenName } from "~/const"; +import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; +import { useLoadingStep } from "../../hooks/useLoadingStep"; +import { TrackScreen } from "~/analytics"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; +import GradientContainer from "~/components/GradientContainer"; +import Animation from "~/components/Animation"; +import { Flex, Text } from "@ledgerhq/native-ui"; +import lottie from "~/screens/ReceiveFunds/assets/lottie.json"; +import { useTheme } from "styled-components/native"; +import { useTranslation } from "react-i18next"; + +type Props = BaseComposite< + StackNavigatorProps +>; + +export function ActivationLoading({ route }: Props) { + const { created } = route.params; + const { colors } = useTheme(); + const { t } = useTranslation(); + const title = "walletSync.loading.title"; + const subtitle = created ? "walletSync.loading.activation" : "walletSync.loading.synch"; + useLoadingStep(created); + + return ( + <> + + + + + + + + {t(title)} + + + {t(subtitle)} + + + + + ); +} diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationSuccess.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationSuccess.tsx index 2864478d1766..b96142165867 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationSuccess.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationSuccess.tsx @@ -15,6 +15,8 @@ import { isFromLedgerSyncOnboardingSelector } from "~/reducers/settings"; import { useNavigation } from "@react-navigation/native"; import { BaseNavigatorStackParamList } from "~/components/RootNavigator/types/BaseNavigator"; import { setFromLedgerSyncOnboarding } from "~/actions/settings"; +import { AnalyticsButton, AnalyticsFlow, AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; +import { track } from "~/analytics"; type Props = BaseComposite< StackNavigatorProps @@ -26,16 +28,27 @@ export function ActivationSuccess({ navigation, route }: Props) { const { created } = route.params; const title = created ? "walletSync.success.activation" : "walletSync.success.sync"; const desc = created ? "walletSync.success.activationDesc" : "walletSync.success.syncDesc"; + const page = created ? AnalyticsPage.BackupCreationSuccess : AnalyticsPage.SyncSuccess; const dispatch = useDispatch(); const navigationOnbarding = useNavigation>>(); function syncAnother(): void { + track("button_clicked", { + button: AnalyticsButton.SyncWithAnotherLedgerLive, + page, + flow: AnalyticsFlow.LedgerSync, + }); navigation.navigate(ScreenName.WalletSyncActivationProcess); } function close(): void { + track("button_clicked", { + button: AnalyticsButton.Close, + page, + flow: AnalyticsFlow.LedgerSync, + }); if (isFromLedgerSyncOnboarding) { dispatch(setFromLedgerSyncOnboarding(false)); navigationOnbarding.navigate(NavigatorName.Base, { @@ -60,6 +73,7 @@ export function ActivationSuccess({ navigation, route }: Props) { label: t("walletSync.success.close"), onPress: close, }} + analyticsPage={page} /> ); } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/useActivationDrawerModel.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/useActivationDrawerModel.ts index a11d1842401c..635a6cdcb374 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/useActivationDrawerModel.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/useActivationDrawerModel.ts @@ -70,7 +70,6 @@ const useActivationDrawerModel = ({ isOpen, startingStep, handleClose }: Props) }; const onCreateKey = () => { - onClickTrack({ button: AnalyticsButton.CreateYourKey, page: AnalyticsPage.Unbacked }); navigation.navigate(NavigatorName.WalletSync, { screen: ScreenName.WalletSyncActivationProcess, }); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/FollowInstructions/index.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/FollowInstructions/index.tsx index 47bc40be5f01..41a043e6dee5 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/FollowInstructions/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/FollowInstructions/index.tsx @@ -6,8 +6,10 @@ import FollowInstructions from "../../components/FollowInstructions"; import GenericErrorView from "~/components/GenericErrorView"; import { Flex, InfiniteLoader } from "@ledgerhq/native-ui"; import { ConfirmManageKey } from "../../components/ManageKey/Confirm"; -import { DeletionError, ErrorReason } from "../../components/ManageInstances/DeletionError"; +import { DeletionError } from "../../components/ManageInstances/DeletionError"; import { DrawerProps, SceneKind } from "../../hooks/useFollowInstructionDrawer"; +import { SpecificError } from "../../components/Error/SpecificError"; +import { ErrorReason } from "../../hooks/useSpecificError"; type Props = DrawerProps & { isOpen: boolean; @@ -41,8 +43,20 @@ const GenericFollowInstructionsDrawer = ({ return ( + ); + + case SceneKind.AlreadySecuredSameSeed: + return ; + + case SceneKind.AlreadySecuredOtherSeed: + return ( + ); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/LedgerSyncDeepLinkHandler.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/LedgerSyncDeepLinkHandler.tsx new file mode 100644 index 000000000000..95f118a04497 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/LedgerSyncDeepLinkHandler.tsx @@ -0,0 +1,27 @@ +import { StackActions, useNavigation } from "@react-navigation/native"; +import { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { trustchainSelector } from "@ledgerhq/trustchain/store"; +import { setLedgerSyncActivateDrawer } from "~/actions/walletSync"; +import { NavigatorName, ScreenName } from "~/const"; + +export function LedgerSyncDeepLinkHandler() { + const hasTrustchain = !!useSelector(trustchainSelector)?.rootId; + const navigation = useNavigation(); + const dispatch = useDispatch(); + + useEffect(() => { + if (hasTrustchain) { + const routeName = NavigatorName.WalletSync; + const screen = ScreenName.WalletSyncActivated; + navigation.dispatch(StackActions.replace(routeName, { screen })); + } else { + const routeName = NavigatorName.Settings; + const screen = ScreenName.GeneralSettings; + navigation.dispatch(StackActions.replace(routeName, { screen })); + dispatch(setLedgerSyncActivateDrawer(true)); + } + }, [hasTrustchain, navigation, dispatch]); + + return null; +} diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Manage/index.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Manage/index.tsx index d31908e1abb0..b848184717ff 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Manage/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Manage/index.tsx @@ -36,22 +36,24 @@ const WalletSyncManage = () => { const [isSyncDrawerOpen, setIsSyncDrawerOpen] = useState(false); const goToSync = () => { + manageInstancesHook.checkInstances(); setIsSyncDrawerOpen(true); - onClickTrack({ button: AnalyticsButton.Synchronize, page: AnalyticsPage.LedgerSyncActivated }); + onClickTrack({ button: AnalyticsButton.Synchronize, page: AnalyticsPage.LedgerSyncSettings }); }; const closeSyncDrawer = () => setIsSyncDrawerOpen(false); const goToManageBackup = () => { manageKeyHook.openDrawer(); - onClickTrack({ button: AnalyticsButton.ManageKey, page: AnalyticsPage.LedgerSyncActivated }); + onClickTrack({ button: AnalyticsButton.ManageKey, page: AnalyticsPage.LedgerSyncSettings }); }; const goToManageInstances = () => { + manageInstancesHook.checkInstances(); manageInstancesHook.openDrawer(); onClickTrack({ - button: AnalyticsButton.ManageSynchronizations, - page: AnalyticsPage.LedgerSyncActivated, + button: AnalyticsButton.ManageInstances, + page: AnalyticsPage.LedgerSyncSettings, }); }; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionSuccess.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionSuccess.tsx index 65c918cc22ad..24c7b1ff217f 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionSuccess.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionSuccess.tsx @@ -5,6 +5,8 @@ import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/t import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; import { NavigatorName, ScreenName } from "~/const"; +import { AnalyticsButton, AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; +import { track } from "~/analytics"; type Props = BaseComposite< StackNavigatorProps< @@ -17,6 +19,10 @@ export function WalletSyncManageInstanceDeletionSuccess({ navigation, route }: P const { t } = useTranslation(); const member = route.params?.member.name; function close(): void { + track("button_clicked", { + button: AnalyticsButton.Close, + page: AnalyticsPage.RemoveInstanceSuccess, + }); navigation.navigate(NavigatorName.Settings, { screen: ScreenName.GeneralSettings, }); @@ -31,6 +37,7 @@ export function WalletSyncManageInstanceDeletionSuccess({ navigation, route }: P label: t("walletSync.success.close"), onPress: close, }} + analyticsPage={AnalyticsPage.RemoveInstanceSuccess} /> ); } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/ManageInstancesDrawer.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/ManageInstancesDrawer.tsx index f0a56a925c3e..952a6efa15e8 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/ManageInstancesDrawer.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/ManageInstancesDrawer.tsx @@ -1,19 +1,14 @@ import React, { useCallback } from "react"; import QueuedDrawer from "LLM/components/QueuedDrawer"; -import { TrackScreen } from "~/analytics"; import GenericErrorView from "~/components/GenericErrorView"; import { Flex, InfiniteLoader } from "@ledgerhq/native-ui"; import { ListInstances } from "../../components/ManageInstances/ListInstances"; -import { DeletionError, ErrorReason } from "../../components/ManageInstances/DeletionError"; +import { DeletionError } from "../../components/ManageInstances/DeletionError"; import { HookResult, Scene } from "./useManageInstanceDrawer"; import { useManageKeyDrawer } from "../ManageKey/useManageKeyDrawer"; -import { - AnalyticsButton, - AnalyticsPage, - useLedgerSyncAnalytics, -} from "../../hooks/useLedgerSyncAnalytics"; +import { ErrorReason } from "../../hooks/useSpecificError"; const ManageInstancesDrawer = ({ isDrawerVisible, @@ -26,13 +21,11 @@ const ManageInstancesDrawer = ({ }: HookResult) => { const { error, isError, isLoading, data } = memberHook; const manageKeyHook = useManageKeyDrawer(); - const { onClickTrack } = useLedgerSyncAnalytics(); const goToManageBackup = useCallback(() => { handleClose(); manageKeyHook.openDrawer(); - onClickTrack({ button: AnalyticsButton.ManageKey, page: AnalyticsPage.AutoRemove }); - }, [manageKeyHook, onClickTrack, handleClose]); + }, [manageKeyHook, handleClose]); const getScene = () => { if (isError) { @@ -61,20 +54,17 @@ const ManageInstancesDrawer = ({ return ( changeScene(Scene.List)} + primaryAction={() => changeScene(Scene.List)} + secondaryAction={goToManageBackup} /> ); } }; return ( - <> - - - {getScene()} - - + + {getScene()} + ); }; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/useManageInstanceDrawer.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/useManageInstanceDrawer.ts index 538b5e11c9b6..738fd1e8bc27 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/useManageInstanceDrawer.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/useManageInstanceDrawer.ts @@ -9,6 +9,8 @@ import { useNavigation } from "@react-navigation/native"; import { StackNavigatorNavigation } from "~/components/RootNavigator/types/helpers"; import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; import { ScreenName } from "~/const"; +import { track } from "~/analytics"; +import { AnalyticsButton, AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; const messageLog = "Follow Steps on device"; @@ -28,6 +30,7 @@ export type HookResult = { onClickInstance: (instance: TrustchainMember) => void; scene: Scene; memberCredentials: MemberCredentials | null; + checkInstances: () => void; }; export const useManageInstancesDrawer = (): HookResult => { @@ -42,16 +45,24 @@ export const useManageInstancesDrawer = (): HookResult => { const openDrawer = useCallback(() => { setIsDrawerInstructionsVisible(true); - logDrawer(messageLog, "open"); }, []); const closeDrawer = useCallback(() => { setIsDrawerInstructionsVisible(false); logDrawer(messageLog, "close"); - }, []); + + track("button_clicked", { + button: AnalyticsButton.Close, + page: scene === Scene.List ? AnalyticsPage.ManageSyncInstances : AnalyticsPage.AutoRemove, + }); + }, [scene]); const onClickInstance = (instance: TrustchainMember) => { + track("button_clicked", { + button: AnalyticsButton.RemoveInstance, + page: AnalyticsPage.ManageSyncInstances, + }); navigation.navigate(ScreenName.WalletSyncManageInstancesProcess, { member: instance, }); @@ -64,6 +75,8 @@ export const useManageInstancesDrawer = (): HookResult => { const changeScene = (scene: Scene) => setScene(scene); + const checkInstances = () => memberHook.refetch(); + return { isDrawerVisible, changeScene, @@ -75,5 +88,6 @@ export const useManageInstancesDrawer = (): HookResult => { onClickDelete, scene, memberCredentials, + checkInstances, }; }; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/DeletionSuccess.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/DeletionSuccess.tsx index 2e6c36c955b7..a7a8d8cfce44 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/DeletionSuccess.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/DeletionSuccess.tsx @@ -5,6 +5,7 @@ import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/t import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; import { NavigatorName, ScreenName } from "~/const"; +import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; type Props = BaseComposite< StackNavigatorProps< @@ -29,6 +30,7 @@ export function WalletSyncManageKeyDeletionSuccess({ navigation }: Props) { label: t("walletSync.success.close"), onPress: close, }} + analyticsPage={AnalyticsPage.DeleteBackupSuccess} /> ); } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/ManageKeyDrawer.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/ManageKeyDrawer.tsx index 582d5181289f..295f235086f4 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/ManageKeyDrawer.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/ManageKeyDrawer.tsx @@ -1,6 +1,5 @@ import React from "react"; import QueuedDrawer from "LLM/components/QueuedDrawer"; -import { TrackScreen } from "~/analytics"; import GenericErrorView from "~/components/GenericErrorView"; import { Flex, InfiniteLoader } from "@ledgerhq/native-ui"; @@ -16,6 +15,7 @@ const ManageKeyDrawer = ({ scene, onClickDelete, onClickConfirm, + handleCancel, }: HookResult) => { const getScene = () => { if (deleteMutation.error) { @@ -40,17 +40,14 @@ const ManageKeyDrawer = ({ return ; } if (scene === Scene.Confirm) { - return ; + return ; } }; return ( - <> - - - {getScene()} - - + + {getScene()} + ); }; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/useManageKeyDrawer.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/useManageKeyDrawer.ts index 98c98c6cf238..a82e22a20424 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/useManageKeyDrawer.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageKey/useManageKeyDrawer.ts @@ -9,6 +9,8 @@ import { UseMutationResult } from "@tanstack/react-query"; import { useDispatch, useSelector } from "react-redux"; import { setWallectSyncManageKeyDrawer } from "~/actions/walletSync"; import { manageKeyDrawerSelector } from "~/reducers/walletSync"; +import { track } from "~/analytics"; +import { AnalyticsButton, AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; const messageLog = "Follow Steps on device"; @@ -26,6 +28,7 @@ export type HookResult = { scene: Scene; onClickConfirm: () => Promise; deleteMutation: UseMutationResult; + handleCancel: () => void; }; export const useManageKeyDrawer = () => { @@ -37,7 +40,13 @@ export const useManageKeyDrawer = () => { const [scene, setScene] = useState(Scene.Manage); - const onClickDelete = () => setScene(Scene.Confirm); + const onClickDelete = () => { + track("button_clicked", { + button: AnalyticsButton.DeleteKey, + page: AnalyticsPage.ManageBackup, + }); + setScene(Scene.Confirm); + }; const openDrawer = useCallback(() => { dispatch(setWallectSyncManageKeyDrawer(true)); @@ -46,6 +55,7 @@ export const useManageKeyDrawer = () => { }, [dispatch]); const closeDrawer = useCallback(() => { + setScene(Scene.Manage); dispatch(setWallectSyncManageKeyDrawer(false)); logDrawer(messageLog, "close"); }, [dispatch]); @@ -54,10 +64,27 @@ export const useManageKeyDrawer = () => { const handleClose = () => { closeDrawer(); - setScene(Scene.Manage); + + track("button_clicked", { + button: AnalyticsButton.Close, + page: AnalyticsPage.ManageBackup, + }); + }; + + const handleCancel = () => { + closeDrawer(); + track("button_clicked", { + button: AnalyticsButton.Cancel, + page: AnalyticsPage.ConfirmDeleteBackup, + }); }; const onClickConfirm = async () => { + track("button_clicked", { + button: AnalyticsButton.Delete, + page: AnalyticsPage.ConfirmDeleteBackup, + }); + await deleteMutation.mutateAsync(); closeDrawer(); navigation.navigate(ScreenName.WalletSyncManageKeyDeleteSuccess); @@ -72,5 +99,6 @@ export const useManageKeyDrawer = () => { scene, onClickConfirm, deleteMutation, + handleCancel, }; }; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/QrCodeMethod.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/QrCodeMethod.tsx index 03b083db11b3..0681e0498738 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/QrCodeMethod.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/QrCodeMethod.tsx @@ -31,9 +31,10 @@ const QrCodeMethod = ({ setSelectedOption(option); const button = option === Options.SCAN ? AnalyticsButton.ScanQRCode : AnalyticsButton.ShowQRCode; + const page = option === Options.SCAN ? AnalyticsPage.ScanQRCode : AnalyticsPage.ShowQRCode; onClickTrack({ button, - page: AnalyticsPage.ScanQRCode, + page, }); }; @@ -42,14 +43,14 @@ const QrCodeMethod = ({ case Options.SCAN: return ( <> - + ); case Options.SHOW_QR: return ( <> - + ); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/SyncError.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/SyncError.tsx index fb5c44da0ebd..1d1946c0bf00 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/SyncError.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/SyncError.tsx @@ -1,6 +1,8 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { ErrorComponent } from "../../components/Error"; +import { ErrorComponent } from "../../components/Error/Simple"; +import { AnalyticsButton, AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; +import { track } from "~/analytics"; interface Props { tryAgain: () => void; @@ -8,15 +10,25 @@ interface Props { export default function SyncError({ tryAgain }: Props) { const { t } = useTranslation(); + + const onTryAgain = () => { + tryAgain(); + track("button_clicked", { + button: AnalyticsButton.TryAgain, + page: AnalyticsPage.PinCodesDoNotMatch, + }); + }; + return ( ); } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/UnbackedError.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/UnbackedError.tsx deleted file mode 100644 index 9554492262de..000000000000 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/UnbackedError.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { ErrorComponent } from "../../components/Error"; - -interface Props { - create: () => void; -} - -export default function UnbackedError({ create }: Props) { - const { t } = useTranslation(); - return ( - - ); -} diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/types/Activation.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/types/Activation.ts index 563db584c53b..1e09b07d4929 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/types/Activation.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/types/Activation.ts @@ -15,4 +15,6 @@ export enum Steps { PinInput = "PinInput", SyncError = "SyncError", UnbackedError = "UnbackedError", + AlreadyBacked = "AlreadyBacked", + BackedWithDifferentSeeds = "BackedWithDifferentSeeds", } diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestRow/CurrencyIconList/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestRow/CurrencyIconList/index.tsx index 7d0e1ae07a4d..f65aa6c5741a 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestRow/CurrencyIconList/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestRow/CurrencyIconList/index.tsx @@ -54,7 +54,7 @@ export default function CurrencyIconList({ currencies }: Props) { justifyContent={"center"} marginLeft={-3} backgroundColor={colors.card} - borderColor={colors.border} + borderColor={colors.background} borderStyle={"solid"} borderWidth={1} borderRadius={50} @@ -75,7 +75,7 @@ export default function CurrencyIconList({ currencies }: Props) { } return list; - }, [colors.background, colors.border, colors.card, currencies]); + }, [colors.background, colors.card, currencies]); return {icons}; } diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestRow/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestRow/index.tsx index c250ca49df97..2117e96384c6 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestRow/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestRow/index.tsx @@ -1,23 +1,43 @@ -import React from "react"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; import { Flex, Text } from "@ledgerhq/native-ui"; +import { useTheme } from "@react-navigation/native"; import { AppManifest } from "@ledgerhq/live-common/wallet-api/types"; import AppIcon from "LLM/features/Web3Hub/components/AppIcon"; +import Label from "LLM/features/Web3Hub/components/ManifestsList/ManifestItem/Label"; import CurrencyIconList from "./CurrencyIconList"; export default function ManifestRow({ manifest }: { manifest: AppManifest }) { + const { colors } = useTheme(); + const { t } = useTranslation(); const isDisabled = manifest.branch === "soon"; + const clearSigningEnabled = useMemo(() => { + return manifest?.categories.includes("clear signing"); + }, [manifest?.categories]); + return ( <> - + - + {manifest.name} + + {clearSigningEnabled && ( + - {/* TODO add badges on certain categories */} ); diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/Label/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/Label/index.tsx index 785961381358..b54d4a09edf4 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/Label/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/Label/index.tsx @@ -1,38 +1,34 @@ -import React from "react"; +import React, { ComponentProps } from "react"; import { Text } from "@ledgerhq/native-ui"; -type ItemStyle = { - badgeColor: string; - borderColor: string; - backgroundColor: string; -}; - type Props = { text: string; - style: ItemStyle; + style: Pick< + ComponentProps, + "color" | "borderColor" | "backgroundColor" | "borderWidth" + >; }; const Label: React.FC = ({ text, style }) => { - const { badgeColor, borderColor, backgroundColor } = style; + const { color, borderColor, backgroundColor, borderWidth = 1 } = style; return ( {text} diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx index 20d2bc24b505..bc4f74b8c4ed 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx @@ -70,8 +70,12 @@ export default function ManifestItem({ ); const url = useMemo(() => { + if (manifest.params && "dappUrl" in manifest.params) { + return new URL(manifest.params.dappUrl).origin; + } + return new URL(manifest.url).origin; - }, [manifest.url]); + }, [manifest.params, manifest.url]); const icon = useMemo(() => { // RN tries to load file locally if there is a space in front of the file url @@ -86,30 +90,28 @@ export default function ManifestItem({ - + - + {manifest.name} - + {manifest.branch !== "stable" && (