diff --git a/app/soapbox/__fixtures__/spinster-soapbox.json b/app/soapbox/__fixtures__/spinster-soapbox.json new file mode 100644 index 000000000..8f3ed63fa --- /dev/null +++ b/app/soapbox/__fixtures__/spinster-soapbox.json @@ -0,0 +1,119 @@ +{ + "allowedEmoji": [ + "👍", + "❤️", + "😆", + "😮", + "😢", + "😡", + "😩" + ], + "brandColor": "#990099", + "copyright": "♡2021. Copying is an act of love. Please copy and share.", + "cryptoAddresses": [ + { + "address": "bc1qv7lk3algpfg4zpyuhvxfm0uza9ck4parz3y3l5", + "note": "", + "ticker": "btc" + }, + { + "address": "0xadc66B63bFee7677CD27CFb81b16a8860f1A1226", + "note": "", + "ticker": "eth" + }, + { + "address": "DSf7UmRf7DGGsjh4QYhzQaqtjJMTXZ8k79", + "note": "", + "ticker": "doge" + }, + { + "address": "ltc1q642pnkuvw0gpuuvddw6vafvl9hhp3efyl9mnqz", + "note": "", + "ticker": "ltc" + }, + { + "address": "t1faHDsoa4bd3pGaLjaU7DiuUtBPzbnEEse", + "note": "", + "ticker": "zec" + }, + { + "address": "XchTLkcSMsDoZGESwr4tqtxSU5dideAZVQ", + "note": "", + "ticker": "dash" + }, + { + "address": "bitcoincash:qp8f80z27294phmhdk55yf05p3f0tkxl4v9r2aavw5", + "note": "", + "ticker": "bch" + } + ], + "cryptoDonatePanel": { + "limit": 1 + }, + "customCss": [ + "/instance/spinster.css" + ], + "defaultSettings": { + "autoPlayGif": false, + "themeMode": "light" + }, + "extensions": { + "patron": { + "enabled": true + } + }, + "logo": "https://spinster.xyz/instance/images/spinster-logo.svg", + "navlinks": { + "homeFooter": [ + { + "title": "About", + "url": "/about" + }, + { + "title": "Terms of Service", + "url": "/about/tos" + }, + { + "title": "Privacy Policy", + "url": "/about/privacy" + }, + { + "title": "DMCA", + "url": "/about/dmca" + }, + { + "title": "Source Code", + "url": "/about#opensource" + } + ] + }, + "promoPanel": { + "items": [ + { + "icon": "shopping-basket", + "text": "Buy Spinster Merch", + "url": "https://shop.4w.pub/collections/spinster" + }, + { + "icon": "eye-slash", + "text": "Privacy Guide", + "url": "https://4w.pub/your-guide-to-spinster-privacy-options/" + }, + { + "icon": "question-circle", + "text": "Spinster FAQs", + "url": "https://spinster.xyz/about#faqs" + }, + { + "icon": "bug", + "text": "Report a Bug", + "url": "https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/" + }, + { + "icon": "fediverse", + "text": "About the Fediverse", + "url": "https://jointhefedi.com/" + } + ] + } +} diff --git a/app/soapbox/features/ui/components/promo_panel.js b/app/soapbox/features/ui/components/promo_panel.js deleted file mode 100644 index 1d152be56..000000000 --- a/app/soapbox/features/ui/components/promo_panel.js +++ /dev/null @@ -1,41 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; - -import { getSettings } from 'soapbox/actions/settings'; -import { getSoapboxConfig } from 'soapbox/actions/soapbox'; -import Icon from 'soapbox/components/icon'; - -const mapStateToProps = state => ({ - promoItems: getSoapboxConfig(state).getIn(['promoPanel', 'items']), - locale: getSettings(state).get('locale'), -}); - -export default @connect(mapStateToProps) -class PromoPanel extends React.PureComponent { - - static propTypes = { - locale: PropTypes.string, - promoItems: ImmutablePropTypes.list, - } - - render() { - const { locale, promoItems } = this.props; - if (!promoItems || promoItems.isEmpty()) return null; - - return ( -
-
- {promoItems.map((item, i) => - ( - - {item.getIn(['textLocales', locale]) || item.get('text')} - ), - )} -
-
- ); - } - -} diff --git a/app/soapbox/features/ui/components/promo_panel.tsx b/app/soapbox/features/ui/components/promo_panel.tsx new file mode 100644 index 000000000..5a0b9a24d --- /dev/null +++ b/app/soapbox/features/ui/components/promo_panel.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import Icon from 'soapbox/components/icon'; +import { Widget, Stack, Text } from 'soapbox/components/ui'; +import { useAppSelector, useSettings, useSoapboxConfig } from 'soapbox/hooks'; + +const PromoPanel: React.FC = () => { + const { promoPanel } = useSoapboxConfig(); + const settings = useSettings(); + + const siteTitle = useAppSelector(state => state.instance.title); + const promoItems = promoPanel.get('items'); + const locale = settings.get('locale'); + + if (!promoItems || promoItems.isEmpty()) return null; + + return ( + + + {promoItems.map((item, i) => ( + + + + {item.textLocales.get(locale) || item.text} + + + ))} + + + ); +}; + +export default PromoPanel; diff --git a/app/soapbox/normalizers/soapbox/__tests__/soapbox_config-test.js b/app/soapbox/normalizers/soapbox/__tests__/soapbox_config-test.js index f0a021586..65c4291f8 100644 --- a/app/soapbox/normalizers/soapbox/__tests__/soapbox_config-test.js +++ b/app/soapbox/normalizers/soapbox/__tests__/soapbox_config-test.js @@ -27,4 +27,11 @@ describe('normalizeSoapboxConfig()', () => { expect(ImmutableRecord.isRecord(result.cryptoAddresses.get(0))).toBe(true); expect(result.toJS()).toMatchObject(expected); }); + + it('normalizes promoPanel', () => { + const result = normalizeSoapboxConfig(require('soapbox/__fixtures__/spinster-soapbox.json')); + expect(ImmutableRecord.isRecord(result.promoPanel)).toBe(true); + expect(ImmutableRecord.isRecord(result.promoPanel.items.get(0))).toBe(true); + expect(result.promoPanel.items.get(2).icon).toBe('question-circle'); + }); }); diff --git a/app/soapbox/normalizers/soapbox/soapbox_config.ts b/app/soapbox/normalizers/soapbox/soapbox_config.ts index f2e8cfef8..e0f2cb4b3 100644 --- a/app/soapbox/normalizers/soapbox/soapbox_config.ts +++ b/app/soapbox/normalizers/soapbox/soapbox_config.ts @@ -61,6 +61,11 @@ export const PromoPanelItemRecord = ImmutableRecord({ icon: '', text: '', url: '', + textLocales: ImmutableMap(), +}); + +export const PromoPanelRecord = ImmutableRecord({ + items: ImmutableList(), }); export const FooterItemRecord = ImmutableRecord({ @@ -86,9 +91,7 @@ export const SoapboxConfigRecord = ImmutableRecord({ defaultSettings: ImmutableMap(), extensions: ImmutableMap(), greentext: false, - promoPanel: ImmutableMap({ - items: ImmutableList(), - }), + promoPanel: PromoPanelRecord(), navlinks: ImmutableMap({ homeFooter: ImmutableList(), }), @@ -160,12 +163,19 @@ const maybeAddMissingColors = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMa return soapboxConfig.set('colors', missing.mergeDeep(colors)); }; +const normalizePromoPanel = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => { + const promoPanel = PromoPanelRecord(soapboxConfig.get('promoPanel')); + const items = promoPanel.items.map(PromoPanelItemRecord); + return soapboxConfig.set('promoPanel', promoPanel.set('items', items)); +}; + export const normalizeSoapboxConfig = (soapboxConfig: Record) => { return SoapboxConfigRecord( ImmutableMap(fromJS(soapboxConfig)).withMutations(soapboxConfig => { normalizeBrandColor(soapboxConfig); normalizeAccentColor(soapboxConfig); normalizeColors(soapboxConfig); + normalizePromoPanel(soapboxConfig); maybeAddMissingColors(soapboxConfig); normalizeCryptoAddresses(soapboxConfig); }), diff --git a/app/soapbox/pages/home_page.js b/app/soapbox/pages/home_page.js index e9b6630aa..fcff5b1e3 100644 --- a/app/soapbox/pages/home_page.js +++ b/app/soapbox/pages/home_page.js @@ -10,6 +10,7 @@ import { WhoToFollowPanel, TrendsPanel, SignUpPanel, + PromoPanel, CryptoDonatePanel, BirthdayPanel, } from 'soapbox/features/ui/util/async-components'; @@ -94,6 +95,9 @@ class HomePage extends ImmutablePureComponent { {Component => } )} + + {Component => } + {features.birthdays && ( {Component => }