Skip to content

Commit

Permalink
feat: Add new push changes modal
Browse files Browse the repository at this point in the history
  • Loading branch information
LautaroPetaccio committed Oct 2, 2024
1 parent f6c4db4 commit d2a55b5
Show file tree
Hide file tree
Showing 20 changed files with 367 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isTPCollection } from 'modules/collection/utils'
import { emailRegex } from 'lib/validators'
import { Props } from './ReviewContentPolicyStep.types'
import styles from './ReviewContentPolicyStep.module.css'
import { REVIEW_CONTENT_POLICY_CONTINUE_DATA_TEST_ID } from './constants'

const termsOfUseLink = (link: string) => (
<a href="https://decentraland.org/terms/" rel="noopener noreferrer" target="_blank">
Expand Down Expand Up @@ -134,7 +135,7 @@ export const ReviewContentPolicyStep: React.FC<Props> = props => {
<Button secondary onClick={onPrevStep}>
{t('global.back')}
</Button>
<Button primary onClick={onNextStep} disabled={isDisabled}>
<Button data-testid={REVIEW_CONTENT_POLICY_CONTINUE_DATA_TEST_ID} primary onClick={onNextStep} disabled={isDisabled}>
{t('publish_wizard_collection_modal.review_content_policy_step.continue')}
</Button>
</Row>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const REVIEW_CONTENT_POLICY_CONTINUE_DATA_TEST_ID = 'review-content-policy-continue-data-test-id'
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ReviewContentPolicyStep'
export * from './ReviewContentPolicyStep.types'
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useCallback } from 'react'
import { AuthorizationStepStatus } from 'decentraland-ui'
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors'
import { getCollection, getError as getCollectionError, getLoading as getCollectionLoading } from 'modules/collection/selectors'
import { getCollectionThirdParty, getError as getThirdPartyError, getThirdPartyPublishStatus } from 'modules/thirdParty/selectors'
import { PUSH_COLLECTION_CURATION_REQUEST, pushCollectionCurationRequest } from 'modules/curations/collectionCuration/actions'
import { isThirdPartyCollection } from 'modules/collection/utils'
import { RootState } from 'modules/common/types'
import { publishAndPushChangesThirdPartyItemsRequest } from 'modules/thirdParty/actions'
import { OwnProps } from './PushChangesModal.types'
import { PushChangesModal } from './PushChangesModal'

export default (props: OwnProps) => {
const dispatch = useDispatch()
const isPushingStandardCollectionChanges = useSelector((store: RootState) =>
isLoadingType(getCollectionLoading(store), PUSH_COLLECTION_CURATION_REQUEST)
)
const thirdPartyPublishStatus = useSelector((store: RootState) => getThirdPartyPublishStatus(store), shallowEqual)
const isPushingThirdPartyItemsChanges =
thirdPartyPublishStatus === AuthorizationStepStatus.WAITING || thirdPartyPublishStatus === AuthorizationStepStatus.PROCESSING

const collection = useSelector((state: RootState) => getCollection(state, props.metadata.collectionId), shallowEqual)
const thirdPartyError = useSelector((state: RootState) => getThirdPartyError(state), shallowEqual)
const collectionError = useSelector((state: RootState) => getCollectionError(state), shallowEqual)
const error = thirdPartyError || collectionError
if (!collection) {
throw new Error('Collection not found')
}

const isThirdParty = isThirdPartyCollection(collection)
const thirdParty = isThirdParty ? useSelector((store: RootState) => getCollectionThirdParty(store, collection)) : null
const isLoading = isPushingStandardCollectionChanges || isPushingThirdPartyItemsChanges
const onPushChanges = useCallback(
(email: string, subscribeToNewsletter: boolean) => {
if (thirdParty) {
dispatch(
publishAndPushChangesThirdPartyItemsRequest(
thirdParty,
[],
props.metadata.itemsWithChanges,
undefined,
email,
subscribeToNewsletter
)
)
} else {
dispatch(pushCollectionCurationRequest(props.metadata.collectionId))
}
},
[dispatch, props.metadata.collectionId, thirdParty, props.metadata.itemsWithChanges]
)

return <PushChangesModal {...props} isLoading={isLoading} error={error} onPushChanges={onPushChanges} collection={collection} />
}
22 changes: 22 additions & 0 deletions src/components/Modals/PushChangesModal/PushChangesModal.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.content {
background-color: #1d1a20;
border-radius: 8px;
padding: 24px 27px 88px 27px;
font-size: 16px;
color: #cfcdd4;
}

.actions {
margin-top: 24px;
width: 100%;
display: flex;
justify-content: space-between !important;
}

.actions :global(.button) {
width: 180px;
}

.error {
margin: 24px !important;
}
77 changes: 77 additions & 0 deletions src/components/Modals/PushChangesModal/PushChangesModal.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import userEvent from '@testing-library/user-event'
import { Collection } from 'modules/collection/types'
import { renderWithProviders } from 'specs/utils'
import { Props } from './PushChangesModal.types'
import { PushChangesModal } from './PushChangesModal'
import {
PUSH_CHANGES_MODAL_CANCEL_CHANGES_DATA_TEST_ID,
PUSH_CHANGES_MODAL_CONFIRM_CHANGES_DATA_TEST_ID,
PUSH_CHANGES_MODAL_FIRST_STEP_DATA_TEST_ID
} from './constants'
import { REVIEW_CONTENT_POLICY_CONTINUE_DATA_TEST_ID } from '../PublishWizardCollectionModal/ReviewContentPolicyStep/constants'

const renderPushChangesModal = (props: Partial<Props> = {}) =>
renderWithProviders(
<PushChangesModal
error={null}
name="aName"
onClose={jest.fn()}
onPushChanges={jest.fn()}
metadata={{ collectionId: 'aCollectionId', itemsWithChanges: [] }}
isLoading={false}
collection={{ id: 'aCollectionId', name: 'aName' } as Collection}
{...props}
/>
)

let props: Partial<Props>
let renderedComponent: ReturnType<typeof renderPushChangesModal>

beforeEach(() => {
props = {}
})

describe('when rendering the component', () => {
beforeEach(() => {
renderedComponent = renderPushChangesModal(props)
})

it('should start the modal in the first step', () => {
expect(renderedComponent.getByTestId(PUSH_CHANGES_MODAL_FIRST_STEP_DATA_TEST_ID)).toBeInTheDocument()
})
})

describe('when rendering the component with an error', () => {
beforeEach(() => {
props.error = 'anError'
renderedComponent = renderPushChangesModal(props)
})

it('should render the error message', () => {
expect(renderedComponent.getByText('anError')).toBeInTheDocument()
})
})

describe('when clicking cancel on the first step', () => {
beforeEach(() => {
props.onClose = jest.fn()
renderedComponent = renderPushChangesModal(props)
userEvent.click(renderedComponent.getByTestId(PUSH_CHANGES_MODAL_CANCEL_CHANGES_DATA_TEST_ID))
})

it('should call onClose', () => {
expect(props.onClose).toHaveBeenCalled()
})
})

describe('when clicking confirm on the first step', () => {
beforeEach(() => {
props.onPushChanges = jest.fn()
renderedComponent = renderPushChangesModal(props)
userEvent.click(renderedComponent.getByTestId(PUSH_CHANGES_MODAL_CONFIRM_CHANGES_DATA_TEST_ID))
})

it('should switch to the ToS step', () => {
expect(renderedComponent.getByTestId(REVIEW_CONTENT_POLICY_CONTINUE_DATA_TEST_ID)).toBeInTheDocument()
})
})
93 changes: 93 additions & 0 deletions src/components/Modals/PushChangesModal/PushChangesModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useCallback, useMemo, useState } from 'react'
import { Button, Message, ModalNavigation } from 'decentraland-ui'
import Modal from 'decentraland-dapps/dist/containers/Modal'
import { T, t } from 'decentraland-dapps/dist/modules/translation/utils'
import { ReviewContentPolicyStep } from '../PublishWizardCollectionModal/ReviewContentPolicyStep'
import styles from './PushChangesModal.module.css'
import { Props } from './PushChangesModal.types'
import {
PUSH_CHANGES_MODAL_CANCEL_CHANGES_DATA_TEST_ID,
PUSH_CHANGES_MODAL_CONFIRM_CHANGES_DATA_TEST_ID,
PUSH_CHANGES_MODAL_FIRST_STEP_DATA_TEST_ID
} from './constants'

