From 5321b97b544b6a167e8454076788b4a7d7e3c92e Mon Sep 17 00:00:00 2001 From: "arod.studio" Date: Tue, 24 Jun 2025 16:18:40 -0300 Subject: [PATCH 1/2] feat: add support for displaying Assembly Items with balance in inventory --- apps/web/app/api/owned-items/route.ts | 1 + apps/web/components/inventories.tsx | 60 +++++++++++++++++++++++---- apps/web/components/items.tsx | 30 ++++++++------ apps/web/lib/atoms.ts | 4 +- apps/web/lib/otoms.ts | 7 +++- apps/web/lib/property-utils.ts | 13 ++++-- apps/web/lib/types.ts | 2 + apps/web/lib/utils.ts | 6 ++- 8 files changed, 94 insertions(+), 29 deletions(-) diff --git a/apps/web/app/api/owned-items/route.ts b/apps/web/app/api/owned-items/route.ts index 2e04262..9d21ba0 100644 --- a/apps/web/app/api/owned-items/route.ts +++ b/apps/web/app/api/owned-items/route.ts @@ -103,6 +103,7 @@ export async function POST(request: Request) { })) : [], supply: craftCount, + balance: Number(nft.balance), }; } catch (e) { console.error(e); diff --git a/apps/web/components/inventories.tsx b/apps/web/components/inventories.tsx index 5cc7491..b3d730d 100644 --- a/apps/web/components/inventories.tsx +++ b/apps/web/components/inventories.tsx @@ -12,7 +12,7 @@ import { Input } from '@/components/ui/input'; import { ScrollArea } from '@/components/ui/scroll-area'; import { isOtomAtom } from '@/lib/otoms'; import { paths } from '@/lib/paths'; -import type { OtomItem } from '@/lib/types'; +import type { OtomItem, OwnedItem } from '@/lib/types'; import { ExternalLinkIcon } from '@radix-ui/react-icons'; import { usePathname } from 'next/navigation'; import { useDeferredValue, useEffect, useMemo, useState, type FC } from 'react'; @@ -20,9 +20,9 @@ import { useInView } from 'react-intersection-observer'; import { InlineLink } from './ui/link'; type GroupedOtomItems = { - representativeItem: OtomItem; + representativeItem: OtomItem | OwnedItem; count: number; - allItems: OtomItem[]; + allItems: (OtomItem | OwnedItem)[]; }; export const OtomsInventory: FC<{ usedCounts: Map }> = ({ usedCounts }) => { @@ -38,6 +38,8 @@ export const OtomsInventory: FC<{ usedCounts: Map }> = ({ usedCo const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError } = useGetOtomItemsForUser(); + const { data: itemsData = [] } = useGetItemsForUser(); + useEffect(() => { if ((moleculesInView || otomsInView) && hasNextPage && !isFetchingNextPage) { fetchNextPage(); @@ -53,7 +55,7 @@ export const OtomsInventory: FC<{ usedCounts: Map }> = ({ usedCo const molecules = filteredItems.filter((item) => !isOtomAtom(item)); const otoms = filteredItems.filter((item) => isOtomAtom(item)); - const groupItems = (items: OtomItem[]): GroupedOtomItems[] => { + const groupOtomItems = (items: OtomItem[]): GroupedOtomItems[] => { const groups = new Map(); for (const item of items) { if (groups.has(item.tokenId)) { @@ -72,12 +74,32 @@ export const OtomsInventory: FC<{ usedCounts: Map }> = ({ usedCo a.representativeItem.name.localeCompare(b.representativeItem.name) ); }; + const groupItems = (items: OwnedItem[]): GroupedOtomItems[] => { + const groups = new Map(); + for (const item of items) { + if (groups.has(item.tokenId)) { + const group = groups.get(item.tokenId)!; + group.count++; + group.allItems.push(item); + } else { + groups.set(item.tokenId, { + representativeItem: item, + count: 1 + (item?.balance ?? 0), + allItems: [item], + }); + } + } + return Array.from(groups.values()).sort((a, b) => + a.representativeItem.name.localeCompare(b.representativeItem.name) + ); + }; return { - molecules: groupItems(molecules), - otoms: groupItems(otoms), + molecules: groupOtomItems(molecules), + otoms: groupOtomItems(otoms), + items: groupItems(itemsData), }; - }, [data?.pages, deferredSearchTerm]); + }, [data?.pages, deferredSearchTerm, itemsData]); if (isLoading) { return ; @@ -92,6 +114,7 @@ export const OtomsInventory: FC<{ usedCounts: Map }> = ({ usedCo const moleculesAmount = inventory.molecules.reduce((acc, group) => acc + group.count, 0); const otomsAmount = inventory.otoms.reduce((acc, group) => acc + group.count, 0); + const itemsAmount = inventory.items.reduce((acc, group) => acc + group.count, 0); const isCreatePage = pathname === paths.create; return ( @@ -123,6 +146,25 @@ export const OtomsInventory: FC<{ usedCounts: Map }> = ({ usedCo disabled={isInventoryEmpty} /> + {itemsAmount > 0 && ( +
+

Assembly Items ({itemsAmount})

+
    + {inventory.items.map((group) => ( + + ))} +
+
+ {isFetchingNextPage && 'Loading more...'} +
+
+ )} + {moleculesAmount > 0 && (

Molecules ({moleculesAmount})

@@ -130,7 +172,7 @@ export const OtomsInventory: FC<{ usedCounts: Map }> = ({ usedCo {inventory.molecules.map((group) => ( @@ -158,7 +200,7 @@ export const OtomsInventory: FC<{ usedCounts: Map }> = ({ usedCo {inventory.otoms.map((group) => ( diff --git a/apps/web/components/items.tsx b/apps/web/components/items.tsx index 0824e7b..0360aaa 100644 --- a/apps/web/components/items.tsx +++ b/apps/web/components/items.tsx @@ -451,7 +451,7 @@ const ItemToCraftCard: FC = ({ item }) => { }; type OtomItemCardProps = { - representativeItem: OtomItem; + representativeItem: OtomItem | OwnedItem; count: number; usedCounts: Map; }; @@ -491,7 +491,7 @@ export const OtomItemCard: FC = ({ representativeItem, count, // Variable component with exact match criteria if (isExactMatchCriteria(b.criteria)) { - return checkCriteria(representativeItem, b.criteria); + return checkCriteria(representativeItem as OtomItem, b.criteria); } return false; @@ -504,7 +504,7 @@ export const OtomItemCard: FC = ({ representativeItem, count, hoveredState.component.itemIdOrOtomTokenId.toString() === representativeItem.tokenId) || (hoveredState.component.componentType === 'variable_otom' && isExactMatchCriteria(hoveredState.component.criteria) && - checkCriteria(representativeItem, hoveredState.component.criteria))); + checkCriteria(representativeItem as OtomItem, hoveredState.component.criteria))); function handleMouseEnter() { if (isRequiredInBlueprint && !areAllItemsUsed) { @@ -517,8 +517,10 @@ export const OtomItemCard: FC = ({ representativeItem, count, } const mass = - representativeItem.giving_atoms.reduce((acc, atom) => acc + atom.mass, 0) + - representativeItem.receiving_atoms.reduce((acc, atom) => acc + atom.mass, 0); + 'giving_atoms' in representativeItem + ? representativeItem.giving_atoms.reduce((acc, atom) => acc + atom.mass, 0) + + representativeItem.receiving_atoms.reduce((acc, atom) => acc + atom.mass, 0) + : 0; const isMolecule = !isOtomAtom(representativeItem); @@ -597,11 +599,15 @@ export const OtomItemCard: FC = ({ representativeItem, count,

{representativeItem.name}

-

{isMolecule ? 'Molecule (M)' : 'Otom'}

-

Hardness: {representativeItem.hardness.toFixed(3)}

-

Toughness: {representativeItem.toughness.toFixed(3)}

-

Ductility: {representativeItem.ductility.toFixed(3)}

-

Mass: {mass}

+ {'giving_atoms' in representativeItem && ( + <> +

{isMolecule ? 'Molecule (M)' : 'Otom'}

+

Hardness: {representativeItem.hardness.toFixed(3)}

+

Toughness: {representativeItem.toughness.toFixed(3)}

+

Ductility: {representativeItem.ductility.toFixed(3)}

+

Mass: {mass}

+ + )}

Token ID: {abbreviateHash(representativeItem.tokenId, 3, 4)}