{"id":18709656,"url":"https://github.com/decentraland/decentraland-dapps","last_synced_at":"2025-12-02T18:04:15.450Z","repository":{"id":32998331,"uuid":"141639552","full_name":"decentraland/decentraland-dapps","owner":"decentraland","description":"🛠 Common modules for dApps","archived":false,"fork":false,"pushed_at":"2025-05-15T17:09:52.000Z","size":8040,"stargazers_count":109,"open_issues_count":37,"forks_count":54,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-05-15T18:24:23.642Z","etag":null,"topics":["dapps","decentraland","modules","redux"],"latest_commit_sha":null,"homepage":"http://npmjs.com/package/decentraland-dapps","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/decentraland.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-07-19T23:15:07.000Z","updated_at":"2025-05-15T17:08:10.000Z","dependencies_parsed_at":"2023-09-22T05:49:42.497Z","dependency_job_id":"68b3f9ec-afdb-499c-9704-a143d4364716","html_url":"https://github.com/decentraland/decentraland-dapps","commit_stats":{"total_commits":826,"total_committers":25,"mean_commits":33.04,"dds":0.7457627118644068,"last_synced_commit":"7df94c00df7ecc1d6b7b29eab0136eb179393893"},"previous_names":[],"tags_count":536,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decentraland%2Fdecentraland-dapps","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decentraland%2Fdecentraland-dapps/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decentraland%2Fdecentraland-dapps/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decentraland%2Fdecentraland-dapps/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/decentraland","download_url":"https://codeload.github.com/decentraland/decentraland-dapps/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254436948,"owners_count":22070947,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dapps","decentraland","modules","redux"],"created_at":"2024-11-07T12:28:32.548Z","updated_at":"2025-12-02T18:04:15.426Z","avatar_url":"https://github.com/decentraland.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"https://ui.decentraland.org/decentraland_256x256.png\" height=\"128\" width=\"128\" /\u003e\n\n# Decentraland dApps\n\nCommon modules for our dApps.\n\n# Table of Contents\n\n- [Modules](https://github.com/decentraland/decentraland-dapps#modules)\n  - [Wallet](https://github.com/decentraland/decentraland-dapps#wallet)\n  - [Storage](https://github.com/decentraland/decentraland-dapps#storage)\n  - [Transaction](https://github.com/decentraland/decentraland-dapps#transaction)\n  - [Authorization](https://github.com/decentraland/decentraland-dapps#authorization)\n  - [Translation](https://github.com/decentraland/decentraland-dapps#translation)\n  - [Analytics](https://github.com/decentraland/decentraland-dapps#analytics)\n  - [Loading](https://github.com/decentraland/decentraland-dapps#loading)\n  - [Modal](https://github.com/decentraland/decentraland-dapps#modal)\n  - [Toasts](https://github.com/decentraland/decentraland-dapps#toasts)\n  - [Profile](https://github.com/decentraland/decentraland-dapps#profile)\n  - [Credits](https://github.com/decentraland/decentraland-dapps#credits)\n- [Lib](https://github.com/decentraland/decentraland-dapps#lib)\n  - [API](https://github.com/decentraland/decentraland-dapps#api)\n  - [ETH](https://github.com/decentraland/decentraland-dapps#eth)\n  - [Entities](https://github.com/decentraland/decentraland-dapps#entities)\n- [Containers](https://github.com/decentraland/decentraland-dapps#containers)\n  - [App](https://github.com/decentraland/decentraland-dapps#app)\n  - [Navbar](https://github.com/decentraland/decentraland-dapps#navbar)\n  - [Footer](https://github.com/decentraland/decentraland-dapps#footer)\n  - [SignInPage](https://github.com/decentraland/decentraland-dapps#signinpage)\n  - [Modal](https://github.com/decentraland/decentraland-dapps#modal)\n  - [TransactionLink](https://github.com/decentraland/decentraland-dapps#transactionlink)\n- [Components](https://github.com/decentraland/decentraland-dapps#components)\n  - [Intercom](https://github.com/decentraland/decentraland-dapps#intercom)\n\n# Modules\n\nCommon redux modules for dApps.\n\n## Wallet\n\nThis module takes care of connecting to MetaMask/Ledger, and insert in the state some useful information like address, network, mana and derivationPath.\n\n### Usage\n\nYou can use the following selectors importing them from `decentraland-dapps/dist/modules/wallet/selectors`:\n\n```tsx\ngetData = (state: State) =\u003e BaseWallet\ngetError = (state: State) =\u003e string\ngetNetwork = (state: State) =\u003e\n  'mainnet' | 'ropsten' | 'rinkeby' | 'kovan' | 'localhost'\ngetAddress = (state: State) =\u003e string\nisConnected = (state: State) =\u003e boolean\nisConnecting = (state: State) =\u003e boolean\n```\n\nAlso you can hook to the following actions from your reducers/sagas by importing them from `decentraland-dapps/dist/modules/wallet/actions`:\n\n```tsx\nCONNECT_WALLET_REQUEST\nCONNECT_WALLET_SUCCESS\nCONNECT_WALLET_FAILURE\n```\n\nAlso you can import types for those actions from that same file:\n\n```tsx\nConnectWalletRequestAction\nConnectWalletSuccessAction\nConnectWalletFailureAction\n```\n\nThis is an example of how you can wait for the `CONNECT_WALLET_SUCCESS` action to trigger other actions:\n\n```tsx\n// modules/something/sagas.ts\n\nimport {\n  CONNECT_WALLET_SUCCESS,\n  ConnectWalletSuccessAction\n} from 'decentraland-dapps/dist/modules/wallet/actions'\nimport { fetchSomethingRequest } from './actions'\n\nexport function* saga() {\n  yield takeLatest(CONNECT_WALLET_SUCCESS, handleConnectWalletSuccess)\n}\n\nfunction* handleConnectWalletSuccess(action: ConnectWalletSuccessAction) {\n  yield put(fetchSomethingRequest())\n}\n```\n\n### Installation\n\nIn order to install this module you will need to add a provider, a reducer and a saga to your dapps.\n\n**Provider**:\n\nAdd the `\u003cWalletProvider\u003e` as a child of your `redux` provider. If you use `react-router-redux` or `connected-react-router` make sure the `\u003cConnectedRouter\u003e` is a child of the `\u003cWalletProvider\u003e` and not the other way around, like this:\n\n```tsx\nimport * as React from 'react'\nimport * as ReactDOM from 'react-dom'\nimport { Provider } from 'react-redux'\nimport { ConnectedRouter } from 'connected-react-router'\nimport WalletProvider from 'decentraland-dapps/dist/providers/WalletProvider'\nimport { store, history } from './store'\n\nReactDOM.render(\n  \u003cProvider store={store}\u003e\n    \u003cWalletProvider\u003e\n      \u003cConnectedRouter history={history}\u003e{/* Your App */}\u003c/ConnectedRouter\u003e\n    \u003c/WalletProvider\u003e\n  \u003c/Provider\u003e,\n  document.getElementById('root')\n)\n```\n\n**Reducer**:\n\nImport the `walletReducer` and add it at the root level of your dApp's reducer as `wallet`, like this:\n\n```ts\nimport { combineReducers } from 'redux'\nimport { walletReducer as wallet } from 'decentraland-dapps/dist/modules/wallet/reducer'\n\nexport const rootReducer = combineReducers({\n  wallet\n  // your other reducers\n})\n```\n\n**Saga**:\n\nYou will need to create a `walletSaga` and add it to your `rootSaga`:\n\n```ts\nimport { all } from 'redux-saga/effects'\nimport { walletSaga } from 'decentraland-dapps/dist/modules/wallet/sagas'\n\nexport function* rootSaga() {\n  yield all([\n    walletSaga()\n    // your other sagas here\n  ])\n}\n```\n\n### Advanced Usage\n\nYou'll need to supply in which chain you're going to work. It won't affect wallets like Metamask, where you can choose which network to use on the wallet itself, but's necessary for things like email/phone based wallets.\nIf you're using the [Navbar](#Navbar) container, this chain will determine in which chain the user **must be**. If they're on the incorrect chain (using a network picker with Metamask for example), a modal will pop up blocking the dapp until the state changes.\n\nRemember that the chain id is the number that represents a particular network, 1 being `mainnet`, 3 being `ropsten`, etc.\n\n\u003cdetails\u003e\u003csummary\u003eLearn More\u003c/summary\u003e\n\u003cp\u003e\n\nInstead of importing `walletSaga`, use `createWalletSaga`:\n\n**Saga**:\n\n```ts\nimport { all } from 'redux-saga/effects'\nimport { createWalletSaga } from 'decentraland-dapps/dist/modules/wallet/sagas'\n\nconst walletSaga = createWalletSaga({ CHAIN_ID: process.env.chainId })\nexport function* rootSaga() {\n  yield all([\n    walletSaga()\n    // your other sagas here\n  ])\n}\n```\n\n**Actions**:\n\nIf you want to hook a callback to connect the wallet, there're two things to keep in mind. The process of connecting a wallet consists in two steps, first `enabling` it and then properly connecting it. The set of actions to keep in mind are the following (all from `decentraland-dapps/dist/modules/wallet/actions`):\n\n```tsx\nenableWalletRequest\nenableWalletSuccess\nenableWalletFailure\n```\n\nWith it's corresponding actions and types from the same file:\n\n```tsx\nENABLE_WALLET_REQUEST\nENABLE_WALLET_SUCCESS\nENABLE_WALLET_FAILURE\n\nEnableWalletRequestAction\nEnableWalletSuccessAction\nEnableWalletFailureAction\n```\n\nThe wallet saga will listen for `ENABLE_WALLET_SUCCESS` and automatically call `CONNECT_WALLET_REQUEST`. If you use `connect wallet` without enabling first it will only work if you enabled first and it'll stop working once the user disconnects the wallet from the site (if she ever does).\n\nAll of this is handled by [SignInPage](#signinpage) behind the scenes, so you can just use that instead. Remember to add [\u003cWalletProvider /\u003e](#installation).\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n### MetaTransactions\n\nThe wallet module includes utilities to send transaction and meta transactions automatically. Meta transactions are sent when trying to send a transaction to a network different from the one connected to.\n\nWeb2 wallets will be prompted to accept or reject the transaction using the Web2TransactionModal. This modal is required for the sendTransaction function to work and must be initiated in your dApp.\n\n## Storage\n\nThe storage module allows you to save parts of the redux store in localStorage to make them persistent and migrate it from different versions without loosing it.\nThis module is required to use other modules like `Transaction`, `Translation` and `Wallet`.\n\n### Installation\n\nYou need to add a middleware and two reducers to your dApp.\n\n**Middleware**:\n\nYou will need to create a `storageMiddleware` and add apply it along with your other middlewares:\n\n```ts\n// store.ts\nimport { applyMiddleware, compose, createStore } from 'redux'\nimport { createStorageMiddleware } from 'decentraland-dapps/dist/modules/storage/middleware'\nimport { migrations } from './migrations'\n\nconst composeEnhancers =\n  (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose\n\nconst { storageMiddleware, loadStorageMiddleware } = createStorageMiddleware({\n  storageKey: 'storage-key' // this is the key used to save the state in localStorage (required)\n  paths: [] // array of paths from state to be persisted (optional)\n  actions: [] // array of actions types that will trigger a SAVE (optional)\n  migrations: migrations // migration object that will migrate your localstorage (optional)\n})\n\nconst middleware = applyMiddleware(\n  // your other middlewares\n  storageMiddleware\n)\nconst enhancer = composeEnhancers(middleware)\nconst store = createStore(rootReducer, enhancer)\n\nloadStorageMiddleware(store)\n```\n\n**Migrations**:\n\n`migrations` looks like\n\n`migrations.ts`:\n\n```ts\nexport const migrations = {\n  2: migrateToVersion2(data),\n  3: migrateToVersion3(data)\n}\n```\n\nWhere every `key` represent a migration and every `method` should return the new localstorage data:\n\n```ts\nfunction migrateToVersion2(data) {\n  return omit(data, 'translations')\n}\n```\n\nYou don't need to care about updating the version of the migration because it will be set automatically.\n\n**Reducer**:\n\nYou will need to add `storageReducer` as `storage` to your `rootReducer` and then wrap the whole reducer with `storageReducerWrapper`\n\n```ts\nimport { combineReducers } from 'redux'\n\nimport {\n  storageReducer as storage,\n  storageReducerWrapper\n} from 'decentraland-dapps/dist/modules/storage/reducer'\n\nexport const rootReducer = storageReducerWrapper(\n  combineReducers({\n    storage\n    // your other reducers\n  })\n)\n```\n\n### Advanced Usage\n\nThis module is necessary to use other modules like `Transaction`, `Translation` and `Wallet`, but you can also use it to make other parts of your dApp's state persistent\n\n\u003cdetails\u003e\u003csummary\u003eLearn More\u003c/summary\u003e\n\u003cp\u003e\n\nThe first parameter of `createStorageMiddleware` is the key used to store the state data in localStorage (required).\n\nThe second parameter is an array of paths from the state that you want to be stored, ie:\n\n```ts\nconst paths = [['invites'][('user', 'name')]]\n```\n\nThat will make `state.invites` and `state.user.name` persistent. This parameter is optional and you don't have to configure it to use the `Transaction` and/or `Translation` modules.\n\nThe third parameter is an array of action types that will trigger a SAVE of the state in localStorage, ie:\n\n```ts\nconst actionTypes = [SEND_INVITE_SUCCESS]\n```\n\nThis parameter is optional and is and you don't have to configure it to use the `Transaction` and/or `Translation` modules.\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n## Transaction\n\nThe transaction module allows you to watch for pending transactions and keep track of the transaction history.\n\n### Dependencies\n\nThis module requires you to install the [Storage](https://github.com/decentraland/decentraland-dapps#storage) module in order to work.\n\n### Usage\n\nWhen you have an action that creates a transaction and you want to watch it, you can do with `buildTransactionPayload`:\n\n```ts\nimport { action } from 'typesafe-actions'\nimport { buildTransactionPayload } from 'decentraland-dapps/dist/modules/transaction/utils'\n\n// Send Invite\n\nexport const SEND_INVITE_REQUEST = '[Request] Send Invite'\nexport const SEND_INVITE_SUCCESS = '[Success] Send Invite'\nexport const SEND_INVITE_FAILURE = '[Failure] Send Invite'\n\nexport const sendInvitesRequest = (address: string) =\u003e\n  action(SEND_INVITE_REQUEST, {\n    address\n  })\n\nexport const sendInvitesSuccess = (txHash: string, address: string) =\u003e\n  action(SEND_INVITE_SUCCESS, {\n    ...buildTransactionPayload(txHash, {\n      address\n    }),\n    address\n  })\n\nexport const sendInvitesFailure = (address: string, errorMessage: string) =\u003e\n  action(SEND_INVITE_FAILURE, {\n    address,\n    errorMessage\n  })\n\nexport type SendInvitesRequestAction = ReturnType\u003ctypeof sendInvitesRequest\u003e\nexport type SendInvitesSuccessAction = ReturnType\u003ctypeof sendInvitesSuccess\u003e\nexport type SendInvitesFailureAction = ReturnType\u003ctypeof sendInvitesFailure\u003e\n```\n\nOr `buildTransactionWithReceiptPayload` if you need the tx event logs\n\n```ts\nexport const sendInvitesSuccess = (txHash: string, address: string) =\u003e\n  action(SEND_INVITE_SUCCESS, {\n    ...buildTransactionWithReceiptPayload(txHash, {\n      address\n    }),\n    address\n  })\n```\n\nIt will save the event logs inside `{ receipt: { logs: [] } }` after the tx was confirmed\n\nThen you can use the selectors `getPendingTransactions` and `getTransactionHistory` from `decentraland-dapps/dist/modules/transaction/selectors` to get the list of pending transactions and the transaction history.\n\n### Installation\n\nYou need to add a middleware, a reducer and a saga to use this module.\n\n**Middleware**:\n\nCreate the `transactionMiddleware` and apply it\n\n```ts\n// store.ts\nimport { createTransactionMiddleware } from 'decentraland-dapps/dist/modules/transaction/middleware'\nconst transactionMiddleware = createTransactionMiddleware()\n\nconst middleware = applyMiddleware(\n  // your other middlewares\n  transactionMiddleware\n)\n```\n\n**Reducer**:\n\nAdd `transactionReducer` as `transaction` to your `rootReducer`\n\n```ts\nimport { combineReducers } from 'redux'\nimport { transactionReducer as transaction } from 'decentraland-dapps/dist/modules/transaction/reducer'\n\nexport const rootReducer = combineReducers({\n  transaction\n  // your other reducers\n})\n```\n\n**Saga**:\n\nAdd `transactionSaga` to your `rootSaga`\n\n```ts\nimport { all } from 'redux-saga/effects'\nimport { transactionSaga } from 'decentraland-dapps/dist/modules/transaction/sagas'\n\nexport function* rootSaga() {\n  yield all([\n    transactionSaga()\n    // your other sagas\n  ])\n}\n```\n\n### Advanced Usage\n\nYou can make your reducers listen to confirmed transactions and update your state accordingly\n\n\u003cdetails\u003e\u003csummary\u003eLearn More\u003c/summary\u003e\n\u003cp\u003e\n\nTaking the example of the `SEND_INVITE_SUCCESS` action type shown in the `Usage` section above, let's say we want to decrement the amount of available invites after the transaction is mined, we can do so by adding the `FETCH_TRANSACTION_SUCCESS` action type in our reducer:\n\n```diff\n// modules/invite/reducer\nimport { AnyAction } from 'redux'\nimport { loadingReducer } from 'decentraland-dapps/dist/modules/loading/reducer'\nimport {\n  FETCH_INVITES_REQUEST,\n  FETCH_INVITES_SUCCESS,\n  FETCH_INVITES_FAILURE,\n  FetchInvitesSuccessAction,\n  FetchInvitesFailureAction,\n  FetchInvitesRequestAction,\n  SEND_INVITE_SUCCESS\n} from './actions'\nimport { FETCH_TRANSACTION_SUCCESS, FetchTransactionSuccessAction } from 'decentraland-dapps/dist/modules/transaction/actions';\n\nexport type InviteState = {\n  loading: AnyAction[]\n  data: {\n    [address: string]: number\n  }\n  error: null | string\n}\n\nexport type InviteReducerAction =\n  | FetchInvitesRequestAction\n  | FetchInvitesSuccessAction\n  | FetchInvitesFailureAction\n  | FetchTransactionSuccessAction\n\nexport const INITIAL_STATE: InviteState = {\n  loading: [],\n  data: {},\n  error: null\n}\n\nexport function invitesReducer(\n  state: InviteState = INITIAL_STATE,\n  action: InviteReducerAction\n): InviteState {\n  switch (action.type) {\n    case FETCH_INVITES_REQUEST: {\n      return {\n        ...state,\n        loading: loadingReducer(state.loading, action)\n      }\n    }\n    case FETCH_INVITES_SUCCESS: {\n      return {\n        loading: loadingReducer(state.loading, action),\n        data: {\n          ...state.data,\n          [action.payload.address]: action.payload.amount\n        },\n        error: null\n      }\n    }\n    case FETCH_INVITES_FAILURE: {\n      return {\n        ...state,\n        loading: loadingReducer(state.loading, action),\n        error: action.payload.errorMessage\n      }\n    }\n    case FETCH_TRANSACTION_SUCCESS: {\n      const { transaction } = action.payload\n      switch (transaction.actionType) {\n        case SEND_INVITE_SUCCESS: {\n          const { address } = (transaction as any).payload\n          return {\n            ...state,\n            data: {\n              ...state.data,\n              [address]: state.data[address] - 1\n            }\n          }\n        }\n        default:\n          return state\n      }\n    }\n    default: {\n      return state\n    }\n  }\n}\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n## Authorization\n\nThis module allows you to grant/revoke approvals to a token. It works for both allowance and approval for all.\n\n### Dependencies\n\nThis module depends on the [wallet](#wallet) and the [transactions](#transactions) module\n\n### Usage\n\nAfter [installing](#installing-3) the module, you'll need to initialize the authorizations you want to query using the following action:\n\n```ts\nfetchAuthorizationsRequest(authorizations: Authorization[])\n```\n\nThat action will query the blockchain for each authorization and update the state so you can check it later. You can hook to:\n\n```ts\nFETCH_AUTHORIZATIONS_REQUEST\nFETCH_AUTHORIZATIONS_SUCCESS\nFETCH_AUTHORIZATIONS_FAILURE\n```\n\nOnce you have this hooked up, you can either grant or revoke a token by using:\n\n```ts\ngrantTokenRequest(authorization: Authorization)\nrevokeTokenRequest(authorization: Authorization)\n```\n\nYou can hook to the following actions:\n\n```ts\nGRANT_TOKEN_REQUEST\nGRANT_TOKEN_SUCCESS\nGRANT_TOKEN_FAILURE\n\nREVOKE_TOKEN_REQUEST\nREVOKE_TOKEN_SUCCESS\nREVOKE_TOKEN_FAILURE\n```\n\nKeep in mind that each of these actions send a transaction, so if you wan't to check if they're done, check the action type of the `FETCH_TRANSACTION_SUCCESS` action. More info on the [transactions](#transactions) module\n\n### Installation\n\n**Reducer**\n\nAdd the `authorizationReducer` as `authorization` to your `rootReducer`:\n\n```ts\nimport { combineReducers } from 'redux'\nimport { authorizationReducer as authorization } from 'decentraland-dapps/dist/modules/authorization/reducer'\n\nexport const rootReducer = combineReducers({\n  authorization\n  // your other reducers\n})\n```\n\n**Sagas**\n\nAdd the `authorizationSaga` to the `rootSaga`:\n\n```ts\nimport { all } from 'redux-saga/effects'\nimport { authorizationSaga } from 'decentraland-dapps/dist/modules/authorization/sagas'\n\nexport function* rootSaga() {\n  yield all([\n    authorizationSaga()\n    // your other sagas\n  ])\n}\n```\n\n## Translation\n\nThis module allows you to do i18n.\n\n### Dependencies\n\nThis module has an optional dependency on [Storage](https://github.com/decentraland/decentraland-dapps#storage) module to cache translations and boot the application faster. To learn more read the `Advanced Usage` section of this module.\n\n### Usage\n\nUsing the helper `t()` you can add translations to your dApp\n\n```tsx\nimport * as React from 'react'\nimport { t } from 'decentraland-dapps/dist/modules/translation/utils'\n\nexport default class BuyButton extends React.PureComponent {\n  render() {\n    return \u003cbutton\u003e{t('but_page.buy_button')}\u003c/button\u003e\n  }\n}\n```\n\nThen you just have to provide locale files like this:\n\n_en.json_\n\n```json\n{\n  \"buy_page\": {\n    \"buy_button\": \"Buy\"\n  }\n}\n```\n\n_es.json_\n\n```json\n{\n  \"buy_page\": {\n    \"buy_button\": \"Comprar\"\n  }\n}\n```\n\nYon can dispatch the `changeLocale(locale: string)` action from `decentraland-dapps/dist/modules/translation/actions` to change the language\n\n### Installation\n\nYou will need to add a provider, a reducer and a saga to use this module\n\n**Provider**:\n\nAdd the `\u003cTranslationProvider\u003e` as a child of your `redux` provider, passing the `locales` that you want to support. If you use `react-router-redux` or `connected-react-router` make sure the `\u003cConnectedRouter\u003e` is a child of the `\u003cTranslationProvider\u003e` and not the other way around, like this:\n\n```tsx\nimport * as React from 'react'\nimport * as ReactDOM from 'react-dom'\nimport { Provider } from 'react-redux'\nimport { ConnectedRouter } from 'connected-react-router'\nimport TranslationProvider from 'decentraland-dapps/dist/providers/TranslationProvider'\nimport { store, history } from './store'\n\nReactDOM.render(\n  \u003cProvider store={store}\u003e\n    \u003cTranslationProvider locales={['en', 'es', 'ko', 'zh']}\u003e\n      \u003cConnectedRouter history={history}\u003e{/* Your App */}\u003c/ConnectedRouter\u003e\n    \u003c/TranslationProvider\u003e\n  \u003c/Provider\u003e,\n  document.getElementById('root')\n)\n```\n\n**Reducer**:\n\nAdd the `translationReducer` as `translation` to your `rootReducer`:\n\n```ts\nimport { combineReducers } from 'redux'\nimport { translationReducer as translation } from 'decentraland-dapps/dist/modules/translation/reducer'\n\nexport const rootReducer = combineReducers({\n  translation\n  // your other reducers\n})\n```\n\n**Saga**:\n\nCreate a `translationSaga` and add it to your `rootSaga`. You need to provide an object containing all the translations, or a function that takes the `locale` and returns a `Promise` of the translations for that locale (you can use that to fetch the translations from a server instead of bundling them in the app). Here are examples for the two options:\n\n1. Bundling the translations in the dApp:\n\n_en.json_\n\n```json\n{\n  \"buy_page\": {\n    \"buy_button\": \"Buy\"\n  }\n}\n```\n\n_es.json_\n\n```json\n{\n  \"buy_page\": {\n    \"buy_button\": \"Comprar\"\n  }\n}\n```\n\n_translations.ts_\n\n```ts\nconst en = require('./en.json')\nconst es = require('./es.json')\nexport { en, es }\n```\n\n_sagas.ts_\n\n```ts\nimport { all } from 'redux-saga/effects'\nimport { createTranslationSaga } from 'decentraland-dapps/dist/modules/translation/sagas'\nimport * as translations from './translations'\n\nexport const translationSaga = createTranslationSaga({\n  translations\n})\n\nexport function* rootSaga() {\n  yield all([\n    translationSaga()\n    // your other sagas\n  ])\n}\n```\n\n2. Fetching translations from server\n\n_sagas.ts_\n\n```ts\nimport { all } from 'redux-saga/effects'\nimport { createTranslationSaga } from 'decentraland-dapps/dist/modules/translation/sagas'\nimport { api } from 'lib/api'\n\nexport const translationSaga = createTranslationSaga({\n  getTranslation: locale =\u003e api.fetchTranslations(locale)\n})\n\nexport function* rootSaga() {\n  yield all([\n    translationSaga()\n    // your other sagas\n  ])\n}\n```\n\nRead the `Advanced Usage` section below to learn how to cache translations and make your application boot faster.\n\n### Advanced Usage\n\nYou can use the [Storage](https://github.com/decentraland/decentraland-dapps#storage) module to cache translations (read `2. Fetching translations from server` above).\n\n\u003cdetails\u003e\u003csummary\u003eLearn More\u003c/summary\u003e\n\u003cp\u003e\n\nAfter [installing the Storage module](https://github.com/decentraland/decentraland-dapps#storage) you can persist the translations by adding `'translation'` to your storage middleware paths:\n\n```ts\n// store.ts\n\nconst { storageMiddleware, loadStorageMiddleware } = createStorageMiddleware({\n  storageKey: 'my-dapp-storage',\n  paths: ['translation']\n})\n```\n\nThis will store the translation module in `localStorage`, so next time your application is started it will boot with all the translations populated before even fetching them from the server.\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n## Analytics\n\nThe analytics module let's integrate Segment into your dApp.\n\nYou need to have the `Wallet` module installed in order to send `identify` events.\n\nThis module will import the segment snippet into your dApp. Be aware that the middleware must be loaded before using segment methods.\n\nTo send `track` events, add an `analytics.ts` file and require it from your entry point, and use the `add()` helper to add actions that you want to track:\n\n```ts\n// analytics.ts\nimport { add } from 'decentraland-dapps/dist/modules/analytics/utils'\nimport {\n  CREATE_VOTE_SUCCESS,\n  CreateVoteSuccessAction\n} from 'modules/vote/actions'\n\nadd(CREATE_VOTE_SUCCESS, 'Vote', (action: CreateVoteSuccessAction) =\u003e ({\n  poll_id: action.payload.vote.poll_id,\n  option_id: action.payload.vote.option_id,\n  address: action.payload.wallet.address\n}))\n```\n\nThe first parameter is the action type that you want to track (required).\n\nThe second parameter is the event name for that action (it will show up with that name in Segment). If none provided the action type will be used as the event name.\n\nThe third parameter is a function that takes the action and returns the data that you want to associate with that event (it will be sent to Segment). If none is provided the whole action will be sent.\n\n### Installation\n\nYou need to apply a middleware and a saga to use this module\n\n**Middleware**:\n\n```ts\n// store.ts\nimport { createAnalyticsMiddleware } from '@dapps/modules/analytics/middleware'\n\nconst analyticsMiddleware = createAnalyticsMiddleware('SEGMENT WRITE KEY')\n\nconst middleware = applyMiddleware(\n  // your other middlewares\n  analyticsMiddleware\n)\nconst enhancer = composeEnhancers(middleware)\nconst store = createStore(rootReducer, enhancer)\n```\n\n**Saga**:\n\n```ts\nimport { all } from 'redux-saga/effects'\nimport { analyticsSaga } from 'decentraland-dapps/dist/modules/analytics/sagas'\n\nexport function* rootSaga() {\n  yield all([\n    analyticsSaga()\n    // your other sagas\n  ])\n}\n```\n\n### Page tracking\n\nIn order to track all page change you will need to use the analytics `page` function. There is already an exported hook you can use the will be triggered everytime a location change in the app\n\nNote: It is important that this hook is triggered in any component inside the router provider.\n\n```ts\nimport usePageTracking from 'decentraland-dapps/dist/hooks/usePageTracking'\n\nfunction Routes() {\n  usePageTracking()\n  /// Route rendering\n}\n```\n\n### Advanced Usage\n\nYou can use the same redux action type to generate different Segment events if you pass a function as the second parameter instead of a string:\n\n```ts\nadd(AUTHORIZE_LAND_SUCCESS, action =\u003e\n  action.isAuthorized ? 'Authorize LAND' : 'Unauthorize LAND'\n)\n```\n\n## Loading\n\nThe loading module is used to keep track of async actions in the state.\n\n### Usage\n\nYou can use the selectors `isLoading(state)` and `isLoadingType(state, ACTION_TYPE)` from `decentraland-dapps/dist/modules/loading/selectors` to know if a domain has pending actions or if a specific action is still pending\n\nIn order to use these selectors you need to use the `loadingReducer` within your domain reducers, here is an example:\n\n```ts\nimport {\n  loadingReducer,\n  LoadingState\n} from 'decentraland-dapps/dist/modules/loading/reducer'\nimport {\n  FETCH_INVITES_REQUEST,\n  FETCH_INVITES_SUCCESS,\n  FETCH_INVITES_FAILURE,\n  FetchInvitesSuccessAction,\n  FetchInvitesFailureAction,\n  FetchInvitesRequestAction\n} from './actions'\n\nexport type InviteState = {\n  loading: LoadingState\n  data: {\n    [address: string]: number\n  }\n  error: null | string\n}\n\nexport const INITIAL_STATE: InviteState = {\n  loading: [],\n  data: {},\n  error: null\n}\n\nexport type InviteReducerAction =\n  | FetchInvitesRequestAction\n  | FetchInvitesSuccessAction\n  | FetchInvitesFailureAction\n\nexport function invitesReducer(\n  state: InviteState = INITIAL_STATE,\n  action: InviteReducerAction\n): InviteState {\n  switch (action.type) {\n    case FETCH_INVITES_REQUEST: {\n      return {\n        ...state,\n        loading: loadingReducer(state.loading, action)\n      }\n    }\n    case FETCH_INVITES_SUCCESS: {\n      return {\n        loading: loadingReducer(state.loading, action),\n        data: {\n          ...state.data,\n          [action.payload.address]: action.payload.amount\n        },\n        error: null\n      }\n    }\n    case FETCH_INVITES_FAILURE: {\n      return {\n        ...state,\n        loading: loadingReducer(state.loading, action),\n        error: action.payload.errorMessage\n      }\n    }\n    default: {\n      return state\n    }\n  }\n}\n```\n\nNow we can for example use the selector `isLoadingType(state.invite.loading, FETCH_INVITES_REQUEST)` to know if that particular action is still pending, or `isLoading(states.invite)` to know if there's any pending action for that domain.\n\nAlso, all the pending actions are stored in an array in `state.invite.loading` so we can use that information in the UI if needed (i.e. disable a button)\n\n## Modal\n\nLeverages redux state and provides actions to open and close each modal by name. It provides a few simple actions:\n\n```ts\nopenModal(name: string, metadata: any = null)\ncloseModal(name: string)\ncloseAllModals()\ntoggleModal(name: sgtring)\n```\n\nIt also provides a selector to get the open modals:\n\n```\ngetOpenModals(state): ModalState\n```\n\n### Installation\n\nIn order to use this module you need to add a reducer and a provider.\n\n**Provider**:\n\nAdd the `\u003cModalProvider\u003e` as a parent of your routes. It takes an object of `{ {modalName: string]: React.Component }` as a prop (`components`). It'll use it to render the appropiate modal when you call `openModal(name: string)`\n\n```tsx\nimport * as React from 'react'\nimport * as ReactDOM from 'react-dom'\nimport { Provider } from 'react-redux'\nimport { ConnectedRouter } from 'connected-react-router'\nimport ModalProvider from 'decentraland-dapps/dist/providers/ModalProvider'\nimport * as modals from 'components/Modals'\nimport { store, history } from './store'\n\nReactDOM.render(\n  \u003cProvider store={store}\u003e\n    \u003cModalProvider components={modals}\u003e\n      \u003cConnectedRouter history={history}\u003e{/* Your App */}\u003c/ConnectedRouter\u003e\n    \u003c/ModalProvider\u003e\n  \u003c/Provider\u003e,\n  document.getElementById('root')\n)\n```\n\n**Reducer**:\n\nAdd the `modalReducer` as `modal` to your `rootReducer`:\n\n```ts\nimport { combineReducers } from 'redux'\nimport { modalReducer as modal } from 'decentraland-dapps/dist/modules/modal/reducer'\n\nexport const rootReducer = combineReducers({\n  modal\n  // your other reducers\n})\n```\n\n### Advanced Usage\n\nYou can have add more strict typing to the actions:\n\n\u003cdetails\u003e\u003csummary\u003eLearn More\u003c/summary\u003e\n\u003cp\u003e\nThe modal actions allow for a generic type for the name. So say you want to type the name of your available modals, you can create a `modal` module in your dApp and add the following files:\n\n**Types**:\n\n```ts\n// modules/types/actions.ts\nimport * as modals from 'components/Modals' // same import as the one use for \u003cModalProvider /\u003e\n\nexport ModalName = keyof typeof modals\n```\n\n**Actions**:\n\n```ts\n// modules/modal/actions.ts\nimport { getModalActions } from 'decentraland-dapps/dist/modules/modal/actions'\nimport { ModalName } from './types'\n\nconst { openModal, closeModal, toggleModal } = getModalActions\u003cModalName\u003e()\n\nexport * from 'decentraland-dapps/dist/modules/modal/actions'\nexport { openModal, closeModal, toggleModal }\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n## Toasts\n\nLeverages redux state and provides actions to show and hide toasts. It provides a few simple actions:\n\n```ts\nshowToast(toast: Omit\u003cToast, 'id'\u003e)\nhideToast(id: number)\n```\n\nYou can check the properties a toast has [here](/src/modules/toast/types.ts). It extends the props already defined on [decentraland-ui's toast](https://github.com/decentraland/ui/blob/master/src/components/Toast/Toast.tsx)\n\nIt also provides a selector to get the open toasts:\n\n```\ngetToasts(state): Toast[]\n```\n\n### Installation\n\nIn order to use this module you need to add a reducer, a provider and a saga.\n\n**Provider**:\n\nAdd the `\u003cToastProvider\u003e` as a parent of your routes. It takes an optional `position` param to set where you want the toasts to appear. It'll default to `top left`\n\n```tsx\nimport * as React from 'react'\nimport * as ReactDOM from 'react-dom'\nimport { Provider } from 'react-redux'\nimport { ConnectedRouter } from 'connected-react-router'\nimport ToastProvider from 'decentraland-dapps/dist/providers/ToastProvider'\nimport * as modals from 'components/Modals'\nimport { store, history } from './store'\n\nReactDOM.render(\n  \u003cProvider store={store}\u003e\n    \u003cToastProvider position=\"bottom right\"\u003e\n      \u003cConnectedRouter history={history}\u003e{/* Your App */}\u003c/ConnectedRouter\u003e\n    \u003c/ToastProvider\u003e\n  \u003c/Provider\u003e,\n  document.getElementById('root')\n)\n```\n\n**Reducer**:\n\nAdd the `toastReducer` as `toast` to your `rootReducer`:\n\n```ts\nimport { combineReducers } from 'redux'\nimport { toastReducer as toast } from 'decentraland-dapps/dist/modules/toast/reducer'\n\nexport const rootReducer = combineReducers({\n  toast\n  // your other reducers\n})\n```\n\n**Saga**:\n\nYou will need to create a `toastSaga` and add it to your `rootSaga`:\n\n```ts\nimport { all } from 'redux-saga/effects'\nimport { toastSaga } from 'decentraland-dapps/dist/modules/wallet/sagas'\n\nexport function* rootSaga() {\n  yield all([\n    toastSaga()\n    // your other sagas here\n  ])\n}\n```\n\nToasts themselves do not do any async action, but this is needed to render each toast properly, without overloading the redux state with unnecesary information.\n\n## Profile\n\nLeverages the redux state and provides actions and selectors to work with profiles.\n\n### Actions\n\nThe module exposes the following actions:\n\nThe `loadProfileRequest` action will trigger a profile fetch through the profile sagas that will result, if successful, in the profile metadata being loaded. The success and failure actions of the request action are also included and will be used to signal a successful or a failing request.\n\nThe `setProfileAvatarDescriptionRequest` action will trigger a change in the first avatar of the user's profile, that will result in a new entity being deployed for that profile, with the description of the avatar changed for the one specified in the action. The success and failure actions of the request action are also included and will be used to signal a successful or a failing request.\n\nThe `clearProfileError` action will clear any profile request errors from the store.\n\n### Installation\n\nTo install the profile module, just import it and add it to the store by combining the existing reducers with the one provided in the profile module.\n\n```ts\nimport { profileReducer as profile } from 'decentraland-dapps/dist/modules/profile/reducer'\n\nexport const createRootReducer = (history: History) =\u003e\n  combineReducers({\n    profile,\n    otherReducer\n  })\n\nexport type RootState = ReturnType\u003cReturnType\u003ctypeof createRootReducer\u003e\u003e\n```\n\n## Credits\n\nThis module helps manage credits in the Decentraland marketplace. It handles fetching credits and provides real-time updates through Server-Sent Events (SSE).\n\n### Usage\n\nYou can start and stop real-time credit updates using SSE:\n\n```ts\nimport {\n  startCreditsSSE,\n  stopCreditsSSE\n} from 'decentraland-dapps/dist/modules/credits/actions'\n\n// Start real-time credit updates when component mounts\ndispatch(startCreditsSSE(address))\n\n// Stop real-time updates when component unmounts\ndispatch(stopCreditsSSE())\n```\n\nFor backward compatibility, the following aliases are also available:\n\n```ts\nimport {\n  startCreditsAutoPolling, // alias for startCreditsSSE\n  stopCreditsAutoPolling // alias for stopCreditsSSE\n} from 'decentraland-dapps/dist/modules/credits/actions'\n```\n\nThe module will automatically:\n\n1. Fetch the initial credits state\n2. Establish an SSE connection with the server\n3. Update the credits in real-time whenever changes occur on the server\n4. Check if the credits feature is enabled before establishing a connection\n\n**Selectors**:\n\n```ts\nimport { getCredits } from 'decentraland-dapps/dist/modules/credits/selectors'\n\nconst credits = getCredits(state, address)\n```\n\n**Installation**:\n\nAdd the `creditsReducer` to your root reducer:\n\n```ts\nimport { combineReducers } from 'redux'\nimport { creditsReducer as credits } from 'decentraland-dapps/dist/modules/credits/reducer'\n\nexport const rootReducer = combineReducers({\n  credits\n  // your other reducers\n})\n```\n\nAdd the `creditsSaga` to your root saga:\n\n```ts\nimport { all } from 'redux-saga/effects'\nimport { creditsSaga } from 'decentraland-dapps/dist/modules/credits/sagas'\nimport { CreditsClient } from 'decentraland-dapps/dist/modules/credits/CreditsClient'\n\n// Create a credits client - make sure your server supports SSE at /users/{address}/credits/stream\nconst creditsClient = new CreditsClient(API_URL)\n\nexport function* rootSaga() {\n  yield all([\n    creditsSaga({ creditsClient })\n    // your other sagas\n  ])\n}\n```\n\n**Server Requirements**:\n\nYour server needs to provide an SSE endpoint at `/users/{address}/credits/stream` that:\n\n1. Keeps the connection open\n2. Sends credit updates in the same format as the regular credits endpoint\n3. Properly handles connection errors and retries\n\nHere's an example of how the server might implement the SSE endpoint:\n\n```typescript\n// Server-side (Node.js with Express)\napp.get('/users/:address/credits/stream', (req, res) =\u003e {\n  const { address } = req.params\n\n  // Set up SSE connection\n  res.setHeader('Content-Type', 'text/event-stream')\n  res.setHeader('Cache-Control', 'no-cache')\n  res.setHeader('Connection', 'keep-alive')\n\n  // Send initial credits data\n  sendCreditsUpdate(address, res)\n\n  // Set up listener for credit changes for this address\n  const listener = (updatedAddress, creditsData) =\u003e {\n    if (updatedAddress === address) {\n      res.write(`data: ${JSON.stringify(creditsData)}\\n\\n`)\n    }\n  }\n\n  // Add listener to your event system\n  creditEventEmitter.on('credits-updated', listener)\n\n  // Clean up when connection closes\n  req.on('close', () =\u003e {\n    creditEventEmitter.off('credits-updated', listener)\n  })\n})\n```\n\n# Lib\n\nCommon libraries for dApps\n\n## API\n\nThe `BaseAPI` class can be extended to make requests and it handles the unwrapping of responses by `decentraland-server`\n\n### Usage\n\n```ts\n// lib/api\nimport { BaseAPI } from 'decentraland-dapps/dist/lib/api'\n\nconst URL = 'http://localhost/api'\n\nexport class API extends BaseAPI {\n  fetchSomething() {\n    return this.request('get', '/something', {})\n  }\n}\n\nexport const api = new API(URL)\n```\n\n## ETH\n\nEthereum helpers\n\n### Pristine Provider\n\nGet user's connected provider without being wrapped by any library\n\n```ts\nimport { getConnectedProvider } from 'decentraland-dapps/dist/lib/eth'\n\nasync function wrapProviderToEthers() {\n  const provider = await getConnectedProvider()\n  if (provider) {\n    return new etheres.providers.Web3Provider(provider)\n  }\n}\n```\n\n### Eth instance\n\nGet an Eth instance with your lib of choice\n\n```ts\nimport { Eth } from 'web3x/eth'\nimport { getConnectedProvider } from 'decentraland-dapps/dist/lib/eth'\n\nasync function doSomething() {\n  const provider = await getConnectedProvider()\n  if (!provider) throw new Error()\n\n  // web3x\n  const eth = new Eth(provider) // or new Eth(new LegacyProviderAdapter(provider))\n\n  // ethers\n  const eth = new ethers.providers.Web3Provider(provider)\n}\n```\n\n### Helpers\n\n- `isCucumberProvider`: Check if the provider is a `cucumberProvider`.\n- `isCoinbaseProvider`: Check if the provider is a `coinbaseProvider`.\n- `isDapperProvider`: Check if the provider is a _dapper's_ provider.\n- `isValidChainId`: Check if the chain id is valid.\n\n## Entities\n\nThe entities library provides a set of methods to retrieve or deploy entities.\n\n### Usage\n\nThe `deployEntity` method does everything needed to deploy an entity that doesn't have new files. It pre-procceses the entity to prepare it for the deployment, it creates the auth chain and asks the user to sign the deployment of the entity and then deploys it.\n\n```ts\n// lib/entities\nimport { EntitesOperator } from 'decentraland-dapps/dist/lib/entities'\n\nconst URL = 'http://localhost/api'\nconst profileEntity = { ... }\nconst entitiesOperator = new EntitesOperator(URL)\nawait entitiesOperator.deployEntityWithoutNewFiles(\n  entity,\n  EntityTypes.PROFILE,\n  anAddress\n)\n```\n\nThe `getProfileEntity` gets the first profile of all the profiles an address has.\n\n```ts\n// lib/entities\nimport { EntitesOperator } from 'decentraland-dapps/dist/lib/entities'\n\nconst URL = 'http://localhost/api'\nconst entitiesOperator = new EntitesOperator(URL)\nawait entitiesOperator.getProfile(anAddress)\n```\n\n# Containers\n\nCommon containers for dApps\n\n## Navbar\n\nThe `\u003cNavbar\u003e` container can be used in the same way as the `\u003cNavbar\u003e` component from [decentraland-ui](https://github.com/decentraland/ui) but it's already connected to the redux store. You can override any `NavbarProp` if you want to connect differently, and you can pass all the regular `NavbarProps` to it.\n\n### Dependencies\n\nThis container requires you to install the [Wallet](https://github.com/decentraland/decentraland-dapps#wallet). It also has support for i18n out of the box if you include the [Translation](https://github.com/decentraland/decentraland-dapps#translation) module.\n\n### Usage\n\nThis is an example of a `SomePage` component that uses the `\u003cNavbar\u003e` container:\n\n```tsx\nimport * as React from 'react'\n\nimport { Container } from 'decentraland-ui/dist/components/Container/Container'\nimport { NavbarPages } from 'decentraland-ui/dist/components/Navbar/Navbar.types'\nimport Navbar from 'decentraland-dapps/dist/containers/Navbar'\n\nimport './SomePage.css'\n\nexport default class SomePage extends React.PureComponent {\n  static defaultProps = {\n    children: null\n  }\n\n  render() {\n    const { children } = this.props\n\n    return (\n      \u003c\u003e\n        \u003cNavbar activePage={NavbarPages.MARKETPLACE} /\u003e\n        \u003cdiv className=\"SomePage\"\u003e\n          \u003cContainer\u003e{children}\u003c/Container\u003e\n        \u003c/div\u003e\n      \u003c/\u003e\n    )\n  }\n}\n```\n\nThis `\u003cNavbar\u003e` will show the user's blockie and mana balance because it is connected to the store.\n\n### i18n\n\nIf you are using the [Translation](https://github.com/decentraland/decentraland-dapps#translation) module, the `Navbar` contatiner comes with support for the 6 languages supported by the library.\n\n### Advanced Usage\n\nYou can override any of the default translations for any locale if you need to\n\n\u003cdetails\u003e\u003csummary\u003eLearn More\u003c/summary\u003e\n\u003cp\u003e\n\nSay you want to override some translations in English, just include any or all of the following translations in your `en.json` locale file:\n\n```json\n{\n  \"@dapps\": {\n    \"navbar\": {\n      \"account\": {\n        \"connecting\": \"Connecting...\",\n        \"signIn\": \"Sign In\"\n      },\n      \"menu\": {\n        \"agora\": \"Agora\",\n        \"blog\": \"Blog\",\n        \"docs\": \"Docs\",\n        \"marketplace\": \"Marketplace\"\n      }\n    }\n  }\n}\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n## Footer\n\nThe `\u003cFooter\u003e` container can be used in the same way as the `\u003cFooter\u003e` component from [decentraland-ui](https://github.com/decentraland/ui) but it's already connected to the redux store. You can override any `FooterProps` if you want to connect differently, and you can pass all the regular `FooterProps` to it.\n\n### Dependencies\n\nThe `\u003cFooter\u003e` container has support for i18n out of the box if you include the [Translation](https://github.com/decentraland/decentraland-dapps#translation) module.\n\n### Usage\n\nThis is an example of a `SomePage` component that uses the `\u003cFooter\u003e` container:\n\n```tsx\nimport * as React from 'react'\n\nimport { Container } from 'decentraland-ui/dist/components/Container/Container'\nimport Navbar from 'decentraland-dapps/dist/containers/Navbar'\n\nimport './SomePage.css'\n\nexport default class SomePage extends React.PureComponent {\n  render() {\n    const { children } = this.props\n    return (\n      \u003c\u003e\n        \u003cdiv className=\"SomePage\"\u003e\n          \u003cContainer\u003e{children}\u003c/Container\u003e\n        \u003c/div\u003e\n        \u003cFooter locales={['en', 'es']} /\u003e\n      \u003c/\u003e\n    )\n  }\n}\n```\n\nThis `\u003cFooter\u003e` will show only English and Spanish as the options in the language dropdown. If you don't provide any it will use only English.\n\n### i18n\n\nIf you are using the [Translation](https://github.com/decentraland/decentraland-dapps#translation) module, the `Footer` contatiner comes with support for the 6 languages supported by the library.\n\n### Advanced Usage\n\nYou can override any of the default translations for any locale if you need to\n\n\u003cdetails\u003e\u003csummary\u003eLearn More\u003c/summary\u003e\n\u003cp\u003e\n\nSay you want to override some translations in English, just include any or all of the following translations in your `en.json` locale file:\n\n```json\n{\n  \"@dapps\": {\n    \"footer\": {\n      \"dropdown\": {\n        \"en\": \"English\",\n        \"es\": \"Spanish\",\n        \"fr\": \"French\",\n        \"ja\": \"Japanese\",\n        \"ko\": \"Korean\",\n        \"zh\": \"Chinese\"\n      },\n      \"links\": {\n        \"content\": \"Content Policy\",\n        \"ethics\": \"Code of Ethics\",\n        \"home\": \"Home\",\n        \"privacy\": \"Privacy Policy\",\n        \"terms\": \"Terms of Use\"\n      }\n    }\n  }\n}\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n## SignInPage\n\nThe `\u003cSignInPage\u003e` container can be used in the same way as the `\u003cSignIn\u003e` component from [decentraland-ui](https://github.com/decentraland/ui), but it's already connected to the redux store. You can override any `SignInProp` if you want to connect differently, and you can pass all the regular `SignInProps` to it.\n\n### Dependencies\n\nThis container requires you to install the [Wallet](https://github.com/decentraland/decentraland-dapps#wallet). It also has support for i18n out of the box if you include the [Translation](https://github.com/decentraland/decentraland-dapps#translation) module.\n\n### Usage\n\nYou can import the `\u003cSignInPage\u003e` container and use it on your routes:\n\n```tsx\nimport * as React from 'react'\nimport { Switch, Route } from 'react-router-dom'\n\nimport SignInPage from 'decentraland-dapps/dist/containers/SignInPage'\n\n//...\n\u003cSwitch\u003e\n  \u003cRoute exact path='/' component={...} /\u003e\n  {/* your dapps routes... */}\n  \u003cRoute exact path='/sign-in' component={SignInPage} /\u003e\n\u003c/Switch\u003e\n```\n\n### i18n\n\nIf you are using the [Translation](https://github.com/decentraland/decentraland-dapps#translation) module, the `SignInPage` contatiner comes with support for the 6 languages supported by the library.\n\n### Advanced Usage\n\nYou can override any of the default translations for any locale if you need to\n\n\u003cdetails\u003e\u003csummary\u003eLearn More\u003c/summary\u003e\n\u003cp\u003e\n\nSay you want to override some translations in English, just include any or all of the following translations in your `en.json` locale file:\n\n```json\n{\n  \"@dapps\": {\n    \"sign_in\": {\n      \"connect\": \"Connect\",\n      \"connected\": \"Connected\",\n      \"connecting\": \"Connecting...\",\n      \"error\": \"Could not connect to wallet.\",\n      \"get_started\": \"Get Started\",\n      \"options\": {\n        \"desktop\": \"You can use the {metamask_link} extension or a hardware wallet like {ledger_nano_link}.\",\n        \"mobile\": \"You can use mobile browsers such as {coinbase_link} or {imtoken_link}.\"\n      }\n    }\n  }\n}\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n## Modal\n\nThe `\u003cModal\u003e` it's a shorthand for some common features used by modals provided to [ModalProvider](https://github.com/decentraland/decentraland-dapps#modal).\n\n### Usage\n\n```tsx\nimport * as React from 'react'\nimport Modal from 'decentraland-dapps/dist/containers/Modal'\n\nexport default class MyComponent extends React.PureComponent {\n  render() {\n    return (\n      const { name } = this.props\n\n      \u003cModal name={name} {/* Other Modal props from decentraland ui */\u003e\n        \u003cModal.Header\u003e\n        \u003c/Modal.Header\u003e\n        \u003cModal.Description\u003e\n        \u003c/Modal.Description\u003e\n      \u003c/Modal\u003e\n    )\n  }\n}\n```\n\nBehind the scenes Modal is setting the following properties:\n\n```js\nopen = { true }\nclassName = { name }\nsize = 'small'\nonClose = {\n  /*close the modal by name*/\n}\n```\n\n## TransactionLink\n\nThe `\u003cTransactionLink\u003e` can be used to link a transaction hash to Etherscan.io, and it connects to the redux store to know on which network the user is on.\n\n### Dependencies\n\nThis container requires you to install the [Wallet](https://github.com/decentraland/decentraland-dapps#wallet) module\n\n### Usage\n\n```tsx\nimport * as React from 'react'\nimport TransactionLink from 'decentraland-dapps/dist/containers/TransactionLink'\n\nexport default class MyComponent extends React.PureComponent {\n  render() {\n    return (\n      \u003cp\u003e\n        You sent an \u003cTransactionLink txHash={'0x...'}\u003einvite\u003c/TransactionLink\u003e\n      \u003c/p\u003e\n    )\n  }\n}\n```\n\n# Components\n\nCommon Components for dApps\n\n## Intercom\n\nThe `\u003cIntercom\u003e` will add an [intercom](https://www.intercom.com/) widget to your app.\n\n### Usage\n\n```tsx\nimport * as React from 'react'\nimport Intercom from 'decentraland-dapps/dist/components/Intercom'\n\nexport default class MyComponent extends React.PureComponent {\n  render() {\n    return (\n      \u003cdiv\u003e\n        {/* (...) */}\n        \u003cIntercom\n          appId={YOUR_APP_ID}\n          data={/*optional data sent to intercom */}\n        /\u003e\n      \u003c/div\u003e\n    )\n  }\n}\n```\n\n## Credits Module\n\n### Auto-polling credits\n\nThe credits module now supports auto-polling to keep the credits balance updated every 2 minutes. This is useful for applications where users need to see their up-to-date credit balance without having to manually refresh the page.\n\nTo use this feature:\n\n1. Start auto-polling when the user needs to see their updated credits:\n\n```typescript\nimport { startCreditsSSE } from 'decentraland-dapps/dist/modules/credits/actions'\n\n// Start auto-polling credits (address is the user's wallet address)\ndispatch(startCreditsSSE(address))\n```\n\n2. Stop auto-polling when it's no longer needed (e.g., when the user navigates away or logs out):\n\n```typescript\nimport { stopCreditsSSE } from 'decentraland-dapps/dist/modules/credits/actions'\n\n// Stop auto-polling credits\ndispatch(stopCreditsSSE())\n```\n\nThe polling will automatically check if the credits feature is enabled before fetching credits, so there's no need for additional checks in your application code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdecentraland%2Fdecentraland-dapps","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdecentraland%2Fdecentraland-dapps","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdecentraland%2Fdecentraland-dapps/lists"}