enum Steps {
CONFIRM_CHANGES = 'CONFIRM_CHANGES',
ACCEPT_TERMS = 'ACCEPT_TERMS'
}

export const PushChangesModal = (props: Props) => {
const { onClose, onPushChanges, isLoading, error, collection } = props

const [currentStep, setCurrentStep] = useState<Steps>(Steps.CONFIRM_CHANGES)
const [email, setEmail] = useState<string>('')
const [subscribeToNewsletter, setSubscribeToNewsletter] = useState<boolean>(false)

const stepTitle = useMemo(() => {
switch (currentStep) {
case Steps.CONFIRM_CHANGES:
return t('push_changes_modal.title')
case Steps.ACCEPT_TERMS:
return t('publish_wizard_collection_modal.title_review_content_policy')
}
}, [currentStep])

const handleProceedFromConfirmChanges = useCallback(() => {
setCurrentStep(Steps.ACCEPT_TERMS)
}, [])

const handleGoBack = useCallback(() => {
setCurrentStep(Steps.CONFIRM_CHANGES)
}, [])

const handleOnPushChanges = useCallback(() => {
onPushChanges(email, subscribeToNewsletter)
}, [onPushChanges, email, subscribeToNewsletter])

return (
<Modal className={styles.main} size="small" onClose={isLoading ? undefined : onClose} closeOnDimmerClick={false}>
<ModalNavigation title={stepTitle} onClose={isLoading ? undefined : onClose} />
{currentStep === Steps.CONFIRM_CHANGES ? (
<Modal.Content>
<div className={styles.content} data-testid={PUSH_CHANGES_MODAL_FIRST_STEP_DATA_TEST_ID}>
<T
id="push_changes_modal.description"
values={{
br: (
<>
<br />
<br />
</>
)
}}
/>
</div>

<div className={styles.actions}>
<Button data-testid={PUSH_CHANGES_MODAL_CANCEL_CHANGES_DATA_TEST_ID} secondary onClick={onClose}>
{t('global.cancel')}
</Button>
<Button data-testid={PUSH_CHANGES_MODAL_CONFIRM_CHANGES_DATA_TEST_ID} primary onClick={handleProceedFromConfirmChanges}>
{t('global.proceed')}
</Button>
</div>
</Modal.Content>
) : (
<ReviewContentPolicyStep
collection={collection}
confirmedEmailAddress={email}
subscribeToNewsletter={subscribeToNewsletter}
onChangeEmailAddress={setEmail}
onSubscribeToNewsletter={setSubscribeToNewsletter}
onNextStep={handleOnPushChanges}
onPrevStep={handleGoBack}
/>
)}
{error ? (
<Message className={styles.error} error>
{error}
</Message>
) : null}
</Modal>
)
}
17 changes: 17 additions & 0 deletions src/components/Modals/PushChangesModal/PushChangesModal.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ModalProps } from 'decentraland-dapps/dist/providers/ModalProvider/ModalProvider.types'
import { Collection } from 'modules/collection/types'
import { Item } from 'modules/item/types'

