diff --git a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.container.ts b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.container.ts index 5ac4fcaae..3849606d6 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.container.ts +++ b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.container.ts @@ -1,14 +1,7 @@ import { connect } from 'react-redux' import { openModal } from 'decentraland-dapps/dist/modules/modal' -import { isLoadingThirdParties, isThirdPartyManager } from 'modules/thirdParty/selectors' -import { MapDispatchProps, MapDispatch, OwnProps, MapStateProps } from './CreateCollectionSelectorModal.types' +import { MapDispatchProps, MapDispatch, OwnProps } from './CreateCollectionSelectorModal.types' import { CreateCollectionSelectorModal } from './CreateCollectionSelectorModal' -import { RootState } from 'modules/common/types' - -const mapState = (state: RootState): MapStateProps => ({ - isThirdPartyManager: isThirdPartyManager(state), - isLoadingThirdParties: isLoadingThirdParties(state) -}) const mapDispatch = (dispatch: MapDispatch, ownProps: OwnProps): MapDispatchProps => ({ onCreateCollection: () => { @@ -21,4 +14,4 @@ const mapDispatch = (dispatch: MapDispatch, ownProps: OwnProps): MapDispatchProp } }) -export default connect(mapState, mapDispatch)(CreateCollectionSelectorModal) +export default connect(undefined, mapDispatch)(CreateCollectionSelectorModal) diff --git a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.module.css b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.module.css index dc50e99ee..b23f7a8c5 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.module.css +++ b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.module.css @@ -16,17 +16,6 @@ border-radius: 8px; } -.collectionSelection .disabled { - width: 100%; - height: 100%; - border-radius: 8px; - position: absolute; - top: 0; - left: 0; - opacity: 0.3; - background-color: black; -} - .collectionSelection img { width: 227px; height: 110px; diff --git a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.spec.tsx b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.spec.tsx index 575a5aa3d..09380c4f9 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.spec.tsx +++ b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.spec.tsx @@ -1,7 +1,7 @@ import { renderWithProviders } from 'specs/utils' import { CreateCollectionSelectorModal } from './CreateCollectionSelectorModal' import { Props } from './CreateCollectionSelectorModal.types' -import { CREATE_BUTTON_TEST_ID, DISABLED_DATA_TEST_ID } from './constants' +import { CREATE_BUTTON_TEST_ID } from './constants' import userEvent from '@testing-library/user-event' export function renderWorldContributorTab(props: Partial) { @@ -12,8 +12,6 @@ export function renderWorldContributorTab(props: Partial) { metadata={{}} name="aName" onClose={jest.fn()} - isLoadingThirdParties={false} - isThirdPartyManager={false} {...props} /> ) @@ -29,8 +27,7 @@ describe('when clicking on the create collection button', () => { onCreateThirdPartyCollection = jest.fn() renderedComponent = renderWorldContributorTab({ onCreateCollection, - onCreateThirdPartyCollection, - isThirdPartyManager: true + onCreateThirdPartyCollection }) }) @@ -57,42 +54,3 @@ describe('when clicking on the create collection button', () => { }) }) }) - -describe('and the linked collections are being loaded', () => { - let renderedComponent: ReturnType - beforeEach(() => { - renderedComponent = renderWorldContributorTab({ isLoadingThirdParties: true }) - }) - - it('should show the disabled overlay for the linked collections', () => { - const disabledOverlay = renderedComponent.getByTestId(DISABLED_DATA_TEST_ID) - expect(disabledOverlay).toBeInTheDocument() - }) - - it('should disable the create button for the linked collections', () => { - const createButton = renderedComponent.getAllByTestId(CREATE_BUTTON_TEST_ID)[1] - expect(createButton).toBeDisabled() - }) - - it('should set the button as loading', () => { - const createButton = renderedComponent.getAllByTestId(CREATE_BUTTON_TEST_ID)[1] - expect(createButton).toHaveClass('loading') - }) -}) - -describe('and the user is not a third party manager', () => { - let renderedComponent: ReturnType - beforeEach(() => { - renderedComponent = renderWorldContributorTab({ isThirdPartyManager: false }) - }) - - it('should show the disabled overlay for the linked collections', () => { - const disabledOverlay = renderedComponent.getByTestId(DISABLED_DATA_TEST_ID) - expect(disabledOverlay).toBeInTheDocument() - }) - - it('should disable the create button for the linked collections', () => { - const createButton = renderedComponent.getAllByTestId(CREATE_BUTTON_TEST_ID)[1] - expect(createButton).toBeDisabled() - }) -}) diff --git a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.tsx b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.tsx index b919217c0..9e4cf827c 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.tsx +++ b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.tsx @@ -7,7 +7,7 @@ import collectionsImage from '../../../images/collections.png' import linkedCollectionsImage from '../../../images/linked-collections.png' import { Props } from './CreateCollectionSelectorModal.types' import styles from './CreateCollectionSelectorModal.module.css' -import { CREATE_BUTTON_TEST_ID, DISABLED_DATA_TEST_ID } from './constants' +import { CREATE_BUTTON_TEST_ID } from './constants' const CollectionSelectionModal = ({ image, @@ -28,7 +28,6 @@ const CollectionSelectionModal = ({ }) => { return (
- {disabled &&
} {title}
@@ -49,7 +48,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, onCreateThirdPartyCollection, name, isThirdPartyManager, isLoadingThirdParties } = props + const { onClose, onCreateCollection, onCreateThirdPartyCollection, name } = props return ( @@ -72,8 +71,6 @@ export const CreateCollectionSelectorModal = (props: Props) => { title={t('create_collection_selector_modal.linked_collection.title')} subtitle={t('create_collection_selector_modal.linked_collection.subtitle')} 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 6c9d2b50c..51e46e450 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.types.ts +++ b/src/components/Modals/CreateCollectionSelectorModal/CreateCollectionSelectorModal.types.ts @@ -3,13 +3,10 @@ import { OpenModalAction } from 'decentraland-dapps/dist/modules/modal' import { ModalProps } from 'decentraland-dapps/dist/providers/ModalProvider/ModalProvider.types' export type Props = ModalProps & { - isThirdPartyManager: boolean - isLoadingThirdParties: boolean onCreateCollection: () => void onCreateThirdPartyCollection: () => void } -export type MapStateProps = Pick export type MapDispatchProps = Pick export type OwnProps = Pick export type MapDispatch = Dispatch diff --git a/src/components/Modals/CreateCollectionSelectorModal/constants.ts b/src/components/Modals/CreateCollectionSelectorModal/constants.ts index 2ae07b038..84c0d8bd4 100644 --- a/src/components/Modals/CreateCollectionSelectorModal/constants.ts +++ b/src/components/Modals/CreateCollectionSelectorModal/constants.ts @@ -1,2 +1 @@ -export const DISABLED_DATA_TEST_ID = 'create-collection-selector-modal-disabled' export const CREATE_BUTTON_TEST_ID = 'create-collection-selector-modal-create-button' diff --git a/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.tsx b/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.tsx index 72dfa1af8..6f4791d03 100644 --- a/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.tsx +++ b/src/components/Modals/CreateThirdPartyCollectionModal/CreateThirdPartyCollectionModal.tsx @@ -217,7 +217,7 @@ export const CreateThirdPartyCollectionModal: FC = (props: Props) => { collectionName }) } - }, [onSubmit, collectionId, collectionName, selectedThirdParty, ownerAddress, analytics]) + }, [onSubmit, collectionId, selectedContract, selectedNetwork, collectionName, selectedThirdParty, ownerAddress, analytics]) const isSubmittable = collectionName && diff --git a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.container.ts b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.container.ts index e51c2ce6f..ff07d4391 100644 --- a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.container.ts +++ b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.container.ts @@ -10,18 +10,20 @@ import { getCollectionItems, getLoading as getLoadingItem, getPaginationData } f import { FETCH_COLLECTION_ITEMS_REQUEST } from 'modules/item/actions' import { FETCH_COLLECTIONS_REQUEST, DELETE_COLLECTION_REQUEST } from 'modules/collection/actions' import { openModal } from 'decentraland-dapps/dist/modules/modal/actions' -import { getCollectionThirdParty, isFetchingAvailableSlots } from 'modules/thirdParty/selectors' -import { fetchThirdPartyAvailableSlotsRequest } from 'modules/thirdParty/actions' +import { getCollectionThirdParty, isFetchingAvailableSlots, isLoadingThirdParties, isLoadingThirdParty } from 'modules/thirdParty/selectors' +import { fetchThirdPartyAvailableSlotsRequest, fetchThirdPartyRequest } from 'modules/thirdParty/actions' import { isThirdPartyCollection } from 'modules/collection/utils' import { Collection } from 'modules/collection/types' import { getIsLinkedWearablesPaymentsEnabled, getIsLinkedWearablesV2Enabled } from 'modules/features/selectors' import { getLastLocation } from 'modules/ui/location/selector' import { MapStateProps, MapDispatchProps, MapDispatch } from './ThirdPartyCollectionDetailPage.types' import CollectionDetailPage from './ThirdPartyCollectionDetailPage' +import { extractThirdPartyId } from 'lib/urn' const mapState = (state: RootState): MapStateProps => { const collectionId = getCollectionId(state) || '' const collection = getCollection(state, collectionId) + const isThirdParty = collection ? isThirdPartyCollection(collection) : false const totalItems = getPaginationData(state, collectionId)?.total || null const items = collection ? getCollectionItems(state, collection.id) : [] const paginatedData = (collection && getPaginationData(state, collection.id)) || null @@ -40,7 +42,9 @@ const mapState = (state: RootState): MapStateProps => { isLoading: isLoadingType(getLoadingCollection(state), FETCH_COLLECTIONS_REQUEST) || isLoadingType(getLoadingCollection(state), DELETE_COLLECTION_REQUEST) || - isLoadingType(getLoadingItem(state), FETCH_COLLECTION_ITEMS_REQUEST), + isLoadingType(getLoadingItem(state), FETCH_COLLECTION_ITEMS_REQUEST) || + isLoadingThirdParties(state) || + !!(isThirdParty && collection && isLoadingThirdParty(state, extractThirdPartyId(collection.urn))), isLoadingAvailableSlots: isFetchingAvailableSlots(state), lastLocation: getLastLocation(state) } @@ -49,7 +53,8 @@ const mapState = (state: RootState): MapStateProps => { const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({ onNewItem: (collectionId: string) => dispatch(openModal('CreateItemsModal', { collectionId })), onEditName: (collection: Collection) => dispatch(openModal('EditCollectionNameModal', { collection })), - onFetchAvailableSlots: (thirdPartyId: string) => dispatch(fetchThirdPartyAvailableSlotsRequest(thirdPartyId)) + onFetchAvailableSlots: (thirdPartyId: string) => dispatch(fetchThirdPartyAvailableSlotsRequest(thirdPartyId)), + onFetchThirdParty: (thirdPartyId: string) => dispatch(fetchThirdPartyRequest(thirdPartyId)) }) export default connect(mapState, mapDispatch)(CollectionDetailPage) diff --git a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.module.css b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.module.css index 76df26ce2..87e7e4886 100644 --- a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.module.css +++ b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.module.css @@ -82,6 +82,7 @@ .editCollectionName { visibility: hidden; + padding-left: 16px; } .header .title .name:hover + .editCollectionName { @@ -95,7 +96,7 @@ text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - width: calc(100% - 80px); + width: fit-content; } .header .title .size { diff --git a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.tsx b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.tsx index cc8b5d7c4..6eb64626a 100644 --- a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.tsx +++ b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.tsx @@ -15,6 +15,8 @@ import { import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { getArrayOfPagesFromTotal } from 'lib/api/pagination' import { locations } from 'routing/locations' +import { ItemMappingStatus } from 'lib/api/builder' +import { extractThirdPartyId } from 'lib/urn' import { isUserManagerOfThirdParty } from 'modules/thirdParty/utils' import { Item } from 'modules/item/types' import { ThirdParty } from 'modules/thirdParty/types' @@ -35,7 +37,6 @@ import { Props, PAGE_SIZE } from './ThirdPartyCollectionDetailPage.types' import { CollectionItemHeader } from './CollectionItemHeader' import { CollectionItemHeaderV2 } from './CollectionItemHeaderV2' import styles from './ThirdPartyCollectionDetailPage.module.css' -import { ItemMappingStatus } from 'lib/api/builder' const Info = ({ children, title, info }: { children: React.ReactNode; title: string; info?: string }) => (
@@ -66,6 +67,7 @@ export default function ThirdPartyCollectionDetailPage({ isThirdPartyV2Enabled, isLinkedWearablesPaymentsEnabled, onFetchAvailableSlots, + onFetchThirdParty, onNewItem, onEditName, isLoadingAvailableSlots @@ -84,6 +86,12 @@ export default function ThirdPartyCollectionDetailPage({ } }, [thirdParty, isLoadingAvailableSlots, onFetchAvailableSlots]) + useEffect(() => { + if (!isLoading && !thirdParty && collection?.urn) { + onFetchThirdParty(extractThirdPartyId(collection.urn)) + } + }, [collection?.urn, isLoading, thirdParty]) + useEffect(() => { // update the state if the page query param changes if (currentPage !== page) { diff --git a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.types.ts b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.types.ts index 717a90db6..97418977d 100644 --- a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.types.ts +++ b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.types.ts @@ -8,7 +8,11 @@ import { Collection } from 'modules/collection/types' import { ThirdParty } from 'modules/thirdParty/types' import { FetchItemCurationsRequestAction } from 'modules/curations/itemCuration/actions' import { ItemCuration } from 'modules/curations/itemCuration/types' -import { fetchThirdPartyAvailableSlotsRequest, FetchThirdPartyAvailableSlotsRequestAction } from 'modules/thirdParty/actions' +import { + fetchThirdPartyAvailableSlotsRequest, + FetchThirdPartyAvailableSlotsRequestAction, + FetchThirdPartyRequestAction +} from 'modules/thirdParty/actions' import { FetchCollectionItemsRequestAction } from 'modules/item/actions' import { ItemPaginationData } from 'modules/item/reducer' @@ -32,6 +36,7 @@ export type Props = { isLinkedWearablesPaymentsEnabled: boolean onNewItem: (collectionId: string) => unknown onEditName: (collection: Collection) => unknown + onFetchThirdParty: (thirdPartyId: string) => unknown onFetchAvailableSlots: typeof fetchThirdPartyAvailableSlotsRequest } @@ -60,7 +65,11 @@ export type MapStateProps = Pick< | 'paginatedData' | 'lastLocation' > -export type MapDispatchProps = Pick +export type MapDispatchProps = Pick export type MapDispatch = Dispatch< - OpenModalAction | FetchItemCurationsRequestAction | FetchThirdPartyAvailableSlotsRequestAction | FetchCollectionItemsRequestAction + | OpenModalAction + | FetchItemCurationsRequestAction + | FetchThirdPartyAvailableSlotsRequestAction + | FetchCollectionItemsRequestAction + | FetchThirdPartyRequestAction > diff --git a/src/lib/api/builder.ts b/src/lib/api/builder.ts index b282cb2d0..5132226f6 100644 --- a/src/lib/api/builder.ts +++ b/src/lib/api/builder.ts @@ -996,6 +996,10 @@ export class BuilderAPI extends BaseAPI { return this.request('get', '/thirdParties', { params: { manager }, retry: retryParams }) as Promise } + async fetchThirdParty(id: string): Promise { + return this.request('get', `/thirdParties/${id}`) as Promise + } + async fetchThirdPartyAvailableSlots(thirdPartyId: string): Promise { return this.request('get', `/thirdParties/${thirdPartyId}/slots`, { retry: retryParams }) as Promise } diff --git a/src/modules/thirdParty/actions.ts b/src/modules/thirdParty/actions.ts index 3af5faf68..e3adc97e5 100644 --- a/src/modules/thirdParty/actions.ts +++ b/src/modules/thirdParty/actions.ts @@ -22,6 +22,20 @@ export type FetchThirdPartiesRequestAction = ReturnType export type FetchThirdPartiesFailureAction = ReturnType +// Fetch a single third party + +export const FETCH_THIRD_PARTY_REQUEST = '[Request] Fetch Third Party' +export const FETCH_THIRD_PARTY_SUCCESS = '[Success] Fetch Third Party' +export const FETCH_THIRD_PARTY_FAILURE = '[Failure] Fetch Third Party' + +export const fetchThirdPartyRequest = (thirdPartyId: ThirdParty['id']) => action(FETCH_THIRD_PARTY_REQUEST, { thirdPartyId }) +export const fetchThirdPartySuccess = (thirdParty: ThirdParty) => action(FETCH_THIRD_PARTY_SUCCESS, { thirdParty }) +export const fetchThirdPartyFailure = (error: string) => action(FETCH_THIRD_PARTY_FAILURE, { error }) + +export type FetchThirdPartyRequestAction = ReturnType +export type FetchThirdPartySuccessAction = ReturnType +export type FetchThirdPartyFailureAction = ReturnType + // Fetch Third Party Available Slots export const FETCH_THIRD_PARTY_AVAILABLE_SLOTS_REQUEST = '[Request] Fetch Third Party Available Slots' diff --git a/src/modules/thirdParty/reducer.spec.ts b/src/modules/thirdParty/reducer.spec.ts index 10c58fa48..c0d7f5ba2 100644 --- a/src/modules/thirdParty/reducer.spec.ts +++ b/src/modules/thirdParty/reducer.spec.ts @@ -17,7 +17,10 @@ import { disableThirdPartyFailure, publishAndPushChangesThirdPartyItemsRequest, publishAndPushChangesThirdPartyItemsSuccess, - publishAndPushChangesThirdPartyItemsFailure + publishAndPushChangesThirdPartyItemsFailure, + fetchThirdPartyRequest, + fetchThirdPartySuccess, + fetchThirdPartyFailure } from './actions' import { INITIAL_STATE, thirdPartyReducer, ThirdPartyState } from './reducer' import { ThirdParty } from './types' @@ -249,3 +252,64 @@ describe('when reducing a PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_FAILURE act }) }) }) + +describe('when reducing a FETCH_THIRD_PARTY_REQUEST action', () => { + beforeEach(() => { + state = { + ...state, + error: 'Some error', + errors: [new ThirdPartyDeploymentError(mockedItem)] + } + }) + + it('should add the action to the loading array and clear the errors', () => { + expect(thirdPartyReducer(state, fetchThirdPartyRequest('anId'))).toEqual({ + ...INITIAL_STATE, + loading: [fetchThirdPartyRequest('anId')], + error: null, + errors: [] + }) + }) +}) + +describe('when reducing a FETCH_THIRD_PARTY_SUCCESS action', () => { + beforeEach(() => { + state = { + ...state, + loading: [fetchThirdPartyRequest('anId')], + error: 'Some error' + } + }) + + it('should remove the corresponding request action from the loading state, clear the error add the third party to the data', () => { + expect(thirdPartyReducer(state, fetchThirdPartySuccess(thirdParty))).toEqual({ + ...INITIAL_STATE, + data: { + [thirdParty.id]: thirdParty + }, + loading: [], + error: null + }) + }) +}) + +describe('when reducing a FETCH_THIRD_PARTY_FAILURE action', () => { + let error: string + + beforeEach(() => { + error = 'anError' + state = { + ...state, + loading: [fetchThirdPartyRequest('anId')], + data: {} + } + }) + + it('should remove the corresponding request action from the loading state and set the error', () => { + expect(thirdPartyReducer(state, fetchThirdPartyFailure(error))).toEqual({ + ...INITIAL_STATE, + loading: [], + error + }) + }) +}) diff --git a/src/modules/thirdParty/reducer.ts b/src/modules/thirdParty/reducer.ts index 0f1192f1f..da56a8430 100644 --- a/src/modules/thirdParty/reducer.ts +++ b/src/modules/thirdParty/reducer.ts @@ -31,7 +31,13 @@ import { PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_SUCCESS, PublishAndPushChangesThirdPartyItemsFailureAction, PublishAndPushChangesThirdPartyItemsSuccessAction, - PublishAndPushChangesThirdPartyItemsRequestAction + PublishAndPushChangesThirdPartyItemsRequestAction, + FetchThirdPartyRequestAction, + FetchThirdPartySuccessAction, + FetchThirdPartyFailureAction, + FETCH_THIRD_PARTY_REQUEST, + FETCH_THIRD_PARTY_SUCCESS, + FETCH_THIRD_PARTY_FAILURE } from './actions' import { ThirdParty } from './types' @@ -66,11 +72,15 @@ type ThirdPartyReducerAction = | PublishAndPushChangesThirdPartyItemsRequestAction | PublishAndPushChangesThirdPartyItemsSuccessAction | PublishAndPushChangesThirdPartyItemsFailureAction + | FetchThirdPartyRequestAction + | FetchThirdPartySuccessAction + | FetchThirdPartyFailureAction export function thirdPartyReducer(state: ThirdPartyState = INITIAL_STATE, action: ThirdPartyReducerAction): ThirdPartyState { switch (action.type) { case DEPLOY_BATCHED_THIRD_PARTY_ITEMS_REQUEST: case FETCH_THIRD_PARTY_AVAILABLE_SLOTS_REQUEST: + case FETCH_THIRD_PARTY_REQUEST: case PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_REQUEST: case DISABLE_THIRD_PARTY_REQUEST: case FETCH_THIRD_PARTIES_REQUEST: { @@ -96,6 +106,19 @@ export function thirdPartyReducer(state: ThirdPartyState = INITIAL_STATE, action error: null } } + case FETCH_THIRD_PARTY_SUCCESS: { + const { thirdParty } = action.payload + return { + ...state, + data: { + ...state.data, + [thirdParty.id]: thirdParty + }, + loading: loadingReducer(state.loading, action), + error: null + } + } + case DISABLE_THIRD_PARTY_SUCCESS: { const { thirdPartyId } = action.payload return { @@ -132,6 +155,7 @@ export function thirdPartyReducer(state: ThirdPartyState = INITIAL_STATE, action error: null } } + case FETCH_THIRD_PARTY_FAILURE: case DISABLE_THIRD_PARTY_FAILURE: case FETCH_THIRD_PARTY_AVAILABLE_SLOTS_FAILURE: case PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_FAILURE: diff --git a/src/modules/thirdParty/sagas.spec.ts b/src/modules/thirdParty/sagas.spec.ts index be9a7be4f..88197dc16 100644 --- a/src/modules/thirdParty/sagas.spec.ts +++ b/src/modules/thirdParty/sagas.spec.ts @@ -40,7 +40,10 @@ import { deployBatchedThirdPartyItemsSuccess, disableThirdPartyFailure, disableThirdPartyRequest, - disableThirdPartySuccess + disableThirdPartySuccess, + fetchThirdPartyFailure, + fetchThirdPartyRequest, + fetchThirdPartySuccess } from './actions' import { mockedItem } from 'specs/item' import { getCollection } from 'modules/collection/selectors' @@ -74,6 +77,7 @@ jest.mock('@dcl/crypto') const mockBuilder = { fetchThirdParties: jest.fn(), + fetchThirdParty: jest.fn(), fetchThirdPartyAvailableSlots: jest.fn(), publishTPCollection: jest.fn(), pushItemCuration: jest.fn(), @@ -204,6 +208,33 @@ describe('when fetching third parties', () => { }) }) +describe('when fetching a third party', () => { + describe('when the api request fails', () => { + let errorMessage: string + beforeEach(() => { + errorMessage = 'Some Error Message' + }) + + it('should put the fetch third party fail action with an error', () => { + return expectSaga(thirdPartySaga, mockBuilder, mockCatalystClient) + .provide([[matchers.call.fn(mockBuilder.fetchThirdParty), throwError(new Error(errorMessage))]]) + .put(fetchThirdPartyFailure(errorMessage)) + .dispatch(fetchThirdPartyRequest('aThirdPartyId')) + .run({ silenceTimeout: true }) + }) + }) + + describe('when the api request succeeds', () => { + it('should put the fetch third party success action with the api response', () => { + return expectSaga(thirdPartySaga, mockBuilder, mockCatalystClient) + .provide([[matchers.call.fn(mockBuilder.fetchThirdParty), thirdParty]]) + .put(fetchThirdPartySuccess(thirdParty)) + .dispatch(fetchThirdPartyRequest(thirdParty.id)) + .run({ silenceTimeout: true }) + }) + }) +}) + describe('when fetching third party available slots', () => { describe('when the api request fails', () => { let errorMessage: string diff --git a/src/modules/thirdParty/sagas.ts b/src/modules/thirdParty/sagas.ts index 4714fa44d..ca4ba920c 100644 --- a/src/modules/thirdParty/sagas.ts +++ b/src/modules/thirdParty/sagas.ts @@ -78,7 +78,11 @@ import { DISABLE_THIRD_PARTY_REQUEST, DisableThirdPartyRequestAction, disableThirdPartyFailure, - disableThirdPartySuccess + disableThirdPartySuccess, + FETCH_THIRD_PARTY_REQUEST, + FetchThirdPartyRequestAction, + fetchThirdPartySuccess, + fetchThirdPartyFailure } from './actions' import { convertThirdPartyMetadataToRawMetadata, getPublishItemsSignature } from './utils' import { Cheque, ThirdParty } from './types' @@ -99,6 +103,7 @@ export function* thirdPartySaga(builder: BuilderAPI, catalystClient: CatalystCli yield takeLatest(LOGIN_SUCCESS, handleLoginSuccess) yield takeLatest(DEPLOY_BATCHED_THIRD_PARTY_ITEMS_REQUEST, handleDeployBatchedThirdPartyItemsRequest) yield takeEvery(FETCH_THIRD_PARTIES_REQUEST, handleFetchThirdPartiesRequest) + yield takeEvery(FETCH_THIRD_PARTY_REQUEST, handleFetchThirdPartyRequest) yield takeEvery(FETCH_THIRD_PARTY_AVAILABLE_SLOTS_REQUEST, handleFetchThirdPartyAvailableSlots) yield takeEvery(PUBLISH_THIRD_PARTY_ITEMS_REQUEST, handlePublishThirdPartyItemRequest) yield takeEvery(PUSH_CHANGES_THIRD_PARTY_ITEMS_REQUEST, handlePushChangesThirdPartyItemRequest) @@ -132,6 +137,16 @@ export function* thirdPartySaga(builder: BuilderAPI, catalystClient: CatalystCli } } + function* handleFetchThirdPartyRequest(action: FetchThirdPartyRequestAction) { + const { thirdPartyId } = action.payload + try { + const thirdParty: ThirdParty = yield call([builder, 'fetchThirdParty'], thirdPartyId) + yield put(fetchThirdPartySuccess(thirdParty)) + } catch (error) { + yield put(fetchThirdPartyFailure(isErrorWithMessage(error) ? error.message : 'Unknown error')) + } + } + function* handleFetchThirdPartyAvailableSlots(action: FetchThirdPartyAvailableSlotsRequestAction) { const { thirdPartyId } = action.payload try { diff --git a/src/modules/thirdParty/selectors.spec.ts b/src/modules/thirdParty/selectors.spec.ts index 30f68abbd..1e77fc1ff 100644 --- a/src/modules/thirdParty/selectors.spec.ts +++ b/src/modules/thirdParty/selectors.spec.ts @@ -5,6 +5,7 @@ import { DISABLE_THIRD_PARTY_SUCCESS, disableThirdPartyRequest, fetchThirdPartiesRequest, + fetchThirdPartyRequest, publishAndPushChangesThirdPartyItemsRequest } from './actions' import { @@ -17,7 +18,8 @@ import { getThirdParty, isDisablingThirdParty, hasPendingDisableThirdPartyTransaction, - isPublishingAndPushingChanges + isPublishingAndPushingChanges, + isLoadingThirdParty } from './selectors' import { ThirdParty } from './types' @@ -312,6 +314,62 @@ describe('Third Party selectors', () => { }) }) + describe('when getting if a third party is being loaded', () => { + let state: RootState + let thirdPartyId: string + beforeEach(() => { + thirdPartyId = 'aThirdPartyId' + }) + + describe('and the third party is being loaded', () => { + beforeEach(() => { + state = { + ...baseState, + thirdParty: { + ...baseState.thirdParty, + loading: [fetchThirdPartyRequest(thirdPartyId)] + } + } + }) + + it('should return true', () => { + expect(isLoadingThirdParty(state, thirdPartyId)).toBe(true) + }) + }) + + describe('and another third party is being loaded', () => { + beforeEach(() => { + state = { + ...baseState, + thirdParty: { + ...baseState.thirdParty, + loading: [fetchThirdPartyRequest('anotherId')] + } + } + }) + + it('should return false', () => { + expect(isLoadingThirdParty(state, thirdPartyId)).toBe(false) + }) + }) + + describe('and no third party is not being loaded', () => { + beforeEach(() => { + state = { + ...baseState, + thirdParty: { + ...baseState.thirdParty, + loading: [] + } + } + }) + + it('should return false', () => { + expect(isLoadingThirdParty(state, thirdPartyId)).toBe(false) + }) + }) + }) + describe('when getting a third party', () => { describe('and the third party exists', () => { let state: RootState diff --git a/src/modules/thirdParty/selectors.ts b/src/modules/thirdParty/selectors.ts index 680046f10..961caaa3f 100644 --- a/src/modules/thirdParty/selectors.ts +++ b/src/modules/thirdParty/selectors.ts @@ -1,3 +1,4 @@ +import { AnyAction } from 'redux-saga' import { createSelector } from 'reselect' import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors' @@ -13,7 +14,9 @@ import { FETCH_THIRD_PARTIES_REQUEST, DISABLE_THIRD_PARTY_REQUEST, FETCH_THIRD_PARTY_AVAILABLE_SLOTS_REQUEST, - PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_REQUEST + PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_REQUEST, + FETCH_THIRD_PARTY_REQUEST, + FetchThirdPartyRequestAction } from './actions' import { getThirdPartyForCollection, getThirdPartyForItem, isUserManagerOfThirdParty } from './utils' @@ -59,9 +62,16 @@ export const getCollectionThirdParty = (state: RootState, collection: Collection export const getItemThirdParty = (state: RootState, item: Item): ThirdParty | null => getThirdPartyForItem(getData(state), item) ?? null export const isLoadingThirdParties = (state: RootState): boolean => isLoadingType(getLoading(state), FETCH_THIRD_PARTIES_REQUEST) +export const isLoadingThirdParty = (state: RootState, id: ThirdParty['id']): boolean => + getLoading(state) + .filter(isFetchThirdPartyRequestAction) + .some(action => action.payload.thirdPartyId === id) export const isFetchingAvailableSlots = (state: RootState): boolean => isLoadingType(getLoading(state), FETCH_THIRD_PARTY_AVAILABLE_SLOTS_REQUEST) export const isDeployingBatchedThirdPartyItems = (state: RootState): boolean => isLoadingType(getLoading(state), DEPLOY_BATCHED_THIRD_PARTY_ITEMS_REQUEST) + +const isFetchThirdPartyRequestAction = (action: AnyAction): action is FetchThirdPartyRequestAction => + action.type === FETCH_THIRD_PARTY_REQUEST