Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LLD] - [LIVE-12264] - Add Wallet Sync activation screen #6782

Merged
merged 7 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/swift-peaches-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ledger-live-desktop": patch
---

Add Wallet Sync activation screen
3 changes: 2 additions & 1 deletion apps/ledger-live-desktop/.unimportedrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"**/types.js",
"**/types.*",
"src/generate-cryptoassets-md.ts",
"src/newArch/Collectibles/**"
"src/newArch/Collectibles/**",
"src/renderer/screens/settings/sections/General/WalletSync/setupTests/shared.tsx"
],
"ignoreUnused": ["@types/semver", "@types/qrcode", "@types/react-key-handler", "prop-types"],
"aliases": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ export type DrawerProps = {
style?: React.CSSProperties;
withPaddingTop?: boolean;
};
const domNode = document.getElementById("modals");

export function SideDrawer({
children,
Expand All @@ -116,6 +115,7 @@ export function SideDrawer({
forceDisableFocusTrap = false,
...props
}: DrawerProps) {
const domNode = document.getElementById("modals");
const deviceBlocked = useDeviceBlocked();

const onKeyPress = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,7 @@ describe("RecipientField", () => {
recipientDomain: undefined,
});
});
expect(
screen.getByText("send.steps.recipient.domainService.noResolution.title"),
).toBeTruthy();
expect(screen.getByText("No address found for this domain")).toBeTruthy();

expect(screen.getByTestId("domain-error-no-resolution")).toBeTruthy();
});
Expand Down
3 changes: 3 additions & 0 deletions apps/ledger-live-desktop/src/renderer/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import postOnboarding from "@ledgerhq/live-common/postOnboarding/reducer";
import market, { MarketState } from "./market";
import wallet from "./wallet";
import { WalletState } from "@ledgerhq/live-wallet/store";
import walletSync, { WalletSyncState } from "./walletSync";

