From e2f993492d5c2581c01b06847533c21f9e4c0d0f Mon Sep 17 00:00:00 2001 From: Martin CAYUELAS <112866305+mcayuelas-ledger@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:01:37 +0200 Subject: [PATCH] [FEAT] : Manage instance with DeviceAction (#7472) --- .../WalletSync/hooks/useRemoveMember.ts | 2 +- .../03-DeleteInstanceWithTrustchain.tsx | 4 +- .../__tests__/test-renderer.tsx | 3 + apps/ledger-live-mobile/src/actions/types.ts | 8 ++ .../src/actions/walletSync.ts | 7 ++ .../types/WalletSyncNavigator.ts | 11 ++- .../src/const/navigation.ts | 6 +- .../src/locales/en/common.json | 3 +- .../WalletSync/WalletSyncNavigator.tsx | 23 ++++- .../WalletSync/__integrations__/shared.tsx | 2 +- .../ManageInstances/ListInstances.tsx | 9 +- .../features/WalletSync/hooks/type.hooks.ts | 4 + .../WalletSync/hooks/useGetMembers.ts | 18 +++- .../WalletSync/hooks/useRemoveMember.ts | 99 +++++++++++++++++++ .../WalletSync/hooks/walletSync.hooks.ts | 40 ++++++++ .../ActivationInstructionDrawer.tsx | 27 +++++ .../screens/Activation/ActivationProcess.tsx | 10 +- .../screens/DeviceSelection/index.tsx | 5 +- .../screens/FollowInstructions/index.tsx | 29 ++++-- .../useFollowInstructions.ts | 4 +- .../WalletSync/screens/Manage/index.tsx | 12 +-- .../DeletionInstructionDrawer.tsx | 30 ++++++ .../ManageInstances/DeletionSuccess.tsx | 36 +++++++ .../ManageInstances/ManageInstancesDrawer.tsx | 12 +-- .../ManageInstancesProcess.tsx | 38 +++++++ .../useManageInstanceDrawer.ts | 24 +++-- .../screens/ManageKey/useManageKeyDrawer.ts | 15 ++- apps/ledger-live-mobile/src/reducers/index.ts | 2 + apps/ledger-live-mobile/src/reducers/types.ts | 7 ++ .../src/reducers/walletSync.ts | 21 ++++ .../Settings/General/WalletSyncRow.tsx | 2 +- 31 files changed, 445 insertions(+), 68 deletions(-) create mode 100644 apps/ledger-live-mobile/src/actions/walletSync.ts create mode 100644 apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useRemoveMember.ts create mode 100644 apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts create mode 100644 apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationInstructionDrawer.tsx create mode 100644 apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionInstructionDrawer.tsx create mode 100644 apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionSuccess.tsx create mode 100644 apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/ManageInstancesProcess.tsx create mode 100644 apps/ledger-live-mobile/src/reducers/walletSync.ts diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useRemoveMember.ts b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useRemoveMember.ts index 38d01c03e722..add427798db3 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useRemoveMember.ts +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/hooks/useRemoveMember.ts @@ -16,7 +16,7 @@ type Props = { member: TrustchainMember | null; }; -export function useRemoveMembers({ device, member }: Props) { +export function useRemoveMember({ device, member }: Props) { const dispatch = useDispatch(); const sdk = useTrustchainSdk(); const trustchain = useSelector(trustchainSelector); diff --git a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/03-DeleteInstanceWithTrustchain.tsx b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/03-DeleteInstanceWithTrustchain.tsx index 9f1cad45f7de..4c1024287996 100644 --- a/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/03-DeleteInstanceWithTrustchain.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/WalletSync/screens/ManageInstances/03-DeleteInstanceWithTrustchain.tsx @@ -2,7 +2,7 @@ import React from "react"; import { TrustchainMember } from "@ledgerhq/trustchain/types"; import FollowStepsOnDevice from "../DeviceActions/FollowStepsOnDevice"; import ErrorDisplay from "~/renderer/components/ErrorDisplay"; -import { useRemoveMembers } from "../../hooks/useRemoveMember"; +import { useRemoveMember } from "LLD/features/WalletSync/hooks/useRemoveMember"; import { InfiniteLoader } from "@ledgerhq/react-ui"; type Props = { @@ -11,7 +11,7 @@ type Props = { }; export default function DeleteInstanceWithTrustchain({ instance, device }: Props) { - const { error, userDeviceInteraction, onRetry } = useRemoveMembers({ device, member: instance }); + const { error, userDeviceInteraction, onRetry } = useRemoveMember({ device, member: instance }); if (error) { return ; diff --git a/apps/ledger-live-mobile/__tests__/test-renderer.tsx b/apps/ledger-live-mobile/__tests__/test-renderer.tsx index fa6ea0f2d9c8..58755d96238c 100644 --- a/apps/ledger-live-mobile/__tests__/test-renderer.tsx +++ b/apps/ledger-live-mobile/__tests__/test-renderer.tsx @@ -26,6 +26,8 @@ import { INITIAL_STATE as WALLET_CONNECT_INITIAL_STATE } from "~/reducers/wallet import { INITIAL_STATE as PROTECT_INITIAL_STATE } from "~/reducers/protect"; import { INITIAL_STATE as NFT_INITIAL_STATE } from "~/reducers/nft"; import { INITIAL_STATE as MARKET_INITIAL_STATE } from "~/reducers/market"; +import { INITIAL_STATE as WALLETSYNC_INITIAL_STATE } from "~/reducers/walletSync"; + import { initialState as WALLET_INITIAL_STATE } from "@ledgerhq/live-wallet/store"; import QueuedDrawersContextProvider from "~/newArch/components/QueuedDrawer/QueuedDrawersContextProvider"; import { INITIAL_STATE as TRUSTCHAIN_INITIAL_STATE } from "@ledgerhq/trustchain/store"; @@ -47,6 +49,7 @@ const initialState = { market: MARKET_INITIAL_STATE, wallet: WALLET_INITIAL_STATE, trustchain: TRUSTCHAIN_INITIAL_STATE, + walletSync: WALLETSYNC_INITIAL_STATE, }; type ExtraOptions = RenderOptions & { diff --git a/apps/ledger-live-mobile/src/actions/types.ts b/apps/ledger-live-mobile/src/actions/types.ts index ee842050eb83..1cfefb9e6972 100644 --- a/apps/ledger-live-mobile/src/actions/types.ts +++ b/apps/ledger-live-mobile/src/actions/types.ts @@ -523,6 +523,14 @@ export type MarketPayload = | MarketSetCurrentPagePayload | MarketImportPayload; +// === WALLETSYNC ACTIONS === +export enum WalletSyncActionTypes { + WALLET_SYNC_SET_MANAGE_KEY_DRAWER = "WALLET_SYNC_SET_MANAGE_KEY_DRAWER", +} + +export type WalletSyncSetManageKeyDrawerPayload = boolean; +export type WalletSyncPayload = WalletSyncSetManageKeyDrawerPayload; + // === PAYLOADS === export type ActionsPayload = diff --git a/apps/ledger-live-mobile/src/actions/walletSync.ts b/apps/ledger-live-mobile/src/actions/walletSync.ts new file mode 100644 index 000000000000..8cde4b6c877e --- /dev/null +++ b/apps/ledger-live-mobile/src/actions/walletSync.ts @@ -0,0 +1,7 @@ +import { createAction } from "redux-actions"; +import { WalletSyncActionTypes } from "./types"; +import type { WalletSyncSetManageKeyDrawerPayload } from "./types"; + +export const setWallectSyncManageKeyDrawer = createAction( + WalletSyncActionTypes.WALLET_SYNC_SET_MANAGE_KEY_DRAWER, +); 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 0a273a1bdc97..7e823a4d5672 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/types/WalletSyncNavigator.ts +++ b/apps/ledger-live-mobile/src/components/RootNavigator/types/WalletSyncNavigator.ts @@ -1,7 +1,8 @@ +import { TrustchainMember } from "@ledgerhq/trustchain/types"; import { ScreenName } from "~/const"; export type WalletSyncNavigatorStackParamList = { - [ScreenName.WalletSyncActivationSettings]: undefined; + [ScreenName.WalletSyncActivationInit]: undefined; [ScreenName.WalletSyncSuccess]: { created: boolean; @@ -12,4 +13,12 @@ export type WalletSyncNavigatorStackParamList = { [ScreenName.WalletSyncManageKeyDeleteSuccess]: undefined; [ScreenName.WalletSyncUnSynchSuccess]: undefined; + + [ScreenName.WalletSyncManageInstancesProcess]: { + member: TrustchainMember; + }; + + [ScreenName.WalletSyncManageInstancesSuccess]: { + member: TrustchainMember; + }; }; diff --git a/apps/ledger-live-mobile/src/const/navigation.ts b/apps/ledger-live-mobile/src/const/navigation.ts index 3b1c3559e727..29aa26204fee 100644 --- a/apps/ledger-live-mobile/src/const/navigation.ts +++ b/apps/ledger-live-mobile/src/const/navigation.ts @@ -511,12 +511,14 @@ export enum ScreenName { AnalyticsOptInPromptMain = "AnalyticsOptInPromptMain", AnalyticsOptInPromptDetails = "AnalyticsOptInPromptDetails", - WalletSyncActivationSettings = "WalletSyncActivationSettings", + WalletSyncActivationInit = "WalletSyncActivationInit", WalletSyncActivationProcess = "WalletSyncActivationProcess", WalletSyncSuccess = "WalletSyncSuccess", WalletSyncActivated = "WalletSyncActivated", WalletSyncManageKeyDeleteSuccess = "WalletSyncManageKeyDeleteSuccess", WalletSyncUnSynchSuccess = "WalletSyncUnSynchSuccess", + WalletSyncManageInstancesProcess = "WalletSyncManageInstancesProcess", + WalletSyncManageInstancesSuccess = "WalletSyncManageInstancesSuccess", MockedAddAssetButton = "MockedAddAssetButton", GenericLandingPage = "GenericLandingPage", @@ -635,7 +637,7 @@ export enum NavigatorName { SyncOnboarding = "SyncOnboarding", AnalyticsOptInPrompt = "AnalyticsOptInPrompt", - WalletSyncActivationSettings = "WalletSyncActivationSettings", + WalletSyncActivationInit = "WalletSyncActivationInit", LandingPages = "LandingPages", // Web3Hub diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index 6fc8706b3f2d..ec5eefb43c03 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -6773,7 +6773,8 @@ "info": "Deleting your encryption key will result in your devices becoming unsynchronized, and all synchronized data will be deleted. However, this will not affect your funds. You can resync your accounts at any time.", "cta": "I understand", "ctaDelete": "Delete my encryption key" - } + }, + "success": "Your {{member}} is no longer synchronized" }, "errors": { "fetching": "Something went wrong while fetching your synchronized instances.", 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 9bab39a67766..cc979921e68d 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/WalletSyncNavigator.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/WalletSyncNavigator.tsx @@ -11,6 +11,8 @@ 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"; const Stack = createStackNavigator(); @@ -23,7 +25,7 @@ export default function WalletSyncNavigator() { return ( null, }} /> + + null, + }} + /> + + null, + headerLeft: () => null, + }} + /> ); } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/shared.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/shared.tsx index 66783035a666..a601c6e486c1 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/shared.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/shared.tsx @@ -31,7 +31,7 @@ export function WalletSyncSettingsNavigator() { export function WalletSyncSharedNavigator() { return ( - + void; + onClickInstance: (member: TrustchainMember) => void; members?: TrustchainMember[]; currentInstance?: string; + changeScene: (scene: Scene) => void; }; -export function ListInstances({ onClickDelete, members, currentInstance }: Props) { +export function ListInstances({ onClickInstance, changeScene, members, currentInstance }: Props) { const { t } = useTranslation(); - const handleAutoRemove = () => onClickDelete(Scene.AutoRemove); + const handleAutoRemove = () => changeScene(Scene.AutoRemove); const handleGoDeleteInstance = (instance: TrustchainMember) => { // eslint-disable-next-line no-console console.log("delete instance IMPLEMENTED IN NEXT PR", instance); - onClickDelete(Scene.DeviceAction); + onClickInstance(instance); }; const renderItem = ({ item }: ListRenderItemInfo) => { 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 b9602df01ec4..90f47ff861d1 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 @@ -2,3 +2,7 @@ export enum QueryKey { getMembers = "useGetMembers", destroyTrustchain = "useDestroyTrustchain", } + +export enum ErrorType { + NO_TRUSTCHAIN = "No such trustchain", +} 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 7361caa03649..ac429cbfe9b9 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 @@ -5,6 +5,8 @@ import { useQuery } from "@tanstack/react-query"; import { QueryKey } from "./type.hooks"; import { useTranslation } from "react-i18next"; import { createCustomErrorClass } from "@ledgerhq/errors"; +import { useLifeCycle } from "./walletSync.hooks"; +import { useEffect } from "react"; export const TrustchainNotFound = createCustomErrorClass("TrustchainNotFound"); export const MemberCredentialsNotFound = createCustomErrorClass("MemberCredentialsNotFound"); @@ -15,11 +17,11 @@ export function useGetMembers() { const memberCredentials = useSelector(memberCredentialsSelector); const { t } = useTranslation(); + const errorHandler = useLifeCycle(); + function getMembers() { if (!memberCredentials) { - throw new MemberCredentialsNotFound( - t("walletSync.walletSyncActivated.errors.memberCredentials"), - ); + return; } if (!trustchain) { @@ -33,7 +35,7 @@ export function useGetMembers() { } } - return useQuery({ + const memberHook = useQuery({ queryKey: [QueryKey.getMembers, trustchain], queryFn: () => getMembers(), refetchOnMount: true, @@ -41,4 +43,12 @@ export function useGetMembers() { refetchOnWindowFocus: true, retry: false, }); + + useEffect(() => { + if (memberHook.isError) { + errorHandler.handleError(memberHook.error); + } + }, [errorHandler, memberHook.error, memberHook.isError]); + + return memberHook; } diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useRemoveMember.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useRemoveMember.ts new file mode 100644 index 000000000000..a3fbcd12f265 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useRemoveMember.ts @@ -0,0 +1,99 @@ +import { + memberCredentialsSelector, + setTrustchain, + trustchainSelector, +} from "@ledgerhq/trustchain/store"; +import { useDispatch, useSelector } from "react-redux"; +import { useTrustchainSdk, runWithDevice } from "./useTrustchainSdk"; +import { TrustchainMember, Trustchain } from "@ledgerhq/trustchain/types"; +import { useCallback, useEffect, useState } from "react"; +import { Device } from "@ledgerhq/live-common/hw/actions/types"; +import { useNavigation } from "@react-navigation/native"; +import { ScreenName } from "~/const"; +import { StackNavigatorNavigation } from "~/components/RootNavigator/types/helpers"; +import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; + +type Props = { + device: Device | null; + member: TrustchainMember | null; +}; + +export function useRemoveMember({ device, member }: Props) { + const dispatch = useDispatch(); + const sdk = useTrustchainSdk(); + const trustchain = useSelector(trustchainSelector); + const memberCredentials = useSelector(memberCredentialsSelector); + const [error, setError] = useState(null); + + const navigation = useNavigation>(); + const [userDeviceInteraction, setUserDeviceInteraction] = useState(false); + + // eslint-disable-next-line no-console + const onRetry = useCallback(() => console.log("onRetry"), []); + // () => dispatch(setFlow({ flow: Flow.ManageInstances, step: Step.DeviceActionInstance })), + + const goToDelete = useCallback( + () => navigation.navigate(ScreenName.WalletSyncActivated), + [navigation], + ); + + // eslint-disable-next-line no-console + const onResetFlow = useCallback(() => console.log("onResetFlow"), []); + // () => dispatch(setFlow({ flow: Flow.ManageInstances, step: Step.SynchronizedInstances })), + + const transitionToNextScreen = useCallback( + (trustchainResult: Trustchain) => { + if (!member) return; + dispatch(setTrustchain(trustchainResult)); + navigation.navigate(ScreenName.WalletSyncManageInstancesSuccess, { + member, + }); + }, + [dispatch, member, navigation], + ); + + const removeMember = useCallback( + async (member: TrustchainMember) => { + if (!device) return; + if (!trustchain || !memberCredentials) { + throw new Error("trustchain or memberCredentials is not set"); + } + try { + await runWithDevice(device.deviceId, async transport => { + const newTrustchain = await sdk.removeMember( + transport, + trustchain, + memberCredentials, + member, + { + onStartRequestUserInteraction: () => setUserDeviceInteraction(true), + onEndRequestUserInteraction: () => setUserDeviceInteraction(false), + }, + ); + + transitionToNextScreen(newTrustchain); + }); + } catch (error) { + if (error instanceof Error) setError(error); + } + }, + [device, memberCredentials, sdk, transitionToNextScreen, trustchain], + ); + + useEffect(() => { + if (device && device.deviceId) { + if (!member) { + onResetFlow(); + } else { + removeMember(member); + } + } + }, [device, member, onResetFlow, onRetry, removeMember]); + + return { + error, + onRetry, + userDeviceInteraction, + goToDelete, + }; +} 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 new file mode 100644 index 000000000000..9560ea7f9b7f --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts @@ -0,0 +1,40 @@ +import { resetTrustchainStore } from "@ledgerhq/trustchain/store"; +import { useDispatch } from "react-redux"; +import { TrustchainEjected, TrustchainNotAllowed } 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"; + +export const useLifeCycle = () => { + const dispatch = useDispatch(); + + const navigation = useNavigation>(); + + function reset() { + dispatch(resetTrustchainStore()); + navigation.navigate(ScreenName.WalletSyncActivationInit); + } + + const includesErrorActions: { [key: string]: () => void } = { + [ErrorType.NO_TRUSTCHAIN]: () => reset(), + }; + + function handleError(error: Error) { + console.error("GetMember :" + error); + + if (error instanceof TrustchainEjected) reset(); + if (error instanceof TrustchainNotAllowed) reset(); + + const errorToHandle = Object.entries(includesErrorActions).find(([err, _action]) => + error.message.includes(err), + ); + + if (errorToHandle) errorToHandle[1](); + } + + return { + handleError, + }; +}; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationInstructionDrawer.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationInstructionDrawer.tsx new file mode 100644 index 000000000000..3562eb13a2da --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationInstructionDrawer.tsx @@ -0,0 +1,27 @@ +import React from "react"; + +import { Device } from "@ledgerhq/live-common/hw/actions/types"; +import { useAddMember } from "../../hooks/useAddMember"; +import GenericFollowInstructionsDrawer from "../FollowInstructions"; + +type Props = { + isOpen: boolean; + handleClose: () => void; + device: Device | null; +}; + +const FollowInstructionsDrawer = ({ isOpen, handleClose, device }: Props) => { + const { error, userDeviceInteraction } = useAddMember({ device }); + + return ( + + ); +}; + +export default FollowInstructionsDrawer; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationProcess.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationProcess.tsx index 980bac7eaf25..5e3c77f57b6b 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationProcess.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationProcess.tsx @@ -1,23 +1,17 @@ import React from "react"; -import FollowInstructionsDrawer from "../FollowInstructions"; import { useFollowInstructions } from "../FollowInstructions/useFollowInstructions"; import WalletSyncActivationDeviceSelection from "../DeviceSelection"; import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; import { ScreenName } from "~/const"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; +import FollowInstructionsDrawer from "./ActivationInstructionDrawer"; type Props = BaseComposite< StackNavigatorProps >; export function ActivationProcess({ route, navigation }: Props) { - const { - isDrawerInstructionsVisible, - closeDrawer, - openDrawer, - - device, - } = useFollowInstructions(); + const { isDrawerInstructionsVisible, closeDrawer, openDrawer, device } = useFollowInstructions(); return ( <> diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/DeviceSelection/index.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/DeviceSelection/index.tsx index de7f8ce8b6ab..cb7a7a8b674b 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/DeviceSelection/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/DeviceSelection/index.tsx @@ -19,7 +19,10 @@ import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/ty import { TRUSTCHAIN_APP_NAME } from "@ledgerhq/hw-trustchain"; type NavigationProps = BaseComposite< - StackNavigatorProps + StackNavigatorProps< + WalletSyncNavigatorStackParamList, + ScreenName.WalletSyncActivationProcess | ScreenName.WalletSyncManageInstancesProcess + > >; type Props = NavigationProps; 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 5df2f179c209..0f109634fa9b 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,23 +6,40 @@ import FollowInstructions from "../../components/FollowInstructions"; import { Device } from "@ledgerhq/live-common/hw/actions/types"; import GenericErrorView from "~/components/GenericErrorView"; import { Flex, InfiniteLoader } from "@ledgerhq/native-ui"; -import { useAddMember } from "../../hooks/useAddMember"; +import { TrustchainNotAllowed } from "@ledgerhq/trustchain/errors"; +import { DeletionError, ErrorReason } from "../../components/ManageInstances/DeletionError"; type Props = { isOpen: boolean; handleClose: () => void; device: Device | null; + userDeviceInteraction: boolean; + error: Error | null; + goToDelete?: () => void; }; -const FollowInstructionsDrawer = ({ isOpen, handleClose, device }: Props) => { - const { error, userDeviceInteraction } = useAddMember({ device }); - +const GenericFollowInstructionsDrawer = ({ + isOpen, + handleClose, + device, + error, + userDeviceInteraction, + goToDelete, +}: Props) => { return ( <> {error ? ( - + error instanceof TrustchainNotAllowed ? ( + + ) : ( + + ) ) : userDeviceInteraction && device ? ( ) : ( @@ -35,4 +52,4 @@ const FollowInstructionsDrawer = ({ isOpen, handleClose, device }: Props) => { ); }; -export default FollowInstructionsDrawer; +export default GenericFollowInstructionsDrawer; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/FollowInstructions/useFollowInstructions.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/FollowInstructions/useFollowInstructions.ts index 506cee39505d..a88a86930057 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/FollowInstructions/useFollowInstructions.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/FollowInstructions/useFollowInstructions.ts @@ -1,4 +1,4 @@ -import { Device } from "@ledgerhq/types-devices"; +import { Device } from "@ledgerhq/live-common/hw/actions/types"; import { useState, useCallback } from "react"; import { logDrawer } from "~/newArch/components/QueuedDrawer/utils/logDrawer"; @@ -6,7 +6,7 @@ const messageLog = "Follow Steps on device"; export const useFollowInstructions = () => { const [isDrawerInstructionsVisible, setIsDrawerInstructionsVisible] = useState(false); - const [device, setDevice] = useState(null); + const [device, setDevice] = useState(null); const openDrawer = useCallback((device: Device) => { setIsDrawerInstructionsVisible(true); 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 df65ac7ddfa4..5ab335723745 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 @@ -1,5 +1,5 @@ import { Box, Flex, Text, Icons, InfiniteLoader, Alert } from "@ledgerhq/native-ui"; -import React from "react"; +import React, { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Option, OptionProps } from "./Option"; import styled from "styled-components"; @@ -26,17 +26,17 @@ const WalletSyncManage = () => { const { onClickTrack } = useWalletSyncAnalytics(); + const goToManageBackup = useCallback(() => { + manageKeyHook.openDrawer(); + onClickTrack({ button: AnalyticsButton.ManageKey, page: AnalyticsPage.WalletSyncActivated }); + }, [manageKeyHook, onClickTrack]); + const goToSync = () => { //dispatch(setFlow({ flow: Flow.Synchronize, step: Step.SynchronizeMode })); onClickTrack({ button: AnalyticsButton.Synchronize, page: AnalyticsPage.WalletSyncActivated }); }; - const goToManageBackup = () => { - manageKeyHook.openDrawer(); - onClickTrack({ button: AnalyticsButton.ManageKey, page: AnalyticsPage.WalletSyncActivated }); - }; - const goToManageInstances = () => { manageInstancesHook.openDrawer(); onClickTrack({ diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionInstructionDrawer.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionInstructionDrawer.tsx new file mode 100644 index 000000000000..e64ffe7f3d5b --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionInstructionDrawer.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +import { Device } from "@ledgerhq/live-common/hw/actions/types"; +import GenericFollowInstructionsDrawer from "../FollowInstructions"; +import { TrustchainMember } from "@ledgerhq/trustchain/types"; +import { useRemoveMember } from "../../hooks/useRemoveMember"; + +type Props = { + isOpen: boolean; + handleClose: () => void; + device: Device | null; + member: TrustchainMember; +}; + +const DeletionFollowInstructionsDrawer = ({ isOpen, handleClose, device, member }: Props) => { + const { error, userDeviceInteraction, goToDelete } = useRemoveMember({ device, member }); + + return ( + + ); +}; + +export default DeletionFollowInstructionsDrawer; 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 new file mode 100644 index 000000000000..65c918cc22ad --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/DeletionSuccess.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { Success } from "../../components/Success"; +import { useTranslation } from "react-i18next"; +import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; +import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; + +import { NavigatorName, ScreenName } from "~/const"; + +type Props = BaseComposite< + StackNavigatorProps< + WalletSyncNavigatorStackParamList, + ScreenName.WalletSyncManageInstancesSuccess + > +>; + +export function WalletSyncManageInstanceDeletionSuccess({ navigation, route }: Props) { + const { t } = useTranslation(); + const member = route.params?.member.name; + function close(): void { + navigation.navigate(NavigatorName.Settings, { + screen: ScreenName.GeneralSettings, + }); + } + + return ( + + ); +} 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 244be521d6b5..cfd8b8004be1 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 @@ -15,8 +15,8 @@ const ManageInstancesDrawer = ({ memberCredentials, memberHook, scene, - onClickDelete, changeScene, + onClickInstance, }: HookResult) => { const { error, isError, isLoading, data } = memberHook; @@ -35,7 +35,8 @@ const ManageInstancesDrawer = ({ if (scene === Scene.List) { return ( @@ -52,13 +53,6 @@ const ManageInstancesDrawer = ({ /> ); } - - if (scene === Scene.DeviceAction) { - return null; - } - if (scene === Scene.Instructions) { - return null; - } }; return ( diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/ManageInstancesProcess.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/ManageInstancesProcess.tsx new file mode 100644 index 000000000000..a79e866fbc75 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/ManageInstances/ManageInstancesProcess.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { useFollowInstructions } from "../FollowInstructions/useFollowInstructions"; +import WalletSyncActivationDeviceSelection from "../DeviceSelection"; +import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; +import { ScreenName } from "~/const"; +import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; +import DeletionFollowInstructionsDrawer from "./DeletionInstructionDrawer"; + +type Props = BaseComposite< + StackNavigatorProps< + WalletSyncNavigatorStackParamList, + ScreenName.WalletSyncManageInstancesProcess + > +>; + +export function ManageInstancesProcess({ route, navigation }: Props) { + const { isDrawerInstructionsVisible, closeDrawer, openDrawer, device } = useFollowInstructions(); + + const member = route.params?.member; + + return ( + <> + + {isDrawerInstructionsVisible && ( + + )} + + ); +} 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 2273b327e002..1d269c163afe 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 @@ -3,18 +3,18 @@ import { logDrawer } from "~/newArch/components/QueuedDrawer/utils/logDrawer"; import { useGetMembers } from "../../hooks/useGetMembers"; import { UseQueryResult } from "@tanstack/react-query"; import { MemberCredentials, TrustchainMember } from "@ledgerhq/trustchain/types"; -import { Device } from "@ledgerhq/live-common/hw/actions/types"; import { memberCredentialsSelector } from "@ledgerhq/trustchain/store"; import { useSelector } from "react-redux"; +import { useNavigation } from "@react-navigation/native"; +import { StackNavigatorNavigation } from "~/components/RootNavigator/types/helpers"; +import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator"; +import { ScreenName } from "~/const"; const messageLog = "Follow Steps on device"; export enum Scene { List, - DeviceAction, - Instructions, AutoRemove, - Unsecured, } export type HookResult = { @@ -22,12 +22,11 @@ export type HookResult = { openDrawer: () => void; closeDrawer: () => void; changeScene: (scene: Scene) => void; - onClickInstance: (device: Device) => void; handleClose: () => void; onClickDelete: (scene: Scene) => void; - memberHook: UseQueryResult; + memberHook: UseQueryResult; + onClickInstance: (instance: TrustchainMember) => void; scene: Scene; - device: Device | null; memberCredentials: MemberCredentials | null; }; @@ -35,12 +34,11 @@ export const useManageInstancesDrawer = (): HookResult => { const memberHook = useGetMembers(); const memberCredentials = useSelector(memberCredentialsSelector); const [scene, setScene] = useState(Scene.List); - const [device, setDevice] = useState(null); const onClickDelete = (scene: Scene) => setScene(scene); const [isDrawerVisible, setIsDrawerInstructionsVisible] = useState(false); - //const navigation = useNavigation>(); + const navigation = useNavigation>(); const openDrawer = useCallback(() => { setIsDrawerInstructionsVisible(true); @@ -53,9 +51,10 @@ export const useManageInstancesDrawer = (): HookResult => { logDrawer(messageLog, "close"); }, []); - const onClickInstance = (device: Device) => { - setScene(Scene.Instructions); - setDevice(device); + const onClickInstance = (instance: TrustchainMember) => { + navigation.navigate(ScreenName.WalletSyncManageInstancesProcess, { + member: instance, + }); }; const handleClose = () => { @@ -75,7 +74,6 @@ export const useManageInstancesDrawer = (): HookResult => { handleClose, onClickDelete, scene, - device, memberCredentials, }; }; 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 977040cf4ec7..ee3a6ab2e77b 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 @@ -6,6 +6,9 @@ import { ScreenName } from "~/const"; import { logDrawer } from "~/newArch/components/QueuedDrawer/utils/logDrawer"; import { useDestroyTrustchain } from "../../hooks/useDestroyTrustchain"; import { UseMutationResult } from "@tanstack/react-query"; +import { useDispatch, useSelector } from "react-redux"; +import { setWallectSyncManageKeyDrawer } from "~/actions/walletSync"; +import { manageKeyDrawerSelector } from "~/reducers/walletSync"; const messageLog = "Follow Steps on device"; @@ -28,22 +31,24 @@ export type HookResult = { export const useManageKeyDrawer = () => { const { deleteMutation } = useDestroyTrustchain(); - const [isDrawerVisible, setIsDrawerInstructionsVisible] = useState(false); + const isDrawerVisible = useSelector(manageKeyDrawerSelector); + + const dispatch = useDispatch(); const [scene, setScene] = useState(Scene.Manage); const onClickDelete = () => setScene(Scene.Confirm); const openDrawer = useCallback(() => { - setIsDrawerInstructionsVisible(true); + dispatch(setWallectSyncManageKeyDrawer(true)); logDrawer(messageLog, "open"); - }, []); + }, [dispatch]); const closeDrawer = useCallback(() => { - setIsDrawerInstructionsVisible(false); + dispatch(setWallectSyncManageKeyDrawer(false)); logDrawer(messageLog, "close"); - }, []); + }, [dispatch]); const navigation = useNavigation>(); diff --git a/apps/ledger-live-mobile/src/reducers/index.ts b/apps/ledger-live-mobile/src/reducers/index.ts index da8671bd45ed..fc3b78cf2d82 100644 --- a/apps/ledger-live-mobile/src/reducers/index.ts +++ b/apps/ledger-live-mobile/src/reducers/index.ts @@ -15,6 +15,7 @@ import nft from "./nft"; import market from "./market"; import wallet from "./wallet"; import trustchain from "./trustchain"; +import walletSync from "./walletSync"; import { State } from "./types"; import { ActionsPayload } from "../actions/types"; @@ -37,6 +38,7 @@ const appReducer = combineReducers({ wallet, market, trustchain, + walletSync, }); // TODO: EXPORT ALL POSSIBLE ACTION TYPES AND USE ACTION diff --git a/apps/ledger-live-mobile/src/reducers/types.ts b/apps/ledger-live-mobile/src/reducers/types.ts index 1d75bb2d83b8..47e7ebc80396 100644 --- a/apps/ledger-live-mobile/src/reducers/types.ts +++ b/apps/ledger-live-mobile/src/reducers/types.ts @@ -341,6 +341,12 @@ export type MarketState = { marketCurrentPage: number; }; +// === WALLETSYNC STATE === + +export type WalletSyncState = { + isManageKeyDrawerOpen: boolean; +}; + // === ROOT STATE === export type State = { @@ -360,4 +366,5 @@ export type State = { market: MarketState; wallet: WalletState; trustchain: TrustchainStore; + walletSync: WalletSyncState; }; diff --git a/apps/ledger-live-mobile/src/reducers/walletSync.ts b/apps/ledger-live-mobile/src/reducers/walletSync.ts new file mode 100644 index 000000000000..7a137c19c4b2 --- /dev/null +++ b/apps/ledger-live-mobile/src/reducers/walletSync.ts @@ -0,0 +1,21 @@ +import { handleActions } from "redux-actions"; +import type { Action, ReducerMap } from "redux-actions"; +import type { State, WalletSyncState } from "./types"; +import { WalletSyncPayload, WalletSyncSetManageKeyDrawerPayload } from "../actions/types"; + +export const INITIAL_STATE: WalletSyncState = { + isManageKeyDrawerOpen: false, +}; + +const handlers: ReducerMap = { + WALLET_SYNC_SET_MANAGE_KEY_DRAWER: (state, action) => ({ + ...state, + isManageKeyDrawerOpen: (action as Action).payload, + }), +}; + +export const storeSelector = (state: State): WalletSyncState => state.walletSync; +export const manageKeyDrawerSelector = (state: State): boolean => + state.walletSync.isManageKeyDrawerOpen; + +export default handleActions(handlers, INITIAL_STATE); diff --git a/apps/ledger-live-mobile/src/screens/Settings/General/WalletSyncRow.tsx b/apps/ledger-live-mobile/src/screens/Settings/General/WalletSyncRow.tsx index 2fe041f205de..c78861279a63 100644 --- a/apps/ledger-live-mobile/src/screens/Settings/General/WalletSyncRow.tsx +++ b/apps/ledger-live-mobile/src/screens/Settings/General/WalletSyncRow.tsx @@ -28,7 +28,7 @@ const WalletSyncRow = () => { }); } else { navigation.navigate(NavigatorName.WalletSync, { - screen: ScreenName.WalletSyncActivationSettings, + screen: ScreenName.WalletSyncActivationInit, }); } }, [navigation, onClickTrack, trustchain?.rootId]);