diff --git a/src/components/ENSDetailPage/ENSDetailPage.module.css b/src/components/ENSDetailPage/ENSDetailPage.module.css index d9dbd1162..7e2a7a2f0 100644 --- a/src/components/ENSDetailPage/ENSDetailPage.module.css +++ b/src/components/ENSDetailPage/ENSDetailPage.module.css @@ -28,6 +28,8 @@ .fieldContainer { display: flex; + justify-content: space-between; + align-items: start; gap: 32px; } @@ -153,3 +155,18 @@ text-decoration: underline; color: white; } + +.unclaimedBadge { + padding: 0 8px; + border-radius: 24px; + font-size: 14px; + line-height: 24px; + background-color: #393640; + text-transform: uppercase; + font-weight: 600; + color: white; +} + +.field.disabled { + opacity: 0.4; +} diff --git a/src/components/ENSDetailPage/ENSDetailPage.spec.tsx b/src/components/ENSDetailPage/ENSDetailPage.spec.tsx index d6fc7f0db..dd80a79d3 100644 --- a/src/components/ENSDetailPage/ENSDetailPage.spec.tsx +++ b/src/components/ENSDetailPage/ENSDetailPage.spec.tsx @@ -27,7 +27,7 @@ const ensSample: ENS = { name: 'test', subdomain: 'test', content: '', - ensOwnerAddress: '0xtest2', + ensOwnerAddress: '0xtest1', nftOwnerAddress: '0xtest1', resolver: '0xtest3', tokenId: '', @@ -184,4 +184,32 @@ describe('when ens is defined', () => { expect(onNavigateMock).toHaveBeenCalledWith('/name/test/set-land') }) }) + + describe('and the ens owner is different from the nft address', () => { + beforeEach(() => { + ens = { + ...ensSample, + nftOwnerAddress: '0xtest1', + ensOwnerAddress: '0xtest2' + } + }) + + it('should call onReclaim when reclaim button is pressed', () => { + const openModalMock = jest.fn() + const screen = renderENSDetailPage({ ens, onOpenModal: openModalMock }) + const reclaimNameBtn = screen.getByRole('button', { name: t('ens_detail_page.reclaim_name') }) + userEvent.click(reclaimNameBtn) + expect(openModalMock).toHaveBeenCalledWith('ReclaimNameModal', { ens }) + }) + + it('should disable assign address button', () => { + const screen = renderENSDetailPage({ ens }) + expect(screen.getByRole('button', { name: t('ens_detail_page.reclaim_for_address') })).toBeDisabled() + }) + + it('should disable assign location button', () => { + const screen = renderENSDetailPage({ ens }) + expect(screen.getByRole('button', { name: t('ens_detail_page.reclaim_for_location') })).toBeDisabled() + }) + }) }) diff --git a/src/components/ENSDetailPage/ENSDetailPage.tsx b/src/components/ENSDetailPage/ENSDetailPage.tsx index 28a78a978..91e98d04d 100644 --- a/src/components/ENSDetailPage/ENSDetailPage.tsx +++ b/src/components/ENSDetailPage/ENSDetailPage.tsx @@ -22,6 +22,8 @@ export default function ENSDetailPage(props: Props) { [ens] ) + const shouldReclaim = ens?.ensOwnerAddress !== ens?.nftOwnerAddress + useEffect(() => { if (name) { onFetchENS(name) @@ -40,6 +42,10 @@ export default function ENSDetailPage(props: Props) { onNavigate(locations.ensSelectLand(ens?.subdomain)) }, [onNavigate, ens?.subdomain]) + const handleReclaim = useCallback(() => { + onOpenModal('ReclaimNameModal', { ens }) + }, [onOpenModal]) + const aliasField = useMemo(() => { let field: React.ReactNode if (alias !== ens?.name) { @@ -76,11 +82,20 @@ export default function ENSDetailPage(props: Props) { {field} ) - }, [ens?.name, avatar, alias, handleSetAsAlias]) + }, [ens?.name, shouldReclaim, avatar, alias, handleSetAsAlias]) const addressField = useMemo(() => { let field: React.ReactNode = null - if (!ens?.ensAddressRecord) { + if (shouldReclaim) { + field = ( +
+ {t('ens_detail_page.reclaim_for_address')} + +
+ ) + } else if (!ens?.ensAddressRecord) { field = (
{t('ens_detail_page.assign_address')} @@ -97,7 +112,7 @@ export default function ENSDetailPage(props: Props) { ) } else { field = ( - + Ethereum {shorten(ens.ensAddressRecord)} @@ -116,29 +131,28 @@ export default function ENSDetailPage(props: Props) { ) } return ( -
+
{t('ens_detail_page.address')} - ( - - {content} - - ) - })} - trigger={} - /> + } /> {field}
) - }, [ens?.ensAddressRecord, handleAssignENSAddress]) + }, [ens?.ensAddressRecord, shouldReclaim, handleAssignENSAddress]) const landField = useMemo(() => { let field: React.ReactNode = null - if (!ens?.landId) { + if (shouldReclaim) { + field = ( +
+ {t('ens_detail_page.reclaim_for_location')} + +
+ ) + } else if (!ens?.landId) { field = (
{t('ens_detail_page.point_location')} @@ -178,25 +192,15 @@ export default function ENSDetailPage(props: Props) { } return ( -
+
{t('ens_detail_page.land')} - ( - - {content} - - ) - })} - trigger={} - /> + } /> {field}
) - }, [ens?.landId, handleAssignENS]) + }, [ens?.landId, shouldReclaim, handleAssignENS]) return ( @@ -219,8 +223,14 @@ export default function ENSDetailPage(props: Props) { + {ens?.ensOwnerAddress !== ens?.nftOwnerAddress ? ( + {t('ens_detail_page.unclaimed')} + ) : null}
+
{aliasField} diff --git a/src/components/ENSListPage/ENSListPage.css b/src/components/ENSListPage/ENSListPage.css index ce874deea..9f4f49ecf 100644 --- a/src/components/ENSListPage/ENSListPage.css +++ b/src/components/ENSListPage/ENSListPage.css @@ -387,3 +387,19 @@ border-radius: 50%; background: var(--secondary); } + +.ENSListPage .ens-list-reclaim-icon { + margin: 0 !important; + width: fit-content; + font-size: 15px; + line-height: 15px; +} + +.ENSListPage .ens-list-unclaimed-badge { + padding: 0 8px; + border-radius: 24px; + font-size: 12px; + background-color: #393640; + text-transform: uppercase; + font-weight: 600; +} diff --git a/src/components/ENSListPage/ENSListPage.tsx b/src/components/ENSListPage/ENSListPage.tsx index b3dce89d6..7495685fe 100644 --- a/src/components/ENSListPage/ENSListPage.tsx +++ b/src/components/ENSListPage/ENSListPage.tsx @@ -62,6 +62,11 @@ export default class ENSListPage extends React.PureComponent { onOpenModal('UseAsAliasModal', { newName }) } + handleReclaim = (ens: ENS) => { + const { onOpenModal } = this.props + onOpenModal('ReclaimNameModal', { ens }) + } + renderSortDropdown = () => { const { sortBy } = this.state return ( @@ -130,6 +135,15 @@ export default class ENSListPage extends React.PureComponent { } renderLandLinkInfo(ens: ENS) { + if (ens.ensOwnerAddress !== ens.nftOwnerAddress) { + return ( + + ) + } + if (!ens.landId) { return ( + ) + } + + if (ens.ensAddressRecord) { + return ( + + Ethereum + {shorten(ens.ensAddressRecord)} + + + + + ) + } + return ( + + ) + } + renderEnsList() { const { ensList, hasProfileCreated } = this.props const { page } = this.state @@ -330,6 +373,9 @@ export default class ENSListPage extends React.PureComponent { + {ens.ensOwnerAddress !== ens.nftOwnerAddress ? ( + {t('ens_list_page.unclaimed')} + ) : null}
), alias: this.isAlias(ens) ? ( @@ -353,20 +399,7 @@ export default class ENSListPage extends React.PureComponent { {t('ens_list_page.button.add_to_avatar')} ), - address: ens.ensAddressRecord ? ( - - Ethereum - {shorten(ens.ensAddressRecord)} - - - - - ) : ( - - ), + address: this.renderAddressInfo(ens), land: this.renderLandLinkInfo(ens), actions: (
@@ -403,9 +436,6 @@ export default class ENSListPage extends React.PureComponent { -
) @@ -459,33 +489,13 @@ export default class ENSListPage extends React.PureComponent { address: ( {t('ens_list_page.table.address')} - ( - - {content} - - ) - })} - trigger={} - /> + } /> ), land: ( {t('ens_list_page.table.land')} - ( - - {content} - - ) - })} - trigger={} - /> + } /> ), actions: {t('ens_list_page.table.actions')} diff --git a/src/components/LandAssignENSPage/LandAssignENSForm/LandAssignENSForm.tsx b/src/components/LandAssignENSPage/LandAssignENSForm/LandAssignENSForm.tsx index 29d1d915a..2ee56fdee 100644 --- a/src/components/LandAssignENSPage/LandAssignENSForm/LandAssignENSForm.tsx +++ b/src/components/LandAssignENSPage/LandAssignENSForm/LandAssignENSForm.tsx @@ -36,7 +36,8 @@ export default class LandAssignENSForm extends React.PureComponent { } render() { - const { ens, land, isLoading, isWaitingTxSetContent, isWaitingTxSetResolver, isWaitingTxReclaim, error } = this.props + const { ens, land, isLoading, isWaitingTxSetContent, isWaitingTxSetResolver, isWaitingTxReclaim, error, isEnsAddressEnabled } = + this.props const needsReclaiming = ens.ensOwnerAddress !== ens.nftOwnerAddress const hasResolver = !isResolverEmpty(ens) && ens.resolver.toLowerCase() === ENS_RESOLVER_ADDRESS.toLowerCase() @@ -73,34 +74,36 @@ export default class LandAssignENSForm extends React.PureComponent { {t('land_assign_ens_page.set_name_message', { strong: (children: React.ReactElement) => {children} })}

- -
-

{t('land_assign_ens_page.reclaim')}

-
-

{t('land_assign_ens_page.reclaim_explanation')}

- - {hasReclaimError ? ( - t('global.retry_tx') - ) : !needsReclaiming ? ( - <> - {t('global.approved_tx')} - {!isWaitingTxReclaim ? : null} - - ) : ( - t('global.submit') - )} - + {!isEnsAddressEnabled ? ( + +
+

{t('land_assign_ens_page.reclaim')}

+
+

{t('land_assign_ens_page.reclaim_explanation')}

+ + {hasReclaimError ? ( + t('global.retry_tx') + ) : !needsReclaiming ? ( + <> + {t('global.approved_tx')} + {!isWaitingTxReclaim ? : null} + + ) : ( + t('global.submit') + )} + +
-
- + + ) : null}

{t('land_assign_ens_page.set_resolver')}

diff --git a/src/components/LandAssignENSPage/LandAssignENSForm/LandAssignENSForm.types.ts b/src/components/LandAssignENSPage/LandAssignENSForm/LandAssignENSForm.types.ts index cf56eb9b3..56917c355 100644 --- a/src/components/LandAssignENSPage/LandAssignENSForm/LandAssignENSForm.types.ts +++ b/src/components/LandAssignENSPage/LandAssignENSForm/LandAssignENSForm.types.ts @@ -9,6 +9,7 @@ export type Props = { isWaitingTxSetResolver: boolean isWaitingTxReclaim: boolean error: ENSError | null + isEnsAddressEnabled: boolean onSetENSResolver: (ens: ENS) => void onSetENSContent: (ens: ENS, land: Land) => void onReclaimName: (ens: ENS) => void diff --git a/src/components/LandAssignENSPage/LandAssignENSPage.container.ts b/src/components/LandAssignENSPage/LandAssignENSPage.container.ts index b5ef2af8e..e13e65873 100644 --- a/src/components/LandAssignENSPage/LandAssignENSPage.container.ts +++ b/src/components/LandAssignENSPage/LandAssignENSPage.container.ts @@ -23,6 +23,7 @@ import { } from 'modules/ens/selectors' import { MapStateProps, MapDispatchProps, MapDispatch, OwnProps } from './LandAssignENSPage.types' import LandAssignENSPage from './LandAssignENSPage' +import { getIsEnsAddressEnabled } from 'modules/features/selectors' const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => { const { landId, subdomain } = ownProps.match.params @@ -36,6 +37,7 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => { isWaitingTxSetResolver: isLoadingType(getLoading(state), SET_ENS_RESOLVER_REQUEST) || isWaitingTxSetResolver(state), isWaitingTxSetContent: isLoadingType(getLoading(state), SET_ENS_CONTENT_REQUEST) || isWaitingTxSetLandContent(state, landId), isWaitingTxReclaim: ens ? isReclaimingName(state, ens.subdomain) || isWaitingTxReclaim(state) : false, + isEnsAddressEnabled: getIsEnsAddressEnabled(state), isLoading: isLoadingType(getLoading(state), SET_ENS_RESOLVER_REQUEST) || isLoadingType(getLoading(state), SET_ENS_CONTENT_REQUEST) || diff --git a/src/components/LandAssignENSPage/LandAssignENSPage.tsx b/src/components/LandAssignENSPage/LandAssignENSPage.tsx index 08cf4eb0c..fa4a6d50b 100644 --- a/src/components/LandAssignENSPage/LandAssignENSPage.tsx +++ b/src/components/LandAssignENSPage/LandAssignENSPage.tsx @@ -21,6 +21,7 @@ export default class LandAssignENSPage extends React.PureComponent { isWaitingTxSetContent, isWaitingTxReclaim, isWaitingTxSetResolver, + isEnsAddressEnabled, onSetENSResolver, onSetENSContent, onReclaimName, @@ -41,6 +42,7 @@ export default class LandAssignENSPage extends React.PureComponent { } > export type MapDispatchProps = Pick export type MapDispatch = Dispatch< diff --git a/src/components/Modals/ReclaimNameModal/ReclaimNameModal.container.ts b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.container.ts new file mode 100644 index 000000000..567ddbeeb --- /dev/null +++ b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.container.ts @@ -0,0 +1,24 @@ +import { connect } from 'react-redux' +import { clearENSErrors, reclaimNameRequest } from 'modules/ens/actions' +import { getENSBySubdomain, getError, isReclaimingName, isWaitingTxReclaim } from 'modules/ens/selectors' +import { RootState } from 'modules/common/types' +import { MapDispatch, MapDispatchProps, OwnProps } from './ReclaimNameModal.types' +import EnsMapAddressModal from './ReclaimNameModal' + +const mapState = (state: RootState, ownProps: OwnProps) => { + const error = getError(state) + const ens = getENSBySubdomain(state, ownProps.metadata.ens.subdomain) + return { + isLoadingReclaim: ens ? isReclaimingName(state, ens.subdomain) : false, + isWaitingTxReclaim: isWaitingTxReclaim(state), + error: error ? error.message : null, + ens + } +} + +const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({ + onUnmount: () => dispatch(clearENSErrors()), + onReclaim: ens => dispatch(reclaimNameRequest(ens)) +}) + +export default connect(mapState, mapDispatch)(EnsMapAddressModal) diff --git a/src/components/Modals/ReclaimNameModal/ReclaimNameModal.module.css b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.module.css new file mode 100644 index 000000000..e438a6a12 --- /dev/null +++ b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.module.css @@ -0,0 +1,29 @@ +.main { + padding: 30px 40px; + display: flex; + flex-direction: column; + gap: 30px; +} + +.info { + font-size: 16px; + font-weight: 400; + line-height: 24px; +} + +.actions { + display: flex; + gap: 20px; +} + +.status { + display: flex; + align-items: center; + flex: 1; + gap: 10px; + font-size: 13px; +} + +.status .icon { + color: var(--primary); +} diff --git a/src/components/Modals/ReclaimNameModal/ReclaimNameModal.spec.tsx b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.spec.tsx new file mode 100644 index 000000000..4fba0b887 --- /dev/null +++ b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.spec.tsx @@ -0,0 +1,58 @@ +import userEvent from '@testing-library/user-event' +import { t } from 'decentraland-dapps/dist/modules/translation/utils' +import { ENS } from 'modules/ens/types' +import { renderWithProviders } from 'specs/utils' +import { Props } from './ReclaimNameModal.types' +import ReclaimNameModal from './ReclaimNameModal' + +function renderReclaimNameModal(props: Partial = {}) { + return renderWithProviders( + + ) +} + +let ens: ENS +beforeEach(() => { + ens = { + name: 'test', + subdomain: 'test.dcl.eth', + resolver: '' + } as ENS +}) + +it('should render reclaim name info', () => { + const screen = renderReclaimNameModal({ ens }) + expect(screen.getByText(t('ens_reclaim_name_modal.info'))).toBeInTheDocument() +}) + +it('should call onReclaim callback when clicking the action button', () => { + const onReclaimNameMock = jest.fn() + const screen = renderReclaimNameModal({ ens, onReclaim: onReclaimNameMock }) + const actionBtn = screen.getByRole('button', { name: t('ens_reclaim_name_modal.action') }) + userEvent.click(actionBtn) + expect(onReclaimNameMock).toHaveBeenCalledWith(ens) +}) + +it('should show error message when an error ocurred', () => { + const screen = renderReclaimNameModal({ ens, error: 'Some error ocurr' }) + expect(screen.getByText(t('ens_reclaim_name_modal.error'))).toBeInTheDocument() +}) + +it('should show confirm transaction message when set resolver is loading', () => { + const screen = renderReclaimNameModal({ ens, isLoadingReclaim: true }) + expect(screen.getByText(t('ens_reclaim_name_modal.confirm_transaction'))).toBeInTheDocument() +}) + +it('should show processing message when set resolver tx is loading', () => { + const screen = renderReclaimNameModal({ ens, isWaitingTxReclaim: true }) + expect(screen.getByText(t('ens_reclaim_name_modal.processing'))).toBeInTheDocument() +}) diff --git a/src/components/Modals/ReclaimNameModal/ReclaimNameModal.tsx b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.tsx new file mode 100644 index 000000000..2886fae77 --- /dev/null +++ b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.tsx @@ -0,0 +1,62 @@ +import { useCallback, useEffect, useMemo } from 'react' +import { Button, Close, Icon, Loader } from 'decentraland-ui' +import { t } from 'decentraland-dapps/dist/modules/translation/utils' +import Modal from 'decentraland-dapps/dist/containers/Modal' +import { Props } from './ReclaimNameModal.types' +import styles from './ReclaimNameModal.module.css' + +export default function ReclaimNameModal(props: Props) { + const { error, ens, isLoadingReclaim, isWaitingTxReclaim, onUnmount, onClose, onReclaim } = props + const handleReclaimName = useCallback(() => { + onReclaim(ens) + }, [ens, onReclaim]) + + useEffect(() => { + return () => { + onUnmount() + } + }, [onUnmount]) + + const statusMessage = useMemo(() => { + if (isLoadingReclaim) { + return ( + + + {t('ens_reclaim_name_modal.confirm_transaction')} + + ) + } else if (isWaitingTxReclaim) { + return ( + + + {t('ens_reclaim_name_modal.processing')} + + ) + } else if (error) { + return ( + + + {t('ens_reclaim_name_modal.error')} + + ) + } + return null + }, [isLoadingReclaim, isWaitingTxReclaim, error]) + + const disableActions = isLoadingReclaim || isWaitingTxReclaim + + return ( + } onClose={disableActions ? undefined : onClose} size="tiny"> +
+

{t('ens_reclaim_name_modal.title')}

+ {t('ens_reclaim_name_modal.info', { br: () =>
})}
+
+ + {statusMessage} +
+
+
+ ) +} diff --git a/src/components/Modals/ReclaimNameModal/ReclaimNameModal.types.ts b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.types.ts new file mode 100644 index 000000000..a9e752dab --- /dev/null +++ b/src/components/Modals/ReclaimNameModal/ReclaimNameModal.types.ts @@ -0,0 +1,18 @@ +import { ModalProps } from 'decentraland-ui' +import { Dispatch } from 'redux' +import { ClearENSErrorsAction, ReclaimNameRequestAction, reclaimNameRequest } from 'modules/ens/actions' +import { ENS } from 'modules/ens/types' + +export type Props = ModalProps & { + error: string | null + ens: ENS + isLoadingReclaim: boolean + isWaitingTxReclaim: boolean + onReclaim: typeof reclaimNameRequest + onUnmount: () => void +} + +export type MapStateProps = Pick +export type MapDispatchProps = Pick +export type MapDispatch = Dispatch +export type OwnProps = Omit diff --git a/src/components/Modals/ReclaimNameModal/index.ts b/src/components/Modals/ReclaimNameModal/index.ts new file mode 100644 index 000000000..b37c375cb --- /dev/null +++ b/src/components/Modals/ReclaimNameModal/index.ts @@ -0,0 +1,2 @@ +import ReclaimNameModal from './ReclaimNameModal.container' +export default ReclaimNameModal diff --git a/src/components/Modals/index.ts b/src/components/Modals/index.ts index 1ce6b650b..f5a8bd6e0 100644 --- a/src/components/Modals/index.ts +++ b/src/components/Modals/index.ts @@ -51,3 +51,4 @@ export { default as EmotesV2AnnouncementModal } from './EmotesV2AnnouncementModa export { default as WorldsYourStorageModal } from './WorldsYourStorageModal' export { default as WorldsForENSOwnersAnnouncementModal } from './WorldsForENSOwnersAnnouncementModal' export { default as EnsMapAddressModal } from './ENSMapAddressModal' +export { default as ReclaimNameModal } from './ReclaimNameModal' diff --git a/src/modules/ens/sagas.ts b/src/modules/ens/sagas.ts index bf816ea69..6059bab3c 100644 --- a/src/modules/ens/sagas.ts +++ b/src/modules/ens/sagas.ts @@ -388,8 +388,8 @@ export function* ensSaga(builderClient: BuilderClient, ensApi: ENSApi) { const wallet: Wallet = yield call(getWallet) const signer: ethers.Signer = yield call(getSigner) const address = wallet.address - const ensContract: ReturnType = yield call([ENS__factory, 'connect'], ENS_ADDRESS, signer) - const dclRegistrarContract: ReturnType = yield call( + const ensContract: ReturnType<(typeof ENS__factory)['connect']> = yield call([ENS__factory, 'connect'], ENS_ADDRESS, signer) + const dclRegistrarContract: ReturnType<(typeof DCLRegistrar__factory)['connect']> = yield call( [DCLRegistrar__factory, 'connect'], REGISTRAR_ADDRESS, signer @@ -527,6 +527,7 @@ export function* ensSaga(builderClient: BuilderClient, ensApi: ENSApi) { const dclRegistrarContract = DCLRegistrar__factory.connect(REGISTRAR_ADDRESS, signer) const transaction: ethers.ContractTransaction = yield call([dclRegistrarContract, 'reclaim'], ens.tokenId, wallet.address) yield put(reclaimNameSuccess(transaction.hash, wallet.chainId, { ...ens, ensOwnerAddress: wallet.address })) + yield put(closeModal('ReclaimNameModal')) } catch (error) { const ensError: ENSError = { message: isErrorWithMessage(error) ? error.message : 'Unknown error' } yield put(reclaimNameFailure(ensError)) @@ -539,7 +540,7 @@ export function* ensSaga(builderClient: BuilderClient, ensApi: ENSApi) { const wallet: Wallet = yield call(getWallet) const signer: ethers.Signer = yield call(getSigner) const from = wallet.address - const manaContract: ReturnType = yield call([ERC20__factory, 'connect'], MANA_ADDRESS, signer) + const manaContract: ReturnType<(typeof ERC20__factory)['connect']> = yield call([ERC20__factory, 'connect'], MANA_ADDRESS, signer) const transaction: ethers.ContractTransaction = yield call([manaContract, 'approve'], CONTROLLER_V2_ADDRESS, allowance) yield put(allowClaimManaSuccess(allowance, from.toString(), wallet.chainId, transaction.hash)) diff --git a/src/modules/ens/selectors.ts b/src/modules/ens/selectors.ts index 1e73210e7..3d75681c5 100644 --- a/src/modules/ens/selectors.ts +++ b/src/modules/ens/selectors.ts @@ -16,7 +16,8 @@ import { RECLAIM_NAME_SUCCESS, CLAIM_NAME_REQUEST, CLAIM_NAME_TRANSACTION_SUBMITTED, - SET_ENS_ADDRESS_SUCCESS + SET_ENS_ADDRESS_SUCCESS, + RECLAIM_NAME_REQUEST } from './actions' import { Authorization, ENS } from './types' import { ENSState } from './reducer' @@ -105,8 +106,8 @@ export const isWaitingTxSetLandContent = (state: RootState, landId: string) => transaction => SET_ENS_CONTENT_SUCCESS === transaction.actionType && transaction.payload.land.id === landId ) -export const isReclaimingName = (state: RootState, name: string) => - getLoading(state).some(loadingAction => loadingAction.type === CLAIM_NAME_REQUEST && loadingAction.name === name) +export const isReclaimingName = (state: RootState, subdomain: string) => + getLoading(state).some(loadingAction => loadingAction.type === RECLAIM_NAME_REQUEST && loadingAction.payload.ens.subdomain === subdomain) export const isLoadingContentBySubdomain = createSelector>( getENSList, diff --git a/src/modules/translation/languages/en.json b/src/modules/translation/languages/en.json index f3a9c044e..3b69b4ede 100644 --- a/src/modules/translation/languages/en.json +++ b/src/modules/translation/languages/en.json @@ -578,9 +578,9 @@ "subtitle": "Your are editing {name}." }, "ens_list_page": { - "title": "NAMEs", + "title": "Your NAMEs", "sort_by": "Sort by", - "mint_name": "Mint name", + "mint_name": "Mint a New NAME", "result": "{count} {count, plural, one {result} other {results}}", "edit": "Edit", "transfer": "Transfer", @@ -588,52 +588,58 @@ "name": "Name", "link": "Link", "assigned_to": "Assigned To", - "address": "Address", - "alias": "Alias", - "land": "Land", + "address": "Associated Address", + "alias": "Linked Avatar", + "land": "Linked LAND", "actions": "Actions", "you": "you" }, + "unclaimed": "Unclaimed", "assigned_to_land": "Land ({landId})", "assigned_to_estate": "Estate ({landId})", "button": { "assign": "Use as Link", "edit": "Edit Link", "use_as_alias": "Use as Alias", - "add_to_avatar": "Add to Avatar", - "link_to_address": "Link to an Ethereum Address", - "assign_to_land": "Assign to Land or World" + "add_to_avatar": "Assign to Avatar", + "link_to_address": "Assign ETH address", + "assign_to_land": "Assign to Location", + "reclaim_land": "Reclaim to assign to location", + "reclaim_address": "Reclaim to assign ETH address" }, "empty_names": "It looks like you don't have any Names.{br} {link} to get started.", "items": "{count} {count, plural, one {result} other {results}}", "alias_popup": "This name is being used by your profile but it can still be assigned", "not_profile_created": "You need to get into the world first to set an alias.", "empty_state": { - "title": "Nothing here yet", - "subtitle": "Own your presence in the metaverse with a username free from numbers on the end (e.g. Genesis#1234).", - "mint_name": "Mint name", - "go_to_marketplace": "Visit marketplace" + "title": "Nothing Here Yet", + "subtitle": "Own your presence in the metaverse with a username free from numbers on the end (e.g. Genesis#1234), plus enjoy the added benefits of Voting Power and access to your own World!", + "mint_name": "Claim Your Name" } }, "ens_detail_page": { "name": "Name", - "address": "Address", - "alias": "Alias", - "land": "Land", - "unassign": "Unassigned", - "set_as_primary": "Set as primary", + "address": "Associated Address", + "alias": "Linked Avatar", + "land": "Linked LAND", + "unassign": "Assign to Avatar", + "set_as_primary": "Assign to Avatar", "edit_address": "Edit address", - "assign_address": "Assign address value", - "point_location": "Point location", + "assign_address": "Assign ETH address", + "point_location": "Assign to Location", "tooltips": { - "alias": "The assigned alias can be only one per Avatar", - "address": "It can be used in external in any WEB3 transaction. Read the benefits of a ENS controller NAME", - "land": "Lands are Located spacial Assets, minimum units are Parcels, learn more about Metaverse advantages" - } + "alias": "You can only assign one NAME per avatar", + "address": "Use your Decentraland NAME across Web3 thanks to a partnership with ENS.", + "land": "When you assign a LAND parcel or estate a NAME it can be easily navigated to with a dedicated URL (e.g. https://name.dcl.eth)." + }, + "unclaimed": "Unclaimed", + "reclaim_name": "Reclaim name", + "reclaim_for_location": "Reclaim to assign location", + "reclaim_for_address": "Reclaim to assign address" }, "ens_map_address_modal": { - "title": "Link to an Ethereum Address", - "description": "Enable others to reference your account by defining an Ethereum address with your Decentraland NAME. This allows you to receive assets simply by providing your NAME.", + "title": "Assigned Address", + "description": "Enable others to reference your account by assigning an Ethereum address to your Decentraland NAME. This allows you to receive assets simply by providing your NAME.", "address": { "label": "Linked Ethereum Address", "placeholder": "Address" @@ -647,11 +653,19 @@ "title": "Grant Permission", "info": "Enable others to reference your account by defining an Ethereum address with your Decentraland NAME.



This allows you to receive assets simply by providing your NAME. Authorize the public ENS resolver to set the address record on your behalf in Decentraland.", "action": "Authorize", - "error": "Something went wrong" + "error": "Something went wrong, please try again." }, "confirm_transaction": "Confirm the transaction...", "processing": "Processing..." }, + "ens_reclaim_name_modal": { + "title": "Reclaim name", + "info": "This reclaims control of your NAME after it has been transferred to you.", + "action": "Reclaim", + "confirm_transaction": "Confirm the transaction...", + "processing": "Processing...", + "error": "Something went wrong, please try again." + }, "worlds_list_page": { "table": { "name": "NAME", @@ -1053,9 +1067,7 @@ "land_assign_ens_page": { "title": "Use {name} as link", "subtitle": "This link will take you to {land}", - "set_name_message": "Before you can assign this name to your land, you need to submit 2 or 3 transactions: The first transaction reclaims your ENS name so you become its controller and it's only required if the name was bought or transferred from another user, the second one allows you to use your name as a link and the third transaction links your name to your LAND.", - "reclaim": "Reclaim", - "reclaim_explanation": "This reclaims the control of your name after it has been transferred to you. The owner of the name has the control over it until it gets reclaimed.", + "set_name_message": "Before you can assign this name to your land, you need to submit 1 or 2 transactions: The first transaction allows you to use your name as a link and the second transaction links your name to your LAND.", "set_resolver": "Set Resolver", "set_resolver_explanation": "This allows your name to be linked to a land.", "set_content": "Set Content", diff --git a/src/modules/translation/languages/es.json b/src/modules/translation/languages/es.json index 760248271..95378d8db 100644 --- a/src/modules/translation/languages/es.json +++ b/src/modules/translation/languages/es.json @@ -580,9 +580,9 @@ "unpublish_needed": "Por favor Despublica tu Escena antes de actualizar los detalles de la misma" }, "ens_list_page": { - "title": "Nombres", + "title": "Tus Nombres", "sort_by": "Ordernar por", - "mint_name": "Comprar nombre", + "mint_name": "Comprar un nuevo nombre", "result": "{count} {count, plural, one {resultado} other {resultados}}", "edit": "Editar", "transfer": "Transferir", @@ -590,31 +590,33 @@ "name": "Nombre", "link": "Enlace", "assigned_to": "Asignado a", - "address": "Dirección", - "alias": "alias", - "land": "Tierra", + "address": "Dirección asociada", + "alias": "Alias vinculado", + "land": "Tierra vinculada", "actions": "Acciones", "you": "tú" }, + "unclaimed": "No recuperado", "assigned_to_land": "Tierra ({landId})", "assigned_to_estate": "Estate ({landId})", "button": { "assign": "Usar como link", "edit": "Editar link", "use_as_alias": "Usar como alias", - "add_to_avatar": "Agregar a Avatar", - "link_to_address": "Enlazar a una dirección de Ethereum", - "assign_to_land": "Asignar a tierra o mundo" + "add_to_avatar": "Asignar a Avatar", + "link_to_address": "Asignar a una dirección de Ethereum", + "assign_to_land": "Asignar a ubicación", + "reclaim_land": "Recuperar para asignar a ubicación", + "reclaim_address": "Recuperar para asigner a dirección de ETH" }, "empty_names": "No tienes ningun nombre aún.{br} {link} para comenzar.", "items": "{count} {count, plural, one {resultado} other {resultados}}", "alias_popup": "Este nombre está siendo usado por tu perfil pero igual puede ser asignado", "not_profile_created": "Primero necesitas entrar en el mundo antes de asignar un alias.", "empty_state": { - "title": "Nada aquí todavía", - "subtitle": "Tenga presencia en el Metaverso con un nombre de usuario libre de números al final (e.j. Genesis#1234).", - "mint_name": "Agregar nombre", - "go_to_marketplace": "Visitar el marketplace" + "title": "Nada Aquí Todavía", + "subtitle": "¡Posee su presencia en el metalto con un nombre de usuario libre de números al final (por ejemplo, Génesis#1234), además de disfrutar de los beneficios adicionales del poder de votación y el acceso a su propio mundo!", + "mint_name": "Comprar un nuevo nombre" } }, "ens_detail_page": { @@ -631,7 +633,11 @@ "alias": "El alias asignado puede ser solo uno por avatar", "address": "Se puede usar externamente en cualquier transacción Web3. Leer los beneficios de ENS", "land": "Las tierras son activos espaciales, las unidades mínimas son parcelas, leer mas sobre los beneficios del Metaverso" - } + }, + "unclaimed": "Sin recuperar", + "reclaim_name": "Recuperar nombre", + "reclaim_for_location": "Recuperar para asignar a ubicación", + "reclaim_for_address": "Recuperar para asigner a dirección de ETH" }, "ens_map_address_modal": { "title": "Enlace a una dirección de Ethereum", @@ -649,11 +655,19 @@ "title": "Conceder permiso", "info": "Permitir que otros hagan referencia a su cuenta definiendo una dirección de Ethereum con su nombre Decentraland.



Esto le permite recibir activos simplemente proporcionando su nombre. Autorice al resolución pública de ENS para establecer el registro de la dirección en su nombre en Decentraland.", "action": "Autorizar", - "error": "Algo salió mal" + "error": "Algo salió mal. Por favor, vuelva a intentarlo." }, "confirm_transaction": "Confirmar transacción...", "processing": "Procesando..." }, + "ens_reclaim_name_modal": { + "title": "Recuperar nombre", + "info": "Esto recupera el control de su nombre después de que se le haya transferido.", + "action": "Recuperar", + "confirm_transaction": "Confirmar la transacción...", + "processing": "Procesando...", + "error": "Algo salió mal. Por favor, vuelva a intentarlo." + }, "worlds_list_page": { "table": { "name": "NOMBRE", diff --git a/src/modules/translation/languages/zh.json b/src/modules/translation/languages/zh.json index 8cf4b9338..0d0af72a1 100644 --- a/src/modules/translation/languages/zh.json +++ b/src/modules/translation/languages/zh.json @@ -582,9 +582,9 @@ "title": "糟糕!" }, "ens_list_page": { - "title": "名称", + "title": "", "sort_by": "排序方式", - "mint_name": "薄荷名称", + "mint_name": "Mint a New NAME", "result": "{count} {count, plural, one {结果} other {结果}}", "edit": "编辑", "transfer": "转移", @@ -592,21 +592,24 @@ "name": "名称", "link": "链接", "assigned_to": "分配给", - "address": "地址", - "alias": "别名", - "land": "土地", + "address": "相关地址", + "alias": "链接的头像", + "land": "链接的土地", "actions": "动作", "you": "你" }, + "unclaimed": "无人认领", "assigned_to_land": "土地 ({landId})", "assigned_to_estate": "州 ({landId})", "button": { "assign": "分配", "edit": "修", "use_as_alias": "用作别名", - "add_to_avatar": "添加到头像", - "link_to_address": "链接到以太坊地址", - "assign_to_land": "分配土地或世界" + "add_to_avatar": "分配给阿凡达", + "link_to_address": "分配ETH地址", + "assign_to_land": "分配到位置", + "reclaim_land": "收回分配到位置", + "reclaim_address": "收回分配ETH地址" }, "empty_names": "看来您没有任何名称。{br} {link}开始使用。", "items": "{count}结果", @@ -614,30 +617,33 @@ "not_profile_created": "您需要先进入世界才能设置别名。", "empty_state": { "title": "这里什么都没有", - "subtitle": "用用户名在元元中拥有无数数字 (例如。 Genesis#1234).", - "mint_name": "薄荷名称", - "go_to_marketplace": "访问市场" + "subtitle": "在元视频中拥有无数数字的用户名(例如Genesis#1234),并享受投票权和访问您自己的世界的额外好处!", + "mint_name": "要求您的名字" } }, "ens_detail_page": { "name": "姓名", - "address": "地址", - "alias": "别名", - "land": "土地", - "unassign": "未分配", - "set_as_primary": "设置为主要", + "address": "相关地址", + "alias": "链接的头像", + "land": "链接的土地", + "unassign": "分配给阿凡达", + "set_as_primary": "分配给阿凡达", "edit_address": "编辑地址", - "assign_address": "分配地址值", - "point_location": "点位置", + "assign_address": "分配ETH地址", + "point_location": "分配到位置", "tooltips": { - "alias": "分配的别名可以是每个头像", - "address": "它可以在任何Web3事务中的外部使用. 阅读ENS控制器名称的好处", - "land": "土地位于空间资产,最低单位是包裹, 了解有关元优势的更多信息" - } + "alias": "您只能为每个头像分配一个名称", + "address": "得益于与ENS的合作伙伴关系,请在整个Web3上使用您的分散名称。", + "land": "当您分配土地包裹或遗产时,可以使用专用URL轻松导航到(例如https://name.dcl.eth)。" + }, + "unclaimed": "无人认领", + "reclaim_name": "回收名称", + "reclaim_for_location": "收回分配位置", + "reclaim_for_address": "收回分配地址" }, "ens_map_address_modal": { - "title": "链接到以太坊地址", - "description": "通过定义您的分散名称来定义以太坊地址,使其他人可以参考您的帐户。这使您只需提供姓名即可获得资产。", + "title": "分配的地址", + "description": "通过将以太坊地址分配给您的分散名称,使其他人可以参考您的帐户。这使您只需提供姓名即可获得资产。", "address": { "label": "链接的以太坊地址", "placeholder": "地址" @@ -651,11 +657,19 @@ "title": "授予权限", "info": "通过定义您的分散名称来定义以太坊地址,使其他人可以参考您的帐户。



这使您只需提供姓名即可获得资产。授权公共ENS解析器代表您在分散的情况下设置地址记录。", "action": "授权", - "error": "出了些问题" + "error": "出了问题,请重试。" }, "confirm_transaction": "确认交易...", "processing": "加工..." }, + "ens_reclaim_name_modal": { + "title": "回收名称", + "info": "这将在将其转移给您后收回对您的名字的控制。", + "action": "回收", + "confirm_transaction": "确认交易...", + "processing": "加工...", + "error": "出了问题,请重试。" + }, "worlds_list_page": { "table": { "name": "姓名",