diff --git a/.changeset/empty-clouds-glow.md b/.changeset/empty-clouds-glow.md new file mode 100644 index 000000000000..373b48d68356 --- /dev/null +++ b/.changeset/empty-clouds-glow.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +Add ui for empty rare sats and inscriptions. Add error message from Error when inscriptions / rare sats can't load. Add dumy drawer for inscriptions diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Error.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Error.tsx index 6e08b8876426..7a668f81ae3a 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Error.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Error.tsx @@ -2,13 +2,18 @@ import React from "react"; import { Flex, Icons, Text } from "@ledgerhq/react-ui"; import { useTranslation } from "react-i18next"; -const Error: React.FC = () => { +type Props = { + error: Error; +}; + +const Error: React.FC = ({ error }) => { const { t } = useTranslation(); return ( - + {t("crash.title")} + {`(${error?.message})`} ); }; 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 new file mode 100644 index 000000000000..eb7ff49015cf --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/index.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { SideDrawer } from "~/renderer/components/SideDrawer"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; + +type Props = { + inscription: SimpleHashNft; + onClose: () => void; +}; +const InscriptionDetailsDrawer: React.FC = ({ inscription, onClose }) => { + // will be replaced by DetailsDrawer from collectibles + return ( + + {inscription.name || inscription.contract.name} + + ); +}; + +export default InscriptionDetailsDrawer; 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 0917bb99b130..7ea111c9c9c9 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 @@ -25,7 +25,10 @@ function matchCorrespondingIcon( }); } -export function getInscriptionsData(inscriptions: SimpleHashNft[]) { +export function getInscriptionsData( + inscriptions: SimpleHashNft[], + onInscriptionClick: (inscription: SimpleHashNft) => void, +) { const inscriptionsWithIcons = matchCorrespondingIcon(inscriptions); return inscriptionsWithIcons.map(item => ({ tokenName: item.name || item.contract.name || "", @@ -39,9 +42,6 @@ export function getInscriptionsData(inscriptions: SimpleHashNft[]) { contentType: item.extra_metadata?.ordinal_details?.content_type, mediaType: "image", }, - onClick: () => { - console.log(`you clicked on : \x1b[32m${item.name}\x1b[0m inscription`); - }, - // it does nothing for now but it will be used for the next PR with the drawer + onClick: () => onInscriptionClick(item), })); } 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 489b9f88d905..3e88f98b0e75 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 @@ -1,5 +1,5 @@ import React from "react"; -import { Box, Flex } from "@ledgerhq/react-ui"; +import { Box, Flex, Icons } from "@ledgerhq/react-ui"; import { useInscriptionsModel } from "./useInscriptionsModel"; import TableContainer from "~/renderer/components/TableContainer"; import TableHeader from "LLD/features/Collectibles/components/Collection/TableHeader"; @@ -9,13 +9,25 @@ import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; 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 Button from "~/renderer/components/Button"; +import { useTranslation } from "react-i18next"; -type ViewProps = ReturnType & { isLoading: boolean; isError: boolean }; +type ViewProps = ReturnType & { + isLoading: boolean; + isError: boolean; + error: Error | null; + onReceive: () => void; +}; type Props = { inscriptions: SimpleHashNft[]; isLoading: boolean; isError: boolean; + error: Error | null; + onReceive: () => void; + onInscriptionClick: (inscription: SimpleHashNft) => void; }; const View: React.FC = ({ @@ -23,17 +35,21 @@ const View: React.FC = ({ isLoading, isError, inscriptions, + error, onShowMore, + onReceive, }) => { + const { t } = useTranslation(); const hasInscriptions = inscriptions.length > 0 && !isError; const nothingToShow = !hasInscriptions && !isLoading && !isError; + const hasError = isError && error; return ( {isLoading && } - {isError && } + {hasError && } {hasInscriptions && inscriptions.map((item, index) => ( = ({ /> ))} {nothingToShow && ( - - {"NOTHING TO SHOW WAITING FOR DESIGN"} - + + + )} {displayShowMore && !isError && } @@ -58,8 +79,21 @@ const View: React.FC = ({ ); }; -const Inscriptions: React.FC = ({ inscriptions, isLoading, isError }) => ( - +const Inscriptions: React.FC = ({ + inscriptions, + isLoading, + isError, + error, + onReceive, + onInscriptionClick, +}) => ( + ); export default Inscriptions; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/useInscriptionsModel.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/useInscriptionsModel.tsx index c3893f978713..431e3234b68f 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/useInscriptionsModel.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/useInscriptionsModel.tsx @@ -5,21 +5,28 @@ import { InscriptionsItemProps } from "LLD/features/Collectibles/types/Inscripti type Props = { inscriptions: SimpleHashNft[]; + onInscriptionClick: (inscription: SimpleHashNft) => void; }; -export const useInscriptionsModel = ({ inscriptions }: Props) => { - const [displayShowMore, setDisplayShowMore] = useState(false); - const [displayedObjects, setDisplayedObjects] = useState([]); - +export const useInscriptionsModel = ({ inscriptions, onInscriptionClick }: Props) => { const items: InscriptionsItemProps[] = useMemo( - () => getInscriptionsData(inscriptions), - [inscriptions], + () => getInscriptionsData(inscriptions, onInscriptionClick), + [inscriptions, onInscriptionClick], ); + const initialDisplayedObjects = items.slice(0, 3); + const initialDisplayShowMore = items.length > 3; + + const [displayShowMore, setDisplayShowMore] = useState(initialDisplayShowMore); + const [displayedObjects, setDisplayedObjects] = + useState(initialDisplayedObjects); + useEffect(() => { - if (items.length > 3) setDisplayShowMore(true); - setDisplayedObjects(items.slice(0, 3)); - }, [items]); + if (displayedObjects.length === 0) { + if (items.length > 3) setDisplayShowMore(true); + setDisplayedObjects(items.slice(0, 3)); + } + }, [items, displayedObjects.length]); const onShowMore = () => { setDisplayedObjects(prevDisplayedObjects => { diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx index fe8603eedfb6..e1e8dc8f9adb 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx @@ -4,16 +4,22 @@ import TableContainer from "~/renderer/components/TableContainer"; import TableHeader from "LLD/features/Collectibles/components/Collection/TableHeader"; import Item from "./Item"; import { TableHeaderTitleKey } from "LLD/features/Collectibles/types/Collection"; -import { Box, Flex } from "@ledgerhq/react-ui"; +import { Box, Flex, Icons } from "@ledgerhq/react-ui"; import { TableHeader as TableHeaderContainer } from "./TableHeader"; import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; import Loader from "../Loader"; import Error from "../Error"; +import EmptyCollection from "../../../components/Collection/EmptyCollection"; +import { CollectibleTypeEnum } from "../../../types/enum/Collectibles"; +import Button from "~/renderer/components/Button"; +import { useTranslation } from "react-i18next"; type ViewProps = ReturnType & { isLoading: boolean; isError: boolean; isFetched: boolean; + error: Error | null; + onReceive: () => void; }; type Props = { @@ -21,19 +27,23 @@ type Props = { isLoading: boolean; isError: boolean; isFetched: boolean; + error: Error | null; + onReceive: () => void; }; -function View({ rareSats, isLoading, isError, isFetched }: ViewProps) { - const isLoaded = isFetched; +function View({ rareSats, isLoading, isError, isFetched, error, onReceive }: ViewProps) { + const { t } = useTranslation(); + const isLoaded = isFetched && !isError && !isLoading; const hasRareSats = Object.values(rareSats).length > 0; const dataReady = isLoaded && hasRareSats; + const hasError = isError && error; return ( {isLoading && } - {isError && } + {hasError && } {dataReady && } {dataReady && @@ -45,9 +55,14 @@ function View({ rareSats, isLoading, isError, isFetched }: ViewProps) { ))} {isLoaded && !hasRareSats && ( - - {"NOTHING TO SHOW WAITING FOR DESIGN"} - + + + )} @@ -55,12 +70,14 @@ function View({ rareSats, isLoading, isError, isFetched }: ViewProps) { ); } -const RareSats = ({ rareSats, isLoading, isError, isFetched }: Props) => { +const RareSats = ({ rareSats, isLoading, isError, isFetched, error, onReceive }: Props) => { return ( ); 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 7f8bb60fa721..f1683da348f9 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 @@ -5,6 +5,7 @@ import RareSats from "../../components/RareSats"; import DiscoveryDrawer from "../../components/Inscriptions/DiscoveryDrawer"; import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types"; import { useBitcoinAccountModel } from "./useBitcoinAccountModel"; +import InscriptionDetailsDrawer from "../../components/Inscriptions/DetailsDrawer"; type ViewProps = ReturnType; @@ -17,12 +18,24 @@ const View: React.FC = ({ rest, rareSats, isDrawerOpen, + selectedInscription, handleDrawerClose, + onReceive, + onInscriptionClick, + onDetailsDrawerClose, }) => ( - - + + + {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 0c7ab8993add..b6bbe99d37d3 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 @@ -1,7 +1,9 @@ import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; import useFetchOrdinals from "LLD/features/Collectibles/hooks/useFetchOrdinals"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { openModal } from "~/renderer/actions/modals"; import { setHasSeenOrdinalsDiscoveryDrawer } from "~/renderer/actions/settings"; import { hasSeenOrdinalsDiscoveryDrawerSelector } from "~/renderer/reducers/settings"; @@ -12,6 +14,7 @@ interface Props { export const useBitcoinAccountModel = ({ account }: Props) => { const dispatch = useDispatch(); const hasSeenDiscoveryDrawer = useSelector(hasSeenOrdinalsDiscoveryDrawerSelector); + const [selectedInscription, setSelectedInscription] = useState(null); const { rareSats, inscriptions, ...rest } = useFetchOrdinals({ account }); @@ -28,5 +31,28 @@ export const useBitcoinAccountModel = ({ account }: Props) => { dispatch(setHasSeenOrdinalsDiscoveryDrawer(true)); }; - return { rareSats, inscriptions, rest, isDrawerOpen, handleDrawerClose }; + const onReceive = useCallback(() => { + dispatch( + openModal("MODAL_RECEIVE", { + account, + receiveOrdinalMode: true, + }), + ); + }, [dispatch, account]); + + const onInscriptionClick = (inscription: SimpleHashNft) => setSelectedInscription(inscription); + + const onDetailsDrawerClose = () => setSelectedInscription(null); + + return { + rareSats, + inscriptions, + rest, + isDrawerOpen, + selectedInscription, + onReceive, + handleDrawerClose, + onInscriptionClick, + onDetailsDrawerClose, + }; }; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/EmptyCollection.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/EmptyCollection.tsx index 464912b45c42..9dde6dc79a93 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/EmptyCollection.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/EmptyCollection.tsx @@ -30,7 +30,7 @@ const Placeholder = styled.div` type Props = { collectionType: CollectibleType; - currencyName: string; + currencyName?: string; children?: ReactNode; }; @@ -59,8 +59,22 @@ const EmptyCollection: React.FC = ({ collectionType, currencyName, childr ); - case CollectibleTypeEnum.Ordinal: - return null; + case CollectibleTypeEnum.Inscriptions: + return ( + + + {t("ordinals.inscriptions.empty")} + + + ); + case CollectibleTypeEnum.RareSat: + return ( + + + {t("ordinals.rareSats.empty")} + + + ); default: return t("collectibles.emptyState.default"); } diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/hooks/useFetchOrdinals.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/hooks/useFetchOrdinals.ts index 01da9b38977c..17587a67bff1 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/hooks/useFetchOrdinals.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/hooks/useFetchOrdinals.ts @@ -7,12 +7,10 @@ type Props = { const useFetchOrdinals = ({ account }: Props) => { const utxosAddresses = account.bitcoinResources?.utxos?.map(utxo => utxo.address).join(",") || ""; - const { rareSats, inscriptions, isLoading, isError, isFetched } = fetchOrdinalsFromSimpleHash({ + return fetchOrdinalsFromSimpleHash({ addresses: utxosAddresses, threshold: 0, }); - - return { rareSats, inscriptions, isLoading, isError, isFetched }; }; export default useFetchOrdinals; diff --git a/apps/ledger-live-desktop/src/renderer/modals/Receive/Body.tsx b/apps/ledger-live-desktop/src/renderer/modals/Receive/Body.tsx index a78d8185ec59..821fc94f75a8 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/Receive/Body.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/Receive/Body.tsx @@ -28,6 +28,7 @@ export type Data = { startWithWarning?: boolean; receiveTokenMode?: boolean; receiveNFTMode?: boolean; + receiveOrdinalMode?: boolean; eventType?: string; isFromPostOnboardingEntryPoint?: boolean; }; @@ -58,6 +59,7 @@ export type StepProps = { token: TokenCurrency | undefined | null; receiveTokenMode: boolean; receiveNFTMode: boolean; + receiveOrdinalMode: boolean; closeModal: () => void; isAddressVerified: boolean | undefined | null; verifyAddressError: Error | undefined | null; @@ -208,6 +210,7 @@ const Body = ({ disabledSteps, receiveTokenMode: !!params.receiveTokenMode, receiveNFTMode: !!params.receiveNFTMode, + receiveOrdinalMode: !!params.receiveOrdinalMode, hideBreadcrumb, token, isAddressVerified, diff --git a/apps/ledger-live-desktop/static/i18n/en/app.json b/apps/ledger-live-desktop/static/i18n/en/app.json index 23772be65a3c..9d544abbb96e 100644 --- a/apps/ledger-live-desktop/static/i18n/en/app.json +++ b/apps/ledger-live-desktop/static/i18n/en/app.json @@ -6595,6 +6595,8 @@ "ordinals": { "inscriptions": { "seeMore": "See more inscriptions", + "empty": "To add Inscriptions, simply send them to your Bitcoin address.", + "receive": "Receive Inscription", "discoveryDrawer": { "title": "Discover Ordinals", "description": "Do you know that you may own valuable rare sats and inscriptions?", @@ -6603,11 +6605,12 @@ } }, "rareSats": { + "empty": "To add Rare Sats, simply send them to your Bitcoin address.", + "receive": "Receive Rare Sat", "title": "Rare Sats", "table": { "type": "Sat Type / Amount", - "year": "Year", - "utxo": "UTXO size" + "year": "Year" }, "rareSat": { "description": {