type ModalMetadata = {
collectionId: string
itemsWithChanges: Item[]
}

export type Props = OwnProps & {
onPushChanges: (email: string, subscribeToNewsletter: boolean) => unknown
isLoading: boolean
error: string | null
collection: Collection
}

export type OwnProps = Omit<ModalProps, 'metadata'> & { metadata: ModalMetadata }
3 changes: 3 additions & 0 deletions src/components/Modals/PushChangesModal/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const PUSH_CHANGES_MODAL_FIRST_STEP_DATA_TEST_ID = 'push-changes-modal-first-step-data-test-id'
export const PUSH_CHANGES_MODAL_CONFIRM_CHANGES_DATA_TEST_ID = 'push-changes-modal-confirm-changes-data-test-id'
export const PUSH_CHANGES_MODAL_CANCEL_CHANGES_DATA_TEST_ID = 'push-changes-modal-cancel-changes-data-test-id'
2 changes: 2 additions & 0 deletions src/components/Modals/PushChangesModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import PushChangesModal from './PushChangesModal.container'
export { PushChangesModal }
1 change: 1 addition & 0 deletions src/components/Modals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ export { default as EnsMapAddressModal } from './ENSMapAddressModal'
export { default as ReclaimNameModal } from './ReclaimNameModal'
export { default as WorldPermissionsModal } from './WorldPermissionsModal'
export { CreateCollectionSelectorModal } from './CreateCollectionSelectorModal'
export { PushChangesModal } from './PushChangesModal'
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => {
const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onNewClick: (collectionId: string, itemsWithChanges: Item[], itemsToPublish: Item[]) =>
dispatch(openModal('PublishWizardCollectionModal', { collectionId, itemsWithChanges, itemsToPublish })),
onPushChangesClick: (collectionId: string, itemsWithChanges: Item[]) =>
dispatch(openModal('PushChangesModal', { collectionId, itemsWithChanges })),
onClick: (collectionId: string, itemIds: string[], action: PublishButtonAction) =>
dispatch(openModal('PublishThirdPartyCollectionModal', { collectionId, itemIds, action }))
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const CollectionPublishButton = (props: Props) => {
isLinkedWearablesPaymentsEnabled,
onClick,
onNewClick,
onPushChangesClick,
itemsStatus,
itemCurations,
isLoadingItemCurations
Expand Down Expand Up @@ -73,11 +74,23 @@ const CollectionPublishButton = (props: Props) => {
const itemsToPushChanges = getItemsWithChanges(items, itemsStatus, itemCurations)
if (isLinkedWearablesPaymentsEnabled && itemsToPublish.length > 0) {
onNewClick(collection.id, itemsToPushChanges, itemsToPublish)
} else if (isLinkedWearablesPaymentsEnabled && itemsToPushChanges.length > 0) {
onPushChangesClick(collection.id, itemsToPushChanges)
} else {
const itemIds = items.map(item => item.id)
onClick(collection.id, itemIds, buttonAction)
}
}, [collection, items, buttonAction, onClick, onNewClick, isLinkedWearablesPaymentsEnabled, itemsStatus, itemCurations])
}, [
collection,
items,
buttonAction,
onClick,
onNewClick,
onPushChangesClick,
isLinkedWearablesPaymentsEnabled,
itemsStatus,
itemCurations
])

