diff --git a/jest.config.js b/jest.config.js index 584655d..3bc8cc1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,6 +13,18 @@ const customJestConfig = { // setupFilesAfterEnv: ['/jest.setup.js'], // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work moduleDirectories: ['node_modules', '/'], + moduleNameMapper: { + '@store/*': ['/src/store'], + '@assets/*': ['/assets/'], + '@ui/*': ['/components/ui/'], + '@components/*': ['/components/'], + '@widgets/*': ['/components/widgets/'], + '@containers/*': ['/containers/'], + '@layout/*': ['/layouts/'], + '@utils/*': ['/utils/'], + '@hooks': ['/hooks'], + '@types': ['/types'], + }, testEnvironment: 'jest-environment-jsdom', preset: 'ts-jest', testEnvironment: 'node', diff --git a/src/common/minting/commands/common/activate-item-command.spec.ts b/src/common/minting/commands/common/activate-item-command.spec.ts index 5852f8a..2745fbb 100644 --- a/src/common/minting/commands/common/activate-item-command.spec.ts +++ b/src/common/minting/commands/common/activate-item-command.spec.ts @@ -132,6 +132,14 @@ describe('ActivateItemCommand', () => { cid: '123', imageUrl: 'url', item: { itemId: '123' } as Item, + collection: { + name: 'ab', + description: 'ab', + image: { + url: 'url', + cid: 'cid', + }, + }, tokenId: 123, } as MintState; diff --git a/src/common/minting/commands/common/activate-item-command.ts b/src/common/minting/commands/common/activate-item-command.ts index d59803a..137b17c 100644 --- a/src/common/minting/commands/common/activate-item-command.ts +++ b/src/common/minting/commands/common/activate-item-command.ts @@ -1,9 +1,9 @@ +import { rejectWithHttp } from '../../../../store/error/error-handler'; import ActivateItemRequest from '../../../types/activate-item-request'; import ICommand from '../../interfaces/command.interface'; import MintError from '../../enums/mint-error.enum'; import MintState from '../../types/mint-state'; import IItemService from '../../../../features/leda-nft/interfaces/item-service.interface'; -import { rejectWithHttp } from '../../../../store/error/error-handler'; export default class ActivateItemCommand implements ICommand { private readonly itemService: IItemService; diff --git a/src/common/minting/commands/common/mint-nft-command.spec.ts b/src/common/minting/commands/common/mint-nft-command.spec.ts index 283caa1..c18dfc7 100644 --- a/src/common/minting/commands/common/mint-nft-command.spec.ts +++ b/src/common/minting/commands/common/mint-nft-command.spec.ts @@ -1,9 +1,9 @@ import { ContractReceipt, Event } from 'ethers'; +import * as ErrorHandler from '../../../../store/error/error-handler'; import MintError from '../../enums/mint-error.enum'; import ICommand from '../../interfaces/command.interface'; import MintState from '../../types/mint-state'; import MintNftCommand from './mint-nft-command'; -import * as errorHandler from '../../../../store/error/error-handler'; const nftServiceMock = { init: jest.fn(), @@ -213,7 +213,7 @@ describe('MintNftCommand', () => { const expected = { ...state, error: MintError.MintNftFailure }; - jest.spyOn(errorHandler, 'rejectWithMetamask').mockResolvedValue(expected); + jest.spyOn(ErrorHandler, 'rejectWithMetamask').mockResolvedValue(expected); nftServiceMock.mint.mockRejectedValue('something went wrong.'); diff --git a/src/common/minting/commands/common/store-draft-item-command.ts b/src/common/minting/commands/common/store-draft-item-command.ts index 3211625..3824c7a 100644 --- a/src/common/minting/commands/common/store-draft-item-command.ts +++ b/src/common/minting/commands/common/store-draft-item-command.ts @@ -1,9 +1,9 @@ +import * as ErrorHandler from '../../../../store/error/error-handler'; import ICommand from '../../interfaces/command.interface'; import MintError from '../../enums/mint-error.enum'; import MintState from '../../types/mint-state'; import IItemService from '../../../../features/leda-nft/interfaces/item-service.interface'; import DraftItemRequest from '../../../types/draft-item-request'; -import { rejectWithHttp } from '../../../../store/error/error-handler'; export default class StoreDraftItemCommand implements ICommand { private readonly itemService: IItemService; @@ -36,7 +36,10 @@ export default class StoreDraftItemCommand implements ICommand { state.item = await this.itemService.create(item); } catch (ex) { - return rejectWithHttp(ex, () => ({ ...state, error: MintError.StoreDraftItemFailure })); + return ErrorHandler.rejectWithHttp(ex, () => ({ + ...state, + error: MintError.StoreDraftItemFailure, + })); } return state; diff --git a/src/common/minting/commands/common/store-ipfs-object-command.ts b/src/common/minting/commands/common/store-ipfs-object-command.ts index 35d7be4..2c581c4 100644 --- a/src/common/minting/commands/common/store-ipfs-object-command.ts +++ b/src/common/minting/commands/common/store-ipfs-object-command.ts @@ -1,8 +1,8 @@ +import { rejectWithHttp } from '../../../../store/error/error-handler'; import ICommand from '../../interfaces/command.interface'; import MintError from '../../enums/mint-error.enum'; import MintState from '../../types/mint-state'; import IImageService from '../../../../features/leda-nft/interfaces/image-service.interface'; -import { rejectWithHttp } from '../../../../store/error/error-handler'; export default class StoreIpfsObjectCommand implements ICommand { private readonly imageService: IImageService; @@ -35,7 +35,10 @@ export default class StoreIpfsObjectCommand implements ICommand { state.collection.image.cid = cidResponse; } } catch (ex) { - return rejectWithHttp(ex, () => ({ ...state, error: MintError.IpfsStoreFailure })); + return rejectWithHttp(ex, () => ({ + ...state, + error: MintError.IpfsStoreFailure, + })); } return state; diff --git a/src/common/minting/commands/lazy/generate-voucher-command.ts b/src/common/minting/commands/lazy/generate-voucher-command.ts index 572773d..3335aa5 100644 --- a/src/common/minting/commands/lazy/generate-voucher-command.ts +++ b/src/common/minting/commands/lazy/generate-voucher-command.ts @@ -1,9 +1,9 @@ import { ethers } from 'ethers'; +import { rejectWithMetamask } from '../../../../store/error/error-handler'; import ICommand from '../../interfaces/command.interface'; import MintError from '../../enums/mint-error.enum'; import MintState from '../../types/mint-state'; import ILazyMintService from '../../../../features/leda-nft/interfaces/lazy-mint-service.interface'; -import { rejectWithMetamask } from '../../../../store/error/error-handler'; import IImageService from '../../../../features/leda-nft/interfaces/image-service.interface'; export default class GenerateVoucherCommand implements ICommand { diff --git a/src/common/minting/commands/lazy/get-voucher-command.ts b/src/common/minting/commands/lazy/get-voucher-command.ts index a0943aa..7054f28 100644 --- a/src/common/minting/commands/lazy/get-voucher-command.ts +++ b/src/common/minting/commands/lazy/get-voucher-command.ts @@ -1,5 +1,5 @@ -import IItemService from '../../../../features/leda-nft/interfaces/item-service.interface'; import { rejectWithHttp } from '../../../../store/error/error-handler'; +import IItemService from '../../../../features/leda-nft/interfaces/item-service.interface'; import MintError from '../../enums/mint-error.enum'; import ICommand from '../../interfaces/command.interface'; import MintState from '../../types/mint-state'; diff --git a/src/common/minting/commands/lazy/store-voucher-command.ts b/src/common/minting/commands/lazy/store-voucher-command.ts index 4ee3d95..6cb48cb 100644 --- a/src/common/minting/commands/lazy/store-voucher-command.ts +++ b/src/common/minting/commands/lazy/store-voucher-command.ts @@ -1,9 +1,9 @@ +import { rejectWithHttp } from '../../../../store/error/error-handler'; import ICommand from '../../interfaces/command.interface'; import MintError from '../../enums/mint-error.enum'; import MintState from '../../types/mint-state'; import IItemService from '../../../../features/leda-nft/interfaces/item-service.interface'; import ProcessLazyItemRequest from '../../../types/process-lazy-item-request'; -import { rejectWithHttp } from '../../../../store/error/error-handler'; export default class StoreVoucherCommand implements ICommand { private readonly itemService: IItemService; diff --git a/src/common/minting/commands/lazy/transfer-command.ts b/src/common/minting/commands/lazy/transfer-command.ts index ad76a11..c51594b 100644 --- a/src/common/minting/commands/lazy/transfer-command.ts +++ b/src/common/minting/commands/lazy/transfer-command.ts @@ -1,8 +1,8 @@ +import { rejectWithHttp } from '../../../../store/error/error-handler'; import IItemService from '../../../../features/leda-nft/interfaces/item-service.interface'; import MintError from '../../enums/mint-error.enum'; import ICommand from '../../interfaces/command.interface'; import MintState from '../../types/mint-state'; -import { rejectWithHttp } from '../../../../store/error/error-handler'; export default class TransferCommand implements ICommand { private readonly itemService: IItemService; diff --git a/src/components/auth/withAuth.tsx b/src/components/auth/withAuth.tsx index 849e77a..f6c90e7 100644 --- a/src/components/auth/withAuth.tsx +++ b/src/components/auth/withAuth.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/router'; -import { selectAuthState } from '../../features/auth/store/auth.slice'; import useAppSelector from '../../store/hooks/useAppSelector'; +import { selectAuthState } from '../../features/auth/store/auth.slice'; const withAuth = (WrappedConmponent: React.FunctionComponent) => { const Component = (props: object) => { diff --git a/src/components/collections/collection-introduction.component.tsx b/src/components/collections/collection-introduction.component.tsx index 3424a25..0361221 100644 --- a/src/components/collections/collection-introduction.component.tsx +++ b/src/components/collections/collection-introduction.component.tsx @@ -1,13 +1,12 @@ import Image from 'next/image'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import Tooltip from 'react-bootstrap/Tooltip'; -import { selectCollectionsState } from '../../features/collections/store/collections.slice'; import useAppSelector from '../../store/hooks/useAppSelector'; -import { formattedAddress } from '../../utils/getFormattedAddress'; import appConfig from '../../common/configuration/app.config'; +import { formattedAddress } from '../../utils/getFormattedAddress'; const CollectionIntroductionComponent = () => { - const { selectedCollection } = useAppSelector(selectCollectionsState); + const { selectedCollection } = useAppSelector((state) => state.marketplace); const lastUpdateDate = new Date(selectedCollection.updatedAt).toLocaleDateString(); diff --git a/src/components/collections/collection-items.component.tsx b/src/components/collections/collection-items.tsx similarity index 70% rename from src/components/collections/collection-items.component.tsx rename to src/components/collections/collection-items.tsx index 116a044..86659bc 100644 --- a/src/components/collections/collection-items.component.tsx +++ b/src/components/collections/collection-items.tsx @@ -6,33 +6,30 @@ import useAppDispatch from '../../store/hooks/useAppDispatch'; import useAppSelector from '../../store/hooks/useAppSelector'; import { Item as ItemType } from '../../types/item'; -const CollectionProductsComponent = () => { +export const CollectionItemsArea = () => { const dispatch = useAppDispatch(); - const { - selectedCollection, - collectionItemsFiltering: { - itemsPagination: { items, totalCount }, - itemsFilters, - isCollectionNftsLoading, - }, - } = useAppSelector((state) => state.collections); + const { items, itemsCount, selectedCollection, isPagingLoading, filters } = useAppSelector( + (state) => state.marketplace + ); - const hasMore = items.length < totalCount; + const hasMore = items.length < itemsCount; const handleNext = useCallback(() => { if (hasMore) { - const newPage = Math.floor(items.length / itemsFilters.limit + 1); - const filters = { ...itemsFilters, page: newPage }; - dispatch(findPagedCollectionItems({ collectionId: selectedCollection.id, filters })); + const newPage = Math.floor(items.length / filters.limit + 1); + const newFilters = { ...filters, page: newPage }; + dispatch( + findPagedCollectionItems({ collectionId: selectedCollection.id, filters: newFilters }) + ); } - }, [dispatch, hasMore, itemsFilters, items, selectedCollection.id]); + }, [dispatch, filters, hasMore, items.length, selectedCollection.id]); const infiniteScrollSettings = { style: { overflow: 'inherit' }, dataLength: items.length, handleNext, hasMore, - loading: isCollectionNftsLoading, + loading: isPagingLoading, endMessageDisplay: 'Looking for more NFTs?', endMessageLink: '/create', endMessageLinkDetails: 'Create one!', @@ -64,5 +61,3 @@ const CollectionProductsComponent = () => { ); }; - -export default CollectionProductsComponent; diff --git a/src/components/collections/collections-filter.component.tsx b/src/components/collections/collections-filter.component.tsx index ec1176c..01e4aab 100644 --- a/src/components/collections/collections-filter.component.tsx +++ b/src/components/collections/collections-filter.component.tsx @@ -1,16 +1,13 @@ import NiceSelect from '@ui/nice-select'; import clsx from 'clsx'; import { useState } from 'react'; -import { - selectCollectionsState, - setCollectionsFilters, -} from '../../features/collections/store/collections.slice'; import useAppDispatch from '../../store/hooks/useAppDispatch'; import useAppSelector from '../../store/hooks/useAppSelector'; +import { setCollectionsFilters } from '../../features/marketplace/store/marketplace.slice'; const CollectionsFilter = () => { const dispatch = useAppDispatch(); - const { collectionsFilters } = useAppSelector(selectCollectionsState); + const { collectionsFilters } = useAppSelector((state) => state.marketplace); const [isOpen, setIsOpen] = useState(false); const [localSearch, setLocalSearch] = useState(''); const handleTriggerButton = () => setIsOpen((prev) => !prev); diff --git a/src/components/collections/collections-rendered.tsx b/src/components/collections/collections-rendered.tsx index 3f07255..e499e97 100644 --- a/src/components/collections/collections-rendered.tsx +++ b/src/components/collections/collections-rendered.tsx @@ -1,19 +1,18 @@ import InfiniteScroll from '@components/common/InfiniteScroll'; import { useCallback } from 'react'; -import { findPagedCollections } from '../../features/collections/store/collections.actions'; -import { selectCollectionsState } from '../../features/collections/store/collections.slice'; import useAppDispatch from '../../store/hooks/useAppDispatch'; import useAppSelector from '../../store/hooks/useAppSelector'; +import { findPagedCollections } from '../../features/collections/store/collections.actions'; import { ICollection } from '../../types/ICollection'; import CollectionComponent from './collection.component'; const CollectionRendered = () => { const dispatch = useAppDispatch(); - const { collectionsFilters, collectionPagination, isPagingLoading } = - useAppSelector(selectCollectionsState); - const { collections, totalCount } = collectionPagination; + const { collectionsFilters, isPagingLoading, collections, collectionsCount } = useAppSelector( + (state) => state.marketplace + ); - const hasMore = collections.length < totalCount; + const hasMore = collections.length < collectionsCount; const handleNext = useCallback(() => { if (hasMore) { diff --git a/src/components/collections/items-collection-filter.component.tsx b/src/components/collections/items-collection-filter.component.tsx index 1c7a881..647c65b 100644 --- a/src/components/collections/items-collection-filter.component.tsx +++ b/src/components/collections/items-collection-filter.component.tsx @@ -5,11 +5,11 @@ import Sticky from '@ui/sticky'; import { useEffect, useMemo, useState } from 'react'; import { Range } from 'react-range'; import { IRenderTrackParams } from 'react-range/lib/types'; -import { findPriceRange } from '../../features/collections/store/collections.actions'; -import { setCollectionsNftsFilters } from '../../features/collections/store/collections.slice'; import useAppDispatch from '../../store/hooks/useAppDispatch'; import useAppSelector from '../../store/hooks/useAppSelector'; import { selectUiReducer } from '../../store/ui/ui.slice'; +import { setFilters } from '../../features/marketplace/store/marketplace.slice'; +import { findCollectionsByPriceRange } from '../../features/collections/store/collections.actions'; type PriceRange = { cheapest: number; @@ -30,10 +30,9 @@ const ItemCollectionFilter = () => { }); const [valuesRange, setValuesRange] = useState([]); const [step, setStep] = useState(DEFAULT_STEP); - const { - selectedCollection, - collectionItemsFiltering: { itemsFilters, itemsPagination }, - } = useAppSelector((state) => state.collections); + const { items, itemsCount, selectedCollection, filters } = useAppSelector( + (state) => state.marketplace + ); const stickyPadding = useMemo( () => (isNetworkAdviceOpen ? '150px' : '100px'), @@ -41,10 +40,10 @@ const ItemCollectionFilter = () => { ); useEffect(() => { - if (itemsPagination.items.length && itemsPagination.totalCount) { - const itemsWithPrice = itemsPagination.items.filter((item) => item.price !== null); + if (items.length && itemsCount) { + const itemsWithPrice = items.filter((item) => item.price !== null); if (itemsWithPrice.length) { - dispatch(findPriceRange(selectedCollection.id)).then(({ payload }) => { + dispatch(findCollectionsByPriceRange(selectedCollection.id)).then(({ payload }) => { const { from, to } = payload as { from: number; to: number }; setPriceRange({ cheapest: from, @@ -53,12 +52,12 @@ const ItemCollectionFilter = () => { }); } } - }, [dispatch, itemsPagination.items, itemsPagination.totalCount, selectedCollection.id]); + }, [dispatch, items, itemsCount, selectedCollection.id]); useEffect(() => { - if (Number(itemsFilters.mostExpensive) > 0 && Number(itemsFilters.cheapest) > 0) - setValuesRange([+itemsFilters.cheapest, +itemsFilters.mostExpensive]); - }, [itemsFilters.mostExpensive, itemsFilters.cheapest]); + if (Number(filters.mostExpensive) > 0 && Number(filters.cheapest) > 0) + setValuesRange([+filters.cheapest, +filters.mostExpensive]); + }, [filters.mostExpensive, filters.cheapest]); useEffect(() => { if (cheapest >= 0 && mostExpensive >= 0) { @@ -71,7 +70,7 @@ const ItemCollectionFilter = () => { }, [cheapest, mostExpensive]); const handleLikesChange = (likesDirection: string) => { - dispatch(setCollectionsNftsFilters({ ...itemsFilters, likesDirection })); + dispatch(setFilters({ ...filters, likesDirection })); }; const renderTrack = (props: IRenderTrackParams) => ( @@ -84,7 +83,7 @@ const ItemCollectionFilter = () => { const handleSearch = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { - dispatch(setCollectionsNftsFilters({ ...itemsFilters, search: localSearch })); + dispatch(setFilters({ ...filters, search: localSearch })); } }; @@ -94,8 +93,8 @@ const ItemCollectionFilter = () => { const handlePriceRangeFinalChange = ([from, to]: number[]) => { dispatch( - setCollectionsNftsFilters({ - ...itemsFilters, + setFilters({ + ...filters, priceRange: { from, to }, }) ); diff --git a/src/components/create-page/nft-collection.component.tsx b/src/components/create-page/nft-collection.component.tsx index 1c98be7..ad1752b 100644 --- a/src/components/create-page/nft-collection.component.tsx +++ b/src/components/create-page/nft-collection.component.tsx @@ -2,11 +2,10 @@ import clsx from 'clsx'; import { useEffect, useRef, useState } from 'react'; import Modal from 'react-bootstrap/Modal'; import { AiOutlinePlus } from 'react-icons/ai'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import { findUserCollectionsWithoutItems } from '../../features/account/store/account.actions'; -import { selectUserCollectionsWithoutItems } from '../../features/account/store/account.slice'; import useMetamask from '../../features/auth/hooks/useMetamask'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; import { CollectionCreateType } from '../../types/collection-type'; const collectionsErrors = { @@ -19,7 +18,7 @@ const NftCollectionComponent = () => { const dispatch = useAppDispatch(); const { address } = useMetamask(); const [open, setOpen] = useState(false); - const userCollections = useAppSelector(selectUserCollectionsWithoutItems); + const myCollections = useAppSelector((state) => state.marketplace.collections); const [dropdownCollection, setDropdownCollection] = useState(''); const [collectionModalOpen, setCollectionModalOpen] = useState(false); const [selectedCollectionImage, setSelectedCollectionImage] = useState(null); @@ -54,7 +53,7 @@ const NftCollectionComponent = () => { handleDropdown(); }; - const existOnUserCollections = userCollections.find((col) => col.name === collectionInput.name); + const existOnUserCollections = myCollections.find((col) => col.name === collectionInput.name); const handleSaveCollection = () => { if (collectionInput.name.length <= 3) setCollectionError(collectionsErrors.ShortString); @@ -123,7 +122,7 @@ const NftCollectionComponent = () => { > Default Collection - {userCollections + {myCollections .filter((col) => col.name !== 'LedaNFT') .map((userCollection) => (
  • { - const { isLoading, selectedItem, isModalOpen } = useAppSelector((state) => state.marketplace); + const { isDelisting, selectedItem, isModalOpen } = useAppSelector((state) => state.marketplace); const dispatch = useAppDispatch(); const { address } = useMetamask(); @@ -36,7 +36,7 @@ export const DelistingTabContent = () => {
    {
    { const { address } = useMetamask(); const [isValid, setIsValid] = useState(false); const [price, setPrice] = useState(''); - const { isLoading, selectedItem, isModalOpen } = useAppSelector((state) => state.marketplace); + const { isListing, selectedItem, isModalOpen } = useAppSelector((state) => state.marketplace); const dispatch = useAppDispatch(); const { register, @@ -102,7 +102,7 @@ export const ListingTabContent = () => {
    {

    Once you list this NFT, it will be shown on the marketplace

    - {isLoading && ( + {isListing && ( This transaction may take a few seconds )}
    @@ -148,7 +148,7 @@ export const ListingTabContent = () => {
    { +export const BuyNft = () => { const dispatch = useAppDispatch(); const { isModalOpen } = useAppSelector((state) => state.marketplace); const { isLoading } = useAppSelector((state) => state.ledaNft); @@ -25,7 +16,7 @@ const BuyNftComponent = ({ highestBid, actionDate, btnColor, className }: Props) return ( <> -
    +
    ); }; - -export default BuyNftComponent; diff --git a/src/components/item-details/hide-item-button.tsx b/src/components/item-details/hide-item-button.tsx index 0194f8f..f6c098e 100644 --- a/src/components/item-details/hide-item-button.tsx +++ b/src/components/item-details/hide-item-button.tsx @@ -2,10 +2,10 @@ import ActionLoaderComponent from '@components/action-loader/action-loader.compo import clsx from 'clsx'; import { useState } from 'react'; import Modal from 'react-bootstrap/Modal'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import { withAuthProtection } from '../../features/auth/store/auth.actions'; import { hideItem } from '../../features/marketplace/store/marketplace.actions'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; export const HideItemButton = () => { const dispatch = useAppDispatch(); diff --git a/src/components/item-details/title.tsx b/src/components/item-details/title.tsx index 460e396..f7b9d3d 100644 --- a/src/components/item-details/title.tsx +++ b/src/components/item-details/title.tsx @@ -1,12 +1,11 @@ import clsx from 'clsx'; import Link from 'next/link'; import { useMemo } from 'react'; -import { selectLikedItems } from '../../features/account/store/account.slice'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import { withAuthProtection } from '../../features/auth/store/auth.actions'; import { likeItem } from '../../features/marketplace/store/marketplace.actions'; import { selectIsOwner } from '../../features/marketplace/store/marketplace.slice'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; import ShareDropdown from '../share-dropdown'; import { HideItemButton } from './hide-item-button'; @@ -18,6 +17,7 @@ const ProductTitle = ({ className }: Props) => { const dispatch = useAppDispatch(); const { selectedItem: { name: title, likes: likeCount, itemId, collection, isHidden }, + likedItems, } = useAppSelector((state) => state.marketplace); const isOwner = useAppSelector(selectIsOwner); @@ -25,7 +25,6 @@ const ProductTitle = ({ className }: Props) => { const handleLikeItem = () => { dispatch(withAuthProtection(likeItem(itemId))); }; - const likedItems = useAppSelector(selectLikedItems); const isLiked = useMemo( () => Boolean(likedItems.find((likedItem) => likedItem.itemId === itemId)), diff --git a/src/components/item-filter/index.tsx b/src/components/item-filter/index.tsx index 78dcf64..3bf33c3 100644 --- a/src/components/item-filter/index.tsx +++ b/src/components/item-filter/index.tsx @@ -1,16 +1,13 @@ -import { IRenderTrackParams } from 'react-range/lib/types'; -import { Range } from 'react-range'; -import clsx from 'clsx'; import NiceSelect from '@ui/nice-select'; +import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; +import { Range } from 'react-range'; +import { IRenderTrackParams } from 'react-range/lib/types'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; +import { setFilters } from '../../features/marketplace/store/marketplace.slice'; import SliderThumb from '../ui/input-range/slider-thumb'; import SliderTrack from '../ui/input-range/slider-track'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; -import { - selectNFTsMarketplace, - setMarketplaceFilters, -} from '../../features/marketplace/store/marketplace.slice'; type Props = { cheapest: number; @@ -23,7 +20,7 @@ const STEP_PRECISION = 3; const ItemFilter = ({ cheapest, mostExpensive }: Props) => { const dispatch = useAppDispatch(); - const { marketplaceFilters } = useAppSelector(selectNFTsMarketplace); + const { filters: marketplaceFilters } = useAppSelector((state) => state.marketplace); const [isOpen, setIsOpen] = useState(false); const [valuesRange, setValuesRange] = useState([] as number[]); const [step, setStep] = useState(DEFAULT_STEP); @@ -54,7 +51,7 @@ const ItemFilter = ({ cheapest, mostExpensive }: Props) => { }; const handleLikesChange = (order: string) => { - dispatch(setMarketplaceFilters({ ...marketplaceFilters, likesDirection: order })); + dispatch(setFilters({ ...marketplaceFilters, likesDirection: order })); }; const handleSearchChange = (e: React.ChangeEvent) => { @@ -63,7 +60,7 @@ const ItemFilter = ({ cheapest, mostExpensive }: Props) => { const handleSearch = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { - dispatch(setMarketplaceFilters({ ...marketplaceFilters, search: localSearch })); + dispatch(setFilters({ ...marketplaceFilters, search: localSearch })); } }; @@ -74,7 +71,7 @@ const ItemFilter = ({ cheapest, mostExpensive }: Props) => { const handlePriceRangeFinalChange = (vals: number[]) => { setValuesRange(vals); dispatch( - setMarketplaceFilters({ + setFilters({ ...marketplaceFilters, priceRange: { from: valuesRange[0], to: valuesRange[1] }, }) diff --git a/src/components/item/index.tsx b/src/components/item/index.tsx index 8dc930f..3d92cf8 100644 --- a/src/components/item/index.tsx +++ b/src/components/item/index.tsx @@ -9,12 +9,11 @@ import CountdownTimer from '@ui/countdown/count-down-timer'; import { getFormattedName } from '@utils/getFormattedName'; import clsx from 'clsx'; import { useMemo } from 'react'; -import { selectLikedItems } from '../../features/account/store/account.slice'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; +import appConfig from '../../common/configuration/app.config'; import { selectAuthState } from '../../features/auth/store/auth.slice'; import { setIsModalOpen } from '../../features/marketplace/store/marketplace.slice'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; -import appConfig from '../../common/configuration/app.config'; type Props = { overlay?: boolean; @@ -65,8 +64,7 @@ const Product = ({ isLazy, }: Props) => { const dispatch = useAppDispatch(); - const { isModalOpen } = useAppSelector((state) => state.marketplace); - const likedItems = useAppSelector(selectLikedItems); + const { isModalOpen, likedItems } = useAppSelector((state) => state.marketplace); const handleBuyModal = () => { dispatch(setIsModalOpen(!isModalOpen)); }; diff --git a/src/components/items-stats/items-stats.components.tsx b/src/components/items-stats/items-stats.components.tsx index 8391994..0eb56a8 100644 --- a/src/components/items-stats/items-stats.components.tsx +++ b/src/components/items-stats/items-stats.components.tsx @@ -1,24 +1,24 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/no-static-element-interactions */ +import InfiniteScroll from '@components/common/InfiniteScroll'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import Anchor from '@ui/anchor'; import { formattedAddress } from '@utils/getFormattedAddress'; import { getFormattedName } from '@utils/getFormattedName'; import Image from 'next/image'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import { FaEthereum, FaRegHeart } from 'react-icons/fa'; import { IoMdHeart } from 'react-icons/io'; import appConfig from '../../common/configuration/app.config'; -import { selectLikedItems } from '../../features/account/store/account.slice'; import { withAuthProtection } from '../../features/auth/store/auth.actions'; -import { findPagedCollectionsNfts } from '../../features/collections/store/collections.actions'; +import { findPagedCollectionItems } from '../../features/collections/store/collections.actions'; import { likeItem } from '../../features/marketplace/store/marketplace.actions'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; import { Item } from '../../types/item'; const LikeRender = ({ likes, itemId }: { likes: number; itemId: string }) => { const dispatch = useAppDispatch(); - const likedItems = useAppSelector(selectLikedItems); + const { likedItems } = useAppSelector((state) => state.marketplace); const isLiked = useMemo( () => Boolean(likedItems.find((likedItem) => likedItem.itemId === itemId)), @@ -40,34 +40,32 @@ const LikeRender = ({ likes, itemId }: { likes: number; itemId: string }) => { const ItemStatsComponent = () => { const dispatch = useAppDispatch(); - const { - selectedCollection, - itemsStats: { page, items, totalCount }, - } = useAppSelector((state) => state.collections); + const { selectedCollection, items, itemsCount, filters, isPagingLoading } = useAppSelector( + (state) => state.marketplace + ); - const hasMore = items.length < totalCount; + const hasMore = items.length < itemsCount; - const getMore = useCallback( - (pg = 1) => { + const handleNext = useCallback(() => { + if (hasMore) { + const newPage = Math.floor(items.length / filters.limit + 1); + const newFilters = { ...filters, page: newPage }; dispatch( - findPagedCollectionsNfts({ - collectionId: selectedCollection.id, - page: pg, - }) + findPagedCollectionItems({ collectionId: selectedCollection.id, filters: newFilters }) ); - }, - [dispatch, selectedCollection.id] - ); - - useEffect(() => { - if (items.length === 0) { - getMore(); } - }, [dispatch, selectedCollection.id, items.length, getMore]); + }, [dispatch, filters, hasMore, items.length, selectedCollection.id]); - const handleLoadNfts = useCallback(() => { - if (hasMore) getMore(page + 1); - }, [hasMore, getMore, page]); + const infiniteScrollSettings = { + style: { overflow: 'inherit' }, + dataLength: items.length, + handleNext, + hasMore, + loading: isPagingLoading, + endMessageDisplay: 'Looking for more NFTs?', + endMessageLink: '/create', + endMessageLinkDetails: 'Create one!', + }; return (
    @@ -75,72 +73,69 @@ const ItemStatsComponent = () => {
    - - - - {['#', 'NFT', 'Chain', 'Author', 'Owner', 'Likes', 'Price'].map((col) => ( - - ))} - - - {items.map((item: Item, idx) => ( - - - - - - - - - + {' '} + +
    - {col} -
    - {idx + 1} - -
    - -
    -
    - Nft_Profile -
    - {getFormattedName(item.name)} -
    -
    -
    -
    - - - ETH - - - {formattedAddress(item.author.address)} - - {formattedAddress(item.owner.address)} - - - - {item.price ? `${item.price} ETH` : 'Not Listed'} -
    + + + {['#', 'NFT', 'Chain', 'Author', 'Owner', 'Likes', 'Price'].map((col) => ( + + ))} - - ))} -
    + {col} +
    - {hasMore ? ( - - ) : ( -

    You've seen all items on the collection

    - )} + + + {items.map((item: Item, idx) => ( + + + + {idx + 1} + + +
    + +
    +
    + Nft_Profile +
    + {getFormattedName(item.name)} +
    +
    +
    + + + + + ETH + + + + {formattedAddress(item.author.address)} + + + {formattedAddress(item.owner.address)} + + + + + + {item.price ? `${item.price} ETH` : 'Not Listed'} + + + + ))} + +
    diff --git a/src/components/modals/buy-modal/buy-modal.tsx b/src/components/modals/buy-modal/buy-modal.tsx index aa8e749..3507de4 100644 --- a/src/components/modals/buy-modal/buy-modal.tsx +++ b/src/components/modals/buy-modal/buy-modal.tsx @@ -1,12 +1,12 @@ import ActionLoaderComponent from '@components/action-loader/action-loader.component'; import Modal from 'react-bootstrap/Modal'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import useMetamask from '../../../features/auth/hooks/useMetamask'; import { withAuthProtection } from '../../../features/auth/store/auth.actions'; import { redeemVoucher } from '../../../features/leda-nft/store/leda-nft.actions'; import { buyItem } from '../../../features/marketplace/store/marketplace.actions'; import { selectIsLoadingWhileBuy } from '../../../features/marketplace/store/marketplace.slice'; -import useAppDispatch from '../../../store/hooks/useAppDispatch'; -import useAppSelector from '../../../store/hooks/useAppSelector'; type Props = { handleModal: () => void; diff --git a/src/components/modals/network-request-modal/network-request.modal.tsx b/src/components/modals/network-request-modal/network-request.modal.tsx index 7603e06..b86329b 100644 --- a/src/components/modals/network-request-modal/network-request.modal.tsx +++ b/src/components/modals/network-request-modal/network-request.modal.tsx @@ -1,8 +1,8 @@ import Button from '@ui/button'; import Modal from 'react-bootstrap/Modal'; -import useAppDispatch from '../../../store/hooks/useAppDispatch'; -import useAppSelector from '../../../store/hooks/useAppSelector'; -import { selectUiReducer, setIsMainnetModalOpen } from '../../../store/ui/ui.slice'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; +import { selectUiReducer, setIsMainnetModalOpen } from '@store/ui/ui.slice'; const NetworkRequestModal = () => { const dispatch = useAppDispatch(); diff --git a/src/components/modals/preview-product-modal/PreviewProductModal.tsx b/src/components/modals/preview-product-modal/PreviewProductModal.tsx index f38ebc6..c5393f2 100644 --- a/src/components/modals/preview-product-modal/PreviewProductModal.tsx +++ b/src/components/modals/preview-product-modal/PreviewProductModal.tsx @@ -2,9 +2,9 @@ import { ItemRequest } from '@types'; import { getFormattedName } from '@utils/getFormattedName'; import clsx from 'clsx'; import Modal from 'react-bootstrap/Modal'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import { setIsOpenPreviewProductModal } from '../../../features/marketplace/store/marketplace.slice'; -import useAppDispatch from '../../../store/hooks/useAppDispatch'; -import useAppSelector from '../../../store/hooks/useAppSelector'; type Props = { item: ItemRequest; diff --git a/src/components/modals/share-modal/index.tsx b/src/components/modals/share-modal/index.tsx index 2a1e056..eda53be 100644 --- a/src/components/modals/share-modal/index.tsx +++ b/src/components/modals/share-modal/index.tsx @@ -1,8 +1,8 @@ import { useRouter } from 'next/router'; import Modal from 'react-bootstrap/Modal'; import { BsWhatsapp } from 'react-icons/bs'; +import useAppSelector from '@store/hooks/useAppSelector'; import { selectById } from '../../../features/leda-nft/store/leda-nft.slice'; -import useAppSelector from '../../../store/hooks/useAppSelector'; type Props = { show: boolean; diff --git a/src/components/product-bid/index.tsx b/src/components/product-bid/index.tsx index 37648ce..0626fba 100644 --- a/src/components/product-bid/index.tsx +++ b/src/components/product-bid/index.tsx @@ -2,10 +2,10 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ import { Price } from '@types'; +import useAppDispatch from '@store/hooks/useAppDispatch'; import { withAuthProtection } from '../../features/auth/store/auth.actions'; import ItemStatus from '../../common/minting/enums/item-status.enum'; import { likeItem } from '../../features/marketplace/store/marketplace.actions'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; type Props = { itemId: string; diff --git a/src/components/signature-area/SignatureArea.tsx b/src/components/signature-area/SignatureArea.tsx index de4d8da..6a3571e 100644 --- a/src/components/signature-area/SignatureArea.tsx +++ b/src/components/signature-area/SignatureArea.tsx @@ -1,10 +1,10 @@ import { useRouter } from 'next/router'; import Button from '@ui/button'; import React, { useEffect, useState } from 'react'; +import { openToastError } from '@store/ui/ui.slice'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import { selectAuthState } from '../../features/auth/store/auth.slice'; -import { openToastError } from '../../store/ui/ui.slice'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; import useMetamask from '../../features/auth/hooks/useMetamask'; type Props = { diff --git a/src/components/user-dropdown/index.tsx b/src/components/user-dropdown/index.tsx index 4219241..0b0ba29 100644 --- a/src/components/user-dropdown/index.tsx +++ b/src/components/user-dropdown/index.tsx @@ -4,8 +4,8 @@ import Anchor from '@ui/anchor'; import Modal from 'react-bootstrap/Modal'; import Button from '@ui/button'; import { formattedAddress } from '@utils/getFormattedAddress'; +import useAppSelector from '@store/hooks/useAppSelector'; import constants from '../../common/configuration/constants'; -import useAppSelector from '../../store/hooks/useAppSelector'; import { selectAuthState } from '../../features/auth/store/auth.slice'; import useMetamask from '../../features/auth/hooks/useMetamask'; import { selectAccountState } from '../../features/account/store/account.slice'; diff --git a/src/containers/activity/activity-area.tsx b/src/containers/activity/activity-area.tsx index 9dcbfc3..14c0cc9 100644 --- a/src/containers/activity/activity-area.tsx +++ b/src/containers/activity/activity-area.tsx @@ -4,36 +4,40 @@ import { getTimeAgo } from '@utils/getTimeAgo'; import clsx from 'clsx'; import Image from 'next/image'; import { useCallback, useEffect } from 'react'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import appConfig from '../../common/configuration/app.config'; import { findAllHistory } from '../../features/marketplace/store/marketplace.actions'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; +import { resetFilters } from '../../features/marketplace/store/marketplace.slice'; export const ActivityArea = () => { const dispatch = useAppDispatch(); - const { history, isLoadingHistory } = useAppSelector((state) => state.marketplace); + const { history, isLoadingHistory, historyCount, filters } = useAppSelector( + (state) => state.marketplace + ); - const hasMore = history.data.length < history.count; + const hasMore = history.length < historyCount; useEffect(() => { + dispatch(resetFilters()); dispatch( findAllHistory({ - limit: history.limit, - page: history.page, + limit: filters.limit, + page: filters.page, }) ); - }, [dispatch, history.limit, history.page]); + }, [dispatch, filters.limit, filters.page]); const handleNext = useCallback(() => { if (hasMore) { - const newPage = Math.floor(history.data.length / history.limit + 1); - dispatch(findAllHistory({ limit: history.limit, page: newPage })); + const newPage = Math.floor(history.length / filters.limit + 1); + dispatch(findAllHistory({ limit: filters.limit, page: newPage })); } - }, [hasMore, history.data.length, history.limit, dispatch]); + }, [hasMore, history.length, filters.limit, dispatch]); const infiniteScrollSettings = { style: { overflow: 'inherit' }, - dataLength: history.data.length, + dataLength: history.length, handleNext, hasMore, loading: isLoadingHistory, @@ -47,7 +51,7 @@ export const ActivityArea = () => {
    - {history?.data.map((e) => ( + {history?.map((e) => (
    diff --git a/src/containers/author-intro/index.tsx b/src/containers/author-intro/AuthorIntroArea.tsx similarity index 73% rename from src/containers/author-intro/index.tsx rename to src/containers/author-intro/AuthorIntroArea.tsx index 8b91075..985fc7c 100644 --- a/src/containers/author-intro/index.tsx +++ b/src/containers/author-intro/AuthorIntroArea.tsx @@ -1,28 +1,23 @@ -import { useMemo, useState } from 'react'; -import clsx from 'clsx'; -import Image from 'next/image'; import ShareModal from '@components/modals/share-modal'; -import { Author } from '@types'; -import { selectAccountState, selectLikedItems } from '../../features/account/store/account.slice'; -import useAppSelector from '../../store/hooks/useAppSelector'; -import ReportModal from '../../components/modals/report-modal/index'; +import Image from 'next/image'; +import { useMemo, useState } from 'react'; +import useAppSelector from '@store/hooks/useAppSelector'; import ItemStatus from '../../common/minting/enums/item-status.enum'; +import ReportModal from '../../components/modals/report-modal/index'; +import { AuthorData } from '../../data/AuthorData'; type Props = { - className?: string; - space?: number; - data: Author; address: string; }; -const AuthorIntroArea = ({ className, space = 1, data, address }: Props) => { +export const AuthorIntroArea = ({ address }: Props) => { const [isShareModalOpen, setIsShareModalOpen] = useState(false); const [showReportModal, setShowReportModal] = useState(false); - const { imageNumber } = useAppSelector(selectAccountState); + const { imageNumber } = useAppSelector((state) => state.account); const shareModalHandler = () => setIsShareModalOpen((prev) => !prev); const handleReportModal = () => setShowReportModal((prev) => !prev); - const likedItems = useAppSelector(selectLikedItems); + const { likedItems } = useAppSelector((state) => state.marketplace); const likedItemsToShow = useMemo( () => @@ -46,17 +41,17 @@ const AuthorIntroArea = ({ className, space = 1, data, address }: Props) => { priority />
    -
    +
    - {data?.image?.src && ( + {AuthorData?.image?.src && (
    {data.image?.alt { )}
    -

    - {address} -

    +

    {address}

    @@ -85,5 +78,3 @@ const AuthorIntroArea = ({ className, space = 1, data, address }: Props) => { ); }; - -export default AuthorIntroArea; diff --git a/src/containers/author-profile/index.tsx b/src/containers/author-profile/AuthorProfileArea.tsx similarity index 93% rename from src/containers/author-profile/index.tsx rename to src/containers/author-profile/AuthorProfileArea.tsx index 2595094..5198727 100644 --- a/src/containers/author-profile/index.tsx +++ b/src/containers/author-profile/AuthorProfileArea.tsx @@ -1,27 +1,24 @@ import Product from '@components/item'; import { Item } from '@types'; -import clsx from 'clsx'; import { useMemo } from 'react'; import Nav from 'react-bootstrap/Nav'; import TabContainer from 'react-bootstrap/TabContainer'; import TabContent from 'react-bootstrap/TabContent'; import TabPane from 'react-bootstrap/TabPane'; +import useAppSelector from '@store/hooks/useAppSelector'; import { selectCreatedItems, - selectLikedItems, selectOnSaleItems, selectOwnedItems, } from '../../features/account/store/account.slice'; -import useAppSelector from '../../store/hooks/useAppSelector'; type Props = { - className?: string; address: string; }; -const AuthorProfileArea = ({ className, address }: Props) => { +export const AuthorProfileArea = ({ address }: Props) => { const createdItems = useAppSelector((state) => selectCreatedItems(state, address)); - const likedItems = useAppSelector(selectLikedItems); + const { likedItems } = useAppSelector((state) => state.marketplace); const onSaleItems = useAppSelector((state) => selectOnSaleItems(state, address)); const ownedItems = useAppSelector((state) => selectOwnedItems(state, address)); @@ -31,7 +28,7 @@ const AuthorProfileArea = ({ className, address }: Props) => { ); return ( -

    +
    @@ -144,5 +141,3 @@ const AuthorProfileArea = ({ className, address }: Props) => {
    ); }; - -export default AuthorProfileArea; diff --git a/src/containers/collection-details/collection-details.container.tsx b/src/containers/collection-details/collection-details.container.tsx index a7dca8e..4390e92 100644 --- a/src/containers/collection-details/collection-details.container.tsx +++ b/src/containers/collection-details/collection-details.container.tsx @@ -3,8 +3,8 @@ import ItemStatsComponent from '@components/items-stats/items-stats.components'; import { useState } from 'react'; import { BsListUl, BsListTask } from 'react-icons/bs'; import { MdOutlineAutoAwesomeMosaic, MdAutoAwesomeMosaic } from 'react-icons/md'; -import useAppSelector from '../../store/hooks/useAppSelector'; -import CollectionItemsContainer from './collection-items.container'; +import useAppSelector from '@store/hooks/useAppSelector'; +import { CollectionItemsContainer } from './collection-items'; const NotFound = () => (
    @@ -15,7 +15,7 @@ const NotFound = () => ( const CollectionDetailsContainer = () => { const [isStatsVisible, setIsStatsVisible] = useState(false); - const { selectedCollection } = useAppSelector((state) => state.collections); + const { selectedCollection } = useAppSelector((state) => state.marketplace); if (!Object.entries(selectedCollection).length) return ; @@ -26,28 +26,20 @@ const CollectionDetailsContainer = () => {
    diff --git a/src/containers/collection-details/collection-items.container.tsx b/src/containers/collection-details/collection-items.tsx similarity index 50% rename from src/containers/collection-details/collection-items.container.tsx rename to src/containers/collection-details/collection-items.tsx index d57f94b..d7ec358 100644 --- a/src/containers/collection-details/collection-items.container.tsx +++ b/src/containers/collection-details/collection-items.tsx @@ -1,22 +1,20 @@ -import CollectionItemsComponent from '@components/collections/collection-items.component'; +import { CollectionItemsArea } from '@components/collections/collection-items'; import ItemCollectionFilter from '@components/collections/items-collection-filter.component'; import NoSearchResults from '@containers/marketplace/no-search-results'; import { SpinnerContainer } from '@ui/spinner-container/spinner-container'; import { useMemo } from 'react'; -import useAppSelector from '../../store/hooks/useAppSelector'; +import useAppSelector from '@store/hooks/useAppSelector'; -const CollectionItemsContainer = () => { - const { itemsPagination, isCollectionNftsLoading } = useAppSelector( - (state) => state.collections.collectionItemsFiltering - ); +export const CollectionItemsContainer = () => { + const { isPagingLoading, items } = useAppSelector((state) => state.marketplace); const renderedComponent = useMemo(() => { - if (itemsPagination.items.length) return ; + if (items.length) return ; - if (!isCollectionNftsLoading) return ; + if (!isPagingLoading) return ; return null; - }, [itemsPagination.items.length, isCollectionNftsLoading]); + }, [items, isPagingLoading]); return (
    @@ -28,13 +26,9 @@ const CollectionItemsContainer = () => {
    - - {renderedComponent} - + {renderedComponent}
    ); }; - -export default CollectionItemsContainer; diff --git a/src/containers/collections/collections.container.tsx b/src/containers/collections/collections.container.tsx index 6919180..0245faa 100644 --- a/src/containers/collections/collections.container.tsx +++ b/src/containers/collections/collections.container.tsx @@ -3,15 +3,16 @@ import CollectionRendered from '@components/collections/collections-rendered'; import { SpinnerContainer } from '@ui/spinner-container/spinner-container'; import Link from 'next/link'; import { useMemo } from 'react'; -import { selectCollectionsState } from '../../features/collections/store/collections.slice'; -import useAppSelector from '../../store/hooks/useAppSelector'; +import useAppSelector from '@store/hooks/useAppSelector'; const CollectionsContainer = () => { - const { collectionPagination, isLoadingCollections } = useAppSelector(selectCollectionsState); + const { collections, collectionsCount, isLoadingCollections } = useAppSelector( + (state) => state.marketplace + ); const renderedComponent = useMemo(() => { - if (collectionPagination.collections.length) return ; - if (collectionPagination.totalCount === 0) + if (collections.length) return ; + if (collectionsCount === 0) return (

    @@ -24,11 +25,11 @@ const CollectionsContainer = () => { ); return null; - }, [collectionPagination.collections.length, collectionPagination.totalCount]); + }, [collections.length, collectionsCount]); return (

    -
    {!!collectionPagination.totalCount && }
    +
    {!!collectionsCount && }
    {renderedComponent}
    ); diff --git a/src/containers/collections/index.tsx b/src/containers/collections/index.tsx index 91d277f..b3c84d0 100644 --- a/src/containers/collections/index.tsx +++ b/src/containers/collections/index.tsx @@ -13,14 +13,13 @@ import { AiOutlinePlus } from 'react-icons/ai'; import { useClickAway } from 'react-use'; import Switch from 'react-switch'; import { RiDeleteBack2Fill } from 'react-icons/ri'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import { mintNft } from '../../features/leda-nft/store/leda-nft.actions'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; import { selectNftState } from '../../features/leda-nft/store/leda-nft.slice'; import useMetamask from '../../features/auth/hooks/useMetamask'; import { ItemProperty } from '../../common/types/ipfs-types'; import { findUserCollectionsWithoutItems } from '../../features/account/store/account.actions'; -import { selectUserCollectionsWithoutItems } from '../../features/account/store/account.slice'; import { CollectionCreateType } from '../../types/collection-type'; const tagsErrorMessages = { @@ -55,7 +54,7 @@ const collectionsErrors = { }; const CreateNewArea = () => { - const userCollections = useAppSelector(selectUserCollectionsWithoutItems); + const { collectionsWithoutItems } = useAppSelector((state) => state.marketplace); const [properties, setProperties] = useState([]); const [propertiesModalMessage, setPropertiesModalMessage] = useState(''); const [propsModalOpen, setPropsModalOpen] = useState(false); @@ -112,7 +111,9 @@ const CreateNewArea = () => { onClose(); }; - const existOnUserCollections = userCollections.find((col) => col.name === collectionInput.name); + const existOnUserCollections = collectionsWithoutItems.find( + (col) => col.name === collectionInput.name + ); const handleSaveCollection = () => { if (collectionInput.name.length <= 3) setCollectionError(collectionsErrors.ShortString); @@ -419,7 +420,7 @@ const CreateNewArea = () => { > Default Collection
  • - {userCollections + {collectionsWithoutItems .filter((col) => col.name !== 'LedaNFT') .map((userCollection) => (
  • ( @@ -17,7 +16,7 @@ const NotFound = () => ( const NewestCollectionArea = () => { const dispatch = useAppDispatch(); - const { newestCollections, isLoadingCollections } = useAppSelector(selectCollectionsState); + const { newestCollections, isLoadingCollections } = useAppSelector((state) => state.marketplace); const qtyItemsToFetch = 4; diff --git a/src/containers/connect/index.tsx b/src/containers/connect/index.tsx index c853708..67e326d 100644 --- a/src/containers/connect/index.tsx +++ b/src/containers/connect/index.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { useRouter } from 'next/router'; import Wallet from '@components/wallet'; -import useAppSelector from '../../store/hooks/useAppSelector'; +import useAppSelector from '@store/hooks/useAppSelector'; import { selectAuthState } from '../../features/auth/store/auth.slice'; import useMetamask from '../../features/auth/hooks/useMetamask'; diff --git a/src/containers/create-new/index.tsx b/src/containers/create-new/index.tsx index 923043e..a3bb7a1 100644 --- a/src/containers/create-new/index.tsx +++ b/src/containers/create-new/index.tsx @@ -17,16 +17,15 @@ import { AiOutlinePlus } from 'react-icons/ai'; import { RiDeleteBack2Fill } from 'react-icons/ri'; import { useClickAway } from 'react-use'; import { useForm } from 'react-hook-form'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import { ItemProperty } from '../../common/types/ipfs-types'; import { findUserCollectionsWithoutItems } from '../../features/account/store/account.actions'; -import { selectUserCollectionsWithoutItems } from '../../features/account/store/account.slice'; import useMetamask from '../../features/auth/hooks/useMetamask'; import { withAuthProtection } from '../../features/auth/store/auth.actions'; import { mintNft } from '../../features/leda-nft/store/leda-nft.actions'; import { selectNftState } from '../../features/leda-nft/store/leda-nft.slice'; import { setIsOpenPreviewProductModal } from '../../features/marketplace/store/marketplace.slice'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; import { CollectionCreateType } from '../../types/collection-type'; const tagsErrorMessages = { @@ -67,7 +66,7 @@ const defaultCollection = { } as CollectionCreateType; const CreateNewArea = () => { - const userCollections = useAppSelector(selectUserCollectionsWithoutItems); + const { collectionsWithoutItems } = useAppSelector((state) => state.marketplace); const [properties, setProperties] = useState([]); const [propertiesModalMessage, setPropertiesModalMessage] = useState(''); const [propsModalOpen, setPropsModalOpen] = useState(false); @@ -124,7 +123,9 @@ const CreateNewArea = () => { onClose(); }; - const existOnUserCollections = userCollections.find((col) => col.name === collectionInput.name); + const existOnUserCollections = collectionsWithoutItems.find( + (col) => col.name === collectionInput.name + ); const handleDefaultCollection = () => { setCollection(defaultCollection); @@ -454,7 +455,7 @@ const CreateNewArea = () => { > Default Collection
  • - {userCollections + {collectionsWithoutItems .filter((col) => col.name !== 'LedaNFT') .map((userCollection) => (
  • { diff --git a/src/containers/item-details/item-details.tsx b/src/containers/item-details/item-details.tsx index aceae08..4401bbc 100644 --- a/src/containers/item-details/item-details.tsx +++ b/src/containers/item-details/item-details.tsx @@ -1,17 +1,17 @@ import { BidTab } from '@components/item-details/bid-tab/bid-tab'; -import BuyNftComponent from '@components/item-details/buy-nft-component'; +import { BuyNft } from '@components/item-details/buy-nft'; import ProductTitle from '@components/item-details/title'; import Button from '@ui/button'; import Sticky from '@ui/sticky'; import clsx from 'clsx'; import Link from 'next/link'; import { useMemo } from 'react'; +import useAppSelector from '@store/hooks/useAppSelector'; +import { selectUiReducer } from '@store/ui/ui.slice'; import appConfig from '../../common/configuration/app.config'; import ItemStatus from '../../common/minting/enums/item-status.enum'; import { selectAuthState } from '../../features/auth/store/auth.slice'; import { selectCanISeeItem } from '../../features/marketplace/store/marketplace.slice'; -import useAppSelector from '../../store/hooks/useAppSelector'; -import { selectUiReducer } from '../../store/ui/ui.slice'; const HiddenLayout = () => (
    @@ -109,7 +109,7 @@ const RenderedItem = () => {
      - {!isOwner && selectedItem.status === ItemStatus.Listed && } + {!isOwner && selectedItem.status === ItemStatus.Listed && }
    diff --git a/src/containers/marketplace/MarketplaceArea.tsx b/src/containers/marketplace/MarketplaceArea.tsx index a8d72ca..9e0eb72 100644 --- a/src/containers/marketplace/MarketplaceArea.tsx +++ b/src/containers/marketplace/MarketplaceArea.tsx @@ -1,19 +1,21 @@ -import { Item as ItemType } from '@types'; -import { useCallback } from 'react'; import InfiniteScroll from '@components/common/InfiniteScroll'; import Item from '@components/item'; -import { selectNFTsMarketplace } from '../../features/marketplace/store/marketplace.slice'; +import { Item as ItemType } from '@types'; +import { useCallback } from 'react'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; import { findPagedItems } from '../../features/marketplace/store/marketplace.actions'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; export const MarketplaceArea = () => { const dispatch = useAppDispatch(); - const { marketplaceFilters, itemPagination, isPagingLoading } = - useAppSelector(selectNFTsMarketplace); - const { items, totalCount } = itemPagination; + const { + filters: marketplaceFilters, + items, + itemsCount: count, + isPagingLoading, + } = useAppSelector((state) => state.marketplace); - const hasMore = items.length < totalCount; + const hasMore = items.length < count; const handleNext = useCallback(() => { if (hasMore) { diff --git a/src/data/AuthorData.ts b/src/data/AuthorData.ts new file mode 100644 index 0000000..4e4c443 --- /dev/null +++ b/src/data/AuthorData.ts @@ -0,0 +1,10 @@ +export const AuthorData = { + name: 'MRS SUNAYRA AHSAN', + twitter: 'it0bsession', + followers: '186k', + following: '156', + image: { + src: '/images/slider/banner-06.png', + }, + slug: '', +}; diff --git a/src/data/author.json b/src/data/author.json deleted file mode 100644 index afe91f6..0000000 --- a/src/data/author.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "MRS SUNAYRA AHSAN", - "twitter": "it0bsession", - "followers": "186k", - "following": "156", - "image": { - "src": "/images/slider/banner-06.png" - }, - "slug": "" -} diff --git a/src/features/account/services/account.service.ts b/src/features/account/services/account.service.ts index 2493033..0672968 100644 --- a/src/features/account/services/account.service.ts +++ b/src/features/account/services/account.service.ts @@ -34,3 +34,5 @@ export default class AccountService extends HttpService { return data; } } + +export const accountService = new AccountService(); diff --git a/src/features/account/store/account.actions.ts b/src/features/account/store/account.actions.ts index ba0f195..87dfdc2 100644 --- a/src/features/account/store/account.actions.ts +++ b/src/features/account/store/account.actions.ts @@ -1,43 +1,25 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { Item } from '@types'; import { ICollection } from '../../../types/ICollection'; -import AccountService from '../services/account.service'; +import { accountService } from '../services/account.service'; -const findItemsByAccount = createAsyncThunk( +export const findItemsByAccount = createAsyncThunk( 'account/findItemsByAccount', - async (address: string): Promise => { - const accountService = new AccountService(); - return accountService.findItemsByAccount(address); - } + async (address: string): Promise => accountService.findItemsByAccount(address) ); -const findLikedItemsByAccount = createAsyncThunk( +export const findLikedItemsByAccount = createAsyncThunk( 'account/findLikedItemsByAccount', - async (address: string): Promise => { - const accountService = new AccountService(); - return accountService.findLikedItemsByAccount(address); - } + async (address: string): Promise => accountService.findLikedItemsByAccount(address) ); -const findUserCollections = createAsyncThunk( +export const findUserCollections = createAsyncThunk( 'account/findUserCollections', - async (address: string): Promise => { - const accountService = new AccountService(); - return accountService.findUserCollections(address); - } + async (address: string): Promise => accountService.findUserCollections(address) ); -const findUserCollectionsWithoutItems = createAsyncThunk( +export const findUserCollectionsWithoutItems = createAsyncThunk( 'account/findUserCollectionsWithoutItems', - async (address: string): Promise => { - const accountService = new AccountService(); - return accountService.findUserCollectionsWithoutItems(address); - } + async (address: string): Promise => + accountService.findUserCollectionsWithoutItems(address) ); - -export { - findItemsByAccount, - findLikedItemsByAccount, - findUserCollections, - findUserCollectionsWithoutItems, -}; diff --git a/src/features/account/store/account.slice.ts b/src/features/account/store/account.slice.ts index 2ad9626..ae2b499 100644 --- a/src/features/account/store/account.slice.ts +++ b/src/features/account/store/account.slice.ts @@ -1,32 +1,14 @@ import { createSelector, createSlice } from '@reduxjs/toolkit'; import { Item } from '@types'; +import type { RootState } from '@store/types'; import ItemStatus from '../../../common/minting/enums/item-status.enum'; -import type { RootState } from '../../../store/types'; -import { ICollection, ICollectionWithoutItems } from '../../../types/ICollection'; -import { likeItem } from '../../marketplace/store/marketplace.actions'; -import { - findItemsByAccount, - findLikedItemsByAccount, - findUserCollections, - findUserCollectionsWithoutItems, -} from './account.actions'; -type LedaNftState = { - items: Item[]; - likedItems: Item[]; +type AccountState = { isLoading: boolean; - userCollections: ICollection[]; - userCollectionsWithoutItems: ICollectionWithoutItems[]; - loadingUserCollection: boolean; imageNumber: number; }; -const initialState: LedaNftState = { - items: [], - likedItems: [], - userCollections: [], - userCollectionsWithoutItems: [], - loadingUserCollection: false, +const initialState: AccountState = { isLoading: false, imageNumber: 1, }; @@ -39,82 +21,27 @@ const accountSlice = createSlice({ state.imageNumber = payload; }, }, - extraReducers: (builder) => { - builder.addCase(findUserCollectionsWithoutItems.pending, (state) => { - state.loadingUserCollection = true; - }); - builder.addCase(findUserCollectionsWithoutItems.fulfilled, (state, { payload }) => { - state.loadingUserCollection = false; - state.userCollectionsWithoutItems = payload; - }); - builder.addCase(findUserCollectionsWithoutItems.rejected, (state) => { - state.loadingUserCollection = false; - }); - builder.addCase(findUserCollections.pending, (state) => { - state.loadingUserCollection = true; - }); - builder.addCase(findUserCollections.fulfilled, (state, { payload }) => { - state.userCollections = payload; - state.loadingUserCollection = false; - }); - builder.addCase(findUserCollections.rejected, (state) => { - state.loadingUserCollection = false; - }); - builder.addCase(findItemsByAccount.pending, (state) => { - state.isLoading = true; - }); - builder.addCase(findItemsByAccount.fulfilled, (state, { payload }) => { - state.items = payload; - }); - builder.addCase(findLikedItemsByAccount.pending, (state) => { - state.isLoading = true; - }); - builder.addCase(findLikedItemsByAccount.fulfilled, (state, { payload }) => { - state.likedItems = payload; - }); - builder.addCase(likeItem.fulfilled, (state, { payload }) => { - const index = state.items.findIndex((i) => i.itemId === payload.itemId); - state.items[index] = payload; - - const likedIndex = state.likedItems.findIndex((i) => i.itemId === payload.itemId); - - if (likedIndex !== -1) state.likedItems.splice(likedIndex, 1); - else state.likedItems.push(payload); - }); - }, }); export const selectAccountState = (state: RootState) => state.account; export const { setProfileImage } = accountSlice.actions; -export const selectItems = createSelector( - (state: RootState) => state.account.items, - (items: Item[]) => items -); - export const selectCreatedItems = createSelector( - selectItems, + (state: RootState) => state.marketplace.items, (_: unknown, address: string) => address, (items: Item[], address: string) => items.filter((item) => item.author.address === address) ); -export const selectLikedItems = (state: RootState) => state.account.likedItems; - export const selectOnSaleItems = createSelector( - selectItems, + (state: RootState) => state.marketplace.items, (_: unknown, address: string) => address, (items: Item[], address: string) => items.filter((item) => item.owner.address === address && item.status === ItemStatus.Listed) ); -export const selectUserCollections = (state: RootState) => state.account.userCollections; - -export const selectUserCollectionsWithoutItems = (state: RootState) => - state.account.userCollectionsWithoutItems; - export const selectOwnedItems = createSelector( - selectItems, + (state: RootState) => state.marketplace.items, (_: unknown, address: string) => address, (items: Item[], address: string) => items.filter((item) => item.owner.address === address) ); diff --git a/src/features/auth/hooks/useMetamask.ts b/src/features/auth/hooks/useMetamask.ts index f58dcfb..5dbf625 100644 --- a/src/features/auth/hooks/useMetamask.ts +++ b/src/features/auth/hooks/useMetamask.ts @@ -1,11 +1,11 @@ +import useAppSelector from '@store/hooks/useAppSelector'; import { ethers } from 'ethers'; -import { useEffect, useCallback, useState } from 'react'; -import { authenticate, signin } from '../store/auth.actions'; -import { openToastError } from '../../../store/ui/ui.slice'; -import { selectAuthState, setEthAddress, setIsConnected } from '../store/auth.slice'; -import useAppDispatch from '../../../store/hooks/useAppDispatch'; +import { useCallback, useEffect, useState } from 'react'; import { NetworkNames } from '../../../common/enums/network-names.enum'; -import useAppSelector from '../../../store/hooks/useAppSelector'; +import useAppDispatch from '../../../store/hooks/useAppDispatch'; +import { openToastError } from '../../../store/ui/ui.slice'; +import { authenticate, signIn } from '../store/auth.actions'; +import { setEthAddress, setIsConnected } from '../store/auth.slice'; const useMetamask = () => { const dispatch = useAppDispatch(); @@ -13,7 +13,7 @@ const useMetamask = () => { const [connected, setConnected] = useState(false); const [isMetamaskIntalled, setIsMetamaskInstalled] = useState(false); const [signer, setSigner] = useState(); - const { address, isConnected } = useAppSelector(selectAuthState); + const { address, isConnected } = useAppSelector((state) => state.auth); useEffect(() => { const isInstalled = window.ethereum && window.ethereum.isMetaMask; @@ -59,7 +59,7 @@ const useMetamask = () => { return; } - dispatch(signin(address)); + dispatch(signIn(address)); }; useEffect(() => { diff --git a/src/features/auth/store/auth.actions.ts b/src/features/auth/store/auth.actions.ts index a9971d4..19edace 100644 --- a/src/features/auth/store/auth.actions.ts +++ b/src/features/auth/store/auth.actions.ts @@ -1,26 +1,18 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import Router from 'next/router'; +import MetaType from '../../../store/enums/meta-type.enum'; +import { openToastError, setIsMainnetModalOpen } from '../../../store/ui/ui.slice'; +import type { RootState } from '../../../store/types'; +import { rejectWithMetamask } from '../../../store/error/error-handler'; import { randomIntFromInterval } from '../../../utils/getRandomIntFromInterval'; import { authService } from '../services/auth.service'; import { getSigner } from '../../../common/utils/metamask-utils'; import { localStorageService } from '../../../common/services/local-storage.service'; -import { openToastError, setIsMainnetModalOpen } from '../../../store/ui/ui.slice'; -import { rejectWithMetamask } from '../../../store/error/error-handler'; import constants from '../../../common/configuration/constants'; -import MetaType from '../../../store/enums/meta-type.enum'; -import type { RootState } from '../../../store/types'; import { setProfileImage } from '../../account/store/account.slice'; import { EnvironmentsEnum } from '../../../common/enums/environments.enum'; -const authenticate = createAsyncThunk( - 'auth/authenticate', - async (address: string) => { - const validToken = await authService.authenticateLocalToken(address); - return !!validToken; - } -); - -const signin = createAsyncThunk( +export const signIn = createAsyncThunk( 'auth/signin', async (address: string, { dispatch, rejectWithValue }) => { try { @@ -49,8 +41,9 @@ const signin = createAsyncThunk( } ); -const withAuthProtection = createAsyncThunk( +export const withAuthProtection = createAsyncThunk( 'auth/withAuthProtection', + // eslint-disable-next-line @typescript-eslint/no-explicit-any async (action: any, { dispatch, getState }) => { const { NEXT_PUBLIC_NODE_ENV } = process.env; @@ -73,7 +66,7 @@ const withAuthProtection = createAsyncThunk( const validToken = await authService.authenticateLocalToken(auth.address); if (!validToken) { - const response = await dispatch(signin(auth.address)); + const response = await dispatch(signIn(auth.address)); if (response.meta.requestStatus === MetaType.rejected) return; } @@ -81,4 +74,7 @@ const withAuthProtection = createAsyncThunk( } ); -export { authenticate, signin, withAuthProtection }; +export const authenticate = createAsyncThunk('auth/authenticate', async (address: string) => { + const validToken = await authService.authenticateLocalToken(address); + return !!validToken; +}); diff --git a/src/features/auth/store/auth.slice.spec.ts b/src/features/auth/store/auth.slice.spec.ts index aeca21b..4134a93 100644 --- a/src/features/auth/store/auth.slice.spec.ts +++ b/src/features/auth/store/auth.slice.spec.ts @@ -1,5 +1,5 @@ import { AnyAction } from '@reduxjs/toolkit'; -import { authenticate, signin } from './auth.actions'; +import { authenticate, signIn } from './auth.actions'; import { authReducer, AuthState, setEthAddress, setIsConnected } from './auth.slice'; describe('Auth slice', () => { @@ -19,7 +19,7 @@ describe('Auth slice', () => { it('should return initial state', () => { const expected = initialState; - const actual = authReducer(undefined, {} as AnyAction); + const actual = authReducer(initialState, {} as AnyAction); expect(actual).toEqual(expected); }); @@ -29,7 +29,7 @@ describe('Auth slice', () => { it('should assign the address correctly', () => { const expected = '0x01123'; - const actual = authReducer(undefined, setEthAddress(expected)); + const actual = authReducer(initialState, setEthAddress(expected)); expect(actual.address).toEqual(expected); }); @@ -39,7 +39,7 @@ describe('Auth slice', () => { it('should assign the isConnected flag correctly', () => { const expected = true; - const actual = authReducer(undefined, setIsConnected(true)); + const actual = authReducer(initialState, setIsConnected(true)); expect(actual.isConnected).toEqual(expected); }); @@ -49,7 +49,7 @@ describe('Auth slice', () => { it('should assign the isAuthCompleted flags to false.', () => { const expected = false; - const actual = authReducer(undefined, authenticate.pending('', '')); + const actual = authReducer(initialState, authenticate.pending('', '', '')); expect(actual.isAuthCompleted).toEqual(expected); }); @@ -60,28 +60,28 @@ describe('Auth slice', () => { const expectedIsAuthenticated = true; const expectedIsAuthCompleted = true; - const actual = authReducer(undefined, authenticate.fulfilled(true, '', '')); + const actual = authReducer(initialState, authenticate.fulfilled(true, '', '')); expect(actual.isAuthenticated).toEqual(expectedIsAuthenticated); expect(actual.isAuthCompleted).toEqual(expectedIsAuthCompleted); }); }); - describe('When signin function is called', () => { + describe('When signIn function is called', () => { it('should assign the isAuthenticated flag to true.', () => { const expected = true; - const actual = authReducer(undefined, signin.fulfilled('', '', '')); + const actual = authReducer(initialState, signIn.fulfilled('', '', '')); expect(actual.isAuthenticated).toEqual(expected); }); }); - describe('When signin function is called and it is rejected', () => { + describe('When signIn function is called and it is rejected', () => { it('should assign the isAuthenticated flag to false.', () => { const expected = false; - const actual = authReducer(undefined, signin.rejected(null, '', '')); + const actual = authReducer(initialState, signIn.rejected(null, '', '')); expect(actual.isAuthenticated).toEqual(expected); }); diff --git a/src/features/auth/store/auth.slice.ts b/src/features/auth/store/auth.slice.ts index db78fba..89daed2 100644 --- a/src/features/auth/store/auth.slice.ts +++ b/src/features/auth/store/auth.slice.ts @@ -1,6 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; -import { authenticate, signin } from './auth.actions'; -import type { RootState } from '../../../store/types'; +import type { RootState } from '@store/types'; +import { authenticate, signIn } from './auth.actions'; export type AuthState = { address: string; @@ -40,10 +40,13 @@ const authSlice = createSlice({ state.isAuthenticated = payload; state.isAuthCompleted = true; }); - builder.addCase(signin.fulfilled, (state) => { + builder.addCase(authenticate.rejected, (state) => { + state.isAuthCompleted = false; + }); + builder.addCase(signIn.fulfilled, (state) => { state.isAuthenticated = true; }); - builder.addCase(signin.rejected, (state) => { + builder.addCase(signIn.rejected, (state) => { state.isAuthenticated = false; }); }, diff --git a/src/features/collections/services/collections.service.ts b/src/features/collections/services/collections.service.ts index eb9bf60..19d1eb8 100644 --- a/src/features/collections/services/collections.service.ts +++ b/src/features/collections/services/collections.service.ts @@ -3,7 +3,7 @@ import { ICollection } from '../../../types/ICollection'; import { Item } from '../../../types/item'; import { FilterType, PriceRangeType } from '../../../types/item-filter-types'; import ICollectionService from '../interfaces/collections-service.interface'; -import { CollectionsFiltersTypes } from '../types/CollectionsFiltersTypes'; +import { CollectionFilterType } from '../types/CollectionsFiltersTypes'; export default class CollectionsService extends HttpService implements ICollectionService { private readonly endpoint: string; @@ -29,7 +29,7 @@ export default class CollectionsService extends HttpService implements ICollecti } async findPagedCollections( - filters: CollectionsFiltersTypes + filters: CollectionFilterType ): Promise<{ collections: ICollection[]; totalCount: number }> { const { limit, page, search, popularityOrder, creationOrder, mintType } = filters; const { data } = await this.instance.get<{ collections: ICollection[]; totalCount: number }>( diff --git a/src/features/collections/store/collections.actions.ts b/src/features/collections/store/collections.actions.ts index 57908c4..a9affdc 100644 --- a/src/features/collections/store/collections.actions.ts +++ b/src/features/collections/store/collections.actions.ts @@ -1,71 +1,47 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import { collectionsService } from '../services/collections.service'; -import { CollectionsFiltersTypes } from '../types/CollectionsFiltersTypes'; -import type { RootState } from '../../../store/types'; -import { openToastError } from '../../../store/ui/ui.slice'; import { FilterType } from '../../../types/item-filter-types'; +import { collectionsService } from '../services/collections.service'; +import { CollectionFilterType } from '../types/CollectionsFiltersTypes'; -const findCollectionById = createAsyncThunk('collections/findById', async (collectionId: string) => - collectionsService.findById(collectionId) -); - -const findAllCollections = createAsyncThunk('collections/findAll', async () => - collectionsService.findAll() +export const findCollectionById = createAsyncThunk( + 'marketplace/findById', + async (collectionId: string) => collectionsService.findById(collectionId) ); -const getNewestCollections = createAsyncThunk('collections/getNewest', async (qty: number) => - collectionsService.findNewest(qty) +export const getNewestCollections = createAsyncThunk( + 'marketplace/getNewestCollections', + async (qty: number) => collectionsService.findNewest(qty) ); -const findPagedCollections = createAsyncThunk( - 'collections/findPagedCollections', - async (filters: CollectionsFiltersTypes) => collectionsService.findPagedCollections(filters) +export const findPagedCollections = createAsyncThunk( + 'marketplace/findPagedCollections', + async (filters: CollectionFilterType) => collectionsService.findPagedCollections(filters) ); -const findFilteredCollections = createAsyncThunk( - 'collections/findFilteredCollections', - async (filters: CollectionsFiltersTypes, { getState, dispatch }) => { - const { collections } = getState() as RootState; - const payload = await collectionsService.findPagedCollections(filters); - if (!payload.totalCount) { - dispatch(openToastError('No collections found.')); - return collections.collectionPagination; - } - return payload; - } +export const findFilteredCollections = createAsyncThunk( + 'marketplace/findFilteredCollections', + async (filters: CollectionFilterType) => collectionsService.findPagedCollections(filters) ); -const findFilteredCollectionItems = createAsyncThunk( - 'collections/findFilteredCollectionItems', +export const findFilteredCollectionItems = createAsyncThunk( + 'marketplace/findFilteredCollectionItems', async ({ collectionId, filters }: { collectionId: string; filters: FilterType }) => collectionsService.findPagedCollectionItems(collectionId, filters) ); -const findPagedCollectionsNfts = createAsyncThunk( - 'collections/findFilteredCollectionsNfts', +export const findPagedCollectionsNfts = createAsyncThunk( + 'marketplace/findFilteredCollectionsNfts', async ({ collectionId, page }: { collectionId: string; page: number }) => collectionsService.findPagedCollectionsNfts(collectionId, page) ); -const findPriceRange = createAsyncThunk( +export const findCollectionsByPriceRange = createAsyncThunk( 'collections/findPriceRange', async (collectionId: string) => collectionsService.findPriceRangeCollectionItems(collectionId) ); -const findPagedCollectionItems = createAsyncThunk( +export const findPagedCollectionItems = createAsyncThunk( 'collections/findPagedCollectionItems', async ({ collectionId, filters }: { collectionId: string; filters: FilterType }) => collectionsService.findPagedCollectionItems(collectionId, filters) ); - -export { - findCollectionById, - findAllCollections, - getNewestCollections, - findPagedCollections, - findFilteredCollections, - findPagedCollectionsNfts, - findPriceRange, - findPagedCollectionItems, - findFilteredCollectionItems, -}; diff --git a/src/features/collections/store/collections.slice.ts b/src/features/collections/store/collections.slice.ts deleted file mode 100644 index 51a7d2a..0000000 --- a/src/features/collections/store/collections.slice.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; -import { ICollection } from '../../../types/ICollection'; -import { - findCollectionById, - getNewestCollections, - findPagedCollections, - findFilteredCollections, - findPagedCollectionsNfts, - findPriceRange, - findFilteredCollectionItems, - findPagedCollectionItems, -} from './collections.actions'; -import type { RootState } from '../../../store/types'; -import { CollectionPagination, CollectionsFiltersTypes } from '../types/CollectionsFiltersTypes'; -import { FilterType, ItemPagination } from '../../../types/item-filter-types'; -import { Item } from '../../../types/item'; -import { likeItem } from '../../marketplace/store/marketplace.actions'; - -type CollectionsState = { - selectedCollection: ICollection; - itemsStats: { - items: Item[]; - totalCount: number; - page: number; - limit: number; - isLoadingItemsStats: boolean; - }; - collectionItemsFiltering: { - itemsFilters: FilterType; - itemsPagination: ItemPagination; - isCollectionNftsLoading: boolean; - }; - newestCollections: ICollection[]; - isLoadingCollections: boolean; - collectionsFilters: CollectionsFiltersTypes; - collectionPagination: CollectionPagination; - isPagingLoading: boolean; -}; - -const initialState: CollectionsState = { - newestCollections: [] as ICollection[], - itemsStats: { - items: [], - totalCount: 0, - page: 1, - limit: 3, - isLoadingItemsStats: false, - }, - collectionItemsFiltering: { - itemsFilters: { - likesDirection: '', - search: '', - priceRange: { - from: '', - to: '', - }, - cheapest: '', - mostExpensive: '', - page: 1, - limit: 8, - } as FilterType, - itemsPagination: { items: [], totalCount: 0 }, - isCollectionNftsLoading: false, - }, - selectedCollection: {} as ICollection, - isLoadingCollections: false, - collectionsFilters: { - search: '', - popularityOrder: '', - creationOrder: '', - mintType: '', - page: 1, - limit: 15, - }, - collectionPagination: { collections: [], totalCount: 0 }, - isPagingLoading: false, -}; - -const collectionsSlice = createSlice({ - name: 'collections', - initialState, - reducers: { - setCollectionsFilters: (state, { payload }) => { - state.collectionsFilters = payload; - }, - resetCollectionsFilters: (state) => { - state.collectionsFilters = initialState.collectionsFilters; - }, - resetSelectedCollectionStats: (state) => { - state.itemsStats = initialState.itemsStats; - }, - setCollectionsNftsFilters: (state, { payload }) => { - state.collectionItemsFiltering.itemsFilters = payload; - }, - resetCollectionsNftFilters: (state) => { - state.collectionItemsFiltering.itemsFilters = - initialState.collectionItemsFiltering.itemsFilters; - }, - setSelectedCollection: (state, { payload }) => { - state.selectedCollection = payload; - }, - }, - extraReducers: (builder) => { - builder.addCase(findPagedCollectionItems.pending, (state) => { - state.collectionItemsFiltering.isCollectionNftsLoading = true; - }); - builder.addCase(findPagedCollectionItems.fulfilled, (state, { payload }) => { - state.collectionItemsFiltering.itemsPagination.items = [ - ...state.collectionItemsFiltering.itemsPagination.items, - ...payload.items, - ]; - state.collectionItemsFiltering.isCollectionNftsLoading = false; - }); - builder.addCase(findPagedCollectionItems.rejected, (state) => { - state.collectionItemsFiltering.isCollectionNftsLoading = false; - }); - // find filtered collection items - builder.addCase(findFilteredCollectionItems.pending, (state) => { - state.collectionItemsFiltering.isCollectionNftsLoading = true; - }); - builder.addCase(findFilteredCollectionItems.fulfilled, (state, { payload }) => { - state.collectionItemsFiltering.itemsPagination = payload; - state.collectionItemsFiltering.isCollectionNftsLoading = false; - }); - builder.addCase(findFilteredCollectionItems.rejected, (state) => { - state.collectionItemsFiltering.isCollectionNftsLoading = false; - }); - // find price range with items inside a collection - builder.addCase(findPriceRange.pending, (state) => { - state.collectionItemsFiltering.isCollectionNftsLoading = true; - }); - builder.addCase(findPriceRange.fulfilled, (state, { payload }) => { - state.collectionItemsFiltering.itemsFilters.cheapest = payload.from; - state.collectionItemsFiltering.itemsFilters.mostExpensive = payload.to; - state.collectionItemsFiltering.isCollectionNftsLoading = false; - }); - builder.addCase(findPriceRange.rejected, (state) => { - state.collectionItemsFiltering.isCollectionNftsLoading = false; - }); - // find nfts from a collections - builder.addCase(findPagedCollectionsNfts.pending, (state) => { - state.itemsStats.isLoadingItemsStats = true; - }); - builder.addCase(findPagedCollectionsNfts.fulfilled, (state, { payload }) => { - state.itemsStats.limit = payload.limit; - state.itemsStats.page = payload.page; - state.itemsStats.totalCount = payload.totalCount; - state.itemsStats.items = [...state.itemsStats.items, ...payload.items]; - state.itemsStats.isLoadingItemsStats = false; - }); - builder.addCase(findPagedCollectionsNfts.rejected, (state) => { - state.itemsStats.isLoadingItemsStats = false; - }); - // find filtered collections - builder.addCase(findFilteredCollections.pending, (state) => { - state.isLoadingCollections = true; - }); - builder.addCase(findFilteredCollections.fulfilled, (state, { payload }) => { - state.collectionPagination = payload; - state.isLoadingCollections = false; - }); - builder.addCase(findFilteredCollections.rejected, (state) => { - state.isLoadingCollections = false; - }); - // find paginated collections - builder.addCase(findPagedCollections.pending, (state) => { - state.isPagingLoading = true; - }); - builder.addCase(findPagedCollections.fulfilled, (state, { payload }) => { - state.collectionPagination.collections = [ - ...state.collectionPagination.collections, - ...payload.collections, - ]; - state.collectionPagination.totalCount = payload.totalCount; - state.isLoadingCollections = false; - }); - builder.addCase(findPagedCollections.rejected, (state) => { - state.isLoadingCollections = false; - }); - // get latets collections - builder.addCase(getNewestCollections.pending, (state) => { - state.isLoadingCollections = true; - }); - builder.addCase(getNewestCollections.fulfilled, (state, { payload }) => { - state.newestCollections = payload; - state.isLoadingCollections = false; - }); - builder.addCase(getNewestCollections.rejected, (state) => { - state.isLoadingCollections = false; - }); - // get collection by id - builder.addCase(findCollectionById.pending, (state) => { - state.isLoadingCollections = true; - }); - builder.addCase(findCollectionById.fulfilled, (state, { payload }) => { - state.selectedCollection = payload; - state.isLoadingCollections = false; - }); - builder.addCase(findCollectionById.rejected, (state) => { - state.isLoadingCollections = false; - }); - builder.addCase(likeItem.fulfilled, (state, { payload }) => { - const indexItemsStats = state.itemsStats.items.findIndex((i) => i.itemId === payload.itemId); - const indexItemsFiltering = state.collectionItemsFiltering.itemsPagination.items.findIndex( - (i) => i.itemId === payload.itemId - ); - state.itemsStats.items[indexItemsStats] = payload; - state.collectionItemsFiltering.itemsPagination.items[indexItemsFiltering] = payload; - }); - }, -}); - -export const collectionsReducer = collectionsSlice.reducer; - -export const { - resetCollectionsFilters, - setCollectionsNftsFilters, - setCollectionsFilters, - resetSelectedCollectionStats, - resetCollectionsNftFilters, - setSelectedCollection, -} = collectionsSlice.actions; - -export const selectCollectionsState = (state: RootState) => state.collections; - -export const selectCurrentSelection = (state: RootState) => state.collections.selectedCollection; - -export const selectCurrentSelectionItemsFiltering = (state: RootState) => - state.collections.collectionItemsFiltering; diff --git a/src/features/collections/types/CollectionsFiltersTypes.ts b/src/features/collections/types/CollectionsFiltersTypes.ts index fbd1bf5..ae1326e 100644 --- a/src/features/collections/types/CollectionsFiltersTypes.ts +++ b/src/features/collections/types/CollectionsFiltersTypes.ts @@ -1,15 +1,7 @@ -import { ICollection } from '../../../types/ICollection'; +import { FilterTypeBase } from '../../../types/item-filter-types'; -export type CollectionsFiltersTypes = { - search: string; +export type CollectionFilterType = { popularityOrder: string | 'asc' | 'desc'; creationOrder: string | 'asc' | 'desc'; mintType: string | 'lazy' | 'normal'; - limit: number; - page: number; -}; - -export type CollectionPagination = { - collections: ICollection[]; - totalCount: number; -}; +} & FilterTypeBase; diff --git a/src/features/leda-nft/store/leda-nft.actions.ts b/src/features/leda-nft/store/leda-nft.actions.ts index 2c8cfab..c0bfd46 100644 --- a/src/features/leda-nft/store/leda-nft.actions.ts +++ b/src/features/leda-nft/store/leda-nft.actions.ts @@ -1,13 +1,13 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { Item, ItemRequest } from '@types'; import Router from 'next/router'; +import { openToastError, openToastSuccess } from '@store/ui/ui.slice'; import BusinessError from '../../../common/exceptions/business-error'; import ClientProcessor from '../../../common/minting/clients/client-processor'; import CollectionType from '../../../common/minting/enums/collection-type.enum'; import ContractEvent from '../../../common/minting/enums/contract-event.enum'; import { LazyProcessType } from '../../../common/minting/enums/lazy-process-type.enum'; import MintState from '../../../common/minting/types/mint-state'; -import { openToastError, openToastSuccess } from '../../../store/ui/ui.slice'; import { getContracts } from '../../../utils/getContracts'; import { setIsModalOpen } from '../../marketplace/store/marketplace.slice'; import { itemService } from '../services/item.service'; diff --git a/src/features/leda-nft/store/leda-nft.slice.ts b/src/features/leda-nft/store/leda-nft.slice.ts index 880fe5d..2a5c0c9 100644 --- a/src/features/leda-nft/store/leda-nft.slice.ts +++ b/src/features/leda-nft/store/leda-nft.slice.ts @@ -1,6 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; import { Item } from '@types'; -import type { RootState } from '../../../store/types'; +import type { RootState } from '@store/types'; import { buyItem, likeItem, listItem } from '../../marketplace/store/marketplace.actions'; import { getNewest, mintNft, redeemVoucher } from './leda-nft.actions'; @@ -76,8 +76,6 @@ export const selectNftState = (state: RootState) => state.ledaNft; export const selectAllItems = (state: RootState) => state.ledaNft.items; -export const selectNewest = (state: RootState) => state.ledaNft.items.slice(0, 5); - export const selectById = (state: RootState, itemId: string) => state.ledaNft.items.find((item) => item.itemId === itemId); diff --git a/src/features/marketplace/process/commands/buy-item/buy-item-command.ts b/src/features/marketplace/process/commands/buy-item/buy-item-command.ts index 5661a3a..bf7c02d 100644 --- a/src/features/marketplace/process/commands/buy-item/buy-item-command.ts +++ b/src/features/marketplace/process/commands/buy-item/buy-item-command.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; -import { IMarketplaceService } from '../../../services/marketplace-service.interface'; import { rejectWithMetamask } from '../../../../../store/error/error-handler'; +import { IMarketplaceService } from '../../../services/marketplace-service.interface'; import ICommand from '../../interfaces/command.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import MarketplaceState from '../../types/marketplace-state'; diff --git a/src/features/marketplace/process/commands/buy-item/store-buy-item-command.ts b/src/features/marketplace/process/commands/buy-item/store-buy-item-command.ts index 1cf8d0c..d23dbf3 100644 --- a/src/features/marketplace/process/commands/buy-item/store-buy-item-command.ts +++ b/src/features/marketplace/process/commands/buy-item/store-buy-item-command.ts @@ -1,8 +1,8 @@ +import { rejectWithHttp } from '../../../../../store/error/error-handler'; import IItemService from '../../../../leda-nft/interfaces/item-service.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import ICommand from '../../interfaces/command.interface'; import MarketplaceState from '../../types/marketplace-state'; -import { rejectWithHttp } from '../../../../../store/error/error-handler'; export default class StoreBuyItemCommand implements ICommand { private readonly itemService: IItemService; diff --git a/src/features/marketplace/process/commands/change-price-item/change-price-item-command.spec.ts b/src/features/marketplace/process/commands/change-price-item/change-price-item-command.spec.ts index 57b5f77..13cc605 100644 --- a/src/features/marketplace/process/commands/change-price-item/change-price-item-command.spec.ts +++ b/src/features/marketplace/process/commands/change-price-item/change-price-item-command.spec.ts @@ -1,9 +1,9 @@ import { ContractReceipt, ethers, Event } from 'ethers'; +import * as errorHandler from '../../../../../store/error/error-handler'; import MarketplaceError from '../../enums/marketplace-error.enum'; import ICommand from '../../interfaces/command.interface'; import MarketplaceState from '../../types/marketplace-state'; import ChangePriceItemCommand from './change-price-item-command'; -import * as errorHandler from '../../../../../store/error/error-handler'; const marketplaceServiceMock = { init: jest.fn(), diff --git a/src/features/marketplace/process/commands/change-price-item/change-price-item-command.ts b/src/features/marketplace/process/commands/change-price-item/change-price-item-command.ts index 998927a..e69e17e 100644 --- a/src/features/marketplace/process/commands/change-price-item/change-price-item-command.ts +++ b/src/features/marketplace/process/commands/change-price-item/change-price-item-command.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; -import { IMarketplaceService } from '../../../services/marketplace-service.interface'; import { rejectWithMetamask } from '../../../../../store/error/error-handler'; +import { IMarketplaceService } from '../../../services/marketplace-service.interface'; import ICommand from '../../interfaces/command.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import MarketplaceState from '../../types/marketplace-state'; diff --git a/src/features/marketplace/process/commands/change-price-item/store-change-price-item-command.ts b/src/features/marketplace/process/commands/change-price-item/store-change-price-item-command.ts index 49fc995..d42b512 100644 --- a/src/features/marketplace/process/commands/change-price-item/store-change-price-item-command.ts +++ b/src/features/marketplace/process/commands/change-price-item/store-change-price-item-command.ts @@ -1,8 +1,8 @@ +import { rejectWithHttp } from '../../../../../store/error/error-handler'; import IItemService from '../../../../leda-nft/interfaces/item-service.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import ICommand from '../../interfaces/command.interface'; import MarketplaceState from '../../types/marketplace-state'; -import { rejectWithHttp } from '../../../../../store/error/error-handler'; export default class StoreChangePriceItemCommand implements ICommand { private readonly itemService: IItemService; diff --git a/src/features/marketplace/process/commands/common-lazy/generate-jup-voucher-command.ts b/src/features/marketplace/process/commands/common-lazy/generate-jup-voucher-command.ts index 6065b0b..9a2936c 100644 --- a/src/features/marketplace/process/commands/common-lazy/generate-jup-voucher-command.ts +++ b/src/features/marketplace/process/commands/common-lazy/generate-jup-voucher-command.ts @@ -1,9 +1,9 @@ import { ethers } from 'ethers'; +import { rejectWithHttp } from '../../../../../store/error/error-handler'; import ILazyMintService from '../../../../leda-nft/interfaces/lazy-mint-service.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import ICommand from '../../interfaces/command.interface'; import MarketplaceState from '../../types/marketplace-state'; -import { rejectWithHttp } from '../../../../../store/error/error-handler'; import IImageService from '../../../../leda-nft/interfaces/image-service.interface'; export default class GenerateJupVoucherCommand implements ICommand { diff --git a/src/features/marketplace/process/commands/common-lazy/generate-voucher-command.ts b/src/features/marketplace/process/commands/common-lazy/generate-voucher-command.ts index 074d7e4..8800456 100644 --- a/src/features/marketplace/process/commands/common-lazy/generate-voucher-command.ts +++ b/src/features/marketplace/process/commands/common-lazy/generate-voucher-command.ts @@ -1,9 +1,9 @@ import { ethers } from 'ethers'; +import { rejectWithHttp } from '../../../../../store/error/error-handler'; import ILazyMintService from '../../../../leda-nft/interfaces/lazy-mint-service.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import ICommand from '../../interfaces/command.interface'; import MarketplaceState from '../../types/marketplace-state'; -import { rejectWithHttp } from '../../../../../store/error/error-handler'; import IImageService from '../../../../leda-nft/interfaces/image-service.interface'; export default class GenerateVoucherCommand implements ICommand { diff --git a/src/features/marketplace/process/commands/common-lazy/store-voucher-command.ts b/src/features/marketplace/process/commands/common-lazy/store-voucher-command.ts index 707f26a..f127279 100644 --- a/src/features/marketplace/process/commands/common-lazy/store-voucher-command.ts +++ b/src/features/marketplace/process/commands/common-lazy/store-voucher-command.ts @@ -1,9 +1,9 @@ +import { rejectWithHttp } from '../../../../../store/error/error-handler'; import ProcessLazyItemRequest from '../../../../../common/types/process-lazy-item-request'; import IItemService from '../../../../leda-nft/interfaces/item-service.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import ICommand from '../../interfaces/command.interface'; import MarketplaceState from '../../types/marketplace-state'; -import { rejectWithHttp } from '../../../../../store/error/error-handler'; export default class StoreVoucherCommand implements ICommand { private readonly itemService: IItemService; diff --git a/src/features/marketplace/process/commands/delist-item/change-status-item-command.spec.ts b/src/features/marketplace/process/commands/delist-item/change-status-item-command.spec.ts index 26b4fdd..eecc9b2 100644 --- a/src/features/marketplace/process/commands/delist-item/change-status-item-command.spec.ts +++ b/src/features/marketplace/process/commands/delist-item/change-status-item-command.spec.ts @@ -169,7 +169,7 @@ describe('ListItemCommand', () => { const errorMessage = 'something went wrong'; - const expected = { ...state, error: MarketplaceError.ChangePriceItemFailure }; + const expected = { ...state, error: MarketplaceError.ChangeStatusItemFailure }; jest.spyOn(errorHandler, 'rejectWithMetamask').mockResolvedValue(expected); diff --git a/src/features/marketplace/process/commands/delist-item/store-delist-item-command.ts b/src/features/marketplace/process/commands/delist-item/store-delist-item-command.ts index 9283ccb..3263429 100644 --- a/src/features/marketplace/process/commands/delist-item/store-delist-item-command.ts +++ b/src/features/marketplace/process/commands/delist-item/store-delist-item-command.ts @@ -1,8 +1,8 @@ +import { rejectWithHttp } from '../../../../../store/error/error-handler'; import IItemService from '../../../../leda-nft/interfaces/item-service.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import ICommand from '../../interfaces/command.interface'; import MarketplaceState from '../../types/marketplace-state'; -import { rejectWithHttp } from '../../../../../store/error/error-handler'; export default class StoreDelistItemCommand implements ICommand { private readonly itemService: IItemService; diff --git a/src/features/marketplace/process/commands/list-item/approve-command.ts b/src/features/marketplace/process/commands/list-item/approve-command.ts index b09b49e..c375d91 100644 --- a/src/features/marketplace/process/commands/list-item/approve-command.ts +++ b/src/features/marketplace/process/commands/list-item/approve-command.ts @@ -1,5 +1,5 @@ -import { getContracts } from '../../../../../utils/getContracts'; import { rejectWithMetamask } from '../../../../../store/error/error-handler'; +import { getContracts } from '../../../../../utils/getContracts'; import ICommand from '../../interfaces/command.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import MarketplaceState from '../../types/marketplace-state'; diff --git a/src/features/marketplace/process/commands/list-item/list-item-command.spec.ts b/src/features/marketplace/process/commands/list-item/list-item-command.spec.ts index bf17f65..1e974dd 100644 --- a/src/features/marketplace/process/commands/list-item/list-item-command.spec.ts +++ b/src/features/marketplace/process/commands/list-item/list-item-command.spec.ts @@ -1,9 +1,9 @@ import { ContractReceipt, ethers, Event } from 'ethers'; +import * as errorHandler from '../../../../../store/error/error-handler'; import MarketplaceError from '../../enums/marketplace-error.enum'; import ICommand from '../../interfaces/command.interface'; import MarketplaceState from '../../types/marketplace-state'; import ListItemCommand from './list-item-command'; -import * as errorHandler from '../../../../../store/error/error-handler'; const marketplaceServiceMock = { init: jest.fn(), diff --git a/src/features/marketplace/process/commands/list-item/list-item-command.ts b/src/features/marketplace/process/commands/list-item/list-item-command.ts index 41212d9..5e9efad 100644 --- a/src/features/marketplace/process/commands/list-item/list-item-command.ts +++ b/src/features/marketplace/process/commands/list-item/list-item-command.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; -import { IMarketplaceService } from '../../../services/marketplace-service.interface'; import { rejectWithMetamask } from '../../../../../store/error/error-handler'; +import { IMarketplaceService } from '../../../services/marketplace-service.interface'; import ICommand from '../../interfaces/command.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import MarketplaceState from '../../types/marketplace-state'; diff --git a/src/features/marketplace/process/commands/list-item/store-list-item-command.ts b/src/features/marketplace/process/commands/list-item/store-list-item-command.ts index 45b1c5a..dae2921 100644 --- a/src/features/marketplace/process/commands/list-item/store-list-item-command.ts +++ b/src/features/marketplace/process/commands/list-item/store-list-item-command.ts @@ -1,8 +1,8 @@ +import { rejectWithHttp } from '../../../../../store/error/error-handler'; import IItemService from '../../../../leda-nft/interfaces/item-service.interface'; import MarketplaceError from '../../enums/marketplace-error.enum'; import ICommand from '../../interfaces/command.interface'; import MarketplaceState from '../../types/marketplace-state'; -import { rejectWithHttp } from '../../../../../store/error/error-handler'; export default class StoreListItemCommand implements ICommand { private readonly itemService: IItemService; diff --git a/src/features/marketplace/store/marketplace.actions.ts b/src/features/marketplace/store/marketplace.actions.ts index fe10d4f..97aa55a 100644 --- a/src/features/marketplace/store/marketplace.actions.ts +++ b/src/features/marketplace/store/marketplace.actions.ts @@ -1,19 +1,18 @@ import { createAsyncThunk, Dispatch } from '@reduxjs/toolkit'; import Router from 'next/router'; -import { getContracts } from '../../../utils/getContracts'; -import { FilterType, FilterTypeBase } from '../../../types/item-filter-types'; +import type { RootState } from '../../../store/types'; import { openToastError, openToastSuccess } from '../../../store/ui/ui.slice'; import BusinessError from '../../../common/exceptions/business-error'; import CollectionType from '../../../common/minting/enums/collection-type.enum'; -import ContractEvent from '../process/enums/contract-event.enum'; -import ItemService, { itemService } from '../../leda-nft/services/item.service'; import ItemStatus from '../../../common/minting/enums/item-status.enum'; -import MarketplaceClientProcessor from '../process/clients/marketplace-client-processor'; -import MarketplaceService from '../services/marketplace.service'; -import MarketplaceState from '../process/types/marketplace-state'; -import type { RootState } from '../../../store/types'; import { LazyProcessType } from '../../../common/minting/enums/lazy-process-type.enum'; import { Item } from '../../../types/item'; +import { FilterType, FilterTypeBase } from '../../../types/item-filter-types'; +import { getContracts } from '../../../utils/getContracts'; +import ItemService, { itemService } from '../../leda-nft/services/item.service'; +import MarketplaceClientProcessor from '../process/clients/marketplace-client-processor'; +import ContractEvent from '../process/enums/contract-event.enum'; +import MarketplaceState from '../process/types/marketplace-state'; const { LedaAddress } = getContracts(); @@ -35,70 +34,58 @@ export const findPriceRange = createAsyncThunk('marketplace/findPriceRange', asy itemService.findPriceRange() ); -export const getOwner = createAsyncThunk('marketplace/getNftList', async () => { - const service = new MarketplaceService(); - return service.getOwner(); -}); - -export const listItem = createAsyncThunk( - 'marketplace/listItem', - async ( - { +export const listItem = createAsyncThunk< + Item, + { + address: string; + price: string; + item: Item; + } +>('marketplace/listItem', async ({ address, price, item }, { dispatch }) => { + const { + tokenId, + listId, + itemId, + image, + isLazy, + royalty, + owner, + collectionAddress, + stakingRewards, + } = item; + try { + const listItemState = { address, + collection: CollectionType.LedaNft, + collectionAddress, + mintEventName: ContractEvent.LogCreateItem, price, - item, - }: { - address: string; - price: string; - item: Item; - }, - { dispatch } - ) => { - const { tokenId, - listId, itemId, - image, + item: { itemId, stakingRewards }, + status: ItemStatus.Listed, + ownerAddress: owner.address, + listId, + cid: image.cid, + imageUrl: image.url, + lazyProcessType: LazyProcessType.Listing, isLazy, royalty, - owner, - collectionAddress, - stakingRewards, - } = item; - try { - const listItemState = { - address, - collection: CollectionType.LedaNft, - collectionAddress, - mintEventName: ContractEvent.LogCreateItem, - price, - tokenId, - itemId, - item: { itemId, stakingRewards }, - status: ItemStatus.Listed, - ownerAddress: owner.address, - listId, - cid: image.cid, - imageUrl: image.url, - lazyProcessType: LazyProcessType.Listing, - isLazy, - royalty, - } as MarketplaceState; + } as MarketplaceState; - const processor = new MarketplaceClientProcessor(); - const listed = await processor.execute(listItemState); + const processor = new MarketplaceClientProcessor(); + const listed = await processor.execute(listItemState); - dispatch(openToastSuccess('The item has been successfully listed on the marketplace.')); + dispatch(openToastSuccess('The item has been successfully listed on the marketplace.')); - return listed.item; - } catch (err) { - if (err instanceof BusinessError) { - dispatch(openToastError(err.message)); - } - throw err; + return listed.item; + } catch (err) { + if (err instanceof BusinessError) { + dispatch(openToastError(err.message)); } + throw err; } -); +}); export const delistItem = createAsyncThunk( 'marketplace/delistItem', diff --git a/src/features/marketplace/store/marketplace.slice.spec.ts b/src/features/marketplace/store/marketplace.slice.spec.ts index ae24919..61dac24 100644 --- a/src/features/marketplace/store/marketplace.slice.spec.ts +++ b/src/features/marketplace/store/marketplace.slice.spec.ts @@ -1,20 +1,14 @@ import { AnyAction } from '@reduxjs/toolkit'; -import store from '../../../store'; -import { FilterType } from '../../../types/item-filter-types'; +import { History } from '../../../types/history'; +import { ICollection } from '../../../types/ICollection'; import { Item } from '../../../types/item'; -import { - findFilteredItems, - findPagedItems, - getOwner, - listItem, - getNewest, -} from './marketplace.actions'; +import { FilterType } from '../../../types/item-filter-types'; +import { findFilteredItems, findPagedItems, getNewest, listItem } from './marketplace.actions'; import { marketplaceReducer, MarketplaceState, - resetMarketplaceFilters, - selectOwner, - setMarketplaceFilters, + resetFilters, + setFilters, } from './marketplace.slice'; describe('Marketplace slice', () => { @@ -22,32 +16,46 @@ describe('Marketplace slice', () => { beforeEach(() => { initialState = { - owner: '', - isLoading: false, - isPagingLoading: false, - isLoadingHistory: false, + items: [], + itemsCount: 0, + likedItems: [], + collections: [], + collectionsCount: 0, + collectionsWithoutItems: [], + selectedItem: {} as Item, newestItems: [], - loadingNewest: false, - marketplaceFilters: { + filters: { likesDirection: '', - cheapest: '', - mostExpensive: '', search: '', priceRange: { from: '', to: '', }, + cheapest: '', + mostExpensive: '', page: 1, - limit: 15, - } as FilterType, - itemPagination: { items: [], totalCount: 0 }, - selectedItem: {} as Item, - history: { - data: [], - count: 0, limit: 3, + }, + history: [] as History[], + historyCount: 0, + newestCollections: [] as ICollection[], + selectedCollection: {} as ICollection, + isLoadingCollections: false, + collectionsFilters: { + search: '', + popularityOrder: '', + creationOrder: '', + mintType: '', page: 1, + limit: 3, }, + isLoading: false, + isDelisting: false, + isListing: false, + isPagingLoading: false, + isLoadingHistory: false, + isLoadingNewest: false, + isLoadingCollection: false, isModalOpen: false, isCompleted: false, isOpenPreviewProductModal: false, @@ -88,17 +96,17 @@ describe('Marketplace slice', () => { it('the loadingNewest should retrieve false if it is fulfilled', () => { const expected = false; const actual = marketplaceReducer(undefined, getNewest.fulfilled); - expect(actual.loadingNewest).toEqual(expected); + expect(actual.isLoadingNewest).toEqual(expected); }); it('the loadingNewest should retrieve true if it is pending', () => { const expected = true; const actual = marketplaceReducer(undefined, getNewest.pending); - expect(actual.loadingNewest).toEqual(expected); + expect(actual.isLoadingNewest).toEqual(expected); }); it('the loadingNewest should retrieve false if it is rejected', () => { const expected = false; const actual = marketplaceReducer(undefined, getNewest.rejected); - expect(actual.loadingNewest).toEqual(expected); + expect(actual.isLoadingNewest).toEqual(expected); }); }); @@ -125,64 +133,38 @@ describe('Marketplace slice', () => { }, }; - const actual = marketplaceReducer(undefined, setMarketplaceFilters(expected)); + const actual = marketplaceReducer(undefined, setFilters(expected)); - expect(actual.marketplaceFilters).toEqual(expected); + expect(actual.filters).toEqual(expected); }); }); describe('When resetMarketplaceFilters reducer is called', () => { it('should assign the marketplace filters initial state correctly', () => { - const expected = { - likesDirection: '', - cheapest: '', - mostExpensive: '', - search: '', - priceRange: { - from: '', - to: '', - }, - limit: 15, - page: 1, - } as FilterType; - - const actual = marketplaceReducer(undefined, resetMarketplaceFilters()); + const actual = marketplaceReducer(undefined, resetFilters()); - expect(actual.marketplaceFilters).toEqual(expected); - }); - }); - - describe('When getOwner function is called', () => { - it('should assign an owner successfully', () => { - const expected = 'Jane Doe'; - - const actual = marketplaceReducer(undefined, getOwner.fulfilled(expected, '')); - - expect(actual.owner).toEqual(expected); - }); - }); - - describe('When selectOwner is called', () => { - it('should return the owner from the state', () => { - const expected = ''; - - const actual = selectOwner(store.getState()); - - expect(actual).toEqual(expected); + expect(actual.filters).toEqual(initialState.filters); }); }); describe('When isListed is called', () => { - it('should return true when isListed is succesfull', () => { + it('should return true when isListed is successfully', () => { const expected = true; - const actual = marketplaceReducer(undefined, listItem.fulfilled); + const actual = marketplaceReducer( + initialState, + listItem.fulfilled({} as Item, '', { + address: 'string', + price: 'string', + item: {} as Item, + }) + ); expect(actual.isCompleted).toEqual(expected); }); it('should return false when isListed is rejected', () => { const expected = false; - const actual = marketplaceReducer(undefined, listItem.rejected); + const actual = marketplaceReducer(initialState, listItem.rejected); expect(actual.isCompleted).toEqual(expected); }); @@ -196,8 +178,8 @@ describe('Marketplace slice', () => { findFilteredItems.pending('', {} as FilterType) ); - expect(actual.itemPagination.items.length).toEqual(0); - expect(actual.itemPagination.totalCount).toEqual(0); + expect(actual.items.length).toEqual(0); + expect(actual.itemsCount).toEqual(0); expect(actual.isLoading).toEqual(true); }); }); @@ -213,8 +195,8 @@ describe('Marketplace slice', () => { findFilteredItems.fulfilled({ items: expectedItems, totalCount: 2 }, '', {} as FilterType) ); - expect(actual.itemPagination.items).toEqual(expectedItems); - expect(actual.itemPagination.totalCount).toEqual(expectedItems.length); + expect(actual.items).toEqual(expectedItems); + expect(actual.itemsCount).toEqual(expectedItems.length); expect(actual.isLoading).toEqual(false); }); }); @@ -251,8 +233,8 @@ describe('Marketplace slice', () => { findPagedItems.fulfilled({ items: expectedItems, totalCount: 2 }, '', {} as FilterType) ); - expect(actual.itemPagination.items).toEqual(expectedItems); - expect(actual.itemPagination.totalCount).toEqual(expectedItems.length); + expect(actual.items).toEqual(expectedItems); + expect(actual.itemsCount).toEqual(expectedItems.length); expect(actual.isPagingLoading).toEqual(false); }); }); diff --git a/src/features/marketplace/store/marketplace.slice.ts b/src/features/marketplace/store/marketplace.slice.ts index a3e671c..ae252f6 100644 --- a/src/features/marketplace/store/marketplace.slice.ts +++ b/src/features/marketplace/store/marketplace.slice.ts @@ -1,41 +1,65 @@ import { createSlice } from '@reduxjs/toolkit'; -import type { RootState } from '../../../store/types'; +import type { RootState } from '@store/types'; +import ItemStatus from '../../../common/minting/enums/item-status.enum'; import { History } from '../../../types/history'; +import { ICollection } from '../../../types/ICollection'; import { Item } from '../../../types/item'; -import { FilterType, ItemPagination } from '../../../types/item-filter-types'; -import ItemStatus from '../../../common/minting/enums/item-status.enum'; +import { FilterType } from '../../../types/item-filter-types'; +import { + findItemsByAccount, + findLikedItemsByAccount, + findUserCollections, + findUserCollectionsWithoutItems, +} from '../../account/store/account.actions'; +import { + findCollectionById, + findFilteredCollectionItems, + findFilteredCollections, + findPagedCollectionItems, + findPagedCollections, + findPagedCollectionsNfts, + findCollectionsByPriceRange, + getNewestCollections, +} from '../../collections/store/collections.actions'; +import { CollectionFilterType } from '../../collections/types/CollectionsFiltersTypes'; import { + buyItem, changePriceItem, delistItem, + findAllHistory, findFilteredItems, + findHistoryByItemId, findPagedItems, findPriceRange, - findAllHistory, - findHistoryByItemId, - getOwner, - listItem, - buyItem, - likeItem, getNewest, hideItem, + likeItem, + listItem, } from './marketplace.actions'; export type MarketplaceState = { - owner: string | undefined; - marketplaceFilters: FilterType; - itemPagination: ItemPagination; + items: Item[]; + likedItems: Item[]; + collections: ICollection[]; + collectionsCount: number; + historyCount: number; + collectionsWithoutItems: ICollection[]; + itemsCount: number; + filters: FilterType; + collectionsFilters: CollectionFilterType; newestItems: Item[]; - loadingNewest: boolean; + selectedItem: Item; + history: History[]; + selectedCollection: ICollection; + newestCollections: ICollection[]; + isLoadingCollections: boolean; + isLoadingNewest: boolean; isLoading: boolean; isPagingLoading: boolean; isLoadingHistory: boolean; - selectedItem: Item; - history: { - data: History[]; - count: number; - limit: number; - page: number; - }; + isDelisting: boolean; + isLoadingCollection: boolean; + isListing: boolean; isModalOpen: boolean; isCompleted: boolean; isOpenPreviewProductModal: boolean; @@ -51,12 +75,15 @@ export const initialFormState = { }; const initialState: MarketplaceState = { - owner: '', - isLoading: false, - isPagingLoading: false, - isLoadingHistory: false, - itemPagination: { items: [], totalCount: 0 }, - marketplaceFilters: { + items: [], + itemsCount: 0, + likedItems: [], + collections: [], + collectionsCount: 0, + collectionsWithoutItems: [], + selectedItem: {} as Item, + newestItems: [], + filters: { likesDirection: '', search: '', priceRange: { @@ -66,17 +93,28 @@ const initialState: MarketplaceState = { cheapest: '', mostExpensive: '', page: 1, - limit: 15, - } as FilterType, - selectedItem: {} as Item, - newestItems: [], - loadingNewest: false, - history: { - data: [], - count: 0, limit: 3, + }, + history: [] as History[], + historyCount: 0, + newestCollections: [] as ICollection[], + selectedCollection: {} as ICollection, + isLoadingCollections: false, + collectionsFilters: { + search: '', + popularityOrder: '', + creationOrder: '', + mintType: '', page: 1, + limit: 3, }, + isLoading: false, + isDelisting: false, + isListing: false, + isPagingLoading: false, + isLoadingHistory: false, + isLoadingNewest: false, + isLoadingCollection: false, isModalOpen: false, isCompleted: false, isOpenPreviewProductModal: false, @@ -86,11 +124,13 @@ const marketplaceSlice = createSlice({ name: 'marketplace', initialState, reducers: { - setMarketplaceFilters: (state, { payload }) => { - state.marketplaceFilters = payload; + setFilters: (state, { payload }) => { + state.filters = payload; }, - resetMarketplaceFilters: (state) => { - state.marketplaceFilters = initialState.marketplaceFilters; + resetFilters: (state) => { + state.filters = initialState.filters; + state.items = initialState.items; + state.history = initialState.history; }, setSelectedItem: (state, { payload }) => { state.selectedItem = payload; @@ -101,43 +141,50 @@ const marketplaceSlice = createSlice({ setIsOpenPreviewProductModal: (state, { payload }) => { state.isOpenPreviewProductModal = payload; }, + setCollectionsFilters: (state, { payload }) => { + state.collectionsFilters = payload; + }, + setSelectedCollection: (state, { payload }) => { + state.selectedCollection = payload; + }, }, extraReducers: (builder) => { // List Item builder.addCase(listItem.pending, (state) => { - state.isLoading = true; + state.isListing = true; }); builder.addCase(listItem.fulfilled, (state, { payload: item }) => { - state.isLoading = false; + state.isListing = false; state.isCompleted = true; state.isModalOpen = false; state.selectedItem = item; }); + builder.addCase(listItem.rejected, (state) => { + state.isListing = false; + }); + // getNewestItems builder.addCase(getNewest.pending, (state) => { - state.loadingNewest = true; + state.isLoadingNewest = true; }); builder.addCase(getNewest.fulfilled, (state, { payload }) => { state.newestItems = payload; - state.loadingNewest = false; + state.isLoadingNewest = false; }); builder.addCase(getNewest.rejected, (state) => { - state.loadingNewest = false; - }); - builder.addCase(listItem.rejected, (state) => { - state.isLoading = false; + state.isLoadingNewest = false; }); // Delist Item builder.addCase(delistItem.pending, (state) => { - state.isLoading = true; + state.isDelisting = true; }); builder.addCase(delistItem.fulfilled, (state, { payload: item }) => { - state.isLoading = false; + state.isDelisting = false; state.isCompleted = true; state.isModalOpen = false; state.selectedItem = item; }); builder.addCase(delistItem.rejected, (state) => { - state.isLoading = false; + state.isDelisting = false; }); // Change Price builder.addCase(changePriceItem.pending, (state) => { @@ -166,14 +213,57 @@ const marketplaceSlice = createSlice({ state.isLoading = false; state.isModalOpen = false; }); - builder.addCase(getOwner.fulfilled, (state, { payload }) => { - state.owner = payload; + builder.addCase(findItemsByAccount.pending, (state) => { + state.isLoading = true; + }); + builder.addCase(findItemsByAccount.fulfilled, (state, { payload }) => { + state.items = payload; + }); + builder.addCase(findLikedItemsByAccount.pending, (state) => { + state.isLoading = true; + }); + builder.addCase(findLikedItemsByAccount.fulfilled, (state, { payload }) => { + state.likedItems = payload; + }); + builder.addCase(findUserCollectionsWithoutItems.pending, (state) => { + state.isLoadingCollection = true; + }); + builder.addCase(findUserCollectionsWithoutItems.fulfilled, (state, { payload }) => { + state.isLoadingCollection = false; + state.collectionsWithoutItems = payload; + }); + builder.addCase(findUserCollectionsWithoutItems.rejected, (state) => { + state.isLoadingCollection = false; + }); + builder.addCase(findUserCollections.pending, (state) => { + state.isLoadingCollection = true; + }); + builder.addCase(findUserCollections.fulfilled, (state, { payload }) => { + state.collections = payload; + state.isLoadingCollection = false; + }); + builder.addCase(findUserCollections.rejected, (state) => { + state.isLoadingCollection = false; + }); + builder.addCase(likeItem.fulfilled, (state, { payload }) => { + const index = state.items.findIndex((i) => i.itemId === payload.itemId); + state.items[index] = payload; + + const likedIndex = state.likedItems.findIndex((i) => i.itemId === payload.itemId); + + if (likedIndex !== -1) state.likedItems.splice(likedIndex, 1); + else state.likedItems.push(payload); + + if (state.selectedItem.itemId === payload.itemId) state.selectedItem = payload; + const indexNewest = state.newestItems.findIndex((i) => i.itemId === payload.itemId); + state.newestItems[indexNewest] = payload; }); builder.addCase(findFilteredItems.pending, (state) => { state.isLoading = true; }); builder.addCase(findFilteredItems.fulfilled, (state, { payload }) => { - state.itemPagination = payload; + state.items = payload.items; + state.itemsCount = payload.totalCount; state.isLoading = false; }); builder.addCase(findFilteredItems.rejected, (state) => { @@ -183,16 +273,16 @@ const marketplaceSlice = createSlice({ state.isPagingLoading = true; }); builder.addCase(findPagedItems.fulfilled, (state, { payload }) => { - state.itemPagination.items = [...state.itemPagination.items, ...payload.items]; - state.itemPagination.totalCount = payload.totalCount; + state.items = [...state.items, ...payload.items]; + state.itemsCount = payload.totalCount; state.isPagingLoading = false; }); builder.addCase(findPagedItems.rejected, (state) => { state.isPagingLoading = false; }); builder.addCase(findPriceRange.fulfilled, (state, { payload }) => { - state.marketplaceFilters.cheapest = payload.from; - state.marketplaceFilters.mostExpensive = payload.to; + state.filters.cheapest = payload.from; + state.filters.mostExpensive = payload.to; }); builder.addCase(findHistoryByItemId.pending, (state) => { state.isLoadingHistory = true; @@ -211,31 +301,114 @@ const marketplaceSlice = createSlice({ state.isLoading = false; state.isLoadingHistory = false; - state.history.data = [...state.history.data, ...payload.history]; - state.history.count = payload.count; + state.history = [...state.history, ...payload.history]; + state.historyCount = payload.count; }); - builder.addCase(likeItem.fulfilled, (state, { payload }) => { - const indexPagination = state.itemPagination.items.findIndex( - (i) => i.itemId === payload.itemId - ); - state.itemPagination.items[indexPagination] = payload; - if (state.selectedItem.itemId === payload.itemId) state.selectedItem = payload; - const indexNewest = state.newestItems.findIndex((i) => i.itemId === payload.itemId); - state.newestItems[indexNewest] = payload; - }); builder.addCase(hideItem.fulfilled, (state, { payload }) => { - const index = state.itemPagination.items.findIndex((i) => i.itemId === payload.itemId); - state.itemPagination.items[index] = payload; + const index = state.items.findIndex((i) => i.itemId === payload.itemId); + state.items[index] = payload; if (state.selectedItem.itemId === payload.itemId) state.selectedItem = payload; }); + + // FIND PAGED COLLECTIONS + builder.addCase(findPagedCollectionItems.pending, (state) => { + state.isPagingLoading = true; + }); + builder.addCase(findPagedCollectionItems.fulfilled, (state, { payload }) => { + state.items = [...state.items, ...payload.items]; + state.isPagingLoading = false; + }); + builder.addCase(findPagedCollectionItems.rejected, (state) => { + state.isPagingLoading = false; + }); + // find filtered collection items + builder.addCase(findFilteredCollectionItems.pending, (state) => { + state.isPagingLoading = true; + }); + builder.addCase(findFilteredCollectionItems.fulfilled, (state, { payload }) => { + state.items = payload.items; + state.itemsCount = payload.totalCount; + state.isPagingLoading = false; + }); + builder.addCase(findFilteredCollectionItems.rejected, (state) => { + state.isPagingLoading = false; + }); + // find price range with items inside a collection + builder.addCase(findCollectionsByPriceRange.pending, (state) => { + state.isPagingLoading = true; + }); + builder.addCase(findCollectionsByPriceRange.fulfilled, (state, { payload }) => { + state.filters.cheapest = payload.from; + state.filters.mostExpensive = payload.to; + state.isPagingLoading = false; + }); + builder.addCase(findCollectionsByPriceRange.rejected, (state) => { + state.isPagingLoading = false; + }); + // find nfts from a collections + builder.addCase(findPagedCollectionsNfts.pending, (state) => { + state.isPagingLoading = true; + }); + builder.addCase(findPagedCollectionsNfts.fulfilled, (state, { payload }) => { + state.filters.page = payload.page; + state.itemsCount = payload.totalCount; + state.items = [...state.items, ...payload.items]; + state.isPagingLoading = false; + }); + builder.addCase(findPagedCollectionsNfts.rejected, (state) => { + state.isPagingLoading = false; + }); + // find filtered collections + builder.addCase(findFilteredCollections.pending, (state) => { + state.isLoadingCollections = true; + }); + builder.addCase(findFilteredCollections.fulfilled, (state, { payload }) => { + state.collections = payload.collections; + state.collectionsCount = payload.totalCount; + state.isLoadingCollections = false; + }); + builder.addCase(findFilteredCollections.rejected, (state) => { + state.isLoadingCollections = false; + }); + // find paginated collections + builder.addCase(findPagedCollections.pending, (state) => { + state.isPagingLoading = true; + }); + builder.addCase(findPagedCollections.fulfilled, (state, { payload }) => { + state.collections = [...state.collections, ...payload.collections]; + state.collectionsCount = payload.totalCount; + state.isLoadingCollections = false; + }); + builder.addCase(findPagedCollections.rejected, (state) => { + state.isLoadingCollections = false; + }); + // get latests collections + builder.addCase(getNewestCollections.pending, (state) => { + state.isLoadingCollections = true; + }); + builder.addCase(getNewestCollections.fulfilled, (state, { payload }) => { + state.newestCollections = payload; + state.isLoadingCollections = false; + }); + builder.addCase(getNewestCollections.rejected, (state) => { + state.isLoadingCollections = false; + }); + // get collection by id + builder.addCase(findCollectionById.pending, (state) => { + state.isLoadingCollections = true; + }); + builder.addCase(findCollectionById.fulfilled, (state, { payload }) => { + state.selectedCollection = payload; + state.isLoadingCollections = false; + }); + builder.addCase(findCollectionById.rejected, (state) => { + state.isLoadingCollections = false; + }); }, }); - -export const selectOwner = (state: RootState) => state.marketplace.owner; - -export const selectNFTsMarketplace = (state: RootState) => state.marketplace; +export const selectMarketplaceState = (state: RootState) => state.marketplace; export const selectCanIList = (state: RootState) => { const { @@ -244,6 +417,7 @@ export const selectCanIList = (state: RootState) => { } = state; return selectedItem.owner?.address === address && selectedItem.status === ItemStatus.NotListed; }; + export const selectCanIDelist = (state: RootState) => { const { auth: { address }, @@ -277,10 +451,6 @@ export const selectIsOwner = (state: RootState) => { return address === selectedItem?.owner?.address; }; -export const selectNewest = (state: RootState) => state.marketplace.newestItems.slice(0, 2); - -export const selectMarketplaceState = (state: RootState) => state.marketplace; - export const selectIsLoadingWhileBuy = (state: RootState) => { const { marketplace, ledaNft } = state; @@ -292,11 +462,13 @@ export const selectIsLoadingWhileBuy = (state: RootState) => { }; export const { - setMarketplaceFilters, - resetMarketplaceFilters, + setFilters, + resetFilters, setSelectedItem, setIsModalOpen, setIsOpenPreviewProductModal, + setCollectionsFilters, + setSelectedCollection, } = marketplaceSlice.actions; export const marketplaceReducer = marketplaceSlice.reducer; diff --git a/src/layouts/header/NetworkNotice.tsx b/src/layouts/header/NetworkNotice.tsx index 7376e3f..835f468 100644 --- a/src/layouts/header/NetworkNotice.tsx +++ b/src/layouts/header/NetworkNotice.tsx @@ -1,9 +1,9 @@ import clsx from 'clsx'; -import { NetworkNames } from '../../common/enums/network-names.enum'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; +import { selectUiReducer, setIsNetworkAdviceOpen } from '@store/ui/ui.slice'; import useMetamask from '../../features/auth/hooks/useMetamask'; -import useAppDispatch from '../../store/hooks/useAppDispatch'; -import useAppSelector from '../../store/hooks/useAppSelector'; -import { selectUiReducer, setIsNetworkAdviceOpen } from '../../store/ui/ui.slice'; +import { NetworkNames } from '../../common/enums/network-names.enum'; const NETWORK_NAMES: { [key: string]: string } = { [NetworkNames.MAINNET]: 'Mainnet', diff --git a/src/layouts/header/index.tsx b/src/layouts/header/index.tsx index 40333a7..ca8b951 100644 --- a/src/layouts/header/index.tsx +++ b/src/layouts/header/index.tsx @@ -8,11 +8,11 @@ import BurgerButton from '@ui/burger-button'; import Button from '@ui/button'; import clsx from 'clsx'; import { useRouter } from 'next/router'; +import useAppSelector from '@store/hooks/useAppSelector'; import headerData from '../../data/general/header-01.json'; import menuData from '../../data/general/menu-01.json'; import useMetamask from '../../features/auth/hooks/useMetamask'; import { selectAuthState } from '../../features/auth/store/auth.slice'; -import useAppSelector from '../../store/hooks/useAppSelector'; import { NetworkNotice } from './NetworkNotice'; type Props = { diff --git a/src/layouts/wrapper.tsx b/src/layouts/wrapper.tsx index c04e4be..5c1ced6 100644 --- a/src/layouts/wrapper.tsx +++ b/src/layouts/wrapper.tsx @@ -4,12 +4,11 @@ import Header from '@layout/header'; import ScrollToTop from '@ui/scroll-to-top'; import { useTheme } from 'next-themes'; import { useEffect } from 'react'; -import { selectAuthState, setIsMainnet } from '../features/auth/store/auth.slice'; -import useAppDispatch from '../store/hooks/useAppDispatch'; +import useAppDispatch from '@store/hooks/useAppDispatch'; +import useAppSelector from '@store/hooks/useAppSelector'; +import { setIsNetworkAdviceOpen } from '@store/ui/ui.slice'; import { findLikedItemsByAccount } from '../features/account/store/account.actions'; -import useAppSelector from '../store/hooks/useAppSelector'; -import { setIsNetworkAdviceOpen } from '../store/ui/ui.slice'; -import { resetSelectedCollectionStats } from '../features/collections/store/collections.slice'; +import { selectAuthState, setIsMainnet } from '../features/auth/store/auth.slice'; import { NetworkNames } from '../common/enums/network-names.enum'; import useMetamask from '../features/auth/hooks/useMetamask'; import NetworkRequestModal from '../components/modals/network-request-modal/network-request.modal'; @@ -26,7 +25,6 @@ const Wrapper = ({ children }: Props) => { useEffect(() => { if (isAuthenticated) dispatch(findLikedItemsByAccount(address)); - dispatch(resetSelectedCollectionStats()); dispatch(setIsNetworkAdviceOpen(true)); }, [dispatch, isAuthenticated, address]); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 43ef6c5..1b1c61c 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -14,6 +14,7 @@ import type { AppProps as NextAppProps } from 'next/app'; import Wrapper from '@layout/wrapper'; import store from '../store'; // modified version - allows for custom pageProps type, falling back to 'any' +// eslint-disable-next-line @typescript-eslint/no-explicit-any type AppProps

    = { pageProps: P; } & Omit, 'pageProps'>; diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 7c20df1..3ccf688 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import Document, { Html, Head, Main, NextScript } from 'next/document'; class MyDocument extends Document { @@ -14,6 +15,7 @@ class MyDocument extends Document {