diff --git a/src/components/ActivityPage/Transaction/Transaction.tsx b/src/components/ActivityPage/Transaction/Transaction.tsx index 9715ab6c7..6beb35393 100644 --- a/src/components/ActivityPage/Transaction/Transaction.tsx +++ b/src/components/ActivityPage/Transaction/Transaction.tsx @@ -35,6 +35,7 @@ import { RECLAIM_NAME_SUCCESS, CLAIM_NAME_TRANSACTION_SUBMITTED } from 'modules/ens/actions' +import { DISABLE_THIRD_PARTY_SUCCESS } from 'modules/thirdParty/actions' import { getSaleAddress, getTotalAmountOfMintedItems } from 'modules/collection/utils' import { isEnoughClaimMana } from 'modules/ens/utils' import { includes } from 'lib/address' @@ -212,6 +213,23 @@ const Transaction = (props: Props) => { /> ) } + case DISABLE_THIRD_PARTY_SUCCESS: { + const { thirdPartyId, thirdPartyName } = tx.payload + return ( + + } + tx={tx} + /> + ) + } case PUBLISH_COLLECTION_SUCCESS: case APPROVE_COLLECTION_SUCCESS: case REJECT_COLLECTION_SUCCESS: diff --git a/src/components/ActivityPage/Transaction/TransactionDetail/TransactionDetail.tsx b/src/components/ActivityPage/Transaction/TransactionDetail/TransactionDetail.tsx index e3b43c6bf..56e487e3d 100644 --- a/src/components/ActivityPage/Transaction/TransactionDetail/TransactionDetail.tsx +++ b/src/components/ActivityPage/Transaction/TransactionDetail/TransactionDetail.tsx @@ -9,6 +9,7 @@ import { Atlas } from 'components/Atlas' import CollectionImage from 'components/CollectionImage' import ItemImage from 'components/ItemImage' import Profile from 'components/Profile' +import { ThirdPartyImage } from 'components/ThirdPartyImage' import { ENSImage } from '../ENSImage' import { Props } from './TransactionDetail.types' import './TransactionDetail.css' @@ -22,7 +23,7 @@ const getHref = (tx: Transaction) => { } const Image = (props: Props) => { - const { selection, address, collectionId, item, subdomain, slotsToyBuy } = props + const { selection, address, collectionId, item, subdomain, slotsToyBuy, thirdPartyId } = props const set = useMemo(() => new Set((selection || []).map(coord => coordsToId(coord.x, coord.y))), [selection]) const selectedStrokeLayer: Layer = useCallback((x, y) => (set.has(coordsToId(x, y)) ? { color: '#ff0044', scale: 1.4 } : null), [set]) @@ -41,6 +42,8 @@ const Image = (props: Props) => { return } else if (slotsToyBuy) { return
+ } else if (thirdPartyId) { + return } else { return null } diff --git a/src/components/ActivityPage/Transaction/TransactionDetail/TransactionDetail.types.ts b/src/components/ActivityPage/Transaction/TransactionDetail/TransactionDetail.types.ts index 869a1a1c2..d25e20f66 100644 --- a/src/components/ActivityPage/Transaction/TransactionDetail/TransactionDetail.types.ts +++ b/src/components/ActivityPage/Transaction/TransactionDetail/TransactionDetail.types.ts @@ -9,6 +9,7 @@ export type Props = { item?: Item subdomain?: string slotsToyBuy?: number + thirdPartyId?: string text: React.ReactNode tx: Transaction } diff --git a/src/components/CollectionsPage/CollectionsPage.tsx b/src/components/CollectionsPage/CollectionsPage.tsx index c32cd4260..6f7cfd71d 100644 --- a/src/components/CollectionsPage/CollectionsPage.tsx +++ b/src/components/CollectionsPage/CollectionsPage.tsx @@ -213,7 +213,7 @@ export default function CollectionsPage(props: Props) { } iconPosition="left" diff --git a/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.container.ts b/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.container.ts index fc1b90db3..672b78ba5 100644 --- a/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.container.ts +++ b/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.container.ts @@ -5,19 +5,25 @@ import { rejectCollectionRequest, REJECT_COLLECTION_REQUEST } from 'modules/coll import { getLoading as getLoadingCollection, hasPendingCurationTransaction } from 'modules/collection/selectors' import { getLoading as getLoadingCuration } from 'modules/curations/collectionCuration/selectors' import { rejectCollectionCurationRequest, REJECT_COLLECTION_CURATION_REQUEST } from 'modules/curations/collectionCuration/actions' -import { MapStateProps, MapDispatchProps, MapDispatch } from './RejectionModal.types' +import { hasPendingDisableThirdPartyTransaction, isDisablingThirdParty } from 'modules/thirdParty/selectors' +import { disableThirdPartyRequest } from 'modules/thirdParty/actions' +import { MapStateProps, MapDispatchProps, MapDispatch, OwnProps } from './RejectionModal.types' import RejectionModal from './RejectionModal' -const mapState = (state: RootState): MapStateProps => ({ +const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => ({ isLoading: isLoadingType(getLoadingCollection(state), REJECT_COLLECTION_REQUEST) || - isLoadingType(getLoadingCuration(state), REJECT_COLLECTION_CURATION_REQUEST), - hasPendingTransaction: hasPendingCurationTransaction(state) + isLoadingType(getLoadingCuration(state), REJECT_COLLECTION_CURATION_REQUEST) || + isDisablingThirdParty(state), + hasPendingTransaction: + hasPendingCurationTransaction(state) || + Boolean(ownProps.thirdParty && hasPendingDisableThirdPartyTransaction(state, ownProps.thirdParty.id)) }) const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({ onReject: collection => dispatch(rejectCollectionRequest(collection)), - onRejectCuration: collection => dispatch(rejectCollectionCurationRequest(collection)) + onRejectCuration: collection => dispatch(rejectCollectionCurationRequest(collection)), + onDisableThirdParty: (id: string) => dispatch(disableThirdPartyRequest(id)) }) export default connect(mapState, mapDispatch)(RejectionModal) diff --git a/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.tsx b/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.tsx index e163fe107..b918312bf 100644 --- a/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.tsx +++ b/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.tsx @@ -16,7 +16,9 @@ const RejectionModal = ({ open, type, isLoading, + thirdParty, hasPendingTransaction, + onDisableThirdParty, collection, curation, onReject, @@ -29,6 +31,8 @@ const RejectionModal = ({ return i18nBase + '.disable_collection' case RejectionType.REJECT_CURATION: return i18nBase + '.reject_curation' + case RejectionType.DISABLE_THIRD_PARTY: + return i18nBase + '.disable_third_party' default: return '' } @@ -42,12 +46,18 @@ const RejectionModal = ({ case RejectionType.REJECT_CURATION: onRejectCuration(curation!.collectionId) break + case RejectionType.DISABLE_THIRD_PARTY: + if (thirdParty) { + onDisableThirdParty(thirdParty.id) + } + break default: } } - const shouldShowVeredict = + const shouldShowVerdict = (type === RejectionType.DISABLE_COLLECTION && !collection.isApproved) || + (type === RejectionType.DISABLE_THIRD_PARTY && !thirdParty?.isApproved) || (type === RejectionType.REJECT_COLLECTION && !collection.isApproved) || (type === RejectionType.REJECT_CURATION && curation?.status === CurationStatus.REJECTED) @@ -55,8 +65,8 @@ const RejectionModal = ({ {hasPendingTransaction ? ( - ) : shouldShowVeredict ? ( - + ) : shouldShowVerdict ? ( + ) : ( )} @@ -82,7 +92,7 @@ const PendingTransaction = ({ i18nKey }: { i18nKey: string }) => ( ) -const Veredict = ({ link, onClose }: { link?: string; onClose: () => void }) => ( +const Verdict = ({ link, onClose }: { link?: string; onClose: () => void }) => ( <> {t(i18nBase + '.veredict_explanation')} {t(i18nBase + '.go_to_forum')} @@ -119,7 +129,9 @@ const Confirmation = ({ {t(`${i18nKey}.action`)} - + ) diff --git a/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.types.ts b/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.types.ts index f9bde58c0..e082fdf46 100644 --- a/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.types.ts +++ b/src/components/ItemEditorPage/TopPanel/RejectionModal/RejectionModal.types.ts @@ -3,11 +3,14 @@ import { InitiateApprovalFlowAction, rejectCollectionRequest, RejectCollectionRe import { Collection } from 'modules/collection/types' import { rejectCollectionCurationRequest, RejectCollectionCurationRequestAction } from 'modules/curations/collectionCuration/actions' import { CollectionCuration } from 'modules/curations/collectionCuration/types' +import { disableThirdPartyRequest, DisableThirdPartyRequestAction } from 'modules/thirdParty/actions' +import { ThirdParty } from 'modules/thirdParty/types' export enum RejectionType { REJECT_COLLECTION = 'REJECT_COLLECTION', REJECT_CURATION = 'REJECT_CURATION', - DISABLE_COLLECTION = 'DISABLE_COLLECTION' + DISABLE_COLLECTION = 'DISABLE_COLLECTION', + DISABLE_THIRD_PARTY = 'DISABLE_THIRD_PARTY' } export type Props = { @@ -17,12 +20,16 @@ export type Props = { hasPendingTransaction: boolean collection: Collection curation: CollectionCuration | null + thirdParty: ThirdParty | null onReject: typeof rejectCollectionRequest + onDisableThirdParty: (...args: Parameters) => unknown onRejectCuration: typeof rejectCollectionCurationRequest onClose: () => void } export type MapStateProps = Pick -export type MapDispatchProps = Pick -export type MapDispatch = Dispatch -export type OwnProps = Pick +export type MapDispatchProps = Pick +export type MapDispatch = Dispatch< + RejectCollectionRequestAction | RejectCollectionCurationRequestAction | InitiateApprovalFlowAction | DisableThirdPartyRequestAction +> +export type OwnProps = Pick diff --git a/src/components/ItemEditorPage/TopPanel/TopPanel.container.ts b/src/components/ItemEditorPage/TopPanel/TopPanel.container.ts index bfbc445cd..efe32b186 100644 --- a/src/components/ItemEditorPage/TopPanel/TopPanel.container.ts +++ b/src/components/ItemEditorPage/TopPanel/TopPanel.container.ts @@ -6,8 +6,6 @@ import { isWalletCommitteeMember } from 'modules/committee/selectors' import { getSelectedCollectionId, isReviewing } from 'modules/location/selectors' import { setCollectionCurationAssigneeRequest } from 'modules/curations/collectionCuration/actions' import { FETCH_COLLECTION_REQUEST, initiateApprovalFlow, initiateTPApprovalFlow } from 'modules/collection/actions' -import { MapStateProps, MapDispatchProps, MapDispatch } from './TopPanel.types' -import TopPanel from './TopPanel' import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors' import { getCollection } from 'modules/collection/selectors' import { FETCH_ITEM_CURATIONS_REQUEST } from 'modules/curations/itemCuration/actions' @@ -15,6 +13,10 @@ import { FETCH_COLLECTION_ITEMS_REQUEST } from 'modules/item/actions' import { getCollectionItems, getLoading as getLoadingItems, getPaginationData } from 'modules/item/selectors' import { getCuration } from 'modules/curations/collectionCuration/selectors' import { getItemCurations, getLoading as getLoadingItemCurations } from 'modules/curations/itemCuration/selectors' +import { getCollectionThirdParty } from 'modules/thirdParty/selectors' +import { isTPCollection } from 'modules/collection/utils' +import { MapStateProps, MapDispatchProps, MapDispatch } from './TopPanel.types' +import TopPanel from './TopPanel' const mapState = (state: RootState): MapStateProps => { const selectedCollectionId = getSelectedCollectionId(state) @@ -23,12 +25,15 @@ const mapState = (state: RootState): MapStateProps => { const itemCurations = collection ? getItemCurations(state, collection.id) : [] const curation = selectedCollectionId ? getCuration(state, selectedCollectionId) : null const itemsPaginationData = selectedCollectionId ? getPaginationData(state, selectedCollectionId) : undefined + const thirdParty = collection && isTPCollection(collection) ? getCollectionThirdParty(state, collection) : null + return { address: getAddress(state), items, totalItems: itemsPaginationData?.total || null, collection, itemCurations, + thirdParty, curation, chainId: getChainId(state), isConnected: isConnected(state), diff --git a/src/components/ItemEditorPage/TopPanel/TopPanel.tsx b/src/components/ItemEditorPage/TopPanel/TopPanel.tsx index 4b423c967..44ca41108 100644 --- a/src/components/ItemEditorPage/TopPanel/TopPanel.tsx +++ b/src/components/ItemEditorPage/TopPanel/TopPanel.tsx @@ -35,7 +35,7 @@ export default class TopPanel extends React.PureComponent { setShowRejectionModal = (showRejectionModal: RejectionType | null) => this.setState({ showRejectionModal }) renderPage = (collection: Collection) => { - const { items, itemCurations, curation, totalItems } = this.props + const { items, itemCurations, curation, totalItems, thirdParty } = this.props const { showRejectionModal, showApproveConfirmModal } = this.state const { chainId } = this.props const type = getCollectionType(collection) @@ -71,6 +71,7 @@ export default class TopPanel extends React.PureComponent { open={true} collection={collection} curation={curation} + thirdParty={thirdParty} onClose={() => this.setShowRejectionModal(null)} /> )} @@ -87,7 +88,7 @@ export default class TopPanel extends React.PureComponent { } renderButton = (type: ButtonType, collection: Collection, curation: CollectionCuration | null) => { - const { address, onInitiateApprovalFlow, onInitiateTPApprovalFlow, reviewedItems, totalItems } = this.props + const { address, thirdParty, onInitiateApprovalFlow, onInitiateTPApprovalFlow, reviewedItems, totalItems } = this.props const onClickMap = { [ButtonType.APPROVE]: () => @@ -97,7 +98,8 @@ export default class TopPanel extends React.PureComponent { ? onInitiateTPApprovalFlow(collection) : onInitiateApprovalFlow(collection), [ButtonType.ENABLE]: () => onInitiateApprovalFlow(collection), - [ButtonType.DISABLE]: () => this.setShowRejectionModal(RejectionType.DISABLE_COLLECTION), + [ButtonType.DISABLE]: () => + this.setShowRejectionModal(thirdParty ? RejectionType.DISABLE_THIRD_PARTY : RejectionType.DISABLE_COLLECTION), [ButtonType.REJECT]: () => { if (curation?.status === CurationStatus.PENDING) { this.setShowRejectionModal(RejectionType.REJECT_CURATION) @@ -147,18 +149,20 @@ export default class TopPanel extends React.PureComponent { } renderTPButtons = (collection: Collection, collectionCuration: CollectionCuration | null, itemCurations: ItemCuration[] | null) => { - const { reviewedItems, totalItems } = this.props - const shouldShowApproveButton = itemCurations?.some(itemCuration => itemCuration.status === CurationStatus.PENDING) + const { reviewedItems, totalItems, thirdParty } = this.props + const areCurationsPending = itemCurations?.some(itemCuration => itemCuration.status === CurationStatus.PENDING) return ( <>
- {t('item_editor.top_panel.reviewed_counter', { count: reviewedItems.length, threshold: getTPThresholdToReview(totalItems!) })} + {areCurationsPending + ? t('item_editor.top_panel.reviewed_counter', { count: reviewedItems.length, threshold: getTPThresholdToReview(totalItems!) }) + : t('item_editor.top_panel.not_enough_items_to_curate_more')} {reviewedItems.length >= getTPThresholdToReview(totalItems!) ? : null}
- {shouldShowApproveButton ? this.renderButton(ButtonType.APPROVE, collection, collectionCuration) : null} - {this.renderButton(ButtonType.REJECT, collection, collectionCuration)} - {/* TODO: the disable button from below is not the same as the original disable one, it will be implemented once the sagas are ready */} - {/* {this.renderButton(ButtonType.DISABLE, collection, collectionCuration)} */} + {areCurationsPending && this.renderButton(ButtonType.APPROVE, collection, collectionCuration)} + {areCurationsPending && this.renderButton(ButtonType.REJECT, collection, collectionCuration)} + {!areCurationsPending && thirdParty?.isApproved && this.renderButton(ButtonType.DISABLE, collection, null)} + {/* TODO: Add when enabling is possible {!areCurationsPending && !thirdParty?.isApproved && thirdParty?.root && this.renderButton(ButtonType.ENABLE, collection, null)} */} ) } diff --git a/src/components/ItemEditorPage/TopPanel/TopPanel.types.ts b/src/components/ItemEditorPage/TopPanel/TopPanel.types.ts index ba5a2b27d..2925877ed 100644 --- a/src/components/ItemEditorPage/TopPanel/TopPanel.types.ts +++ b/src/components/ItemEditorPage/TopPanel/TopPanel.types.ts @@ -12,16 +12,18 @@ import { setCollectionCurationAssigneeRequest, SetCollectionCurationAssigneeRequestAction } from 'modules/curations/collectionCuration/actions' -import { RejectionType } from './RejectionModal/RejectionModal.types' import { Collection } from 'modules/collection/types' import { CollectionCuration } from 'modules/curations/collectionCuration/types' import { ItemCuration } from 'modules/curations/itemCuration/types' +import { ThirdParty } from 'modules/thirdParty/types' +import { RejectionType } from './RejectionModal/RejectionModal.types' export type Props = { items: Item[] collection: Collection | null curation: CollectionCuration | null itemCurations: ItemCuration[] | null + thirdParty: ThirdParty | null isLoading: boolean reviewedItems: Item[] totalItems: number | null @@ -57,6 +59,7 @@ export type MapStateProps = Pick< | 'collection' | 'curation' | 'itemCurations' + | 'thirdParty' | 'isLoading' | 'address' | 'chainId' diff --git a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.module.css b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.module.css index 6f7156a8a..9c451de0e 100644 --- a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.module.css +++ b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.module.css @@ -211,8 +211,3 @@ margin: auto; margin-bottom: 20px; } - -.thirdPartyLogo { - width: 48px; - height: 48px; -} diff --git a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.tsx b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.tsx index 215169aaf..9cd785bcf 100644 --- a/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.tsx +++ b/src/components/ThirdPartyCollectionDetailPage/ThirdPartyCollectionDetailPage.tsx @@ -10,8 +10,7 @@ import { Loader, Dropdown, DropdownProps, - InfoTooltip, - Blockie + InfoTooltip } from 'decentraland-ui' import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { getArrayOfPagesFromTotal } from 'lib/api/pagination' @@ -24,6 +23,7 @@ import LoggedInDetailPage from 'components/LoggedInDetailPage' import CollectionProvider from 'components/CollectionProvider' import NotFound from 'components/NotFound' import BuilderIcon from 'components/Icon' +import { ThirdPartyImage } from 'components/ThirdPartyImage' import Back from 'components/Back' import { shorten } from 'lib/address' import { CopyToClipboard } from 'components/CopyToClipboard' @@ -198,7 +198,7 @@ export default function ThirdPartyCollectionDetailPage({
- +
{collection.name}
diff --git a/src/components/ThirdPartyImage/ThirdPartyImage.module.css b/src/components/ThirdPartyImage/ThirdPartyImage.module.css new file mode 100644 index 000000000..30395d61e --- /dev/null +++ b/src/components/ThirdPartyImage/ThirdPartyImage.module.css @@ -0,0 +1,4 @@ +.main { + width: 48px; + height: 48px; +} diff --git a/src/components/ThirdPartyImage/ThirdPartyImage.tsx b/src/components/ThirdPartyImage/ThirdPartyImage.tsx new file mode 100644 index 000000000..0063ec75c --- /dev/null +++ b/src/components/ThirdPartyImage/ThirdPartyImage.tsx @@ -0,0 +1,9 @@ +import { Blockie } from 'decentraland-ui' +import classNames from 'classnames' +import styles from './ThirdPartyImage.module.css' +import { Props } from './ThirdPartyImage.types' + +export const ThirdPartyImage = (props: Props) => { + const { thirdPartyId, className, shape } = props + return +} diff --git a/src/components/ThirdPartyImage/ThirdPartyImage.types.ts b/src/components/ThirdPartyImage/ThirdPartyImage.types.ts new file mode 100644 index 000000000..a866ade10 --- /dev/null +++ b/src/components/ThirdPartyImage/ThirdPartyImage.types.ts @@ -0,0 +1,5 @@ +export type Props = { + className?: string + thirdPartyId: string + shape?: 'circle' | 'square' +} diff --git a/src/components/ThirdPartyImage/index.ts b/src/components/ThirdPartyImage/index.ts new file mode 100644 index 000000000..130c7a89e --- /dev/null +++ b/src/components/ThirdPartyImage/index.ts @@ -0,0 +1,2 @@ +export * from './ThirdPartyImage' +export * from './ThirdPartyImage.types' diff --git a/src/modules/collection/selectors.spec.ts b/src/modules/collection/selectors.spec.ts index e05bcdd71..4e76745c8 100644 --- a/src/modules/collection/selectors.spec.ts +++ b/src/modules/collection/selectors.spec.ts @@ -223,6 +223,8 @@ describe('when getting the authorized collections', () => { [thirdPartyId]: { id: thirdPartyId, managers: [address], + root: '', + isApproved: true, contracts: [], name: 'aName', description: 'aDescription', diff --git a/src/modules/curations/itemCuration/reducer.spec.ts b/src/modules/curations/itemCuration/reducer.spec.ts index 282e8b98e..0a4420697 100644 --- a/src/modules/curations/itemCuration/reducer.spec.ts +++ b/src/modules/curations/itemCuration/reducer.spec.ts @@ -42,6 +42,8 @@ describe('when an action of type PUBLISH_THIRD_PARTY_ITEMS_REQUEST is called', ( thirdParty = { id: '1', name: 'a third party', + root: '', + isApproved: true, description: 'some desc', managers: ['0x1', '0x2'], contracts: [], @@ -72,6 +74,8 @@ describe('when an action of type PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_REQU thirdParty = { id: '1', name: 'a third party', + root: '', + isApproved: true, description: 'some desc', managers: ['0x1', '0x2'], contracts: [], @@ -99,6 +103,8 @@ describe('when an action of type PUBLISH_THIRD_PARTY_ITEMS_SUCCESS is called', ( thirdParty = { id: '1', name: 'a third party', + root: '', + isApproved: true, description: 'some desc', managers: ['0x1', '0x2'], contracts: [], @@ -182,6 +188,8 @@ describe('when an action of type PUBLISH_THIRD_PARTY_ITEMS_FAILURE is called', ( thirdParty = { id: '1', name: 'a third party', + root: '', + isApproved: true, description: 'some desc', managers: ['0x1', '0x2'], contracts: [], @@ -208,6 +216,8 @@ describe('when an action of type PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_FAIL thirdParty = { id: '1', name: 'a third party', + root: '', + isApproved: true, description: 'some desc', managers: ['0x1', '0x2'], contracts: [], diff --git a/src/modules/thirdParty/actions.ts b/src/modules/thirdParty/actions.ts index eef8377d4..fdd1ea5b1 100644 --- a/src/modules/thirdParty/actions.ts +++ b/src/modules/thirdParty/actions.ts @@ -151,3 +151,18 @@ export const deployBatchedThirdPartyItemsFailure = (errors: ThirdPartyError[], e export type DeployBatchedThirdPartyItemsRequestAction = ReturnType export type DeployBatchedThirdPartyItemsSuccessAction = ReturnType export type DeployBatchedThirdPartyItemsFailureAction = ReturnType + +// Disable Third Party + +export const DISABLE_THIRD_PARTY_REQUEST = '[Request] Disable Third Party' +export const DISABLE_THIRD_PARTY_SUCCESS = '[Success] Disable Third Party' +export const DISABLE_THIRD_PARTY_FAILURE = '[Failure] Disable Third Party' + +export const disableThirdPartyRequest = (thirdPartyId: ThirdParty['id']) => action(DISABLE_THIRD_PARTY_REQUEST, { thirdPartyId }) +export const disableThirdPartySuccess = (thirdPartyId: ThirdParty['id'], chainId: ChainId, txHash: string, thirdPartyName: string) => + action(DISABLE_THIRD_PARTY_SUCCESS, { thirdPartyId, ...buildTransactionPayload(chainId, txHash, { thirdPartyId, thirdPartyName }) }) +export const disableThirdPartyFailure = (error: string) => action(DISABLE_THIRD_PARTY_FAILURE, { error }) + +export type DisableThirdPartyRequestAction = ReturnType +export type DisableThirdPartySuccessAction = ReturnType +export type DisableThirdPartyFailureAction = ReturnType diff --git a/src/modules/thirdParty/reducer.spec.ts b/src/modules/thirdParty/reducer.spec.ts index aa074352b..28294aeac 100644 --- a/src/modules/thirdParty/reducer.spec.ts +++ b/src/modules/thirdParty/reducer.spec.ts @@ -10,10 +10,14 @@ import { DEPLOY_BATCHED_THIRD_PARTY_ITEMS_SUCCESS, deployBatchedThirdPartyItemsSuccess, deployBatchedThirdPartyItemsFailure, - DEPLOY_BATCHED_THIRD_PARTY_ITEMS_FAILURE + DEPLOY_BATCHED_THIRD_PARTY_ITEMS_FAILURE, + disableThirdPartyRequest, + disableThirdPartySuccess, + disableThirdPartyFailure } from './actions' import { INITIAL_STATE, thirdPartyReducer, ThirdPartyState } from './reducer' import { ThirdParty } from './types' +import { ChainId } from '@dcl/schemas' describe('when an action of type FETCH_THIRD_PARTIES_REQUEST is called', () => { it('should add a fetchThirdPartiesRequest to the loading array', () => { @@ -29,6 +33,8 @@ describe('when an action of type FETCH_THIRD_PARTIES_SUCCESS is called', () => { beforeEach(() => { thirdParty = { id: '1', + root: '', + isApproved: true, name: 'a third party', description: 'some desc', managers: ['0x1', '0x2'], @@ -115,3 +121,85 @@ describe('when reducing an DEPLOY_BATCHED_THIRD_PARTY_ITEMS_FAILURE action', () }) }) }) + +describe('when reducing a DISABLE_THIRD_PARTY_REQUEST action', () => { + let state: ThirdPartyState + + beforeEach(() => { + state = { + ...INITIAL_STATE, + error: 'Some error', + errors: [new ThirdPartyDeploymentError(mockedItem)] + } + }) + + it('should add the action to the loading array and clear the errors', () => { + expect(thirdPartyReducer(state, disableThirdPartyRequest('anId'))).toStrictEqual({ + ...INITIAL_STATE, + loading: [disableThirdPartyRequest('anId')], + error: null, + errors: [] + }) + }) +}) + +describe('when reducing a DISABLE_THIRD_PARTY_SUCCESS action', () => { + let state: ThirdPartyState + let thirdParty: ThirdParty + + beforeEach(() => { + thirdParty = { + id: 'anId', + root: '', + isApproved: true, + name: 'a third party', + description: 'some desc', + managers: ['0x1', '0x2'], + contracts: [], + maxItems: '0', + totalItems: '0' + } + state = { + ...INITIAL_STATE, + loading: [disableThirdPartyRequest('anId')], + data: { + anId: thirdParty + } + } + }) + + it('should remove the corresponding request action from the loading state and set the third party as not approved', () => { + expect(thirdPartyReducer(state, disableThirdPartySuccess('anId', ChainId.MATIC_MAINNET, 'aTxHash', 'thirdPartyName'))).toStrictEqual({ + ...INITIAL_STATE, + data: { + anId: { + ...thirdParty, + isApproved: false + } + }, + loading: [] + }) + }) +}) + +describe('when reducing a DISABLE_THIRD_PARTY_FAILURE action', () => { + let state: ThirdPartyState + let error: string + + beforeEach(() => { + error = 'anError' + state = { + ...INITIAL_STATE, + loading: [disableThirdPartyRequest('anId')], + data: {} + } + }) + + it('should remove the corresponding request action from the loading state and set the error', () => { + expect(thirdPartyReducer(state, disableThirdPartyFailure(error))).toStrictEqual({ + ...INITIAL_STATE, + loading: [], + error + }) + }) +}) diff --git a/src/modules/thirdParty/reducer.ts b/src/modules/thirdParty/reducer.ts index 22caa8fa0..889e2c040 100644 --- a/src/modules/thirdParty/reducer.ts +++ b/src/modules/thirdParty/reducer.ts @@ -19,7 +19,13 @@ import { DeployBatchedThirdPartyItemsFailureAction, DEPLOY_BATCHED_THIRD_PARTY_ITEMS_REQUEST, DEPLOY_BATCHED_THIRD_PARTY_ITEMS_FAILURE, - DEPLOY_BATCHED_THIRD_PARTY_ITEMS_SUCCESS + DEPLOY_BATCHED_THIRD_PARTY_ITEMS_SUCCESS, + DisableThirdPartySuccessAction, + DisableThirdPartyFailureAction, + DisableThirdPartyRequestAction, + DISABLE_THIRD_PARTY_SUCCESS, + DISABLE_THIRD_PARTY_REQUEST, + DISABLE_THIRD_PARTY_FAILURE } from './actions' import { ThirdParty } from './types' @@ -50,11 +56,15 @@ type ThirdPartyReducerAction = | DeployBatchedThirdPartyItemsRequestAction | DeployBatchedThirdPartyItemsSuccessAction | DeployBatchedThirdPartyItemsFailureAction + | DisableThirdPartySuccessAction + | DisableThirdPartyFailureAction + | DisableThirdPartyRequestAction 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 DISABLE_THIRD_PARTY_REQUEST: case FETCH_THIRD_PARTIES_REQUEST: { return { ...state, @@ -78,6 +88,20 @@ export function thirdPartyReducer(state: ThirdPartyState = INITIAL_STATE, action error: null } } + case DISABLE_THIRD_PARTY_SUCCESS: { + const { thirdPartyId } = action.payload + return { + ...state, + data: { + ...state.data, + [thirdPartyId]: { + ...state.data[thirdPartyId], + isApproved: false + } + }, + loading: loadingReducer(state.loading, action) + } + } case FETCH_THIRD_PARTY_AVAILABLE_SLOTS_SUCCESS: { const { thirdPartyId, availableSlots } = action.payload return { @@ -100,6 +124,7 @@ export function thirdPartyReducer(state: ThirdPartyState = INITIAL_STATE, action error: null } } + case DISABLE_THIRD_PARTY_FAILURE: case FETCH_THIRD_PARTY_AVAILABLE_SLOTS_FAILURE: case FETCH_THIRD_PARTIES_FAILURE: { return { diff --git a/src/modules/thirdParty/sagas.spec.ts b/src/modules/thirdParty/sagas.spec.ts index 11ed72b62..983076ebb 100644 --- a/src/modules/thirdParty/sagas.spec.ts +++ b/src/modules/thirdParty/sagas.spec.ts @@ -12,6 +12,8 @@ import { ToastType } from 'decentraland-ui' import { SHOW_TOAST } from 'decentraland-dapps/dist/modules/toast/actions' import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types' import { closeModal } from 'decentraland-dapps/dist/modules/modal/actions' +import { getChainIdByNetwork } from 'decentraland-dapps/dist/lib' +import { sendTransaction } from 'decentraland-dapps/dist/modules/wallet/utils' import { loginSuccess } from 'modules/identity/actions' import { BuilderAPI } from 'lib/api/builder' import { ThirdParty } from './types' @@ -33,7 +35,10 @@ import { publishAndPushChangesThirdPartyItemsSuccess, deployBatchedThirdPartyItemsRequest, deployBatchedThirdPartyItemsFailure, - deployBatchedThirdPartyItemsSuccess + deployBatchedThirdPartyItemsSuccess, + disableThirdPartyFailure, + disableThirdPartyRequest, + disableThirdPartySuccess } from './actions' import { mockedItem } from 'specs/item' import { getCollection } from 'modules/collection/selectors' @@ -54,6 +59,9 @@ import { ThirdPartyAction } from 'modules/ui/thirdparty/types' import { Item } from 'modules/item/types' import { thirdPartySaga } from './sagas' import { getPublishItemsSignature } from './utils' +import { getThirdParty } from './selectors' +import { ContractData, ContractName, getContract } from 'decentraland-transactions' +import { ChainId, Network } from '@dcl/schemas' jest.mock('modules/item/export') jest.mock('@dcl/crypto') @@ -81,6 +89,8 @@ beforeEach(() => { thirdParty = { id: '1', name: 'test', + root: '', + isApproved: true, description: 'aDescription', managers: [], contracts: [], @@ -130,13 +140,25 @@ describe('when fetching third parties', () => { { id: '1', name: 'a third party', + root: '', + isApproved: true, description: 'some desc', managers: ['0x1', '0x2'], maxItems: '0', totalItems: '0', contracts: [] }, - { id: '2', name: 'a third party', description: 'some desc', managers: ['0x3'], maxItems: '0', totalItems: '0', contracts: [] } + { + id: '2', + name: 'a third party', + description: 'some desc', + managers: ['0x3'], + maxItems: '0', + totalItems: '0', + contracts: [], + root: '', + isApproved: true + } ] }) @@ -647,3 +669,51 @@ describe('when handling the batched deployment of third party items', () => { }) }) }) + +describe('when handling the disabling of a third party', () => { + let contract: ContractData + + beforeEach(() => { + contract = { address: '0xcontract', abi: [], version: '1', name: 'name', chainId: ChainId.MATIC_MAINNET } + }) + + describe('and the transaction fails', () => { + let errorMessage: string + beforeEach(() => { + errorMessage = 'Some Error Message' + }) + + it('should put a disable third party failure action with the error', () => { + return expectSaga(thirdPartySaga, mockBuilder, mockCatalystClient) + .provide([ + [call(getChainIdByNetwork, Network.MATIC), ChainId.MATIC_MAINNET], + [select(getThirdParty, thirdParty.id), thirdParty], + [call(getContract, ContractName.ThirdPartyRegistry, ChainId.MATIC_MAINNET), contract], + [call(sendTransaction as any, contract, 'reviewThirdParties', [[thirdParty.id, false, []]]), throwError(new Error(errorMessage))] + ]) + .put(disableThirdPartyFailure(errorMessage)) + .dispatch(disableThirdPartyRequest(thirdParty.id)) + .run({ silenceTimeout: true }) + }) + }) + + describe('and the transaction succeeds', () => { + let txHash: string + beforeEach(() => { + txHash = '0xtxHash' + }) + + it('should put a successful disable third party action with the transaction data', () => { + return expectSaga(thirdPartySaga, mockBuilder, mockCatalystClient) + .provide([ + [select(getThirdParty, thirdParty.id), thirdParty], + [call(getChainIdByNetwork, Network.MATIC), ChainId.MATIC_MAINNET], + [call(getContract, ContractName.ThirdPartyRegistry, ChainId.MATIC_MAINNET), contract], + [call(sendTransaction as any, contract, 'reviewThirdParties', [[thirdParty.id, false, []]]), txHash] + ]) + .put(disableThirdPartySuccess(thirdParty.id, ChainId.MATIC_MAINNET, txHash, thirdParty.name)) + .dispatch(disableThirdPartyRequest(thirdParty.id)) + .run({ silenceTimeout: true }) + }) + }) +}) diff --git a/src/modules/thirdParty/sagas.ts b/src/modules/thirdParty/sagas.ts index 9244adf79..ab45eda70 100644 --- a/src/modules/thirdParty/sagas.ts +++ b/src/modules/thirdParty/sagas.ts @@ -69,10 +69,15 @@ import { PUSH_CHANGES_THIRD_PARTY_ITEMS_SUCCESS, PUSH_CHANGES_THIRD_PARTY_ITEMS_FAILURE, PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_FAILURE, - PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_SUCCESS + PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_SUCCESS, + DISABLE_THIRD_PARTY_REQUEST, + DisableThirdPartyRequestAction, + disableThirdPartyFailure, + disableThirdPartySuccess } from './actions' import { getPublishItemsSignature } from './utils' import { ThirdParty } from './types' +import { getThirdParty } from './selectors' export function* getContractInstance( contract: ContractName.ThirdPartyRegistry | ContractName.ChainlinkOracle, @@ -95,6 +100,7 @@ export function* thirdPartySaga(builder: BuilderAPI, catalystClient: CatalystCli yield takeEvery(PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_REQUEST, handlePublishAndPushChangesThirdPartyItemRequest) yield takeEvery(PUBLISH_THIRD_PARTY_ITEMS_SUCCESS, handlePublishThirdPartyItemSuccess) yield takeLatest(REVIEW_THIRD_PARTY_REQUEST, handleReviewThirdPartyRequest) + yield takeEvery(DISABLE_THIRD_PARTY_REQUEST, handleDisableThirdPartyRequest) yield takeEvery(actionProgressChannel, handleUpdateApprovalFlowProgress) yield takeEvery( [ @@ -340,4 +346,17 @@ export function* thirdPartySaga(builder: BuilderAPI, catalystClient: CatalystCli yield put(deployBatchedThirdPartyItemsSuccess(collection, deployedItemsCurations)) } } + + function* handleDisableThirdPartyRequest(action: DisableThirdPartyRequestAction) { + const { thirdPartyId } = action.payload + try { + const maticChainId: ChainId = yield call(getChainIdByNetwork, Network.MATIC) + const thirdParty: ThirdParty | null = yield select(getThirdParty, thirdPartyId) + const thirdPartyContract: ContractData = yield call(getContract, ContractName.ThirdPartyRegistry, maticChainId) + const txHash: string = yield call(sendTransaction as any, thirdPartyContract, 'reviewThirdParties', [[thirdPartyId, false, []]]) + yield put(disableThirdPartySuccess(thirdPartyId, maticChainId, txHash, thirdParty?.name ?? 'Unknown Third Party Name')) + } catch (error) { + yield put(disableThirdPartyFailure(isErrorWithMessage(error) ? error.message : 'Unknown error')) + } + } } diff --git a/src/modules/thirdParty/selectors.spec.ts b/src/modules/thirdParty/selectors.spec.ts index ea45bc32c..490881f20 100644 --- a/src/modules/thirdParty/selectors.spec.ts +++ b/src/modules/thirdParty/selectors.spec.ts @@ -1,13 +1,21 @@ import { Collection } from 'modules/collection/types' import { RootState } from 'modules/common/types' -import { DEPLOY_BATCHED_THIRD_PARTY_ITEMS_REQUEST, fetchThirdPartiesRequest } from './actions' +import { + DEPLOY_BATCHED_THIRD_PARTY_ITEMS_REQUEST, + DISABLE_THIRD_PARTY_SUCCESS, + disableThirdPartyRequest, + fetchThirdPartiesRequest +} from './actions' import { isThirdPartyManager, getWalletThirdParties, getCollectionThirdParty, getItemThirdParty, isDeployingBatchedThirdPartyItems, - isLoadingThirdParties + isLoadingThirdParties, + getThirdParty, + isDisablingThirdParty, + hasPendingDisableThirdPartyTransaction } from './selectors' import { ThirdParty } from './types' @@ -23,29 +31,35 @@ describe('Third Party selectors', () => { thirdParty1 = { id: 'urn:decentraland:mumbai:collections-thirdparty:thirdparty1', name: 'a third party', + root: '', description: 'some desc', maxItems: '0', totalItems: '0', contracts: [], - managers: [address, '0xa'] + managers: [address, '0xa'], + isApproved: true } thirdParty2 = { id: 'urn:decentraland:mumbai:collections-thirdparty:thirdparty2', name: 'a third party', + root: '', description: 'some desc', maxItems: '0', totalItems: '0', contracts: [], - managers: [address, '0xb'] + managers: [address, '0xb'], + isApproved: true } thirdParty3 = { id: 'urn:decentraland:mumbai:collections-thirdparty:thirdparty3', name: 'a third party', + root: '', description: 'some desc', maxItems: '0', totalItems: '0', contracts: [], - managers: ['0xc'] + managers: ['0xc'], + isApproved: true } baseState = { wallet: { @@ -289,4 +303,147 @@ describe('Third Party selectors', () => { }) }) }) + + describe('when getting a third party', () => { + describe('and the third party exists', () => { + let state: RootState + + beforeEach(() => { + state = { + ...baseState, + thirdParty: { + data: { + [thirdParty1.id]: thirdParty1 + } + } + } as any + }) + + it('should return the third party', () => { + expect(getThirdParty(state, thirdParty1.id)).toBe(thirdParty1) + }) + }) + + describe('and the third party does not exist', () => { + let state: RootState + + beforeEach(() => { + state = { + ...baseState, + thirdParty: { + data: {} + } + } as any + }) + + it('should return null', () => { + expect(getThirdParty(state, thirdParty1.id)).toBe(null) + }) + }) + }) + + describe('when checking if a third party is being disabled', () => { + let state: RootState + + describe('and the disable third party request is being processed', () => { + beforeEach(() => { + state = { + ...baseState, + thirdParty: { + ...baseState.thirdParty, + loading: [disableThirdPartyRequest(thirdParty1.id)] + } + } + }) + + it('should return true', () => { + expect(isDisablingThirdParty(state)).toBe(true) + }) + }) + + describe('and the disable third party request is not being processed', () => { + beforeEach(() => { + state = { + ...baseState, + thirdParty: { + ...baseState.thirdParty, + loading: [] + } + } + }) + + it('should return false', () => { + expect(isDisablingThirdParty(state)).toBe(false) + }) + }) + }) + + describe('when checking if a disable third party transaction is pending', () => { + beforeEach(() => { + baseState = { + ...baseState, + transaction: { + ...baseState.transaction, + data: [ + { + events: [], + hash: '0x123', + nonce: 1, + actionType: DISABLE_THIRD_PARTY_SUCCESS, + payload: { thirdPartyId: thirdParty1.id }, + status: null, + from: address, + replacedBy: null, + timestamp: 0, + url: 'url', + isCrossChain: false, + chainId: 1 + } + ] + } + } as RootState + }) + + describe('and the transaction is pending', () => { + beforeEach(() => { + baseState = { + ...baseState, + transaction: { + ...baseState.transaction, + data: [ + { + ...baseState.transaction.data[0], + status: null + } + ] + } + } as RootState + }) + + it('should return true', () => { + expect(hasPendingDisableThirdPartyTransaction(baseState, thirdParty1.id)).toBe(true) + }) + }) + + describe('and the transaction is not pending', () => { + beforeEach(() => { + baseState = { + ...baseState, + transaction: { + ...baseState.transaction, + data: [ + { + ...baseState.transaction.data[0], + status: 'confirmed' + } + ] + } + } as RootState + }) + + it('should return false', () => { + expect(hasPendingDisableThirdPartyTransaction(baseState, thirdParty1.id)).toBe(false) + }) + }) + }) }) diff --git a/src/modules/thirdParty/selectors.ts b/src/modules/thirdParty/selectors.ts index ef93bfd39..df5809788 100644 --- a/src/modules/thirdParty/selectors.ts +++ b/src/modules/thirdParty/selectors.ts @@ -3,10 +3,17 @@ 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 { Collection } from 'modules/collection/types' +import { getPendingTransactions, Transaction } from 'decentraland-dapps/dist/modules/transaction' import { Item } from 'modules/item/types' import { ThirdPartyState } from './reducer' import { ThirdParty } from './types' -import { DEPLOY_BATCHED_THIRD_PARTY_ITEMS_REQUEST, FETCH_THIRD_PARTIES_REQUEST, FETCH_THIRD_PARTY_AVAILABLE_SLOTS_REQUEST } from './actions' +import { + DEPLOY_BATCHED_THIRD_PARTY_ITEMS_REQUEST, + DISABLE_THIRD_PARTY_SUCCESS, + FETCH_THIRD_PARTIES_REQUEST, + DISABLE_THIRD_PARTY_REQUEST, + FETCH_THIRD_PARTY_AVAILABLE_SLOTS_REQUEST +} from './actions' import { getThirdPartyForCollection, getThirdPartyForItem, isUserManagerOfThirdParty } from './utils' export const getState = (state: RootState) => state.thirdParty @@ -28,6 +35,21 @@ export const getWalletThirdParties = createSelector Object.values(thirdParties).filter(thirdParty => thirdParty.managers.includes(address)) ) +export const hasPendingDisableThirdPartyTransaction = (state: RootState, thirdPartyId: string): boolean => { + const userAddress = getAddress(state) + return ( + userAddress !== undefined && + getPendingTransactions(state, userAddress).some( + (transaction: Transaction) => + [DISABLE_THIRD_PARTY_SUCCESS].includes(transaction.actionType) && transaction.payload.thirdPartyId === thirdPartyId + ) + ) +} + +export const getThirdParty = (state: RootState, id: string): ThirdParty | null => getData(state)[id] ?? null + +export const isDisablingThirdParty = (state: RootState): boolean => isLoadingType(getLoading(state), DISABLE_THIRD_PARTY_REQUEST) + export const getCollectionThirdParty = (state: RootState, collection: Collection): ThirdParty | null => getThirdPartyForCollection(getData(state), collection) ?? null diff --git a/src/modules/thirdParty/types.ts b/src/modules/thirdParty/types.ts index 9cc73f082..2a6483a53 100644 --- a/src/modules/thirdParty/types.ts +++ b/src/modules/thirdParty/types.ts @@ -2,9 +2,11 @@ import { ContractNetwork } from '@dcl/schemas' export type ThirdParty = { id: string + root: string managers: string[] name: string description: string + isApproved: boolean contracts: LinkedContract[] maxItems: string totalItems: string diff --git a/src/modules/translation/languages/en.json b/src/modules/translation/languages/en.json index efa7700b5..6f4853726 100644 --- a/src/modules/translation/languages/en.json +++ b/src/modules/translation/languages/en.json @@ -1423,7 +1423,8 @@ "disallowed_claim_mana": "Disallowed MANA to claim a new name", "claim_name": "Claimed new name: \"{name}\".", "reclaim_name": "Reclaimed the name: \"{subdomain}\".", - "rescue_items": "Approved content of {count} {count, plural, one {item} other {items}} for {collectionName}" + "rescue_items": "Approved content of {count} {count, plural, one {item} other {items}} for {collectionName}", + "disable_third_party": "Disabled third party: {name}" }, "transfer_page": { "title": "Transfer", @@ -1979,6 +1980,12 @@ "subtitle": "The collection will not be available anymore. Don't worry, you will be able to enable it again later.", "tx_pending": "Waiting for the disable collection transaction to confirm", "action": "Disable" + }, + "disable_third_party": { + "title": "Disable Third Party", + "subtitle": "This third party will not be available anymore. This include all collections under this third party. Don't worry, you'll be able to enable it again later.", + "tx_pending": "Waiting for the disable third party transaction to confirm", + "action": "Disable" } } }, diff --git a/src/modules/translation/languages/es.json b/src/modules/translation/languages/es.json index cfa9eda35..642ddda71 100644 --- a/src/modules/translation/languages/es.json +++ b/src/modules/translation/languages/es.json @@ -1430,7 +1430,8 @@ "allowed_claim_mana": "Permitió el uso de MANA para reclamar un nuevo nombre", "disallowed_claim_mana": "No permitió el uso de MANA para reclamar un nuevo nombre", "claim_name": "Haz reclamado el nombre: \"{name}\".", - "rescue_items": "Has aprobado el contenido de {count} {count, plural, one {item} other {items}} para la collección {collectionName}" + "rescue_items": "Has aprobado el contenido de {count} {count, plural, one {item} other {items}} para la collección {collectionName}", + "disable_third_party": "Has deshabilitado el proveedor: {name}" }, "transfer_page": { "title": "Transferir", @@ -1997,6 +1998,12 @@ "subtitle": "Esta colleccion no estara mas disponible. Puedes habilitarla nuevamente en el futuro.", "tx_pending": "Esperando a que se confirme la transacción de deshabilitado", "action": "Deshabilitar" + }, + "disable_third_party": { + "title": "Deshabilitar proveedor", + "subtitle": "Éste proveedor no estará más disponible. Ésto incluye todas las collecciones bajo éste proveedor. No te preocupes, podrás habilitarlo nuevamente luego.", + "tx_pending": "Esperando a la confirmación de la transacción para deshabilitar el proveedor", + "action": "Deshabilitar" } } }, diff --git a/src/modules/translation/languages/zh.json b/src/modules/translation/languages/zh.json index a70d7a179..d8003a911 100644 --- a/src/modules/translation/languages/zh.json +++ b/src/modules/translation/languages/zh.json @@ -1413,7 +1413,8 @@ "allowed_claim_mana": "允许MANA声明新名称", "disallowed_claim_mana": "不允许MANA申请新名称", "claim_name": "声明新名称:\"{name}\"。", - "rescue_items": "您已批准 {count} 项目 的内容,用于 {collectionName} 集合" + "rescue_items": "您已批准 {count} 项目 的内容,用于 {collectionName} 集合", + "disable_third_party": "已禁用第三方:{name}" }, "transfer_page": { "title": "转移", @@ -1979,6 +1980,12 @@ "subtitle": "此集合将不再可用。将来可以再次启用它。", "tx_pending": "等待已禁用的交易得到确认。", "action": "禁用" + }, + "disable_third_party": { + "title": "禁用第三方", + "subtitle": "此第三方将不再可用。这包括此第三方下的所有集合。别担心,您稍后可以再次启用它。", + "tx_pending": "等待禁用第三方交易确认", + "action": "禁用" } } },