We are now going to create all the components that allow us to interact with the Asset Register.
Modify NftToken.tsx
so it looks like
import type { AssetModel } from '@futureverse/asset-register/models';
import { useState } from 'react';
import { Skeleton } from './ui/skeleton';
import { useGetSlots } from '@/hooks/useGetSlots';
import { useGetAsset } from '@futureverse/asset-register-react/v2';
import { COLLECTION_ID } from '@/lib/utils';
import { useAuth } from '@futureverse/auth-react';
import NftAssetLinker from './NftAssetLinker';
type NftTokenProps = {
asset: AssetModel;
};
export default function NftToken({ asset }: NftTokenProps) {
const { userSession } = useAuth();
const [equipperOpen, setEquipperOpen] = useState<boolean | null>(null);
const { assetTree } = useGetAsset({
// @ts-expect-error - Error will be fixed in SDK
tokenId: asset?.rawData?.tokenId,
collectionId: `7672:root:${COLLECTION_ID}`,
addresses: [userSession?.eoa, userSession?.futurepass],
});
const { data: slots, isLoading: isSlotsLoading } = useGetSlots(
// @ts-expect-error - Error will be fixed in SDK
asset?.rawData?.tokenId,
assetTree
);
return (
<>
<div
className="cursor-pointer"
onClick={() => {
setEquipperOpen(true);
}}
>
<div className="aspect-square relative cursor-pointer">
{asset?.metadata?.properties?.image ? (
<img
src={asset?.metadata?.properties?.image}
alt="alt"
className="aspect-square relative pointer-events-none cursor-pointer rounded-md overflow-hidden"
/>
) : (
<Skeleton className="aspect-square animate-pulse w-full" />
)}
<div className="absolute top-2 left-0 text-left text-sm md:text-base lg:text-lg leading-none font-semibold p-2 bg-slate-400 bg-opacity-50 backdrop-blur-sm">
{/* @ts-expect-error - Error will be fixed in SDK */}
Token {asset?.rawData?.tokenId}
</div>
{isSlotsLoading && (
<div className="absolute bottom-0 translate-y-[calc(50%-20px)] left-0 w-full p-4 grid grid-cols-3 gap-4 ">
<Skeleton className="bg-slate-500 aspect-square rounded-md drop-shadow-md " />
<Skeleton className="bg-slate-500 aspect-square rounded-md drop-shadow-md " />
<Skeleton className="bg-slate-500 aspect-square rounded-md drop-shadow-md " />
</div>
)}
{!isSlotsLoading && slots && (
<div className="absolute bottom-0 translate-y-[calc(50%-20px)] left-0 w-full p-4 grid grid-cols-3 gap-4 ">
{slots.map(slot => {
return (
slot.equippedAsset &&
slot?.equippedAsset?.metadata?.properties?.image && (
<div
className="aspect-square border-2 border-slate-300 rounded-md drop-shadow-md"
key={`${
// @ts-expect-error - Error will be fixed in SDK
asset?.rawData?.tokenId
}-${slot?.schemaName}`}
>
<img
src={slot?.equippedAsset?.metadata?.properties?.image}
alt={slot.schemaName}
/>
</div>
)
);
})}
</div>
)}
</div>
</div>
{equipperOpen && (
<NftAssetLinker
isSlotsLoading={isSlotsLoading}
slots={slots}
// @ts-expect-error - Error will be fixed in SDK
selectedId={asset?.rawData?.tokenId}
setEquipperOpen={setEquipperOpen}
/>
)}
</>
);
}
Create src/components/NftAssetLinker.tsx
using the following content
import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog';
import {
useCallback,
useMemo,
useState,
type Dispatch,
type SetStateAction,
} from 'react';
import { useAssets, useGetAsset } from '@futureverse/asset-register-react/v2';
import {
CLOTHING_COLLECTION_ID,
COLLECTION_ID,
HEAD_COLLECTION_ID,
NECK_COLLECTION_ID,
} from '@/lib/utils';
import { useAuth } from '@futureverse/auth-react';
import { Skeleton } from './ui/skeleton';
import type { NftAssetLink } from '@futureverse/asset-register';
import ARModal from './ARModal';
import { useQueryClient } from '@tanstack/react-query';
import type { AssetSlot } from '@futureverse/asset-register/v2';
import { NftAssetLinkerAccessories } from './NftAssetLinkerAccessories';
import { NftAssetLinkerSlots } from './NftAssetLinkerSlots';
export type ARCommand = {
type: string;
action: 'create' | 'delete';
args: [
string,
`${string}:${string}:${string}:${string}:${string}:${string}`,
`${string}:${string}:${string}:${string}:${string}:${string}`,
];
};
type NftAssetLinkProps = {
selectedId: string;
slots: Array<AssetSlot> | null | undefined;
isSlotsLoading: boolean;
setEquipperOpen: Dispatch<SetStateAction<boolean | null>>;
};
export type SelectedItems =
| Array<{ type: string; collectionId: string; tokenId: string }>
| undefined;
export default function NftAssetLinker({
selectedId,
slots,
isSlotsLoading,
setEquipperOpen,
}: NftAssetLinkProps) {
const { userSession } = useAuth();
const queryClient = useQueryClient();
const [showModal, setShowModal] = useState(false);
const [selectedItems, setSelectedItems] = useState<SelectedItems>();
const [removalPaths, setRemovalPaths] = useState<
Array<{ path: string; collectionId: string; tokenId: string }>
>([]);
const assetQueryParamsHead = useMemo(
() => ({
first: 5,
addresses: [userSession?.eoa, userSession?.futurepass],
collectionIds: [`7672:root:${HEAD_COLLECTION_ID}`],
}),
[userSession]
);
const assetQueryParamsClothes = useMemo(
() => ({
first: 5,
addresses: [userSession?.eoa, userSession?.futurepass],
collectionIds: [`7672:root:${CLOTHING_COLLECTION_ID}`],
}),
[userSession]
);
const assetQueryParamsNeck = useMemo(
() => ({
first: 5,
addresses: [userSession?.eoa, userSession?.futurepass],
collectionIds: [`7672:root:${NECK_COLLECTION_ID}`],
}),
[userSession]
);
const { assets: clothesAssets, reactQuery: clothesReactQuery } = useAssets(
assetQueryParamsClothes
);
const { assets: hatAssets, reactQuery: hatReactQuery } =
useAssets(assetQueryParamsHead);
const { assets: neckAssets, reactQuery: neckReactQuery } =
useAssets(assetQueryParamsNeck);
const itemsToLoad = useMemo(
() => [
{ name: 'Clothes', data: clothesAssets, reactQuery: clothesReactQuery },
{ name: 'Neck', data: neckAssets, reactQuery: neckReactQuery },
{ name: 'Hat', data: hatAssets, reactQuery: hatReactQuery },
],
[
clothesAssets,
clothesReactQuery,
hatAssets,
hatReactQuery,
neckAssets,
neckReactQuery,
]
);
const arAsset = useGetAsset({
tokenId: selectedId.toString(),
collectionId: `7672:root:${COLLECTION_ID}`,
addresses: [userSession?.eoa, userSession?.futurepass],
});
const linkedAssets = useMemo(() => {
if (!arAsset?.asset) {
return;
}
return (arAsset?.asset?.links as NftAssetLink)?.childLinks
? (arAsset?.asset?.links as NftAssetLink)?.childLinks
: [];
}, [arAsset?.asset]);
const getLinkedAsset = useCallback(
(path: string) => {
return linkedAssets?.filter((link: { path: string }) =>
link.path.includes(path)
);
},
[linkedAssets]
);
const prepareDelete = useCallback(
(ops: ARCommand[], path: string) => {
const linkedItems = getLinkedAsset(path);
if (!linkedItems) {
return;
}
linkedItems.forEach(link => {
ops.push({
type: 'asset-link',
action: 'delete',
args: [
path,
`did:fv-asset:7672:root:${COLLECTION_ID}:${selectedId}`,
`did:fv-asset:${link?.asset?.collectionId as `${string}:${string}:${string}`}:${link?.asset?.tokenId}`,
],
});
});
},
[getLinkedAsset, selectedId]
);
const operations = useMemo(() => {
const ops: Array<ARCommand> = [];
if (removalPaths.length > 0) {
removalPaths.forEach(removal => {
ops.push({
type: 'asset-link',
action: 'delete',
args: [
removal.path,
`did:fv-asset:7672:root:${COLLECTION_ID}:${selectedId}`,
`did:fv-asset:${removal.collectionId as `${string}:${string}:${string}`}:${removal.tokenId}`,
],
});
});
}
selectedItems?.forEach(item => {
const selectedItem = item.type;
prepareDelete(ops, `equippedWith_${selectedItem}`);
ops.push({
type: 'asset-link',
action: 'create',
args: [
`equippedWith_${selectedItem}`,
`did:fv-asset:7672:root:${COLLECTION_ID}:${selectedId}`,
`did:fv-asset:${item?.collectionId as `${string}:${string}:${string}`}:${item?.tokenId}`,
],
});
});
return ops;
}, [removalPaths, selectedItems, selectedId, prepareDelete]);
const successCallBack = useCallback(async () => {
setSelectedItems(undefined);
setRemovalPaths([]);
await queryClient.invalidateQueries({
predicate: query =>
query.queryKey[0] === 'GetAsset' &&
(query.queryKey[1] as { tokenId: string })?.tokenId === selectedId,
});
setTimeout(() => {
void (async () => {
await queryClient.invalidateQueries({
queryKey: ['slots', selectedId],
});
})();
}, 2000);
}, [queryClient, selectedId]);
return (
<>
<Dialog
open={true}
defaultOpen={true}
onOpenChange={() => setEquipperOpen(null)}
>
<DialogContent
className="max-w-[460px] md:max-w-[800px] lg:max-w-[900px] xl:max-w-[1200px] p-6 w-[90vw] border-white"
onInteractOutside={e => {
e.preventDefault();
}}
>
{!arAsset?.asset && (
<>
<DialogHeader>
<DialogTitle className="text-md -mb-2">
Loading Token Links
</DialogTitle>
</DialogHeader>
<div className="py-16 px-24 text-center">Loading...</div>
</>
)}
{arAsset?.asset && (
<>
<DialogHeader>
<DialogTitle className="text-md -mb-2">Token Links</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 md:grid-cols-6 gap-4">
<div className="col-span-2 image-wrap md:aspect-square text-center flex justify-center">
{arAsset?.asset?.metadata?.properties?.image ? (
<img
src={arAsset?.asset?.metadata?.properties?.image}
alt="alt"
className="max-w-[250px] md:max-w-none md:aspect-square relative pointer-events-none cursor-pointer"
/>
) : (
<Skeleton className="aspect-square animate-pulse w-full" />
)}
</div>
<div className="col-span-full md:col-span-4 flex flex-col gap-8">
<NftAssetLinkerSlots
selectedId={selectedId}
slots={slots}
isSlotsLoading={isSlotsLoading}
removalPaths={removalPaths}
setRemovalPaths={setRemovalPaths}
/>
<NftAssetLinkerAccessories
selectedId={selectedId}
slots={slots}
selectedItems={selectedItems}
setSelectedItems={setSelectedItems}
operations={operations}
setShowModal={setShowModal}
itemsToLoad={itemsToLoad}
removalPaths={removalPaths}
/>
</div>
</div>
</>
)}
</DialogContent>
</Dialog>
{showModal && (
<ARModal
operations={operations}
setShowModal={setShowModal}
successCallBack={successCallBack}
/>
)}
</>
);
}
Create src/components/NftAssetLinkerSlots.tsx
using the following content
import { type AssetSlot } from '@futureverse/asset-register/v2';
import { useQueryClient } from '@tanstack/react-query';
import React, { type Dispatch } from 'react';
import Spinner from './Spinner';
import { Skeleton } from './ui/skeleton';
import { Button } from './ui/button';
type SlotsTabProps = {
selectedId: string;
isSlotsLoading: boolean;
slots: Array<AssetSlot> | null | undefined;
removalPaths: Array<{ path: string; collectionId: string; tokenId: string }>;
setRemovalPaths: Dispatch<
React.SetStateAction<
Array<{ path: string; collectionId: string; tokenId: string }>
>
>;
};
export const NftAssetLinkerSlots: React.FC<SlotsTabProps> = ({
selectedId,
isSlotsLoading,
slots,
removalPaths,
setRemovalPaths,
}) => {
const queryClient = useQueryClient();
return (
<div className="w-full">
<div className="col-span-full mb-2 flex flex-row gap-2 items-baseline">
<h3 className="text-xl font-bold">Slots</h3>{' '}
<span
className="text-xs text-start cursor-pointer inline-flex leading-none"
onClick={() => {
void queryClient.invalidateQueries({
queryKey: ['slots', selectedId],
});
}}
>
[ <span className="underline">REFRESH</span>
]
</span>
</div>
{isSlotsLoading && (
<div className="flex flex-row gap-2">
<Spinner /> Checking Links
</div>
)}
{!isSlotsLoading && !slots && (
<div className="flex flex-row ">Nothing equipped</div>
)}
{!isSlotsLoading && slots && (
<div className="grid grid-cols-5 ">
{slots?.map(slot => (
<div
key={`${slot?.schemaName}`}
className={`flex flex-col p-1 rounded-md `}
>
<div
className={`flex flex-col gap-1 relative justify-between p-2 border-[1px] rounded-md ${removalPaths.some(removal => removal.collectionId === slot?.equippedAsset?.collectionId && removal.tokenId === (slot?.equippedAsset?.tokenId ?? '')) ? 'bg-red-500 ' : 'bg-slate-700'} `}
>
<div className="flex flex-row justify-start uppercase text-xs font-extrabold tracking-widest mb-1">
{slot?.schemaName?.replace('richie', '')}
</div>
{slot?.equippedAsset?.metadata?.properties?.image ? (
<img
src={slot?.equippedAsset?.metadata?.properties?.image}
alt={slot?.schemaName?.replace('richie', '')}
className="object-cover rounded-lg"
/>
) : (
<Skeleton className="aspect-square animate-none bg-slate-400 bg-opacity-15 object-cover rounded-lg" />
)}
<div className="text-xs whitespace-nowrap overflow-hidden text-ellipsis">
{slot?.equippedAsset
? slot?.equippedAsset?.metadata?.properties?.name
: 'Slot Empty'}
</div>
{slot?.equippedAsset && (
<Button
className={`absolute top-1 right-1 leading-none p-0 w-6 h-6 ${removalPaths.some(removal => removal.collectionId === slot?.equippedAsset?.collectionId && removal.tokenId === (slot?.equippedAsset?.tokenId ?? '')) ? 'bg-white text-red-500 hover:text-background' : 'bg-orange-500'} `}
onClick={() => {
setRemovalPaths(() => {
const existingIndex = removalPaths.findIndex(
removal =>
removal.collectionId ===
slot?.equippedAsset?.collectionId &&
removal.tokenId ===
(slot?.equippedAsset?.tokenId ?? '')
);
if (existingIndex !== -1) {
return removalPaths.filter(
removal =>
removal.collectionId !==
slot?.equippedAsset?.collectionId ||
removal.tokenId !==
(slot?.equippedAsset?.tokenId ?? '')
);
} else {
return [
...removalPaths,
{
path: `equippedWith_${slot?.schemaName}`,
collectionId: slot?.equippedAsset?.collectionId,
tokenId: slot?.equippedAsset?.tokenId ?? '',
},
];
}
});
}}
>
{removalPaths.some(
removal =>
removal.collectionId ===
slot?.equippedAsset?.collectionId &&
removal.tokenId === (slot?.equippedAsset?.tokenId ?? '')
)
? '+'
: 'x'}
</Button>
)}
</div>
</div>
))}
</div>
)}
</div>
);
};
Create src/components/NftAssetLinkerAccessories.tsx
using the following content
import React, { type Dispatch, type SetStateAction } from 'react';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { Card, CardContent } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { Checkbox } from '@/components/ui/checkbox';
import { Button } from '@/components/ui/button';
import {
type DefinedUseInfiniteQueryResult,
type InfiniteData,
useQueryClient,
} from '@tanstack/react-query';
import type { ARCommand, SelectedItems } from './NftAssetLinker';
import type { ARError, AssetSlot } from '@futureverse/asset-register/v2';
import type {
AssetModel,
AssetsModel,
} from '@futureverse/asset-register/models';
import { SftAssetLink } from '@futureverse/asset-register';
type AccessoriesTabProps = {
itemsToLoad: Array<{
name: string;
data: Array<AssetModel>;
reactQuery: DefinedUseInfiniteQueryResult<
InfiniteData<AssetsModel, unknown>,
ARError
>;
}>;
selectedId: string;
selectedItems: SelectedItems;
setSelectedItems: Dispatch<SetStateAction<SelectedItems>>;
slots: Array<AssetSlot> | null | undefined;
removalPaths: Array<{ path: string; collectionId: string; tokenId: string }>;
operations: Array<ARCommand>;
setShowModal: (value: boolean) => void;
};
export const NftAssetLinkerAccessories: React.FC<AccessoriesTabProps> = ({
itemsToLoad,
selectedId,
selectedItems,
setSelectedItems,
slots,
removalPaths,
operations,
setShowModal,
}) => {
const queryClient = useQueryClient();
return (
<div className="w-full flex flex-col gap-2">
<div className="col-span-full mb-2 flex flex-row gap-2 items-baseline">
<h3 className="text-xl font-bold">Accessories</h3>{' '}
<span
className="text-xs text-start cursor-pointer inline-flex leading-none"
onClick={() => {
void queryClient.invalidateQueries({
queryKey: ['slots', selectedId],
});
}}
>
[ <span className="underline">REFRESH</span>
]
</span>
</div>
<Tabs defaultValue="clothes" className="w-full">
<TabsList className="grid w-full grid-cols-3">
{itemsToLoad.map(item => (
<TabsTrigger
key={`tab-${item?.name}`}
value={item?.name.toLowerCase()}
>
{item?.name}
</TabsTrigger>
))}
</TabsList>
{itemsToLoad.map(item => (
<TabsContent
key={`content-${item?.name}`}
value={item?.name.toLowerCase()}
>
<Card>
<CardContent className="grid grid-cols-5 gap-4 p-2 md:p-6">
{item?.reactQuery.isLoading && (
<>
<Skeleton className="aspect-square animate-pulse" />
<Skeleton className="aspect-square animate-pulse" />
<Skeleton className="aspect-square animate-pulse" />
<Skeleton className="aspect-square animate-pulse" />
<Skeleton className="aspect-square animate-pulse" />
</>
)}
{item?.data
?.sort((a, b) => {
return Number(a?.tokenId) > Number(b?.tokenId) ? 1 : -1;
})
?.map(data => {
const ownershipBalance =
//@ts-expect-error - needs to be fixed in future
data?.rawData?.ownership?.balancesOf?.reduce(
(acc: number, balance: { balance: number }) => {
return balance?.balance;
}
);
const linkedTotal =
(data?.links as SftAssetLink)?.parentLinks?.length ?? 0;
const remaining = ownershipBalance - linkedTotal;
const typeName = `${
(data?.schema?.name?.charAt(0)?.toLowerCase() ?? '') +
(data?.schema?.name?.slice(1) ?? '')
}`;
const itemSelected =
selectedItems
?.find(selectedItem => selectedItem.type === typeName)
?.tokenId.toString() === data?.tokenId;
const alreadyEquipped = slots
?.filter(
slot =>
slot?.schemaName.toLowerCase() ===
data?.schema?.name.toLowerCase()
)
.some(
slot => slot?.equippedAsset?.tokenId === data?.tokenId
);
return (
<div
key={data?.tokenId}
className={`w-full flex items-center justify-between ${remaining === 0 || alreadyEquipped ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
onClick={() => {
if (itemSelected) {
//remove the item from the selected items and UPDATE the selected items
const newItems = selectedItems.filter(
i => i.type !== typeName
);
setSelectedItems([...newItems]);
} else {
if (remaining > 0 && !alreadyEquipped) {
setSelectedItems([
...(selectedItems ?? []),
{
type: typeName,
collectionId: data.collectionId,
tokenId: data.tokenId,
},
]);
}
}
}}
>
<div className="w-full flex items-center relative">
<div className="w-full flex flex-col gap-1">
<div className="relative">
<img
src={data?.metadata?.properties?.image ?? ''}
alt={data?.metadata?.properties?.name}
className={`object-cover rounded-lg border-[2px] ${alreadyEquipped ? 'border-white' : itemSelected ? 'border-orange-500' : 'border-transparent'}`}
/>
{alreadyEquipped && (
<div className="absolute bottom-1.5 left-1.5 cursor-pointer leading-none bg-slate-300 p-1 rounded-md text-xs font-black text-orange-500">
EQUIPPED
</div>
)}
</div>
<div className="text-xs whitespace-nowrap overflow-hidden text-ellipsis">
{data?.metadata?.properties?.name}
</div>
<div className="absolute top-1.5 left-1.5 cursor-pointer leading-none bg-slate-300 py-1.5 px-2 rounded-md text-xs font-black text-slate-900">
{remaining}
</div>
{itemSelected && (
<Checkbox
id={`${item?.name}-${data?.tokenId}`}
className="absolute top-1.5 right-1.5 cursor-pointer"
checked={itemSelected}
/>
)}
</div>
</div>
</div>
);
})}
</CardContent>
</Card>
</TabsContent>
))}
</Tabs>
{(removalPaths.length > 0 || operations.length > 0) && (
<div className="grid grid-cols-1 gap-4 row-auto">
<Button onClick={() => setShowModal(true)}>Check Changes</Button>
</div>
)}
</div>
);
};
Create src/components/ARModal.tsx
using the following content
import { useEffect, useMemo, useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog';
import { Button } from './ui/button';
import { type ARCommand } from './NftAssetLinker';
import {
useGetARTM,
useGetTransaction,
useSignAndSubmitTransaction,
} from '@futureverse/asset-register-react/v2';
import { useAuth } from '@futureverse/auth-react';
import { cn } from '@/lib/utils';
type ModalProps = {
operations: Array<ARCommand>;
setShowModal: (show: boolean) => void;
successCallBack?: () => void;
};
export default function ARModal({
operations,
setShowModal,
successCallBack,
}: ModalProps) {
const { userSession } = useAuth();
const [callBackCalled, setCallBackCalled] = useState(false);
const operationsText = useMemo(() => {
return JSON.stringify(operations, null, 2);
}, [operations]);
const {
reactQuery: { refetch: getARTM },
} = useGetARTM(
{
address: userSession?.eoa ?? '',
operations,
},
{ enabled: false }
);
const { submitAsync, transaction } = useSignAndSubmitTransaction();
const submitARTM = async () => {
const { data: artm } = await getARTM();
if (!artm) {
return;
}
return submitAsync({ artm, check: false });
};
const txDataComplete = useGetTransaction(
{ transactionHash: transaction },
{
refetchInterval: query => {
return query.state.data?.status === 'PENDING' ? 2000 : false;
},
}
);
useEffect(() => {
if (
txDataComplete?.reactQuery?.data?.status === 'SUCCESS' &&
callBackCalled === false
) {
successCallBack?.();
setCallBackCalled(true);
}
}, [txDataComplete?.reactQuery?.data, successCallBack, callBackCalled]);
return (
<Dialog
open={true}
defaultOpen={true}
onOpenChange={() => setShowModal(false)}
>
<DialogContent
className="max-w-[450px] p-6 w-[90vw] border-white"
onInteractOutside={e => {
e.preventDefault();
}}
>
<DialogHeader>
<DialogTitle className="text-md -mb-2">Check The Changes</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4">
{txDataComplete?.reactQuery?.data?.status !== 'SUCCESS' && (
<div className="w-full">
<div className="w-full bg-foreground text-background font-mono text-xs p-2 rounded-lg break-all">
<pre className="overflow-scroll max-h-[20dvh] max-w-[275px] md:max-w-none">
{operationsText}
</pre>
</div>
</div>
)}
{!transaction && (
<Button onClick={() => submitARTM()}>Save Changes</Button>
)}
{transaction && (
<div className="w-full">
<div className="w-full text-xs">TRANSACTION ID</div>
<div className="w-full bg-foreground text-background font-mono text-xs p-2 rounded-lg break-all">
{transaction}
</div>
</div>
)}
{txDataComplete?.reactQuery?.data && (
<div className="w-full">
<div
className={cn(
`w-full font-mono text-xs p-2 rounded-lg break-all`,
`${txDataComplete?.reactQuery?.data?.status === 'PENDING' ? 'animate-pulse' : txDataComplete?.reactQuery?.data?.status === 'SUCCESS' ? 'bg-green-400 text-background' : 'bg-red-600 text-white'}`
)}
>
{txDataComplete?.reactQuery?.data?.status}
</div>
</div>
)}
{txDataComplete?.reactQuery?.data?.status === 'FAILED' && (
<div className="w-full">
<div
className={
'w-full font-mono text-xs p-2 rounded-lg break-all bg-red-600 text-white'
}
>
{txDataComplete?.reactQuery?.data?.error?.message}
</div>
</div>
)}
{txDataComplete?.reactQuery?.data &&
txDataComplete?.reactQuery?.data?.status !== 'PENDING' && (
<Button
onClick={() => {
setShowModal(false);
}}
>
Close
</Button>
)}
</div>
</DialogContent>
</Dialog>
);
}