From f04929ed72e867c11ca18a06e9bf2c0837e4da07 Mon Sep 17 00:00:00 2001 From: Lautaro Petaccio <1120791+LautaroPetaccio@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:12:05 -0300 Subject: [PATCH] feat: Display collections by type (#3162) * feat: Display collections by type * fix: Correctly show collection items * fix: Add the collections icon * fix: Tests --- package-lock.json | 47 ++++ package.json | 1 + .../CollectionsPage/CollectionsPage.css | 16 +- .../CollectionsPage/CollectionsPage.tsx | 205 ++++++++------- .../CollectionsPage/CollectionsPage.types.ts | 5 +- .../WorldPermissionsAddUserFrom.spec.tsx | 1 - src/icons/collections.svg | 6 + src/lib/pagination.spec.ts | 248 ++++++++++++++++++ src/lib/pagination.ts | 106 ++++++++ src/modules/translation/languages/en.json | 1 + src/modules/translation/languages/es.json | 1 + src/modules/translation/languages/zh.json | 1 + 12 files changed, 544 insertions(+), 94 deletions(-) create mode 100644 src/icons/collections.svg create mode 100644 src/lib/pagination.spec.ts create mode 100644 src/lib/pagination.ts diff --git a/package-lock.json b/package-lock.json index e2e1c1a57..941bb7491 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,6 +88,7 @@ "@swc/core": "^1.3.105", "@swc/jest": "^0.2.31", "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "^8.0.1", "@types/blob-to-buffer": "^1.2.0", "@types/file-saver": "^2.0.0", "@types/interface-datastore": "^1.0.0", @@ -6789,6 +6790,36 @@ "react-dom": "<18.0.0" } }, + "node_modules/@testing-library/react-hooks": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", + "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "react-error-boundary": "^3.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0", + "react": "^16.9.0 || ^17.0.0", + "react-dom": "^16.9.0 || ^17.0.0", + "react-test-renderer": "^16.9.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-test-renderer": { + "optional": true + } + } + }, "node_modules/@testing-library/user-event": { "version": "13.5.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", @@ -20837,6 +20868,22 @@ "react": ">= 16.8" } }, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-fast-compare": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", diff --git a/package.json b/package.json index 9d658baf2..b1ce6445f 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@swc/core": "^1.3.105", "@swc/jest": "^0.2.31", "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "^8.0.1", "@types/blob-to-buffer": "^1.2.0", "@types/file-saver": "^2.0.0", "@types/interface-datastore": "^1.0.0", diff --git a/src/components/CollectionsPage/CollectionsPage.css b/src/components/CollectionsPage/CollectionsPage.css index e67a0cf46..a95f50285 100644 --- a/src/components/CollectionsPage/CollectionsPage.css +++ b/src/components/CollectionsPage/CollectionsPage.css @@ -180,8 +180,9 @@ .CollectionsPage .collections-main-actions { display: flex; + justify-content: end; gap: 20px; - margin-top: 20px; + min-height: 74px; } .CollectionsPage .collections-main-actions > :first-child { @@ -201,3 +202,16 @@ justify-self: end; flex: none; } + +.CollectionsPage .collections-icon { + width: 32px; + height: 32px; + margin-bottom: 20px; +} + +.CollectionsPage .action-tabs-container { + display: flex; + align-items: center; + flex-direction: row; + gap: 8px; +} diff --git a/src/components/CollectionsPage/CollectionsPage.tsx b/src/components/CollectionsPage/CollectionsPage.tsx index a15dc239a..92a2db872 100644 --- a/src/components/CollectionsPage/CollectionsPage.tsx +++ b/src/components/CollectionsPage/CollectionsPage.tsx @@ -28,6 +28,9 @@ import EventBanner from 'components/EventBanner' import { locations } from 'routing/locations' import { CollectionPageView } from 'modules/ui/collection/types' import { CurationSortOptions } from 'modules/curations/types' +import { usePagination } from 'lib/pagination' +import { CollectionType } from 'modules/collection/types' +import collectionsIcon from 'icons/collections.svg' import ItemCard from './ItemCard' import ItemRow from './ItemRow' import CollectionCard from './CollectionCard' @@ -59,21 +62,39 @@ export default function CollectionsPage(props: Props) { onSetView } = props - const [currentTab, setCurrentTab] = useState(TABS.COLLECTIONS) - const [sort, setSort] = useState(CurationSortOptions.CREATED_AT_DESC) - const [page, setPage] = useState(1) - const [search, setSearch] = useState('') - const timeout = useRef(null) const history = useHistory() + const { page, pages, goToPage, filters, changeFilter, sortBy, changeSorting } = usePagination<'search' | 'section', CurationSortOptions>({ + pageSize: PAGE_SIZE, + count: collectionsPaginationData?.total + }) + const currentTab = filters.section ?? TABS.STANDARD_COLLECTIONS + const isViewingCollections = currentTab === TABS.STANDARD_COLLECTIONS || currentTab === TABS.THIRD_PARTY_COLLECTIONS + const [search, setSearch] = useState(filters.search ?? '') + const timeout = useRef(null) + + // Fetch user orphan items + useEffect(() => { + if (hasUserOrphanItems === undefined && address) { + onFetchOrphanItem(address) + } + }, [address, hasUserOrphanItems]) + // Fetch collections or orphan items useEffect(() => { if (address) { - onFetchCollections(address, { page: 1, limit: PAGE_SIZE, sort }) - if (hasUserOrphanItems === undefined) { - onFetchOrphanItem(address) + if (currentTab === TABS.ITEMS) { + onFetchOrphanItems(address, { page, limit: PAGE_SIZE }) + } else { + onFetchCollections(address, { + page, + limit: PAGE_SIZE, + q: filters.search ?? undefined, + type: currentTab === TABS.STANDARD_COLLECTIONS ? CollectionType.STANDARD : CollectionType.THIRD_PARTY, + sort: sortBy + }) } } - }, [address, sort]) + }, [address, page, currentTab, filters.search, sortBy, onFetchOrphanItems, onFetchCollections]) const handleNewThirdPartyCollection = useCallback(() => { onOpenModal('CreateThirdPartyCollectionModal') @@ -87,17 +108,20 @@ export default function CollectionsPage(props: Props) { } }, [onOpenModal, isLinkedWearablesPaymentsEnabled]) - const handleSearchChange = (_evt: React.ChangeEvent, data: InputOnChangeData) => { - setSearch(data.value) - if (timeout.current) { - clearTimeout(timeout.current) - timeout.current = null - } + const handleSearchChange = useCallback( + (_evt: React.ChangeEvent, data: InputOnChangeData) => { + setSearch(data.value) + if (timeout.current) { + clearTimeout(timeout.current) + timeout.current = null + } - timeout.current = setTimeout(() => { - onFetchCollections(address, { page: 1, limit: PAGE_SIZE, q: data.value }) - }, 500) - } + timeout.current = setTimeout(() => { + changeFilter('search', data.value) + }, 500) + }, + [changeFilter] + ) const handleOpenEditor = useCallback(() => { history.push(locations.itemEditor()) @@ -106,30 +130,24 @@ export default function CollectionsPage(props: Props) { const handleSortChange = useCallback( (_event: React.SyntheticEvent, { value }: DropdownProps) => { const sort = value as CurationSortOptions - setSort(sort) - setPage(1) - onFetchCollections(address, { page: 1, limit: PAGE_SIZE, sort }) + changeSorting(sort) }, - [address] + [changeSorting] ) const handleTabChange = useCallback( (tab: TABS) => { - setCurrentTab(tab) - setPage(1) - const fetchFn = tab === TABS.ITEMS ? onFetchOrphanItems : onFetchCollections - const params = tab === TABS.ITEMS ? { page: 1, limit: PAGE_SIZE } : { page: 1, limit: PAGE_SIZE, sort } - if (address) { - fetchFn(address, params) + if (currentTab !== tab) { + changeFilter('section', tab) } }, - [address] + [address, currentTab, changeFilter] ) const renderGrid = useCallback(() => { return ( - {currentTab === TABS.COLLECTIONS ? ( + {isViewingCollections ? ( collections.map((collection, index) => ) ) : isLoadingItems ? ( @@ -138,10 +156,10 @@ export default function CollectionsPage(props: Props) { )} ) - }, [items, collections, isLoadingItems, currentTab]) + }, [items, collections, isLoadingItems, isViewingCollections, currentTab]) const renderList = useCallback(() => { - if (currentTab === TABS.COLLECTIONS) { + if (isViewingCollections) { return (
@@ -183,43 +201,32 @@ export default function CollectionsPage(props: Props) {
) - }, [currentTab, items, collections]) - - const fetchCollections = useCallback(() => { - onFetchCollections(address, { page, limit: PAGE_SIZE, sort }) - }, [onFetchCollections, page, address, sort]) - - const fetchItems = useCallback(() => { - if (address) { - onFetchOrphanItems(address, { page, limit: PAGE_SIZE }) - } - }, [onFetchOrphanItems, address, page]) + }, [items, collections, isViewingCollections]) const handlePageChange = useCallback( (_event: React.MouseEvent, props: PaginationProps) => { - setPage(+props.activePage!) - if (currentTab === TABS.COLLECTIONS) { - fetchCollections() - } else { - fetchItems() + if (page !== props.activePage) { + goToPage(Number(props.activePage)) } }, - [currentTab, fetchCollections, fetchItems] + [page, goToPage] ) const renderMainActions = useCallback(() => { return (
- } - iconPosition="left" - value={search} - isClearable - /> + {currentTab !== TABS.ITEMS && ( + } + iconPosition="left" + value={search} + isClearable + /> + )} {isThirdPartyManager && !isLinkedWearablesPaymentsEnabled && (