const itemsTryingToPublish = useMemo(
() => items.filter(item => !itemCurations?.find(itemCuration => itemCuration.itemId === item.id)).length,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ export type Props = {
itemsStatus: Record<string, SyncStatus>
slots: number
onClick: (collectionId: string, itemIds: string[], action: PublishButtonAction) => void
onPushChangesClick: (collectionId: string, itemsWithChanges: Item[]) => unknown
onNewClick: (collectionId: string, itemsWithChanges: Item[], itemsToPublish: Item[]) => void
}

export type OwnProps = Pick<Props, 'items' | 'collection'>
export type MapStateProps = Pick<Props, 'itemCurations' | 'itemsStatus' | 'isLoadingItemCurations' | 'isLinkedWearablesPaymentsEnabled'>
export type MapDispatchProps = Pick<Props, 'onClick' | 'onNewClick'>
export type MapDispatchProps = Pick<Props, 'onClick' | 'onNewClick' | 'onPushChangesClick'>
export type MapDispatch = Dispatch<OpenModalAction>
3 changes: 1 addition & 2 deletions src/modules/thirdParty/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,8 @@ export const FINISH_PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_FAILURE = '[Failu
export const finishPublishAndPushChangesThirdPartyItemsSuccess = (
thirdParty: ThirdParty,
collectionId: Collection['id'],
items: Item[],
itemCurations: ItemCuration[]
) => action(FINISH_PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_SUCCESS, { thirdParty, collectionId, items, itemCurations })
) => action(FINISH_PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_SUCCESS, { thirdParty, collectionId, itemCurations })
export const finishPublishAndPushChangesThirdPartyItemsFailure = (error: string) =>
action(FINISH_PUBLISH_AND_PUSH_CHANGES_THIRD_PARTY_ITEMS_FAILURE, { error })

Expand Down
Loading

0 comments on commit d2a55b5

Please sign in to comment.