diff --git a/.changeset/forty-ants-end.md b/.changeset/forty-ants-end.md
new file mode 100644
index 000000000000..a8639cb40431
--- /dev/null
+++ b/.changeset/forty-ants-end.md
@@ -0,0 +1,5 @@
+---
+"ledger-live-desktop": minor
+---
+
+Unify the Memo/Tag input wording, tooltip for coins that support memo tag except (solana, ton, stellar -> at this scoping level we decided not to touch the flow/wording of memo for those coins)
diff --git a/apps/ledger-live-desktop/src/config/urls.ts b/apps/ledger-live-desktop/src/config/urls.ts
index 70cae363b2a4..4f9e147e34f9 100644
--- a/apps/ledger-live-desktop/src/config/urls.ts
+++ b/apps/ledger-live-desktop/src/config/urls.ts
@@ -188,6 +188,9 @@ export const urls = {
"https://shop.ledger.com?utm_source=live&utm_medium=draw&utm_campaign=ledger_sync_lns_uncompatible&utm_content=to_shop",
learnMoreLedgerSync:
"https://www.ledger.com/blog-ledger-sync-synchronize-your-crypto-accounts-effortless-private-and-secure",
+ memoTag: {
+ learnMore: "https://support.ledger.com/article/4409603715217-zd",
+ },
};
export const vaultSigner = {
diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/LearnMoreCta.test.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/LearnMoreCta.test.tsx
new file mode 100644
index 000000000000..14ff941c8923
--- /dev/null
+++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/LearnMoreCta.test.tsx
@@ -0,0 +1,27 @@
+/**
+ * @jest-environment jsdom
+ */
+import React from "react";
+import { fireEvent, render, screen } from "tests/testUtils";
+import * as LinkingHelpers from "~/renderer/linking";
+import LearnMoreCta from "../components/LearnMoreCta";
+
+jest.mock("~/renderer/linking", () => ({
+ openURL: jest.fn(),
+}));
+
+describe("MemoTagInfoBody", () => {
+ it("should display MemoTagInfoBody correctly", async () => {
+ render();
+
+ const learnMoreLink = screen.getByText(/Learn more about Tag\/Memo/);
+
+ expect(learnMoreLink).toBeVisible();
+
+ fireEvent.click(learnMoreLink);
+
+ jest.spyOn(LinkingHelpers, "openURL");
+ // spec: when a user clicks on the learn more link, it should open the link
+ expect(LinkingHelpers.openURL).toHaveBeenCalledWith("example.domain.com");
+ });
+});
diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagField.test.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagField.test.tsx
new file mode 100644
index 000000000000..d05201646a8b
--- /dev/null
+++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagField.test.tsx
@@ -0,0 +1,54 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import React from "react";
+import { render, screen, fireEvent } from "tests/testUtils";
+import MemoTagField from "../components/MemoTagField";
+
+jest.mock("react-i18next", () => ({
+ ...jest.requireActual("react-i18next"),
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}));
+
+describe("MemoTagField", () => {
+ it("renders MemoTagField with label and text field", () => {
+ render();
+ expect(screen.getByText("MemoTagField.label")).toBeInTheDocument();
+ expect(screen.getByRole("textbox")).toBeInTheDocument();
+ expect(screen.getByPlaceholderText("MemoTagField.placeholder")).toBeInTheDocument();
+ });
+
+ it("should render MemoTagField without label", () => {
+ render();
+ expect(screen.queryByText("MemoTagField.label")).not.toBeInTheDocument();
+ });
+
+ it("should call onChange when input value changes", () => {
+ const handleChange = jest.fn();
+ render();
+ fireEvent.change(screen.getByPlaceholderText("MemoTagField.placeholder"), {
+ target: { value: "new memo" },
+ });
+ expect(handleChange).toHaveBeenCalledTimes(1);
+ });
+
+ it("should render CaracterCountComponent if provided", () => {
+ const CaracterCountComponent = () =>
Character Count
;
+ render();
+ expect(screen.getByText("Character Count")).toBeInTheDocument();
+ });
+
+ it("should render instruction text on autoFocus", () => {
+ render();
+ expect(screen.getByPlaceholderText("MemoTagField.placeholder")).toHaveFocus();
+ expect(screen.getByText("MemoTagField.instruction")).toBeInTheDocument();
+ });
+
+ it("should render with error", () => {
+ render();
+ expect(screen.getByTestId("input-error")).toBeInTheDocument();
+ });
+});
diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfoBody.test.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfoBody.test.tsx
index 3becffa6c68e..0206eaea8b66 100644
--- a/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfoBody.test.tsx
+++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagInfoBody.test.tsx
@@ -2,10 +2,8 @@
* @jest-environment jsdom
*/
import React from "react";
-import { fireEvent, render, screen } from "tests/testUtils";
+import { render, screen } from "tests/testUtils";
import MemoTagInfoBody from "../components/MemoTagInfoBody";
-import { MEMO_TAG_LEARN_MORE_LINK } from "../constants";
-import * as LinkingHelpers from "~/renderer/linking";
jest.mock("~/renderer/linking", () => ({
openURL: jest.fn(),
@@ -16,15 +14,7 @@ describe("MemoTagInfoBody", () => {
render();
const memoTagInfoBody = screen.getByTestId("memo-tag-info-body");
- const learnMoreLink = screen.getByText(/Learn more about Tag\/Memo/);
expect(memoTagInfoBody).toBeVisible();
- expect(learnMoreLink).toBeVisible();
-
- fireEvent.click(learnMoreLink);
-
- jest.spyOn(LinkingHelpers, "openURL");
- // spec: when a user clicks on the learn more link, it should open the link
- expect(LinkingHelpers.openURL).toHaveBeenCalledWith(MEMO_TAG_LEARN_MORE_LINK);
});
});
diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/LearnMoreCta.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/LearnMoreCta.tsx
new file mode 100644
index 000000000000..49847bb9f0fb
--- /dev/null
+++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/LearnMoreCta.tsx
@@ -0,0 +1,41 @@
+import React from "react";
+import { Link } from "@ledgerhq/react-ui";
+import { Trans } from "react-i18next";
+import { openURL } from "~/renderer/linking";
+import { useLocalizedUrl } from "~/renderer/hooks/useLocalizedUrls";
+
+type LearnMoreCtaProps = {
+ size?: "small" | "medium" | "large";
+ color?: string;
+ style?: React.CSSProperties;
+ Icon?: React.ComponentType<{ size: number; color?: string }>;
+ url: string;
+};
+
+const LearnMoreCta = ({
+ size = "small",
+ color = "neutral.c80",
+ style,
+ Icon,
+ url,
+}: LearnMoreCtaProps) => {
+ const localizedUrl = useLocalizedUrl(url);
+
+ if (!localizedUrl) return null;
+
+ const handleOpenLMLink = () => openURL(localizedUrl);
+
+ return (
+
+
+
+ );
+};
+
+export default LearnMoreCta;
diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagField.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagField.tsx
new file mode 100644
index 000000000000..1485ae9ae29e
--- /dev/null
+++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagField.tsx
@@ -0,0 +1,78 @@
+import React from "react";
+import Input, { Props as InputBaseProps } from "~/renderer/components/Input";
+import Label from "~/renderer/components/Label";
+import Box from "~/renderer/components/Box";
+import { useTranslation } from "react-i18next";
+import { Flex, Text, Tooltip } from "@ledgerhq/react-ui";
+import styled from "styled-components";
+import InfoCircle from "~/renderer/icons/InfoCircle";
+
+const TooltipContainer = styled(Box)`
+ background-color: ${({ theme }) => theme.colors.palette.neutral.c100};
+ padding: 10px;
+ border-radius: 4px;
+ display: flex;
+ gap: 8px;
+`;
+
+const InstructionText = styled(Text)`
+ color: ${({ theme }) => theme.colors.primary.c80};
+ margin-top: 6px;
+ font-size: 12px;
+ line-height: 16.8px;
+`;
+
+type MemoTagFieldProps = InputBaseProps & {
+ maxMemoLength?: number;
+ showLabel?: boolean;
+ CaracterCountComponent?: React.FC;
+ autoFocus?: boolean;
+};
+
+const MemoTagField = ({
+ warning,
+ error,
+ value,
+ onChange,
+ showLabel = true,
+ maxMemoLength,
+ CaracterCountComponent,
+ autoFocus,
+}: MemoTagFieldProps) => {
+ const { t } = useTranslation();
+ return (
+
+ {showLabel && (
+
+ )}
+
+ {CaracterCountComponent && }
+
+ {autoFocus && {t("MemoTagField.instruction")}}
+
+
+ );
+};
+
+export default MemoTagField;
diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfoBody.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfoBody.tsx
index 6354db4e01d1..3365a44a3bdf 100644
--- a/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfoBody.tsx
+++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagInfoBody.tsx
@@ -1,33 +1,19 @@
import React from "react";
-import { Link, Text } from "@ledgerhq/react-ui";
+import { Text } from "@ledgerhq/react-ui";
import { Trans } from "react-i18next";
-import { openURL } from "~/renderer/linking";
-import { MEMO_TAG_LEARN_MORE_LINK } from "../constants";
+import LearnMoreCta from "./LearnMoreCta";
+import { urls } from "~/config/urls";
-const MemoTagInfoBody = () => {
- const handleOpenLMLink = () => openURL(MEMO_TAG_LEARN_MORE_LINK);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-};
+const MemoTagInfoBody = () => (
+
+
+
+
+
+
+
+
+
+);
export default MemoTagInfoBody;
diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagSendInfo.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagSendInfo.tsx
new file mode 100644
index 000000000000..c556afbd8258
--- /dev/null
+++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagSendInfo.tsx
@@ -0,0 +1,36 @@
+import { Flex, Icons } from "@ledgerhq/react-ui";
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { useTheme } from "styled-components";
+import LearnMoreCta from "LLD/features/MemoTag/components/LearnMoreCta";
+import { CircleWrapper } from "~/renderer/components/CryptoCurrencyIcon";
+import Text from "~/renderer/components/Text";
+import { urls } from "~/config/urls";
+
+const MemoTagSendInfo = () => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+
+ {t("send.info.needMemoTag.title")}
+
+
+ {t("send.info.needMemoTag.description")}
+
+ }
+ url={urls.memoTag.learnMore}
+ />
+
+
+ );
+};
+
+export default MemoTagSendInfo;
diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/constants.ts b/apps/ledger-live-desktop/src/newArch/features/MemoTag/constants.ts
index 43d38061a4ee..77ccc2914a04 100644
--- a/apps/ledger-live-desktop/src/newArch/features/MemoTag/constants.ts
+++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/constants.ts
@@ -1,4 +1,3 @@
-export const MEMO_TAG_LEARN_MORE_LINK = "https://support.ledger.com/article/4409603715217-zd";
export const MEMO_TAG_COINS: string[] = [
"ripple",
"stellar",
@@ -11,4 +10,6 @@ export const MEMO_TAG_COINS: string[] = [
"ton",
"eos",
"bsc",
+ "casper",
+ "cardano",
];
diff --git a/apps/ledger-live-desktop/src/renderer/actions/UI.ts b/apps/ledger-live-desktop/src/renderer/actions/UI.ts
index 4d9ee0e6b948..44c8d50ff94a 100644
--- a/apps/ledger-live-desktop/src/renderer/actions/UI.ts
+++ b/apps/ledger-live-desktop/src/renderer/actions/UI.ts
@@ -68,3 +68,17 @@ export const openPlatformAppDisclaimerDrawer = createAction(
}),
);
export const closePlatformAppDrawer = createAction("PLATFORM_APP_DRAWER_CLOSE");
+
+export const setMemoTagInfoBoxDisplay = createAction(
+ "TOGGLE_MEMOTAG_DISPLAY",
+ ({
+ isMemoTagBoxVisible,
+ forceAutoFocusOnMemoField,
+ }: {
+ isMemoTagBoxVisible: boolean;
+ forceAutoFocusOnMemoField?: boolean;
+ }) => ({
+ isMemoTagBoxVisible,
+ forceAutoFocusOnMemoField,
+ }),
+);
diff --git a/apps/ledger-live-desktop/src/renderer/actions/application.ts b/apps/ledger-live-desktop/src/renderer/actions/application.ts
index e86f70be0a54..c4f8331ac66c 100644
--- a/apps/ledger-live-desktop/src/renderer/actions/application.ts
+++ b/apps/ledger-live-desktop/src/renderer/actions/application.ts
@@ -34,3 +34,10 @@ export const toggleSkeletonVisibility = createAction(
},
}),
);
+
+export const toggleShouldDisplayMemoTagInfo = createAction(
+ "APPLICATION_SET_DATA",
+ (alwaysShowMemoTagInfo: boolean) => ({
+ alwaysShowMemoTagInfo,
+ }),
+);
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoField.tsx b/apps/ledger-live-desktop/src/renderer/families/cardano/SendRecipientFields/MemoField.tsx
similarity index 54%
rename from apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoField.tsx
rename to apps/ledger-live-desktop/src/renderer/families/cardano/SendRecipientFields/MemoField.tsx
index 0c379939cfae..31ce9045d031 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoField.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/cardano/SendRecipientFields/MemoField.tsx
@@ -1,7 +1,5 @@
import React from "react";
-import { useTranslation } from "react-i18next";
import Box from "~/renderer/components/Box";
-import Label from "~/renderer/components/Label";
import MemoValueField from "./MemoValueField";
import {
CardanoAccount,
@@ -16,19 +14,9 @@ const MemoField = (props: {
onChange: (t: Transaction) => void;
trackProperties?: Record;
}) => {
- const { t } = useTranslation();
return (
-
-
-
-
-
-
-
-
+
);
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoValueField.tsx b/apps/ledger-live-desktop/src/renderer/families/cardano/SendRecipientFields/MemoValueField.tsx
similarity index 85%
rename from apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoValueField.tsx
rename to apps/ledger-live-desktop/src/renderer/families/cardano/SendRecipientFields/MemoValueField.tsx
index 69072f358fa6..c68466a1c9d1 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoValueField.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/cardano/SendRecipientFields/MemoValueField.tsx
@@ -1,7 +1,5 @@
import React, { useCallback } from "react";
-import { useTranslation } from "react-i18next";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
-import Input from "~/renderer/components/Input";
import invariant from "invariant";
import {
CardanoAccount,
@@ -9,6 +7,7 @@ import {
TransactionStatus,
} from "@ledgerhq/live-common/families/cardano/types";
import { track } from "~/renderer/analytics/segment";
+import MemoTagField from "LLD/features/MemoTag/components/MemoTagField";
const MemoValueField = ({
onChange,
@@ -16,14 +15,15 @@ const MemoValueField = ({
transaction,
status,
trackProperties,
+ autoFocus,
}: {
onChange: (a: Transaction) => void;
account: CardanoAccount;
transaction: Transaction;
status: TransactionStatus;
trackProperties?: Record;
+ autoFocus?: boolean;
}) => {
- const { t } = useTranslation();
invariant(transaction.family === "cardano", "Memo: cardano family expected");
const bridge = getAccountBridge(account);
const onMemoValueChange = useCallback(
@@ -42,10 +42,11 @@ const MemoValueField = ({
[trackProperties, onChange, bridge, transaction],
);
return (
-
);
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/index.tsx b/apps/ledger-live-desktop/src/renderer/families/cardano/SendRecipientFields/index.tsx
similarity index 100%
rename from apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/index.tsx
rename to apps/ledger-live-desktop/src/renderer/families/cardano/SendRecipientFields/index.tsx
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/index.ts b/apps/ledger-live-desktop/src/renderer/families/cardano/index.ts
index 08a93acd247a..438d6524716c 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cardano/index.ts
+++ b/apps/ledger-live-desktop/src/renderer/families/cardano/index.ts
@@ -1,6 +1,6 @@
import AccountBodyHeader from "./AccountBodyHeader";
import AccountSubHeader from "./AccountSubHeader";
-import sendAmountFields from "./SendAmountFields";
+import sendRecipientFields from "./SendRecipientFields";
import AccountBalanceSummaryFooter from "./AccountBalanceSummaryFooter";
import accountHeaderManageActions from "./AccountHeaderManageActions";
import { CardanoFamily } from "./types";
@@ -8,7 +8,7 @@ import { CardanoFamily } from "./types";
const family: CardanoFamily = {
AccountBodyHeader,
AccountSubHeader,
- sendAmountFields,
+ sendRecipientFields,
AccountBalanceSummaryFooter,
accountHeaderManageActions,
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/cosmos/MemoValueField.tsx b/apps/ledger-live-desktop/src/renderer/families/cosmos/MemoValueField.tsx
index 756f694248d6..b911d300873c 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cosmos/MemoValueField.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/cosmos/MemoValueField.tsx
@@ -1,25 +1,25 @@
import React, { useCallback } from "react";
-import { useTranslation } from "react-i18next";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
-import Input from "~/renderer/components/Input";
import invariant from "invariant";
import {
CosmosAccount,
Transaction,
TransactionStatus,
} from "@ledgerhq/live-common/families/cosmos/types";
+import MemoTagField from "LLD/features/MemoTag/components/MemoTagField";
const MemoValueField = ({
onChange,
account,
transaction,
status,
+ autoFocus,
}: {
onChange: (transaction: Transaction) => void;
account: CosmosAccount;
transaction: Transaction;
status: TransactionStatus;
+ autoFocus?: boolean;
}) => {
- const { t } = useTranslation();
invariant(transaction.family === "cosmos", "MemoTypeField: cosmos family expected");
const bridge = getAccountBridge(account);
const onMemoValueChange = useCallback(
@@ -37,12 +37,12 @@ const MemoValueField = ({
// It will be usefull to block a memo wrong format
// on the ledger-live mobile
return (
-
);
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/cosmos/SendRecipientFields.tsx b/apps/ledger-live-desktop/src/renderer/families/cosmos/SendRecipientFields.tsx
index 8f9643e41390..26f2c15028e2 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cosmos/SendRecipientFields.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/cosmos/SendRecipientFields.tsx
@@ -1,27 +1,12 @@
import React from "react";
-import { useTranslation } from "react-i18next";
import Box from "~/renderer/components/Box";
-import Label from "~/renderer/components/Label";
-import LabelInfoTooltip from "~/renderer/components/LabelInfoTooltip";
import MemoValueField from "./MemoValueField";
import { CosmosFamily } from "./types";
const Root: NonNullable["component"] = props => {
- const { t } = useTranslation();
return (
-
-
-
-
-
-
-
-
+
);
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/crypto_org/MemoValueField.tsx b/apps/ledger-live-desktop/src/renderer/families/crypto_org/MemoValueField.tsx
index 25e78a21fb23..4d7b3eb8d3f5 100644
--- a/apps/ledger-live-desktop/src/renderer/families/crypto_org/MemoValueField.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/crypto_org/MemoValueField.tsx
@@ -1,25 +1,25 @@
import React, { useCallback } from "react";
-import { useTranslation } from "react-i18next";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
-import Input from "~/renderer/components/Input";
import {
CryptoOrgAccount,
Transaction,
TransactionStatus,
} from "@ledgerhq/live-common/families/crypto_org/types";
+import MemoTagField from "LLD/features/MemoTag/components/MemoTagField";
const MemoValueField = ({
onChange,
account,
transaction,
status,
+ autoFocus,
}: {
onChange: (t: Transaction) => void;
account: CryptoOrgAccount;
transaction: Transaction;
status: TransactionStatus;
+ autoFocus?: boolean;
}) => {
- const { t } = useTranslation();
const bridge = getAccountBridge(account);
const onMemoValueChange = useCallback(
(memo: string) => {
@@ -36,12 +36,12 @@ const MemoValueField = ({
// It will be usefull to block a memo wrong format
// on the ledger-live mobile
return (
-
);
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/crypto_org/SendRecipientFields.tsx b/apps/ledger-live-desktop/src/renderer/families/crypto_org/SendRecipientFields.tsx
index 51c8fe4e8b11..94e39c35b959 100644
--- a/apps/ledger-live-desktop/src/renderer/families/crypto_org/SendRecipientFields.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/crypto_org/SendRecipientFields.tsx
@@ -1,27 +1,12 @@
import React from "react";
-import { useTranslation } from "react-i18next";
import Box from "~/renderer/components/Box";
-import Label from "~/renderer/components/Label";
-import LabelInfoTooltip from "~/renderer/components/LabelInfoTooltip";
import MemoValueField from "./MemoValueField";
import { CryptoOrgFamily } from "./types";
const Root: NonNullable["component"] = props => {
- const { t } = useTranslation();
return (
-
-
-
-
-
-
-
-
+
);
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/hedera/MemoField.tsx b/apps/ledger-live-desktop/src/renderer/families/hedera/MemoField.tsx
index 1a79db03bd82..ce327bc6ddeb 100644
--- a/apps/ledger-live-desktop/src/renderer/families/hedera/MemoField.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/hedera/MemoField.tsx
@@ -1,20 +1,19 @@
import React, { useCallback } from "react";
-import { Trans, useTranslation } from "react-i18next";
+import { Trans } from "react-i18next";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
import { track } from "~/renderer/analytics/segment";
import { SendAmountProps } from "./types";
-import Box from "~/renderer/components/Box";
-import Label from "~/renderer/components/Label";
-import Input from "~/renderer/components/Input";
import Text from "~/renderer/components/Text";
+import MemoTagField from "LLD/features/MemoTag/components/MemoTagField";
+
const MemoField = ({
account,
transaction,
onChange,
status,
trackProperties = {},
+ autoFocus,
}: SendAmountProps) => {
- const { t } = useTranslation();
const MEMO_MAX_LENGTH = 100;
const [memoLength, setMemoLength] = React.useState(0);
const bridge = getAccountBridge(account);
@@ -36,17 +35,11 @@ const MemoField = ({
);
if (!status) return null;
return (
-
-
-
- {/* memo character count */}
+ (
-
- {/* memo input */}
-
-
+ )}
+ autoFocus={autoFocus}
+ />
);
};
export default MemoField;
diff --git a/apps/ledger-live-desktop/src/renderer/families/hedera/SendAmountFields.tsx b/apps/ledger-live-desktop/src/renderer/families/hedera/SendRecipientFields.tsx
similarity index 100%
rename from apps/ledger-live-desktop/src/renderer/families/hedera/SendAmountFields.tsx
rename to apps/ledger-live-desktop/src/renderer/families/hedera/SendRecipientFields.tsx
diff --git a/apps/ledger-live-desktop/src/renderer/families/hedera/index.ts b/apps/ledger-live-desktop/src/renderer/families/hedera/index.ts
index 29ff3b21cf43..e96e064b89c6 100644
--- a/apps/ledger-live-desktop/src/renderer/families/hedera/index.ts
+++ b/apps/ledger-live-desktop/src/renderer/families/hedera/index.ts
@@ -1,13 +1,13 @@
import AccountSubHeader from "./AccountSubHeader";
import NoAssociatedAccounts from "./NoAssociatedAccounts";
-import sendAmountFields from "./SendAmountFields";
+import sendRecipientFields from "./SendRecipientFields";
import StepReceiveFunds from "./StepReceiveFunds";
import getTransactionExplorer from "./getTransactionExplorer";
import { HederaFamily } from "./types";
const family: HederaFamily = {
AccountSubHeader,
- sendAmountFields,
+ sendRecipientFields,
StepReceiveFunds,
NoAssociatedAccounts,
getTransactionExplorer,
diff --git a/apps/ledger-live-desktop/src/renderer/families/hedera/types.ts b/apps/ledger-live-desktop/src/renderer/families/hedera/types.ts
index b9d9b20c6ab0..d94767b5c091 100644
--- a/apps/ledger-live-desktop/src/renderer/families/hedera/types.ts
+++ b/apps/ledger-live-desktop/src/renderer/families/hedera/types.ts
@@ -11,4 +11,5 @@ export type SendAmountProps = {
status: TransactionStatus;
onChange: (a: Transaction) => void;
trackProperties?: object;
+ autoFocus?: boolean;
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/internet_computer/MemoField.tsx b/apps/ledger-live-desktop/src/renderer/families/internet_computer/MemoField.tsx
index 741b6672f19f..92552250656c 100644
--- a/apps/ledger-live-desktop/src/renderer/families/internet_computer/MemoField.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/internet_computer/MemoField.tsx
@@ -1,29 +1,28 @@
import React, { useCallback } from "react";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
-import Input from "~/renderer/components/Input";
import invariant from "invariant";
import { Account } from "@ledgerhq/types-live";
import {
Transaction,
TransactionStatus,
} from "@ledgerhq/live-common/families/internet_computer/types";
-import { useTranslation } from "react-i18next";
+import MemoTagField from "LLD/features/MemoTag/components/MemoTagField";
const MemoField = ({
onChange,
account,
transaction,
status,
+ autoFocus,
}: {
onChange: (a: Transaction) => void;
account: Account;
transaction: Transaction;
status: TransactionStatus;
+ autoFocus?: boolean;
}) => {
invariant(transaction.family === "internet_computer", "Memo: Internet Computer family expected");
- const { t } = useTranslation();
-
const bridge = getAccountBridge(account);
const onMemoFieldChange = useCallback(
@@ -37,13 +36,13 @@ const MemoField = ({
// We use transaction as an error here.
// on the ledger-live mobile
return (
-
);
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/internet_computer/SendAmountFields.tsx b/apps/ledger-live-desktop/src/renderer/families/internet_computer/SendAmountFields.tsx
index faf929bf6218..1121538918e8 100644
--- a/apps/ledger-live-desktop/src/renderer/families/internet_computer/SendAmountFields.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/internet_computer/SendAmountFields.tsx
@@ -1,9 +1,5 @@
import React from "react";
-import { Trans } from "react-i18next";
import MemoField from "./MemoField";
-import Box from "~/renderer/components/Box";
-import Label from "~/renderer/components/Label";
-import LabelInfoTooltip from "~/renderer/components/LabelInfoTooltip";
import {
Transaction,
TransactionStatus,
@@ -17,29 +13,7 @@ const Root = (props: {
onChange: (a: Transaction) => void;
trackProperties?: object;
}) => {
- return (
-
-
-
-
-
-
-
-
-
-
- );
+ return ;
};
export default {
diff --git a/apps/ledger-live-desktop/src/renderer/families/stacks/MemoValueField.tsx b/apps/ledger-live-desktop/src/renderer/families/stacks/MemoValueField.tsx
index d5f9ec5ea208..44bc821e9428 100644
--- a/apps/ledger-live-desktop/src/renderer/families/stacks/MemoValueField.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/stacks/MemoValueField.tsx
@@ -1,23 +1,21 @@
import React, { useCallback } from "react";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
-import Input from "~/renderer/components/Input";
import invariant from "invariant";
import { Account } from "@ledgerhq/types-live";
import { Transaction, TransactionStatus } from "@ledgerhq/live-common/families/stacks/types";
-import { useTranslation } from "react-i18next";
+import MemoTagField from "LLD/features/MemoTag/components/MemoTagField";
type Props = {
onChange: (t: Transaction) => void;
account: Account;
transaction: Transaction;
status: TransactionStatus;
+ autoFocus?: boolean;
};
-const MemoValueField = ({ onChange, account, transaction, status }: Props) => {
+const MemoValueField = ({ onChange, account, transaction, status, autoFocus }: Props) => {
invariant(transaction.family === "stacks", "MemoField: stacks family expected");
- const { t } = useTranslation();
-
const bridge = getAccountBridge(account);
const onMemoValueChange = useCallback(
@@ -31,12 +29,12 @@ const MemoValueField = ({ onChange, account, transaction, status }: Props) => {
// It will be usefull to block a memo wrong format
// on the ledger-live mobile
return (
-
);
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/stacks/SendRecipientFields.tsx b/apps/ledger-live-desktop/src/renderer/families/stacks/SendRecipientFields.tsx
index bd46841b9ddb..ee83c658452f 100644
--- a/apps/ledger-live-desktop/src/renderer/families/stacks/SendRecipientFields.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/stacks/SendRecipientFields.tsx
@@ -1,9 +1,6 @@
import React from "react";
-import { Trans } from "react-i18next";
import MemoValueField from "./MemoValueField";
import Box from "~/renderer/components/Box";
-import Label from "~/renderer/components/Label";
-import LabelInfoTooltip from "~/renderer/components/LabelInfoTooltip";
import { Account } from "@ledgerhq/types-live";
import { Transaction, TransactionStatus } from "@ledgerhq/live-common/families/stacks/types";
@@ -17,20 +14,7 @@ type Props = {
const Root = (props: Props) => {
return (
-
-
-
-
-
-
-
-
+
);
};
diff --git a/apps/ledger-live-desktop/src/renderer/families/xrp/SendRecipientFields.tsx b/apps/ledger-live-desktop/src/renderer/families/xrp/SendRecipientFields.tsx
index b198732eadb3..4ff917db0ec7 100644
--- a/apps/ledger-live-desktop/src/renderer/families/xrp/SendRecipientFields.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/xrp/SendRecipientFields.tsx
@@ -1,12 +1,9 @@
import React, { useCallback } from "react";
import { BigNumber } from "bignumber.js";
-import { Trans, useTranslation } from "react-i18next";
import { Account } from "@ledgerhq/types-live";
import { Transaction } from "@ledgerhq/live-common/families/xrp/types";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
-import Box from "~/renderer/components/Box";
-import Input from "~/renderer/components/Input";
-import Label from "~/renderer/components/Label";
+import MemoTagField from "LLD/features/MemoTag/components/MemoTagField";
type Props = {
onChange: (a: Transaction) => void;
transaction: Transaction;
@@ -14,7 +11,6 @@ type Props = {
};
const uint32maxPlus1 = BigNumber(2).pow(32);
const TagField = ({ onChange, account, transaction }: Props) => {
- const { t } = useTranslation();
const onChangeTag = useCallback(
(str: string) => {
const bridge = getAccountBridge(account);
@@ -35,23 +31,7 @@ const TagField = ({ onChange, account, transaction }: Props) => {
},
[onChange, account, transaction],
);
- return (
-
-
-
-
-
-
- );
+ return ;
};
export default {
component: TagField,
diff --git a/apps/ledger-live-desktop/src/renderer/icons/MemoIcon.tsx b/apps/ledger-live-desktop/src/renderer/icons/MemoIcon.tsx
new file mode 100644
index 000000000000..f0cd4e82b460
--- /dev/null
+++ b/apps/ledger-live-desktop/src/renderer/icons/MemoIcon.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+
+const MemoIcon = ({ size = 18, color = "currentColor" }: { size?: number; color?: string }) => (
+
+);
+
+export default MemoIcon;
diff --git a/apps/ledger-live-desktop/src/renderer/modals/Receive/steps/StepReceiveFunds.tsx b/apps/ledger-live-desktop/src/renderer/modals/Receive/steps/StepReceiveFunds.tsx
index 55f74e5fde7d..621a79cc3931 100644
--- a/apps/ledger-live-desktop/src/renderer/modals/Receive/steps/StepReceiveFunds.tsx
+++ b/apps/ledger-live-desktop/src/renderer/modals/Receive/steps/StepReceiveFunds.tsx
@@ -73,8 +73,7 @@ const Receive1ShareAddress = ({
const { currency } = account;
const isUTXOCompliantCurrency = isUTXOCompliant(currency.family);
- const shouldRenderMemoTagInfo =
- currency.explorerId && MEMO_TAG_COINS.includes(currency.explorerId);
+ const shouldRenderMemoTagInfo = currency.family && MEMO_TAG_COINS.includes(currency.family);
return (
<>
diff --git a/apps/ledger-live-desktop/src/renderer/modals/Send/SendRecipientFields.tsx b/apps/ledger-live-desktop/src/renderer/modals/Send/SendRecipientFields.tsx
index bcaa39effbd6..8ce587da21b6 100644
--- a/apps/ledger-live-desktop/src/renderer/modals/Send/SendRecipientFields.tsx
+++ b/apps/ledger-live-desktop/src/renderer/modals/Send/SendRecipientFields.tsx
@@ -9,6 +9,7 @@ type Props = {
transaction: Transaction;
status: TransactionStatus;
onChange: (a: Transaction) => void;
+ autoFocus?: boolean;
};
export const getFields = (account: Account): string[] => {
diff --git a/apps/ledger-live-desktop/src/renderer/modals/Send/index.tsx b/apps/ledger-live-desktop/src/renderer/modals/Send/index.tsx
index ea2d721c214a..91007fa5bfad 100644
--- a/apps/ledger-live-desktop/src/renderer/modals/Send/index.tsx
+++ b/apps/ledger-live-desktop/src/renderer/modals/Send/index.tsx
@@ -3,6 +3,8 @@ import { DomainServiceProvider } from "@ledgerhq/domain-service/hooks/index";
import Modal from "~/renderer/components/Modal";
import Body from "./Body";
import { StepId } from "./types";
+import { useDispatch } from "react-redux";
+import { setMemoTagInfoBoxDisplay } from "~/renderer/actions/UI";
type Props = {
stepId?: StepId;
@@ -23,13 +25,24 @@ const SendModal = ({ stepId: initialStepId, onClose }: Props) => {
const handleReset = useCallback(() => setStep("recipient"), []);
const handleStepChange = useCallback((stepId: StepId) => setStep(stepId), []);
const isModalLocked = MODAL_LOCKED[stepId as StepId];
+ const dispatch = useDispatch();
+
+ const handleModalClose = useCallback(() => {
+ dispatch(
+ setMemoTagInfoBoxDisplay({
+ isMemoTagBoxVisible: false,
+ forceAutoFocusOnMemoField: false,
+ }),
+ );
+ onClose?.();
+ }, [dispatch, onClose]);
return (
(
{
+ const isMemoTagBoxVisibile = useSelector(memoTagBoxVisibilitySelector);
+ const forceAutoFocusOnMemoField = useSelector(forceAutoFocusOnMemoFieldSelector);
+
if (!status || !account) return null;
+
const mainAccount = getMainAccount(account, parentAccount);
// check if there is a stuck transaction. If so, display a warning panel with "speed up or cancel" button
const stuckAccountAndOperation = getStuckAccountAndOperation(account, parentAccount);
@@ -47,67 +63,105 @@ const StepRecipient = ({
currencyName={currencyName}
isNFTSend={isNFTSend}
/>
- {mainAccount ? : null}
- {error ? : null}
- {isNFTSend ? (
-
-
- {account && (
-
- )}
-
+ {isMemoTagBoxVisibile ? (
+
) : (
-
-
-
-
- )}
- {stuckAccountAndOperation ? (
-
- ) : null}
-
- {account && transaction && mainAccount && (
<>
-
-
+ {mainAccount ? : null}
+ {error ? : null}
+ {isNFTSend ? (
+
+
+ {account && (
+
+ )}
+
+ ) : (
+
+
+
+
+ )}
+ {stuckAccountAndOperation ? (
+
+ ) : null}
+
+ {account && transaction && mainAccount && (
+ <>
+
+
+ >
+ )}
>
)}
);
};
-export class StepRecipientFooter extends PureComponent {
- onNext = async () => {
- const { transitionTo, shouldSkipAmount } = this.props;
+
+export const StepRecipientFooter = ({
+ t,
+ account,
+ parentAccount,
+ status,
+ bridgePending,
+ transitionTo,
+ shouldSkipAmount,
+ transaction,
+}: StepProps) => {
+ const dispatch = useDispatch();
+ const { errors } = status;
+ const mainAccount = account ? getMainAccount(account, parentAccount) : null;
+ const isTerminated = mainAccount && mainAccount.currency.terminated;
+ const fields = ["recipient"].concat(mainAccount ? getFields(mainAccount) : []);
+ const hasFieldError = Object.keys(errors).some(name => fields.includes(name));
+ const canNext = !bridgePending && !hasFieldError && !isTerminated;
+ const isMemoTagBoxVisibile = useSelector(memoTagBoxVisibilitySelector);
+ const alwaysShowMemoTagInfo = useSelector(alwaysShowMemoTagInfoSelector);
+
+ const handleOnNext = async () => {
+ if (
+ !transaction?.memo &&
+ MEMO_TAG_COINS.includes(transaction?.family as string) &&
+ alwaysShowMemoTagInfo
+ ) {
+ dispatch(
+ setMemoTagInfoBoxDisplay({
+ isMemoTagBoxVisible: true,
+ }),
+ );
+ return;
+ }
if (shouldSkipAmount) {
transitionTo("summary");
} else {
@@ -115,28 +169,60 @@ export class StepRecipientFooter extends PureComponent {
}
};
- render() {
- const { t, account, parentAccount, status, bridgePending } = this.props;
- const { errors } = status;
- const mainAccount = account ? getMainAccount(account, parentAccount) : null;
- const isTerminated = mainAccount && mainAccount.currency.terminated;
- const fields = ["recipient"].concat(mainAccount ? getFields(mainAccount) : []);
- const hasFieldError = Object.keys(errors).some(name => fields.includes(name));
- const canNext = !bridgePending && !hasFieldError && !isTerminated;
- return (
- <>
-