diff --git a/frontend/src/lib/components/accounts/LedgerNeuronHotkeyWarning.svelte b/frontend/src/lib/components/accounts/LedgerNeuronHotkeyWarning.svelte
new file mode 100644
index 00000000000..5f88f943728
--- /dev/null
+++ b/frontend/src/lib/components/accounts/LedgerNeuronHotkeyWarning.svelte
@@ -0,0 +1,35 @@
+
+
+
+ {#if isVisible}
+
+
+
+
+
+ {/if}
+
diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json
index a037d362fae..376ae424e75 100644
--- a/frontend/src/lib/i18n/en.json
+++ b/frontend/src/lib/i18n/en.json
@@ -393,7 +393,8 @@
"losing_rewards": {
"description": "ICP neurons that are inactive for $period start missing voting rewards. To avoid missing rewards, vote manually, edit, or confirm your following.",
"confirming": "Confirming following. This may take a moment.",
- "confirm": "Confirm Following"
+ "confirm": "Confirm Following",
+ "hw_hotkey_warning": "You may have neurons that are not added to the NNS dapp. If you want to view your neurons and get alerts in case they start missing voting rewards, you can add them by clicking Show Neurons > Add to NNS Dapp."
},
"losing_rewards_banner": {
"confirm_title": "Confirm following or vote manually to avoid missing rewards",
diff --git a/frontend/src/lib/pages/NnsWallet.svelte b/frontend/src/lib/pages/NnsWallet.svelte
index bad0f058f53..7cd7841afb3 100644
--- a/frontend/src/lib/pages/NnsWallet.svelte
+++ b/frontend/src/lib/pages/NnsWallet.svelte
@@ -76,6 +76,8 @@
} from "@dfinity/utils";
import { onDestroy, onMount, setContext } from "svelte";
import { writable, type Readable } from "svelte/store";
+ import { ENABLE_PERIODIC_FOLLOWING_CONFIRMATION } from "$lib/stores/feature-flags.store";
+ import LedgerNeuronHotkeyWarning from "$lib/components/accounts/LedgerNeuronHotkeyWarning.svelte";
$: if ($authSignedInStore) {
pollAccounts();
@@ -381,6 +383,10 @@
+ {#if $ENABLE_PERIODIC_FOLLOWING_CONFIRMATION && isHardwareWallet}
+
+ {/if}
+
{#if $selectedAccountStore.account !== undefined}
diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts
index 7fcf6fb5ea3..d133c8809cc 100644
--- a/frontend/src/lib/types/i18n.d.ts
+++ b/frontend/src/lib/types/i18n.d.ts
@@ -408,6 +408,7 @@ interface I18nLosing_rewards {
description: string;
confirming: string;
confirm: string;
+ hw_hotkey_warning: string;
}
interface I18nLosing_rewards_banner {
diff --git a/frontend/src/tests/lib/components/accounts/LedgerNeuronHotkeyWarning.spec.ts b/frontend/src/tests/lib/components/accounts/LedgerNeuronHotkeyWarning.spec.ts
new file mode 100644
index 00000000000..d0708051ba6
--- /dev/null
+++ b/frontend/src/tests/lib/components/accounts/LedgerNeuronHotkeyWarning.spec.ts
@@ -0,0 +1,44 @@
+import LedgerNeuronHotkeyWarning from "$lib/components/accounts/LedgerNeuronHotkeyWarning.svelte";
+import { LedgerNeuronHotkeyWarningPo } from "$tests/page-objects/LedgerNeuronHotkeyWarning.page-object";
+import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
+import { render } from "@testing-library/svelte";
+
+describe("LedgerNeuronHotkeyWarning", () => {
+ const localStorageKey = "isLedgerNeuronHotkeyWarningDisabled";
+ const renderComponent = () => {
+ const { container } = render(LedgerNeuronHotkeyWarning);
+ return LedgerNeuronHotkeyWarningPo.under(
+ new JestPageObjectElement(container)
+ );
+ };
+
+ beforeEach(() => {
+ localStorage.clear();
+ });
+
+ it("should be shown when localStorageKey not set", async () => {
+ const po = await renderComponent();
+ expect(localStorage.getItem(localStorageKey)).toBeNull();
+
+ expect(await po.isBannerVisible()).toBe(true);
+ });
+
+ it("should not render when localStorageKey is set to true", async () => {
+ localStorage.setItem(localStorageKey, "true");
+ const po = await renderComponent();
+
+ expect(await po.isBannerVisible()).toBe(false);
+ });
+
+ it("should be closable", async () => {
+ const po = await renderComponent();
+
+ expect(await po.isBannerVisible()).toBe(true);
+ expect(localStorage.getItem(localStorageKey)).toBeNull();
+
+ await po.getBannerPo().clickClose();
+
+ expect(await po.isBannerVisible()).toBe(false);
+ expect(localStorage.getItem(localStorageKey)).toEqual("true");
+ });
+});
diff --git a/frontend/src/tests/lib/pages/NnsWallet.spec.ts b/frontend/src/tests/lib/pages/NnsWallet.spec.ts
index e1e4c8795b8..d624cde4f94 100644
--- a/frontend/src/tests/lib/pages/NnsWallet.spec.ts
+++ b/frontend/src/tests/lib/pages/NnsWallet.spec.ts
@@ -14,6 +14,7 @@ import { AppPath } from "$lib/constants/routes.constants";
import { pageStore } from "$lib/derived/page.derived";
import NnsWallet from "$lib/pages/NnsWallet.svelte";
import { cancelPollAccounts } from "$lib/services/icp-accounts.services";
+import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store";
import { icpTransactionsStore } from "$lib/stores/icp-transactions.store";
import { neuronsStore } from "$lib/stores/neurons.store";
import { getSwapCanisterAccount } from "$lib/utils/sns.utils";
@@ -408,6 +409,17 @@ describe("NnsWallet", () => {
expect(await po.getWalletPageHeadingPo().getTitle()).toBe("4.32 ICP");
});
+ it("should not render Ledger neuron hotkey warning for not HW wallet", async () => {
+ overrideFeatureFlagsStore.setFlag(
+ "ENABLE_PERIODIC_FOLLOWING_CONFIRMATION",
+ true
+ );
+ const po = await renderWallet(props);
+ expect(await po.getLedgerNeuronHotkeyWarningPo().isBannerVisible()).toBe(
+ false
+ );
+ });
+
it("should reload balance on open", async () => {
const oldBalance = 135_000_000n;
const oldBalanceFormatted = "1.35 ICP";
@@ -1008,6 +1020,30 @@ describe("NnsWallet", () => {
expect(await po.getShowHardwareWalletButtonPo().isPresent()).toBe(true);
});
+ it("should display Ledger neuron hotkey warning", async () => {
+ overrideFeatureFlagsStore.setFlag(
+ "ENABLE_PERIODIC_FOLLOWING_CONFIRMATION",
+ true
+ );
+ const po = await renderWallet(props);
+
+ expect(await po.getLedgerNeuronHotkeyWarningPo().isBannerVisible()).toBe(
+ true
+ );
+ });
+
+ it("should not display Ledger neuron hotkey warning when feature flag off", async () => {
+ overrideFeatureFlagsStore.setFlag(
+ "ENABLE_PERIODIC_FOLLOWING_CONFIRMATION",
+ false
+ );
+ const po = await renderWallet(props);
+
+ expect(await po.getLedgerNeuronHotkeyWarningPo().isBannerVisible()).toBe(
+ false
+ );
+ });
+
describe("when there are staking transactions", () => {
const neuronController = testHwPrincipal;
const memo = 54321n;
diff --git a/frontend/src/tests/page-objects/LedgerNeuronHotkeyWarning.page-object.ts b/frontend/src/tests/page-objects/LedgerNeuronHotkeyWarning.page-object.ts
new file mode 100644
index 00000000000..38de152b758
--- /dev/null
+++ b/frontend/src/tests/page-objects/LedgerNeuronHotkeyWarning.page-object.ts
@@ -0,0 +1,21 @@
+import { BasePageObject } from "$tests/page-objects/base.page-object";
+import type { PageObjectElement } from "$tests/types/page-object.types";
+import { BannerPo } from "./Banner.page-object";
+
+export class LedgerNeuronHotkeyWarningPo extends BasePageObject {
+ private static readonly TID = "ledger-neuron-hotkey-warning-component";
+
+ static under(element: PageObjectElement): LedgerNeuronHotkeyWarningPo {
+ return new LedgerNeuronHotkeyWarningPo(
+ element.byTestId(LedgerNeuronHotkeyWarningPo.TID)
+ );
+ }
+
+ getBannerPo(): BannerPo {
+ return BannerPo.under(this.root);
+ }
+
+ async isBannerVisible(): Promise {
+ return this.getBannerPo().isPresent();
+ }
+}
diff --git a/frontend/src/tests/page-objects/NnsWallet.page-object.ts b/frontend/src/tests/page-objects/NnsWallet.page-object.ts
index 72f83015bf1..4747e37e625 100644
--- a/frontend/src/tests/page-objects/NnsWallet.page-object.ts
+++ b/frontend/src/tests/page-objects/NnsWallet.page-object.ts
@@ -6,6 +6,7 @@ import { WalletPageHeaderPo } from "$tests/page-objects/WalletPageHeader.page-ob
import { WalletPageHeadingPo } from "$tests/page-objects/WalletPageHeading.page-object";
import { BasePageObject } from "$tests/page-objects/base.page-object";
import type { PageObjectElement } from "$tests/types/page-object.types";
+import { LedgerNeuronHotkeyWarningPo } from "./LedgerNeuronHotkeyWarning.page-object";
import { UiTransactionsListPo } from "./UiTransactionsList.page-object";
export class NnsWalletPo extends BasePageObject {
@@ -51,6 +52,10 @@ export class NnsWalletPo extends BasePageObject {
return this.getButton("ledger-list-button");
}
+ getLedgerNeuronHotkeyWarningPo(): LedgerNeuronHotkeyWarningPo {
+ return LedgerNeuronHotkeyWarningPo.under(this.root);
+ }
+
getShowHardwareWalletButtonPo(): ButtonPo {
return this.getButton("ledger-show-button");
}