From 55bce2dd4a8446dd4b1d7a3cf4f93c7309a6ee40 Mon Sep 17 00:00:00 2001 From: George Mamoulasvili <22020168+goga-m@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:10:47 +0100 Subject: [PATCH 1/2] feat: add support for multiple addresses (#911) --- .github/workflows/test.yml | 6 +- src/app/assets/svg/arrows/double-chevron.svg | 4 + src/app/assets/svg/arrows/index.ts | 2 + .../contexts/Configuration/Configuration.tsx | 1 + src/app/i18n/common/i18n.ts | 4 + .../dashboard/pages/Dashboard/Dashboard.tsx | 50 ++- .../PortfolioHeader.blocks.tsx | 43 +++ .../PortfolioHeader/PortfolioHeader.tsx | 310 +++++++++++++++ .../components/PortfolioHeader/index.tsx | 1 + src/domains/portfolio/hooks/use-portfolio.ts | 131 +++++++ src/domains/wallet/i18n.ts | 1 + .../pages/CreateWallet/CreateWallet.test.tsx | 8 +- .../pages/CreateWallet/CreateWallet.tsx | 2 +- .../EncryptionPasswordStep.test.tsx | 6 +- .../pages/ImportWallet/ImportWallet.test.tsx | 2 +- .../pages/ImportWallet/ImportWallet.tsx | 2 +- .../WalletDetails.Actions.test.tsx | 156 -------- .../AddressesSidePanel/AddressRow.tsx | 3 + .../AddressesSidePanel.test.tsx | 4 +- .../AddressesSidePanel/AddressesSidePanel.tsx | 12 +- .../components/WalletVote/WalletVote.test.tsx | 91 ++++- .../components/WalletVote/WalletVote.tsx | 108 ++++-- .../__snapshots__/WalletVote.test.tsx.snap | 358 +----------------- 23 files changed, 708 insertions(+), 597 deletions(-) create mode 100644 src/app/assets/svg/arrows/double-chevron.svg create mode 100644 src/domains/portfolio/components/PortfolioHeader/PortfolioHeader.blocks.tsx create mode 100644 src/domains/portfolio/components/PortfolioHeader/PortfolioHeader.tsx create mode 100644 src/domains/portfolio/components/PortfolioHeader/index.tsx create mode 100644 src/domains/portfolio/hooks/use-portfolio.ts delete mode 100755 src/domains/wallet/pages/WalletDetails/WalletDetails.Actions.test.tsx diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aeb5219d27..05d87828c3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -855,9 +855,9 @@ jobs: - name: Test uses: nick-invision/retry@v2 env: - COVERAGE_THRESHOLD_LINES: 96.64 - COVERAGE_THRESHOLD_FUNCTIONS: 98.02 - COVERAGE_THRESHOLD_STATEMENTS: 96.68 + COVERAGE_THRESHOLD_LINES: 96.49 + COVERAGE_THRESHOLD_FUNCTIONS: 97.51 + COVERAGE_THRESHOLD_STATEMENTS: 96.54 COVERAGE_THRESHOLD_BRANCHES: 92.27 COVERAGE_INCLUDE_PATH: src/domains/wallet with: diff --git a/src/app/assets/svg/arrows/double-chevron.svg b/src/app/assets/svg/arrows/double-chevron.svg new file mode 100644 index 0000000000..f42edd7670 --- /dev/null +++ b/src/app/assets/svg/arrows/double-chevron.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/app/assets/svg/arrows/index.ts b/src/app/assets/svg/arrows/index.ts index d8b80e6160..c62dafea6a 100644 --- a/src/app/assets/svg/arrows/index.ts +++ b/src/app/assets/svg/arrows/index.ts @@ -24,6 +24,7 @@ import DoubleChevronLeft from "./double-chevron-left.svg?react"; import DoubleChevronLeftSmall from "./double-chevron-left-small.svg?react"; import DoubleChevronRight from "./double-chevron-right.svg?react"; import DoubleChevronRightSmall from "./double-chevron-right-small.svg?react"; +import DoubleChevron from "./double-chevron.svg?react"; export const ArrowIcons: any = { ArrowDown, @@ -46,6 +47,7 @@ export const ArrowIcons: any = { ChevronUpSmall, DoubleArrowDashed, DoubleArrowRight, + DoubleChevron, DoubleChevronLeft, DoubleChevronLeftSmall, DoubleChevronRight, diff --git a/src/app/contexts/Configuration/Configuration.tsx b/src/app/contexts/Configuration/Configuration.tsx index 12de510eb0..3029deb391 100644 --- a/src/app/contexts/Configuration/Configuration.tsx +++ b/src/app/contexts/Configuration/Configuration.tsx @@ -50,6 +50,7 @@ export const ConfigurationProvider = ({ children, defaultConfiguration }: Proper profileIsSyncingWallets: false, restoredProfiles: [], + selectedAddresses: [], serverStatus: defaultServerStatus(), ...defaultConfiguration, }); diff --git a/src/app/i18n/common/i18n.ts b/src/app/i18n/common/i18n.ts index 54f58750e2..018ed8a959 100644 --- a/src/app/i18n/common/i18n.ts +++ b/src/app/i18n/common/i18n.ts @@ -26,6 +26,7 @@ export const translations = { APP_NAME: "ARKVault", ARK: "ARK", ARKVAULT: "ARK Vault", + ARK_BALANCE: "Ark Balance", ASSET: "Asset", ASSETS: "Assets", ASSET_TYPE: "Asset Type", @@ -245,9 +246,11 @@ export const translations = { MOST_POPULAR: "Most popular", MULTIPAYMENTS: "Multipayments", MULTIPLE: "Multiple", + MULTIPLE_ADDRESSES: "Multiple addresses ({{count}})", MULTISIG: "Multisig", MULTISIGNATURE: "Multisignature", MY_ADDRESS: "My Address", + MY_VOTES: "My Votes", MY_WALLET: "My Wallet", NAME: "Name", NETHASH: "Nethash", @@ -385,6 +388,7 @@ export const translations = { SUBMIT: "Submit", SUCCESS: "Success", SUPPORT: "Support", + SWITCH_TO_SINGLE_VIEW: "Switch to single address view for options", SYMBOL: "Symbol", TEST_NETWORK: "Test Network", THEME: "Theme", diff --git a/src/domains/dashboard/pages/Dashboard/Dashboard.tsx b/src/domains/dashboard/pages/Dashboard/Dashboard.tsx index 1100df9fc6..54b1605f51 100755 --- a/src/domains/dashboard/pages/Dashboard/Dashboard.tsx +++ b/src/domains/dashboard/pages/Dashboard/Dashboard.tsx @@ -11,9 +11,10 @@ import { TransactionDetailModal } from "@/domains/transaction/components/Transac import { Transactions } from "@/domains/transaction/components/Transactions"; import { Tab, TabList, Tabs, TabScroll } from "@/app/components/Tabs"; import { TabId } from "@/app/components/Tabs/useTab"; -import { WalletHeader } from "@/domains/wallet/pages/WalletDetails/components/WalletHeader"; import { WalletVote } from "@/domains/wallet/pages/WalletDetails/components"; import { DashboardEmpty } from "./Dashboard.Empty"; +import { PortfolioHeader } from "@/domains/portfolio/components/PortfolioHeader"; +import { usePortfolio } from "@/domains/portfolio/hooks/use-portfolio"; export const Dashboard = () => { const [transactionModalItem, setTransactionModalItem] = useState(); @@ -26,40 +27,47 @@ export const Dashboard = () => { const { env } = useEnvironmentContext(); const activeProfile = useActiveProfile(); - const activeWallet = activeProfile.wallets().first() as Contracts.IReadWriteWallet | undefined; const { profileIsSyncing } = useConfiguration(); - const networkAllowsVoting = useMemo(() => activeWallet?.network().allowsVoting(), [activeWallet]); + const { selectedWallets, selectedWallet } = usePortfolio({ profile: activeProfile }); const handleVoteButton = (filter?: string) => { + if (selectedWallets.length > 1) { + return history.push({ + pathname: `/profiles/${activeProfile.id()}/votes`, + }); + } + + const wallet = selectedWallets.at(0); /* istanbul ignore else -- @preserve */ if (filter) { return history.push({ - pathname: `/profiles/${activeProfile.id()}/wallets/${activeWallet?.id()}/votes`, + pathname: `/profiles/${activeProfile.id()}/wallets/${wallet?.id()}/votes`, search: `?filter=${filter}`, }); } - history.push(`/profiles/${activeProfile.id()}/wallets/${activeWallet?.id()}/votes`); + history.push(`/profiles/${activeProfile.id()}/wallets/${wallet?.id()}/votes`); }; const [mobileActiveTab, setMobileActiveTab] = useState("transactions"); - const [isLoadingVotes, setIsLoadingVotes] = useState(true); const [votes, setVotes] = useState([]); + const networkAllowsVoting = useMemo(() => selectedWallet?.network().allowsVoting(), [selectedWallet]); useEffect(() => { - const syncVotes = async () => { + const syncVotes = async (wallet) => { try { - if (!activeWallet) { + if (!wallet) { return; } + setIsLoadingVotes(true); - await env.delegates().sync(activeProfile, activeWallet.coinId(), activeWallet.networkId()); - await activeWallet.synchroniser().votes(); + await env.delegates().sync(activeProfile, wallet.coinId(), wallet.networkId()); + await wallet.synchroniser().votes(); - setVotes(activeWallet.voting().current()); + setVotes(wallet.voting().current()); } catch { // TODO: Retry sync if error code is greater than 499. Needs status code number from sdk. } @@ -67,8 +75,13 @@ export const Dashboard = () => { setIsLoadingVotes(false); }; - syncVotes(); - }, [activeWallet, env, activeProfile]); + if (!selectedWallet) { + setIsLoadingVotes(false); + return; + } + + syncVotes(selectedWallet); + }, [selectedWallet, env, activeProfile]); useEffect(() => { if (!isUpdatingTransactions) { @@ -76,7 +89,7 @@ export const Dashboard = () => { } }, [isUpdatingTransactions]); - if (!activeWallet) { + if (activeProfile.wallets().count() === 0) { if (activeProfile.status().isRestored() && !profileIsSyncing) { return ( @@ -89,14 +102,13 @@ export const Dashboard = () => { return ( <> - +
- {
@@ -147,7 +159,7 @@ export const Dashboard = () => { > { + const { t } = useTranslation(); + const { getWalletAlias } = useWalletAlias(); + const firstWallet = wallets.at(0); + + if (wallets.length === 1 && firstWallet) { + const { alias } = getWalletAlias({ + address: firstWallet.address(), + network: firstWallet.network(), + profile, + }); + + return ( +
+ ); + } + + return ( +
+ {t("COMMON.MULTIPLE_ADDRESSES", { + count: wallets.length, + })} +
+ ); +}; diff --git a/src/domains/portfolio/components/PortfolioHeader/PortfolioHeader.tsx b/src/domains/portfolio/components/PortfolioHeader/PortfolioHeader.tsx new file mode 100644 index 0000000000..ab39c10e9e --- /dev/null +++ b/src/domains/portfolio/components/PortfolioHeader/PortfolioHeader.tsx @@ -0,0 +1,310 @@ +import React, { useState } from "react"; +import { Address } from "@/app/components/Address"; +import { Button } from "@/app/components/Button"; +import { Divider } from "@/app/components/Divider"; +import { Icon } from "@/app/components/Icon"; +import { useWalletActions } from "@/domains/wallet/hooks"; +import { Contracts } from "@ardenthq/sdk-profiles"; +import { useWalletOptions } from "@/domains/wallet/pages/WalletDetails/hooks/use-wallet-options"; +import { Dropdown } from "@/app/components/Dropdown"; +import { t } from "i18next"; +import { Amount } from "@/app/components/Amount"; +import { useExchangeRate } from "@/app/hooks/use-exchange-rate"; +import { WalletIcons } from "@/app/components/WalletIcons"; +import { Copy } from "@/app/components/Copy"; +import { WalletVote } from "@/domains/wallet/pages/WalletDetails/components/WalletVote/WalletVote"; +import { WalletActions } from "@/domains/wallet/pages/WalletDetails/components/WalletHeader/WalletHeader.blocks"; +import { Skeleton } from "@/app/components/Skeleton"; +import { AddressesSidePanel } from "@/domains/wallet/pages/WalletDetails/components/AddressesSidePanel"; +import { ViewingAddressInfo } from "./PortfolioHeader.blocks"; +import { Tooltip } from "@/app/components/Tooltip"; +import { assertWallet } from "@/utils/assertions"; +import { usePortfolio } from "@/domains/portfolio/hooks/use-portfolio"; + +export const PortfolioHeader = ({ + profile, + votes, + isLoadingVotes, + isUpdatingTransactions, + handleVotesButtonClick, + onUpdate, +}: { + profile: Contracts.IProfile; + votes: Contracts.VoteRegistryItem[]; + isLoadingVotes: boolean; + isUpdatingTransactions: boolean; + handleVotesButtonClick: (address?: string) => void; + onUpdate?: (status: boolean) => void; +}) => { + const [showAddressesPanel, setShowAddressesPanel] = useState(false); + + const { balance, setSelectedAddresses, selectedAddresses, selectedWallets } = usePortfolio({ profile }); + + const wallet = selectedWallets.at(0); + assertWallet(wallet); + + const isRestored = wallet.hasBeenFullyRestored(); + const { convert } = useExchangeRate({ exchangeTicker: wallet.exchangeCurrency(), ticker: wallet.currency() }); + const { handleImport, handleCreate, handleSelectOption, handleSend } = useWalletActions(wallet); + const { primaryOptions, secondaryOptions, additionalOptions, registrationOptions } = useWalletOptions(wallet); + + return ( +
+
+
+
+

+ {t("COMMON.VIEWING")}: +

+
setShowAddressesPanel(true)} + tabIndex={0} + onKeyPress={() => setShowAddressesPanel(true)} + className="cursor-pointer" + data-testid="ShowAddressesPanel" + > +
+ + +
+
+
+
+ + + +
+
+ +
+
+
+ {selectedWallets.length === 1 && ( +
+

+ {t("COMMON.ADDRESS")} +

+
+
+
+ +
+ )} + + {selectedWallets.length > 1 && ( +
+

+ {t("COMMON.ARK_BALANCE")} +

+
+ +
+
+ )} + +
+ {selectedWallets.length === 1 && ( + <> +
+ + isCopied ? : + } + /> + + {!!wallet.publicKey() && ( + } + /> + )} +
+ + + + )} + +
+ +
+
+
+ +
+
+
+

{t("COMMON.TOTAL_BALANCE")}

+ + +
+ +
+ {isRestored && selectedWallets.length === 1 && ( + + )} + {!isRestored && ( + + )} + {selectedWallets.length === 1 && ( + + )} + {isRestored && ( + + )} + {!isRestored && } +
+
+ +
+ {selectedWallets.length === 1 && ( + + )} + + {selectedWallets.length > 1 && ( + + )} + +
+ + + + + + } + onSelect={handleSelectOption} + /> +
+
+
+
+ +
+ +
+
+
+ + { + setSelectedAddresses(addresses); + }} + open={showAddressesPanel} + onOpenChange={setShowAddressesPanel} + onDeleteAddress={(address: string) => { + const wallets = profile.wallets().filterByAddress(address); + profile.wallets().forget(wallets[0].id()); + }} + /> +
+ ); +}; diff --git a/src/domains/portfolio/components/PortfolioHeader/index.tsx b/src/domains/portfolio/components/PortfolioHeader/index.tsx new file mode 100644 index 0000000000..e284e6782f --- /dev/null +++ b/src/domains/portfolio/components/PortfolioHeader/index.tsx @@ -0,0 +1 @@ +export * from "./PortfolioHeader"; diff --git a/src/domains/portfolio/hooks/use-portfolio.ts b/src/domains/portfolio/hooks/use-portfolio.ts new file mode 100644 index 0000000000..ec49ead736 --- /dev/null +++ b/src/domains/portfolio/hooks/use-portfolio.ts @@ -0,0 +1,131 @@ +import { BigNumber } from "@ardenthq/sdk-helpers"; +import { Contracts, Environment } from "@ardenthq/sdk-profiles"; +import { IProfile } from "@ardenthq/sdk-profiles/distribution/esm/profile.contract"; +import { IReadWriteWallet } from "@ardenthq/sdk-profiles/distribution/esm/wallet.contract"; +import { useConfiguration, useEnvironmentContext } from "@/app/contexts"; +import { useEffect } from "react"; + +interface PortfolioConfiguration { + selectedAddresses: string[]; +} + +function Balance({ wallets }: { wallets: IReadWriteWallet[] }) { + return { + total(): BigNumber { + let balance = BigNumber.make(0); + for (const wallet of wallets) { + balance = balance.plus(wallet.balance()); + } + + return balance; + }, + totalConverted(): BigNumber { + let balance = BigNumber.make(0); + for (const wallet of wallets) { + balance = balance.plus(wallet.convertedBalance()); + } + + return balance; + }, + }; +} + +function SelectedAddresses({ profile, env }: { profile: IProfile; env: Environment }) { + return { + /** + * Returns all the selected profile selected addresses. + * + * @returns {string[]} + */ + all(): string[] { + const config = profile.settings().get(Contracts.ProfileSetting.DashboardConfiguration, { + selectedAddresses: [], + }) as PortfolioConfiguration; + + return config.selectedAddresses ?? []; + }, + /** + * Find the default selected wallet. + * Returns the first available wallet if profile hasn't stored any selection yet. + * Otherwise returns the first selection. + * + * @returns {IReadWriteWallet | undefined} + */ + defaultSelectedWallet(): IReadWriteWallet | undefined { + if (profile.wallets().count() === 1) { + return profile.wallets().first(); + } + + if (this.all().length === 0) { + return profile.wallets().first(); + } + + return this.toWallets().at(0); + }, + /** + * Determines whether the profile has a selected address. + * + * @returns {boolean} + */ + hasSelected(): boolean { + return this.all().length > 0; + }, + /** + * Sets a new address and persists the change. + * + * @param {string[]} selectedAddresses + * @returns {Promise} + */ + async set(selectedAddresses: string[]): Promise { + profile.settings().set(Contracts.ProfileSetting.DashboardConfiguration, { selectedAddresses }); + await env.persist(); + }, + /** + * Returns the selected addresses as wallets. + * + * @returns {IReadWriteWallet[]} + */ + toWallets(): IReadWriteWallet[] { + const selected = this.all(); + + const wallets = profile + .wallets() + .values() + .filter((wallet) => selected.includes(wallet.address())); + + if (wallets.length === 0) { + // TODO: Define default active wallet none are selected. + return [profile.wallets().first()]; + } + + return wallets; + }, + }; +} + +export const usePortfolio = ({ profile }: { profile: Contracts.IProfile }) => { + const { env } = useEnvironmentContext(); + const { selectedAddresses, setConfiguration } = useConfiguration(); + + const addresses = SelectedAddresses({ env, profile }); + const wallets = addresses.toWallets(); + const balance = Balance({ wallets }); + const allAddresses = addresses.all(); + + useEffect(() => { + if (selectedAddresses.length === 0) { + setConfiguration({ selectedAddresses: allAddresses }); + } + }, [selectedAddresses, allAddresses]); + + return { + balance, + selectedAddresses, + selectedWallet: addresses.defaultSelectedWallet(), + selectedWallets: wallets, + setSelectedAddresses: async (selectedAddresses: string[]) => { + await addresses.set(selectedAddresses); + setConfiguration({ selectedAddresses }); + }, + }; +}; diff --git a/src/domains/wallet/i18n.ts b/src/domains/wallet/i18n.ts index 2299ef54da..e1d3cd56ef 100644 --- a/src/domains/wallet/i18n.ts +++ b/src/domains/wallet/i18n.ts @@ -205,6 +205,7 @@ export const translations = { ADDITIONAL_OPTIONS: "Additional Options", COPY_ADDRESS: "Copy Address", COPY_PUBLIC_KEY: "Copy Public Key", + MANAGE_VOTES_FOR_YOUR_ADDRESSES: "Manage votes for your addresses", OPTIONS: { ADDRESS_NAME: "Address Name", DELETE: "Delete", diff --git a/src/domains/wallet/pages/CreateWallet/CreateWallet.test.tsx b/src/domains/wallet/pages/CreateWallet/CreateWallet.test.tsx index 60d9e8e0a9..853e0ae72b 100644 --- a/src/domains/wallet/pages/CreateWallet/CreateWallet.test.tsx +++ b/src/domains/wallet/pages/CreateWallet/CreateWallet.test.tsx @@ -167,9 +167,7 @@ describe("CreateWallet", () => { expect(wallet.alias()).toBe("test alias"); - await waitFor(() => - expect(historySpy).toHaveBeenCalledWith(`/profiles/${profile.id()}/wallets/${wallet.id()}`), - ); + await waitFor(() => expect(historySpy).toHaveBeenCalledWith(`/profiles/${profile.id()}/dashboard`)); expect(asFragment()).toMatchSnapshot(); @@ -334,9 +332,7 @@ describe("CreateWallet", () => { expect(wallet.alias()).toBe("ARK Devnet #1"); - await waitFor(() => - expect(historySpy).toHaveBeenCalledWith(`/profiles/${profile.id()}/wallets/${wallet.id()}`), - ); + await waitFor(() => expect(historySpy).toHaveBeenCalledWith(`/profiles/${profile.id()}/dashboard`)); historySpy.mockRestore(); }); diff --git a/src/domains/wallet/pages/CreateWallet/CreateWallet.tsx b/src/domains/wallet/pages/CreateWallet/CreateWallet.tsx index 47827cb51e..e711b0459f 100644 --- a/src/domains/wallet/pages/CreateWallet/CreateWallet.tsx +++ b/src/domains/wallet/pages/CreateWallet/CreateWallet.tsx @@ -83,7 +83,7 @@ export const CreateWallet = () => { assertWallet(wallet); - history.push(`/profiles/${activeProfile.id()}/wallets/${wallet.id()}`); + history.push(`/profiles/${activeProfile.id()}/dashboard`); }; const generateWallet = () => { diff --git a/src/domains/wallet/pages/CreateWallet/EncryptionPasswordStep.test.tsx b/src/domains/wallet/pages/CreateWallet/EncryptionPasswordStep.test.tsx index 13a83f4502..510ee0dc5d 100644 --- a/src/domains/wallet/pages/CreateWallet/EncryptionPasswordStep.test.tsx +++ b/src/domains/wallet/pages/CreateWallet/EncryptionPasswordStep.test.tsx @@ -285,11 +285,7 @@ describe("EncryptionPasswordStep", () => { await userEvent.click(screen.getByTestId("CreateWallet__finish-button")); - const walletId = profile.wallets().first().id(); - - await waitFor(() => - expect(historySpy).toHaveBeenCalledWith(`/profiles/${fixtureProfileId}/wallets/${walletId}`), - ); + await waitFor(() => expect(historySpy).toHaveBeenCalledWith(`/profiles/${fixtureProfileId}/dashboard`)); historySpy.mockRestore(); }); diff --git a/src/domains/wallet/pages/ImportWallet/ImportWallet.test.tsx b/src/domains/wallet/pages/ImportWallet/ImportWallet.test.tsx index 9abe29a2df..c9b1639e22 100755 --- a/src/domains/wallet/pages/ImportWallet/ImportWallet.test.tsx +++ b/src/domains/wallet/pages/ImportWallet/ImportWallet.test.tsx @@ -435,7 +435,7 @@ describe("ImportWallet", () => { await userEvent.click(finishButton()); await waitFor(() => { - expect(historySpy).toHaveBeenCalledWith(expect.stringContaining(`/profiles/${profile.id()}/wallets/`)); + expect(historySpy).toHaveBeenCalledWith(expect.stringContaining(`/profiles/${profile.id()}/dashboard`)); }); historySpy.mockRestore(); diff --git a/src/domains/wallet/pages/ImportWallet/ImportWallet.tsx b/src/domains/wallet/pages/ImportWallet/ImportWallet.tsx index a371af3321..db80b54a6c 100755 --- a/src/domains/wallet/pages/ImportWallet/ImportWallet.tsx +++ b/src/domains/wallet/pages/ImportWallet/ImportWallet.tsx @@ -237,7 +237,7 @@ export const ImportWallet = () => { const handleFinish = () => { assertWallet(importedWallet); - history.push(`/profiles/${activeProfile.id()}/wallets/${importedWallet.id()}`); + history.push(`/profiles/${activeProfile.id()}/dashboard`); }; const isNextDisabled = useMemo(() => { diff --git a/src/domains/wallet/pages/WalletDetails/WalletDetails.Actions.test.tsx b/src/domains/wallet/pages/WalletDetails/WalletDetails.Actions.test.tsx deleted file mode 100755 index 891a16ae50..0000000000 --- a/src/domains/wallet/pages/WalletDetails/WalletDetails.Actions.test.tsx +++ /dev/null @@ -1,156 +0,0 @@ -/* eslint-disable @typescript-eslint/require-await */ -import { Contracts } from "@ardenthq/sdk-profiles"; -import userEvent from "@testing-library/user-event"; -import { createHashHistory } from "history"; -import React from "react"; -import { Route } from "react-router-dom"; - -import { WalletDetails } from "./WalletDetails"; -import { buildTranslations } from "@/app/i18n/helpers"; -import walletMock from "@/tests/fixtures/coins/ark/devnet/wallets/D8rr7B1d6TL6pf14LgMz4sKp1VBMs6YUYD.json"; -import { - env, - getDefaultProfileId, - MNEMONICS, - render, - RenderResult, - screen, - syncDelegates, - waitFor, -} from "@/utils/testing-library"; -import { server, requestMock } from "@/tests/mocks/server"; - -const translations = buildTranslations(); - -const history = createHashHistory(); -let walletUrl: string; - -let profile: Contracts.IProfile; -let wallet: Contracts.IReadWriteWallet; -let unvotedWallet: Contracts.IReadWriteWallet; - -const renderPage = async ({ waitForTopSection = true } = {}) => { - const utils: RenderResult = render( - - , - , - { - history, - route: walletUrl, - }, - ); - - if (waitForTopSection) { - await expect(screen.findAllByTestId("WalletVote")).resolves.toHaveLength(2); - } - - return utils; -}; - -describe("WalletDetails", () => { - beforeAll(async () => { - profile = env.profiles().findById(getDefaultProfileId()); - - await env.profiles().restore(profile); - await profile.sync(); - - wallet = profile.wallets().findById("ac38fe6d-4b67-4ef1-85be-17c5f6841129"); - - unvotedWallet = await profile.walletFactory().fromMnemonicWithBIP39({ - coin: "ARK", - mnemonic: MNEMONICS[0], - network: "ark.devnet", - }); - - profile.wallets().push(unvotedWallet); - - await syncDelegates(profile); - await wallet.synchroniser().identity(); - await wallet.synchroniser().coin(); - - vi.spyOn(wallet.transaction(), "sync").mockResolvedValue(void 0); - }); - - beforeEach(async () => { - server.use( - requestMock(`https://ark-test.arkvault.io/api/wallets/${unvotedWallet.address()}`, walletMock), - requestMock("https://ark-test-musig.arkvault.io", undefined, { method: "post" }), - ); - - walletUrl = `/profiles/${profile.id()}/wallets/${wallet.id()}`; - history.push(walletUrl); - }); - - it("should navigate to send transfer", async () => { - await renderPage({ waitForTopSection: true }); - - const historySpy = vi.spyOn(history, "push").mockImplementation(vi.fn()); - - await expect(screen.findByTestId("WalletHeader__send-button")).resolves.toBeVisible(); - await waitFor(() => expect(screen.getByTestId("WalletHeader__send-button")).toBeEnabled()); - - await userEvent.click(screen.getByTestId("WalletHeader__send-button")); - - await waitFor(() => { - expect(historySpy).toHaveBeenCalledWith(`/profiles/${profile.id()}/wallets/${wallet.id()}/send-transfer`); - }); - - historySpy.mockRestore(); - }); - - it("should navigate to votes page when clicking on WalletVote button", async () => { - await profile.sync(); - - const walletSpy = vi.spyOn(wallet.voting(), "current").mockReturnValue([]); - const historySpy = vi.spyOn(history, "push"); - - await renderPage(); - - await expect(screen.findAllByText(translations.COMMON.LEARN_MORE)).resolves.toHaveLength(2); - await waitFor(() => expect(screen.queryAllByTestId("WalletVote__button")).toHaveLength(2)); - await waitFor(() => expect(screen.queryAllByTestId("WalletVote__button")[0]).toBeEnabled()); - - await userEvent.click(screen.queryAllByTestId("WalletVote__button")[0]); - - await waitFor(() => { - expect(historySpy).toHaveBeenCalledWith(`/profiles/${profile.id()}/wallets/${wallet.id()}/votes`); - }); - - walletSpy.mockRestore(); - historySpy.mockRestore(); - }); - - // @TODO: Enable & refactor tests once mainsail coin support will be completed. - // See https://app.clickup.com/t/86dvbvrvf - it.skip("should manually sync wallet data", async () => { - await renderPage(); - - await userEvent.click(screen.getByTestId("WalletHeader__refresh")); - await waitFor(() => expect(screen.getByTestId("WalletHeader__refresh")).toHaveAttribute("aria-busy", "true")); - }); - - it("should not fail if the votes have not yet been synchronized", async () => { - const newWallet = await profile.walletFactory().fromMnemonicWithBIP39({ - coin: "ARK", - mnemonic: MNEMONICS[2], - network: "ark.devnet", - }); - - server.use(requestMock(`https://ark-test.arkvault.io/api/wallets/${newWallet.address()}`, walletMock)); - - profile.wallets().push(newWallet); - - await newWallet.synchroniser().identity(); - - const syncVotesSpy = vi.spyOn(newWallet.synchroniser(), "votes").mockImplementation(vi.fn()); - - walletUrl = `/profiles/${profile.id()}/wallets/${newWallet.id()}`; - history.push(walletUrl); - - await renderPage(); - - await expect(screen.findAllByText(translations.COMMON.LEARN_MORE)).resolves.toHaveLength(2); - - syncVotesSpy.mockRestore(); - }); -}); diff --git a/src/domains/wallet/pages/WalletDetails/components/AddressesSidePanel/AddressRow.tsx b/src/domains/wallet/pages/WalletDetails/components/AddressesSidePanel/AddressRow.tsx index 424e931289..012b857e14 100644 --- a/src/domains/wallet/pages/WalletDetails/components/AddressesSidePanel/AddressRow.tsx +++ b/src/domains/wallet/pages/WalletDetails/components/AddressesSidePanel/AddressRow.tsx @@ -15,7 +15,9 @@ export const AddressRow = ({ isSelected, usesDeleteMode, onDelete, + isDeleteDisabled = false, }: { + isDeleteDisabled: boolean; wallet: Contracts.IReadWriteWallet; toggleAddress: (address: string) => void; isSelected: boolean; @@ -52,6 +54,7 @@ export const AddressRow = ({ > {usesDeleteMode && ( + {wallets.length > 1 && ( + <> + + + + + )} + {wallets.length === 1 && ( + <> + - + + + )} diff --git a/src/domains/wallet/pages/WalletDetails/components/WalletVote/__snapshots__/WalletVote.test.tsx.snap b/src/domains/wallet/pages/WalletDetails/components/WalletVote/__snapshots__/WalletVote.test.tsx.snap index 496604bf3e..6186cf44b4 100644 --- a/src/domains/wallet/pages/WalletDetails/components/WalletVote/__snapshots__/WalletVote.test.tsx.snap +++ b/src/domains/wallet/pages/WalletDetails/components/WalletVote/__snapshots__/WalletVote.test.tsx.snap @@ -236,94 +236,7 @@ exports[`WalletVote > should render 1`] = `
- - -
+ /> `; @@ -718,96 +631,7 @@ exports[`WalletVote > should render ledger for incompatible ledger wallet 1`] =
- - -
+ /> `; @@ -1201,94 +1025,7 @@ exports[`WalletVote > should render without votes 1`] = `
- - -
+ /> `; @@ -1509,94 +1246,7 @@ exports[`WalletVote > single vote networks > should render a vote for a standby
- - -
+ /> `; From d8a8fdc26a496a3eb7351def7752e543967d1f82 Mon Sep 17 00:00:00 2001 From: Patricio Marroquin <55117912+patricio0312rev@users.noreply.github.com> Date: Wed, 29 Jan 2025 05:28:23 -0500 Subject: [PATCH 2/2] refactor: dashboard setup card styles (#918) --- .../Dashboard/Dashboard.Empty.blocks.tsx | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/domains/dashboard/pages/Dashboard/Dashboard.Empty.blocks.tsx b/src/domains/dashboard/pages/Dashboard/Dashboard.Empty.blocks.tsx index eed8de0434..3067708dc9 100644 --- a/src/domains/dashboard/pages/Dashboard/Dashboard.Empty.blocks.tsx +++ b/src/domains/dashboard/pages/Dashboard/Dashboard.Empty.blocks.tsx @@ -60,36 +60,37 @@ export const DashboardSetupAddressCard = ({ image, buttonText, onClick, - variant = "primary", }: { title: string; description: string; image: string; buttonText: string; onClick: () => void; - variant?: "primary" | "secondary"; }) => ( -

{title}

-

{description}

+

+ {description} +

- -
+ + ); export const DashboardSetupAddressCards = () => { @@ -103,22 +104,19 @@ export const DashboardSetupAddressCards = () => {
- - +