Skip to content

Commit

Permalink
[FEAT]: Handling already created key with new or same Ledger device (#…
Browse files Browse the repository at this point in the history
…7687)

* [FEAT]: Handling alredy created key with new or same Ledger device

* [FIX]: integration tests ✅

* Fix mocktest
  • Loading branch information
mcayuelas-ledger committed Aug 28, 2024
1 parent eb9a36f commit 297ce51
Show file tree
Hide file tree
Showing 36 changed files with 1,735 additions and 1,488 deletions.
6 changes: 6 additions & 0 deletions .changeset/lazy-pillows-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"ledger-live-desktop": patch
"@ledgerhq/trustchain": patch
---

Handling alredy created key with new or same Ledger device
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Box, Flex, Icons, Text } from "@ledgerhq/react-ui";
import { Box, Flex, Icons, Link, Text } from "@ledgerhq/react-ui";
import React from "react";
import styled, { useTheme } from "styled-components";
import ButtonV3 from "~/renderer/components/ButtonV3";
import { AnalyticsPage } from "../hooks/useWalletSyncAnalytics";
import TrackPage from "~/renderer/analytics/TrackPage";
import { useTranslation } from "react-i18next";

type Props = {
title?: string;
Expand All @@ -12,6 +13,7 @@ type Props = {
onClick?: () => void;
analyticsPage?: AnalyticsPage;
ctaVariant?: "shade" | "main";
onClose?: () => void;
};

const Container = styled(Box)`
Expand All @@ -31,8 +33,10 @@ export const Error = ({
onClick,
analyticsPage,
ctaVariant = "shade",
onClose,
}: Props) => {
const { colors } = useTheme();
const { t } = useTranslation();
return (
<Flex flexDirection="column" alignItems="center" justifyContent="center" rowGap="24px">
<TrackPage category={String(analyticsPage)} />
Expand All @@ -50,6 +54,14 @@ export const Error = ({
{cta}
</ButtonV3>
)}

{onClose && (
<Link color={"neutral.c100"} onClick={onClick}>
<Text fontSize={14} variant="paragraph" fontWeight="semiBold" color="neutral.c70">
{t("walletSync.close")}
</Text>
</Link>
)}
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -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/useWalletSyncAnalytics";
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 (
<Flex flexDirection="column" alignItems="center" justifyContent="center" rowGap="24px">
<TrackPage category={String(analyticsPage)} />
<Container>
{type === "info" ? (
<Icons.InformationFill size={"L"} color={colors.primary.c60} />
) : (
<Icons.CheckmarkCircleFill size={"L"} color={colors.success.c60} />
)}
</Container>
<Text fontSize={24} variant="h4Inter" color="neutral.c100" textAlign="center">
{title}
</Text>
<Text variant="bodyLineHeight" color="neutral.c70" textAlign="center">
{description}
</Text>

{withClose || withCta ? (
<BottomContainer
mb={3}
width={"100%"}
px={"40px"}
flexDirection="column"
justifyContent="center"
rowGap={"16px"}
>
{withCta && onClick && (
<ButtonV3 variant="main" onClick={onClick} flex={1}>
{specificCta ?? t("walletSync.success.synchAnother")}
</ButtonV3>
)}
{withClose && (
<ButtonV3 variant="shade" onClick={onClose} flex={1}>
{t("walletSync.success.close")}
</ButtonV3>
)}
</BottomContainer>
) : null}
</Flex>
);
};

const BottomContainer = styled(Flex)``;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import React from "react";
import { GenericProps, GenericStatusDisplay } from "./GenericStatusDisplay";

export const Info = (props: GenericProps) => <GenericStatusDisplay {...props} type="info" />;
Original file line number Diff line number Diff line change
@@ -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 (
<Flex flexDirection="column" alignItems="center" justifyContent="center" rowGap="24px">
<TrackPage category={String(analyticsPage)} />
<Container>
<Icons.CheckmarkCircleFill size={"L"} color={colors.success.c60} />
</Container>
<Text fontSize={24} variant="h4Inter" color="neutral.c100" textAlign="center">
{title}
</Text>
<Text variant="bodyLineHeight" color="neutral.c70" textAlign="center">
{description}
</Text>

{withClose || withCta ? (
<BottomContainer
mb={3}
width={"100%"}
px={"40px"}
flexDirection="column"
justifyContent="center"
rowGap={"16px"}
>
{withCta && onClick && (
<ButtonV3 variant="main" onClick={onClick} flex={1}>
{t("walletSync.success.synchAnother")}
</ButtonV3>
)}
{withClose && (
<ButtonV3 variant="shade" onClick={onClose} flex={1}>
{t("walletSync.success.close")}
</ButtonV3>
)}
</BottomContainer>
) : null}
</Flex>
);
};

const BottomContainer = styled(Flex)``;
export const Success = (props: GenericProps) => <GenericStatusDisplay {...props} type="success" />;
Original file line number Diff line number Diff line change
@@ -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<Error | null>(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(
Expand Down Expand Up @@ -63,11 +73,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);
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export enum AnalyticsPage {
InstanceRemovalSuccess = "Instance removal success",
Unsecured = "Remove instance wrong device connected",
AutoRemove = "Remove current instance",
AlreadySecuredSameSeed = "Already secured Ledger with this seed",
AlreadySecuredOtherSeed = "Already secured Ledger with another seed",

Activation = "Activate Wallet Sync",
DeviceActionActivation = "Device Action Activate Wallet Sync",
Expand Down Expand Up @@ -60,6 +62,8 @@ export const StepMappedToAnalytics: Record<Step, string> = {
[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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const WalletSyncActivation = forwardRef<BackRef, BackProps>((_props, ref) => {
return <ActivationFinalStep isNewBackup={true} />;
case Step.SynchronizationFinal:
return <ActivationFinalStep isNewBackup={false} />;

case Step.SynchronizationError:
return <ErrorStep />;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics";
import { Info } from "../../components/Info";

export default function AlreadyCreatedWithSameSeedStep() {
const { t } = useTranslation();
const dispatch = useDispatch();

const { onClickTrack } = useWalletSyncAnalytics();

const understood = () => {
dispatch(setFlow({ flow: Flow.WalletSyncActivated, step: Step.WalletSyncActivated }));
onClickTrack({
button: "I Understand",
page: AnalyticsPage.AlreadySecuredSameSeed,
flow: "Wallet Sync",
});
};

const onClose = () => {
dispatch(setDrawerVisibility(false));
onClickTrack({
button: "Close",
page: AnalyticsPage.AlreadySecuredSameSeed,
flow: "Wallet Sync",
});
};
return (
<Info
title={t("walletSync.alreadySecureError.title")}
description={t("walletSync.alreadySecureError.description")}
withCta
onClick={understood}
onClose={onClose}
analyticsPage={AnalyticsPage.AlreadySecuredSameSeed}
specificCta={t("walletSync.alreadySecureError.cta")}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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, useWalletSyncAnalytics } from "../../hooks/useWalletSyncAnalytics";
import { Error } from "../../components/Error";

export default function AlreadyCreatedOtherSeedStep() {
const { t } = useTranslation();
const dispatch = useDispatch();

const { onClickTrack } = useWalletSyncAnalytics();

const deleteKey = () => {
dispatch(setFlow({ flow: Flow.ManageBackup, step: Step.ManageBackup }));
onClickTrack({
button: "Delete my encryption key",
page: AnalyticsPage.AlreadySecuredOtherSeed,
flow: "Wallet Sync",
});
};

const onClose = () => {
dispatch(setDrawerVisibility(false));
onClickTrack({
button: "close",
page: AnalyticsPage.AlreadySecuredOtherSeed,
flow: "Wallet Sync",
});
};

return (
<Error
title={t("walletSync.alreadySecureOtherSeedError.title")}
description={t("walletSync.alreadySecureOtherSeedError.description")}
onClick={deleteKey}
analyticsPage={AnalyticsPage.AlreadySecuredOtherSeed}
cta={t("walletSync.alreadySecureOtherSeedError.cta")}
ctaVariant="shade"
onClose={onClose}
/>
);
}
Loading

0 comments on commit 297ce51

Please sign in to comment.