diff --git a/app/soapbox/components/ui/index.ts b/app/soapbox/components/ui/index.ts index 67ed49f37..302738ed9 100644 --- a/app/soapbox/components/ui/index.ts +++ b/app/soapbox/components/ui/index.ts @@ -27,3 +27,4 @@ export { default as Tabs } from './tabs/tabs'; export { default as Text } from './text/text'; export { default as Textarea } from './textarea/textarea'; export { default as Tooltip } from './tooltip/tooltip'; +export { default as Widget } from './widget/widget'; diff --git a/app/soapbox/components/ui/widget/widget.tsx b/app/soapbox/components/ui/widget/widget.tsx new file mode 100644 index 000000000..e5cc6dfd1 --- /dev/null +++ b/app/soapbox/components/ui/widget/widget.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import { Stack, Text } from 'soapbox/components/ui'; + +interface IWidgetTitle { + title: string | React.ReactNode +} + +const WidgetTitle = ({ title }: IWidgetTitle): JSX.Element => ( + {title} +); + +const WidgetBody: React.FC = ({ children }): JSX.Element => ( + {children} +); + +interface IWidget { + title: string | React.ReactNode, +} + +const Widget: React.FC = ({ title, children }): JSX.Element => { + return ( + + + {children} + + ); +}; + +export default Widget; diff --git a/app/soapbox/features/crypto_donate/components/crypto_address.js b/app/soapbox/features/crypto_donate/components/crypto_address.js deleted file mode 100644 index d3115853e..000000000 --- a/app/soapbox/features/crypto_donate/components/crypto_address.js +++ /dev/null @@ -1,60 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - -import { openModal } from 'soapbox/actions/modals'; -import Icon from 'soapbox/components/icon'; -import { CopyableInput } from 'soapbox/features/forms'; - -import { getExplorerUrl } from '../utils/block_explorer'; -import CoinDB from '../utils/coin_db'; - -import CryptoIcon from './crypto_icon'; - -export default @connect() -class CryptoAddress extends ImmutablePureComponent { - - static propTypes = { - address: PropTypes.string.isRequired, - ticker: PropTypes.string.isRequired, - note: PropTypes.string, - } - - handleModalClick = e => { - this.props.dispatch(openModal('CRYPTO_DONATE', this.props)); - e.preventDefault(); - } - - render() { - const { address, ticker, note } = this.props; - const title = CoinDB.getIn([ticker, 'name']); - const explorerUrl = getExplorerUrl(ticker, address); - - return ( -
-
- -
{title || ticker.toUpperCase()}
-
- - - - {explorerUrl && - - } -
-
- {note &&
{note}
} -
- -
-
- ); - } - -} diff --git a/app/soapbox/features/crypto_donate/components/crypto_address.tsx b/app/soapbox/features/crypto_donate/components/crypto_address.tsx new file mode 100644 index 000000000..716e4eee5 --- /dev/null +++ b/app/soapbox/features/crypto_donate/components/crypto_address.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { useDispatch } from 'react-redux'; + +import { openModal } from 'soapbox/actions/modals'; +import { Text, Icon, Stack, HStack } from 'soapbox/components/ui'; +import { CopyableInput } from 'soapbox/features/forms'; + +import { getExplorerUrl } from '../utils/block_explorer'; +import { getTitle } from '../utils/coin_db'; + +import CryptoIcon from './crypto_icon'; + +interface ICryptoAddress { + address: string, + ticker: string, + note?: string, +} + +const CryptoAddress: React.FC = (props): JSX.Element => { + const { address, ticker, note } = props; + + const dispatch = useDispatch(); + + const handleModalClick = (e: React.MouseEvent): void => { + dispatch(openModal('CRYPTO_DONATE', props)); + e.preventDefault(); + }; + + const title = getTitle(ticker); + const explorerUrl = getExplorerUrl(ticker, address); + + return ( + + + + + {title || ticker.toUpperCase()} + + + + + + + {explorerUrl && ( + + + + )} + + + + {note && ( + {note} + )} + +
+ +
+
+ ); +}; + +export default CryptoAddress; diff --git a/app/soapbox/features/crypto_donate/components/crypto_donate_panel.js b/app/soapbox/features/crypto_donate/components/crypto_donate_panel.js deleted file mode 100644 index 23b401c7f..000000000 --- a/app/soapbox/features/crypto_donate/components/crypto_donate_panel.js +++ /dev/null @@ -1,76 +0,0 @@ -import classNames from 'classnames'; -import { List as ImmutableList } from 'immutable'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; - -import Icon from 'soapbox/components/icon'; - -import SiteWallet from './site_wallet'; - -const mapStateToProps = state => { - const addresses = state.getIn(['soapbox', 'cryptoAddresses'], ImmutableList()); - return { - total: addresses.size, - siteTitle: state.getIn(['instance', 'title']), - }; -}; - -export default @connect(mapStateToProps) -class CryptoDonatePanel extends ImmutablePureComponent { - - static propTypes = { - limit: PropTypes.number, - total: PropTypes.number, - } - - static defaultProps = { - limit: 3, - } - - shouldDisplay = () => { - const { limit, total } = this.props; - if (limit === 0 || total === 0) return false; - return true; - } - - render() { - const { limit, total, siteTitle } = this.props; - const more = total - limit; - const hasMore = more > 0; - - if (!this.shouldDisplay()) return null; - - return ( -
-
- - - - -
-
-
- -
- -
- {hasMore && - - } -
- ); - } - -} diff --git a/app/soapbox/features/crypto_donate/components/crypto_donate_panel.tsx b/app/soapbox/features/crypto_donate/components/crypto_donate_panel.tsx new file mode 100644 index 000000000..073852a8b --- /dev/null +++ b/app/soapbox/features/crypto_donate/components/crypto_donate_panel.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; + +import { Text, Widget } from 'soapbox/components/ui'; +import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks'; + +import SiteWallet from './site_wallet'; + +interface ICryptoDonatePanel { + limit: number, +} + +const CryptoDonatePanel: React.FC = ({ limit = 3 }): JSX.Element | null => { + const addresses = useSoapboxConfig().get('cryptoAddresses'); + const siteTitle = useAppSelector((state) => state.instance.title); + + if (limit === 0 || addresses.size === 0) { + return null; + } + + const more = addresses.size - limit; + const hasMore = more > 0; + + return ( + }> + + + + + + + {hasMore && ( + + + + + + )} + + ); +}; + +export default CryptoDonatePanel; diff --git a/app/soapbox/features/crypto_donate/components/crypto_icon.js b/app/soapbox/features/crypto_donate/components/crypto_icon.js deleted file mode 100644 index 950776913..000000000 --- a/app/soapbox/features/crypto_donate/components/crypto_icon.js +++ /dev/null @@ -1,34 +0,0 @@ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; - -const getIcon = ticker => { - try { - return require(`cryptocurrency-icons/svg/color/${ticker.toLowerCase()}.svg`); - } catch { - return require('cryptocurrency-icons/svg/color/generic.svg'); - } -}; - -export default class CryptoIcon extends React.PureComponent { - - static propTypes = { - ticker: PropTypes.string.isRequired, - title: PropTypes.string, - className: PropTypes.string, - } - - render() { - const { ticker, title, className } = this.props; - - return ( -
- {title -
- ); - } - -} diff --git a/app/soapbox/features/crypto_donate/components/crypto_icon.tsx b/app/soapbox/features/crypto_donate/components/crypto_icon.tsx new file mode 100644 index 000000000..28c0535ab --- /dev/null +++ b/app/soapbox/features/crypto_donate/components/crypto_icon.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +/** Get crypto icon URL by ticker symbol, or fall back to generic icon */ +const getIcon = (ticker: string): string => { + try { + return require(`cryptocurrency-icons/svg/color/${ticker.toLowerCase()}.svg`); + } catch { + return require('cryptocurrency-icons/svg/color/generic.svg'); + } +}; + +interface ICryptoIcon { + ticker: string, + title?: string, + className?: string, +} + +const CryptoIcon: React.FC = ({ ticker, title, className }): JSX.Element => { + return ( +
+ {title +
+ ); +}; + +export default CryptoIcon; diff --git a/app/soapbox/features/crypto_donate/components/detailed_crypto_address.js b/app/soapbox/features/crypto_donate/components/detailed_crypto_address.js deleted file mode 100644 index 40df4909d..000000000 --- a/app/soapbox/features/crypto_donate/components/detailed_crypto_address.js +++ /dev/null @@ -1,53 +0,0 @@ -import PropTypes from 'prop-types'; -import QRCode from 'qrcode.react'; -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -import Icon from 'soapbox/components/icon'; -import { CopyableInput } from 'soapbox/features/forms'; - -import { getExplorerUrl } from '../utils/block_explorer'; -import CoinDB from '../utils/coin_db'; - -import CryptoIcon from './crypto_icon'; - -export default class DetailedCryptoAddress extends ImmutablePureComponent { - - static propTypes = { - address: PropTypes.string.isRequired, - ticker: PropTypes.string.isRequired, - note: PropTypes.string, - } - - render() { - const { address, ticker, note } = this.props; - const title = CoinDB.getIn([ticker, 'name']); - const explorerUrl = getExplorerUrl(ticker, address); - - return ( -
-
- -
{title || ticker.toUpperCase()}
-
- {explorerUrl && - - } -
-
- {note &&
{note}
} -
- -
-
- -
-
- ); - } - -} diff --git a/app/soapbox/features/crypto_donate/components/detailed_crypto_address.tsx b/app/soapbox/features/crypto_donate/components/detailed_crypto_address.tsx new file mode 100644 index 000000000..053ea16c7 --- /dev/null +++ b/app/soapbox/features/crypto_donate/components/detailed_crypto_address.tsx @@ -0,0 +1,48 @@ +import QRCode from 'qrcode.react'; +import React from 'react'; + +import Icon from 'soapbox/components/icon'; +import { CopyableInput } from 'soapbox/features/forms'; + +import { getExplorerUrl } from '../utils/block_explorer'; +import { getTitle } from '../utils/coin_db'; + +import CryptoIcon from './crypto_icon'; + +interface IDetailedCryptoAddress { + address: string, + ticker: string, + note?: string, +} + +const DetailedCryptoAddress: React.FC = ({ address, ticker, note }): JSX.Element => { + const title = getTitle(ticker); + const explorerUrl = getExplorerUrl(ticker, address); + + return ( +
+
+ +
{title || ticker.toUpperCase()}
+
+ {explorerUrl && + + } +
+
+ {note &&
{note}
} +
+ +
+
+ +
+
+ ); +}; + +export default DetailedCryptoAddress; diff --git a/app/soapbox/features/crypto_donate/components/site_wallet.js b/app/soapbox/features/crypto_donate/components/site_wallet.js deleted file mode 100644 index 21acffe4e..000000000 --- a/app/soapbox/features/crypto_donate/components/site_wallet.js +++ /dev/null @@ -1,67 +0,0 @@ -import { trimStart } from 'lodash'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; - -import CryptoAddress from './crypto_address'; - -const normalizeAddress = address => { - return address.update('ticker', '', ticker => { - return trimStart(ticker, '$').toLowerCase(); - }); -}; - -const normalizeAddresses = addresses => addresses.map(normalizeAddress); - -const makeGetCoinList = () => { - return createSelector( - [(addresses, limit) => typeof limit === 'number' ? addresses.take(limit) : addresses], - addresses => normalizeAddresses(addresses), - ); -}; - -const makeMapStateToProps = () => { - const getCoinList = makeGetCoinList(); - - const mapStateToProps = (state, ownProps) => { - // Address example: - // {"ticker": "btc", "address": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n", "note": "This is our main address"} - const addresses = state.getIn(['soapbox', 'cryptoAddresses']); - const { limit } = ownProps; - - return { - coinList: getCoinList(addresses, limit), - }; - }; - - return mapStateToProps; -}; - -export default @connect(makeMapStateToProps) -class CoinList extends ImmutablePureComponent { - - static propTypes = { - coinList: ImmutablePropTypes.list, - limit: PropTypes.number, - } - - render() { - const { coinList } = this.props; - if (!coinList) return null; - - return ( -
- {coinList.map(coin => ( - - ))} -
- ); - } - -} diff --git a/app/soapbox/features/crypto_donate/components/site_wallet.tsx b/app/soapbox/features/crypto_donate/components/site_wallet.tsx new file mode 100644 index 000000000..af5964796 --- /dev/null +++ b/app/soapbox/features/crypto_donate/components/site_wallet.tsx @@ -0,0 +1,45 @@ +import { trimStart } from 'lodash'; +import React from 'react'; + +import { Stack } from 'soapbox/components/ui'; +import { useSoapboxConfig } from 'soapbox/hooks'; + +import CryptoAddress from './crypto_address'; + +import type { Map as ImmutableMap, List as ImmutableList } from 'immutable'; + +type Address = ImmutableMap; + +// Address example: +// {"ticker": "btc", "address": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n", "note": "This is our main address"} +const normalizeAddress = (address: Address): Address => { + return address.update('ticker', '', ticker => { + return trimStart(ticker, '$').toLowerCase(); + }); +}; + +interface ISiteWallet { + limit?: number, +} + +const SiteWallet: React.FC = ({ limit }): JSX.Element => { + const addresses: ImmutableList
= + useSoapboxConfig().get('cryptoAddresses').map(normalizeAddress); + + const coinList = typeof limit === 'number' ? addresses.take(limit) : addresses; + + return ( + + {coinList.map(coin => ( + + ))} + + ); +}; + +export default SiteWallet; diff --git a/app/soapbox/features/crypto_donate/index.js b/app/soapbox/features/crypto_donate/index.js deleted file mode 100644 index 747c4df2d..000000000 --- a/app/soapbox/features/crypto_donate/index.js +++ /dev/null @@ -1,64 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; - -import Accordion from 'soapbox/features/ui/components/accordion'; - -import Column from '../ui/components/column'; - -import SiteWallet from './components/site_wallet'; - -const messages = defineMessages({ - heading: { id: 'column.crypto_donate', defaultMessage: 'Donate Cryptocurrency' }, -}); - -const mapStateToProps = state => ({ - siteTitle: state.getIn(['instance', 'title']), -}); - -export default @connect(mapStateToProps) -@injectIntl -class CryptoDonate extends ImmutablePureComponent { - - static propTypes = { - intl: PropTypes.object.isRequired, - }; - - state = { - explanationBoxExpanded: true, - } - - toggleExplanationBox = (setting) => { - this.setState({ explanationBoxExpanded: setting }); - } - - render() { - const { intl, siteTitle } = this.props; - const { explanationBoxExpanded } = this.state; - - return ( - -
-
- } - expanded={explanationBoxExpanded} - onToggle={this.toggleExplanationBox} - > - - - -
- -
-
- ); - } - -} diff --git a/app/soapbox/features/crypto_donate/index.tsx b/app/soapbox/features/crypto_donate/index.tsx new file mode 100644 index 000000000..86de895c9 --- /dev/null +++ b/app/soapbox/features/crypto_donate/index.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; + +import { Column } from 'soapbox/components/ui'; +import Accordion from 'soapbox/features/ui/components/accordion'; +import { useAppSelector } from 'soapbox/hooks'; + +import SiteWallet from './components/site_wallet'; + +const messages = defineMessages({ + heading: { id: 'column.crypto_donate', defaultMessage: 'Donate Cryptocurrency' }, +}); + +const CryptoDonate: React.FC = (): JSX.Element => { + const [explanationBoxExpanded, toggleExplanationBox] = useState(true); + const siteTitle = useAppSelector((state) => state.instance.title); + const intl = useIntl(); + + return ( + +
+
+ } + expanded={explanationBoxExpanded} + onToggle={toggleExplanationBox} + > + + + +
+ +
+
+ ); +}; + +export default CryptoDonate; diff --git a/app/soapbox/features/crypto_donate/utils/block_explorer.js b/app/soapbox/features/crypto_donate/utils/block_explorer.js deleted file mode 100644 index 0929250bf..000000000 --- a/app/soapbox/features/crypto_donate/utils/block_explorer.js +++ /dev/null @@ -1,7 +0,0 @@ -import blockExplorers from './block_explorers.json'; - -export const getExplorerUrl = (ticker, address) => { - const template = blockExplorers[ticker]; - if (!template) return false; - return template.replace('{address}', address); -}; diff --git a/app/soapbox/features/crypto_donate/utils/block_explorer.ts b/app/soapbox/features/crypto_donate/utils/block_explorer.ts new file mode 100644 index 000000000..c02ea4abc --- /dev/null +++ b/app/soapbox/features/crypto_donate/utils/block_explorer.ts @@ -0,0 +1,9 @@ +import blockExplorers from './block_explorers.json'; + +type BlockExplorers = Record; + +export const getExplorerUrl = (ticker: string, address: string): string | null => { + const template = (blockExplorers as BlockExplorers)[ticker]; + if (!template) return null; + return template.replace('{address}', address); +}; diff --git a/app/soapbox/features/crypto_donate/utils/coin_db.js b/app/soapbox/features/crypto_donate/utils/coin_db.ts similarity index 51% rename from app/soapbox/features/crypto_donate/utils/coin_db.js rename to app/soapbox/features/crypto_donate/utils/coin_db.ts index 3ca2d3a18..582ace540 100644 --- a/app/soapbox/features/crypto_donate/utils/coin_db.js +++ b/app/soapbox/features/crypto_donate/utils/coin_db.ts @@ -5,3 +5,9 @@ import manifestMap from './manifest_map'; // All this does is converts the result from manifest_map.js into an ImmutableMap const coinDB = fromJS(manifestMap); export default coinDB; + +/** Get title from CoinDB based on ticker symbol */ +export const getTitle = (ticker: string): string => { + const title = coinDB.getIn([ticker, 'name']); + return typeof title === 'string' ? title : ''; +}; diff --git a/app/soapbox/features/forms/index.js b/app/soapbox/features/forms/index.js index 7c2268794..5cc640a25 100644 --- a/app/soapbox/features/forms/index.js +++ b/app/soapbox/features/forms/index.js @@ -303,7 +303,7 @@ export class CopyableInput extends ImmutablePureComponent { return (
-
diff --git a/app/soapbox/features/ui/components/trends-panel.tsx b/app/soapbox/features/ui/components/trends-panel.tsx index e0f9684e3..4fa4a7b26 100644 --- a/app/soapbox/features/ui/components/trends-panel.tsx +++ b/app/soapbox/features/ui/components/trends-panel.tsx @@ -3,11 +3,11 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { useDispatch } from 'react-redux'; +import { Widget } from 'soapbox/components/ui'; import { useAppSelector } from 'soapbox/hooks'; import { fetchTrends } from '../../../actions/trends'; import Hashtag from '../../../components/hashtag'; -import { Stack, Text } from '../../../components/ui'; interface ITrendsPanel { limit: number @@ -35,17 +35,11 @@ const TrendsPanel = ({ limit }: ITrendsPanel) => { } return ( - - - - - - - {sortedTrends.map((hashtag: ImmutableMap) => ( - - ))} - - + }> + {sortedTrends.map((hashtag: ImmutableMap) => ( + + ))} + ); }; diff --git a/app/soapbox/features/ui/components/who-to-follow-panel.tsx b/app/soapbox/features/ui/components/who-to-follow-panel.tsx index d55f04585..cea616407 100644 --- a/app/soapbox/features/ui/components/who-to-follow-panel.tsx +++ b/app/soapbox/features/ui/components/who-to-follow-panel.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; -import { Stack, Text } from 'soapbox/components/ui'; +import { Widget } from 'soapbox/components/ui'; import { useAppSelector } from 'soapbox/hooks'; import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestions'; @@ -37,24 +37,18 @@ const WhoToFollowPanel = ({ limit }: IWhoToFollowPanel) => { } return ( - - - - - - - {suggestionsToRender.map((suggestion: ImmutableMap) => ( - , but it isn't - id={suggestion.get('account')} - actionIcon={require('@tabler/icons/icons/x.svg')} - actionTitle={intl.formatMessage(messages.dismissSuggestion)} - onActionClick={handleDismiss} - /> - ))} - - + }> + {suggestionsToRender.map((suggestion: ImmutableMap) => ( + , but it isn't + id={suggestion.get('account')} + actionIcon={require('@tabler/icons/icons/x.svg')} + actionTitle={intl.formatMessage(messages.dismissSuggestion)} + onActionClick={handleDismiss} + /> + ))} + ); }; diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 0d516fe2f..d2fd18704 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -102,7 +102,7 @@ import { // AwaitingApproval, // Reports, // ModerationLog, - // CryptoDonate, + CryptoDonate, // ScheduledStatuses, // UserIndex, // FederationRestrictions, @@ -347,8 +347,8 @@ class SwitchingColumnsArea extends React.PureComponent { - {/* + {/* */} diff --git a/app/soapbox/pages/home_page.js b/app/soapbox/pages/home_page.js index 65d581d28..e8a12a5d8 100644 --- a/app/soapbox/pages/home_page.js +++ b/app/soapbox/pages/home_page.js @@ -10,6 +10,7 @@ import { WhoToFollowPanel, TrendsPanel, SignUpPanel, + CryptoDonatePanel, } from 'soapbox/features/ui/util/async-components'; // import GroupSidebarPanel from '../features/groups/sidebar_panel'; import { getFeatures } from 'soapbox/utils/features'; @@ -31,10 +32,9 @@ const mapStateToProps = state => { me, account: state.getIn(['accounts', me]), showFundingPanel: hasPatron, - showCryptoDonatePanel: hasCrypto && cryptoLimit > 0, + hasCrypto, cryptoLimit, - showTrendsPanel: features.trends, - showWhoToFollowPanel: features.suggestions, + features, }; }; @@ -47,7 +47,7 @@ class HomePage extends ImmutablePureComponent { } render() { - const { me, children, account, showTrendsPanel, showWhoToFollowPanel } = this.props; + const { me, children, account, features, hasCrypto, cryptoLimit } = this.props; const acct = account ? account.get('acct') : ''; @@ -80,17 +80,22 @@ class HomePage extends ImmutablePureComponent { {!me && ( - {Component => } + {Component => } )} - {showTrendsPanel && ( + {features.trends && ( - {Component => } + {Component => } )} - {showWhoToFollowPanel && ( + {hasCrypto && cryptoLimit > 0 && ( + + {Component => } + + )} + {features.suggestions && ( - {Component => } + {Component => } )} diff --git a/package.json b/package.json index 42bc83259..ff9568cd7 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@types/escape-html": "^1.0.1", "@types/http-link-header": "^1.0.3", "@types/lodash": "^4.14.180", + "@types/qrcode.react": "^1.0.2", "@types/react-helmet": "^6.1.5", "@types/react-router-dom": "^5.3.3", "@types/react-toggle": "^4.0.3", diff --git a/tsconfig.json b/tsconfig.json index 6af5fd813..beff5f647 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "jsx": "react", "allowJs": true, "moduleResolution": "node", + "resolveJsonModule": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "typeRoots": [ "./types", "./node_modules/@types"] diff --git a/yarn.lock b/yarn.lock index 146c677b1..4c7708e0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2095,6 +2095,13 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== +"@types/qrcode.react@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/qrcode.react/-/qrcode.react-1.0.2.tgz#f892432cc41b5dac52e3ca8873b717c8bfea6002" + integrity sha512-I9Oq5Cjlkgy3Tw7krCnCXLw2/zMhizkTere49OOcta23tkvH0xBTP0yInimTh0gstLRtb8Ki9NZVujE5UI6ffQ== + dependencies: + "@types/react" "*" + "@types/react-helmet@^6.1.5": version "6.1.5" resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.5.tgz#35f89a6b1646ee2bc342a33a9a6c8777933f9083"