From f2da8c5edbc9fc32d4489f1db951fe8f6bc13e6f Mon Sep 17 00:00:00 2001 From: Johnny Cage Wins <2409861+0xjohnnycagewins@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:02:00 -0500 Subject: [PATCH 1/2] updated redeemable offers + UI fixes --- app/bot/src/offer/post-escrow-message.ts | 4 +- .../expire-listing-task-request-handler.ts | 20 ++++--- .../expire-offer-task-request-handler.ts | 20 ++++--- app/frontend/next.config.js | 4 +- app/frontend/package.json | 1 + app/frontend/src/app/me/page.tsx | 6 ++- app/storybook/src/swap/swap-card.stories.tsx | 54 +++++++++++++++++++ .../src/helpers/offer/is-offer-redeemable.ts | 47 ++++++++++++++++ .../erc721-transfer-event-handler.ts | 5 +- lib/firestore/src/crud/nft/escrow-nft.ts | 4 +- .../src/crud/nft/get-nft-snapshot.ts | 11 ---- .../src/crud/nft/remove-nft-owner.ts | 4 +- lib/firestore/src/crud/nft/set-nft-owner.ts | 4 +- .../helpers/offer/get-redeemable-offers.ts | 14 ----- .../src/components/offer/card/offer-card.tsx | 30 +++++------ lib/ui/src/components/swap/card/swap-card.tsx | 42 ++++++++++----- .../swap/card/swap-stack-picture.tsx | 17 ------ .../collection/collection-navigation.tsx | 5 +- .../src/pages/profile/profile-navigation.tsx | 5 +- lib/ui/src/pages/user/user-navigation.tsx | 5 +- package.json | 2 +- pnpm-lock.yaml | 17 ++++++ 22 files changed, 215 insertions(+), 106 deletions(-) create mode 100644 app/storybook/src/swap/swap-card.stories.tsx create mode 100644 lib/backend/src/helpers/offer/is-offer-redeemable.ts delete mode 100644 lib/firestore/src/crud/nft/get-nft-snapshot.ts delete mode 100644 lib/model/src/helpers/offer/get-redeemable-offers.ts delete mode 100644 lib/ui/src/components/swap/card/swap-stack-picture.tsx diff --git a/app/bot/src/offer/post-escrow-message.ts b/app/bot/src/offer/post-escrow-message.ts index dda6fcca8..22d528f35 100644 --- a/app/bot/src/offer/post-escrow-message.ts +++ b/app/bot/src/offer/post-escrow-message.ts @@ -2,7 +2,7 @@ import { sendToThread } from '@echo/bot/helpers/send-to-thread' import { buildOfferLinkButton } from '@echo/bot/offer/build-offer-link-button' import { getOfferThreadOnEchoChannel } from '@echo/bot/offer/get-offer-thread-on-echo-channel' import { getEscrowedNftSnapshot } from '@echo/firestore/crud/nft/get-escrowed-nft-snapshot' -import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot' +import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index' import { getUserByUsername } from '@echo/firestore/crud/user/get-user-by-username' import { OfferError } from '@echo/model/constants/errors/offer-error' import { offerReceiverNftItems } from '@echo/model/helpers/offer/offer-receiver-nft-items' @@ -22,7 +22,7 @@ async function getUserDiscordId(username: string) { } async function isNftFromItemInEscrow(item: NftItem): Promise { - const nftSnapshot = await getNftSnapshot(item.token) + const nftSnapshot = await getNftSnapshotByIndex(item.token) if (!isNil(nftSnapshot)) { return pipe(prop('id'), getEscrowedNftSnapshot, andThen(complement(isNil)), otherwise(always(false)))(nftSnapshot) } diff --git a/app/firestore-functions/src/tasks/request-handlers/expire-listing-task-request-handler.ts b/app/firestore-functions/src/tasks/request-handlers/expire-listing-task-request-handler.ts index 2ad119c30..7e55f16a2 100644 --- a/app/firestore-functions/src/tasks/request-handlers/expire-listing-task-request-handler.ts +++ b/app/firestore-functions/src/tasks/request-handlers/expire-listing-task-request-handler.ts @@ -2,15 +2,21 @@ import { ListingError } from '@echo/firestore-functions/constants/errors/listing import { error } from '@echo/firestore-functions/constants/logger' import type { ExpireListingTaskData } from '@echo/firestore-functions/tasks/expire-listing-task' import { expireListing } from '@echo/firestore/crud/listing/expire-listing' +import { getListing } from '@echo/firestore/crud/listing/get-listing' +import type { Listing } from '@echo/model/types/listing' import { withSlugSchema } from '@echo/model/validators/slug-schema' -import { otherwise, pipe } from 'ramda' +import type { Nullable } from '@echo/utils/types/nullable' +import { always, isNil, otherwise, pipe } from 'ramda' export async function expireListingTaskRequestHandler(data: ExpireListingTaskData) { const { slug } = withSlugSchema.parse(data) - await pipe( - expireListing, - otherwise((err) => { - error({ err, listing: { slug } }, ListingError.Expire) - }) - )(slug) + const listing = await pipe(getListing, otherwise(always>(undefined)))(slug) + if (!isNil(listing) && !listing.locked) { + await pipe( + expireListing, + otherwise((err) => { + error({ err, listing: { slug } }, ListingError.Expire) + }) + )(slug) + } } diff --git a/app/firestore-functions/src/tasks/request-handlers/expire-offer-task-request-handler.ts b/app/firestore-functions/src/tasks/request-handlers/expire-offer-task-request-handler.ts index d353186d3..cc618bd6e 100644 --- a/app/firestore-functions/src/tasks/request-handlers/expire-offer-task-request-handler.ts +++ b/app/firestore-functions/src/tasks/request-handlers/expire-offer-task-request-handler.ts @@ -2,15 +2,21 @@ import { OfferError } from '@echo/firestore-functions/constants/errors/offer-err import { error } from '@echo/firestore-functions/constants/logger' import type { ExpireOfferTaskData } from '@echo/firestore-functions/tasks/expire-offer-task' import { expireOffer } from '@echo/firestore/crud/offer/expire-offer' +import { getOffer } from '@echo/firestore/crud/offer/get-offer' +import type { Offer } from '@echo/model/types/offer' import { withSlugSchema } from '@echo/model/validators/slug-schema' -import { otherwise, pipe } from 'ramda' +import type { Nullable } from '@echo/utils/types/nullable' +import { always, isNil, otherwise, pipe } from 'ramda' export async function expireOfferTaskRequestHandler(data: ExpireOfferTaskData) { const { slug } = withSlugSchema.parse(data) - await pipe( - expireOffer, - otherwise((err) => { - error({ err, offer: { slug } }, OfferError.Expire) - }) - )(slug) + const offer = await pipe(getOffer, otherwise(always>(undefined)))(slug) + if (!isNil(offer) && !offer.locked) { + await pipe( + expireOffer, + otherwise((err) => { + error({ err, offer: { slug } }, OfferError.Expire) + }) + )(slug) + } } diff --git a/app/frontend/next.config.js b/app/frontend/next.config.js index 7de13f73e..968ab8462 100644 --- a/app/frontend/next.config.js +++ b/app/frontend/next.config.js @@ -27,7 +27,9 @@ const nextConfig = { }, swcMinify: true, webpack: (config) => { - config.devtool = 'hidden-source-map' + if (process.env.NODE_ENV === 'production') { + config.devtool = 'hidden-source-map' + } return config } } diff --git a/app/frontend/package.json b/app/frontend/package.json index 8b867c4a4..16433b9f9 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -29,6 +29,7 @@ "next": "14.2.20", "next-auth": "5.0.0-beta.25", "next-intl": "3.25.3", + "p-filter": "4.1.0", "ramda": "0.30.1", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/app/frontend/src/app/me/page.tsx b/app/frontend/src/app/me/page.tsx index e2ed22563..ed3de1ee5 100644 --- a/app/frontend/src/app/me/page.tsx +++ b/app/frontend/src/app/me/page.tsx @@ -1,3 +1,4 @@ +import { isOfferRedeemable } from '@echo/backend/helpers/offer/is-offer-redeemable' import { getListingsForCreator } from '@echo/firestore/crud/listing/get-listings-for-creator' import { getPendingListingsForUser } from '@echo/firestore/crud/listing/get-pending-listings-for-user' import { getNftsForOwner } from '@echo/firestore/crud/nft/get-nfts-for-owner' @@ -9,11 +10,12 @@ import { toListingsWithRole } from '@echo/frontend/lib/helpers/listing/to-listin import { toOffersWithRole } from '@echo/frontend/lib/helpers/offer/to-offers-with-role' import { otherwiseEmptyArray } from '@echo/frontend/lib/helpers/otherwise-empty-array' import { toSwapsWithRole } from '@echo/frontend/lib/helpers/swap/to-swaps-with-role' -import { getRedeemableOffers } from '@echo/model/helpers/offer/get-redeemable-offers' import type { User } from '@echo/model/types/user' import type { SwapDetailsSearchParams } from '@echo/routing/types/frontend/search-params/swap-details-search-params' import { swapDetailsSearchParamsTransformSchema } from '@echo/routing/validators/frontend/swap/swap-details-search-params-transform-schema' import { ProfilePage } from '@echo/ui/pages/profile/profile-page' +import type { OfferWithRole } from '@echo/ui/types/offer-with-role' +import pFilter from 'p-filter' import { always, andThen, otherwise, pipe, prop } from 'ramda' interface Props { @@ -41,7 +43,7 @@ async function render({ user, searchParams }: Props) { andThen(toOffersWithRole(user)), otherwiseEmptyArray )(user) - const redeemableOffers = getRedeemableOffers(offers) + const redeemableOffers = await pFilter(offers, isOfferRedeemable(user.username)) const offersCount = await pipe(prop('username'), getUserOffersCount, otherwise(always(0)))(user) const swaps = await pipe(prop('username'), getSwapsForUser, andThen(toSwapsWithRole(user)), otherwiseEmptyArray)(user) const selection = swapDetailsSearchParamsTransformSchema.parse({ swaps, searchParams }) diff --git a/app/storybook/src/swap/swap-card.stories.tsx b/app/storybook/src/swap/swap-card.stories.tsx new file mode 100644 index 000000000..c207c9416 --- /dev/null +++ b/app/storybook/src/swap/swap-card.stories.tsx @@ -0,0 +1,54 @@ +// noinspection JSUnusedGlobalSymbols + +import { TokenType } from '@echo/model/constants/token-type' +import { nftMockPx2 } from '@echo/model/mocks/nft-mock' +import { swapMock } from '@echo/model/mocks/swap-mock' +import type { Erc721Item, Item } from '@echo/model/types/item' +import type { Swap } from '@echo/model/types/swap' +import { SwapCard } from '@echo/ui/components/swap/card/swap-card' +import { type Meta, type StoryObj } from '@storybook/react' +import { append, modify } from 'ramda' +import { type FunctionComponent } from 'react' + +type ComponentType = FunctionComponent<{ + stack: boolean +}> + +const metadata: Meta = { + title: 'Swap/Card', + args: { + stack: false + }, + argTypes: { + stack: { + control: 'boolean' + } + } +} +export default metadata +export const Default: StoryObj = { + render: ({ stack }) => { + const swap = stack + ? modify( + 'senderItems', + append({ + token: { + contract: nftMockPx2.collection.contract, + collection: { + name: nftMockPx2.collection.name, + slug: nftMockPx2.collection.slug, + totalSupply: nftMockPx2.collection.totalSupply + }, + name: nftMockPx2.name, + pictureUrl: nftMockPx2.pictureUrl, + tokenId: nftMockPx2.tokenId, + type: TokenType.Erc721 + } + } as Erc721Item), + swapMock + ) + : swapMock + + return + } +} diff --git a/lib/backend/src/helpers/offer/is-offer-redeemable.ts b/lib/backend/src/helpers/offer/is-offer-redeemable.ts new file mode 100644 index 000000000..bc1c25c5c --- /dev/null +++ b/lib/backend/src/helpers/offer/is-offer-redeemable.ts @@ -0,0 +1,47 @@ +import { getEscrowedNftSnapshot } from '@echo/firestore/crud/nft/get-escrowed-nft-snapshot' +import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index' +import type { EscrowedNftDocument } from '@echo/firestore/types/model/escrowed-nft-document' +import type { QueryDocumentSnapshot } from '@echo/firestore/types/query-document-snapshot' +import { OfferState } from '@echo/model/constants/offer-state' +import { itemToken } from '@echo/model/helpers/item/item-token' +import { offerReceiverNftItems } from '@echo/model/helpers/offer/offer-receiver-nft-items' +import { offerSenderNftItems } from '@echo/model/helpers/offer/offer-sender-nft-items' +import type { NftItem } from '@echo/model/types/item' +import type { Offer } from '@echo/model/types/offer' +import { unlessNil } from '@echo/utils/helpers/unless-nil' +import type { Nullable } from '@echo/utils/types/nullable' +import { always, andThen, ifElse, isNil, otherwise, pathEq, pipe, prop } from 'ramda' + +export function isOfferRedeemable(username: string) { + return async function (offer: Offer): Promise { + const { state } = offer + if (state === OfferState.Cancelled || state === OfferState.Rejected || state === OfferState.Expired) { + const nftItems = ifElse<[Offer], NftItem[], NftItem[]>( + pathEq(username, ['sender', 'username']), + offerSenderNftItems, + ifElse(pathEq(username, ['receiver', 'username']), offerReceiverNftItems, always([])) + )(offer) + for (const item of nftItems) { + const escrowedNftSnapshot = await pipe( + itemToken, + getNftSnapshotByIndex, + andThen( + unlessNil( + pipe( + prop('id'), + getEscrowedNftSnapshot, + otherwise(always>>(undefined)) + ) + ) + ), + otherwise(always>>(undefined)) + )(item) + if (!isNil(escrowedNftSnapshot)) { + return true + } + } + return false + } + return false + } +} diff --git a/lib/backend/src/request-handlers/webhook/event-handlers/erc721-transfer-event-handler.ts b/lib/backend/src/request-handlers/webhook/event-handlers/erc721-transfer-event-handler.ts index ded0e8c7e..1f8a5e5d0 100644 --- a/lib/backend/src/request-handlers/webhook/event-handlers/erc721-transfer-event-handler.ts +++ b/lib/backend/src/request-handlers/webhook/event-handlers/erc721-transfer-event-handler.ts @@ -1,8 +1,7 @@ import { userDocumentToModel } from '@echo/firestore/converters/user-document-to-model' import { getCollection } from '@echo/firestore/crud/collection/get-collection' import { escrowNft } from '@echo/firestore/crud/nft/escrow-nft' -import { getNftByIndex } from '@echo/firestore/crud/nft/get-nft-by-index' -import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot' +import { getNftByIndex, getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index' import { removeNftOwner } from '@echo/firestore/crud/nft/remove-nft-owner' import { setNftOwner } from '@echo/firestore/crud/nft/set-nft-owner' import { unescrowNft } from '@echo/firestore/crud/nft/unescrow-nft' @@ -27,7 +26,7 @@ export async function erc721TransferEventHandler({ contract, from, to, tokenId } } if (from === echoAddress) { const nftSnapshot = await pipe( - getNftSnapshot, + getNftSnapshotByIndex, otherwise(always>>(undefined)) )({ collection, tokenId }) if (isNil(nftSnapshot)) { diff --git a/lib/firestore/src/crud/nft/escrow-nft.ts b/lib/firestore/src/crud/nft/escrow-nft.ts index ad6a928d9..7c3970518 100644 --- a/lib/firestore/src/crud/nft/escrow-nft.ts +++ b/lib/firestore/src/crud/nft/escrow-nft.ts @@ -1,5 +1,5 @@ import { getEscrowedNftSnapshot } from '@echo/firestore/crud/nft/get-escrowed-nft-snapshot' -import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot' +import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index' import { removeNftOwner } from '@echo/firestore/crud/nft/remove-nft-owner' import { escrowedNftsCollection } from '@echo/firestore/helpers/collection/collections' import { setReference } from '@echo/firestore/helpers/reference/set-reference' @@ -9,7 +9,7 @@ import type { Nft, OwnedNftIndex } from '@echo/model/types/nft' import { isNil } from 'ramda' export async function escrowNft(nft: OwnedNftIndex): Promise { - const snapshot = await getNftSnapshot(nft) + const snapshot = await getNftSnapshotByIndex(nft) if (isNil(snapshot)) { return Promise.reject(Error(NftError.NotFound)) } diff --git a/lib/firestore/src/crud/nft/get-nft-snapshot.ts b/lib/firestore/src/crud/nft/get-nft-snapshot.ts deleted file mode 100644 index 08c62f106..000000000 --- a/lib/firestore/src/crud/nft/get-nft-snapshot.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index' -import type { NftDocument } from '@echo/firestore/types/model/nft-document' -import { nftIndex } from '@echo/model/helpers/nft/nft-index' -import type { NftIndex } from '@echo/model/types/nft' -import type { Nullable } from '@echo/utils/types/nullable' -import { QueryDocumentSnapshot } from 'firebase-admin/firestore' -import { pipe } from 'ramda' - -export function getNftSnapshot(nft: NftIndex): Promise>> { - return pipe(nftIndex, getNftSnapshotByIndex)(nft) -} diff --git a/lib/firestore/src/crud/nft/remove-nft-owner.ts b/lib/firestore/src/crud/nft/remove-nft-owner.ts index 96baed69c..1de3ca4d8 100644 --- a/lib/firestore/src/crud/nft/remove-nft-owner.ts +++ b/lib/firestore/src/crud/nft/remove-nft-owner.ts @@ -1,4 +1,4 @@ -import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot' +import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index' import { nftsCollection } from '@echo/firestore/helpers/collection/collections' import { updateReference } from '@echo/firestore/helpers/reference/update-reference' import type { NftDocument } from '@echo/firestore/types/model/nft-document' @@ -8,7 +8,7 @@ import { FieldValue } from 'firebase-admin/firestore' import { isNil } from 'ramda' export async function removeNftOwner(nft: NftIndex): Promise { - const snapshot = await getNftSnapshot(nft) + const snapshot = await getNftSnapshotByIndex(nft) if (isNil(snapshot)) { return Promise.reject(Error(NftError.NotFound)) } diff --git a/lib/firestore/src/crud/nft/set-nft-owner.ts b/lib/firestore/src/crud/nft/set-nft-owner.ts index 9f43e14a8..c2d09d582 100644 --- a/lib/firestore/src/crud/nft/set-nft-owner.ts +++ b/lib/firestore/src/crud/nft/set-nft-owner.ts @@ -1,4 +1,4 @@ -import { getNftSnapshot } from '@echo/firestore/crud/nft/get-nft-snapshot' +import { getNftSnapshotByIndex } from '@echo/firestore/crud/nft/get-nft-by-index' import { nftsCollection } from '@echo/firestore/helpers/collection/collections' import { updateReference } from '@echo/firestore/helpers/reference/update-reference' import type { NftDocument } from '@echo/firestore/types/model/nft-document' @@ -12,7 +12,7 @@ interface SetNftOwnerArgs { } export async function setNftOwner({ nft, owner }: SetNftOwnerArgs): Promise { - const snapshot = await getNftSnapshot(nft) + const snapshot = await getNftSnapshotByIndex(nft) if (isNil(snapshot)) { return Promise.reject(Error(NftError.NotFound)) } diff --git a/lib/model/src/helpers/offer/get-redeemable-offers.ts b/lib/model/src/helpers/offer/get-redeemable-offers.ts deleted file mode 100644 index 085691820..000000000 --- a/lib/model/src/helpers/offer/get-redeemable-offers.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { OfferState } from '@echo/model/constants/offer-state' -import type { OfferWithRole } from '@echo/ui/types/offer-with-role' -import dayjs from 'dayjs' -import { filter, pipe, prop } from 'ramda' - -export function getRedeemableOffers(offers: OfferWithRole[]): OfferWithRole[] { - const now = dayjs() - return pipe( - filter((offer: OfferWithRole) => { - const expirationDate = dayjs.unix(prop('expiresAt', offer)) - return expirationDate.isBefore(now) || offer.state === OfferState.Cancelled || offer.state === OfferState.Rejected - }) - )(offers) -} diff --git a/lib/ui/src/components/offer/card/offer-card.tsx b/lib/ui/src/components/offer/card/offer-card.tsx index 387a43727..681bbebb9 100644 --- a/lib/ui/src/components/offer/card/offer-card.tsx +++ b/lib/ui/src/components/offer/card/offer-card.tsx @@ -8,7 +8,6 @@ import { OfferCardPicture } from '@echo/ui/components/offer/card/offer-card-pict import { OfferStackPicture } from '@echo/ui/components/offer/card/offer-stack-picture' import { buildNftStack } from '@echo/ui/helpers/nft/build-nft-stack' import { nftLabel } from '@echo/ui/helpers/nft/nft-label' -import { isNonEmptyArray } from '@echo/utils/helpers/is-non-empty-array' import { head } from 'ramda' import { type FunctionComponent, useCallback } from 'react' @@ -23,23 +22,20 @@ export const OfferCard: FunctionComponent = ({ offer, onSelect }) => { onSelect?.(offer.slug) }, [offer.slug, onSelect]) - if (isNonEmptyArray(items)) { - if (items.length > 1) { - const stack = buildNftStack(items, offer.sender) - return ( - - - - - ) - } - const item = head(items) + if (items.length > 1) { + const stack = buildNftStack(items, offer.sender) return ( - - - - + + + + ) } - return null + const item = head(items) + return ( + + + + + ) } diff --git a/lib/ui/src/components/swap/card/swap-card.tsx b/lib/ui/src/components/swap/card/swap-card.tsx index ceeba0fd9..73145c3e3 100644 --- a/lib/ui/src/components/swap/card/swap-card.tsx +++ b/lib/ui/src/components/swap/card/swap-card.tsx @@ -1,13 +1,17 @@ 'use client' import { swapSenderNftItems } from '@echo/model/helpers/swap/swap-sender-nft-items' -import type { NftItem } from '@echo/model/types/item' import type { Swap } from '@echo/model/types/swap' import { CardFooter } from '@echo/ui/components/base/card/card-footer' +import { CardImage } from '@echo/ui/components/base/card/card-image' +import { CardLayout } from '@echo/ui/components/base/card/layout/card-layout' +import { CardPictureLayout } from '@echo/ui/components/base/card/layout/card-picture-layout' import { StackLayout } from '@echo/ui/components/base/stack/layout/stack-layout' -import { SwapStackPicture } from '@echo/ui/components/swap/card/swap-stack-picture' +import { StackPictureLayout } from '@echo/ui/components/base/stack/layout/stack-picture-layout' +import { StackImage } from '@echo/ui/components/base/stack/stack-image' +import { buildNftStack } from '@echo/ui/helpers/nft/build-nft-stack' import { nftLabel } from '@echo/ui/helpers/nft/nft-label' -import { head, type NonEmptyArray, pipe } from 'ramda' -import { type FunctionComponent } from 'react' +import { head } from 'ramda' +import { type FunctionComponent, useCallback } from 'react' interface SwapCardProps { swap: Swap @@ -15,16 +19,30 @@ interface SwapCardProps { } export const SwapCard: FunctionComponent = ({ swap, onSelect }) => { - const item = pipe<[Swap], NonEmptyArray, NftItem>(swapSenderNftItems, head)(swap) + const items = swapSenderNftItems(swap) + const select = useCallback(() => { + onSelect?.(swap.slug) + }, [swap.slug, onSelect]) + if (items.length > 1) { + const stack = buildNftStack(items, swap.sender) + return ( + + + + + + + ) + } + + const item = head(items) return ( - { - onSelect?.(swap.slug) - }} - > - + + + + - + ) } diff --git a/lib/ui/src/components/swap/card/swap-stack-picture.tsx b/lib/ui/src/components/swap/card/swap-stack-picture.tsx deleted file mode 100644 index 0550e3eec..000000000 --- a/lib/ui/src/components/swap/card/swap-stack-picture.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { StackPictureLayout } from '@echo/ui/components/base/stack/layout/stack-picture-layout' -import { StackImage } from '@echo/ui/components/base/stack/stack-image' -import type { Nullable } from '@echo/utils/types/nullable' -import { type FunctionComponent } from 'react' - -interface Props { - pictureUrl: Nullable - label: string -} - -export const SwapStackPicture: FunctionComponent = ({ pictureUrl, label }) => { - return ( - - - - ) -} diff --git a/lib/ui/src/pages/collection/collection-navigation.tsx b/lib/ui/src/pages/collection/collection-navigation.tsx index 4b5eb494f..b00cbf07f 100644 --- a/lib/ui/src/pages/collection/collection-navigation.tsx +++ b/lib/ui/src/pages/collection/collection-navigation.tsx @@ -15,7 +15,7 @@ import type { SwapWithRole } from '@echo/ui/types/swap-with-role' import type { TabOptions } from '@echo/ui/types/tab-options' import { isFalsy } from '@echo/utils/helpers/is-falsy' import { TabGroup, TabList, TabPanels } from '@headlessui/react' -import { all, always, find, findIndex, ifElse, isEmpty, isNil, map, pipe, prop, propEq } from 'ramda' +import { all, always, filter, find, findIndex, ifElse, isEmpty, isNil, map, pipe, prop, propEq } from 'ramda' import type { FunctionComponent } from 'react' type TabName = 'items' | 'listings' | 'offers' | 'swaps' @@ -63,7 +63,8 @@ export const CollectionNavigation: FunctionComponent = ({ if (isNil(selection)) { return {} } - return { defaultIndex: findIndex(propEq('listings', 'name'), tabs) } + const defaultIndex = pipe(filter(propEq(true, 'show')), findIndex(propEq('listings', 'name')))(tabs) + return { defaultIndex } } function showTab(name: TabName) { return pipe(find>(propEq(name, 'name')), ifElse(isNil, always(false), prop('show')))(tabs) diff --git a/lib/ui/src/pages/profile/profile-navigation.tsx b/lib/ui/src/pages/profile/profile-navigation.tsx index a8267cfd9..9228bd0f8 100644 --- a/lib/ui/src/pages/profile/profile-navigation.tsx +++ b/lib/ui/src/pages/profile/profile-navigation.tsx @@ -17,7 +17,7 @@ import type { SwapWithRole } from '@echo/ui/types/swap-with-role' import type { TabOptions } from '@echo/ui/types/tab-options' import { isFalsy } from '@echo/utils/helpers/is-falsy' import { TabGroup, TabList, TabPanels } from '@headlessui/react' -import { all, always, find, findIndex, ifElse, isEmpty, isNil, map, pipe, prop, propEq } from 'ramda' +import { all, always, filter, find, findIndex, ifElse, isEmpty, isNil, map, pipe, prop, propEq } from 'ramda' import type { FunctionComponent } from 'react' type TabName = 'items' | 'listings' | 'offers' | 'swaps' | 'explore' | 'redeemable' @@ -81,7 +81,8 @@ export const ProfileNavigation: FunctionComponent = ({ if (isNil(selection)) { return {} } - return { defaultIndex: findIndex(propEq('swaps', 'name'), tabs) } + const defaultIndex = pipe(filter(propEq(true, 'show')), findIndex(propEq('swaps', 'name')))(tabs) + return { defaultIndex } } return ( diff --git a/lib/ui/src/pages/user/user-navigation.tsx b/lib/ui/src/pages/user/user-navigation.tsx index 6f5b3eb63..db981bfa9 100644 --- a/lib/ui/src/pages/user/user-navigation.tsx +++ b/lib/ui/src/pages/user/user-navigation.tsx @@ -15,7 +15,7 @@ import type { SwapWithRole } from '@echo/ui/types/swap-with-role' import type { TabOptions } from '@echo/ui/types/tab-options' import { isFalsy } from '@echo/utils/helpers/is-falsy' import { TabGroup, TabList, TabPanels } from '@headlessui/react' -import { all, always, find, findIndex, ifElse, isEmpty, isNil, map, pipe, prop, propEq } from 'ramda' +import { all, always, filter, find, findIndex, ifElse, isEmpty, isNil, map, pipe, prop, propEq } from 'ramda' import type { FunctionComponent } from 'react' type TabName = 'items' | 'listings' | 'offers' | 'swaps' @@ -53,7 +53,8 @@ export const UserNavigation: FunctionComponent = ({ isAuthUser, listings, if (isNil(selection)) { return {} } - return { defaultIndex: findIndex(propEq('offers', 'name'), tabs) } + const defaultIndex = pipe(filter(propEq(true, 'show')), findIndex(propEq('offers', 'name')))(tabs) + return { defaultIndex } } function showTab(name: TabName) { diff --git a/package.json b/package.json index d726d11aa..d4b3d5b82 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "typescript-eslint": "8.17.0", "vercel": "39.1.3" }, - "packageManager": "pnpm@9.14.4", + "packageManager": "pnpm@9.15.0", "engines": { "node": "20.x" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25191c894..1e52eedcb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -226,6 +226,9 @@ importers: next-intl: specifier: 3.25.3 version: 3.25.3(next@14.2.20(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + p-filter: + specifier: 4.1.0 + version: 4.1.0 ramda: specifier: 0.30.1 version: 0.30.1 @@ -8316,6 +8319,10 @@ packages: resolution: {integrity: sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==} engines: {node: '>=8'} + p-filter@4.1.0: + resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==} + engines: {node: '>=18'} + p-finally@2.0.1: resolution: {integrity: sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==} engines: {node: '>=8'} @@ -8348,6 +8355,10 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} + p-map@7.0.3: + resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} + engines: {node: '>=18'} + p-throttle@5.1.0: resolution: {integrity: sha512-+N+s2g01w1Zch4D0K3OpnPDqLOKmLcQ4BvIFq3JC0K29R28vUOjWpO+OJZBNt8X9i3pFCksZJZ0YXkUGjaFE6g==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -20215,6 +20226,10 @@ snapshots: p-defer@3.0.0: {} + p-filter@4.1.0: + dependencies: + p-map: 7.0.3 + p-finally@2.0.1: {} p-limit@2.3.0: @@ -20246,6 +20261,8 @@ snapshots: aggregate-error: 3.1.0 optional: true + p-map@7.0.3: {} + p-throttle@5.1.0: {} p-try@2.2.0: {} From 9051b81ee79dff5e762171d0e23ab8908696ee55 Mon Sep 17 00:00:00 2001 From: Johnny Cage Wins <2409861+0xjohnnycagewins@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:05:22 -0500 Subject: [PATCH 2/2] test fixes --- app/storybook/.storybook/manager-head.html | 2 -- .../test/mappers/echo-offer-to-base-offer.test.ts | 9 +++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) delete mode 100644 app/storybook/.storybook/manager-head.html diff --git a/app/storybook/.storybook/manager-head.html b/app/storybook/.storybook/manager-head.html deleted file mode 100644 index d3e747f8a..000000000 --- a/app/storybook/.storybook/manager-head.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/lib/backend/test/mappers/echo-offer-to-base-offer.test.ts b/lib/backend/test/mappers/echo-offer-to-base-offer.test.ts index 6b812ea3e..113a3c138 100644 --- a/lib/backend/test/mappers/echo-offer-to-base-offer.test.ts +++ b/lib/backend/test/mappers/echo-offer-to-base-offer.test.ts @@ -1,10 +1,13 @@ import { echoOfferToBaseOffer } from '@echo/backend/mappers/echo-offer-to-base-offer' import { getCollectionByContract } from '@echo/firestore/crud/collection/get-collection-by-contract' import { getNftByIndex } from '@echo/firestore/crud/nft/get-nft-by-index' +import { getUserByWallet } from '@echo/firestore/crud/user/get-user-by-wallet' +import type { UserDocument } from '@echo/firestore/types/model/user-document' import { eqNft } from '@echo/model/helpers/nft/eq-nft' import { collectionMocks } from '@echo/model/mocks/collection-mock' import { nftMocks } from '@echo/model/mocks/nft-mock' import { baseOfferMockFromJohnnycage } from '@echo/model/mocks/offer-mock' +import { userMocks } from '@echo/model/mocks/user-mock' import type { Collection } from '@echo/model/types/collection' import type { Nft } from '@echo/model/types/nft' import { toPromise } from '@echo/utils/helpers/to-promise' @@ -15,6 +18,7 @@ import { find, pipe, propEq } from 'ramda' jest.mock('@echo/firestore/crud/collection/get-collection-by-contract') jest.mock('@echo/firestore/crud/nft/get-nft-by-index') +jest.mock('@echo/firestore/crud/user/get-user-by-wallet') describe('mappers - contractOfferToBaseOffer', () => { jest @@ -23,6 +27,11 @@ describe('mappers - contractOfferToBaseOffer', () => { jest .mocked(getNftByIndex) .mockImplementation((index) => pipe(find(eqNft(index)), toPromise>)(nftMocks)) + jest + .mocked(getUserByWallet) + .mockImplementation((wallet) => + pipe(find(propEq(wallet, 'wallet')), toPromise)(userMocks as UserDocument[]) + ) beforeEach(() => { jest.clearAllMocks()