From 6cf125821cced8312557d5b8f8f0a7b5d0af537c Mon Sep 17 00:00:00 2001 From: Lucas Werey <73439207+LucasWerey@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:09:27 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(lld):=20add=20details=20drawer=20for?= =?UTF-8?q?=20inscriptions=20(#7908)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :sparkles:(lld): add detaildrawer for inscriptions --- .changeset/dry-otters-shop.md | 6 + .../Inscriptions/DetailsDrawer/Actions.tsx | 32 + .../Inscriptions/DetailsDrawer/SubTitle.tsx | 25 + .../Inscriptions/DetailsDrawer/index.tsx | 62 +- .../useInscriptionDetailDrawer.ts | 64 + .../DiscoveryDrawer/ProtectBox.tsx | 3 +- .../Ordinals/components/Inscriptions/Item.tsx | 29 - .../components/Inscriptions/Item/index.tsx | 40 + .../components/Inscriptions/helpers.ts | 38 +- .../components/Inscriptions/index.tsx | 16 +- .../Ordinals/components/RareSats/helpers.ts | 62 - .../components/RareSats/useRareSatsModel.ts | 2 +- .../Ordinals/components/helpers.ts | 78 + .../Ordinals/screens/Account/index.tsx | 12 +- .../screens/Account/useBitcoinAccountModel.ts | 16 +- .../__integration__/bitcoinPage.test.tsx | 44 +- .../components/Collection/HeaderActions.tsx | 5 +- .../Collectibles/types/Inscriptions.ts | 5 +- .../utils/createInscriptionDetailsArrays.ts | 114 + .../utils/findCorrespondingSat.ts | 9 + .../static/i18n/en/app.json | 18 + .../fixtures/ordinals/mockedOrdinals.json | 2883 +++++++++++------ .../src/hooks/useFetchOrdinals.ts | 21 +- libs/live-nft/src/api/types.ts | 50 +- 24 files changed, 2550 insertions(+), 1084 deletions(-) create mode 100644 .changeset/dry-otters-shop.md create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/Actions.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/SubTitle.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/useInscriptionDetailDrawer.ts delete mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item.tsx create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item/index.tsx delete mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/helpers.ts create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/utils/createInscriptionDetailsArrays.ts create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/utils/findCorrespondingSat.ts diff --git a/.changeset/dry-otters-shop.md b/.changeset/dry-otters-shop.md new file mode 100644 index 000000000000..187563b2688a --- /dev/null +++ b/.changeset/dry-otters-shop.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"@ledgerhq/live-nft": patch +--- + +Add detail drawers for inscriptions of ordinals protocol diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/Actions.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/Actions.tsx new file mode 100644 index 000000000000..f81c2a28202b --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/Actions.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { Flex, Icons, IconsLegacy, Text } from "@ledgerhq/react-ui"; +import Button from "~/renderer/components/Button"; +import { useTranslation } from "react-i18next"; + +const Actions = () => { + const { t } = useTranslation(); + return ( + + + + + ); +}; +export default Actions; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/SubTitle.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/SubTitle.tsx new file mode 100644 index 000000000000..21c43cae7fe2 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/SubTitle.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { Flex, Text } from "@ledgerhq/react-ui"; +import IconContainer from "LLD/features/Collectibles/components/Collection/TableRow/IconContainer"; +import { IconProps } from "LLD/features/Collectibles/types/Collection"; +import { MappingKeys } from "LLD/features/Collectibles/types/Ordinals"; +import { useTranslation } from "react-i18next"; + +type Props = { + icons: (({ size, color, style }: IconProps) => JSX.Element)[]; + names: MappingKeys[]; +}; + +const SubTitle: React.FC = ({ icons, names }) => { + const { t } = useTranslation(); + return ( + + + + {t("ordinals.inscriptions.detailsDrawer.storedOnChain")} + + + ); +}; + +export default SubTitle; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/index.tsx index eb7ff49015cf..abedd3a803ae 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/index.tsx @@ -1,18 +1,66 @@ import React from "react"; -import { SideDrawer } from "~/renderer/components/SideDrawer"; import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import { DetailDrawer } from "LLD/features/Collectibles/components"; +import { CollectibleTypeEnum } from "LLD/features/Collectibles/types/enum/Collectibles"; +import useInscriptionDetailDrawer from "./useInscriptionDetailDrawer"; +import Actions from "./Actions"; +import SubTitle from "./SubTitle"; + +type ViewProps = ReturnType & { + onClose: () => void; +}; type Props = { inscription: SimpleHashNft; + correspondingRareSat: SimpleHashNft | null | undefined; + isLoading: boolean; onClose: () => void; }; -const InscriptionDetailsDrawer: React.FC = ({ inscription, onClose }) => { - // will be replaced by DetailsDrawer from collectibles + +const View: React.FC = ({ data, rareSat, onClose }) => ( + + {rareSat?.icons && ( + + + + )} + + + + +); + +const InscriptionDetailDrawer = ({ + inscription, + isLoading, + correspondingRareSat, + onClose, +}: Props) => { return ( - - {inscription.name || inscription.contract.name} - + ); }; -export default InscriptionDetailsDrawer; +export default InscriptionDetailDrawer; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/useInscriptionDetailDrawer.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/useInscriptionDetailDrawer.ts new file mode 100644 index 000000000000..c82cae275514 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/useInscriptionDetailDrawer.ts @@ -0,0 +1,64 @@ +import { useMemo, useState } from "react"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import useCollectibles from "LLD/features/Collectibles/hooks/useCollectibles"; +import { createDetails } from "LLD/features/Collectibles/utils/createInscriptionDetailsArrays"; +import { useCalendarFormatted } from "~/renderer/hooks/useDateFormatter"; +import { Tag } from "LLD/features/Collectibles/types/DetailDrawer"; +import { processRareSat } from "../helpers"; + +type Props = { + isLoading: boolean; + inscription: SimpleHashNft; + correspondingRareSat: SimpleHashNft | null | undefined; +}; + +const useInscriptionDetailDrawer = ({ isLoading, inscription, correspondingRareSat }: Props) => { + const [useFallback, setUseFallback] = useState(false); + const imageUri = + inscription.video_url || inscription.previews?.image_large_url || inscription.image_url; + + const isVideo = !!inscription.video_url; + + const contentType = isVideo ? "video" : imageUri ? "image" : ""; + + const { isPanAndZoomOpen, openCollectiblesPanAndZoom, closeCollectiblesPanAndZoom } = + useCollectibles(); + + const createdDateFromTimestamp = new Date(inscription.first_created?.timestamp || 0); + const formattedCreatedDate = useCalendarFormatted(createdDateFromTimestamp); + const createdDate = createdDateFromTimestamp === new Date(0) ? "" : formattedCreatedDate; + const details = createDetails(inscription, createdDate); + + const tags: Tag[] = + inscription.extra_metadata?.attributes?.map(attr => ({ + key: attr.trait_type, + value: attr.value, + })) || []; + + const rareSat = useMemo(() => { + if (correspondingRareSat) return processRareSat(correspondingRareSat); + }, [correspondingRareSat]); + + const data = { + areFieldsLoading: isLoading, + collectibleName: inscription.name || inscription.contract.name || "", + contentType, + collectionName: inscription.collection.name || "", + details: details, + previewUri: imageUri, + originalUri: imageUri, + isPanAndZoomOpen, + mediaType: inscription.video_properties?.mime_type || "image", + tags: tags, + useFallback: useFallback, + tokenId: inscription.nft_id, + isOpened: true, + closeCollectiblesPanAndZoom, + openCollectiblesPanAndZoom, + setUseFallback: setUseFallback, + }; + + return { inscription, data, rareSat }; +}; + +export default useInscriptionDetailDrawer; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/ProtectBox.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/ProtectBox.tsx index 482a46ebcbff..8c428e9f8a18 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/ProtectBox.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/ProtectBox.tsx @@ -4,9 +4,10 @@ import Switch from "~/renderer/components/Switch"; import { hasProtectedOrdinalsAssetsSelector } from "~/renderer/reducers/settings"; import { setHasProtectedOrdinalsAssets } from "~/renderer/actions/settings"; import { useDispatch, useSelector } from "react-redux"; -import { t } from "i18next"; +import { useTranslation } from "react-i18next"; const ProtectBox: React.FC = () => { + const { t } = useTranslation(); const dispatch = useDispatch(); const hasProtectedOrdinals = useSelector(hasProtectedOrdinalsAssetsSelector); diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item.tsx deleted file mode 100644 index dc3f2ab8ffd8..000000000000 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { InscriptionsItemProps } from "LLD/features/Collectibles/types/Inscriptions"; -import TableRow from "LLD/features/Collectibles/components/Collection/TableRow"; -import React from "react"; - -type ItemProps = { - isLoading: boolean; -} & InscriptionsItemProps; - -const Item: React.FC = ({ - isLoading, - tokenName, - collectionName, - tokenIcons, - media, - rareSatName, - onClick, -}) => ( - -); - -export default Item; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item/index.tsx new file mode 100644 index 000000000000..b9c404527346 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item/index.tsx @@ -0,0 +1,40 @@ +import React, { useMemo } from "react"; +import { InscriptionsItemProps } from "LLD/features/Collectibles/types/Inscriptions"; +import TableRow from "LLD/features/Collectibles/components/Collection/TableRow"; +import { GroupedNftOrdinals } from "@ledgerhq/live-nft-react/index"; +import { findCorrespondingSat } from "LLD/features/Collectibles/utils/findCorrespondingSat"; +import { processRareSat } from "../helpers"; + +type ItemProps = { + isLoading: boolean; + inscriptionsGroupedWithRareSats: GroupedNftOrdinals[]; +} & InscriptionsItemProps; + +const Item: React.FC = ({ + isLoading, + tokenName, + collectionName, + media, + nftId, + inscriptionsGroupedWithRareSats, + onClick, +}) => { + const correspondingRareSat = findCorrespondingSat(inscriptionsGroupedWithRareSats, nftId); + const rareSat = useMemo(() => { + if (correspondingRareSat) return processRareSat(correspondingRareSat.rareSat); + }, [correspondingRareSat]); + + return ( + + ); +}; + +export default Item; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/helpers.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/helpers.ts index 7ea111c9c9c9..695f0a214b1d 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/helpers.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/helpers.ts @@ -1,40 +1,14 @@ import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; -import { IconProps } from "LLD/features/Collectibles/types/Collection"; -import { mappingKeysWithIconAndName } from "../Icons"; -import { MappingKeys } from "LLD/features/Collectibles/types/Ordinals"; - -function matchCorrespondingIcon( - rareSats: SimpleHashNft[], -): Array JSX.Element> }> { - return rareSats.map(rareSat => { - const iconKeys: string[] = []; - const rarity = rareSat.extra_metadata?.ordinal_details?.sat_rarity?.toLowerCase(); - - if (rarity && rarity !== "common") { - iconKeys.push(rarity.replace(" ", "_")); - } - - const icons = iconKeys - .map( - iconKey => - mappingKeysWithIconAndName[iconKey as keyof typeof mappingKeysWithIconAndName]?.icon, - ) - .filter(Boolean) as Array<({ size, color, style }: IconProps) => JSX.Element>; - - return { ...rareSat, icons }; - }); -} +import { createRareSatObject, matchCorrespondingIcon } from "../helpers"; export function getInscriptionsData( inscriptions: SimpleHashNft[], onInscriptionClick: (inscription: SimpleHashNft) => void, ) { - const inscriptionsWithIcons = matchCorrespondingIcon(inscriptions); - return inscriptionsWithIcons.map(item => ({ + return inscriptions.map(item => ({ tokenName: item.name || item.contract.name || "", + nftId: item.nft_id, collectionName: item.collection.name, - tokenIcons: item.icons, - rareSatName: [item.extra_metadata?.ordinal_details?.sat_rarity] as MappingKeys[], media: { uri: item.image_url || item.previews?.image_small_url, isLoading: false, @@ -45,3 +19,9 @@ export function getInscriptionsData( onClick: () => onInscriptionClick(item), })); } + +export function processRareSat(inscription: SimpleHashNft) { + const matchedRareSatsIcons = matchCorrespondingIcon(inscription); + const rareSatObject = createRareSatObject({ rareSat: matchedRareSatsIcons }); + return rareSatObject.rareSat[0]; +} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/index.tsx index 3e88f98b0e75..e6269cb4034b 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/index.tsx @@ -10,14 +10,16 @@ import Loader from "../Loader"; import Error from "../Error"; import Item from "./Item"; import EmptyCollection from "LLD/features/Collectibles/components/Collection/EmptyCollection"; -import { CollectibleTypeEnum } from "../../../types/enum/Collectibles"; +import { CollectibleTypeEnum } from "LLD/features/Collectibles/types/enum/Collectibles"; import Button from "~/renderer/components/Button"; import { useTranslation } from "react-i18next"; +import { GroupedNftOrdinals } from "@ledgerhq/live-nft-react/index"; type ViewProps = ReturnType & { isLoading: boolean; isError: boolean; error: Error | null; + inscriptionsGroupedWithRareSats: GroupedNftOrdinals[]; onReceive: () => void; }; @@ -26,6 +28,7 @@ type Props = { isLoading: boolean; isError: boolean; error: Error | null; + inscriptionsGroupedWithRareSats: GroupedNftOrdinals[]; onReceive: () => void; onInscriptionClick: (inscription: SimpleHashNft) => void; }; @@ -36,6 +39,7 @@ const View: React.FC = ({ isError, inscriptions, error, + inscriptionsGroupedWithRareSats, onShowMore, onReceive, }) => { @@ -55,12 +59,8 @@ const View: React.FC = ({ ))} {nothingToShow && ( @@ -84,6 +84,7 @@ const Inscriptions: React.FC = ({ isLoading, isError, error, + inscriptionsGroupedWithRareSats, onReceive, onInscriptionClick, }) => ( @@ -92,6 +93,7 @@ const Inscriptions: React.FC = ({ isError={isError} error={error} onReceive={onReceive} + inscriptionsGroupedWithRareSats={inscriptionsGroupedWithRareSats} {...useInscriptionsModel({ inscriptions, onInscriptionClick })} /> ); diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts deleted file mode 100644 index 9a7423bbe53b..000000000000 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { mappingKeysWithIconAndName } from "../Icons"; -import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; -import { - SimpleHashNftWithIcons, - RareSat, - MappingKeys, -} from "LLD/features/Collectibles/types/Ordinals"; - -export function matchCorrespondingIcon(rareSats: SimpleHashNft[]): SimpleHashNftWithIcons[] { - return rareSats.map(rareSat => { - const iconKeys: string[] = []; - if (rareSat.name) { - iconKeys.push(rareSat.name.toLowerCase().replace(" ", "_")); - } - - if (rareSat.extra_metadata?.utxo_details?.satributes) { - iconKeys.push(...Object.keys(rareSat.extra_metadata.utxo_details.satributes)); - } - - const icons = iconKeys - .map( - iconKey => - mappingKeysWithIconAndName[iconKey as keyof typeof mappingKeysWithIconAndName]?.icon, - ) - .filter(Boolean) as RareSat["icons"]; - - return { ...rareSat, icons }; - }); -} - -export function createRareSatObject( - rareSats: Record, -): Record { - const result: Record = {}; - - for (const [key, value] of Object.entries(rareSats)) { - result[key] = value.map(rareSat => { - const { icons, extra_metadata } = rareSat; - const year = extra_metadata?.utxo_details?.sat_ranges?.[0]?.year || ""; - const displayed_names = - Object.values(extra_metadata?.utxo_details?.satributes || {}) - .map(attr => attr.display_name) - .join(" / ") || ""; - const names = rareSat.extra_metadata?.utxo_details?.satributes - ? (Object.keys(rareSat.extra_metadata.utxo_details.satributes) as MappingKeys[]) - : []; - const count = extra_metadata?.utxo_details?.value || 0; - const isMultipleRow = value.length > 1; - - return { - year, - displayed_names, - names, - count: `${count} ${count > 1 ? "sats" : "sat"}`, - isMultipleRow, - icons, - }; - }); - } - - return result; -} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts index a59b1eafc054..e73078536530 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts @@ -1,4 +1,4 @@ -import { matchCorrespondingIcon, createRareSatObject } from "./helpers"; +import { matchCorrespondingIcon, createRareSatObject } from "../helpers"; import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; import { regroupRareSatsByContractAddress } from "@ledgerhq/live-nft-react"; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/helpers.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/helpers.ts new file mode 100644 index 000000000000..c323b37e8bd9 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/helpers.ts @@ -0,0 +1,78 @@ +import { mappingKeysWithIconAndName } from "./Icons"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import { + SimpleHashNftWithIcons, + RareSat, + MappingKeys, +} from "LLD/features/Collectibles/types/Ordinals"; + +function processSingleRareSat(rareSat: SimpleHashNft): SimpleHashNftWithIcons { + const iconKeys: string[] = []; + if (rareSat.name) { + iconKeys.push(rareSat.name.toLowerCase().replace(" ", "_")); + } + + if (rareSat.extra_metadata?.utxo_details?.satributes) { + iconKeys.push(...Object.keys(rareSat.extra_metadata.utxo_details.satributes)); + } + + const icons = iconKeys + .map( + iconKey => + mappingKeysWithIconAndName[iconKey as keyof typeof mappingKeysWithIconAndName]?.icon, + ) + .filter(Boolean) as RareSat["icons"]; + + return { ...rareSat, icons }; +} + +function mapToRareSat(rareSat: SimpleHashNftWithIcons, isMultipleRow: boolean): RareSat { + const { icons, extra_metadata } = rareSat; + const year = extra_metadata?.utxo_details?.sat_ranges?.[0]?.year || ""; + const displayed_names = + Object.values(extra_metadata?.utxo_details?.satributes || {}) + .map(attr => attr.display_name) + .join(" / ") || ""; + const names = rareSat.extra_metadata?.utxo_details?.satributes + ? (Object.keys(rareSat.extra_metadata.utxo_details.satributes) as MappingKeys[]) + : []; + const count = extra_metadata?.utxo_details?.value || 0; + + return { + year, + displayed_names, + names, + count: `${count} ${count > 1 ? "sats" : "sat"}`, + isMultipleRow, + icons, + }; +} + +function processRareSatObject( + rareSats: Record, +): Record { + const result: Record = {}; + + for (const [key, value] of Object.entries(rareSats)) { + result[key] = value.map(rareSat => mapToRareSat(rareSat, value.length > 1)); + } + + return result; +} + +export function matchCorrespondingIcon( + rareSats: SimpleHashNft | SimpleHashNft[], +): SimpleHashNftWithIcons[] { + const rareSatArray = Array.isArray(rareSats) ? rareSats : [rareSats]; + return rareSatArray.map(processSingleRareSat); +} + +export function createRareSatObject( + rareSats: Record | SimpleHashNftWithIcons[], +): Record { + if (Array.isArray(rareSats)) { + return { default: rareSats.map(rareSat => mapToRareSat(rareSat, rareSats.length > 1)) }; + } else { + return processRareSatObject(rareSats); + } +} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx index f1683da348f9..cbec04e52909 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx @@ -19,6 +19,8 @@ const View: React.FC = ({ rareSats, isDrawerOpen, selectedInscription, + correspondingRareSat, + inscriptionsGroupedWithRareSats, handleDrawerClose, onReceive, onInscriptionClick, @@ -26,15 +28,21 @@ const View: React.FC = ({ }) => ( {selectedInscription && ( - + )} ); diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/useBitcoinAccountModel.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/useBitcoinAccountModel.ts index b6bbe99d37d3..c1c8d0ad620b 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/useBitcoinAccountModel.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/useBitcoinAccountModel.ts @@ -6,6 +6,7 @@ import { useDispatch, useSelector } from "react-redux"; import { openModal } from "~/renderer/actions/modals"; import { setHasSeenOrdinalsDiscoveryDrawer } from "~/renderer/actions/settings"; import { hasSeenOrdinalsDiscoveryDrawerSelector } from "~/renderer/reducers/settings"; +import { findCorrespondingSat } from "LLD/features/Collectibles/utils/findCorrespondingSat"; interface Props { account: BitcoinAccount; @@ -15,8 +16,13 @@ export const useBitcoinAccountModel = ({ account }: Props) => { const dispatch = useDispatch(); const hasSeenDiscoveryDrawer = useSelector(hasSeenOrdinalsDiscoveryDrawerSelector); const [selectedInscription, setSelectedInscription] = useState(null); + const [correspondingRareSat, setCorrespondingRareSat] = useState< + SimpleHashNft | null | undefined + >(null); - const { rareSats, inscriptions, ...rest } = useFetchOrdinals({ account }); + const { rareSats, inscriptions, inscriptionsGroupedWithRareSats, ...rest } = useFetchOrdinals({ + account, + }); const [isDrawerOpen, setIsDrawerOpen] = useState(!hasSeenDiscoveryDrawer); @@ -40,7 +46,11 @@ export const useBitcoinAccountModel = ({ account }: Props) => { ); }, [dispatch, account]); - const onInscriptionClick = (inscription: SimpleHashNft) => setSelectedInscription(inscription); + const onInscriptionClick = (inscription: SimpleHashNft) => { + const groupedNft = findCorrespondingSat(inscriptionsGroupedWithRareSats, inscription.nft_id); + setCorrespondingRareSat(groupedNft?.rareSat ?? null); + setSelectedInscription(inscription); + }; const onDetailsDrawerClose = () => setSelectedInscription(null); @@ -50,6 +60,8 @@ export const useBitcoinAccountModel = ({ account }: Props) => { rest, isDrawerOpen, selectedInscription, + correspondingRareSat, + inscriptionsGroupedWithRareSats, onReceive, handleDrawerClose, onInscriptionClick, diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/bitcoinPage.test.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/bitcoinPage.test.tsx index e1046b592629..66797fa60c8e 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/bitcoinPage.test.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/bitcoinPage.test.tsx @@ -18,24 +18,24 @@ jest.mock("~/renderer/linking", () => ({ describe("displayBitcoinPage", () => { it("should display Bitcoin page with rare sats and inscriptions", async () => { - const { user } = render(); + const { user } = render(, { + initialState: { + settings: { + hasSeenOrdinalsDiscoveryDrawer: true, + }, + }, + }); - await waitFor(() => expect(screen.getByText(/inscription #63691311/i)).toBeVisible()); - await waitFor(() => expect(screen.getByTestId(/raresaticon-palindrome-0/i)).toBeVisible()); - await user.hover(screen.getByTestId(/raresaticon-palindrome-0/i)); - await waitFor(() => expect(screen.getByText(/in a playful twist/i)).toBeVisible()); - await waitFor(() => - expect( - screen.getByText(/block 9 \/ first transaction \/ nakamoto \/ vintage/i), - ).toBeVisible(), - ); + await waitFor(() => expect(screen.getByText(/the great war #3695/i)).toBeVisible()); await waitFor(() => expect(screen.getByText(/see more inscriptions/i)).toBeVisible()); await user.click(screen.getByText(/see more inscriptions/i)); - await waitFor(() => expect(screen.getByText(/timechain #136/i)).toBeVisible()); - await waitFor(() => expect(screen.getByTestId(/raresaticon-jpeg-0/i)).toBeVisible()); - await user.hover(screen.getByTestId(/raresaticon-jpeg-0/i)); - await waitFor(() => expect(screen.getByText(/journey into the past with jpeg/i)).toBeVisible()); + await user.click(screen.getByText(/see more inscriptions/i)); + await waitFor(() => expect(screen.getByText(/bitcoin puppet #71/i)).toBeVisible()); + await waitFor(() => expect(screen.queryAllByTestId(/raresaticon-pizza-0/i)).toHaveLength(2)); + await user.hover(screen.queryAllByTestId(/raresaticon-pizza-0/i)[0]); + await waitFor(() => expect(screen.getByText(/papa john's pizza/i)).toBeVisible()); }); + it("should open discovery drawer when it is the first time feature is activated", async () => { const { user } = render(); @@ -43,4 +43,20 @@ describe("displayBitcoinPage", () => { await user.click(screen.getByText(/learn more/i)); expect(openURL).toHaveBeenCalledWith("https://www.ledger.com/academy/bitcoin-ordinals"); }); + + it("should open inscription detail drawer", async () => { + const { user } = render(, { + initialState: { + settings: { + hasSeenOrdinalsDiscoveryDrawer: true, + }, + }, + }); + + await waitFor(() => expect(screen.getByText(/the great war #3695/i)).toBeVisible()); + await user.click(screen.getByText(/the great war #3695/i)); + await expect(screen.getByText(/hide/i)).toBeVisible(); + // sat name + await expect(screen.getByText(/dlngbapxjdv/i)).toBeVisible(); + }); }); diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/HeaderActions.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/HeaderActions.tsx index 5a3074491d6d..8f993d1b3d24 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/HeaderActions.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/HeaderActions.tsx @@ -1,7 +1,7 @@ -import Button from "~/renderer/components/Button"; -import { t } from "i18next"; import React from "react"; +import Button from "~/renderer/components/Button"; import Box from "~/renderer/components/Box"; +import { useTranslation } from "react-i18next"; type Props = { children?: JSX.Element; @@ -9,6 +9,7 @@ type Props = { }; const HeaderActions: React.FC = ({ children, textKey }) => { + const { t } = useTranslation(); return (