diff --git a/package-lock.json b/package-lock.json index 7ceec26db..d79e48aa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,13 @@ "dependencies": { "@babylonjs/core": "^4.2.0", "@babylonjs/loaders": "^4.2.0", - "@dcl/builder-client": "^4.4.0", + "@dcl/builder-client": "^5.0.0", "@dcl/builder-templates": "^0.2.0", "@dcl/content-hash-tree": "^1.1.3", "@dcl/crypto": "^3.4.5", "@dcl/hashing": "^3.0.4", "@dcl/mini-rpc": "^1.0.7", - "@dcl/schemas": "^11.12.0", + "@dcl/schemas": "^13.2.2", "@dcl/sdk": "7.5.5", "@dcl/single-sign-on-client": "^0.1.0", "@dcl/ui-env": "^1.5.0", @@ -2261,13 +2261,13 @@ } }, "node_modules/@dcl/builder-client": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@dcl/builder-client/-/builder-client-4.4.0.tgz", - "integrity": "sha512-T09X9dkBfqo87JinBh2QYEN4m+KV71oQIebl6pOyhcP3NQgYr8o2HyMGp0ewmKzDTE7U1MBqIgqqbBAllUmN0w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@dcl/builder-client/-/builder-client-5.0.0.tgz", + "integrity": "sha512-6xyxQTNPfoZh0LzMAO0Wkavaq+nSwGHxmxdkCl6jPH66AOilGRXCETq7r1BYQTG1KBNcgN1nTrg4ZzrFSbso0g==", "dependencies": { "@dcl/crypto": "^3.0.1", "@dcl/hashing": "^1.1.0", - "@dcl/schemas": "^11.12.0", + "@dcl/schemas": "^13.2.2", "ajv": "^8.11.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", @@ -2723,9 +2723,9 @@ } }, "node_modules/@dcl/schemas": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-11.12.0.tgz", - "integrity": "sha512-L04KTucvxSnrHDAl3/rnkzhjfZ785dSSPeKarBVfzyuw41uyQ0Mh4HVFWjX9hC+f/nMpM5Adg5udlT5efmepcA==", + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-13.2.2.tgz", + "integrity": "sha512-mQDvLxNL/yoWdmDKFzfpi0xJoPAEmxAoMGzwCVcmiBbP3h5T6S6RjXV9rwkZ8OQKhDURbf6wEQTJc9WF3xEF0g==", "dependencies": { "ajv": "^8.11.0", "ajv-errors": "^3.0.0", @@ -11333,6 +11333,17 @@ "tslib": "^2.6.2" } }, + "node_modules/decentraland-connect/node_modules/@dcl/schemas": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-11.12.0.tgz", + "integrity": "sha512-L04KTucvxSnrHDAl3/rnkzhjfZ785dSSPeKarBVfzyuw41uyQ0Mh4HVFWjX9hC+f/nMpM5Adg5udlT5efmepcA==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-errors": "^3.0.0", + "ajv-keywords": "^5.1.0", + "mitt": "^3.0.1" + } + }, "node_modules/decentraland-connect/node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -11481,6 +11492,17 @@ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" }, + "node_modules/decentraland-dapps/node_modules/@dcl/schemas": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-11.12.0.tgz", + "integrity": "sha512-L04KTucvxSnrHDAl3/rnkzhjfZ785dSSPeKarBVfzyuw41uyQ0Mh4HVFWjX9hC+f/nMpM5Adg5udlT5efmepcA==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-errors": "^3.0.0", + "ajv-keywords": "^5.1.0", + "mitt": "^3.0.1" + } + }, "node_modules/decentraland-dapps/node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -11854,6 +11876,17 @@ } } }, + "node_modules/decentraland-ui/node_modules/@dcl/schemas": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-11.12.0.tgz", + "integrity": "sha512-L04KTucvxSnrHDAl3/rnkzhjfZ785dSSPeKarBVfzyuw41uyQ0Mh4HVFWjX9hC+f/nMpM5Adg5udlT5efmepcA==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-errors": "^3.0.0", + "ajv-keywords": "^5.1.0", + "mitt": "^3.0.1" + } + }, "node_modules/decentraland-ui/node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", diff --git a/package.json b/package.json index 958d4f0c7..916d86302 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,13 @@ "dependencies": { "@babylonjs/core": "^4.2.0", "@babylonjs/loaders": "^4.2.0", - "@dcl/builder-client": "^4.4.0", + "@dcl/builder-client": "^5.0.0", "@dcl/builder-templates": "^0.2.0", "@dcl/content-hash-tree": "^1.1.3", "@dcl/crypto": "^3.4.5", "@dcl/hashing": "^3.0.4", "@dcl/mini-rpc": "^1.0.7", - "@dcl/schemas": "^11.12.0", + "@dcl/schemas": "^13.2.2", "@dcl/sdk": "7.5.5", "@dcl/single-sign-on-client": "^0.1.0", "@dcl/ui-env": "^1.5.0", diff --git a/src/components/CollectionsPage/CollectionsPage.tsx b/src/components/CollectionsPage/CollectionsPage.tsx index a0dc8da4e..c32cd4260 100644 --- a/src/components/CollectionsPage/CollectionsPage.tsx +++ b/src/components/CollectionsPage/CollectionsPage.tsx @@ -76,7 +76,7 @@ export default function CollectionsPage(props: Props) { }, [address, sort]) const handleNewThirdPartyCollection = useCallback(() => { - onOpenModal('CreateLinkedWearablesCollectionModal') + onOpenModal('CreateThirdPartyCollectionModal') }, [onOpenModal, isLinkedWearablesV2Enabled]) const handleNewCollection = useCallback(() => { @@ -213,7 +213,7 @@ export default function CollectionsPage(props: Props) { } iconPosition="left" @@ -236,7 +236,7 @@ export default function CollectionsPage(props: Props) { ) - }, [search, isThirdPartyManager, handleSearchChange, handleOpenEditor, handleNewCollection]) + }, [search, isThirdPartyManager, isLinkedWearablesV2Enabled, handleSearchChange, handleOpenEditor, handleNewCollection]) const renderViewActions = useCallback(() => { return ( diff --git a/src/components/MappingEditor/MappingEditor.module.css b/src/components/MappingEditor/MappingEditor.module.css index 5cff41846..96a20ec80 100644 --- a/src/components/MappingEditor/MappingEditor.module.css +++ b/src/components/MappingEditor/MappingEditor.module.css @@ -8,10 +8,22 @@ flex-direction: column; } -.main :global(.ui.dropdown > .text > img) { +.mappingType :global(.ui.dropdown > .text > img) { margin-top: 3px; } .mappingType :global(.ui.dropdown .menu > .item > img) { margin-top: 0px; } + +.linkedContractSelect :global(.divider.text), +.linkedContractSelect :global(.visible.menu.transition) > div { + display: flex !important; + align-items: center !important; +} + +.linkedContractSelect :global(.divider.text) img, +.linkedContractSelect :global(.visible.menu.transition) img { + height: 25px; + width: 25px; +} diff --git a/src/components/MappingEditor/MappingEditor.tsx b/src/components/MappingEditor/MappingEditor.tsx index 33a0f3244..43707e100 100644 --- a/src/components/MappingEditor/MappingEditor.tsx +++ b/src/components/MappingEditor/MappingEditor.tsx @@ -2,10 +2,14 @@ import { SyntheticEvent, useCallback, useMemo } from 'react' import { DropdownProps, Field, InputOnChangeData, SelectField, TextAreaField, TextAreaProps } from 'decentraland-ui' import { MappingType, MultipleMapping } from '@dcl/schemas' import { t } from 'decentraland-dapps/dist/modules/translation' +import { LinkedContractProtocol } from 'modules/thirdParty/types' +import { shorten } from 'lib/address' import allIcon from '../../icons/all.svg' import multipleIcon from '../../icons/multiple.svg' import singleIcon from '../../icons/single.svg' import rangeIcon from '../../icons/range.svg' +import ethereumSvg from '../../icons/ethereum.svg' +import polygonSvg from '../../icons/polygon.svg' import { Props } from './MappingEditor.types' import styles from './MappingEditor.module.css' @@ -15,9 +19,26 @@ const mappingTypeIcons = { [MappingType.SINGLE]: singleIcon, [MappingType.RANGE]: rangeIcon } +const imgSrcByNetwork = { + [LinkedContractProtocol.MAINNET]: ethereumSvg, + [LinkedContractProtocol.MATIC]: polygonSvg, + [LinkedContractProtocol.SEPOLIA]: ethereumSvg, + [LinkedContractProtocol.AMOY]: polygonSvg +} export const MappingEditor = (props: Props) => { - const { mapping, error, disabled, onChange } = props + const { mapping, error, disabled, contract, contracts, onChange } = props + const linkedContractsOptions = useMemo( + () => + contracts.map((contract, index) => ({ + value: index, + key: index, + image: imgSrcByNetwork[contract.network], + text: shorten(contract.address, 14, 14) + })), + [contracts, imgSrcByNetwork] + ) + const [mappingType, mappingValue] = useMemo(() => { switch (mapping.type) { case MappingType.MULTIPLE: @@ -42,58 +63,87 @@ export const MappingEditor = (props: Props) => { [] ) - const handleMappingTypeChange = useCallback((_: SyntheticEvent, { value }: DropdownProps) => { - const mappingType = value as MappingType - switch (mappingType) { - case MappingType.ANY: - props.onChange({ type: mappingType }) - break - case MappingType.MULTIPLE: - props.onChange({ type: mappingType, ids: [] }) - break - case MappingType.SINGLE: - props.onChange({ type: mappingType, id: '' }) - break - case MappingType.RANGE: - props.onChange({ type: mappingType, to: '', from: '' }) - break - } - }, []) + const handleMappingTypeChange = useCallback( + (_: SyntheticEvent, { value }: DropdownProps) => { + const mappingType = value as MappingType + switch (mappingType) { + case MappingType.ANY: + onChange({ type: mappingType }, contract) + break + case MappingType.MULTIPLE: + onChange({ type: mappingType, ids: [] }, contract) + break + case MappingType.SINGLE: + onChange({ type: mappingType, id: '' }, contract) + break + case MappingType.RANGE: + onChange({ type: mappingType, to: '', from: '' }, contract) + break + } + }, + [contract, onChange] + ) - const handleSingleMappingValueChange = useCallback((_: React.ChangeEvent, data: InputOnChangeData) => { - onChange({ type: MappingType.SINGLE, id: data.value }) - }, []) + const handleSingleMappingValueChange = useCallback( + (_: React.ChangeEvent, data: InputOnChangeData) => { + onChange({ type: MappingType.SINGLE, id: data.value }, contract) + }, + [contract] + ) - const handleMultipleMappingValueChange = useCallback((_: React.ChangeEvent, data: TextAreaProps) => { - const ids = - data.value - ?.toString() - .replaceAll(/[^0-9,\s]/g, '') - .split(',') - .map(value => value.trim()) ?? [] + const handleMultipleMappingValueChange = useCallback( + (_: React.ChangeEvent, data: TextAreaProps) => { + const ids = + data.value + ?.toString() + .replaceAll(/[^0-9,\s]/g, '') + .split(',') + .map(value => value.trim()) ?? [] - onChange({ - type: MappingType.MULTIPLE, - ids - }) - }, []) + onChange( + { + type: MappingType.MULTIPLE, + ids + }, + contract + ) + }, + [contract] + ) const handleFromMappingValueChange = useCallback( (_: React.ChangeEvent, data: InputOnChangeData) => { - onChange({ type: MappingType.RANGE, from: data.value, to: mappingValue.split(',')[1] }) + onChange({ type: MappingType.RANGE, from: data.value, to: mappingValue.split(',')[1] }, contract) }, - [mappingValue] + [mappingValue, contract] ) const handleToMappingValueChange = useCallback( (_: React.ChangeEvent, data: InputOnChangeData) => { - onChange({ type: MappingType.RANGE, from: mappingValue.split(',')[0], to: data.value }) + onChange({ type: MappingType.RANGE, from: mappingValue.split(',')[0], to: data.value }, contract) }, - [mappingValue] + [mappingValue, contract] + ) + + const handleLinkedContractChange = useCallback( + (_: SyntheticEvent, { value }: DropdownProps) => { + onChange(mapping, contracts[value as number]) + }, + [mapping, contracts] ) return (
+ void + onChange: (mapping: Mapping, contract: LinkedContract) => void } diff --git a/src/components/Modals/CreateAndEditMultipleItemsModal/CreateAndEditMultipleItemsModal.tsx b/src/components/Modals/CreateAndEditMultipleItemsModal/CreateAndEditMultipleItemsModal.tsx index 0797f4901..e96d72b5e 100644 --- a/src/components/Modals/CreateAndEditMultipleItemsModal/CreateAndEditMultipleItemsModal.tsx +++ b/src/components/Modals/CreateAndEditMultipleItemsModal/CreateAndEditMultipleItemsModal.tsx @@ -1,6 +1,5 @@ import * as React from 'react' import PQueue from 'p-queue' -import uuid from 'uuid' import { ItemFactory, loadFile, @@ -24,14 +23,12 @@ import { config } from 'config' import { EngineType, getModelData } from 'lib/getModelData' import { getExtension, toMB } from 'lib/file' import { - getDefaultThirdPartyItemUrnSuffix, buildThirdPartyURN, - buildThirdPartyV2URN, decodedCollectionsUrnAreEqual, DecodedURN, decodeURN, + getDefaultThirdPartyUrnSuffix, isThirdPartyCollectionDecodedUrn, - isThirdPartyV2CollectionDecodedUrn, URNType } from 'lib/urn' import { convertImageIntoWearableThumbnail, dataURLToBlob, getImageType } from 'modules/media/utils' @@ -176,15 +173,10 @@ export default class CreateAndEditMultipleItemsModal extends React.PureComponent // Generate or set the correct URN for the items taking into consideration the selected collection const decodedCollectionUrn: DecodedURN | null = collection?.urn ? decodeURN(collection.urn) : null // Check if the collection is a third party collection - if ( - decodedCollectionUrn && - (isThirdPartyCollectionDecodedUrn(decodedCollectionUrn) || isThirdPartyV2CollectionDecodedUrn(decodedCollectionUrn)) - ) { + if (decodedCollectionUrn && isThirdPartyCollectionDecodedUrn(decodedCollectionUrn)) { const decodedUrn: DecodedURN | null = loadedFile.wearable.id ? decodeURN(loadedFile.wearable.id) : null const thirdPartyTokenId = - loadedFile.wearable.id && - decodedUrn && - (decodedUrn.type === URNType.COLLECTIONS_THIRDPARTY || decodedUrn.type === URNType.COLLECTIONS_THIRDPARTY_V2) + loadedFile.wearable.id && decodedUrn && decodedUrn.type === URNType.COLLECTIONS_THIRDPARTY ? decodedUrn.thirdPartyTokenId ?? null : null @@ -199,16 +191,7 @@ export default class CreateAndEditMultipleItemsModal extends React.PureComponent buildThirdPartyURN( decodedCollectionUrn.thirdPartyName, decodedCollectionUrn.thirdPartyCollectionId, - thirdPartyTokenId ?? uuid.v4() - ) - ) - } else { - itemFactory.withUrn( - buildThirdPartyV2URN( - decodedCollectionUrn.thirdPartyLinkedCollectionName, - decodedCollectionUrn.linkedCollectionNetwork, - decodedCollectionUrn.linkedCollectionAddress, - getDefaultThirdPartyItemUrnSuffix(loadedFile.wearable.name) + thirdPartyTokenId ?? getDefaultThirdPartyUrnSuffix(loadedFile.wearable.name) ) ) } diff --git a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.container.ts b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.container.ts index 67fb4502e..5ac4fcaae 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.container.ts +++ b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.container.ts @@ -15,9 +15,9 @@ const mapDispatch = (dispatch: MapDispatch, ownProps: OwnProps): MapDispatchProp ownProps.onClose() dispatch(openModal('CreateCollectionModal')) }, - onCreateLinkedWearablesCollection: () => { + onCreateThirdPartyCollection: () => { ownProps.onClose() - dispatch(openModal('CreateLinkedWearablesCollectionModal')) + dispatch(openModal('CreateThirdPartyCollectionModal')) } }) diff --git a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.spec.tsx b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.spec.tsx index 3e8ccf754..575a5aa3d 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.spec.tsx +++ b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.spec.tsx @@ -8,7 +8,7 @@ export function renderWorldContributorTab(props: Partial) { return renderWithProviders( ) { describe('when clicking on the create collection button', () => { let renderedComponent: ReturnType let onCreateCollection: jest.Mock - let onCreateLinkedWearablesCollection: jest.Mock + let onCreateThirdPartyCollection: jest.Mock beforeEach(() => { onCreateCollection = jest.fn() - onCreateLinkedWearablesCollection = jest.fn() + onCreateThirdPartyCollection = jest.fn() renderedComponent = renderWorldContributorTab({ onCreateCollection, - onCreateLinkedWearablesCollection, + onCreateThirdPartyCollection, isThirdPartyManager: true }) }) @@ -52,8 +52,8 @@ describe('when clicking on the create collection button', () => { userEvent.click(createButton) }) - it('should call the onCreateLinkedWearablesCollection method prop', () => { - expect(onCreateLinkedWearablesCollection).toHaveBeenCalled() + it('should call the onCreateThirdPartyCollection method prop', () => { + expect(onCreateThirdPartyCollection).toHaveBeenCalled() }) }) }) diff --git a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.tsx b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.tsx index 0b5206b5c..dafe2cdf4 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.tsx +++ b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.tsx @@ -49,7 +49,7 @@ const COLLECTIONS_LEARN_MORE_URL = `${config.get('DOCS_URL')}/creator/wearables- const LINKED_COLLECTIONS_LEARN_MORE_URL = `${config.get('DOCS_URL')}/creator/wearables/linked-wearables/` export const CreateCollectionSelectorModal = (props: Props) => { - const { onClose, onCreateCollection, onCreateLinkedWearablesCollection, name, isThirdPartyManager, isLoadingThirdParties } = props + const { onClose, onCreateCollection, onCreateThirdPartyCollection, name, isThirdPartyManager, isLoadingThirdParties } = props return ( @@ -73,7 +73,7 @@ export const CreateCollectionSelectorModal = (props: Props) => { image={polygonSvg} title={t('create_collection_selector_modal.linked_collection.title')} subtitle={t('create_collection_selector_modal.linked_collection.subtitle')} - onCreate={onCreateLinkedWearablesCollection} + onCreate={onCreateThirdPartyCollection} isLoading={isLoadingThirdParties} disabled={!isThirdPartyManager || isLoadingThirdParties} learnMoreUrl={LINKED_COLLECTIONS_LEARN_MORE_URL} diff --git a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.types.ts b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.types.ts index d4cdd6aad..6c9d2b50c 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.types.ts +++ b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.types.ts @@ -6,10 +6,10 @@ export type Props = ModalProps & { isThirdPartyManager: boolean isLoadingThirdParties: boolean onCreateCollection: () => void - onCreateLinkedWearablesCollection: () => void + onCreateThirdPartyCollection: () => void } export type MapStateProps = Pick -export type MapDispatchProps = Pick +export type MapDispatchProps = Pick export type OwnProps = Pick export type MapDispatch = Dispatch diff --git a/src/components/Modals/CreateLinkedWearablesCollectionModal/CrateLinkedWearablesCollectionModal.container.ts b/src/components/Modals/CreateLinkedWearablesCollectionModal/CrateLinkedWearablesCollectionModal.container.ts deleted file mode 100644 index 54e5cf861..000000000 --- a/src/components/Modals/CreateLinkedWearablesCollectionModal/CrateLinkedWearablesCollectionModal.container.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { connect } from 'react-redux' -import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' -import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors' -import { RootState } from 'modules/common/types' -import { getLoading } from 'modules/collection/selectors' -import { openModal } from 'decentraland-dapps/dist/modules/modal' -import { SAVE_COLLECTION_REQUEST, saveCollectionRequest } from 'modules/collection/actions' -import { getWalletThirdParties, getError } from 'modules/thirdParty/selectors' -import { MapStateProps, MapDispatchProps, MapDispatch, OwnProps } from './CreateLinkedWearablesCollectionModal.types' -import { CreateLinkedWearablesCollectionModal } from './CreateLinkedWearablesCollectionModal' - -const mapState = (state: RootState): MapStateProps => ({ - ownerAddress: getAddress(state), - thirdParties: getWalletThirdParties(state), - error: getError(state), - isCreatingCollection: isLoadingType(getLoading(state), SAVE_COLLECTION_REQUEST) -}) - -const mapDispatch = (dispatch: MapDispatch, ownProps: OwnProps): MapDispatchProps => ({ - onBack: () => { - ownProps.onClose() - dispatch(openModal('CreateCollectionSelectorModal')) - }, - onSubmit: collection => dispatch(saveCollectionRequest(collection)) -}) - -export default connect(mapState, mapDispatch)(CreateLinkedWearablesCollectionModal) diff --git a/src/components/Modals/CreateLinkedWearablesCollectionModal/CreateLinkedWearablesCollectionModal.module.css b/src/components/Modals/CreateLinkedWearablesCollectionModal/CreateLinkedWearablesCollectionModal.module.css deleted file mode 100644 index e8883bd5e..000000000 --- a/src/components/Modals/CreateLinkedWearablesCollectionModal/CreateLinkedWearablesCollectionModal.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.linkedContractSelect :global(.divider.text), -.linkedContractSelect :global(.visible.menu.transition) > div { - display: flex !important; - align-items: center !important; -} - -.linkedContractSelect :global(.divider.text) img, -.linkedContractSelect :global(.visible.menu.transition) img { - height: 25px; - width: 25px; -} diff --git a/src/components/Modals/CreateLinkedWearablesCollectionModal/CreateLinkedWearablesCollectionModal.tsx b/src/components/Modals/CreateLinkedWearablesCollectionModal/CreateLinkedWearablesCollectionModal.tsx deleted file mode 100644 index 0d0243d9e..000000000 --- a/src/components/Modals/CreateLinkedWearablesCollectionModal/CreateLinkedWearablesCollectionModal.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import { useState, useMemo, useCallback, FC, SyntheticEvent } from 'react' -import slug from 'slug' -import { Collection, TP_COLLECTION_NAME_MAX_LENGTH } from 'modules/collection/types' -import { - ModalNavigation, - Button, - Form, - Field, - ModalContent, - ModalActions, - SelectField, - InputOnChangeData, - DropdownProps -} from 'decentraland-ui' -import uuid from 'uuid' -import { t } from 'decentraland-dapps/dist/modules/translation/utils' -import Modal from 'decentraland-dapps/dist/containers/Modal' -import { getAnalytics } from 'decentraland-dapps/dist/modules/analytics' -import { LinkedContract, ThirdPartyVersion } from 'modules/thirdParty/types' -import { LinkedContractProtocol, buildThirdPartyURN, buildThirdPartyV2URN, decodeURN } from 'lib/urn' -import { getThirdPartyVersion } from 'modules/thirdParty/utils' -import ethereumSvg from '../../../icons/ethereum.svg' -import polygonSvg from '../../../icons/polygon.svg' -import { Props } from './CreateLinkedWearablesCollectionModal.types' -import styles from './CreateLinkedWearablesCollectionModal.module.css' - -const imgSrcByNetwork = { - [LinkedContractProtocol.MAINNET]: ethereumSvg, - [LinkedContractProtocol.MATIC]: polygonSvg, - [LinkedContractProtocol.SEPOLIA]: ethereumSvg, - [LinkedContractProtocol.AMOY]: polygonSvg -} - -export const CreateLinkedWearablesCollectionModal: FC = (props: Props) => { - const { name, thirdParties, onClose, isCreatingCollection, error, ownerAddress, onSubmit, onBack } = props - const [collectionName, setCollectionName] = useState('') - const [linkedContract, setLinkedContract] = useState() - const [hasCollectionIdBeenTyped, setHasCollectionIdBeenTyped] = useState(false) - const [collectionId, setCollectionId] = useState('') - const [thirdPartyId, setThirdPartyId] = useState(thirdParties[0].id) - const analytics = getAnalytics() - - const selectedThirdParty = useMemo(() => { - return thirdParties.find(thirdParty => thirdParty.id === thirdPartyId) || thirdParties[0] - }, [thirdParties, thirdPartyId]) - const selectedThirdPartyVersion = useMemo( - () => (selectedThirdParty ? getThirdPartyVersion(selectedThirdParty) : undefined), - [selectedThirdParty, getThirdPartyVersion] - ) - const thirdPartyOptions = useMemo(() => thirdParties.map(thirdParty => ({ value: thirdParty.id, text: thirdParty.name })), [thirdParties]) - const linkedContractsOptions = useMemo( - () => - selectedThirdParty?.contracts.map((contract, index) => ({ - value: index, - key: index, - image: imgSrcByNetwork[contract.network], - text: contract.address - })), - [selectedThirdParty, imgSrcByNetwork] - ) - const isCollectionNameInvalid = useMemo(() => collectionName.includes(':'), [collectionName]) - - const handleNameChange = useCallback( - (_: SyntheticEvent, data: InputOnChangeData) => { - setCollectionName(data.value) - setCollectionId(hasCollectionIdBeenTyped ? collectionId : slug(data.value)) - }, - [setCollectionName, hasCollectionIdBeenTyped, collectionId] - ) - const handleThirdPartyChange = useCallback( - (_: React.SyntheticEvent, data: DropdownProps) => { - if (data.value) { - setLinkedContract(undefined) - setThirdPartyId(data.value.toString()) - } - }, - [setThirdPartyId, setLinkedContract] - ) - const handleLinkedContractChange = useCallback( - (_: SyntheticEvent, data: DropdownProps) => { - setLinkedContract(selectedThirdParty.contracts[data.value as number]) - }, - [selectedThirdParty, setLinkedContract] - ) - const handleCollectionIdChange = useCallback( - (_event: React.ChangeEvent, data: InputOnChangeData) => { - setCollectionId(data.value) - setHasCollectionIdBeenTyped(!!data.value) - }, - [setCollectionId, setHasCollectionIdBeenTyped] - ) - - const handleSubmit = useCallback(() => { - if ( - collectionName && - ownerAddress && - ((selectedThirdPartyVersion === ThirdPartyVersion.V2 && linkedContract) || - (selectedThirdPartyVersion === ThirdPartyVersion.V1 && collectionId)) - ) { - const now = Date.now() - const decodedURN = decodeURN(selectedThirdParty.id) - const urn = - selectedThirdPartyVersion === ThirdPartyVersion.V1 - ? buildThirdPartyURN(decodedURN.suffix, collectionId) - : buildThirdPartyV2URN(decodedURN.suffix, linkedContract!.network, linkedContract!.address) - const collection: Collection = { - id: uuid.v4(), - name: collectionName, - owner: ownerAddress, - urn, - isPublished: false, - isApproved: false, - minters: [], - managers: [], - createdAt: now, - updatedAt: now - } - onSubmit(collection) - analytics.track('Create TP Collection', { - collectionId: collection.id, - version: selectedThirdPartyVersion, - thirdPartyId: selectedThirdParty.id, - linkedContract: linkedContract?.address, - linkedContractNetwork: linkedContract?.network, - collectionName - }) - } - }, [onSubmit, collectionId, collectionName, selectedThirdPartyVersion, selectedThirdParty, linkedContract, ownerAddress, analytics]) - - const isSubmittable = - collectionName && - ownerAddress && - !isCollectionNameInvalid && - ((selectedThirdPartyVersion === ThirdPartyVersion.V2 && linkedContract) || - (selectedThirdPartyVersion === ThirdPartyVersion.V1 && collectionId)) && - !isCreatingCollection - const isLoading = isCreatingCollection - - return ( - - -
- - - {selectedThirdPartyVersion === ThirdPartyVersion.V2 && ( - - )} - - {selectedThirdPartyVersion === ThirdPartyVersion.V1 && ( - - )} - {error ? {error} : null} - - - - -
-
- ) -} diff --git a/src/components/Modals/CreateLinkedWearablesCollectionModal/CreateLinkedWearablesCollectionModal.types.ts b/src/components/Modals/CreateLinkedWearablesCollectionModal/CreateLinkedWearablesCollectionModal.types.ts deleted file mode 100644 index 3b808ab38..000000000 --- a/src/components/Modals/CreateLinkedWearablesCollectionModal/CreateLinkedWearablesCollectionModal.types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Dispatch } from 'redux' -import { ModalProps } from 'decentraland-dapps/dist/providers/ModalProvider/ModalProvider.types' -import { OpenModalAction } from 'decentraland-dapps/dist/modules/modal' -import { SaveCollectionRequestAction, saveCollectionRequest } from 'modules/collection/actions' -import { ThirdParty } from 'modules/thirdParty/types' - -export type Props = ModalProps & { - ownerAddress?: string - thirdParties: ThirdParty[] - isCreatingCollection: boolean - error: string | null - onSubmit: typeof saveCollectionRequest - onBack: () => void -} - -export type MapStateProps = Pick -export type MapDispatchProps = Pick -export type OwnProps = Pick -export type MapDispatch = Dispatch diff --git a/src/components/Modals/CreateLinkedWearablesCollectionModal/index.ts b/src/components/Modals/CreateLinkedWearablesCollectionModal/index.ts deleted file mode 100644 index a63c2275d..000000000 --- a/src/components/Modals/CreateLinkedWearablesCollectionModal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './CreateLinkedWearablesCollectionModal.types' -import CreateLinkedWearablesCollectionModal from './CrateLinkedWearablesCollectionModal.container' -export default CreateLinkedWearablesCollectionModal diff --git a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.container.ts b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.container.ts index 36f623ef3..a4099df97 100644 --- a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.container.ts +++ b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.container.ts @@ -4,8 +4,11 @@ import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors import { RootState } from 'modules/common/types' import { getCollection } from 'modules/collection/selectors' import { Collection } from 'modules/collection/types' +import { getIsLinkedWearablesV2Enabled } from 'modules/features/selectors' import { saveItemRequest, SAVE_ITEM_REQUEST } from 'modules/item/actions' import { getLoading, getError, getStatusByItemId } from 'modules/item/selectors' +import { getCollectionThirdParty } from 'modules/thirdParty/selectors' +import { isThirdPartyCollection } from 'modules/collection/utils' import { MapStateProps, MapDispatchProps, MapDispatch, OwnProps } from './CreateSingleItemModal.types' import CreateSingleItemModal from './CreateSingleItemModal' @@ -13,11 +16,14 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => { const collection: Collection | null = ownProps.metadata.collectionId ? getCollection(state, ownProps.metadata.collectionId) : null const statusByItemId = getStatusByItemId(state) const itemStatus = ownProps.metadata.item ? statusByItemId[ownProps.metadata.item.id] : null + const contracts = collection && isThirdPartyCollection(collection) ? getCollectionThirdParty(state, collection)?.contracts ?? [] : [] return { collection, address: getAddress(state), error: getError(state), + isThirdPartyV2Enabled: getIsLinkedWearablesV2Enabled(state), + contracts, itemStatus, isLoading: isLoadingType(getLoading(state), SAVE_ITEM_REQUEST) } diff --git a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.tsx b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.tsx index 7ee85bd2f..19960f0f4 100644 --- a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.tsx +++ b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.tsx @@ -1,5 +1,4 @@ import * as React from 'react' -import uuid from 'uuid' import { ethers } from 'ethers' import { BodyPartCategory, @@ -10,7 +9,10 @@ import { PreviewProjection, WearableCategory, Mapping, - MappingType + MappingType, + Mappings, + ContractNetwork, + ContractAddress } from '@dcl/schemas' import { MAX_EMOTE_FILE_SIZE, @@ -70,15 +72,12 @@ import { import { EngineType, getItemData, getModelData } from 'lib/getModelData' import { getExtension, toMB } from 'lib/file' import { - getDefaultThirdPartyItemUrnSuffix, + getDefaultThirdPartyUrnSuffix, buildThirdPartyURN, - buildThirdPartyV2URN, DecodedURN, decodeURN, isThirdParty, - isThirdPartyCollectionDecodedUrn, - isThirdPartyV2CollectionDecodedUrn, - URNType + isThirdPartyCollectionDecodedUrn } from 'lib/urn' import ItemDropdown from 'components/ItemDropdown' import Icon from 'components/Icon' @@ -106,6 +105,9 @@ import { ITEM_LOADED_CHECK_DELAY } from './CreateSingleItemModal.types' import './CreateSingleItemModal.css' +import { LinkedContract } from 'modules/thirdParty/types' + +const defaultMapping: Mapping = { type: MappingType.ANY } export default class CreateSingleItemModal extends React.PureComponent { state: State = this.getInitialState() thumbnailInput = React.createRef() @@ -113,8 +115,23 @@ export default class CreateSingleItemModal extends React.PureComponent() timer: ReturnType | undefined + getDefaultMappings( + contracts: LinkedContract[], + isThirdPartyV2Enabled: boolean + ): Partial>> | undefined { + if (contracts.length === 0 || !isThirdPartyV2Enabled) { + return undefined + } + + return { + [contracts[0].network]: { + [contracts[0].address]: [defaultMapping] + } + } + } + getInitialState() { - const { metadata } = this.props + const { metadata, contracts, isThirdPartyV2Enabled } = this.props const state: State = { view: CreateItemView.IMPORT, @@ -141,7 +158,7 @@ export default class CreateSingleItemModal extends React.PureComponent 0 ? item.mappings[0] : { type: MappingType.ANY } + state.mappings = item.mappings ?? this.getDefaultMappings(contracts, isThirdPartyV2Enabled) if (addRepresentation) { const missingBodyShape = getMissingBodyShapeType(item) @@ -150,6 +167,8 @@ export default class CreateSingleItemModal extends React.PureComponent | null = collection?.urn ? decodeURN(collection.urn) : null let urn: string | undefined if (decodedCollectionUrn && isThirdPartyCollectionDecodedUrn(decodedCollectionUrn)) { - urn = buildThirdPartyURN(decodedCollectionUrn.thirdPartyName, decodedCollectionUrn.thirdPartyCollectionId, uuid.v4()) - } else if (decodedCollectionUrn && isThirdPartyV2CollectionDecodedUrn(decodedCollectionUrn)) { - urn = buildThirdPartyV2URN( - decodedCollectionUrn.thirdPartyLinkedCollectionName, - decodedCollectionUrn.linkedCollectionNetwork, - decodedCollectionUrn.linkedCollectionContractAddress, - getDefaultThirdPartyItemUrnSuffix(name) + urn = buildThirdPartyURN( + decodedCollectionUrn.thirdPartyName, + decodedCollectionUrn.thirdPartyCollectionId, + getDefaultThirdPartyUrnSuffix(name) ) } @@ -326,7 +342,7 @@ export default class CreateSingleItemModal extends React.PureComponent { - switch (mapping.type) { - case MappingType.SINGLE: - return !!mapping.id - case MappingType.MULTIPLE: - return mapping.ids.length > 0 - case MappingType.RANGE: - return !!mapping.from && !!mapping.to && BigInt(mapping.from) <= BigInt(mapping.to) + areMappingsValid = (mappings: Mappings): boolean => { + try { + const validate = Mappings.validate(mappings) + return !!validate + } catch (error) { + return false } } - handleMappingChange = (mapping: Mapping) => { - this.setState({ mapping }) + getContract = (): LinkedContract | undefined => { + const { contracts } = this.props + const { mappings } = this.state + // If no mappings are selected, return the first contract + if (!mappings) { + return contracts[0] + } + + // Get the first contract from the mappings data or the first contract from the contracts list + const firstNetwork = Object.keys(mappings)[0] as ContractNetwork + const contractsInNetwork = firstNetwork ? mappings[firstNetwork] : undefined + const firstContractAddress = contractsInNetwork ? Object.keys(contractsInNetwork)[0] : undefined + return firstContractAddress ? contracts.find(contract => contract.address === firstContractAddress) : contracts[0] + } + + getMapping = (): Mapping => { + const { contracts, isThirdPartyV2Enabled } = this.props + const { mappings } = this.state + const contract = this.getContract() + if (!contract) { + return defaultMapping + } + + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + let mapping: Mapping | undefined + if (mappings) { + mapping = mappings[contract.network]?.[contract.address][0] + } else { + mapping = this.getDefaultMappings(contracts, isThirdPartyV2Enabled)?.[contract.network]?.[contract.address][0] + } + + return mapping ?? defaultMapping + } + + handleMappingChange = (mapping: Mapping, contract: LinkedContract) => { + const mappings: Mappings = { + [contract.network]: { + [contract.address]: [mapping] + } + } + + this.setState({ mappings }) } handleOpenDocs = () => window.open('https://docs.decentraland.org/3d-modeling/3d-models/', '_blank') @@ -877,14 +931,13 @@ export default class CreateSingleItemModal extends React.PureComponent ({ value, text: t(`${type!}.category.${value}`) }))} onChange={this.handleCategoryChange} /> - {belongsToAThirdPartyV2Collection ? ( - - ) : null} + {isThirdPartyV2Enabled && contract && ( + + )} ) } @@ -972,10 +1025,10 @@ export default class CreateSingleItemModal extends React.PureComponent prop !== undefined) - if ((belongsToAThirdPartyV2Collection && !mapping) || (belongsToAThirdPartyV2Collection && mapping && !this.isMappingValid(mapping))) { + if (isThirdPartyV2Enabled && ((!mappings && contracts.length > 0) || (mappings && !this.areMappingsValid(mappings)))) { return false } diff --git a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.types.ts b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.types.ts index 9e0239f88..ffa323880 100644 --- a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.types.ts +++ b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.types.ts @@ -1,10 +1,11 @@ import { Dispatch } from 'redux' import { ModalProps } from 'decentraland-dapps/dist/providers/ModalProvider/ModalProvider.types' -import { IPreviewController, Mapping, Rarity } from '@dcl/schemas' +import { IPreviewController, Mappings, Rarity } from '@dcl/schemas' import { Metrics } from 'modules/models/types' import { Collection } from 'modules/collection/types' import { saveItemRequest, SaveItemRequestAction } from 'modules/item/actions' import { BodyShapeType, Item, ItemType, SyncStatus } from 'modules/item/types' +import { LinkedContract } from 'modules/thirdParty/types' export enum CreateItemView { IMPORT = 'import', @@ -20,7 +21,9 @@ export type Props = ModalProps & { address?: string metadata: CreateSingleItemModalMetadata error: string | null + isThirdPartyV2Enabled: boolean isLoading: boolean + contracts: LinkedContract[] collection: Collection | null itemStatus: SyncStatus | null onSave: typeof saveItemRequest @@ -52,7 +55,7 @@ export type StateData = { requiredPermissions?: string[] tags?: string[] modelSize?: number - mapping: Mapping + mappings: Mappings blockVrmExport?: boolean } export type State = { @@ -103,6 +106,9 @@ export type AcceptedFileProps = Pick< | 'blockVrmExport' > export type OwnProps = Pick -export type MapStateProps = Pick +export type MapStateProps = Pick< + Props, + 'address' | 'error' | 'isLoading' | 'collection' | 'itemStatus' | 'isThirdPartyV2Enabled' | 'contracts' +> export type MapDispatchProps = Pick export type MapDispatch = Dispatch diff --git a/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.container.ts b/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.container.ts index 59d5c32a7..1e4254319 100644 --- a/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.container.ts +++ b/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.container.ts @@ -3,19 +3,26 @@ import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors' import { RootState } from 'modules/common/types' import { getLoading } from 'modules/collection/selectors' +import { openModal } from 'decentraland-dapps/dist/modules/modal' import { SAVE_COLLECTION_REQUEST, saveCollectionRequest } from 'modules/collection/actions' import { getWalletThirdParties, getError } from 'modules/thirdParty/selectors' -import { MapStateProps, MapDispatchProps, MapDispatch } from './CreateThirdPartyCollectionModal.types' -import CreateThirdPartyCollectionModal from './CreateThirdPartyCollectionModal' +import { getIsLinkedWearablesV2Enabled } from 'modules/features/selectors' +import { MapStateProps, MapDispatchProps, MapDispatch, OwnProps } from './CreateThirdPartyCollectionModal.types' +import { CreateThirdPartyCollectionModal } from './CreateThirdPartyCollectionModal' const mapState = (state: RootState): MapStateProps => ({ - address: getAddress(state), + ownerAddress: getAddress(state), thirdParties: getWalletThirdParties(state), error: getError(state), - isLoading: isLoadingType(getLoading(state), SAVE_COLLECTION_REQUEST) + isThirdPartyV2Enabled: getIsLinkedWearablesV2Enabled(state), + isCreatingCollection: isLoadingType(getLoading(state), SAVE_COLLECTION_REQUEST) }) -const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({ +const mapDispatch = (dispatch: MapDispatch, ownProps: OwnProps): MapDispatchProps => ({ + onBack: () => { + ownProps.onClose() + dispatch(openModal('CreateCollectionSelectorModal')) + }, onSubmit: collection => dispatch(saveCollectionRequest(collection)) }) diff --git a/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.tsx b/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.tsx index 14ae9f3cc..331203271 100644 --- a/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.tsx +++ b/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.tsx @@ -1,6 +1,5 @@ -import * as React from 'react' -import uuid from 'uuid' -import slug from 'slug' +import { useState, useMemo, useCallback, FC, SyntheticEvent } from 'react' +import { Collection, TP_COLLECTION_NAME_MAX_LENGTH } from 'modules/collection/types' import { ModalNavigation, Button, @@ -8,40 +7,64 @@ import { Field, ModalContent, ModalActions, - InputOnChangeData, SelectField, + InputOnChangeData, DropdownProps } from 'decentraland-ui' -import { getAnalytics } from 'decentraland-dapps/dist/modules/analytics/utils' +import uuid from 'uuid' import { t } from 'decentraland-dapps/dist/modules/translation/utils' import Modal from 'decentraland-dapps/dist/containers/Modal' -import { buildThirdPartyURN, decodeURN } from 'lib/urn' -import { Collection, TP_COLLECTION_NAME_MAX_LENGTH } from 'modules/collection/types' -import { Props, State } from './CreateThirdPartyCollectionModal.types' +import { getAnalytics } from 'decentraland-dapps/dist/modules/analytics' +import { buildThirdPartyURN, decodeURN, getDefaultThirdPartyUrnSuffix } from 'lib/urn' +import { Props } from './CreateThirdPartyCollectionModal.types' -export default class CreateThirdPartyCollectionModal extends React.PureComponent { - analytics = getAnalytics() - state: State = { - thirdPartyId: '', - collectionName: '', - urnSuffix: '', - isTypedUrnSuffix: false - } +export const CreateThirdPartyCollectionModal: FC = (props: Props) => { + const { name, thirdParties, onClose, isCreatingCollection, isThirdPartyV2Enabled, error, ownerAddress, onSubmit, onBack } = props + const [collectionName, setCollectionName] = useState('') + const [hasCollectionIdBeenTyped, setHasCollectionIdBeenTyped] = useState(false) + const [collectionId, setCollectionId] = useState('') + const [thirdPartyId, setThirdPartyId] = useState(thirdParties[0].id) + const analytics = getAnalytics() - handleSubmit = () => { - const { address, onSubmit } = this.props - const { collectionName, urnSuffix } = this.state + const selectedThirdParty = useMemo(() => { + return thirdParties.find(thirdParty => thirdParty.id === thirdPartyId) || thirdParties[0] + }, [thirdParties, thirdPartyId]) + const thirdPartyOptions = useMemo(() => thirdParties.map(thirdParty => ({ value: thirdParty.id, text: thirdParty.name })), [thirdParties]) + const isCollectionNameInvalid = useMemo(() => collectionName.includes(':'), [collectionName]) - if (collectionName && urnSuffix) { - const now = Date.now() - const thirdParty = this.getSelectedThirdParty() - const decodedURN = decodeURN(thirdParty.id) + const handleNameChange = useCallback( + (_: SyntheticEvent, data: InputOnChangeData) => { + setCollectionName(data.value) + setCollectionId(hasCollectionIdBeenTyped ? collectionId : getDefaultThirdPartyUrnSuffix(data.value)) + }, + [setCollectionName, hasCollectionIdBeenTyped, collectionId] + ) + const handleThirdPartyChange = useCallback( + (_: React.SyntheticEvent, data: DropdownProps) => { + if (data.value) { + setThirdPartyId(data.value.toString()) + } + }, + [setThirdPartyId] + ) + const handleCollectionIdChange = useCallback( + (_event: React.ChangeEvent, data: InputOnChangeData) => { + setCollectionId(data.value) + setHasCollectionIdBeenTyped(!!data.value) + }, + [setCollectionId, setHasCollectionIdBeenTyped] + ) + const handleSubmit = useCallback(() => { + if (collectionName && ownerAddress && collectionId) { + const now = Date.now() + const decodedURN = decodeURN(selectedThirdParty.id) + const urn = buildThirdPartyURN(decodedURN.suffix, collectionId) const collection: Collection = { id: uuid.v4(), name: collectionName, - owner: address!, - urn: buildThirdPartyURN(decodedURN.suffix, urnSuffix), + owner: ownerAddress, + urn, isPublished: false, isApproved: false, minters: [], @@ -50,81 +73,62 @@ export default class CreateThirdPartyCollectionModal extends React.PureComponent updatedAt: now } onSubmit(collection) - this.analytics.track('Create TP Collection', { collectionId: collection.id }) - } - } - - handleIdChange = (_: React.SyntheticEvent, data: DropdownProps) => { - if (data.value) { - this.setState({ thirdPartyId: data.value.toString() }) + analytics.track('Create TP Collection', { + collectionId: collection.id, + thirdPartyId: selectedThirdParty.id, + collectionName + }) } - } - - handleNameChange = (_event: React.ChangeEvent, data: InputOnChangeData) => { - const { urnSuffix, isTypedUrnSuffix } = this.state - const collectionName = data.value - const newUrnSuffix = isTypedUrnSuffix ? urnSuffix : slug(collectionName) - this.setState({ collectionName, urnSuffix: newUrnSuffix }) - } - - handleUrnSuffixChange = (_event: React.ChangeEvent, data: InputOnChangeData) => { - this.setState({ urnSuffix: slug(data.value), isTypedUrnSuffix: !!data.value }) - } - - getSelectedThirdParty() { - const { thirdParties } = this.props - const { thirdPartyId } = this.state - return thirdParties.find(thirdParty => thirdParty.id === thirdPartyId) || thirdParties[0] - } - - render() { - const { name, thirdParties, onClose, isLoading, error } = this.props - const { collectionName, urnSuffix } = this.state - const isDisabled = !collectionName || isLoading + }, [onSubmit, collectionId, collectionName, selectedThirdParty, ownerAddress, analytics]) - // TODO: Check for repeated urnSuffix error (needs server update) + const isSubmittable = collectionName && ownerAddress && !isCollectionNameInvalid && collectionId + !isCreatingCollection + const isLoading = isCreatingCollection - const selectedThirdParty = this.getSelectedThirdParty() - - return ( - - -
- - ({ value: thirdParty.id, text: thirdParty.name }))} - onChange={this.handleIdChange} - value={selectedThirdParty.id} - /> - + return ( + + + + + + + {!isThirdPartyV2Enabled && ( - {error ? {error} : null} - - - - - - - ) - } + )} + {error ? {error} : null} +
+ + + + +
+ ) } diff --git a/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.types.ts b/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.types.ts index 3877159d3..ee6013e7b 100644 --- a/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.types.ts +++ b/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.types.ts @@ -1,23 +1,20 @@ import { Dispatch } from 'redux' import { ModalProps } from 'decentraland-dapps/dist/providers/ModalProvider/ModalProvider.types' +import { OpenModalAction } from 'decentraland-dapps/dist/modules/modal' import { SaveCollectionRequestAction, saveCollectionRequest } from 'modules/collection/actions' import { ThirdParty } from 'modules/thirdParty/types' export type Props = ModalProps & { - address?: string + ownerAddress?: string thirdParties: ThirdParty[] - isLoading: boolean + isCreatingCollection: boolean + isThirdPartyV2Enabled: boolean error: string | null onSubmit: typeof saveCollectionRequest + onBack: () => void } -export type State = { - thirdPartyId: string - collectionName: string - urnSuffix: string - isTypedUrnSuffix: boolean -} - -export type MapStateProps = Pick -export type MapDispatchProps = Pick -export type MapDispatch = Dispatch +export type MapStateProps = Pick +export type MapDispatchProps = Pick +export type OwnProps = Pick +export type MapDispatch = Dispatch diff --git a/src/components/Modals/CreateThirdPartyCollectionModal/index.ts b/src/components/Modals/CreateThirdPartyCollectionModal/index.ts index 4e2cb724c..f0eaa109c 100644 --- a/src/components/Modals/CreateThirdPartyCollectionModal/index.ts +++ b/src/components/Modals/CreateThirdPartyCollectionModal/index.ts @@ -1,2 +1,3 @@ +export * from './CreateThirdPartyCollectionModal.types' import CreateThirdPartyCollectionModal from './CreateThirdPartyCollectionModal.container' export default CreateThirdPartyCollectionModal diff --git a/src/components/Modals/EditURNModals/EditCollectionURNModal/EditCollectionURNModal.container.ts b/src/components/Modals/EditURNModals/EditCollectionURNModal/EditCollectionURNModal.container.ts index a603740fc..a4b023ad9 100644 --- a/src/components/Modals/EditURNModals/EditCollectionURNModal/EditCollectionURNModal.container.ts +++ b/src/components/Modals/EditURNModals/EditCollectionURNModal/EditCollectionURNModal.container.ts @@ -1,7 +1,7 @@ import { connect } from 'react-redux' import { RootState } from 'modules/common/types' import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors' -import { buildThirdPartyURN, DecodedURN, isThirdPartyCollectionDecodedUrn, URNType } from 'lib/urn' +import { buildThirdPartyURN, DecodedURN, URNType } from 'lib/urn' import { getLoading as getCollectionLoading, getError } from 'modules/collection/selectors' import { saveCollectionRequest, SAVE_COLLECTION_REQUEST } from 'modules/collection/actions' import { MapStateProps, MapDispatchProps, MapDispatch, OwnProps } from './EditCollectionURNModal.types' @@ -19,10 +19,8 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => { const mapDispatch = (dispatch: MapDispatch, ownProps: OwnProps): MapDispatchProps => ({ onSave: urn => dispatch(saveCollectionRequest({ ...ownProps.metadata.collection, urn })), - onBuildURN: ( - decodedURN: DecodedURN | DecodedURN, - collectionId: string - ) => (isThirdPartyCollectionDecodedUrn(decodedURN) ? buildThirdPartyURN(decodedURN.thirdPartyName, collectionId) : '') + onBuildURN: (decodedURN: DecodedURN, collectionId: string) => + buildThirdPartyURN(decodedURN.thirdPartyName, collectionId) }) export default connect(mapState, mapDispatch)(EditURNModal) diff --git a/src/components/Modals/EditURNModals/EditCollectionURNModal/EditCollectionURNModal.types.ts b/src/components/Modals/EditURNModals/EditCollectionURNModal/EditCollectionURNModal.types.ts index 21fa55e51..a460d3996 100644 --- a/src/components/Modals/EditURNModals/EditCollectionURNModal/EditCollectionURNModal.types.ts +++ b/src/components/Modals/EditURNModals/EditCollectionURNModal/EditCollectionURNModal.types.ts @@ -11,10 +11,7 @@ export type Props = ModalProps & { metadata: EditURNModalMetadata error: string | null onSave: (urn: string) => ReturnType - onBuildURN: ( - decodedURN: DecodedURN | DecodedURN, - collectionId: string - ) => string + onBuildURN: (decodedURN: DecodedURN, collectionId: string) => string } export type EditURNModalMetadata = { diff --git a/src/components/Modals/EditURNModals/EditItemURNModal/EditItemURNModal.container.ts b/src/components/Modals/EditURNModals/EditItemURNModal/EditItemURNModal.container.ts index 0a13d99fc..53c9e6409 100644 --- a/src/components/Modals/EditURNModals/EditItemURNModal/EditItemURNModal.container.ts +++ b/src/components/Modals/EditURNModals/EditItemURNModal/EditItemURNModal.container.ts @@ -1,7 +1,7 @@ import { connect } from 'react-redux' import { RootState } from 'modules/common/types' import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors' -import { buildThirdPartyURN, buildThirdPartyV2URN, DecodedURN, URNType } from 'lib/urn' +import { buildThirdPartyURN, DecodedURN, URNType } from 'lib/urn' import { getError, getLoading as getItemLoading } from 'modules/item/selectors' import { saveItemRequest, SAVE_ITEM_REQUEST } from 'modules/item/actions' import { MapStateProps, MapDispatchProps, MapDispatch, OwnProps } from './EditItemURNModal.types' @@ -19,15 +19,8 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => { const mapDispatch = (dispatch: MapDispatch, ownProps: OwnProps): MapDispatchProps => ({ onSave: (urn: string) => dispatch(saveItemRequest({ ...ownProps.metadata.item, urn }, {})), - onBuildURN: (decodedURN: DecodedURN | DecodedURN, tokenId: string) => - decodedURN.type === URNType.COLLECTIONS_THIRDPARTY - ? buildThirdPartyURN(decodedURN.thirdPartyName, decodedURN.thirdPartyCollectionId!, tokenId) - : buildThirdPartyV2URN( - decodedURN.thirdPartyLinkedCollectionName, - decodedURN.linkedCollectionNetwork, - decodedURN.linkedCollectionContractAddress, - tokenId - ) + onBuildURN: (decodedURN: DecodedURN, tokenId: string) => + buildThirdPartyURN(decodedURN.thirdPartyName, decodedURN.thirdPartyCollectionId!, tokenId) }) export default connect(mapState, mapDispatch)(EditURNModal) diff --git a/src/components/Modals/EditURNModals/EditItemURNModal/EditItemURNModal.types.ts b/src/components/Modals/EditURNModals/EditItemURNModal/EditItemURNModal.types.ts index f2b4f8976..578eb24f9 100644 --- a/src/components/Modals/EditURNModals/EditItemURNModal/EditItemURNModal.types.ts +++ b/src/components/Modals/EditURNModals/EditItemURNModal/EditItemURNModal.types.ts @@ -11,10 +11,7 @@ export type Props = ModalProps & { metadata: EditURNModalMetadata error: string | null onSave: (urn: string) => ReturnType - onBuildURN: ( - decodedURN: DecodedURN | DecodedURN, - tokenId: string - ) => string + onBuildURN: (decodedURN: DecodedURN, tokenId: string) => string } export type EditURNModalMetadata = { diff --git a/src/components/Modals/EditURNModals/EditURNModal/EditURNModal.tsx b/src/components/Modals/EditURNModals/EditURNModal/EditURNModal.tsx index c20fbc163..a276f2fb2 100644 --- a/src/components/Modals/EditURNModals/EditURNModal/EditURNModal.tsx +++ b/src/components/Modals/EditURNModals/EditURNModal/EditURNModal.tsx @@ -7,7 +7,7 @@ import { DecodedURN, decodeURN, isThirdParty, URNType } from 'lib/urn' import { Props, State } from './EditURNModal.types' export default class EditURNModal extends React.PureComponent { - decodedURN: DecodedURN | DecodedURN = this.decodeURN() + decodedURN: DecodedURN = this.decodeURN() analytics = getAnalytics() state: State = { @@ -22,11 +22,7 @@ export default class EditURNModal extends React.PureComponent { const { onSave, urn: oldURN } = this.props const urn = this.getUpdatedURN() if (isThirdParty(urn)) { - const metric = - (this.decodedURN.type === URNType.COLLECTIONS_THIRDPARTY && this.decodedURN.thirdPartyCollectionId) || - this.decodedURN.type === URNType.COLLECTIONS_THIRDPARTY_V2 - ? 'Change TP Item URN' - : 'Change TP Collection URN' + const metric = this.decodedURN.thirdPartyCollectionId ? 'Change TP Item URN' : 'Change TP Collection URN' this.analytics.track(metric, { oldURN, newURN: urn }) } @@ -47,7 +43,7 @@ export default class EditURNModal extends React.PureComponent { decodeURN() { const { urn } = this.props const decodedURN = decodeURN(urn) - if (decodedURN.type !== URNType.COLLECTIONS_THIRDPARTY && decodedURN.type !== URNType.COLLECTIONS_THIRDPARTY_V2) { + if (decodedURN.type !== URNType.COLLECTIONS_THIRDPARTY) { throw new Error(`Invalid URN type ${this.decodedURN.type}`) } return decodedURN diff --git a/src/components/Modals/EditURNModals/EditURNModal/EditURNModal.types.ts b/src/components/Modals/EditURNModals/EditURNModal/EditURNModal.types.ts index ebff9b8f7..4f9cce736 100644 --- a/src/components/Modals/EditURNModals/EditURNModal/EditURNModal.types.ts +++ b/src/components/Modals/EditURNModals/EditURNModal/EditURNModal.types.ts @@ -10,9 +10,6 @@ export type Props = ModalProps & { urn: URN isLoading: boolean error: string | null - onBuildURN: ( - decodedURN: DecodedURN | DecodedURN, - newURNSection: string - ) => string + onBuildURN: (decodedURN: DecodedURN, newURNSection: string) => string onSave: (newURN: string) => void } diff --git a/src/components/Modals/index.ts b/src/components/Modals/index.ts index e6560e87c..a01d47ebf 100644 --- a/src/components/Modals/index.ts +++ b/src/components/Modals/index.ts @@ -51,5 +51,4 @@ export { default as WorldsForENSOwnersAnnouncementModal } from './WorldsForENSOw export { default as EnsMapAddressModal } from './ENSMapAddressModal' export { default as ReclaimNameModal } from './ReclaimNameModal' export { default as WorldPermissionsModal } from './WorldPermissionsModal' -export { default as CreateLinkedWearablesCollectionModal } from './CreateLinkedWearablesCollectionModal' export { CreateCollectionSelectorModal } from './CreateCollectionSelectorModal' diff --git a/src/components/ThirdPartyCollectionDetailPage/CollectionItem/CollectionItem.tsx b/src/components/ThirdPartyCollectionDetailPage/CollectionItem/CollectionItem.tsx index ef530bb58..4623e2eab 100644 --- a/src/components/ThirdPartyCollectionDetailPage/CollectionItem/CollectionItem.tsx +++ b/src/components/ThirdPartyCollectionDetailPage/CollectionItem/CollectionItem.tsx @@ -4,7 +4,7 @@ import { Grid, Dropdown, Icon, Button, Checkbox, CheckboxProps, Popup } from 'de import { Link, useHistory } from 'react-router-dom' import { locations } from 'routing/locations' import { preventDefault } from 'lib/event' -import { decodeURN, isThirdPartyCollectionDecodedUrn, isThirdPartyV2CollectionDecodedUrn } from 'lib/urn' +import { decodeURN, isThirdPartyCollectionDecodedUrn } from 'lib/urn' import ItemStatus from 'components/ItemStatus' import { SyncStatus } from 'modules/item/types' import { FromParam } from 'modules/location/types' @@ -49,9 +49,7 @@ export default function CollectionItem({ item, status, selected, onSelect, onOpe try { const decodedURN = decodeURN(item.urn) - return isThirdPartyCollectionDecodedUrn(decodedURN) || isThirdPartyV2CollectionDecodedUrn(decodedURN) - ? decodedURN.thirdPartyTokenId ?? '' - : '' + return isThirdPartyCollectionDecodedUrn(decodedURN) ? decodedURN.thirdPartyTokenId ?? '' : '' } catch (error) { return '' } diff --git a/src/lib/address.ts b/src/lib/address.ts index 276383294..aa7f541ce 100644 --- a/src/lib/address.ts +++ b/src/lib/address.ts @@ -14,6 +14,6 @@ export const isValid = (addr: string) => { return /^0x[a-fA-F0-9]{40}$/g.test(addr) } -export function shorten(address: string) { - return address ? address.slice(0, 6) + '...' + address.slice(42 - 5) : '' +export function shorten(address: string, leftChars: number = 6, rightChars: number = 5) { + return address ? address.slice(0, leftChars) + '...' + address.slice(42 - rightChars) : '' } diff --git a/src/lib/api/builder.ts b/src/lib/api/builder.ts index 5baa34821..d780f5268 100644 --- a/src/lib/api/builder.ts +++ b/src/lib/api/builder.ts @@ -1,5 +1,5 @@ import { AxiosRequestConfig, AxiosError } from 'axios' -import { Entity, Mapping, Rarity } from '@dcl/schemas' +import { ContractAddress, ContractNetwork, Entity, Mapping, Rarity } from '@dcl/schemas' import { BaseAPI, APIParam, RetryParams } from 'decentraland-dapps/dist/lib/api' import { Omit } from 'decentraland-dapps/dist/lib/types' import { config } from 'config' @@ -76,7 +76,7 @@ export type RemoteItem = { created_at: Date updated_at: Date utility: string | null - mappings: Mapping[] | null + mappings: Partial>> | null local_content_hash: string | null catalyst_content_hash: string | null } diff --git a/src/lib/urn.spec.ts b/src/lib/urn.spec.ts index 09c086579..6f64a0dea 100644 --- a/src/lib/urn.spec.ts +++ b/src/lib/urn.spec.ts @@ -12,13 +12,10 @@ import { extractEntityId, extractCollectionAddress, extractTokenId, - buildThirdPartyV2URN, - LinkedContractProtocol, DecodedURN, - isThirdPartyV2CollectionDecodedUrn, isThirdPartyCollectionDecodedUrn, decodedCollectionsUrnAreEqual, - getDefaultThirdPartyItemUrnSuffix + getDefaultThirdPartyUrnSuffix } from './urn' jest.mock('decentraland-dapps/dist/lib/eth') @@ -78,42 +75,6 @@ describe('when building the third party URN', () => { }) }) -describe('when building the third party v2 URN', () => { - const thirdPartyName = 'some-tp-name' - const linkedNetwork = LinkedContractProtocol.AMOY - const linkedAddress = '0x123123' - - beforeEach(() => { - ;(getChainIdByNetwork as jest.Mock).mockReturnValueOnce(ChainId.MATIC_MAINNET) - }) - - it('should return a valid third party collection urn', () => { - expect(buildThirdPartyV2URN(thirdPartyName, linkedNetwork, linkedAddress)).toBe( - `urn:decentraland:matic:collections-linked-wearables:${thirdPartyName}:${linkedNetwork}:${linkedAddress}` - ) - }) - - it('should get the chain id for the matic network', () => { - buildThirdPartyV2URN(thirdPartyName, linkedNetwork, linkedAddress) - expect(getChainIdByNetwork).toHaveBeenCalledWith(Network.MATIC) - }) - - describe('when supplying a token id', () => { - const tokenId = 'a-wonderful-token-id' - - it('should return a valid third party item urn', () => { - expect(buildThirdPartyV2URN(thirdPartyName, linkedNetwork, linkedAddress, tokenId)).toBe( - `urn:decentraland:matic:collections-linked-wearables:${thirdPartyName}:${linkedNetwork}:${linkedAddress}:${tokenId}` - ) - }) - - it('should get the chain id for the matic network', () => { - buildThirdPartyV2URN(thirdPartyName, linkedNetwork, linkedAddress, tokenId) - expect(getChainIdByNetwork).toHaveBeenCalledWith(Network.MATIC) - }) - }) -}) - describe('when decoding an URN', () => { describe('when the urn is empty', () => { it('should return false', () => { @@ -163,7 +124,7 @@ describe('when decoding an URN', () => { }) }) - describe('when a valid third party v1 URN is used', () => { + describe('when a valid third party', () => { const thirdPartyRecordURN = 'urn:decentraland:matic:collections-thirdparty:crypto-motors' describe('when third party record urn is used', () => { @@ -206,52 +167,6 @@ describe('when decoding an URN', () => { }) }) - describe('when a valid third party v2 URN is used', () => { - const thirdPartyRecordURN = 'urn:decentraland:matic:collections-linked-wearables:crypto-motors' - - describe('when third party record urn is used', () => { - it('should decode and return each group', () => { - expect(decodeURN(thirdPartyRecordURN)).toEqual({ - type: URNType.COLLECTIONS_THIRDPARTY_V2, - protocol: URNProtocol.MATIC, - suffix: 'crypto-motors', - thirdPartyLinkedCollectionName: 'crypto-motors', - linkedCollectionNetwork: undefined, - linkedCollectionContractAddress: undefined, - thirdPartyTokenId: undefined - }) - }) - }) - - describe('when third party collection urn is used', () => { - it('should decode and return each group', () => { - expect(decodeURN(thirdPartyRecordURN + ':amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c')).toEqual({ - type: URNType.COLLECTIONS_THIRDPARTY_V2, - protocol: URNProtocol.MATIC, - suffix: 'crypto-motors:amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c', - thirdPartyLinkedCollectionName: 'crypto-motors', - linkedCollectionNetwork: 'amoy', - linkedCollectionContractAddress: '0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c', - thirdPartyTokenId: undefined - }) - }) - }) - - describe('when a third party item urn is used', () => { - it('should decode and return each group', () => { - expect(decodeURN(thirdPartyRecordURN + ':amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c:better-token-id')).toEqual({ - type: URNType.COLLECTIONS_THIRDPARTY_V2, - protocol: URNProtocol.MATIC, - suffix: 'crypto-motors:amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c:better-token-id', - thirdPartyLinkedCollectionName: 'crypto-motors', - linkedCollectionNetwork: 'amoy', - linkedCollectionContractAddress: '0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c', - thirdPartyTokenId: 'better-token-id' - }) - }) - }) - }) - describe('when a valid entity urn is used', () => { describe('and the URN is an entity with a baseUrl', () => { it('should decode and return each group', () => { @@ -276,7 +191,7 @@ describe('when decoding an URN', () => { }) }) -describe('when extracting the third party item token id from a third party v1 URN', () => { +describe('when extracting the third party item token id from an URN', () => { describe('when the URN is not a valid third party URN', () => { it("should throw an error signaling that the URN doesn't belong to a third party", () => { expect(() => @@ -287,7 +202,7 @@ describe('when extracting the third party item token id from a third party v1 UR }) }) - describe('when the URN is a valid third party v1 URN', () => { + describe('when the URN is a valid third party URN', () => { it('should extract the collection and token ids', () => { expect(extractThirdPartyTokenId('urn:decentraland:mumbai:collections-thirdparty:thirdparty2:collection-id:token-id')).toBe( 'collection-id:token-id' @@ -296,28 +211,6 @@ describe('when extracting the third party item token id from a third party v1 UR }) }) -describe('when extracting the third party item token id from a third party v2 URN', () => { - describe('when the URN is not a valid third party URN', () => { - it("should throw an error signaling that the URN doesn't belong to a third party", () => { - expect(() => - extractThirdPartyTokenId('urn:decentraland:matic:collections-v2:0xc6d2000a7a1ddca92941f4e2b41360fe4ee2abd8') - ).toThrowError( - 'Tried to build a third party token for a non third party URN "urn:decentraland:matic:collections-v2:0xc6d2000a7a1ddca92941f4e2b41360fe4ee2abd8"' - ) - }) - }) - - describe('when the URN is a valid third party v2 URN', () => { - it('should extract the collection and token ids', () => { - expect( - extractThirdPartyTokenId( - 'urn:decentraland:matic:collections-linked-wearables:crypto-motors:amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c:better-token-id' - ) - ).toBe('amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c:better-token-id') - }) - }) -}) - describe('when checking if a collection is a third party', () => { let urn: string @@ -444,140 +337,43 @@ describe('when checking if a decoded URN belongs to a third party one', () => { }) }) -describe('when checking if a decoded URN belongs to a third party v2 one', () => { - describe('and the decoded URN does not belong to a third party v2', () => { - let decodedUrn: DecodedURN - - beforeEach(() => { - decodedUrn = decodeURN('urn:decentraland:goerli:collections-v2:0xc6d2000a7a1ddca92941f4e2b41360fe4ee2abd8') - }) - - it('should return false', () => { - expect(isThirdPartyV2CollectionDecodedUrn(decodedUrn)).toBe(false) - }) - }) - - describe('and the decoded URN belongs to a third party v2', () => { - let decodedUrn: DecodedURN - - beforeEach(() => { - decodedUrn = decodeURN( - 'urn:decentraland:matic:collections-linked-wearables:crypto-motors:amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c:better-token-id' - ) - }) - - it('should return true', () => { - expect(isThirdPartyV2CollectionDecodedUrn(decodedUrn)).toBe(true) - }) - }) -}) - describe('when checking if two decoded collection URNs are equal', () => { let fistDecodedUrn: DecodedURN let secondDecodedUrn: DecodedURN - describe('and the URNs are of collections v2', () => { - describe('and the URNs are different', () => { - beforeEach(() => { - fistDecodedUrn = decodeURN('urn:decentraland:amoy:collections-v2:0xc6d2000a7a1ddca92941f4e2b41360fe4ee2abd8') - secondDecodedUrn = decodeURN('urn:decentraland:amoy:collections-v2:0x16a2040b2b1eeca12344f4e2b11260ae2ee2edc2') - }) - - it('should return false', () => { - expect(decodedCollectionsUrnAreEqual(fistDecodedUrn, secondDecodedUrn)).toBe(false) - }) - }) - - describe('and the URNs are equal', () => { - beforeEach(() => { - fistDecodedUrn = decodeURN('urn:decentraland:amoy:collections-v2:0xc6d2000a7a1ddca92941f4e2b41360fe4ee2abd8') - secondDecodedUrn = decodeURN('urn:decentraland:amoy:collections-v2:0xc6d2000a7a1ddca92941f4e2b41360fe4ee2abd8') - }) - - it('should return true', () => { - expect(decodedCollectionsUrnAreEqual(fistDecodedUrn, secondDecodedUrn)).toBe(true) - }) - }) - }) - - describe('and the URNs are of third party v1 collections', () => { - describe('and the URNs are different', () => { - beforeEach(() => { - fistDecodedUrn = decodeURN('urn:decentraland:amoy:collections-thirdparty:thirdparty2:collection-id') - secondDecodedUrn = decodeURN('urn:decentraland:amoy:collections-thirdparty:thirdparty2:another-collection-id') - }) - - it('should return false', () => { - expect(decodedCollectionsUrnAreEqual(fistDecodedUrn, secondDecodedUrn)).toBe(false) - }) - }) - - describe('and the URNs are equal', () => { - beforeEach(() => { - fistDecodedUrn = decodeURN('urn:decentraland:amoy:collections-thirdparty:thirdparty2:collection-id') - secondDecodedUrn = decodeURN('urn:decentraland:amoy:collections-thirdparty:thirdparty2:collection-id') - }) - - it('should return true', () => { - expect(decodedCollectionsUrnAreEqual(fistDecodedUrn, secondDecodedUrn)).toBe(true) - }) - }) - }) - - describe('and the URNs are of third party v2 collections', () => { - describe('and the URNs are different', () => { - beforeEach(() => { - fistDecodedUrn = decodeURN( - 'urn:decentraland:matic:collections-linked-wearables:crypto-motors:amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c' - ) - secondDecodedUrn = decodeURN( - 'urn:decentraland:matic:collections-linked-wearables:crypto-motors:amoy:0x21c28f5a2ab14f11d4fd08425cf0ea5c2367215c' - ) - }) - - it('should return false', () => { - expect(decodedCollectionsUrnAreEqual(fistDecodedUrn, secondDecodedUrn)).toBe(false) - }) + describe('and the URNs are different', () => { + beforeEach(() => { + fistDecodedUrn = decodeURN('urn:decentraland:amoy:collections-v2:0xc6d2000a7a1ddca92941f4e2b41360fe4ee2abd8') + secondDecodedUrn = decodeURN('urn:decentraland:amoy:collections-v2:0x16a2040b2b1eeca12344f4e2b11260ae2ee2edc2') }) - describe('and the URNs are equal', () => { - beforeEach(() => { - fistDecodedUrn = decodeURN( - 'urn:decentraland:matic:collections-linked-wearables:crypto-motors:amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c' - ) - secondDecodedUrn = decodeURN( - 'urn:decentraland:matic:collections-linked-wearables:crypto-motors:amoy:0x74c78f5a4ab22f01d5fd08455cf0ff5c3367535c' - ) - }) - - it('should return true', () => { - expect(decodedCollectionsUrnAreEqual(fistDecodedUrn, secondDecodedUrn)).toBe(true) - }) + it('should return false', () => { + expect(decodedCollectionsUrnAreEqual(fistDecodedUrn, secondDecodedUrn)).toBe(false) }) }) - describe('and the URNs are of different types', () => { + describe('and the URNs are equal', () => { beforeEach(() => { fistDecodedUrn = decodeURN('urn:decentraland:amoy:collections-v2:0xc6d2000a7a1ddca92941f4e2b41360fe4ee2abd8') - secondDecodedUrn = decodeURN('urn:decentraland:amoy:collections-thirdparty:thirdparty2:collection-id') + secondDecodedUrn = decodeURN('urn:decentraland:amoy:collections-v2:0xc6d2000a7a1ddca92941f4e2b41360fe4ee2abd8') }) - it('should return false', () => { - expect(decodedCollectionsUrnAreEqual(fistDecodedUrn, secondDecodedUrn)).toBe(false) + it('should return true', () => { + expect(decodedCollectionsUrnAreEqual(fistDecodedUrn, secondDecodedUrn)).toBe(true) }) }) }) -describe('when getting a default third party item URN suffix', () => { +describe('when getting a default third party URN suffix', () => { describe('and the item name is empty', () => { it('should return a string with the "default" word plus 4 random hex characters', () => { - expect(getDefaultThirdPartyItemUrnSuffix('')).toMatch(/^default-[0-9a-f]{4}$/) + expect(getDefaultThirdPartyUrnSuffix('')).toMatch(/^default-[0-9a-f]{4}$/) }) }) describe('and the item name is not empty', () => { it('should return a string with the sluggled item name plus 4 random hex characters', () => { - expect(getDefaultThirdPartyItemUrnSuffix('a wonderful item: name')).toMatch(/^a-wonderful-item-name-[0-9a-f]{4}$/) + expect(getDefaultThirdPartyUrnSuffix('a wonderful item: name')).toMatch(/^a-wonderful-item-name-[0-9a-f]{4}$/) }) }) }) diff --git a/src/lib/urn.ts b/src/lib/urn.ts index f2667723e..b21d4c4d3 100644 --- a/src/lib/urn.ts +++ b/src/lib/urn.ts @@ -1,5 +1,5 @@ -import { getURNProtocol, Network } from '@dcl/schemas' import slug from 'slug' +import { getURNProtocol, Network } from '@dcl/schemas' import { getChainIdByNetwork } from 'decentraland-dapps/dist/lib/eth' /** @@ -20,7 +20,6 @@ import { getChainIdByNetwork } from 'decentraland-dapps/dist/lib/eth' * base-avatars| * collections-v2| * collections-thirdparty| - * collections-linked-wearables| * entity * ): * (? @@ -30,27 +29,19 @@ import { getChainIdByNetwork } from 'decentraland-dapps/dist/lib/eth' * (?[^:|\\s]+) * (:(?[^:|\\s]+))? * (:(?[^:|\\s]+))? - * )| - * ((?<=collections-linked-wearables:) - * (?[^:|\\s]+) - * (:?mainnet|sepolia|matic|amoy) - * (:?0x[a-fA-F0-9]{40})? - * (:(?[^:|\\s]+))?) + * ) * ((?<=entity:)(?[^:|\\s]+)?\\?=\\&baseUrl=(?https:[^=\\s]+)?) * ) * ) */ const baseMatcher = 'urn:decentraland' const protocolMatcher = '(?mainnet|goerli|sepolia|matic|mumbai|amoy|off-chain)' -const typeMatcher = '(?base-avatars|collections-v2|collections-thirdparty|collections-linked-wearables|entity)' +const typeMatcher = '(?base-avatars|collections-v2|collections-thirdparty|entity)' const baseAvatarsSuffixMatcher = '((?<=base-avatars:)BaseMale|BaseFemale)' const collectionsSuffixMatcher = '((?<=collections-v2:)(?0x[a-fA-F0-9]{40}))(:(?[^:|\\s]+))?' -const thirdPartySuffixMatcher = '((?<=collections-thirdparty:)(?[^:|\\s]+)(:(?[^:|\\s]+))?)' -const thirdPartyV2SuffixMatcher = - '((?<=collections-linked-wearables:)(?[^:|\\s]+)(:(?mainnet|sepolia|matic|amoy):(?0x[a-fA-F0-9]{40}))?)' -const thirdPartyMatchers = `(:?${thirdPartySuffixMatcher}|${thirdPartyV2SuffixMatcher})(:(?[^:|\\s]+))?` - +const thirdPartySuffixMatcher = + '((?<=collections-thirdparty:)(?[^:|\\s]+)(:(?[^:|\\s]+))?(:(?[^:|\\s]+))?)' const entitySuffixMatcher = '((?<=entity:)(?[^\\?|\\s]+)(\\?=\\&baseUrl=(?[^\\?|\\s]+))?)' export enum URNProtocol { @@ -62,17 +53,10 @@ export enum URNProtocol { AMOY = 'amoy', OFF_CHAIN = 'off-chain' } -export enum LinkedContractProtocol { - MAINNET = 'mainnet', - SEPOLIA = 'sepolia', - MATIC = 'matic', - AMOY = 'amoy' -} export enum URNType { BASE_AVATARS = 'base-avatars', COLLECTIONS_V2 = 'collections-v2', COLLECTIONS_THIRDPARTY = 'collections-thirdparty', - COLLECTIONS_THIRDPARTY_V2 = 'collections-linked-wearables', ENTITY = 'entity' } export type URN = string @@ -89,26 +73,17 @@ type CollectionThirdPartyURN = { thirdPartyCollectionId?: string thirdPartyTokenId?: string } -type CollectionThirdPartyV2URN = { - type: URNType.COLLECTIONS_THIRDPARTY_V2 - thirdPartyLinkedCollectionName: string - linkedCollectionNetwork: LinkedContractProtocol - linkedCollectionContractAddress: string - thirdPartyTokenId?: string -} type EntityURN = { type: URNType.ENTITY; entityId: string; baseUrl?: string } export type DecodedURN = BaseDecodedURN & (T extends URNType.BASE_AVATARS ? BaseAvatarURN : T extends URNType.COLLECTIONS_V2 ? CollectionsV2URN - : T extends URNType.COLLECTIONS_THIRDPARTY_V2 - ? CollectionThirdPartyV2URN : T extends URNType.COLLECTIONS_THIRDPARTY ? CollectionThirdPartyURN : T extends URNType.ENTITY ? EntityURN - : BaseAvatarURN | CollectionsV2URN | CollectionThirdPartyURN | CollectionThirdPartyV2URN | EntityURN) + : BaseAvatarURN | CollectionsV2URN | CollectionThirdPartyURN | EntityURN) export function buildThirdPartyURN(thirdPartyName: string, collectionId: string, tokenId?: string) { let urn = `urn:decentraland:${getNetworkURNProtocol(Network.MATIC)}:collections-thirdparty:${thirdPartyName}:${collectionId}` @@ -118,21 +93,6 @@ export function buildThirdPartyURN(thirdPartyName: string, collectionId: string, return urn } -export function buildThirdPartyV2URN( - thirdPartyName: string, - thirdPartyLinkedContractNetwork: LinkedContractProtocol, - thirdPartyLinkedContractAddress: string, - tokenId?: string -) { - let urn = `urn:decentraland:${getNetworkURNProtocol( - Network.MATIC - )}:collections-linked-wearables:${thirdPartyName}:${thirdPartyLinkedContractNetwork}:${thirdPartyLinkedContractAddress}` - if (tokenId) { - urn += `:${tokenId}` - } - return urn -} - export function buildCatalystItemURN(contractAddress: string, tokenId: string): URN { return `urn:decentraland:${getNetworkURNProtocol(Network.MATIC)}:collections-v2:${contractAddress}:${tokenId}` } @@ -143,44 +103,32 @@ export function buildDefaultCatalystCollectionURN() { export function extractThirdPartyId(urn: URN): string { const decodedURN = decodeURN(urn) - if (decodedURN.type !== URNType.COLLECTIONS_THIRDPARTY && decodedURN.type !== URNType.COLLECTIONS_THIRDPARTY_V2) { + if (decodedURN.type !== URNType.COLLECTIONS_THIRDPARTY) { throw new Error('URN is not a third party URN') } - if (decodedURN.type === URNType.COLLECTIONS_THIRDPARTY) { - return `urn:decentraland:${decodedURN.protocol}:collections-thirdparty:${decodedURN.thirdPartyName}` - } else { - return `urn:decentraland:${decodedURN.protocol}:collections-linked-wearables:${decodedURN.thirdPartyLinkedCollectionName}` - } + return `urn:decentraland:${decodedURN.protocol}:collections-thirdparty:${decodedURN.thirdPartyName}` } export function extractThirdPartyTokenId(urn: URN) { const decodedURN = decodeURN(urn) - - if (decodedURN.type === URNType.COLLECTIONS_THIRDPARTY) { - const { thirdPartyCollectionId, thirdPartyTokenId } = decodedURN - return `${thirdPartyCollectionId ?? ''}:${thirdPartyTokenId ?? ''}` - } else if (decodedURN.type === URNType.COLLECTIONS_THIRDPARTY_V2) { - const { linkedCollectionNetwork, linkedCollectionContractAddress, thirdPartyTokenId } = decodedURN - return `${linkedCollectionNetwork}:${linkedCollectionContractAddress}:${thirdPartyTokenId ?? ''}` + if (decodedURN.type !== URNType.COLLECTIONS_THIRDPARTY) { + throw new Error(`Tried to build a third party token for a non third party URN "${urn}"`) } - throw new Error(`Tried to build a third party token for a non third party URN "${urn}"`) + const { thirdPartyCollectionId, thirdPartyTokenId } = decodedURN + return `${thirdPartyCollectionId ?? ''}:${thirdPartyTokenId ?? ''}` } // TODO: This logic is repeated in collection/util's `getCollectionType`, but being used only for items (item.urn). // It should probably be replaced by a getItemType or we should see if it's better to only keep one way of doing this -export function isThirdParty(urn?: string, version?: URNType.COLLECTIONS_THIRDPARTY | URNType.COLLECTIONS_THIRDPARTY_V2) { +export function isThirdParty(urn?: string) { if (!urn) { return false } const decodedURN = decodeURN(urn) - if (version) { - return decodedURN.type === version - } - - return decodedURN.type === URNType.COLLECTIONS_THIRDPARTY || decodedURN.type === URNType.COLLECTIONS_THIRDPARTY_V2 + return decodedURN.type === URNType.COLLECTIONS_THIRDPARTY } export function extractEntityId(urn: URN): string { @@ -194,7 +142,7 @@ export function extractEntityId(urn: URN): string { export function decodeURN(urn: URN): DecodedURN { const urnRegExp = new RegExp( - `${baseMatcher}:(${protocolMatcher}:)?${typeMatcher}:(?${baseAvatarsSuffixMatcher}|${collectionsSuffixMatcher}|${thirdPartyMatchers}|${entitySuffixMatcher})` + `${baseMatcher}:(${protocolMatcher}:)?${typeMatcher}:(?${baseAvatarsSuffixMatcher}|${collectionsSuffixMatcher}|${thirdPartySuffixMatcher}|${entitySuffixMatcher})` ) const matches = urnRegExp.exec(urn) @@ -235,6 +183,11 @@ export function extractTokenId(urn: URN): string { return `${collectionAddress}:${tokenId ?? ''}` } +export const isThirdPartyCollectionDecodedUrn = ( + urn: DecodedURN +): urn is DecodedURN & { thirdPartyName: string; thirdPartyCollectionId: string } => + urn.type === URNType.COLLECTIONS_THIRDPARTY && !!urn.thirdPartyName && !!urn.thirdPartyCollectionId + export const decodedCollectionsUrnAreEqual = (urnA: DecodedURN, urnB: DecodedURN) => { if (urnA.type !== urnB.type) { return false @@ -252,34 +205,10 @@ export const decodedCollectionsUrnAreEqual = (urnA: DecodedURN, urnB: DecodedURN urnA.thirdPartyCollectionId === (urnB as DecodedURN).thirdPartyCollectionId && urnA.thirdPartyTokenId === (urnB as DecodedURN).thirdPartyTokenId ) - case URNType.COLLECTIONS_THIRDPARTY_V2: - return ( - urnA.thirdPartyLinkedCollectionName === (urnB as DecodedURN).thirdPartyLinkedCollectionName && - urnA.linkedCollectionNetwork === (urnB as DecodedURN).linkedCollectionNetwork && - urnA.linkedCollectionContractAddress === (urnB as DecodedURN).linkedCollectionContractAddress && - urnA.thirdPartyTokenId === (urnB as DecodedURN).thirdPartyTokenId - ) } } -export const isThirdPartyCollectionDecodedUrn = ( - urn: DecodedURN -): urn is DecodedURN & { thirdPartyName: string; thirdPartyCollectionId: string } => - urn.type === URNType.COLLECTIONS_THIRDPARTY && !!urn.thirdPartyName && !!urn.thirdPartyCollectionId - -export const isThirdPartyV2CollectionDecodedUrn = ( - urn: DecodedURN -): urn is DecodedURN & { - thirdPartyLinkedCollectionName: string - linkedCollectionNetwork: string - linkedCollectionAddress: string -} => - urn.type === URNType.COLLECTIONS_THIRDPARTY_V2 && - !!urn.thirdPartyLinkedCollectionName && - !!urn.linkedCollectionNetwork && - !!urn.linkedCollectionContractAddress - -export const getDefaultThirdPartyItemUrnSuffix = (itemName: string) => { +export const getDefaultThirdPartyUrnSuffix = (itemName: string) => { const randHex = Array.from({ length: 4 }, () => Math.floor(Math.random() * 16).toString(16)).join('') return `${slug(itemName.length > 0 ? itemName : 'default')}-${randHex}` } diff --git a/src/modules/collection/sagas.spec.ts b/src/modules/collection/sagas.spec.ts index f6213b0b8..522fc32c1 100644 --- a/src/modules/collection/sagas.spec.ts +++ b/src/modules/collection/sagas.spec.ts @@ -1259,7 +1259,7 @@ describe('when saving a collection', () => { return expectSaga(collectionSaga, mockBuilder, mockBuilderClient) .provide([ [getContext('history'), { push: pushMock }], - [select(getOpenModals), { CreateThirdPartyCollectionModal: true, CreateLinkedWearablesCollectionModal: true }], + [select(getOpenModals), { CreateThirdPartyCollectionModal: true }], [call([mockBuilder, 'saveCollection'], thirdPartyCollection, ''), remoteCollection] ]) .dispatch(saveCollectionRequest(thirdPartyCollection)) @@ -1278,7 +1278,6 @@ describe('when saving a collection', () => { .put(saveCollectionSuccess({ ...thirdPartyCollection, contractAddress: remoteCollection.contractAddress })) .put(closeModal('CreateCollectionModal')) .put(closeModal('CreateThirdPartyCollectionModal')) - .put(closeModal('CreateLinkedWearablesCollectionModal')) .put(closeModal('EditCollectionURNModal')) .put(closeModal('EditCollectionNameModal')) .dispatch(saveCollectionRequest(thirdPartyCollection)) diff --git a/src/modules/collection/sagas.ts b/src/modules/collection/sagas.ts index 065bae60b..a6da794fa 100644 --- a/src/modules/collection/sagas.ts +++ b/src/modules/collection/sagas.ts @@ -247,11 +247,7 @@ export function* collectionSaga(legacyBuilderClient: BuilderAPI, client: Builder const openModals: ModalState = yield select(getOpenModals) const history: History = yield getContext('history') - if ( - openModals['CreateCollectionModal'] || - openModals['CreateThirdPartyCollectionModal'] || - openModals['CreateLinkedWearablesCollectionModal'] - ) { + if (openModals['CreateCollectionModal'] || openModals['CreateThirdPartyCollectionModal']) { // Redirect to the newly created collection detail const { collection } = action.payload const detailPageLocation = isTPCollection(collection) ? locations.thirdPartyCollectionDetail : locations.collectionDetail @@ -262,7 +258,6 @@ export function* collectionSaga(legacyBuilderClient: BuilderAPI, client: Builder yield put(closeModal('CreateCollectionModal')) yield put(closeModal('CreateThirdPartyCollectionModal')) yield put(closeModal('EditCollectionURNModal')) - yield put(closeModal('CreateLinkedWearablesCollectionModal')) yield put(closeModal('EditCollectionNameModal')) } diff --git a/src/modules/collection/selectors.ts b/src/modules/collection/selectors.ts index 3ad812d6c..83838a154 100644 --- a/src/modules/collection/selectors.ts +++ b/src/modules/collection/selectors.ts @@ -81,8 +81,7 @@ export const getAuthorizedCollections = createSelector< switch (type) { case CollectionType.STANDARD: return address && canSeeCollection(collection, address) - case CollectionType.THIRD_PARTY: - case CollectionType.THIRD_PARTY_V2: { + case CollectionType.THIRD_PARTY: { const thirdParty = getThirdPartyForCollection(thirdParties, collection) return address && thirdParty && isUserManagerOfThirdParty(address, thirdParty) } diff --git a/src/modules/collection/types.ts b/src/modules/collection/types.ts index cad38b019..f3a36f9dc 100644 --- a/src/modules/collection/types.ts +++ b/src/modules/collection/types.ts @@ -23,8 +23,7 @@ export type Collection = { export enum CollectionType { STANDARD = 'standard', - THIRD_PARTY = 'third_party', - THIRD_PARTY_V2 = 'third_party_v2' + THIRD_PARTY = 'third_party' } export enum RoleType { diff --git a/src/modules/collection/utils.ts b/src/modules/collection/utils.ts index 7063711e4..e94101092 100644 --- a/src/modules/collection/utils.ts +++ b/src/modules/collection/utils.ts @@ -81,7 +81,7 @@ export function getCollectionBaseURI() { export function isThirdPartyCollection(collection: Collection) { const collectionType = getCollectionType(collection) - return collectionType === CollectionType.THIRD_PARTY || collectionType === CollectionType.THIRD_PARTY_V2 + return collectionType === CollectionType.THIRD_PARTY } export function getCollectionType(collection: Collection): CollectionType { @@ -90,8 +90,6 @@ export function getCollectionType(collection: Collection): CollectionType { switch (type) { case URNType.COLLECTIONS_THIRDPARTY: return CollectionType.THIRD_PARTY - case URNType.COLLECTIONS_THIRDPARTY_V2: - return CollectionType.THIRD_PARTY_V2 case URNType.COLLECTIONS_V2: case URNType.BASE_AVATARS: return CollectionType.STANDARD diff --git a/src/modules/item/export.ts b/src/modules/item/export.ts index 46ecac794..fbf5748b1 100644 --- a/src/modules/item/export.ts +++ b/src/modules/item/export.ts @@ -3,7 +3,7 @@ import { DeploymentPreparationData, buildEntity } from 'dcl-catalyst-client/dist import { MerkleDistributorInfo } from '@dcl/content-hash-tree/dist/types' import { calculateMultipleHashesADR32, calculateMultipleHashesADR32LegacyQmHash } from '@dcl/hashing' import { BuilderAPI } from 'lib/api/builder' -import { buildCatalystItemURN, decodeURN, isThirdPartyV2CollectionDecodedUrn } from 'lib/urn' +import { buildCatalystItemURN } from 'lib/urn' import { makeContentFiles, computeHashes } from 'modules/deployment/contentUtils' import { Collection } from 'modules/collection/types' import { Item, IMAGE_PATH, THUMBNAIL_PATH, ItemType, EntityHashingType, isEmoteItemType, VIDEO_PATH } from './types' @@ -131,8 +131,6 @@ function buildTPItemEntityMetadata(item: Item, itemHash: string, tree: MerkleDis throw new Error('Item does not have URN') } - const decodedURN = decodeURN(item.urn) - // The order of the metadata properties can't be changed. Changing it will result in a different content hash. const baseEntityData = { id: item.urn, @@ -152,7 +150,7 @@ function buildTPItemEntityMetadata(item: Item, itemHash: string, tree: MerkleDis thumbnail: THUMBNAIL_PATH, metrics: item.metrics, content: item.contents, - ...(isThirdPartyV2CollectionDecodedUrn(decodedURN) && item.mappings ? { mappings: item.mappings } : {}) + ...(item.mappings ? { mappings: item.mappings } : {}) } return { diff --git a/src/modules/item/reducer.ts b/src/modules/item/reducer.ts index 58001ae11..45397b5b0 100644 --- a/src/modules/item/reducer.ts +++ b/src/modules/item/reducer.ts @@ -110,15 +110,7 @@ import { } from 'modules/thirdParty/actions' import { toItemObject } from './utils' import { Item, BlockchainRarity } from './types' -import { - buildCatalystItemURN, - buildThirdPartyURN, - buildThirdPartyV2URN, - decodeURN, - isThirdPartyCollectionDecodedUrn, - isThirdPartyV2CollectionDecodedUrn, - URNType -} from 'lib/urn' +import { buildCatalystItemURN, buildThirdPartyURN, decodeURN, isThirdPartyCollectionDecodedUrn, URNType } from 'lib/urn' import { CLOSE_MODAL, CloseModalAction } from 'decentraland-dapps/dist/modules/modal/actions' export type ItemPaginationData = { @@ -517,18 +509,11 @@ export function itemReducer(state: ItemState = INITIAL_STATE, action: ItemReduce if (item.collectionId === collection.id && item.urn) { let newItemURN: string const itemURN = decodeURN(item.urn) - if (isThirdPartyCollectionDecodedUrn(collectionURN) || isThirdPartyV2CollectionDecodedUrn(collectionURN)) { - if (!isThirdPartyCollectionDecodedUrn(itemURN) && !isThirdPartyV2CollectionDecodedUrn(itemURN)) { + if (isThirdPartyCollectionDecodedUrn(collectionURN)) { + if (!isThirdPartyCollectionDecodedUrn(itemURN)) { throw new Error(`The item ${item.id} is not part of a third-party collection but it should be`) } - newItemURN = isThirdPartyCollectionDecodedUrn(collectionURN) - ? buildThirdPartyURN(collectionURN.thirdPartyName, collectionURN.thirdPartyCollectionId, itemURN.thirdPartyTokenId) - : buildThirdPartyV2URN( - collectionURN.thirdPartyLinkedCollectionName, - collectionURN.linkedCollectionNetwork, - collectionURN.linkedCollectionContractAddress, - itemURN.thirdPartyTokenId - ) + newItemURN = buildThirdPartyURN(collectionURN.thirdPartyName, collectionURN.thirdPartyCollectionId, itemURN.thirdPartyTokenId) } else if (collectionURN.type === URNType.COLLECTIONS_V2) { if (itemURN.type !== URNType.COLLECTIONS_V2) { throw new Error(`The item ${item.id} is not part of a decentraland collection but it should be`) diff --git a/src/modules/item/sagas.ts b/src/modules/item/sagas.ts index d7b2bbefd..1f03dd10e 100644 --- a/src/modules/item/sagas.ts +++ b/src/modules/item/sagas.ts @@ -447,6 +447,7 @@ export function* itemSaga(legacyBuilder: LegacyBuilderAPI, builder: BuilderClien } yield call([legacyBuilder, 'saveItem'], item, contents) + yield saveItemRequest(item, contents) yield put(saveItemSuccess(item, contents)) } catch (error) { yield put(saveItemFailure(actionItem, actionContents, isErrorWithMessage(error) ? error.message : 'Unknown error')) diff --git a/src/modules/item/types.ts b/src/modules/item/types.ts index 8b21de205..42c6c49ad 100644 --- a/src/modules/item/types.ts +++ b/src/modules/item/types.ts @@ -1,5 +1,15 @@ import { BuiltItem, Content } from '@dcl/builder-client' -import { BodyShape, EmoteDataADR74, Wearable, WearableCategory, Rarity, HideableWearableCategory, Mapping } from '@dcl/schemas' +import { + BodyShape, + EmoteDataADR74, + Wearable, + WearableCategory, + Rarity, + HideableWearableCategory, + Mapping, + ContractAddress, + ContractNetwork +} from '@dcl/schemas' import { AnimationMetrics, ModelMetrics } from 'modules/models/types' import { Cheque } from 'modules/thirdParty/types' @@ -112,7 +122,7 @@ export type Item = Omit & { catalystContentHash: string | null data: T extends ItemType.WEARABLE ? WearableData : EmoteDataADR74 metrics: T extends ItemType.WEARABLE ? ModelMetrics : AnimationMetrics - mappings: Mapping[] | null + mappings: Partial>> | null } export const isEmoteItemType = (item: Item | Item): item is Item => diff --git a/src/modules/thirdParty/types.ts b/src/modules/thirdParty/types.ts index 605999304..9ac8a56bd 100644 --- a/src/modules/thirdParty/types.ts +++ b/src/modules/thirdParty/types.ts @@ -1,4 +1,9 @@ -import { LinkedContractProtocol } from 'lib/urn' +export enum LinkedContractProtocol { + MATIC = 'matic', + SEPOLIA = 'sepolia', + AMOY = 'amoy', + MAINNET = 'mainnet' +} export type ThirdParty = { id: string @@ -29,8 +34,3 @@ export type Slot = { sigS: string sigV: number } - -export enum ThirdPartyVersion { - V1 = 1, - V2 = 2 -} diff --git a/src/modules/thirdParty/utils.ts b/src/modules/thirdParty/utils.ts index 030a637b6..02446c75a 100644 --- a/src/modules/thirdParty/utils.ts +++ b/src/modules/thirdParty/utils.ts @@ -7,15 +7,12 @@ import { ContractData, ContractName, getContract } from 'decentraland-transactio import { extractThirdPartyId } from 'lib/urn' import { Collection } from 'modules/collection/types' import { Item } from 'modules/item/types' -import { ThirdParty, ThirdPartyVersion } from './types' +import { ThirdParty } from './types' export function isUserManagerOfThirdParty(address: string, thirdParty: ThirdParty): boolean { return thirdParty.managers.map(manager => manager.toLowerCase()).includes(address.toLowerCase()) } -export const getThirdPartyVersion = (thirdParty: ThirdParty): number => - thirdParty.id.split(':')[3] === 'collections-linked-wearables' ? ThirdPartyVersion.V2 : ThirdPartyVersion.V1 - export const getThirdPartyForCollection = (thirdParties: Record, collection: Collection): ThirdParty | undefined => thirdParties[extractThirdPartyId(collection.urn)] diff --git a/src/routing/locations.ts b/src/routing/locations.ts index 3d2ceac9f..c366b6f0e 100644 --- a/src/routing/locations.ts +++ b/src/routing/locations.ts @@ -37,7 +37,6 @@ export const locations = { case CollectionType.STANDARD: return injectParams(`/collections/${collectionId}`, { tab: 'tab' }, options) case CollectionType.THIRD_PARTY: - case CollectionType.THIRD_PARTY_V2: return injectParams(locations.thirdPartyCollectionDetail(collectionId), { tab: 'tab' }, options) default: throw new Error(`Invalid collection type ${type as unknown as string}`)