export type State = {
accounts: AccountsState;
Expand All @@ -25,6 +26,7 @@ export type State = {
postOnboarding: PostOnboardingState;
market: MarketState;
wallet: WalletState;
walletSync: WalletSyncState;
};

export default combineReducers({
Expand All @@ -39,4 +41,5 @@ export default combineReducers({
swap,
market,
wallet,
walletSync,
});
36 changes: 36 additions & 0 deletions apps/ledger-live-desktop/src/renderer/reducers/walletSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { handleActions } from "redux-actions";
import { Handlers } from "./types";

export type WalletSyncState = {
activated: boolean;
};

const initialState: WalletSyncState = {
activated: false,
};

type HandlersPayloads = {
WALLET_SYNC_ACTIVATE: boolean;
WALLET_SYNC_DEACTIVATE: boolean;
};

type MarketHandlers<PreciseKey = true> = Handlers<WalletSyncState, HandlersPayloads, PreciseKey>;

const handlers: MarketHandlers = {
WALLET_SYNC_ACTIVATE: (state: WalletSyncState) => ({
...state,
activated: true,
}),
WALLET_SYNC_DEACTIVATE: (state: WalletSyncState) => ({
...state,
activated: false,
}),
};

// Selectors
export const walletSyncSelector = (state: { walletSync: WalletSyncState }) => state.walletSync;

export default handleActions<WalletSyncState, HandlersPayloads[keyof HandlersPayloads]>(
handlers as unknown as MarketHandlers<false>,
initialState,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Box, Flex, Icons, Text } from "@ledgerhq/react-ui";
import React, { PropsWithChildren } from "react";
import { useTranslation } from "react-i18next";
import ButtonV3 from "~/renderer/components/ButtonV3";
import useTheme from "~/renderer/hooks/useTheme";

const LogoWrapper = ({ children, opacity = "70%" }: PropsWithChildren & { opacity?: string }) => (
<Box>
<Flex padding="7px" borderRadius="13px" border="1px solid hsla(0, 0%, 100%, 0.05)">
<Flex
borderRadius="9px"
backgroundColor="hsla(248, 100%, 85%, 0.08)"
padding="5px"
opacity={opacity}
>
{children}
</Flex>
</Flex>
</Box>
);

const WalletSyncActivation = () => {
const { colors } = useTheme();
const { t } = useTranslation();

return (
<Flex
flexDirection="column"
height="100%"
paddingX="64px"
alignSelf="center"
justifyContent="center"
rowGap="48px"
>
<Flex flexDirection="column" alignSelf="center" justifyContent="center" rowGap="24px">
<Flex justifyContent="center" alignItems="center">
<LogoWrapper>
<Icons.Mobile color={colors.constant.purple} />
</LogoWrapper>

<LogoWrapper opacity="100%">
<Icons.Refresh size="L" color={colors.constant.purple} />
</LogoWrapper>

<LogoWrapper>
<Icons.Desktop color={colors.constant.purple} />
</LogoWrapper>
</Flex>

<Text fontSize={24} variant="h4Inter" textAlign="center">
{t("walletSync.activate.title")}
</Text>
<Text fontSize={14} variant="body" color="hsla(0, 0%, 58%, 1)" textAlign="center">
{t("walletSync.activate.description")}
</Text>
<Flex justifyContent="center">
<ButtonV3 variant="main"> {t("walletSync.activate.cta")}</ButtonV3>
</Flex>
</Flex>

<Box>
<Flex
flexDirection="row"
padding="18px"
borderRadius="12px"
backgroundColor={colors.opacityDefault.c05}
justifyContent="space-between"
>
<Text variant="body" fontSize={14} flexShrink={1}>
{t("walletSync.alreadySync.title")}
</Text>
<Box>
<ButtonV3 variant="shade">{t("walletSync.alreadySync.cta")}</ButtonV3>
</Box>
</Flex>
</Box>
</Flex>
);
};

export default WalletSyncActivation;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const WalletSyncManage = () => {
return <div>Wallet Sync</div>;
};

export default WalletSyncManage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @jest-environment jsdom
*/

import { describe, it, expect } from "@jest/globals";

import React from "react";
import { render, screen, waitFor } from "tests/testUtils";
import "@testing-library/jest-dom";
import { WalletSyncPages } from "../setupTests/shared";

describe("Rendering", () => {
it("should loads and displays WalletSync Row", async () => {
render(<WalletSyncPages />);
expect(screen.getByRole("button", { name: "Manage" })).toBeTruthy();
});

it("should open drawer and display Wallet Sync Activation flow", async () => {
const { user } = render(<WalletSyncPages />);
const button = screen.getByRole("button", { name: "Manage" });

await user.click(button);
await waitFor(() =>
expect(screen.getByRole("button", { name: "Create a backup" })).toBeDefined(),
);

expect(screen.getByRole("button", { name: "Synchronize" })).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import React, { useState } from "react";
import { useSelector } from "react-redux";
import Button from "~/renderer/components/Button";
import { SideDrawer } from "~/renderer/components/SideDrawer";

const SideContentActivateWalletSync = () => {
return <div>Activate Wallet Sync</div>;
};

const SideContentWalletSync = () => {
return <div>Wallet Sync</div>;
};
import { walletSyncSelector } from "~/renderer/reducers/walletSync";
import WalletSyncActivation from "./SideContent/Activation";
import WalletSyncManage from "./SideContent/Manage";
import { useTranslation } from "react-i18next";

const WalletSyncRow = () => {
const lldWalletSync = useFeature("lldWalletSync");

const walletSync = useSelector(walletSyncSelector);
const [open, setOpen] = useState(false);
const { t } = useTranslation();

return (
<>
<SideDrawer isOpen={open} onRequestClose={() => setOpen(false)} direction="left">
{lldWalletSync?.enabled ? <SideContentWalletSync /> : <SideContentActivateWalletSync />}
{walletSync.activated ? <WalletSyncManage /> : <WalletSyncActivation />}
</SideDrawer>

<Button small event="Manage WalletSync" primary onClick={() => setOpen(true)}>
Manage
{t("walletSync.manage")}
</Button>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from "react";
import WalletSyncRow from "../index";

export function WalletSyncPages() {
return (
<>
<div id="modals"></div>
<WalletSyncRow />
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import CounterValueSelect from "./CounterValueSelect";
import LanguageSelect from "./LanguageSelect";
import RegionSelect from "./RegionSelect";
import ThemeSelect from "./ThemeSelect";
import WalletSync from "./WalletSyncRow";
import WalletSync from "./WalletSync";
import PasswordButton from "./PasswordButton";
import PasswordAutoLockSelect from "./PasswordAutoLockSelect";
import SentryLogsButton from "./SentryLogsButton";
Expand Down
12 changes: 12 additions & 0 deletions apps/ledger-live-desktop/static/i18n/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -6274,5 +6274,17 @@
"personalizedExpDesc": "Receive personalised recommendations that match your preferences.",
"analyticsDesc": "Enable analytics to help Ledger improve the user experience."
}
},
"walletSync": {
"manage": "Manage",
"activate": {
"title": "Activate wallet sync",
"description": "Create a secure backup of your Ledger Live and synchronize multiple instances of Ledger Live, both on mobile and desktop.",
"cta": "Create a backup"
},
"alreadySync": {
"title": "Already created a back-up on another Device?",
"cta": "Synchronize"
}
}
}
15 changes: 15 additions & 0 deletions apps/ledger-live-desktop/tests/jestSetup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import { jest } from "@jest/globals";
import { TextDecoder, TextEncoder } from "util";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore FIX TS config
jest.mock("@sentry/electron/renderer", () => ({
init: jest.fn(),
setUser: jest.fn(),
captureException: jest.fn(),
addBreadcrumb: jest.fn(),
setTags: jest.fn(),
}));

jest.mock("src/sentry/install", () => ({
init: jest.fn(),
}));

global.TextEncoder = TextEncoder;
// @ts-expect-error weird compatibility
global.TextDecoder = TextDecoder;
14 changes: 9 additions & 5 deletions apps/ledger-live-desktop/tests/testUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { render as rtlRender } from "@testing-library/react";
import { type State } from "~/renderer/reducers";
import createStore from "../src/renderer/createStore";
import dbMiddleware from "../src/renderer/middlewares/db";
import { I18nextProvider } from "react-i18next";
import i18n from "~/renderer/i18n/init";

interface ChildrenProps {
children: JSX.Element;
Expand Down Expand Up @@ -38,11 +40,13 @@ function render(
): RenderReturn {
function Wrapper({ children }: ChildrenProps) {
return (
<Provider store={store}>
<StyleProvider selectedPalette="dark">
<Router>{children}</Router>
</StyleProvider>
</Provider>
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<StyleProvider selectedPalette="dark">
<Router>{children}</Router>
</StyleProvider>
</Provider>
</I18nextProvider>
);
}
return {
Expand Down
3 changes: 2 additions & 1 deletion apps/ledger-live-desktop/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"package.json",
"release-notes.json",
"tests",
"index-types.d.ts"
"index-types.d.ts",
"tests/jestSetup.ts"
]
}