Skip to content

Commit

Permalink
[FEAT]: User connects backup to instance with same backup or differen…
Browse files Browse the repository at this point in the history
…t backup (#7697)
  • Loading branch information
mcayuelas-ledger authored Aug 29, 2024
1 parent ea1500d commit b1a8c54
Show file tree
Hide file tree
Showing 14 changed files with 284 additions and 127 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-peaches-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": patch
---

User connects backup to instance with same backup or different backup
10 changes: 10 additions & 0 deletions apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -6863,6 +6863,16 @@
"desc": "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"
}
},
"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"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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 (
<Flex flexDirection="column" alignItems="center" justifyContent="center">
<TrackScreen category={analyticsPage} />
<Container mb={6}>{icon}</Container>
<Text variant="h4" color="neutral.c100" textAlign="center" fontWeight="semiBold" mb={4}>
{title}
</Text>

<Flex flexDirection="column" mb={8} rowGap={16}>
<Text variant="bodyLineHeight" color="neutral.c70" textAlign="center">
{description}
</Text>
<Text variant="bodyLineHeight" color="neutral.c70" textAlign="center">
{info}
</Text>
</Flex>

<Flex flexDirection="column" rowGap={24} mb={6} width={"100%"} px={"16px"}>
<Button type={buttonType} outline={outline} onPress={primaryAction}>
{cta}
</Button>

<Link onPress={secondaryAction}>
<Text variant="paragraph" fontWeight="semiBold" color="neutral.c70">
{ctaSecondary}
</Text>
</Link>
</Flex>
</Flex>
);
}

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;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";
import { ErrorReason, useSpecificError } from "../../hooks/useSpecificError";
import { DetailedError } from "./Detailed";

type Props = {
error: ErrorReason;
cancel?: () => void;
understood?: () => void;
goToDelete?: () => void;
tryAgain?: () => void;
};

export const SpecificError = ({ error, cancel, goToDelete, understood, tryAgain }: Props) => {
const { getErrorConfig } = useSpecificError({ cancel, goToDelete, understood, tryAgain });
const config = getErrorConfig(error);

return <DetailedError {...config} />;
};
Original file line number Diff line number Diff line change
@@ -1,28 +1,6 @@
import React from "react";
import { Box, Icons, Flex, Text, Button, Link } from "@ledgerhq/native-ui";
import { useTranslation } from "react-i18next";
import {
useLedgerSyncAnalytics,
AnalyticsButton,
AnalyticsPage,
} from "../../hooks/useLedgerSyncAnalytics";
import styled, { useTheme } from "styled-components/native";
import TrackScreen from "~/analytics/TrackScreen";

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;
`;

export enum ErrorReason {
UNSECURED = "unsecured",
AUTO_REMOVE = "auto-remove",
}
import { ErrorReason } from "../../hooks/useSpecificError";
import { SpecificError } from "../Error/SpecificError";

type Props = {
error: ErrorReason;
Expand All @@ -31,100 +9,6 @@ type Props = {
goToDelete?: () => void;
};

export const DeletionError = ({ error, tryAgain, goToDelete, understood }: Props) => {
const { onClickTrack } = useLedgerSyncAnalytics();

const onTryAgain = () => {
tryAgain?.();
onClickTrack({ button: AnalyticsButton.UseAnother, page: errorConfig[error].analyticsPage });
};
const onGoToDelete = () => {
goToDelete?.();
onClickTrack({ button: AnalyticsButton.DeleteKey, page: errorConfig[error].analyticsPage });
};

const onUnderstood = () => {
understood?.();
onClickTrack({ button: AnalyticsButton.Understand, page: errorConfig[error].analyticsPage });
};
const { t } = useTranslation();
const { colors } = useTheme();

const errorConfig = {
[ErrorReason.UNSECURED]: {
icon: <Icons.DeleteCircleFill size={"L"} color={colors.error.c60} />,
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",
),
primaryAction: onTryAgain,
secondaryAction: onGoToDelete,
analyticsPage: AnalyticsPage.RemoveInstanceWrongDevice,
},
[ErrorReason.AUTO_REMOVE]: {
icon: <Icons.InformationFill size={"L"} color={colors.primary.c80} />,
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",
),
primaryAction: onUnderstood,
secondaryAction: onGoToDelete,
analyticsPage: AnalyticsPage.AutoRemove,
},
};

const getErrorConfig = (error: ErrorReason) => errorConfig[error];

const {
icon,
title,
description,
info,
cta,
ctaSecondary,
primaryAction,
secondaryAction,
analyticsPage,
} = getErrorConfig(error);

return (
<Flex flexDirection="column" alignItems="center" justifyContent="center">
<TrackScreen category={analyticsPage} />
<Container mb={6}>{icon}</Container>
<Text variant="h4" color="neutral.c100" textAlign="center" fontWeight="semiBold" mb={4}>
{title}
</Text>

<Flex flexDirection="column" mb={8} rowGap={16}>
<Text variant="bodyLineHeight" color="neutral.c70" textAlign="center">
{description}
</Text>
<Text variant="bodyLineHeight" color="neutral.c70" textAlign="center">
{info}
</Text>
</Flex>

<Flex flexDirection="column" rowGap={24} mb={6} width={"100%"} px={"16px"}>
<Button type="main" onPress={primaryAction}>
{cta}
</Button>

<Link onPress={secondaryAction}>
<Text variant="paragraph" fontWeight="semiBold" color="neutral.c70">
{ctaSecondary}
</Text>
</Link>
</Flex>
</Flex>
);
export const DeletionError = (props: Props) => {
return <SpecificError {...props} />;
};
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -13,11 +21,12 @@ 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<StackNavigatorNavigation<WalletSyncNavigatorStackParamList>>();

const transitionToNextScreen = useCallback(
Expand Down Expand Up @@ -45,13 +54,19 @@ 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);
}
} 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 });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export enum AnalyticsPage {
LedgerSyncActivated = "Ledger Sync activated",
AutoRemove = "Remove current instance",
Unbacked = "Unbacked",
OtherSeed = "Other seed",
SameSeed = "Same seed",
}

export enum AnalyticsFlow {
Expand Down
Loading

0 comments on commit b1a8c54

Please sign in to comment.