Skip to content

Commit

Permalink
feat: Rework hiddenCollections to handle more easily displays
Browse files Browse the repository at this point in the history
  • Loading branch information
mcayuelas-ledger committed Nov 25, 2024
1 parent b1bc784 commit 8d3dadd
Show file tree
Hide file tree
Showing 24 changed files with 295 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function createContextMenuItems({
openModal("MODAL_HIDE_NFT_COLLECTION", {
collectionName: collectionName ?? collectionAddress,
collectionId: `${account.id}|${collectionAddress}`,
blockchain: account.currency.id,
onClose: () => {
if (goBackToAccount) {
setDrawer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ export const useOpenHideCollectionModal = (
collectionName: collectionName as string,
collectionId: `${account.id}|${nft.contract}`,
onClose,
blockchain: account.currency.id,
}),
),
};
}, [account.id, dispatch, metadata, nft.contract, onClose, t]);
}, [account.currency.id, account.id, dispatch, metadata, nft.contract, onClose, t]);
};
27 changes: 11 additions & 16 deletions apps/ledger-live-desktop/src/renderer/actions/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
import { useRefreshAccountsOrdering } from "~/renderer/actions/general";
import { Language, Locale } from "~/config/languages";
import { Layout } from "LLD/features/Collectibles/types/Layouts";
import { BlockchainsType } from "@ledgerhq/live-nft/supported";
import { NftStatus } from "@ledgerhq/live-nft/types";
export type SaveSettings = (a: Partial<Settings>) => {
type: string;
payload: Partial<Settings>;
Expand Down Expand Up @@ -214,15 +216,6 @@ export const blacklistToken = (tokenId: string) => ({
type: "BLACKLIST_TOKEN",
payload: tokenId,
});
export const hideNftCollection = (collectionId: string) => ({
type: "HIDE_NFT_COLLECTION",
payload: collectionId,
});

export const whitelistNftCollection = (collectionId: string) => ({
type: "WHITELIST_NFT_COLLECTION",
payload: collectionId,
});

export const hideOrdinalsAsset = (inscriptionId: string) => ({
type: "HIDE_ORDINALS_ASSET",
Expand Down Expand Up @@ -254,14 +247,16 @@ export const showToken = (tokenId: string) => ({
type: "SHOW_TOKEN",
payload: tokenId,
});
export const unhideNftCollection = (collectionId: string) => ({
type: "UNHIDE_NFT_COLLECTION",
payload: collectionId,
});
export const unwhitelistNftCollection = (collectionId: string) => ({
type: "UNWHITELIST_NFT_COLLECTION",
payload: collectionId,

export const updateNftStatus = (
blockchain: BlockchainsType,
collectionId: string,
status: NftStatus,
) => ({
type: "UPDATE_NFT_COLLECTION_STATUS",
payload: { blockchain, collectionId, status },
});

export const unhideOrdinalsAsset = (inscriptionId: string) => ({
type: "UNHIDE_ORDINALS_ASSET",
payload: inscriptionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default function NFTCollectionContextMenu({
history.replace(`account/${account.id}`);
}
},
blockchain: account.currency.id,
}),
),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { hideNftCollection } from "~/renderer/actions/settings";
import { updateNftStatus } from "~/renderer/actions/settings";
import { useHideSpamCollection } from "../useHideSpamCollection";
import { renderHook } from "tests/testUtils";
import { INITIAL_STATE } from "~/renderer/reducers/settings";
import { useDispatch } from "react-redux";
import { BlockchainEVM } from "@ledgerhq/live-nft/supported";
import { NftStatus } from "@ledgerhq/live-nft/types";

jest.mock("react-redux", () => ({
...jest.requireActual("react-redux"),
Expand All @@ -17,31 +19,41 @@ describe("useHideSpamCollection", () => {
mockDispatch.mockClear();
});

it("should dispatch hideNftCollection action if collection is not whitelisted", () => {
it("should dispatch updateNftStatus action if collection is not already marked with a status", () => {
const { result } = renderHook(() => useHideSpamCollection(), {
initialState: {
settings: {
...INITIAL_STATE,
whitelistedNftCollections: ["collectionA", "collectionB"],
hiddenNftCollections: [],
nftCollectionsStatusByNetwork: {},
},
},
});
result.current.hideSpamCollection("collectionC");
result.current.hideSpamCollection("collectionC", BlockchainEVM.Ethereum);

expect(mockDispatch).toHaveBeenCalledWith(hideNftCollection("collectionC"));
expect(mockDispatch).toHaveBeenCalledWith(
updateNftStatus(BlockchainEVM.Ethereum, "collectionC", NftStatus.spam),
);
});

it("should not dispatch hideNftCollection action if collection is whitelisted", () => {
it("should not dispatch hideNftCollection action if collection is already marked with a status", () => {
const { result } = renderHook(() => useHideSpamCollection(), {
initialState: {
settings: {
hiddenNftCollections: [],
nftCollectionsStatusByNetwork: {
[BlockchainEVM.Ethereum]: {
collectionA: NftStatus.spam,
},
[BlockchainEVM.Avalanche]: {
collectionB: NftStatus.spam,
},
},
whitelistedNftCollections: ["collectionA", "collectionB"],
},
},
});
result.current.hideSpamCollection("collectionA");

result.current.hideSpamCollection("collectionA", BlockchainEVM.Ethereum);
result.current.hideSpamCollection("collectionB", BlockchainEVM.Avalanche);

expect(mockDispatch).not.toHaveBeenCalled();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { renderHook } from "tests/testUtils";
import { INITIAL_STATE } from "~/renderer/reducers/settings";
import { BlockchainEVM } from "@ledgerhq/live-nft/supported";
import { NftStatus } from "@ledgerhq/live-nft/types";
import { useNftCollectionsStatus } from "../useNftCollectionsStatus";

describe("useNftCollectionsStatus", () => {
it("should returns only NFTs (contract) with NftStatus !== whitelisted when FF is ON", () => {
const { result } = renderHook(() => useNftCollectionsStatus(), {
initialState: {
settings: {
...INITIAL_STATE,
overriddenFeatureFlags: {
nftsFromSimplehash: {
enabled: true,
},
},
nftCollectionsStatusByNetwork: {
[BlockchainEVM.Ethereum]: {
collectionETHA: NftStatus.whitelisted,
collectionETHB: NftStatus.blacklisted,
collectionETHC: NftStatus.spam,
collectionETHD: NftStatus.spam,
},
[BlockchainEVM.Avalanche]: {
collectionAVAX1: NftStatus.blacklisted,
collectionAVAX2: NftStatus.spam,
collectionAVAX3: NftStatus.blacklisted,
},
[BlockchainEVM.Polygon]: {
collectionP1: NftStatus.blacklisted,
collectionP2: NftStatus.whitelisted,
},
},
},
},
});

expect(result.current.hiddenNftCollections).toEqual([
"collectionETHB",
"collectionETHC",
"collectionETHD",
"collectionAVAX1",
"collectionAVAX2",
"collectionAVAX3",
"collectionP1",
]);
});

it("should returns only NFTs (contract) with NftStatus.blacklisted when FF is oFF ", () => {
const { result } = renderHook(() => useNftCollectionsStatus(), {
initialState: {
settings: {
...INITIAL_STATE,
overriddenFeatureFlags: {
nftsFromSimplehash: {
enabled: false,
},
},
nftCollectionsStatusByNetwork: {
[BlockchainEVM.Ethereum]: {
collectionETHA: NftStatus.whitelisted,
collectionETHB: NftStatus.blacklisted,
collectionETHC: NftStatus.spam,
collectionETHD: NftStatus.spam,
},
[BlockchainEVM.Avalanche]: {
collectionAVAX1: NftStatus.blacklisted,
collectionAVAX2: NftStatus.spam,
collectionAVAX3: NftStatus.blacklisted,
},
[BlockchainEVM.Polygon]: {
collectionP1: NftStatus.blacklisted,
collectionP2: NftStatus.whitelisted,
},
},
},
},
});

expect(result.current.hiddenNftCollections).toEqual([
"collectionETHB",
"collectionAVAX1",
"collectionAVAX3",
"collectionP1",
]);
});
});
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import { whitelistedNftCollectionsSelector } from "~/renderer/reducers/settings";
import { hideNftCollection } from "~/renderer/actions/settings";
import { nftCollectionsStatusByNetworkSelector } from "~/renderer/reducers/settings";
import { updateNftStatus } from "~/renderer/actions/settings";
import { BlockchainsType } from "@ledgerhq/live-nft/supported";
import { NftStatus } from "@ledgerhq/live-nft/types";

export function useHideSpamCollection() {
const spamFilteringTxFeature = useFeature("spamFilteringTx");
const whitelistedNftCollections = useSelector(whitelistedNftCollectionsSelector);
const nftsFromSimplehashFeature = useFeature("nftsFromSimplehash");

const nftCollectionsStatusByNetwork = useSelector(nftCollectionsStatusByNetworkSelector);

const dispatch = useDispatch();

const hideSpamCollection = useCallback(
(collection: string) => {
if (!whitelistedNftCollections.includes(collection)) {
dispatch(hideNftCollection(collection));
(collection: string, blockchain: BlockchainsType) => {
const elem = Object.entries(nftCollectionsStatusByNetwork).find(
([key]) => key === blockchain,
)?.[1];

if (!elem) {
dispatch(updateNftStatus(blockchain, collection, NftStatus.spam));
}
},
[dispatch, whitelistedNftCollections],
[dispatch, nftCollectionsStatusByNetwork],
);

return {
hideSpamCollection,
enabled: spamFilteringTxFeature?.enabled,
enabled: spamFilteringTxFeature?.enabled && nftsFromSimplehashFeature?.enabled,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import { nftsByCollections } from "@ledgerhq/live-nft/index";
import { BlockchainEVM } from "@ledgerhq/live-nft/supported";
import { Account, ProtoNFT } from "@ledgerhq/types-live";
import { useMemo } from "react";
import { useSelector } from "react-redux";
import {
hiddenNftCollectionsSelector,
whitelistedNftCollectionsSelector,
} from "~/renderer/reducers/settings";
import { useNftCollectionsStatus } from "./useNftCollectionsStatus";

export function useNftCollections({
account,
Expand All @@ -25,19 +21,18 @@ export function useNftCollections({
const threshold = nftsFromSimplehashFeature?.params?.threshold;
const simplehashEnabled = nftsFromSimplehashFeature?.enabled;

const whitelistNft = useSelector(whitelistedNftCollectionsSelector);
const hiddenNftCollections = useSelector(hiddenNftCollectionsSelector);
const { hiddenNftCollections, whitelistedNftCollections } = useNftCollectionsStatus();

const nftsOwnedToCheck = useMemo(() => account?.nfts ?? nftsOwned, [account?.nfts, nftsOwned]);

const whitelistedNfts = useMemo(
() =>
nftsOwnedToCheck?.filter(nft =>
whitelistNft
whitelistedNftCollections
.map(collection => decodeCollectionId(collection).contractAddress)
.includes(nft.contract),
) ?? [],
[nftsOwnedToCheck, whitelistNft],
[nftsOwnedToCheck, whitelistedNftCollections],
);

const { nfts, fetchNextPage, hasNextPage } = useNftGalleryFilter({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMemo } from "react";
import { useSelector } from "react-redux";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import { nftCollectionsStatusByNetworkSelector } from "~/renderer/reducers/settings";
import { NftStatus } from "@ledgerhq/live-nft/types";

export function useNftCollectionsStatus() {
const nftsFromSimplehashFeature = useFeature("nftsFromSimplehash");
const nftCollectionsStatusByNetwork = useSelector(nftCollectionsStatusByNetworkSelector);

const shouldDisplaySpams = !nftsFromSimplehashFeature?.enabled;

const list = useMemo(() => {
return Object.values(nftCollectionsStatusByNetwork).flatMap(contracts =>
Object.entries(contracts)
.filter(([_, status]) =>
!shouldDisplaySpams ? status !== NftStatus.whitelisted : status === NftStatus.blacklisted,
)
.map(([contract]) => contract),
);
}, [nftCollectionsStatusByNetwork, shouldDisplaySpams]);

const whitelisted = useMemo(() => {
return Object.values(nftCollectionsStatusByNetwork).flatMap(contracts =>
Object.entries(contracts)
.filter(([_, status]) => status === NftStatus.whitelisted)
.map(([contract]) => contract),
);
}, [nftCollectionsStatusByNetwork]);

return {
hiddenNftCollections: list,
whitelistedNftCollections: whitelisted,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,12 @@ export default (
collectionName: metadata?.tokenName ?? nft.contract,
collectionId: `${account.id}|${nft.contract}`,
onClose,
blockchain: account.currency.id,
}),
);
},
}),
[account.id, dispatch, metadata?.tokenName, nft.contract, onClose, t],
[account.currency.id, account.id, dispatch, metadata?.tokenName, nft.contract, onClose, t],
);
const customImageUri = useMemo(() => {
const mediaTypes = metadata ? getMetadataMediaTypes(metadata) : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import Text from "~/renderer/components/Text";
import ModalBody from "~/renderer/components/Modal/ModalBody";
import React from "react";
import Footer from "~/renderer/modals/HideNftCollection/Footer";
import { BlockchainsType } from "@ledgerhq/live-nft/supported";
const Body = ({
onClose,
collectionId,
collectionName,
blockchain,
}: {
onClose: () => void;
collectionId: string;
collectionName: string;
blockchain: string;
}) => {
const { t } = useTranslation();
return (
Expand Down Expand Up @@ -47,7 +50,13 @@ const Body = ({
</Box>
</Box>
)}
renderFooter={() => <Footer collectionId={collectionId} onClose={onClose} />}
renderFooter={() => (
<Footer
collectionId={collectionId}
onClose={onClose}
blockchain={blockchain as BlockchainsType}
/>
)}
/>
);
};
Expand Down
Loading

0 comments on commit 8d3dadd

Please sign in to comment.