diff --git a/src/app/components/rareSatAsset/rareSatAsset.tsx b/src/app/components/rareSatAsset/rareSatAsset.tsx new file mode 100644 index 000000000..27c763650 --- /dev/null +++ b/src/app/components/rareSatAsset/rareSatAsset.tsx @@ -0,0 +1,85 @@ +import RareSatIcon from '@components/rareSatIcon/rareSatIcon'; +import OrdinalImage from '@screens/ordinals/ordinalImage'; +import { Inscription } from '@secretkeylabs/xverse-core'; +import { BundleItem } from '@utils/rareSats'; +import styled from 'styled-components'; + +const Container = styled.div` + width: 100%; + height: 100%; +`; + +const InscriptionContainer = styled.div` + width: 100%; + height: 100%; + position: relative; + border-radius: 8px; + overflow: hidden; +`; + +const RareSatIconContainer = styled.div<{ isGallery: boolean }>((props) => ({ + display: 'flex', + position: 'absolute', + zIndex: 1, + left: props.isGallery ? 20 : 8, + top: props.isGallery ? 20 : 8, +})); + +const RareSatsContainer = styled.div((props) => ({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + aspectRatio: '1', + overflow: 'hidden', + position: 'relative', + backgroundColor: props.theme.colors.elevation1, + borderRadius: 8, +})); + +const DynamicSizeContainer = styled.div<{ isCollage: boolean }>((props) => ({ + width: props.isCollage ? '40%' : '50%', + height: props.isCollage ? '40%' : '50%', +})); + +interface Props { + item: BundleItem; + isCollage?: boolean; +} + +function RareSatAsset({ item, isCollage = false }: Props) { + const isGallery: boolean = document.documentElement.clientWidth > 360; + const isInscription = item.type === 'inscription' || item.type === 'inscribed-sat'; + + return ( + + {isInscription ? ( + + {!isCollage && !!item.rarity_ranking && item.rarity_ranking !== 'COMMON' && ( + + + + )} + + + ) : ( + + + + + + )} + + ); +} + +export default RareSatAsset; diff --git a/src/app/screens/signPsbtRequest/bundleItemsComponent.tsx b/src/app/screens/signPsbtRequest/bundleItemsComponent.tsx new file mode 100644 index 000000000..82d0de52d --- /dev/null +++ b/src/app/screens/signPsbtRequest/bundleItemsComponent.tsx @@ -0,0 +1,219 @@ +import Eye from '@assets/img/createPassword/Eye.svg'; +import Cross from '@assets/img/dashboard/X.svg'; +import IconOrdinal from '@assets/img/transactions/ordinal.svg'; +import RareSatAsset from '@components/rareSatAsset/rareSatAsset'; +import { animated, useSpring } from '@react-spring/web'; +import { getTruncatedAddress } from '@utils/helper'; +import { BundleItem, getBundleItemSubText } from '@utils/rareSats'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +const Container = styled.div((props) => ({ + display: 'flex', + flexDirection: 'column', + background: props.theme.colors.elevation1, + borderRadius: 12, + padding: '16px 16px', + justifyContent: 'center', + marginBottom: 12, +})); + +const RecipientTitleText = styled.h1((props) => ({ + ...props.theme.body_medium_m, + color: props.theme.colors.white_200, + marginBottom: 10, +})); + +const RowContainer = styled.div({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', +}); + +const TransparentButton = styled.button({ + background: 'transparent', + display: 'flex', + alignItems: 'center', + marginLeft: 10, +}); + +const Icon = styled.img((props) => ({ + marginRight: props.theme.spacing(4), + width: 32, + height: 32, + borderRadius: 30, +})); + +const TitleText = styled.h1((props) => ({ + ...props.theme.body_medium_m, + color: props.theme.colors.white_200, +})); + +const ValueText = styled.h1((props) => ({ + ...props.theme.body_medium_m, + color: props.theme.colors.white_0, +})); + +const SubValueText = styled.h1((props) => ({ + ...props.theme.body_m, + fontSize: 12, + color: props.theme.colors.white_400, +})); + +const InscriptionText = styled.h1((props) => ({ + ...props.theme.body_medium_m, + fontSize: 21, + marginTop: 24, + textAlign: 'center', + color: props.theme.colors.white[0], + overflowWrap: 'break-word', + wordWrap: 'break-word', + wordBreak: 'break-word', +})); + +const ColumnContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + justifyContent: 'flex-end', + alignItems: 'flex-end', + marginTop: 12, +}); + +const CrossContainer = styled.div({ + display: 'flex', + marginTop: 10, + justifyContent: 'flex-end', + alignItems: 'flex-end', +}); + +const OrdinalOuterImageContainer = styled.div({ + justifyContent: 'center', + alignItems: 'center', + borderRadius: 2, + display: 'flex', + flexDirection: 'column', + flex: 1, +}); + +const OrdinalImageContainer = styled.div({ + width: '50%', +}); + +const OrdinalBackgroundContainer = styled(animated.div)({ + width: '100%', + height: '100%', + top: 0, + left: 0, + bottom: 0, + right: 0, + position: 'fixed', + zIndex: 10, + background: 'rgba(18, 21, 30, 0.8)', + backdropFilter: 'blur(16px)', + padding: 16, + display: 'flex', + flexDirection: 'column', +}); + +const EyeIcon = styled.img({ + width: 20, + height: 20, +}); + +interface Props { + item: BundleItem; + userReceivesOrdinal: boolean; +} +function BundleItemsComponent({ item, userReceivesOrdinal }: Props) { + const { t } = useTranslation('translation'); + const [showOrdinal, setShowOrdinal] = useState(false); + const styles = useSpring({ + from: { + opacity: 0, + y: 24, + }, + to: { + y: 0, + opacity: 1, + }, + delay: 100, + }); + const onButtonClick = () => { + setShowOrdinal(true); + }; + + const onCrossClick = () => { + setShowOrdinal(false); + }; + const getItemId = () => { + if (item.type === 'inscription') { + return item.inscription.id; + } + if (item.type === 'inscribed-sat' || item.type === 'rare-sat') { + return item.number; + } + return ''; + }; + const itemSubText = getBundleItemSubText({ + satType: item.type, + rareSatsType: item.rarity_ranking as any, + }); + const getDetail = () => { + if (item.type === 'inscription' || item.type === 'inscribed-sat') { + return item.inscription.content_type; + } + return itemSubText; + }; + const getTitle = () => { + if (item.type === 'inscription') { + return t('COMMON.INSCRIPTION'); + } + if (item.type === 'inscribed-sat') { + return t('RARE_SATS.INSCRIBED_SAT'); + } + return t('RARE_SATS.RARE_SAT'); + }; + return ( + <> + {showOrdinal && ( + + + + cross + + + + + + + {`${getTitle()} ${getItemId()} `} + + + )} + + + {userReceivesOrdinal + ? t('CONFIRM_TRANSACTION.YOU_WILL_RECEIVE') + : t('CONFIRM_TRANSACTION.YOU_WILL_TRANSFER')} + + + + {getTitle()} + + + {getTruncatedAddress(String(getItemId()))} + + + + + {getDetail()} + + + + + ); +} + +export default BundleItemsComponent; diff --git a/src/app/screens/signPsbtRequest/index.tsx b/src/app/screens/signPsbtRequest/index.tsx index f6be05754..8caae6b05 100644 --- a/src/app/screens/signPsbtRequest/index.tsx +++ b/src/app/screens/signPsbtRequest/index.tsx @@ -5,19 +5,17 @@ import { delay } from '@common/utils/ledger'; import AccountHeaderComponent from '@components/accountHeader'; import BottomModal from '@components/bottomModal'; import ActionButton from '@components/button'; -import SatsBundle from '@components/confirmBtcTransactionComponent/bundle'; import InputOutputComponent from '@components/confirmBtcTransactionComponent/inputOutputComponent'; import InfoContainer from '@components/infoContainer'; import LedgerConnectionView from '@components/ledger/connectLedgerView'; import RecipientComponent from '@components/recipientComponent'; import TransactionDetailComponent from '@components/transactionDetailComponent'; import useBtcClient from '@hooks/useBtcClient'; -import useDetectOrdinalInSignPsbt, { InputsBundle } from '@hooks/useDetectOrdinalInSignPsbt'; +import useDetectOrdinalInSignPsbt from '@hooks/useDetectOrdinalInSignPsbt'; import useSignPsbtTx from '@hooks/useSignPsbtTx'; import useWalletSelector from '@hooks/useWalletSelector'; import Transport from '@ledgerhq/hw-transport-webusb'; import { - Bundle, getBtcFiatEquivalent, parsePsbt, psbtBase64ToHex, @@ -26,6 +24,7 @@ import { Transport as TransportType, } from '@secretkeylabs/xverse-core'; import { isLedgerAccount } from '@utils/helper'; +import { BundleItem, convertV2ToV1Bundle } from '@utils/rareSats'; import BigNumber from 'bignumber.js'; import { decodeToken } from 'jsontokens'; import { useEffect, useMemo, useState } from 'react'; @@ -35,6 +34,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { MoonLoader } from 'react-spinners'; import { SignTransactionOptions } from 'sats-connect'; import styled from 'styled-components'; +import BundleItemsComponent from './bundleItemsComponent'; const OuterContainer = styled.div` display: flex; @@ -110,9 +110,6 @@ function SignPsbtRequest() { const { t: signatureRequestTranslate } = useTranslation('translation', { keyPrefix: 'SIGNATURE_REQUEST', }); - const { t: tCommon } = useTranslation('translation', { - keyPrefix: 'COMMON', - }); const [expandInputOutputView, setExpandInputOutputView] = useState(false); const { payload, confirmSignPsbt, cancelSignPsbt, getSigningAddresses } = useSignPsbtTx(); const [isSigning, setIsSigning] = useState(false); @@ -142,7 +139,7 @@ function SignPsbtRequest() { const handleOrdinalAndOrdinalInfo = useDetectOrdinalInSignPsbt(); const [isLoading, setIsLoading] = useState(true); const [userReceivesOrdinal, setUserReceivesOrdinal] = useState(false); - const [bundleItemsData, setBundleItemsData] = useState(); + const [bundleItemsData, setBundleItemsData] = useState([]); const signingAddresses = useMemo( () => getSigningAddresses(payload.inputsToSign), [payload.inputsToSign], @@ -189,7 +186,7 @@ function SignPsbtRequest() { const checkIfUserReceivesOrdinal = async () => { try { const result = await handleOrdinalAndOrdinalInfo(parsedPsbt); - setBundleItemsData(result.bundleItemsData); + setBundleItemsData(convertV2ToV1Bundle(result.bundleItemsData)); setUserReceivesOrdinal(result.userReceivesOrdinal); } catch { navigate('/tx-status', { @@ -387,21 +384,15 @@ function SignPsbtRequest() { {t('REVIEW_TRANSACTION')} {!payload.broadcast && } - {Array.isArray(bundleItemsData) && - bundleItemsData.map((bundle, index) => ( - ( + ))} - { ? `${t('COMMON.COMBO')}` : `${getRareSatsLabelByType(satributes[0])} ${getRareSatsLabelByType(satributes[1])}`; }; + +// remove later when we fix sign psbt screen +export type BundleItem = + | { + type: 'rare-sat'; + rarity_ranking: RodarmorRareSatsType; + number: string; + } + | { + type: 'inscribed-sat'; + rarity_ranking: RodarmorRareSatsType; + number: string; + inscription: { + id: string; + content_type: string; + }; + } + | { + type: 'inscription'; + rarity_ranking: RodarmorRareSatsType; + inscription: { + id: string; + content_type: string; + }; + } + | { + type: 'unknown'; + rarity_ranking: 'unknown'; + }; + +// remove later when we fix sign psbt screen +export const convertV2ToV1Bundle = (v2: any): BundleItem[] => { + const bundleItems: BundleItem[] = []; + v2.forEach((item) => { + item.satRanges.forEach((satRange) => { + satRange.inscriptions.forEach((inscription) => { + bundleItems.push({ + type: 'inscription', + rarity_ranking: 'COMMON', + inscription, + }); + }); + }); + }); + return bundleItems; +}; + +// remove later when we fix sign psbt screen +export const getBundleItemSubText = ({ + satType, + rareSatsType, +}: { + satType: any; + rareSatsType: RareSatsType; +}) => + ({ + inscription: t('COMMON.INSCRIPTION'), + 'rare-sat': t('RARE_SATS.SAT_TYPES.RARE_SAT', { + type: getRareSatsLabelByType(rareSatsType ?? 'unknown'), + }), + 'inscribed-sat': t('RARE_SATS.SAT_TYPES.INSCRIBED_RARE_SAT', { + type: getRareSatsLabelByType(rareSatsType ?? 'unknown'), + }), + unknown: t('RARE_SATS.SAT_TYPES.UNKNOWN_RARE_SAT'), + }[satType]);